2
0
mirror of https://gitlab.isc.org/isc-projects/bind9 synced 2025-08-30 05:57:52 +00:00

Implement dnssec-ksr sign

Add code that can create a Signed Key Response (SKR) given a Key
Signing Request (KSR), a DNSSEC policy, a set of keys and an interval.
This commit is contained in:
Matthijs Mekking 2024-02-22 15:16:03 +01:00
parent 31521fade2
commit 887fa0ddc9
2 changed files with 408 additions and 9 deletions

View File

@ -13,13 +13,16 @@
/*! \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>
@ -27,6 +30,7 @@
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/time.h>
#include <dns/ttl.h>
#include "dnssectool.h"
@ -50,6 +54,7 @@ static dns_name_t *name = NULL;
struct ksr_ctx {
const char *policy;
const char *configfile;
const char *file;
const char *keydir;
dns_keystore_t *keystore;
isc_stdtime_t now;
@ -65,6 +70,8 @@ struct ksr_ctx {
time_t propagation;
time_t publishsafety;
time_t retiresafety;
time_t sigrefresh;
time_t sigvalidity;
time_t signdelay;
time_t ttlsig;
};
@ -77,6 +84,29 @@ typedef struct ksr_ctx ksr_ctx_t;
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 TTL_MAX INT32_MAX
#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) { \
@ -91,20 +121,23 @@ usage(int ret) {
fprintf(stderr, "Version: %s\n", PACKAGE_VERSION);
fprintf(stderr, "\n");
fprintf(stderr, "Options:\n");
fprintf(stderr, " -e <date/offset>: end date\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>: write keys into directory\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 <level>: set verbosity level\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);
}
@ -208,6 +241,8 @@ 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);
@ -247,6 +282,27 @@ progress(int p) {
(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,
@ -547,6 +603,161 @@ fail:
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) {
char timestr[26]; /* Minimal buf as per ctime_r() spec. */
char utc[sizeof("YYYYMMDDHHSSMM")];
dns_rdatalist_t *rrsiglist = NULL;
dns_rdataset_t rrsigset = DNS_RDATASET_INIT;
isc_buffer_t timebuf;
isc_buffer_t b;
isc_region_t r;
isc_result_t ret;
UNUSED(ksr);
/* Bundle header */
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);
/* DNSKEY 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 = NULL;
dns_rdata_t *rrsig = NULL;
isc_region_t rs;
unsigned char rdatabuf[SIG_FORMATSIZE];
isc_stdtime_t clockskew = inception - 3600;
rdata = isc_mem_get(mctx, sizeof(*rdata));
rrsig = isc_mem_get(mctx, sizeof(*rrsig));
dns_rdata_init(rdata);
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_buffer_clear(newbuf);
}
dns_rdatalist_tordataset(rrsiglist, &rrsigset);
print_rdata(&rrsigset);
freerrset(&rrsigset);
}
static void
sign_bundle(ksr_ctx_t *ksr, isc_stdtime_t inception,
isc_stdtime_t next_inception, dns_rdatalist_t *rdatalist,
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);
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;
@ -653,6 +864,174 @@ request(ksr_ctx_t *ksr) {
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), "generated") == 0) {
/* Final bundle */
goto readline;
} else if (strcmp(STR(token), "1.0") != 0) {
fatal("bad KSR file %s(%lu): expected version",
ksr->file, isc_lex_getsourceline(lex));
}
/* Date and time of bundle */
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));
}
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, &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 = TTL_MAX;
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 = TTL_MAX;
isc_buffer_t buf;
isc_buffer_t *newbuf = NULL;
dns_rdata_t *rdata = NULL;
isc_region_t r;
u_char rdatabuf[DST_KEY_MAXSIZE];
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);
}
}
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, &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 */
isc_lex_destroy(&lex);
cleanup(&keys, kasp);
}
int
main(int argc, char *argv[]) {
isc_result_t ret;
@ -671,19 +1050,22 @@ main(int argc, char *argv[]) {
isc_commandline_errprint = false;
#define OPTIONS "E:e:Fhi:K:k:l:v:V"
#define OPTIONS "E:e:Ff:hi:K:k:l:v: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 'E':
engine = isc_commandline_argument;
break;
case 'F':
set_fips_mode = true;
break;
case 'f':
ksr.file = isc_commandline_argument;
break;
case 'h':
usage(0);
break;
@ -776,6 +1158,8 @@ main(int argc, char *argv[]) {
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]);
}

View File

@ -114,6 +114,11 @@ Commands
Create a Key Signing Request (KSR), given a DNSSEC policy and an interval.
.. option:: sign
Sign a Key Signing Request (KSR), given a DNSSEC policy and an interval,
creating a Signed Key Response (SKR).
Exit Status
~~~~~~~~~~~
@ -130,11 +135,21 @@ given a ``dnssec-policy`` named "mypolicy":
dnssec-ksr -i now -e +1y -k mypolicy -l named.conf keygen example.com
Creating a Key Signing Request for the same zone and period can be done with:
Creating a KSR for the same zone and period can be done with:
::
dnssec-ksr -i now -e +1y -k mypolicy -l named.conf request example.com
dnssec-ksr -i now -e +1y -k mypolicy -l named.conf request example.com > ksr.txt
Typically you would now transfer the KSR to the system that has access to the KSK.
Signing the KSR created above can be done with:
::
dnssec-ksr -i now -e +1y -k kskpolicy -l named.conf -f ksr.txt sign example.com
Make sure that the DNSSEC parameters in ``kskpolicy`` match those in ``mypolicy``.
See Also
~~~~~~~~