1483 lines
39 KiB
C
1483 lines
39 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
|
|
#include <isc/buffer.h>
|
|
#include <isc/commandline.h>
|
|
#include <isc/fips.h>
|
|
#include <isc/lex.h>
|
|
#include <isc/mem.h>
|
|
|
|
#include <dns/callbacks.h>
|
|
#include <dns/dnssec.h>
|
|
#include <dns/fixedname.h>
|
|
#include <dns/keymgr.h>
|
|
#include <dns/keyvalues.h>
|
|
#include <dns/rdataclass.h>
|
|
#include <dns/rdatalist.h>
|
|
#include <dns/rdataset.h>
|
|
#include <dns/time.h>
|
|
#include <dns/ttl.h>
|
|
|
|
#include "dnssectool.h"
|
|
|
|
const char *program = "dnssec-ksr";
|
|
|
|
/*
|
|
* Infrastructure
|
|
*/
|
|
static isc_log_t *lctx = NULL;
|
|
static isc_mem_t *mctx = NULL;
|
|
const char *engine = NULL;
|
|
/*
|
|
* The domain we are working on
|
|
*/
|
|
static const char *namestr = NULL;
|
|
static dns_fixedname_t fname;
|
|
static dns_name_t *name = NULL;
|
|
/*
|
|
* KSR context
|
|
*/
|
|
struct ksr_ctx {
|
|
const char *policy;
|
|
const char *configfile;
|
|
const char *file;
|
|
const char *keydir;
|
|
dns_keystore_t *keystore;
|
|
isc_stdtime_t now;
|
|
isc_stdtime_t start;
|
|
isc_stdtime_t end;
|
|
bool setstart;
|
|
bool setend;
|
|
/* keygen */
|
|
bool ksk;
|
|
dns_ttl_t ttl;
|
|
dns_secalg_t alg;
|
|
int size;
|
|
time_t lifetime;
|
|
time_t parentpropagation;
|
|
time_t propagation;
|
|
time_t publishsafety;
|
|
time_t retiresafety;
|
|
time_t sigrefresh;
|
|
time_t sigvalidity;
|
|
time_t signdelay;
|
|
time_t ttlds;
|
|
time_t ttlsig;
|
|
};
|
|
typedef struct ksr_ctx ksr_ctx_t;
|
|
|
|
/*
|
|
* These are set here for backwards compatibility.
|
|
* They are raised to 2048 in FIPS mode.
|
|
*/
|
|
static int min_rsa = 1024;
|
|
static int min_dh = 128;
|
|
|
|
#define KSR_LINESIZE 1500 /* should be long enough for any DNSKEY record */
|
|
#define DATETIME_INDEX 25
|
|
|
|
#define MAXWIRE (64 * 1024)
|
|
|
|
#define STR(t) ((t).value.as_textregion.base)
|
|
|
|
#define READLINE(lex, opt, token)
|
|
|
|
#define NEXTTOKEN(lex, opt, token) \
|
|
{ \
|
|
ret = isc_lex_gettoken(lex, opt, token); \
|
|
if (ret != ISC_R_SUCCESS) \
|
|
goto cleanup; \
|
|
}
|
|
|
|
#define BADTOKEN() \
|
|
{ \
|
|
ret = ISC_R_UNEXPECTEDTOKEN; \
|
|
goto cleanup; \
|
|
}
|
|
|
|
#define CHECK(r) \
|
|
ret = (r); \
|
|
if (ret != ISC_R_SUCCESS) { \
|
|
goto fail; \
|
|
}
|
|
|
|
isc_bufferlist_t cleanup_list = ISC_LIST_INITIALIZER;
|
|
|
|
static void
|
|
usage(int ret) {
|
|
fprintf(stderr, "Usage:\n");
|
|
fprintf(stderr, " %s options [options] <command> <zone>\n", program);
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "Version: %s\n", PACKAGE_VERSION);
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "Options:\n");
|
|
fprintf(stderr, " -E <engine>: name of an OpenSSL engine to use\n");
|
|
fprintf(stderr, " -e <date/offset>: end date\n");
|
|
fprintf(stderr, " -F: FIPS mode\n");
|
|
fprintf(stderr, " -f: KSR file to sign\n");
|
|
fprintf(stderr, " -i <date/offset>: start date\n");
|
|
fprintf(stderr, " -K <directory>: key directory\n");
|
|
fprintf(stderr, " -k <policy>: name of a DNSSEC policy\n");
|
|
fprintf(stderr, " -l <file>: file with dnssec-policy config\n");
|
|
fprintf(stderr, " -h: print usage and exit\n");
|
|
fprintf(stderr, " -V: print version information\n");
|
|
fprintf(stderr, " -v <level>: set verbosity level\n");
|
|
fprintf(stderr, "\n");
|
|
fprintf(stderr, "Commands:\n");
|
|
fprintf(stderr, " keygen: pregenerate ZSKs\n");
|
|
fprintf(stderr, " request: create a Key Signing Request (KSR)\n");
|
|
fprintf(stderr, " sign: sign a KSR, creating a Signed Key "
|
|
"Response (SKR)\n");
|
|
exit(ret);
|
|
}
|
|
|
|
static isc_stdtime_t
|
|
between(isc_stdtime_t t, isc_stdtime_t start, isc_stdtime_t end) {
|
|
isc_stdtime_t r = end;
|
|
if (t > 0 && t > start && t < end) {
|
|
r = t;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
static void
|
|
checkparams(ksr_ctx_t *ksr, const char *command) {
|
|
if (ksr->configfile == NULL) {
|
|
fatal("%s requires a configuration file", command);
|
|
}
|
|
if (ksr->policy == NULL) {
|
|
fatal("%s requires a dnssec-policy", command);
|
|
}
|
|
if (!ksr->setend) {
|
|
fatal("%s requires an end date", command);
|
|
}
|
|
if (!ksr->setstart) {
|
|
ksr->start = ksr->now;
|
|
}
|
|
if (ksr->keydir == NULL) {
|
|
ksr->keydir = ".";
|
|
}
|
|
}
|
|
|
|
static void
|
|
getkasp(ksr_ctx_t *ksr, dns_kasp_t **kasp) {
|
|
cfg_parser_t *parser = NULL;
|
|
cfg_obj_t *config = NULL;
|
|
|
|
RUNTIME_CHECK(cfg_parser_create(mctx, lctx, &parser) == ISC_R_SUCCESS);
|
|
if (cfg_parse_file(parser, ksr->configfile, &cfg_type_namedconf,
|
|
&config) != ISC_R_SUCCESS)
|
|
{
|
|
fatal("unable to load dnssec-policy '%s' from '%s'",
|
|
ksr->policy, ksr->configfile);
|
|
}
|
|
kasp_from_conf(config, mctx, lctx, ksr->policy, ksr->keydir, engine,
|
|
kasp);
|
|
if (*kasp == NULL) {
|
|
fatal("failed to load dnssec-policy '%s'", ksr->policy);
|
|
}
|
|
if (ISC_LIST_EMPTY(dns_kasp_keys(*kasp))) {
|
|
fatal("dnssec-policy '%s' has no keys configured", ksr->policy);
|
|
}
|
|
cfg_obj_destroy(parser, &config);
|
|
cfg_parser_destroy(&parser);
|
|
}
|
|
|
|
static int
|
|
keyalgtag_cmp(const void *k1, const void *k2) {
|
|
dns_dnsseckey_t **key1 = (dns_dnsseckey_t **)k1;
|
|
dns_dnsseckey_t **key2 = (dns_dnsseckey_t **)k2;
|
|
if (dst_key_alg((*key1)->key) < dst_key_alg((*key2)->key)) {
|
|
return -1;
|
|
} else if (dst_key_alg((*key1)->key) > dst_key_alg((*key2)->key)) {
|
|
return 1;
|
|
} else if (dst_key_id((*key1)->key) < dst_key_id((*key2)->key)) {
|
|
return -1;
|
|
} else if (dst_key_id((*key1)->key) > dst_key_id((*key2)->key)) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
get_dnskeys(ksr_ctx_t *ksr, dns_dnsseckeylist_t *keys) {
|
|
dns_dnsseckeylist_t keys_read;
|
|
dns_dnsseckey_t **keys_sorted;
|
|
int i = 0, n = 0;
|
|
isc_result_t ret;
|
|
|
|
ISC_LIST_INIT(*keys);
|
|
ISC_LIST_INIT(keys_read);
|
|
ret = dns_dnssec_findmatchingkeys(name, NULL, ksr->keydir, NULL,
|
|
ksr->now, mctx, &keys_read);
|
|
if (ret != ISC_R_SUCCESS && ret != ISC_R_NOTFOUND) {
|
|
fatal("failed to load existing keys from %s: %s", ksr->keydir,
|
|
isc_result_totext(ret));
|
|
}
|
|
/* Sort on keytag. */
|
|
for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(keys_read); dk != NULL;
|
|
dk = ISC_LIST_NEXT(dk, link))
|
|
{
|
|
n++;
|
|
}
|
|
keys_sorted = isc_mem_cget(mctx, n, sizeof(dns_dnsseckey_t *));
|
|
for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(keys_read); dk != NULL;
|
|
dk = ISC_LIST_NEXT(dk, link), i++)
|
|
{
|
|
keys_sorted[i] = dk;
|
|
}
|
|
qsort(keys_sorted, n, sizeof(dns_dnsseckey_t *), keyalgtag_cmp);
|
|
while (!ISC_LIST_EMPTY(keys_read)) {
|
|
dns_dnsseckey_t *key = ISC_LIST_HEAD(keys_read);
|
|
ISC_LIST_UNLINK(keys_read, key, link);
|
|
}
|
|
/* Save sorted list in 'keys' */
|
|
for (i = 0; i < n; i++) {
|
|
ISC_LIST_APPEND(*keys, keys_sorted[i], link);
|
|
}
|
|
INSIST(ISC_LIST_EMPTY(keys_read));
|
|
isc_mem_cput(mctx, keys_sorted, n, sizeof(dns_dnsseckey_t *));
|
|
}
|
|
|
|
static void
|
|
setcontext(ksr_ctx_t *ksr, dns_kasp_t *kasp) {
|
|
ksr->parentpropagation = dns_kasp_parentpropagationdelay(kasp);
|
|
ksr->propagation = dns_kasp_zonepropagationdelay(kasp);
|
|
ksr->publishsafety = dns_kasp_publishsafety(kasp);
|
|
ksr->retiresafety = dns_kasp_retiresafety(kasp);
|
|
ksr->sigvalidity = dns_kasp_sigvalidity_dnskey(kasp);
|
|
ksr->sigrefresh = dns_kasp_sigrefresh(kasp);
|
|
ksr->signdelay = dns_kasp_signdelay(kasp);
|
|
ksr->ttl = dns_kasp_dnskeyttl(kasp);
|
|
ksr->ttlds = dns_kasp_dsttl(kasp);
|
|
ksr->ttlsig = dns_kasp_zonemaxttl(kasp, true);
|
|
}
|
|
|
|
static void
|
|
cleanup(dns_dnsseckeylist_t *keys, dns_kasp_t *kasp) {
|
|
while (!ISC_LIST_EMPTY(*keys)) {
|
|
dns_dnsseckey_t *key = ISC_LIST_HEAD(*keys);
|
|
ISC_LIST_UNLINK(*keys, key, link);
|
|
dst_key_free(&key->key);
|
|
dns_dnsseckey_destroy(mctx, &key);
|
|
}
|
|
dns_kasp_detach(&kasp);
|
|
|
|
isc_buffer_t *cbuf = ISC_LIST_HEAD(cleanup_list);
|
|
while (cbuf != NULL) {
|
|
isc_buffer_t *nbuf = ISC_LIST_NEXT(cbuf, link);
|
|
ISC_LIST_UNLINK(cleanup_list, cbuf, link);
|
|
isc_buffer_free(&cbuf);
|
|
cbuf = nbuf;
|
|
}
|
|
}
|
|
|
|
static void
|
|
progress(int p) {
|
|
char c = '*';
|
|
switch (p) {
|
|
case 0:
|
|
c = '.';
|
|
break;
|
|
case 1:
|
|
c = '+';
|
|
break;
|
|
case 2:
|
|
c = '*';
|
|
break;
|
|
case 3:
|
|
c = ' ';
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
(void)putc(c, stderr);
|
|
(void)fflush(stderr);
|
|
}
|
|
|
|
static void
|
|
freerrset(dns_rdataset_t *rdataset) {
|
|
dns_rdatalist_t *rdlist;
|
|
dns_rdata_t *rdata;
|
|
|
|
if (!dns_rdataset_isassociated(rdataset)) {
|
|
return;
|
|
}
|
|
|
|
dns_rdatalist_fromrdataset(rdataset, &rdlist);
|
|
|
|
for (rdata = ISC_LIST_HEAD(rdlist->rdata); rdata != NULL;
|
|
rdata = ISC_LIST_HEAD(rdlist->rdata))
|
|
{
|
|
ISC_LIST_UNLINK(rdlist->rdata, rdata, link);
|
|
isc_mem_put(mctx, rdata, sizeof(*rdata));
|
|
}
|
|
isc_mem_put(mctx, rdlist, sizeof(*rdlist));
|
|
dns_rdataset_disassociate(rdataset);
|
|
}
|
|
|
|
static void
|
|
create_key(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_kasp_key_t *kaspkey,
|
|
dns_dnsseckeylist_t *keys, isc_stdtime_t inception,
|
|
isc_stdtime_t active, isc_stdtime_t *expiration) {
|
|
bool conflict = false;
|
|
bool freekey = false;
|
|
bool show_progress = true;
|
|
char algstr[DNS_SECALG_FORMATSIZE];
|
|
char filename[PATH_MAX + 1];
|
|
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
|
|
dst_key_t *key = NULL;
|
|
int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE);
|
|
isc_buffer_t buf;
|
|
isc_result_t ret;
|
|
isc_stdtime_t prepub;
|
|
uint16_t flags = DNS_KEYOWNER_ZONE;
|
|
|
|
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
|
|
|
|
/* ZSK or KSK? */
|
|
if (ksr->ksk) {
|
|
flags |= DNS_KEYFLAG_KSK;
|
|
}
|
|
|
|
/* Check algorithm and size. */
|
|
dns_secalg_format(ksr->alg, algstr, sizeof(algstr));
|
|
if (!dst_algorithm_supported(ksr->alg)) {
|
|
fatal("unsupported algorithm: %s", algstr);
|
|
}
|
|
INSIST(ksr->size >= 0);
|
|
switch (ksr->alg) {
|
|
case DST_ALG_RSASHA1:
|
|
case DST_ALG_NSEC3RSASHA1:
|
|
if (isc_fips_mode()) {
|
|
/* verify-only in FIPS mode */
|
|
fatal("unsupported algorithm: %s", algstr);
|
|
}
|
|
FALLTHROUGH;
|
|
case DST_ALG_RSASHA256:
|
|
case DST_ALG_RSASHA512:
|
|
if (ksr->size != 0 &&
|
|
(ksr->size < min_rsa || ksr->size > MAX_RSA))
|
|
{
|
|
fatal("RSA key size %d out of range", ksr->size);
|
|
}
|
|
break;
|
|
case DST_ALG_ECDSA256:
|
|
ksr->size = 256;
|
|
break;
|
|
case DST_ALG_ECDSA384:
|
|
ksr->size = 384;
|
|
break;
|
|
case DST_ALG_ED25519:
|
|
ksr->size = 256;
|
|
break;
|
|
case DST_ALG_ED448:
|
|
ksr->size = 456;
|
|
break;
|
|
default:
|
|
show_progress = false;
|
|
break;
|
|
}
|
|
|
|
isc_buffer_init(&buf, filename, sizeof(filename) - 1);
|
|
|
|
/* Check existing keys. */
|
|
for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(*keys); dk != NULL;
|
|
dk = ISC_LIST_NEXT(dk, link))
|
|
{
|
|
isc_stdtime_t act = 0, inact = 0;
|
|
|
|
if (!dns_kasp_key_match(kaspkey, dk)) {
|
|
continue;
|
|
}
|
|
(void)dst_key_gettime(dk->key, DST_TIME_ACTIVATE, &act);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_INACTIVE, &inact);
|
|
/*
|
|
* If this key's activation time is set after the inception
|
|
* time, it is not eligble for the current bundle.
|
|
*/
|
|
if (act > inception) {
|
|
continue;
|
|
}
|
|
/*
|
|
* If this key's inactive time is set before the inception
|
|
* time, it is not eligble for the current bundle.
|
|
*/
|
|
if (inact > 0 && inception >= inact) {
|
|
continue;
|
|
}
|
|
|
|
/* Found matching existing key. */
|
|
if (verbose > 0 && show_progress) {
|
|
fprintf(stderr,
|
|
"Selecting key pair for bundle %s: ", timestr);
|
|
fflush(stderr);
|
|
}
|
|
key = dk->key;
|
|
*expiration = inact;
|
|
goto output;
|
|
}
|
|
|
|
/* No existing keys match. */
|
|
do {
|
|
conflict = false;
|
|
|
|
if (verbose > 0 && show_progress) {
|
|
fprintf(stderr,
|
|
"Generating key pair for bundle %s: ", timestr);
|
|
}
|
|
if (ksr->keystore != NULL && ksr->policy != NULL) {
|
|
ret = dns_keystore_keygen(
|
|
ksr->keystore, name, ksr->policy,
|
|
dns_rdataclass_in, mctx, ksr->alg, ksr->size,
|
|
flags, &key);
|
|
} else if (show_progress) {
|
|
ret = dst_key_generate(name, ksr->alg, ksr->size, 0,
|
|
flags, DNS_KEYPROTO_DNSSEC,
|
|
dns_rdataclass_in, NULL, mctx,
|
|
&key, &progress);
|
|
fflush(stderr);
|
|
} else {
|
|
ret = dst_key_generate(name, ksr->alg, ksr->size, 0,
|
|
flags, DNS_KEYPROTO_DNSSEC,
|
|
dns_rdataclass_in, NULL, mctx,
|
|
&key, NULL);
|
|
}
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("failed to generate key %s/%s: %s\n", namestr,
|
|
algstr, isc_result_totext(ret));
|
|
}
|
|
|
|
/* Do not overwrite an existing key. */
|
|
if (key_collision(key, name, ksr->keydir, mctx,
|
|
dns_kasp_key_tagmin(kaspkey),
|
|
dns_kasp_key_tagmax(kaspkey), NULL))
|
|
{
|
|
conflict = true;
|
|
if (verbose > 0) {
|
|
isc_buffer_clear(&buf);
|
|
ret = dst_key_buildfilename(key, 0, ksr->keydir,
|
|
&buf);
|
|
if (ret == ISC_R_SUCCESS) {
|
|
fprintf(stderr,
|
|
"%s: %s already exists, or "
|
|
"might collide with another "
|
|
"key upon revokation. "
|
|
"Generating a new key\n",
|
|
program, filename);
|
|
}
|
|
}
|
|
dst_key_free(&key);
|
|
}
|
|
} while (conflict);
|
|
|
|
freekey = true;
|
|
|
|
/* Set key timing metadata. */
|
|
prepub = ksr->ttl + ksr->publishsafety + ksr->propagation;
|
|
dst_key_setttl(key, ksr->ttl);
|
|
dst_key_setnum(key, DST_NUM_LIFETIME, ksr->lifetime);
|
|
dst_key_setbool(key, DST_BOOL_KSK, ksr->ksk);
|
|
dst_key_setbool(key, DST_BOOL_ZSK, !ksr->ksk);
|
|
dst_key_settime(key, DST_TIME_CREATED, ksr->now);
|
|
dst_key_settime(key, DST_TIME_PUBLISH, (active - prepub));
|
|
dst_key_settime(key, DST_TIME_ACTIVATE, active);
|
|
if (ksr->ksk) {
|
|
dns_keymgr_settime_syncpublish(key, kasp,
|
|
(inception == ksr->start));
|
|
}
|
|
|
|
if (ksr->lifetime > 0) {
|
|
isc_stdtime_t inactive = (active + ksr->lifetime);
|
|
isc_stdtime_t remove;
|
|
|
|
if (ksr->ksk) {
|
|
remove = ksr->ttlds + ksr->parentpropagation +
|
|
ksr->retiresafety;
|
|
dst_key_settime(key, DST_TIME_SYNCDELETE, inactive);
|
|
} else {
|
|
remove = ksr->ttlsig + ksr->propagation +
|
|
ksr->retiresafety + ksr->signdelay;
|
|
}
|
|
dst_key_settime(key, DST_TIME_INACTIVE, inactive);
|
|
dst_key_settime(key, DST_TIME_DELETE, (inactive + remove));
|
|
*expiration = inactive;
|
|
} else {
|
|
*expiration = 0;
|
|
}
|
|
|
|
ret = dst_key_tofile(key, options, ksr->keydir);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
char keystr[DST_KEY_FORMATSIZE];
|
|
dst_key_format(key, keystr, sizeof(keystr));
|
|
fatal("failed to write key %s: %s\n", keystr,
|
|
isc_result_totext(ret));
|
|
}
|
|
|
|
output:
|
|
isc_buffer_clear(&buf);
|
|
ret = dst_key_buildfilename(key, 0, NULL, &buf);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("dst_key_buildfilename returned: %s\n",
|
|
isc_result_totext(ret));
|
|
}
|
|
printf("%s\n", filename);
|
|
fflush(stdout);
|
|
if (freekey) {
|
|
dst_key_free(&key);
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_rdata(dns_rdataset_t *rrset) {
|
|
isc_buffer_t target;
|
|
isc_region_t r;
|
|
isc_result_t ret;
|
|
char buf[4096];
|
|
|
|
isc_buffer_init(&target, buf, sizeof(buf));
|
|
ret = dns_rdataset_totext(rrset, name, false, false, &target);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("failed to print rdata");
|
|
}
|
|
isc_buffer_usedregion(&target, &r);
|
|
fprintf(stdout, "%.*s", (int)r.length, (char *)r.base);
|
|
}
|
|
|
|
static isc_stdtime_t
|
|
print_dnskeys(dns_kasp_key_t *kaspkey, dns_ttl_t ttl, dns_dnsseckeylist_t *keys,
|
|
isc_stdtime_t inception, isc_stdtime_t next_inception) {
|
|
char algstr[DNS_SECALG_FORMATSIZE];
|
|
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
|
|
dns_rdatalist_t *rdatalist = NULL;
|
|
dns_rdataset_t rdataset = DNS_RDATASET_INIT;
|
|
isc_result_t ret = ISC_R_SUCCESS;
|
|
isc_stdtime_t next_bundle = next_inception;
|
|
|
|
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
|
|
dns_secalg_format(dns_kasp_key_algorithm(kaspkey), algstr,
|
|
sizeof(algstr));
|
|
|
|
/* Fetch matching key pair. */
|
|
rdatalist = isc_mem_get(mctx, sizeof(*rdatalist));
|
|
dns_rdatalist_init(rdatalist);
|
|
rdatalist->rdclass = dns_rdataclass_in;
|
|
rdatalist->type = dns_rdatatype_dnskey;
|
|
rdatalist->ttl = ttl;
|
|
for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(*keys); dk != NULL;
|
|
dk = ISC_LIST_NEXT(dk, link))
|
|
{
|
|
isc_stdtime_t pub = 0, del = 0;
|
|
|
|
(void)dst_key_gettime(dk->key, DST_TIME_PUBLISH, &pub);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_DELETE, &del);
|
|
|
|
/* Determine next bundle. */
|
|
if (pub > 0 && pub > inception && pub < next_bundle) {
|
|
next_bundle = pub;
|
|
}
|
|
if (del > 0 && del > inception && del < next_bundle) {
|
|
next_bundle = del;
|
|
}
|
|
/* Find matching key. */
|
|
if (!dns_kasp_key_match(kaspkey, dk)) {
|
|
continue;
|
|
}
|
|
if (pub > inception) {
|
|
continue;
|
|
}
|
|
if (del != 0 && inception >= del) {
|
|
continue;
|
|
}
|
|
/* Found matching key pair, add DNSKEY record to RRset. */
|
|
isc_buffer_t buf;
|
|
isc_buffer_t *newbuf = NULL;
|
|
dns_rdata_t *rdata = NULL;
|
|
isc_region_t r;
|
|
unsigned char rdatabuf[DST_KEY_MAXSIZE];
|
|
|
|
rdata = isc_mem_get(mctx, sizeof(*rdata));
|
|
dns_rdata_init(rdata);
|
|
isc_buffer_init(&buf, rdatabuf, sizeof(rdatabuf));
|
|
CHECK(dst_key_todns(dk->key, &buf));
|
|
isc_buffer_usedregion(&buf, &r);
|
|
isc_buffer_allocate(mctx, &newbuf, r.length);
|
|
isc_buffer_putmem(newbuf, r.base, r.length);
|
|
isc_buffer_usedregion(newbuf, &r);
|
|
dns_rdata_fromregion(rdata, dns_rdataclass_in,
|
|
dns_rdatatype_dnskey, &r);
|
|
ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
|
|
ISC_LIST_APPEND(cleanup_list, newbuf, link);
|
|
isc_buffer_clear(newbuf);
|
|
}
|
|
/* Error if no key pair found. */
|
|
if (ISC_LIST_EMPTY(rdatalist->rdata)) {
|
|
fatal("no %s/%s zsk key pair found for bundle %s", namestr,
|
|
algstr, timestr);
|
|
}
|
|
|
|
/* All good, print DNSKEY RRset. */
|
|
dns_rdatalist_tordataset(rdatalist, &rdataset);
|
|
print_rdata(&rdataset);
|
|
|
|
fail:
|
|
/* Cleanup */
|
|
freerrset(&rdataset);
|
|
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("failed to print %s/%s zsk key pair found for bundle %s",
|
|
namestr, algstr, timestr);
|
|
}
|
|
|
|
return next_bundle;
|
|
}
|
|
|
|
static isc_stdtime_t
|
|
sign_rrset(ksr_ctx_t *ksr, isc_stdtime_t inception, isc_stdtime_t expiration,
|
|
dns_rdataset_t *rrset, dns_dnsseckeylist_t *keys) {
|
|
dns_rdatalist_t *rrsiglist = NULL;
|
|
dns_rdataset_t rrsigset = DNS_RDATASET_INIT;
|
|
isc_result_t ret;
|
|
isc_stdtime_t next_bundle = expiration;
|
|
|
|
UNUSED(ksr);
|
|
|
|
/* Bundle header */
|
|
if (rrset->type == dns_rdatatype_dnskey) {
|
|
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
|
|
char utc[sizeof("YYYYMMDDHHSSMM")];
|
|
isc_buffer_t timebuf;
|
|
isc_buffer_t b;
|
|
isc_region_t r;
|
|
isc_buffer_init(&timebuf, timestr, sizeof(timestr));
|
|
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
|
|
isc_buffer_init(&b, utc, sizeof(utc));
|
|
ret = dns_time32_totext(inception, &b);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("failed to convert bundle time32 to text: %s",
|
|
isc_result_totext(ret));
|
|
}
|
|
isc_buffer_usedregion(&b, &r);
|
|
fprintf(stdout, ";; SignedKeyResponse 1.0 %.*s (%s)\n",
|
|
(int)r.length, r.base, timestr);
|
|
}
|
|
|
|
/* RRset */
|
|
print_rdata(rrset);
|
|
|
|
/* Signatures */
|
|
rrsiglist = isc_mem_get(mctx, sizeof(*rrsiglist));
|
|
dns_rdatalist_init(rrsiglist);
|
|
rrsiglist->rdclass = dns_rdataclass_in;
|
|
rrsiglist->type = dns_rdatatype_rrsig;
|
|
rrsiglist->ttl = rrset->ttl;
|
|
for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(*keys); dk != NULL;
|
|
dk = ISC_LIST_NEXT(dk, link))
|
|
{
|
|
isc_buffer_t buf;
|
|
isc_buffer_t *newbuf = NULL;
|
|
dns_rdata_t rdata = DNS_RDATA_INIT;
|
|
dns_rdata_t *rrsig = NULL;
|
|
isc_region_t rs;
|
|
unsigned char rdatabuf[SIG_FORMATSIZE];
|
|
isc_stdtime_t clockskew = inception - 3600;
|
|
|
|
isc_stdtime_t pub = 0, act = 0, inact = 0, del = 0;
|
|
|
|
/* Determine next bundle. */
|
|
(void)dst_key_gettime(dk->key, DST_TIME_PUBLISH, &pub);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_ACTIVATE, &act);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_INACTIVE, &inact);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_DELETE, &del);
|
|
next_bundle = between(pub, inception, next_bundle);
|
|
next_bundle = between(act, inception, next_bundle);
|
|
next_bundle = between(inact, inception, next_bundle);
|
|
next_bundle = between(del, inception, next_bundle);
|
|
|
|
if (act > inception) {
|
|
continue;
|
|
}
|
|
if (inact != 0 && inception >= inact) {
|
|
continue;
|
|
}
|
|
|
|
rrsig = isc_mem_get(mctx, sizeof(*rrsig));
|
|
dns_rdata_init(rrsig);
|
|
isc_buffer_init(&buf, rdatabuf, sizeof(rdatabuf));
|
|
ret = dns_dnssec_sign(name, rrset, dk->key, &clockskew,
|
|
&expiration, mctx, &buf, &rdata);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("failed to sign KSR");
|
|
}
|
|
isc_buffer_usedregion(&buf, &rs);
|
|
isc_buffer_allocate(mctx, &newbuf, rs.length);
|
|
isc_buffer_putmem(newbuf, rs.base, rs.length);
|
|
isc_buffer_usedregion(newbuf, &rs);
|
|
dns_rdata_fromregion(rrsig, dns_rdataclass_in,
|
|
dns_rdatatype_rrsig, &rs);
|
|
ISC_LIST_APPEND(rrsiglist->rdata, rrsig, link);
|
|
ISC_LIST_APPEND(cleanup_list, newbuf, link);
|
|
isc_buffer_clear(newbuf);
|
|
}
|
|
dns_rdatalist_tordataset(rrsiglist, &rrsigset);
|
|
print_rdata(&rrsigset);
|
|
freerrset(&rrsigset);
|
|
|
|
return next_bundle;
|
|
}
|
|
|
|
/*
|
|
* Create the DNSKEY, CDS, and CDNSKEY records beloing to the KSKs
|
|
* listed in 'keys'.
|
|
*/
|
|
static isc_stdtime_t
|
|
get_keymaterial(ksr_ctx_t *ksr, dns_kasp_t *kasp, isc_stdtime_t inception,
|
|
isc_stdtime_t next_inception, dns_dnsseckeylist_t *keys,
|
|
dns_rdataset_t *dnskeyset, dns_rdataset_t *cdnskeyset,
|
|
dns_rdataset_t *cdsset) {
|
|
dns_kasp_digestlist_t digests = dns_kasp_digests(kasp);
|
|
dns_rdatalist_t *dnskeylist = isc_mem_get(mctx, sizeof(*dnskeylist));
|
|
dns_rdatalist_t *cdnskeylist = isc_mem_get(mctx, sizeof(*cdnskeylist));
|
|
dns_rdatalist_t *cdslist = isc_mem_get(mctx, sizeof(*cdslist));
|
|
isc_result_t ret = ISC_R_SUCCESS;
|
|
isc_stdtime_t next_bundle = next_inception;
|
|
|
|
dns_rdatalist_init(dnskeylist);
|
|
dnskeylist->rdclass = dns_rdataclass_in;
|
|
dnskeylist->type = dns_rdatatype_dnskey;
|
|
dnskeylist->ttl = ksr->ttl;
|
|
|
|
dns_rdatalist_init(cdnskeylist);
|
|
cdnskeylist->rdclass = dns_rdataclass_in;
|
|
cdnskeylist->type = dns_rdatatype_cdnskey;
|
|
cdnskeylist->ttl = ksr->ttl;
|
|
|
|
dns_rdatalist_init(cdslist);
|
|
cdslist->rdclass = dns_rdataclass_in;
|
|
cdslist->type = dns_rdatatype_cds;
|
|
cdslist->ttl = ksr->ttl;
|
|
|
|
for (dns_dnsseckey_t *dk = ISC_LIST_HEAD(*keys); dk != NULL;
|
|
dk = ISC_LIST_NEXT(dk, link))
|
|
{
|
|
bool published = true;
|
|
isc_buffer_t buf;
|
|
isc_buffer_t *newbuf;
|
|
dns_rdata_t *rdata;
|
|
isc_region_t r;
|
|
isc_region_t rcds;
|
|
isc_stdtime_t pub = 0, del = 0;
|
|
unsigned char kskbuf[DST_KEY_MAXSIZE];
|
|
unsigned char cdnskeybuf[DST_KEY_MAXSIZE];
|
|
unsigned char cdsbuf[DNS_DS_BUFFERSIZE];
|
|
|
|
/* KSK */
|
|
(void)dst_key_gettime(dk->key, DST_TIME_PUBLISH, &pub);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_DELETE, &del);
|
|
next_bundle = between(pub, inception, next_bundle);
|
|
next_bundle = between(del, inception, next_bundle);
|
|
|
|
if (pub > inception) {
|
|
published = false;
|
|
}
|
|
if (del != 0 && inception >= del) {
|
|
published = false;
|
|
}
|
|
|
|
if (published) {
|
|
newbuf = NULL;
|
|
rdata = isc_mem_get(mctx, sizeof(*rdata));
|
|
dns_rdata_init(rdata);
|
|
|
|
isc_buffer_init(&buf, kskbuf, sizeof(kskbuf));
|
|
CHECK(dst_key_todns(dk->key, &buf));
|
|
isc_buffer_usedregion(&buf, &r);
|
|
isc_buffer_allocate(mctx, &newbuf, r.length);
|
|
isc_buffer_putmem(newbuf, r.base, r.length);
|
|
isc_buffer_usedregion(newbuf, &r);
|
|
dns_rdata_fromregion(rdata, dns_rdataclass_in,
|
|
dns_rdatatype_dnskey, &r);
|
|
ISC_LIST_APPEND(dnskeylist->rdata, rdata, link);
|
|
ISC_LIST_APPEND(cleanup_list, newbuf, link);
|
|
isc_buffer_clear(newbuf);
|
|
}
|
|
|
|
published = true;
|
|
if (dns_kasp_cdnskey(kasp) || !ISC_LIST_EMPTY(digests)) {
|
|
pub = 0;
|
|
del = 0;
|
|
(void)dst_key_gettime(dk->key, DST_TIME_SYNCPUBLISH,
|
|
&pub);
|
|
(void)dst_key_gettime(dk->key, DST_TIME_SYNCDELETE,
|
|
&del);
|
|
|
|
next_bundle = between(pub, inception, next_bundle);
|
|
next_bundle = between(del, inception, next_bundle);
|
|
|
|
if (pub != 0 && pub > inception) {
|
|
published = false;
|
|
}
|
|
if (del != 0 && inception >= del) {
|
|
published = false;
|
|
}
|
|
} else {
|
|
published = false;
|
|
}
|
|
|
|
if (!published) {
|
|
continue;
|
|
}
|
|
|
|
/* CDNSKEY */
|
|
newbuf = NULL;
|
|
rdata = isc_mem_get(mctx, sizeof(*rdata));
|
|
dns_rdata_init(rdata);
|
|
|
|
isc_buffer_init(&buf, cdnskeybuf, sizeof(cdnskeybuf));
|
|
CHECK(dst_key_todns(dk->key, &buf));
|
|
isc_buffer_usedregion(&buf, &r);
|
|
isc_buffer_allocate(mctx, &newbuf, r.length);
|
|
isc_buffer_putmem(newbuf, r.base, r.length);
|
|
isc_buffer_usedregion(newbuf, &r);
|
|
dns_rdata_fromregion(rdata, dns_rdataclass_in,
|
|
dns_rdatatype_cdnskey, &r);
|
|
if (dns_kasp_cdnskey(kasp)) {
|
|
ISC_LIST_APPEND(cdnskeylist->rdata, rdata, link);
|
|
}
|
|
ISC_LIST_APPEND(cleanup_list, newbuf, link);
|
|
isc_buffer_clear(newbuf);
|
|
|
|
/* CDS */
|
|
for (dns_kasp_digest_t *alg = ISC_LIST_HEAD(digests);
|
|
alg != NULL; alg = ISC_LIST_NEXT(alg, link))
|
|
{
|
|
isc_buffer_t *newbuf2 = NULL;
|
|
dns_rdata_t *rdata2 = NULL;
|
|
dns_rdata_t cds = DNS_RDATA_INIT;
|
|
|
|
rdata2 = isc_mem_get(mctx, sizeof(*rdata2));
|
|
dns_rdata_init(rdata2);
|
|
|
|
CHECK(dns_ds_buildrdata(name, rdata, alg->digest,
|
|
cdsbuf, &cds));
|
|
cds.type = dns_rdatatype_cds;
|
|
dns_rdata_toregion(&cds, &rcds);
|
|
isc_buffer_allocate(mctx, &newbuf2, rcds.length);
|
|
isc_buffer_putmem(newbuf2, rcds.base, rcds.length);
|
|
isc_buffer_usedregion(newbuf2, &rcds);
|
|
dns_rdata_fromregion(rdata2, dns_rdataclass_in,
|
|
dns_rdatatype_cds, &rcds);
|
|
ISC_LIST_APPEND(cdslist->rdata, rdata2, link);
|
|
ISC_LIST_APPEND(cleanup_list, newbuf2, link);
|
|
isc_buffer_clear(newbuf2);
|
|
}
|
|
|
|
if (!dns_kasp_cdnskey(kasp)) {
|
|
isc_mem_put(mctx, rdata, sizeof(*rdata));
|
|
}
|
|
}
|
|
/* All good */
|
|
dns_rdatalist_tordataset(dnskeylist, dnskeyset);
|
|
dns_rdatalist_tordataset(cdnskeylist, cdnskeyset);
|
|
dns_rdatalist_tordataset(cdslist, cdsset);
|
|
|
|
return next_bundle;
|
|
|
|
fail:
|
|
fatal("failed to create KSK/CDS/CDNSKEY");
|
|
return 0;
|
|
}
|
|
|
|
static void
|
|
sign_bundle(ksr_ctx_t *ksr, dns_kasp_t *kasp, isc_stdtime_t inception,
|
|
isc_stdtime_t next_inception, dns_rdatalist_t *zsklist,
|
|
dns_dnsseckeylist_t *keys) {
|
|
isc_stdtime_t expiration = inception + ksr->sigvalidity;
|
|
isc_stdtime_t next_bundle = next_inception;
|
|
dns_rdataset_t zsk;
|
|
|
|
dns_rdataset_init(&zsk);
|
|
dns_rdatalist_tordataset(zsklist, &zsk);
|
|
|
|
while (inception <= next_inception) {
|
|
isc_stdtime_t next_time = next_bundle;
|
|
|
|
/* DNSKEY RRset */
|
|
dns_rdatalist_t *dnskeylist;
|
|
dnskeylist = isc_mem_get(mctx, sizeof(*dnskeylist));
|
|
dns_rdatalist_init(dnskeylist);
|
|
dnskeylist->rdclass = dns_rdataclass_in;
|
|
dnskeylist->type = dns_rdatatype_dnskey;
|
|
dnskeylist->ttl = ksr->ttl;
|
|
|
|
dns_rdataset_t ksk, cdnskey, cds, rrset;
|
|
dns_rdataset_init(&ksk);
|
|
dns_rdataset_init(&cdnskey);
|
|
dns_rdataset_init(&cds);
|
|
dns_rdataset_init(&rrset);
|
|
next_time = get_keymaterial(ksr, kasp, inception, next_time,
|
|
keys, &ksk, &cdnskey, &cds);
|
|
if (next_bundle > next_time) {
|
|
next_bundle = next_time;
|
|
}
|
|
|
|
for (isc_result_t r = dns_rdatalist_first(&ksk);
|
|
r == ISC_R_SUCCESS; r = dns_rdatalist_next(&ksk))
|
|
{
|
|
dns_rdata_t *clone = isc_mem_get(mctx, sizeof(*clone));
|
|
dns_rdata_init(clone);
|
|
dns_rdatalist_current(&ksk, clone);
|
|
ISC_LIST_APPEND(dnskeylist->rdata, clone, link);
|
|
}
|
|
|
|
for (isc_result_t r = dns_rdatalist_first(&zsk);
|
|
r == ISC_R_SUCCESS; r = dns_rdatalist_next(&zsk))
|
|
{
|
|
dns_rdata_t *clone = isc_mem_get(mctx, sizeof(*clone));
|
|
dns_rdata_init(clone);
|
|
dns_rdatalist_current(&zsk, clone);
|
|
ISC_LIST_APPEND(dnskeylist->rdata, clone, link);
|
|
}
|
|
|
|
dns_rdatalist_tordataset(dnskeylist, &rrset);
|
|
next_time = sign_rrset(ksr, inception, expiration, &rrset,
|
|
keys);
|
|
if (next_bundle > next_time) {
|
|
next_bundle = next_time;
|
|
}
|
|
freerrset(&ksk);
|
|
freerrset(&rrset);
|
|
|
|
/* CDNSKEY */
|
|
if (dns_rdataset_count(&cdnskey) > 0) {
|
|
(void)sign_rrset(ksr, inception, expiration, &cdnskey,
|
|
keys);
|
|
}
|
|
freerrset(&cdnskey);
|
|
|
|
/* CDS */
|
|
if (dns_rdataset_count(&cds) > 0) {
|
|
(void)sign_rrset(ksr, inception, expiration, &cds,
|
|
keys);
|
|
}
|
|
freerrset(&cds);
|
|
|
|
/* Next response bundle. */
|
|
inception = expiration - ksr->sigrefresh;
|
|
if (inception > next_bundle) {
|
|
inception = next_bundle;
|
|
}
|
|
expiration = inception + ksr->sigvalidity;
|
|
next_bundle = expiration;
|
|
}
|
|
|
|
freerrset(&zsk);
|
|
}
|
|
|
|
static isc_result_t
|
|
parse_dnskey(isc_lex_t *lex, char *owner, isc_buffer_t *buf, dns_ttl_t *ttl) {
|
|
dns_fixedname_t dfname;
|
|
dns_name_t *dname = NULL;
|
|
dns_rdataclass_t rdclass = dns_rdataclass_in;
|
|
isc_buffer_t b;
|
|
isc_result_t ret;
|
|
isc_token_t token;
|
|
unsigned int opt = ISC_LEXOPT_EOL;
|
|
|
|
isc_lex_setcomments(lex, ISC_LEXCOMMENT_DNSMASTERFILE);
|
|
|
|
/* Read the domain name */
|
|
if (!strcmp(owner, "@")) {
|
|
BADTOKEN();
|
|
}
|
|
|
|
dname = dns_fixedname_initname(&dfname);
|
|
isc_buffer_init(&b, owner, strlen(owner));
|
|
isc_buffer_add(&b, strlen(owner));
|
|
ret = dns_name_fromtext(dname, &b, dns_rootname, 0, NULL);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
return ret;
|
|
}
|
|
if (dns_name_compare(dname, name) != 0) {
|
|
return DNS_R_BADOWNERNAME;
|
|
}
|
|
isc_buffer_clear(&b);
|
|
|
|
/* Read the next word: either TTL, class, or type */
|
|
NEXTTOKEN(lex, opt, &token);
|
|
if (token.type != isc_tokentype_string) {
|
|
BADTOKEN();
|
|
}
|
|
|
|
/* If it's a TTL, read the next one */
|
|
ret = dns_ttl_fromtext(&token.value.as_textregion, ttl);
|
|
if (ret == ISC_R_SUCCESS) {
|
|
NEXTTOKEN(lex, opt, &token);
|
|
}
|
|
if (token.type != isc_tokentype_string) {
|
|
BADTOKEN();
|
|
}
|
|
|
|
/* If it's a class, read the next one */
|
|
ret = dns_rdataclass_fromtext(&rdclass, &token.value.as_textregion);
|
|
if (ret == ISC_R_SUCCESS) {
|
|
NEXTTOKEN(lex, opt, &token);
|
|
}
|
|
if (token.type != isc_tokentype_string) {
|
|
BADTOKEN();
|
|
}
|
|
|
|
/* Must be the type */
|
|
if (strcasecmp(STR(token), "DNSKEY") != 0) {
|
|
BADTOKEN();
|
|
}
|
|
|
|
ret = dns_rdata_fromtext(NULL, rdclass, dns_rdatatype_dnskey, lex, name,
|
|
0, mctx, buf, NULL);
|
|
|
|
cleanup:
|
|
isc_lex_setcomments(lex, 0);
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
keygen(ksr_ctx_t *ksr) {
|
|
dns_kasp_t *kasp = NULL;
|
|
dns_dnsseckeylist_t keys;
|
|
bool noop = true;
|
|
|
|
/* Check parameters */
|
|
checkparams(ksr, "keygen");
|
|
/* Get the policy */
|
|
getkasp(ksr, &kasp);
|
|
/* Get existing keys */
|
|
get_dnskeys(ksr, &keys);
|
|
/* Set context */
|
|
setcontext(ksr, kasp);
|
|
/* Key generation */
|
|
for (dns_kasp_key_t *kk = ISC_LIST_HEAD(dns_kasp_keys(kasp));
|
|
kk != NULL; kk = ISC_LIST_NEXT(kk, link))
|
|
{
|
|
if (dns_kasp_key_ksk(kk) && !ksr->ksk) {
|
|
/* only ZSKs allowed */
|
|
continue;
|
|
} else if (dns_kasp_key_zsk(kk) && ksr->ksk) {
|
|
/* only KSKs allowed */
|
|
continue;
|
|
}
|
|
ksr->alg = dns_kasp_key_algorithm(kk);
|
|
ksr->lifetime = dns_kasp_key_lifetime(kk);
|
|
ksr->keystore = dns_kasp_key_keystore(kk);
|
|
ksr->size = dns_kasp_key_size(kk);
|
|
noop = false;
|
|
|
|
for (isc_stdtime_t inception = ksr->start, act = ksr->start;
|
|
inception < ksr->end; inception += ksr->lifetime)
|
|
{
|
|
create_key(ksr, kasp, kk, &keys, inception, act, &act);
|
|
if (ksr->lifetime == 0) {
|
|
/* unlimited lifetime, but not infinite loop */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (noop) {
|
|
fatal("no keys created for policy '%s'", ksr->policy);
|
|
}
|
|
/* Cleanup */
|
|
cleanup(&keys, kasp);
|
|
}
|
|
|
|
static void
|
|
request(ksr_ctx_t *ksr) {
|
|
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
|
|
dns_dnsseckeylist_t keys;
|
|
dns_kasp_t *kasp = NULL;
|
|
isc_stdtime_t next = 0;
|
|
isc_stdtime_t inception = 0;
|
|
|
|
/* Check parameters */
|
|
checkparams(ksr, "request");
|
|
/* Get the policy */
|
|
getkasp(ksr, &kasp);
|
|
/* Get keys */
|
|
get_dnskeys(ksr, &keys);
|
|
/* Set context */
|
|
setcontext(ksr, kasp);
|
|
/* Create request */
|
|
inception = ksr->start;
|
|
while (inception <= ksr->end) {
|
|
char utc[sizeof("YYYYMMDDHHSSMM")];
|
|
isc_buffer_t b;
|
|
isc_region_t r;
|
|
isc_result_t ret;
|
|
|
|
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
|
|
isc_buffer_init(&b, utc, sizeof(utc));
|
|
ret = dns_time32_totext(inception, &b);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("failed to convert bundle time32 to text: %s",
|
|
isc_result_totext(ret));
|
|
}
|
|
isc_buffer_usedregion(&b, &r);
|
|
fprintf(stdout, ";; KeySigningRequest 1.0 %.*s (%s)\n",
|
|
(int)r.length, r.base, timestr);
|
|
|
|
next = ksr->end + 1;
|
|
for (dns_kasp_key_t *kk = ISC_LIST_HEAD(dns_kasp_keys(kasp));
|
|
kk != NULL; kk = ISC_LIST_NEXT(kk, link))
|
|
{
|
|
/*
|
|
* Output the DNSKEY records for the current bundle
|
|
* that starts at 'inception. The 'next' variable is
|
|
* updated to the start time of the
|
|
* next bundle, determined by the earliest publication
|
|
* or withdrawal of a key that is after the current
|
|
* inception.
|
|
*/
|
|
if (dns_kasp_key_ksk(kk)) {
|
|
/* We only want ZSKs in the request. */
|
|
continue;
|
|
}
|
|
|
|
next = print_dnskeys(kk, ksr->ttl, &keys, inception,
|
|
next);
|
|
}
|
|
inception = next;
|
|
}
|
|
|
|
isc_stdtime_tostring(ksr->now, timestr, sizeof(timestr));
|
|
fprintf(stdout, ";; KeySigningRequest 1.0 generated at %s by %s\n",
|
|
timestr, PACKAGE_VERSION);
|
|
|
|
/* Cleanup */
|
|
cleanup(&keys, kasp);
|
|
}
|
|
|
|
static void
|
|
sign(ksr_ctx_t *ksr) {
|
|
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
|
|
bool have_bundle = false;
|
|
dns_dnsseckeylist_t keys;
|
|
dns_kasp_t *kasp = NULL;
|
|
dns_rdatalist_t *rdatalist = NULL;
|
|
isc_result_t ret;
|
|
isc_stdtime_t inception;
|
|
isc_lex_t *lex = NULL;
|
|
isc_lexspecials_t specials;
|
|
isc_token_t token;
|
|
unsigned int opt = ISC_LEXOPT_EOL;
|
|
|
|
/* Check parameters */
|
|
checkparams(ksr, "sign");
|
|
if (ksr->file == NULL) {
|
|
fatal("'sign' requires a KSR file");
|
|
}
|
|
/* Get the policy */
|
|
getkasp(ksr, &kasp);
|
|
/* Get keys */
|
|
get_dnskeys(ksr, &keys);
|
|
/* Set context */
|
|
setcontext(ksr, kasp);
|
|
/* Sign request */
|
|
inception = ksr->start;
|
|
isc_lex_create(mctx, KSR_LINESIZE, &lex);
|
|
memset(specials, 0, sizeof(specials));
|
|
specials['('] = 1;
|
|
specials[')'] = 1;
|
|
specials['"'] = 1;
|
|
isc_lex_setspecials(lex, specials);
|
|
ret = isc_lex_openfile(lex, ksr->file);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("unable to open KSR file %s: %s", ksr->file,
|
|
isc_result_totext(ret));
|
|
}
|
|
|
|
for (ret = isc_lex_gettoken(lex, opt, &token); ret == ISC_R_SUCCESS;
|
|
ret = isc_lex_gettoken(lex, opt, &token))
|
|
{
|
|
if (token.type != isc_tokentype_string) {
|
|
fatal("bad KSR file %s(%lu): syntax error", ksr->file,
|
|
isc_lex_getsourceline(lex));
|
|
}
|
|
|
|
if (strcmp(STR(token), ";;") == 0) {
|
|
char bundle[KSR_LINESIZE];
|
|
isc_stdtime_t next_inception;
|
|
|
|
CHECK(isc_lex_gettoken(lex, opt, &token));
|
|
if (token.type != isc_tokentype_string ||
|
|
strcmp(STR(token), "KeySigningRequest") != 0)
|
|
{
|
|
fatal("bad KSR file %s(%lu): expected "
|
|
"'KeySigningRequest'",
|
|
ksr->file, isc_lex_getsourceline(lex));
|
|
}
|
|
|
|
CHECK(isc_lex_gettoken(lex, opt, &token));
|
|
if (token.type != isc_tokentype_string) {
|
|
fatal("bad KSR file %s(%lu): expected string",
|
|
ksr->file, isc_lex_getsourceline(lex));
|
|
}
|
|
|
|
if (strcmp(STR(token), "1.0") != 0) {
|
|
fatal("bad KSR file %s(%lu): expected version",
|
|
ksr->file, isc_lex_getsourceline(lex));
|
|
}
|
|
|
|
CHECK(isc_lex_gettoken(lex, opt, &token));
|
|
if (token.type != isc_tokentype_string) {
|
|
fatal("bad KSR file %s(%lu): expected datetime",
|
|
ksr->file, isc_lex_getsourceline(lex));
|
|
}
|
|
if (strcmp(STR(token), "generated") == 0) {
|
|
/* Final bundle */
|
|
goto readline;
|
|
}
|
|
|
|
/* Date and time of bundle */
|
|
sscanf(STR(token), "%s", bundle);
|
|
next_inception = strtotime(bundle, ksr->now, ksr->now,
|
|
NULL);
|
|
|
|
if (have_bundle) {
|
|
/* Sign previous bundle */
|
|
sign_bundle(ksr, kasp, inception,
|
|
next_inception, rdatalist, &keys);
|
|
fprintf(stdout, "\n");
|
|
}
|
|
|
|
/* Start next bundle */
|
|
rdatalist = isc_mem_get(mctx, sizeof(*rdatalist));
|
|
dns_rdatalist_init(rdatalist);
|
|
rdatalist->rdclass = dns_rdataclass_in;
|
|
rdatalist->type = dns_rdatatype_dnskey;
|
|
rdatalist->ttl = ksr->ttl;
|
|
|
|
inception = next_inception;
|
|
have_bundle = true;
|
|
|
|
readline:
|
|
/* Read remainder of header line */
|
|
do {
|
|
ret = isc_lex_gettoken(lex, opt, &token);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("bad KSR file %s(%lu): bad "
|
|
"header (%s)",
|
|
ksr->file,
|
|
isc_lex_getsourceline(lex),
|
|
isc_result_totext(ret));
|
|
}
|
|
} while (token.type != isc_tokentype_eol);
|
|
} else {
|
|
/* Parse DNSKEY */
|
|
dns_ttl_t ttl = ksr->ttl;
|
|
isc_buffer_t buf;
|
|
isc_buffer_t *newbuf = NULL;
|
|
dns_rdata_t *rdata = NULL;
|
|
isc_region_t r;
|
|
u_char rdatabuf[DST_KEY_MAXSIZE];
|
|
|
|
INSIST(rdatalist != NULL);
|
|
|
|
rdata = isc_mem_get(mctx, sizeof(*rdata));
|
|
dns_rdata_init(rdata);
|
|
isc_buffer_init(&buf, rdatabuf, sizeof(rdatabuf));
|
|
ret = parse_dnskey(lex, STR(token), &buf, &ttl);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("bad KSR file %s(%lu): bad DNSKEY (%s)",
|
|
ksr->file, isc_lex_getsourceline(lex),
|
|
isc_result_totext(ret));
|
|
}
|
|
isc_buffer_usedregion(&buf, &r);
|
|
isc_buffer_allocate(mctx, &newbuf, r.length);
|
|
isc_buffer_putmem(newbuf, r.base, r.length);
|
|
isc_buffer_usedregion(newbuf, &r);
|
|
dns_rdata_fromregion(rdata, dns_rdataclass_in,
|
|
dns_rdatatype_dnskey, &r);
|
|
if (rdatalist != NULL && ttl < rdatalist->ttl) {
|
|
rdatalist->ttl = ttl;
|
|
}
|
|
|
|
ISC_LIST_APPEND(rdatalist->rdata, rdata, link);
|
|
ISC_LIST_APPEND(cleanup_list, newbuf, link);
|
|
isc_buffer_clear(newbuf);
|
|
}
|
|
}
|
|
|
|
if (ret != ISC_R_EOF) {
|
|
fatal("bad KSR file %s(%lu): trailing garbage data", ksr->file,
|
|
isc_lex_getsourceline(lex));
|
|
}
|
|
|
|
/* Final bundle */
|
|
if (have_bundle && rdatalist != NULL) {
|
|
sign_bundle(ksr, kasp, inception, ksr->end, rdatalist, &keys);
|
|
} else {
|
|
fatal("bad KSR file %s(%lu): no bundles", ksr->file,
|
|
isc_lex_getsourceline(lex));
|
|
}
|
|
|
|
/* Bundle footer */
|
|
isc_stdtime_tostring(ksr->now, timestr, sizeof(timestr));
|
|
fprintf(stdout, ";; SignedKeyResponse 1.0 generated at %s by %s\n",
|
|
timestr, PACKAGE_VERSION);
|
|
|
|
fail:
|
|
isc_lex_destroy(&lex);
|
|
cleanup(&keys, kasp);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
isc_result_t ret;
|
|
isc_buffer_t buf;
|
|
int ch;
|
|
char *endp;
|
|
bool set_fips_mode = false;
|
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
|
|
OSSL_PROVIDER *fips = NULL, *base = NULL;
|
|
#endif
|
|
ksr_ctx_t ksr = {
|
|
.now = isc_stdtime_now(),
|
|
};
|
|
|
|
isc_mem_create(&mctx);
|
|
|
|
isc_commandline_errprint = false;
|
|
|
|
#define OPTIONS "E:e:Ff:hi:K:k:l:ov:V"
|
|
while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
|
|
switch (ch) {
|
|
case 'E':
|
|
engine = isc_commandline_argument;
|
|
break;
|
|
case 'e':
|
|
ksr.end = strtotime(isc_commandline_argument, ksr.now,
|
|
ksr.now, &ksr.setend);
|
|
break;
|
|
case 'F':
|
|
set_fips_mode = true;
|
|
break;
|
|
case 'f':
|
|
ksr.file = isc_commandline_argument;
|
|
break;
|
|
case 'h':
|
|
usage(0);
|
|
break;
|
|
case 'i':
|
|
ksr.start = strtotime(isc_commandline_argument, ksr.now,
|
|
ksr.now, &ksr.setstart);
|
|
break;
|
|
case 'K':
|
|
ksr.keydir = isc_commandline_argument;
|
|
ret = try_dir(ksr.keydir);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("cannot open directory %s: %s",
|
|
ksr.keydir, isc_result_totext(ret));
|
|
}
|
|
break;
|
|
case 'k':
|
|
ksr.policy = isc_commandline_argument;
|
|
break;
|
|
case 'l':
|
|
ksr.configfile = isc_commandline_argument;
|
|
break;
|
|
case 'o':
|
|
ksr.ksk = true;
|
|
break;
|
|
case 'V':
|
|
version(program);
|
|
break;
|
|
case 'v':
|
|
verbose = strtoul(isc_commandline_argument, &endp, 0);
|
|
if (*endp != '\0') {
|
|
fatal("-v must be followed by a number");
|
|
}
|
|
break;
|
|
default:
|
|
usage(1);
|
|
break;
|
|
}
|
|
}
|
|
argv += isc_commandline_index;
|
|
argc -= isc_commandline_index;
|
|
|
|
if (argc != 2) {
|
|
fatal("must provide a command and zone name");
|
|
}
|
|
|
|
ret = dst_lib_init(mctx, engine);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("could not initialize dst: %s", isc_result_totext(ret));
|
|
}
|
|
|
|
/*
|
|
* After dst_lib_init which will set FIPS mode if requested
|
|
* at build time. The minumums are both raised to 2048.
|
|
*/
|
|
if (isc_fips_mode()) {
|
|
min_rsa = min_dh = 2048;
|
|
}
|
|
|
|
setup_logging(mctx, &lctx);
|
|
|
|
if (set_fips_mode) {
|
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L && OPENSSL_API_LEVEL >= 30000
|
|
fips = OSSL_PROVIDER_load(NULL, "fips");
|
|
if (fips == NULL) {
|
|
fatal("Failed to load FIPS provider");
|
|
}
|
|
base = OSSL_PROVIDER_load(NULL, "base");
|
|
if (base == NULL) {
|
|
OSSL_PROVIDER_unload(fips);
|
|
fatal("Failed to load base provider");
|
|
}
|
|
#endif
|
|
if (!isc_fips_mode()) {
|
|
if (isc_fips_set_mode(1) != ISC_R_SUCCESS) {
|
|
fatal("setting FIPS mode failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
/* zone */
|
|
namestr = argv[1];
|
|
name = dns_fixedname_initname(&fname);
|
|
isc_buffer_init(&buf, argv[1], strlen(argv[1]));
|
|
isc_buffer_add(&buf, strlen(argv[1]));
|
|
ret = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
|
|
if (ret != ISC_R_SUCCESS) {
|
|
fatal("invalid zone name %s: %s", argv[1],
|
|
isc_result_totext(ret));
|
|
}
|
|
|
|
/* command */
|
|
if (strcmp(argv[0], "keygen") == 0) {
|
|
keygen(&ksr);
|
|
} else if (strcmp(argv[0], "request") == 0) {
|
|
request(&ksr);
|
|
} else if (strcmp(argv[0], "sign") == 0) {
|
|
sign(&ksr);
|
|
} else {
|
|
fatal("unknown command '%s'", argv[0]);
|
|
}
|
|
|
|
exit(0);
|
|
}
|