2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-22 01:59:26 +00:00
bind/bin/dnssec/dnssec-ksr.c
Ondřej Surý ef7aba7072
Remove OpenSSL Engine support
The OpenSSL 1.x Engines support has been deprecated in the OpenSSL 3.x
and is going to be removed.  Remove the OpenSSL Engine support in favor
of OpenSSL Providers.
2024-08-06 15:17:48 +02:00

1325 lines
35 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/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;
/*
* 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 */
dns_ttl_t ttl;
dns_secalg_t alg;
int size;
time_t lifetime;
time_t propagation;
time_t publishsafety;
time_t retiresafety;
time_t sigrefresh;
time_t sigvalidity;
time_t signdelay;
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 <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 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, 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
keytag_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_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 *), keytag_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->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->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_zsk(ksr_ctx_t *ksr, 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;
isc_stdtime_tostring(inception, timestr, sizeof(timestr));
/* 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,
DNS_KEYOWNER_ZONE, &key);
} else if (show_progress) {
ret = dst_key_generate(
name, ksr->alg, ksr->size, 0, DNS_KEYOWNER_ZONE,
DNS_KEYPROTO_DNSSEC, dns_rdataclass_in, NULL,
mctx, &key, &progress);
fflush(stderr);
} else {
ret = dst_key_generate(
name, ksr->alg, ksr->size, 0, DNS_KEYOWNER_ZONE,
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, 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, false);
dst_key_setbool(key, DST_BOOL_ZSK, true);
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->lifetime > 0) {
isc_stdtime_t inactive = (active + ksr->lifetime);
isc_stdtime_t 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 void
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;
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;
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);
}
/*
* Create the DNSKEY, CDS, and CDNSKEY records beloing to the KSKs
* listed in 'keys'.
*/
static void
create_ksk(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_dnsseckeylist_t *keys,
dns_rdataset_t *dnskeyset, dns_rdataset_t *cdnskeyset,
dns_rdataset_t *cdsset) {
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;
dns_kasp_digestlist_t digests = dns_kasp_digests(kasp);
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))
{
isc_buffer_t buf;
isc_buffer_t *newbuf;
dns_rdata_t *rdata;
isc_region_t r;
isc_region_t rcds;
unsigned char kskbuf[DST_KEY_MAXSIZE];
unsigned char cdnskeybuf[DST_KEY_MAXSIZE];
unsigned char cdsbuf[DNS_DS_BUFFERSIZE];
/* KSK */
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);
/* 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;
fail:
fatal("failed to create KSK/CDS/CDNSKEY");
}
static void
sign_bundle(ksr_ctx_t *ksr, isc_stdtime_t inception,
isc_stdtime_t next_inception, dns_rdatalist_t *rdatalist,
dns_rdataset_t *cds, dns_rdataset_t *cdnskey,
dns_dnsseckeylist_t *keys) {
dns_rdataset_t rrset = DNS_RDATASET_INIT;
isc_stdtime_t expiration;
dns_rdataset_init(&rrset);
dns_rdatalist_tordataset(rdatalist, &rrset);
expiration = inception + ksr->sigvalidity;
while (inception <= next_inception) {
sign_rrset(ksr, inception, expiration, &rrset, keys);
if (dns_rdataset_count(cdnskey) > 0) {
sign_rrset(ksr, inception, expiration, cdnskey, keys);
}
if (dns_rdataset_count(cds) > 0) {
sign_rrset(ksr, inception, expiration, cds, keys);
}
inception = expiration - ksr->sigrefresh;
expiration = inception + ksr->sigvalidity;
}
freerrset(&rrset);
}
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)) {
/* only ZSKs 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_zsk(ksr, kk, &keys, inception, act, &act);
if (ksr->lifetime == 0) {
/* unlimited lifetime, but not infinite loop */
break;
}
}
}
if (noop) {
fatal("policy '%s' has no zsks", 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;
dns_rdataset_t ksk = DNS_RDATASET_INIT;
dns_rdataset_t cdnskey = DNS_RDATASET_INIT;
dns_rdataset_t cds = DNS_RDATASET_INIT;
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));
}
/* KSK, CDS and CDNSKEY */
create_ksk(ksr, kasp, &keys, &ksk, &cdnskey, &cds);
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, inception, next_inception,
rdatalist, &cds, &cdnskey, &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;
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(rdatalist->rdata, clone, link);
}
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, inception, ksr->end, rdatalist, &cds, &cdnskey,
&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:
/* Clean up */
freerrset(&ksk);
freerrset(&cdnskey);
freerrset(&cds);
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:v:V"
while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
switch (ch) {
case 'E':
fatal("%s", isc_result_totext(DST_R_NOENGINE));
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 '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);
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);
}