mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-09 04:52:05 -04:00
If the inception time of the signature is exactly equal to the
inactive time of the key, still include the signature. Otherwise there
may be corner cases where signatures are omitted erroneously.
(cherry picked from commit bc6dad585d)
1475 lines
39 KiB
C
1475 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) \
|
|
{ \
|
|
CHECK(isc_lex_gettoken(lex, opt, token)); \
|
|
}
|
|
|
|
#define BADTOKEN() \
|
|
{ \
|
|
result = ISC_R_UNEXPECTEDTOKEN; \
|
|
goto cleanup; \
|
|
}
|
|
|
|
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 result;
|
|
|
|
ISC_LIST_INIT(*keys);
|
|
ISC_LIST_INIT(keys_read);
|
|
result = dns_dnssec_findmatchingkeys(name, NULL, ksr->keydir, NULL,
|
|
ksr->now, false, mctx, &keys_read);
|
|
if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
|
|
fatal("failed to load existing keys from %s: %s", ksr->keydir,
|
|
isc_result_totext(result));
|
|
}
|
|
/* 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 result;
|
|
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) {
|
|
result = dns_keystore_keygen(
|
|
ksr->keystore, name, ksr->policy,
|
|
dns_rdataclass_in, mctx, ksr->alg, ksr->size,
|
|
flags, &key);
|
|
} else if (show_progress) {
|
|
result = dst_key_generate(name, ksr->alg, ksr->size, 0,
|
|
flags, DNS_KEYPROTO_DNSSEC,
|
|
dns_rdataclass_in, NULL, mctx,
|
|
&key, &progress);
|
|
fflush(stderr);
|
|
} else {
|
|
result = dst_key_generate(name, ksr->alg, ksr->size, 0,
|
|
flags, DNS_KEYPROTO_DNSSEC,
|
|
dns_rdataclass_in, NULL, mctx,
|
|
&key, NULL);
|
|
}
|
|
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("failed to generate key %s/%s: %s\n", namestr,
|
|
algstr, isc_result_totext(result));
|
|
}
|
|
|
|
/* 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);
|
|
result = dst_key_buildfilename(
|
|
key, 0, ksr->keydir, &buf);
|
|
if (result == 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;
|
|
}
|
|
|
|
result = dst_key_tofile(key, options, ksr->keydir);
|
|
if (result != 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(result));
|
|
}
|
|
|
|
output:
|
|
isc_buffer_clear(&buf);
|
|
result = dst_key_buildfilename(key, 0, NULL, &buf);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("dst_key_buildfilename returned: %s\n",
|
|
isc_result_totext(result));
|
|
}
|
|
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 result;
|
|
char buf[4096];
|
|
|
|
isc_buffer_init(&target, buf, sizeof(buf));
|
|
result = dns_rdataset_totext(rrset, name, false, false, &target);
|
|
if (result != 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 result = 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);
|
|
|
|
cleanup:
|
|
/* Cleanup */
|
|
freerrset(&rdataset);
|
|
|
|
if (result != 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 result;
|
|
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));
|
|
result = dns_time32_totext(inception, &b);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("failed to convert bundle time32 to text: %s",
|
|
isc_result_totext(result));
|
|
}
|
|
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));
|
|
result = dns_dnssec_sign(name, rrset, dk->key, &clockskew,
|
|
&expiration, mctx, &buf, &rdata);
|
|
if (result != 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 result = 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;
|
|
|
|
cleanup:
|
|
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 result;
|
|
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));
|
|
result = dns_name_fromtext(dname, &b, dns_rootname, 0, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
goto cleanup;
|
|
}
|
|
if (dns_name_compare(dname, name) != 0) {
|
|
CHECK(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 */
|
|
result = dns_ttl_fromtext(&token.value.as_textregion, ttl);
|
|
if (result == ISC_R_SUCCESS) {
|
|
NEXTTOKEN(lex, opt, &token);
|
|
}
|
|
if (token.type != isc_tokentype_string) {
|
|
BADTOKEN();
|
|
}
|
|
|
|
/* If it's a class, read the next one */
|
|
result = dns_rdataclass_fromtext(&rdclass, &token.value.as_textregion);
|
|
if (result == 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();
|
|
}
|
|
|
|
result = dns_rdata_fromtext(NULL, rdclass, dns_rdatatype_dnskey, lex,
|
|
name, 0, mctx, buf, NULL);
|
|
|
|
cleanup:
|
|
isc_lex_setcomments(lex, 0);
|
|
return result;
|
|
}
|
|
|
|
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 result;
|
|
|
|
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
|
|
isc_buffer_init(&b, utc, sizeof(utc));
|
|
result = dns_time32_totext(inception, &b);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("failed to convert bundle time32 to text: %s",
|
|
isc_result_totext(result));
|
|
}
|
|
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 result;
|
|
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);
|
|
result = isc_lex_openfile(lex, ksr->file);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("unable to open KSR file %s: %s", ksr->file,
|
|
isc_result_totext(result));
|
|
}
|
|
|
|
for (result = isc_lex_gettoken(lex, opt, &token);
|
|
result == ISC_R_SUCCESS;
|
|
result = 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) {
|
|
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 */
|
|
next_inception = strtotime(STR(token), 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 {
|
|
result = isc_lex_gettoken(lex, opt, &token);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("bad KSR file %s(%lu): bad "
|
|
"header (%s)",
|
|
ksr->file,
|
|
isc_lex_getsourceline(lex),
|
|
isc_result_totext(result));
|
|
}
|
|
} 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));
|
|
result = parse_dnskey(lex, STR(token), &buf, &ttl);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("bad KSR file %s(%lu): bad DNSKEY (%s)",
|
|
ksr->file, isc_lex_getsourceline(lex),
|
|
isc_result_totext(result));
|
|
}
|
|
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 (result != 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);
|
|
|
|
cleanup:
|
|
isc_lex_destroy(&lex);
|
|
cleanup(&keys, kasp);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[]) {
|
|
isc_result_t result;
|
|
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;
|
|
result = try_dir(ksr.keydir);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("cannot open directory %s: %s",
|
|
ksr.keydir, isc_result_totext(result));
|
|
}
|
|
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");
|
|
}
|
|
|
|
result = dst_lib_init(mctx, engine);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("could not initialize dst: %s",
|
|
isc_result_totext(result));
|
|
}
|
|
|
|
/*
|
|
* 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]));
|
|
result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
|
|
if (result != ISC_R_SUCCESS) {
|
|
fatal("invalid zone name %s: %s", argv[1],
|
|
isc_result_totext(result));
|
|
}
|
|
|
|
/* 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);
|
|
}
|