From 45d6379135504814ab723b57f0eb8be23393a51d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 27 Apr 2024 09:24:22 +0200 Subject: Adding upstream version 1:9.16.44. Signed-off-by: Daniel Baumann --- bin/dnssec/Makefile.in | 112 + bin/dnssec/dnssec-cds.c | 1314 +++++++ bin/dnssec/dnssec-cds.rst | 203 ++ bin/dnssec/dnssec-dsfromkey.c | 568 +++ bin/dnssec/dnssec-dsfromkey.rst | 144 + bin/dnssec/dnssec-importkey.c | 485 +++ bin/dnssec/dnssec-importkey.rst | 113 + bin/dnssec/dnssec-keyfromlabel.c | 782 ++++ bin/dnssec/dnssec-keyfromlabel.rst | 262 ++ bin/dnssec/dnssec-keygen.c | 1315 +++++++ bin/dnssec/dnssec-keygen.rst | 318 ++ bin/dnssec/dnssec-revoke.c | 278 ++ bin/dnssec/dnssec-revoke.rst | 71 + bin/dnssec/dnssec-settime.c | 985 +++++ bin/dnssec/dnssec-settime.rst | 231 ++ bin/dnssec/dnssec-signzone.c | 4197 ++++++++++++++++++++++ bin/dnssec/dnssec-signzone.rst | 396 ++ bin/dnssec/dnssec-verify.c | 366 ++ bin/dnssec/dnssec-verify.rst | 98 + bin/dnssec/dnssectool.c | 594 +++ bin/dnssec/dnssectool.h | 119 + bin/dnssec/win32/cds.vcxproj.filters.in | 18 + bin/dnssec/win32/cds.vcxproj.in | 121 + bin/dnssec/win32/cds.vcxproj.user | 3 + bin/dnssec/win32/dnssectool.vcxproj.filters.in | 27 + bin/dnssec/win32/dnssectool.vcxproj.in | 118 + bin/dnssec/win32/dnssectool.vcxproj.user | 3 + bin/dnssec/win32/dsfromkey.vcxproj.filters.in | 18 + bin/dnssec/win32/dsfromkey.vcxproj.in | 147 + bin/dnssec/win32/dsfromkey.vcxproj.user | 3 + bin/dnssec/win32/importkey.vcxproj.filters.in | 18 + bin/dnssec/win32/importkey.vcxproj.in | 121 + bin/dnssec/win32/importkey.vcxproj.user | 3 + bin/dnssec/win32/keyfromlabel.vcxproj.filters.in | 18 + bin/dnssec/win32/keyfromlabel.vcxproj.in | 121 + bin/dnssec/win32/keyfromlabel.vcxproj.user | 3 + bin/dnssec/win32/keygen.vcxproj.filters.in | 18 + bin/dnssec/win32/keygen.vcxproj.in | 121 + bin/dnssec/win32/keygen.vcxproj.user | 3 + bin/dnssec/win32/revoke.vcxproj.filters.in | 18 + bin/dnssec/win32/revoke.vcxproj.in | 121 + bin/dnssec/win32/revoke.vcxproj.user | 3 + bin/dnssec/win32/settime.vcxproj.filters.in | 18 + bin/dnssec/win32/settime.vcxproj.in | 121 + bin/dnssec/win32/settime.vcxproj.user | 3 + bin/dnssec/win32/signzone.vcxproj.filters.in | 18 + bin/dnssec/win32/signzone.vcxproj.in | 121 + bin/dnssec/win32/signzone.vcxproj.user | 3 + bin/dnssec/win32/verify.vcxproj.filters.in | 18 + bin/dnssec/win32/verify.vcxproj.in | 121 + bin/dnssec/win32/verify.vcxproj.user | 3 + 51 files changed, 14403 insertions(+) create mode 100644 bin/dnssec/Makefile.in create mode 100644 bin/dnssec/dnssec-cds.c create mode 100644 bin/dnssec/dnssec-cds.rst create mode 100644 bin/dnssec/dnssec-dsfromkey.c create mode 100644 bin/dnssec/dnssec-dsfromkey.rst create mode 100644 bin/dnssec/dnssec-importkey.c create mode 100644 bin/dnssec/dnssec-importkey.rst create mode 100644 bin/dnssec/dnssec-keyfromlabel.c create mode 100644 bin/dnssec/dnssec-keyfromlabel.rst create mode 100644 bin/dnssec/dnssec-keygen.c create mode 100644 bin/dnssec/dnssec-keygen.rst create mode 100644 bin/dnssec/dnssec-revoke.c create mode 100644 bin/dnssec/dnssec-revoke.rst create mode 100644 bin/dnssec/dnssec-settime.c create mode 100644 bin/dnssec/dnssec-settime.rst create mode 100644 bin/dnssec/dnssec-signzone.c create mode 100644 bin/dnssec/dnssec-signzone.rst create mode 100644 bin/dnssec/dnssec-verify.c create mode 100644 bin/dnssec/dnssec-verify.rst create mode 100644 bin/dnssec/dnssectool.c create mode 100644 bin/dnssec/dnssectool.h create mode 100644 bin/dnssec/win32/cds.vcxproj.filters.in create mode 100644 bin/dnssec/win32/cds.vcxproj.in create mode 100644 bin/dnssec/win32/cds.vcxproj.user create mode 100644 bin/dnssec/win32/dnssectool.vcxproj.filters.in create mode 100644 bin/dnssec/win32/dnssectool.vcxproj.in create mode 100644 bin/dnssec/win32/dnssectool.vcxproj.user create mode 100644 bin/dnssec/win32/dsfromkey.vcxproj.filters.in create mode 100644 bin/dnssec/win32/dsfromkey.vcxproj.in create mode 100644 bin/dnssec/win32/dsfromkey.vcxproj.user create mode 100644 bin/dnssec/win32/importkey.vcxproj.filters.in create mode 100644 bin/dnssec/win32/importkey.vcxproj.in create mode 100644 bin/dnssec/win32/importkey.vcxproj.user create mode 100644 bin/dnssec/win32/keyfromlabel.vcxproj.filters.in create mode 100644 bin/dnssec/win32/keyfromlabel.vcxproj.in create mode 100644 bin/dnssec/win32/keyfromlabel.vcxproj.user create mode 100644 bin/dnssec/win32/keygen.vcxproj.filters.in create mode 100644 bin/dnssec/win32/keygen.vcxproj.in create mode 100644 bin/dnssec/win32/keygen.vcxproj.user create mode 100644 bin/dnssec/win32/revoke.vcxproj.filters.in create mode 100644 bin/dnssec/win32/revoke.vcxproj.in create mode 100644 bin/dnssec/win32/revoke.vcxproj.user create mode 100644 bin/dnssec/win32/settime.vcxproj.filters.in create mode 100644 bin/dnssec/win32/settime.vcxproj.in create mode 100644 bin/dnssec/win32/settime.vcxproj.user create mode 100644 bin/dnssec/win32/signzone.vcxproj.filters.in create mode 100644 bin/dnssec/win32/signzone.vcxproj.in create mode 100644 bin/dnssec/win32/signzone.vcxproj.user create mode 100644 bin/dnssec/win32/verify.vcxproj.filters.in create mode 100644 bin/dnssec/win32/verify.vcxproj.in create mode 100644 bin/dnssec/win32/verify.vcxproj.user (limited to 'bin/dnssec') diff --git a/bin/dnssec/Makefile.in b/bin/dnssec/Makefile.in new file mode 100644 index 0000000..f49f775 --- /dev/null +++ b/bin/dnssec/Makefile.in @@ -0,0 +1,112 @@ +# 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. + +srcdir = @srcdir@ +VPATH = @srcdir@ +top_srcdir = @top_srcdir@ + +VERSION=@BIND9_VERSION@ + +@BIND9_MAKE_INCLUDES@ + +CINCLUDES = ${DNS_INCLUDES} ${ISC_INCLUDES} ${ISCCFG_INCLUDES} \ + ${OPENSSL_CFLAGS} + +CDEFINES = -DVERSION=\"${VERSION}\" -DNAMED_CONFFILE=\"${sysconfdir}/named.conf\" +CWARNINGS = + +DNSLIBS = ../../lib/dns/libdns.@A@ @NO_LIBTOOL_DNSLIBS@ +ISCCFGLIBS = ../../lib/isccfg/libisccfg.@A@ +ISCLIBS = ../../lib/isc/libisc.@A@ @NO_LIBTOOL_ISCLIBS@ +ISCNOSYMLIBS = ../../lib/isc/libisc-nosymtbl.@A@ @NO_LIBTOOL_ISCLIBS@ + +DNSDEPLIBS = ../../lib/dns/libdns.@A@ +ISCDEPLIBS = ../../lib/isc/libisc.@A@ +ISCCFGDEPLIBS = ../../lib/isccfg/libisccfg.@A@ + +DEPLIBS = ${DNSDEPLIBS} ${ISCCFGDEPLIBS} ${ISCDEPLIBS} + +LIBS = ${DNSLIBS} ${ISCCFGLIBS} ${ISCLIBS} @LIBS@ + +NOSYMLIBS = ${DNSLIBS} ${ISCCFGLIBS} ${ISCNOSYMLIBS} @LIBS@ + +# Alphabetically +TARGETS = dnssec-cds@EXEEXT@ dnssec-dsfromkey@EXEEXT@ \ + dnssec-importkey@EXEEXT@ dnssec-keyfromlabel@EXEEXT@ \ + dnssec-keygen@EXEEXT@ dnssec-revoke@EXEEXT@ \ + dnssec-settime@EXEEXT@ dnssec-signzone@EXEEXT@ \ + dnssec-verify@EXEEXT@ + +OBJS = dnssectool.@O@ + +SRCS = dnssec-cds.c dnssec-dsfromkey.c dnssec-importkey.c \ + dnssec-keyfromlabel.c dnssec-keygen.c dnssec-revoke.c \ + dnssec-settime.c dnssec-signzone.c dnssec-verify.c \ + dnssectool.c + +@BIND9_MAKE_RULES@ + +dnssec-cds@EXEEXT@: dnssec-cds.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-cds.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + +dnssec-dsfromkey@EXEEXT@: dnssec-dsfromkey.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-dsfromkey.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + +dnssec-keyfromlabel@EXEEXT@: dnssec-keyfromlabel.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-keyfromlabel.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + +dnssec-keygen@EXEEXT@: dnssec-keygen.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-keygen.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + +dnssec-signzone.@O@: dnssec-signzone.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -DVERSION=\"${VERSION}\" \ + -c ${srcdir}/dnssec-signzone.c + +dnssec-signzone@EXEEXT@: dnssec-signzone.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-signzone.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + +dnssec-verify.@O@: dnssec-verify.c + ${LIBTOOL_MODE_COMPILE} ${CC} ${ALL_CFLAGS} -DVERSION=\"${VERSION}\" \ + -c ${srcdir}/dnssec-verify.c + +dnssec-verify@EXEEXT@: dnssec-verify.@O@ ${OBJS} ${DEPLIBS} + export BASEOBJS="dnssec-verify.@O@ ${OBJS}"; \ + ${FINALBUILDCMD} + +dnssec-revoke@EXEEXT@: dnssec-revoke.@O@ ${OBJS} ${DEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + dnssec-revoke.@O@ ${OBJS} ${LIBS} + +dnssec-settime@EXEEXT@: dnssec-settime.@O@ ${OBJS} ${DEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + dnssec-settime.@O@ ${OBJS} ${LIBS} + +dnssec-importkey@EXEEXT@: dnssec-importkey.@O@ ${OBJS} ${DEPLIBS} + ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ \ + dnssec-importkey.@O@ ${OBJS} ${LIBS} + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${sbindir} + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${mandir}/man8 + +install:: ${TARGETS} installdirs + for t in ${TARGETS}; do ${LIBTOOL_MODE_INSTALL} ${INSTALL_PROGRAM} $$t ${DESTDIR}${sbindir} || exit 1; done + +uninstall:: + for t in ${TARGETS}; do ${LIBTOOL_MODE_UNINSTALL} rm -f ${DESTDIR}${sbindir}/$$t || exit 1; done + +clean distclean:: + rm -f ${TARGETS} diff --git a/bin/dnssec/dnssec-cds.c b/bin/dnssec/dnssec-cds.c new file mode 100644 index 0000000..15f9f4e --- /dev/null +++ b/bin/dnssec/dnssec-cds.c @@ -0,0 +1,1314 @@ +/* + * 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. + */ + +/* + * Written by Tony Finch + * at Cambridge University Information Services + */ + +/*! \file */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-cds"; + +/* + * 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 fixed; +static dns_name_t *name = NULL; +static dns_rdataclass_t rdclass = dns_rdataclass_in; + +static const char *startstr = NULL; /* from which we derive notbefore */ +static isc_stdtime_t notbefore = 0; /* restrict sig inception times */ +static dns_rdata_rrsig_t oldestsig; /* for recording inception time */ + +static int nkey; /* number of child zone DNSKEY records */ + +/* + * The validation strategy of this program is top-down. + * + * We start with an implicitly trusted authoritative dsset. + * + * The child DNSKEY RRset is scanned to find out which keys are + * authenticated by DS records, and the result is recorded in a key + * table as described later in this comment. + * + * The key table is used up to three times to verify the signatures on + * the child DNSKEY, CDNSKEY, and CDS RRsets. In this program, only keys + * that have matching DS records are used for validating signatures. + * + * For replay attack protection, signatures are ignored if their inception + * time is before the previously recorded inception time. We use the earliest + * signature so that another run of dnssec-cds with the same records will + * still accept all the signatures. + * + * A key table is an array of nkey keyinfo structures, like + * + * keyinfo_t key_tbl[nkey]; + * + * Each key is decoded into more useful representations, held in + * keyinfo->rdata + * keyinfo->dst + * + * If a key has no matching DS record then keyinfo->dst is NULL. + * + * The key algorithm and ID are saved in keyinfo->algo and + * keyinfo->tag for quicky skipping DS and RRSIG records that can't + * match. + */ +typedef struct keyinfo { + dns_rdata_t rdata; + dst_key_t *dst; + dns_secalg_t algo; + dns_keytag_t tag; +} keyinfo_t; + +/* A replaceable function that can generate a DS RRset from some input */ +typedef isc_result_t +ds_maker_func_t(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *rdata); + +static dns_rdataset_t cdnskey_set, cdnskey_sig; +static dns_rdataset_t cds_set, cds_sig; +static dns_rdataset_t dnskey_set, dnskey_sig; +static dns_rdataset_t old_ds_set, new_ds_set; + +static keyinfo_t *old_key_tbl, *new_key_tbl; + +isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */ + +static void +verbose_time(int level, const char *msg, isc_stdtime_t time) { + isc_result_t result; + isc_buffer_t timebuf; + char timestr[32]; + + if (verbose < level) { + return; + } + + isc_buffer_init(&timebuf, timestr, sizeof(timestr)); + result = dns_time64_totext(time, &timebuf); + check_result(result, "dns_time64_totext()"); + isc_buffer_putuint8(&timebuf, 0); + if (verbose < 3) { + vbprintf(level, "%s %s\n", msg, timestr); + } else { + vbprintf(level, "%s %s (%" PRIu32 ")\n", msg, timestr, time); + } +} + +static void +initname(char *setname) { + isc_result_t result; + isc_buffer_t buf; + + name = dns_fixedname_initname(&fixed); + namestr = setname; + + isc_buffer_init(&buf, setname, strlen(setname)); + isc_buffer_add(&buf, strlen(setname)); + result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize name %s", setname); + } +} + +static void +findset(dns_db_t *db, dns_dbnode_t *node, dns_rdatatype_t type, + dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) { + isc_result_t result; + + dns_rdataset_init(rdataset); + if (sigrdataset != NULL) { + dns_rdataset_init(sigrdataset); + } + result = dns_db_findrdataset(db, node, NULL, type, 0, 0, rdataset, + sigrdataset); + if (result != ISC_R_NOTFOUND) { + check_result(result, "dns_db_findrdataset()"); + } +} + +static void +freeset(dns_rdataset_t *rdataset) { + if (dns_rdataset_isassociated(rdataset)) { + dns_rdataset_disassociate(rdataset); + } +} + +static void +freelist(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 +free_all_sets(void) { + freeset(&cdnskey_set); + freeset(&cdnskey_sig); + freeset(&cds_set); + freeset(&cds_sig); + freeset(&dnskey_set); + freeset(&dnskey_sig); + freeset(&old_ds_set); + freelist(&new_ds_set); + if (new_ds_buf != NULL) { + isc_buffer_free(&new_ds_buf); + } +} + +static void +load_db(const char *filename, dns_db_t **dbp, dns_dbnode_t **nodep) { + isc_result_t result; + + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0, + NULL, dbp); + check_result(result, "dns_db_create()"); + + result = dns_db_load(*dbp, filename, dns_masterformat_text, + DNS_MASTER_HINT); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + fatal("can't load %s: %s", filename, isc_result_totext(result)); + } + + result = dns_db_findnode(*dbp, name, false, nodep); + if (result != ISC_R_SUCCESS) { + fatal("can't find %s node in %s", namestr, filename); + } +} + +static void +free_db(dns_db_t **dbp, dns_dbnode_t **nodep) { + dns_db_detachnode(*dbp, nodep); + dns_db_detach(dbp); +} + +static void +load_child_sets(const char *file) { + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + + load_db(file, &db, &node); + findset(db, node, dns_rdatatype_dnskey, &dnskey_set, &dnskey_sig); + findset(db, node, dns_rdatatype_cdnskey, &cdnskey_set, &cdnskey_sig); + findset(db, node, dns_rdatatype_cds, &cds_set, &cds_sig); + free_db(&db, &node); +} + +static void +get_dsset_name(char *filename, size_t size, const char *path, + const char *suffix) { + isc_result_t result; + isc_buffer_t buf; + size_t len; + + isc_buffer_init(&buf, filename, size); + + len = strlen(path); + + /* allow room for a trailing slash */ + if (isc_buffer_availablelength(&buf) <= len) { + fatal("%s: pathname too long", path); + } + isc_buffer_putstr(&buf, path); + + if (isc_file_isdirectory(path) == ISC_R_SUCCESS) { + const char *prefix = "dsset-"; + + if (path[len - 1] != '/') { + isc_buffer_putstr(&buf, "/"); + } + + if (isc_buffer_availablelength(&buf) < strlen(prefix)) { + fatal("%s: pathname too long", path); + } + isc_buffer_putstr(&buf, prefix); + + result = dns_name_tofilenametext(name, false, &buf); + check_result(result, "dns_name_tofilenametext()"); + if (isc_buffer_availablelength(&buf) == 0) { + fatal("%s: pathname too long", path); + } + } + /* allow room for a trailing nul */ + if (isc_buffer_availablelength(&buf) <= strlen(suffix)) { + fatal("%s: pathname too long", path); + } + isc_buffer_putstr(&buf, suffix); + isc_buffer_putuint8(&buf, 0); +} + +static void +load_parent_set(const char *path) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + isc_time_t modtime; + char filename[PATH_MAX + 1]; + + get_dsset_name(filename, sizeof(filename), path, ""); + + result = isc_file_getmodtime(filename, &modtime); + if (result != ISC_R_SUCCESS) { + fatal("could not get modification time of %s: %s", filename, + isc_result_totext(result)); + } + notbefore = isc_time_seconds(&modtime); + if (startstr != NULL) { + isc_stdtime_t now; + isc_stdtime_get(&now); + notbefore = strtotime(startstr, now, notbefore, NULL); + } + verbose_time(1, "child records must not be signed before", notbefore); + + load_db(filename, &db, &node); + findset(db, node, dns_rdatatype_ds, &old_ds_set, NULL); + + if (!dns_rdataset_isassociated(&old_ds_set)) { + fatal("could not find DS records for %s in %s", namestr, + filename); + } + + free_db(&db, &node); +} + +#define MAX_CDS_RDATA_TEXT_SIZE DNS_RDATA_MAXLENGTH * 2 + +static isc_buffer_t * +formatset(dns_rdataset_t *rdataset) { + isc_result_t result; + isc_buffer_t *buf = NULL; + dns_master_style_t *style = NULL; + unsigned int styleflags; + + styleflags = (rdataset->ttl == 0) ? DNS_STYLEFLAG_NO_TTL : 0; + + /* + * This style is for consistency with the output of dnssec-dsfromkey + * which just separates fields with spaces. The huge tab stop width + * eliminates any tab characters. + */ + result = dns_master_stylecreate(&style, styleflags, 0, 0, 0, 0, 0, + 1000000, 0, mctx); + check_result(result, "dns_master_stylecreate2 failed"); + + isc_buffer_allocate(mctx, &buf, MAX_CDS_RDATA_TEXT_SIZE); + result = dns_master_rdatasettotext(name, rdataset, style, NULL, buf); + + if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) { + result = ISC_R_NOSPACE; + } + + check_result(result, "dns_rdataset_totext()"); + + isc_buffer_putuint8(buf, 0); + + dns_master_styledestroy(&style, mctx); + + return (buf); +} + +static void +write_parent_set(const char *path, const char *inplace, bool nsupdate, + dns_rdataset_t *rdataset) { + isc_result_t result; + isc_buffer_t *buf = NULL; + isc_region_t r; + isc_time_t filetime; + char backname[PATH_MAX + 1]; + char filename[PATH_MAX + 1]; + char tmpname[PATH_MAX + 1]; + FILE *fp = NULL; + + if (nsupdate && inplace == NULL) { + return; + } + + buf = formatset(rdataset); + isc_buffer_usedregion(buf, &r); + + /* + * Try to ensure a write error doesn't make a zone go insecure! + */ + if (inplace == NULL) { + printf("%s", (char *)r.base); + isc_buffer_free(&buf); + if (fflush(stdout) == EOF) { + fatal("error writing to stdout: %s", strerror(errno)); + } + return; + } + + if (inplace[0] != '\0') { + get_dsset_name(backname, sizeof(backname), path, inplace); + } + get_dsset_name(filename, sizeof(filename), path, ""); + get_dsset_name(tmpname, sizeof(tmpname), path, "-XXXXXXXXXX"); + + result = isc_file_openunique(tmpname, &fp); + if (result != ISC_R_SUCCESS) { + fatal("open %s: %s", tmpname, isc_result_totext(result)); + } + fprintf(fp, "%s", (char *)r.base); + isc_buffer_free(&buf); + if (fclose(fp) == EOF) { + int err = errno; + isc_file_remove(tmpname); + fatal("error writing to %s: %s", tmpname, strerror(err)); + } + + isc_time_set(&filetime, oldestsig.timesigned, 0); + result = isc_file_settime(tmpname, &filetime); + if (result != ISC_R_SUCCESS) { + isc_file_remove(tmpname); + fatal("can't set modification time of %s: %s", tmpname, + isc_result_totext(result)); + } + + if (inplace[0] != '\0') { + isc_file_rename(filename, backname); + } + isc_file_rename(tmpname, filename); +} + +typedef enum { LOOSE, TIGHT } strictness_t; + +/* + * Find out if any (C)DS record matches a particular (C)DNSKEY. + */ +static bool +match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness) { + isc_result_t result; + unsigned char dsbuf[DNS_DS_BUFFERSIZE]; + + for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(dsset)) + { + dns_rdata_ds_t ds; + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_t newdsrdata = DNS_RDATA_INIT; + bool c; + + dns_rdataset_current(dsset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + check_result(result, "dns_rdata_tostruct(DS)"); + + if (ki->tag != ds.key_tag || ki->algo != ds.algorithm) { + continue; + } + + result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type, + dsbuf, &newdsrdata); + if (result != ISC_R_SUCCESS) { + vbprintf(3, + "dns_ds_buildrdata(" + "keytag=%d, algo=%d, digest=%d): %s\n", + ds.key_tag, ds.algorithm, ds.digest_type, + dns_result_totext(result)); + continue; + } + /* allow for both DS and CDS */ + c = dsrdata.type != dns_rdatatype_ds; + dsrdata.type = dns_rdatatype_ds; + if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) { + vbprintf(1, "found matching %s %d %d %d\n", + c ? "CDS" : "DS", ds.key_tag, ds.algorithm, + ds.digest_type); + return (true); + } else if (strictness == TIGHT) { + vbprintf(0, + "key does not match %s %d %d %d " + "when it looks like it should\n", + c ? "CDS" : "DS", ds.key_tag, ds.algorithm, + ds.digest_type); + return (false); + } + } + + vbprintf(1, "no matching %s for %s %d %d\n", + dsset->type == dns_rdatatype_cds ? "CDS" : "DS", + ki->rdata.type == dns_rdatatype_cdnskey ? "CDNSKEY" : "DNSKEY", + ki->tag, ki->algo); + + return (false); +} + +/* + * Find which (C)DNSKEY records match a (C)DS RRset. + * This creates a keyinfo_t key_tbl[nkey] array. + */ +static keyinfo_t * +match_keyset_dsset(dns_rdataset_t *keyset, dns_rdataset_t *dsset, + strictness_t strictness) { + isc_result_t result; + keyinfo_t *keytable; + int i; + + nkey = dns_rdataset_count(keyset); + + keytable = isc_mem_get(mctx, sizeof(keyinfo_t) * nkey); + + for (result = dns_rdataset_first(keyset), i = 0; + result == ISC_R_SUCCESS; result = dns_rdataset_next(keyset), i++) + { + keyinfo_t *ki; + dns_rdata_dnskey_t dnskey; + dns_rdata_t *keyrdata; + isc_region_t r; + + INSIST(i < nkey); + ki = &keytable[i]; + keyrdata = &ki->rdata; + + dns_rdata_init(keyrdata); + dns_rdataset_current(keyset, keyrdata); + + result = dns_rdata_tostruct(keyrdata, &dnskey, NULL); + check_result(result, "dns_rdata_tostruct(DNSKEY)"); + ki->algo = dnskey.algorithm; + + dns_rdata_toregion(keyrdata, &r); + ki->tag = dst_region_computeid(&r); + + ki->dst = NULL; + if (!match_key_dsset(ki, dsset, strictness)) { + continue; + } + + result = dns_dnssec_keyfromrdata(name, keyrdata, mctx, + &ki->dst); + if (result != ISC_R_SUCCESS) { + vbprintf(3, + "dns_dnssec_keyfromrdata(" + "keytag=%d, algo=%d): %s\n", + ki->tag, ki->algo, dns_result_totext(result)); + } + } + + return (keytable); +} + +static void +free_keytable(keyinfo_t **keytable_p) { + keyinfo_t *keytable = *keytable_p; + *keytable_p = NULL; + keyinfo_t *ki; + int i; + + for (i = 0; i < nkey; i++) { + ki = &keytable[i]; + if (ki->dst != NULL) { + dst_key_free(&ki->dst); + } + } + + isc_mem_put(mctx, keytable, sizeof(keyinfo_t) * nkey); +} + +/* + * Find out which keys have signed an RRset. Keys that do not match a + * DS record are skipped. + * + * The return value is an array with nkey elements, one for each key, + * either zero if the key was skipped or did not sign the RRset, or + * otherwise the key algorithm. This is used by the signature coverage + * check functions below. + */ +static dns_secalg_t * +matching_sigs(keyinfo_t *keytbl, dns_rdataset_t *rdataset, + dns_rdataset_t *sigset) { + isc_result_t result; + dns_secalg_t *algo; + int i; + + algo = isc_mem_get(mctx, nkey); + memset(algo, 0, nkey); + + for (result = dns_rdataset_first(sigset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(sigset)) + { + dns_rdata_t sigrdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t sig; + + dns_rdataset_current(sigset, &sigrdata); + result = dns_rdata_tostruct(&sigrdata, &sig, NULL); + check_result(result, "dns_rdata_tostruct(RRSIG)"); + + /* + * Replay attack protection: check against current age limit + */ + if (isc_serial_lt(sig.timesigned, notbefore)) { + vbprintf(1, "skip RRSIG by key %d: too old\n", + sig.keyid); + continue; + } + + for (i = 0; i < nkey; i++) { + keyinfo_t *ki = &keytbl[i]; + if (sig.keyid != ki->tag || sig.algorithm != ki->algo || + !dns_name_equal(&sig.signer, name)) + { + continue; + } + if (ki->dst == NULL) { + vbprintf(1, + "skip RRSIG by key %d:" + " no matching (C)DS\n", + sig.keyid); + continue; + } + + result = dns_dnssec_verify(name, rdataset, ki->dst, + false, 0, mctx, &sigrdata, + NULL); + + if (result != ISC_R_SUCCESS && + result != DNS_R_FROMWILDCARD) + { + vbprintf(1, + "skip RRSIG by key %d:" + " verification failed: %s\n", + sig.keyid, isc_result_totext(result)); + continue; + } + + vbprintf(1, "found RRSIG by key %d\n", ki->tag); + algo[i] = sig.algorithm; + + /* + * Replay attack protection: work out next age limit, + * only after the signature has been verified + */ + if (oldestsig.timesigned == 0 || + isc_serial_lt(sig.timesigned, oldestsig.timesigned)) + { + verbose_time(2, "this is the oldest so far", + sig.timesigned); + oldestsig = sig; + } + } + } + + return (algo); +} + +/* + * Consume the result of matching_sigs(). When checking records + * fetched from the child zone, any working signature is enough. + */ +static bool +signed_loose(dns_secalg_t *algo) { + bool ok = false; + int i; + for (i = 0; i < nkey; i++) { + if (algo[i] != 0) { + ok = true; + } + } + isc_mem_put(mctx, algo, nkey); + return (ok); +} + +/* + * Consume the result of matching_sigs(). To ensure that the new DS + * RRset does not break the chain of trust to the DNSKEY RRset, every + * key algorithm in the DS RRset must have a signature in the DNSKEY + * RRset. + */ +static bool +signed_strict(dns_rdataset_t *dsset, dns_secalg_t *algo) { + isc_result_t result; + bool all_ok = true; + + for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(dsset)) + { + dns_rdata_t dsrdata = DNS_RDATA_INIT; + dns_rdata_ds_t ds; + bool ds_ok; + int i; + + dns_rdataset_current(dsset, &dsrdata); + result = dns_rdata_tostruct(&dsrdata, &ds, NULL); + check_result(result, "dns_rdata_tostruct(DS)"); + + ds_ok = false; + for (i = 0; i < nkey; i++) { + if (algo[i] == ds.algorithm) { + ds_ok = true; + } + } + if (!ds_ok) { + vbprintf(0, + "missing signature for algorithm %d " + "(key %d)\n", + ds.algorithm, ds.key_tag); + all_ok = false; + } + } + + isc_mem_put(mctx, algo, nkey); + return (all_ok); +} + +static dns_rdata_t * +rdata_get(void) { + dns_rdata_t *rdata; + + rdata = isc_mem_get(mctx, sizeof(*rdata)); + dns_rdata_init(rdata); + + return (rdata); +} + +static isc_result_t +rdata_put(isc_result_t result, dns_rdatalist_t *rdlist, dns_rdata_t *rdata) { + if (result == ISC_R_SUCCESS) { + ISC_LIST_APPEND(rdlist->rdata, rdata, link); + } else { + isc_mem_put(mctx, rdata, sizeof(*rdata)); + } + + return (result); +} + +/* + * This basically copies the rdata into the buffer, but going via the + * unpacked struct has the side-effect of changing the rdatatype. The + * dns_rdata_cds_t and dns_rdata_ds_t types are aliases. + */ +static isc_result_t +ds_from_cds(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *cds) { + isc_result_t result; + dns_rdata_ds_t ds; + dns_rdata_t *rdata; + + REQUIRE(buf != NULL); + + rdata = rdata_get(); + + result = dns_rdata_tostruct(cds, &ds, NULL); + check_result(result, "dns_rdata_tostruct(CDS)"); + ds.common.rdtype = dns_rdatatype_ds; + + result = dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_ds, &ds, + buf); + + return (rdata_put(result, dslist, rdata)); +} + +static isc_result_t +ds_from_cdnskey(dns_rdatalist_t *dslist, isc_buffer_t *buf, + dns_rdata_t *cdnskey) { + isc_result_t result; + unsigned i, n; + + REQUIRE(buf != NULL); + + n = sizeof(dtype) / sizeof(dtype[0]); + for (i = 0; i < n; i++) { + if (dtype[i] != 0) { + dns_rdata_t *rdata; + isc_region_t r; + + isc_buffer_availableregion(buf, &r); + if (r.length < DNS_DS_BUFFERSIZE) { + return (ISC_R_NOSPACE); + } + + rdata = rdata_get(); + result = dns_ds_buildrdata(name, cdnskey, dtype[i], + r.base, rdata); + if (result == ISC_R_SUCCESS) { + isc_buffer_add(buf, DNS_DS_BUFFERSIZE); + } + + result = rdata_put(result, dslist, rdata); + if (result != ISC_R_SUCCESS) { + return (result); + } + } + } + + return (ISC_R_SUCCESS); +} + +static void +make_new_ds_set(ds_maker_func_t *ds_from_rdata, uint32_t ttl, + dns_rdataset_t *rdset) { + unsigned int size = 16; + for (;;) { + isc_result_t result; + dns_rdatalist_t *dslist; + + dslist = isc_mem_get(mctx, sizeof(*dslist)); + + dns_rdatalist_init(dslist); + dslist->rdclass = rdclass; + dslist->type = dns_rdatatype_ds; + dslist->ttl = ttl; + + dns_rdataset_init(&new_ds_set); + result = dns_rdatalist_tordataset(dslist, &new_ds_set); + check_result(result, "dns_rdatalist_tordataset(dslist)"); + + isc_buffer_allocate(mctx, &new_ds_buf, size); + + for (result = dns_rdataset_first(rdset); + result == ISC_R_SUCCESS; result = dns_rdataset_next(rdset)) + { + isc_result_t tresult; + dns_rdata_t rdata = DNS_RDATA_INIT; + + dns_rdataset_current(rdset, &rdata); + + tresult = ds_from_rdata(dslist, new_ds_buf, &rdata); + if (tresult == ISC_R_NOSPACE) { + vbprintf(20, "DS list buffer size %u\n", size); + freelist(&new_ds_set); + isc_buffer_free(&new_ds_buf); + size *= 2; + break; + } + + check_result(tresult, "ds_from_rdata()"); + } + + if (result == ISC_R_NOMORE) { + break; + } + } +} + +static int +rdata_cmp(const void *rdata1, const void *rdata2) { + return (dns_rdata_compare((const dns_rdata_t *)rdata1, + (const dns_rdata_t *)rdata2)); +} + +/* + * Ensure that every key identified by the DS RRset has the same set of + * digest types. + */ +static bool +consistent_digests(dns_rdataset_t *dsset) { + isc_result_t result; + dns_rdata_t *arrdata; + dns_rdata_ds_t *ds; + dns_keytag_t key_tag; + dns_secalg_t algorithm; + bool match; + int i, j, n, d; + + /* + * First sort the dsset. DS rdata fields are tag, algorithm, digest, + * so sorting them brings together all the records for each key. + */ + + n = dns_rdataset_count(dsset); + + arrdata = isc_mem_get(mctx, n * sizeof(dns_rdata_t)); + + for (result = dns_rdataset_first(dsset), i = 0; result == ISC_R_SUCCESS; + result = dns_rdataset_next(dsset), i++) + { + dns_rdata_init(&arrdata[i]); + dns_rdataset_current(dsset, &arrdata[i]); + } + + qsort(arrdata, n, sizeof(dns_rdata_t), rdata_cmp); + + /* + * Convert sorted arrdata to more accessible format + */ + ds = isc_mem_get(mctx, n * sizeof(dns_rdata_ds_t)); + + for (i = 0; i < n; i++) { + result = dns_rdata_tostruct(&arrdata[i], &ds[i], NULL); + check_result(result, "dns_rdata_tostruct(DS)"); + } + + /* + * Count number of digest types (d) for first key + */ + key_tag = ds[0].key_tag; + algorithm = ds[0].algorithm; + for (d = 0, i = 0; i < n; i++, d++) { + if (ds[i].key_tag != key_tag || ds[i].algorithm != algorithm) { + break; + } + } + + /* + * Check subsequent keys match the first one + */ + match = true; + while (i < n) { + key_tag = ds[i].key_tag; + algorithm = ds[i].algorithm; + for (j = 0; j < d && i + j < n; j++) { + if (ds[i + j].key_tag != key_tag || + ds[i + j].algorithm != algorithm || + ds[i + j].digest_type != ds[j].digest_type) + { + match = false; + } + } + i += d; + } + + /* + * Done! + */ + isc_mem_put(mctx, ds, n * sizeof(dns_rdata_ds_t)); + isc_mem_put(mctx, arrdata, n * sizeof(dns_rdata_t)); + + return (match); +} + +static void +print_diff(const char *cmd, dns_rdataset_t *rdataset) { + isc_buffer_t *buf; + isc_region_t r; + unsigned char *nl; + size_t len; + + buf = formatset(rdataset); + isc_buffer_usedregion(buf, &r); + + while ((nl = memchr(r.base, '\n', r.length)) != NULL) { + len = nl - r.base + 1; + printf("update %s %.*s", cmd, (int)len, (char *)r.base); + isc_region_consume(&r, len); + } + + isc_buffer_free(&buf); +} + +static void +update_diff(const char *cmd, uint32_t ttl, dns_rdataset_t *addset, + dns_rdataset_t *delset) { + isc_result_t result; + dns_db_t *db; + dns_dbnode_t *node; + dns_dbversion_t *ver; + dns_rdataset_t diffset; + uint32_t save; + + db = NULL; + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0, + NULL, &db); + check_result(result, "dns_db_create()"); + + ver = NULL; + result = dns_db_newversion(db, &ver); + check_result(result, "dns_db_newversion()"); + + node = NULL; + result = dns_db_findnode(db, name, true, &node); + check_result(result, "dns_db_findnode()"); + + dns_rdataset_init(&diffset); + + result = dns_db_addrdataset(db, node, ver, 0, addset, DNS_DBADD_MERGE, + NULL); + check_result(result, "dns_db_addrdataset()"); + + result = dns_db_subtractrdataset(db, node, ver, delset, 0, &diffset); + if (result == DNS_R_UNCHANGED) { + save = addset->ttl; + addset->ttl = ttl; + print_diff(cmd, addset); + addset->ttl = save; + } else if (result != DNS_R_NXRRSET) { + check_result(result, "dns_db_subtractrdataset()"); + diffset.ttl = ttl; + print_diff(cmd, &diffset); + dns_rdataset_disassociate(&diffset); + } + + dns_db_detachnode(db, &node); + dns_db_closeversion(db, &ver, false); + dns_db_detach(&db); +} + +static void +nsdiff(uint32_t ttl, dns_rdataset_t *oldset, dns_rdataset_t *newset) { + if (ttl == 0) { + vbprintf(1, "warning: no TTL in nsupdate script\n"); + } + update_diff("add", ttl, newset, oldset); + update_diff("del", 0, oldset, newset); + if (verbose > 0) { + printf("show\nsend\nanswer\n"); + } else { + printf("send\n"); + } + if (fflush(stdout) == EOF) { + fatal("write stdout: %s", strerror(errno)); + } +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, + " %s options [options] -f -d \n", + program); + fprintf(stderr, "Version: %s\n", VERSION); + fprintf(stderr, "Options:\n" + " -a digest algorithm (SHA-1 / " + "SHA-256 / SHA-384)\n" + " -c of domain (default IN)\n" + " -D prefer CDNSKEY records instead " + "of CDS\n" + " -d where to find parent dsset- " + "file\n" + " -f child DNSKEY+CDNSKEY+CDS+RRSIG " + "records\n" + " -i[extension] update dsset- file in place\n" + " -s oldest permitted child " + "signatures\n" + " -u emit nsupdate script\n" + " -T TTL of DS records\n" + " -V print version\n" + " -v \n"); + exit(1); +} + +int +main(int argc, char *argv[]) { + const char *child_path = NULL; + const char *ds_path = NULL; + const char *inplace = NULL; + isc_result_t result; + bool prefer_cdnskey = false; + bool nsupdate = false; + uint32_t ttl = 0; + int ch; + char *endp; + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + +#define OPTIONS "a:c:Dd:f:i:ms:T:uv:V" + while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) { + switch (ch) { + case 'a': + add_dtype(strtodsdigest(isc_commandline_argument)); + break; + case 'c': + rdclass = strtoclass(isc_commandline_argument); + break; + case 'D': + prefer_cdnskey = true; + break; + case 'd': + ds_path = isc_commandline_argument; + break; + case 'f': + child_path = isc_commandline_argument; + break; + case 'i': + /* + * This is a bodge to make the argument optional, + * so that it works just like sed(1). + */ + if (isc_commandline_argument == + argv[isc_commandline_index - 1]) + { + isc_commandline_index--; + inplace = ""; + } else { + inplace = isc_commandline_argument; + } + break; + case 'm': + isc_mem_debugging = ISC_MEM_DEBUGTRACE | + ISC_MEM_DEBUGRECORD; + break; + case 's': + startstr = isc_commandline_argument; + break; + case 'T': + ttl = strtottl(isc_commandline_argument); + break; + case 'u': + nsupdate = true; + break; + case 'V': + /* Does not return. */ + 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(); + break; + } + } + argv += isc_commandline_index; + argc -= isc_commandline_index; + + if (argc != 1) { + usage(); + } + initname(argv[0]); + + /* + * Default digest type if none specified. + */ + if (dtype[0] == 0) { + dtype[0] = DNS_DSDIGEST_SHA256; + } + + setup_logging(mctx, &lctx); + + result = dst_lib_init(mctx, NULL); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", + isc_result_totext(result)); + } + + if (ds_path == NULL) { + fatal("missing -d DS pathname"); + } + load_parent_set(ds_path); + + /* + * Preserve the TTL if it wasn't overridden. + */ + if (ttl == 0) { + ttl = old_ds_set.ttl; + } + + if (child_path == NULL) { + fatal("path to file containing child data must be specified"); + } + + load_child_sets(child_path); + + /* + * Check child records have accompanying RRSIGs and DNSKEYs + */ + + if (!dns_rdataset_isassociated(&dnskey_set) || + !dns_rdataset_isassociated(&dnskey_sig)) + { + fatal("could not find signed DNSKEY RRset for %s", namestr); + } + + if (dns_rdataset_isassociated(&cdnskey_set) && + !dns_rdataset_isassociated(&cdnskey_sig)) + { + fatal("missing RRSIG CDNSKEY records for %s", namestr); + } + if (dns_rdataset_isassociated(&cds_set) && + !dns_rdataset_isassociated(&cds_sig)) + { + fatal("missing RRSIG CDS records for %s", namestr); + } + + vbprintf(1, "which child DNSKEY records match parent DS records?\n"); + old_key_tbl = match_keyset_dsset(&dnskey_set, &old_ds_set, LOOSE); + + /* + * We have now identified the keys that are allowed to authenticate + * the DNSKEY RRset (RFC 4035 section 5.2 bullet 2), and CDNSKEY and + * CDS RRsets (RFC 7344 section 4.1 bullet 2). + */ + + vbprintf(1, "verify DNSKEY signature(s)\n"); + if (!signed_loose(matching_sigs(old_key_tbl, &dnskey_set, &dnskey_sig))) + { + fatal("could not validate child DNSKEY RRset for %s", namestr); + } + + if (dns_rdataset_isassociated(&cdnskey_set)) { + vbprintf(1, "verify CDNSKEY signature(s)\n"); + if (!signed_loose(matching_sigs(old_key_tbl, &cdnskey_set, + &cdnskey_sig))) + { + fatal("could not validate child CDNSKEY RRset for %s", + namestr); + } + } + if (dns_rdataset_isassociated(&cds_set)) { + vbprintf(1, "verify CDS signature(s)\n"); + if (!signed_loose( + matching_sigs(old_key_tbl, &cds_set, &cds_sig))) + { + fatal("could not validate child CDS RRset for %s", + namestr); + } + } + + free_keytable(&old_key_tbl); + + /* + * Report the result of the replay attack protection checks + * used for the output file timestamp + */ + if (oldestsig.timesigned != 0 && verbose > 0) { + char type[32]; + dns_rdatatype_format(oldestsig.covered, type, sizeof(type)); + verbose_time(1, "child signature inception time", + oldestsig.timesigned); + vbprintf(2, "from RRSIG %s by key %d\n", type, oldestsig.keyid); + } + + /* + * Successfully do nothing if there's neither CDNSKEY nor CDS + * RFC 7344 section 4.1 first paragraph + */ + if (!dns_rdataset_isassociated(&cdnskey_set) && + !dns_rdataset_isassociated(&cds_set)) + { + vbprintf(1, "%s has neither CDS nor CDNSKEY records\n", + namestr); + write_parent_set(ds_path, inplace, nsupdate, &old_ds_set); + exit(0); + } + + /* + * Make DS records from the CDS or CDNSKEY records + * Prefer CDS if present, unless run with -D + */ + if (prefer_cdnskey && dns_rdataset_isassociated(&cdnskey_set)) { + make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set); + } else if (dns_rdataset_isassociated(&cds_set)) { + make_new_ds_set(ds_from_cds, ttl, &cds_set); + } else { + make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set); + } + + /* + * Now we have a candidate DS RRset, we need to check it + * won't break the delegation. + */ + vbprintf(1, "which child DNSKEY records match new DS records?\n"); + new_key_tbl = match_keyset_dsset(&dnskey_set, &new_ds_set, TIGHT); + + if (!consistent_digests(&new_ds_set)) { + fatal("CDS records at %s do not cover each key " + "with the same set of digest types", + namestr); + } + + vbprintf(1, "verify DNSKEY signature(s)\n"); + if (!signed_strict(&new_ds_set, matching_sigs(new_key_tbl, &dnskey_set, + &dnskey_sig))) + { + fatal("could not validate child DNSKEY RRset " + "with new DS records for %s", + namestr); + } + + free_keytable(&new_key_tbl); + + /* + * OK, it's all good! + */ + if (nsupdate) { + nsdiff(ttl, &old_ds_set, &new_ds_set); + } + + write_parent_set(ds_path, inplace, nsupdate, &new_ds_set); + + free_all_sets(); + cleanup_logging(&lctx); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_destroy(&mctx); + + exit(0); +} diff --git a/bin/dnssec/dnssec-cds.rst b/bin/dnssec/dnssec-cds.rst new file mode 100644 index 0000000..4ba77fb --- /dev/null +++ b/bin/dnssec/dnssec-cds.rst @@ -0,0 +1,203 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-cds: + +dnssec-cds - change DS records for a child zone based on CDS/CDNSKEY +-------------------------------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`dnssec-cds` [**-a** alg...] [**-c** class] [**-D**] {**-d** dsset-file} {**-f** child-file} [**-i**[extension]] [**-s** start-time] [**-T** ttl] [**-u**] [**-v** level] [**-V**] {domain} + +Description +~~~~~~~~~~~ + +The ``dnssec-cds`` command changes DS records at a delegation point +based on CDS or CDNSKEY records published in the child zone. If both CDS +and CDNSKEY records are present in the child zone, the CDS is preferred. +This enables a child zone to inform its parent of upcoming changes to +its key-signing keys (KSKs); by polling periodically with ``dnssec-cds``, the +parent can keep the DS records up-to-date and enable automatic rolling +of KSKs. + +Two input files are required. The ``-f child-file`` option specifies a +file containing the child's CDS and/or CDNSKEY records, plus RRSIG and +DNSKEY records so that they can be authenticated. The ``-d path`` option +specifies the location of a file containing the current DS records. For +example, this could be a ``dsset-`` file generated by +``dnssec-signzone``, or the output of ``dnssec-dsfromkey``, or the +output of a previous run of ``dnssec-cds``. + +The ``dnssec-cds`` command uses special DNSSEC validation logic +specified by :rfc:`7344`. It requires that the CDS and/or CDNSKEY records +be validly signed by a key represented in the existing DS records. This +is typically the pre-existing KSK. + +For protection against replay attacks, the signatures on the child +records must not be older than they were on a previous run of +``dnssec-cds``. Their age is obtained from the modification time of the +``dsset-`` file, or from the ``-s`` option. + +To protect against breaking the delegation, ``dnssec-cds`` ensures that +the DNSKEY RRset can be verified by every key algorithm in the new DS +RRset, and that the same set of keys are covered by every DS digest +type. + +By default, replacement DS records are written to the standard output; +with the ``-i`` option the input file is overwritten in place. The +replacement DS records are the same as the existing records, when no +change is required. The output can be empty if the CDS/CDNSKEY records +specify that the child zone wants to be insecure. + +.. warning:: + + Be careful not to delete the DS records when ``dnssec-cds`` fails! + +Alternatively, ``dnssec-cds -u`` writes an ``nsupdate`` script to the +standard output. The ``-u`` and ``-i`` options can be used together to +maintain a ``dsset-`` file as well as emit an ``nsupdate`` script. + +Options +~~~~~~~ + +``-a algorithm`` + This option specifies a digest algorithm to use when converting CDNSKEY records to + DS records. This option can be repeated, so that multiple DS records + are created for each CDNSKEY record. This option has no effect when + using CDS records. + + The algorithm must be one of SHA-1, SHA-256, or SHA-384. These values + are case-insensitive, and the hyphen may be omitted. If no algorithm + is specified, the default is SHA-256. + +``-c class`` + This option specifies the DNS class of the zones. + +``-D`` + This option generates DS records from CDNSKEY records if both CDS and CDNSKEY + records are present in the child zone. By default CDS records are + preferred. + +``-d path`` + This specifies the location of the parent DS records. The path can be the name of a file + containing the DS records; if it is a directory, ``dnssec-cds`` + looks for a ``dsset-`` file for the domain inside the directory. + + To protect against replay attacks, child records are rejected if they + were signed earlier than the modification time of the ``dsset-`` + file. This can be adjusted with the ``-s`` option. + +``-f child-file`` + This option specifies the file containing the child's CDS and/or CDNSKEY records, plus its + DNSKEY records and the covering RRSIG records, so that they can be + authenticated. + + The examples below describe how to generate this file. + +``-iextension`` + This option updates the ``dsset-`` file in place, instead of writing DS records to + the standard output. + + There must be no space between the ``-i`` and the extension. If + no extension is provided, the old ``dsset-`` is discarded. If an + extension is present, a backup of the old ``dsset-`` file is kept + with the extension appended to its filename. + + To protect against replay attacks, the modification time of the + ``dsset-`` file is set to match the signature inception time of the + child records, provided that it is later than the file's current + modification time. + +``-s start-time`` + This option specifies the date and time after which RRSIG records become + acceptable. This can be either an absolute or a relative time. An + absolute start time is indicated by a number in YYYYMMDDHHMMSS + notation; 20170827133700 denotes 13:37:00 UTC on August 27th, 2017. A + time relative to the ``dsset-`` file is indicated with ``-N``, which is N + seconds before the file modification time. A time relative to the + current time is indicated with ``now+N``. + + If no start-time is specified, the modification time of the + ``dsset-`` file is used. + +``-T ttl`` + This option specifies a TTL to be used for new DS records. If not specified, the + default is the TTL of the old DS records. If they had no explicit TTL, + the new DS records also have no explicit TTL. + +``-u`` + This option writes an ``nsupdate`` script to the standard output, instead of + printing the new DS reords. The output is empty if no change is + needed. + + Note: The TTL of new records needs to be specified: it can be done in the + original ``dsset-`` file, with the ``-T`` option, or using the + ``nsupdate`` ``ttl`` command. + +``-V`` + This option prints version information. + +``-v level`` + This option sets the debugging level. Level 1 is intended to be usefully verbose + for general users; higher levels are intended for developers. + +``domain`` + This indicates the name of the delegation point/child zone apex. + +Exit Status +~~~~~~~~~~~ + +The ``dnssec-cds`` command exits 0 on success, or non-zero if an error +occurred. + +If successful, the DS records may or may not need to be +changed. + +Examples +~~~~~~~~ + +Before running ``dnssec-signzone``, ensure that the delegations +are up-to-date by running ``dnssec-cds`` on every ``dsset-`` file. + +To fetch the child records required by ``dnssec-cds``, invoke +``dig`` as in the script below. It is acceptable if the ``dig`` fails, since +``dnssec-cds`` performs all the necessary checking. + +:: + + for f in dsset-* + do + d=${f#dsset-} + dig +dnssec +noall +answer $d DNSKEY $d CDNSKEY $d CDS | + dnssec-cds -i -f /dev/stdin -d $f $d + done + +When the parent zone is automatically signed by ``named``, +``dnssec-cds`` can be used with ``nsupdate`` to maintain a delegation as follows. +The ``dsset-`` file allows the script to avoid having to fetch and +validate the parent DS records, and it maintains the replay attack +protection time. + +:: + + dig +dnssec +noall +answer $d DNSKEY $d CDNSKEY $d CDS | + dnssec-cds -u -i -f /dev/stdin -d $f $d | + nsupdate -l + +See Also +~~~~~~~~ + +:manpage:`dig(1)`, :manpage:`dnssec-settime(8)`, :manpage:`dnssec-signzone(8)`, :manpage:`nsupdate(1)`, BIND 9 Administrator +Reference Manual, :rfc:`7344`. diff --git a/bin/dnssec/dnssec-dsfromkey.c b/bin/dnssec/dnssec-dsfromkey.c new file mode 100644 index 0000000..404239e --- /dev/null +++ b/bin/dnssec/dnssec-dsfromkey.c @@ -0,0 +1,568 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-dsfromkey"; + +static dns_rdataclass_t rdclass; +static dns_fixedname_t fixed; +static dns_name_t *name = NULL; +static isc_mem_t *mctx = NULL; +static uint32_t ttl; +static bool emitttl = false; + +static isc_result_t +initname(char *setname) { + isc_result_t result; + isc_buffer_t buf; + + name = dns_fixedname_initname(&fixed); + + isc_buffer_init(&buf, setname, strlen(setname)); + isc_buffer_add(&buf, strlen(setname)); + result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); + return (result); +} + +static void +db_load_from_stream(dns_db_t *db, FILE *fp) { + isc_result_t result; + dns_rdatacallbacks_t callbacks; + + dns_rdatacallbacks_init(&callbacks); + result = dns_db_beginload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + fatal("dns_db_beginload failed: %s", isc_result_totext(result)); + } + + result = dns_master_loadstream(fp, name, name, rdclass, 0, &callbacks, + mctx); + if (result != ISC_R_SUCCESS) { + fatal("can't load from input: %s", isc_result_totext(result)); + } + + result = dns_db_endload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + fatal("dns_db_endload failed: %s", isc_result_totext(result)); + } +} + +static isc_result_t +loadset(const char *filename, dns_rdataset_t *rdataset) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + char setname[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, setname, sizeof(setname)); + + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0, + NULL, &db); + if (result != ISC_R_SUCCESS) { + fatal("can't create database"); + } + + if (strcmp(filename, "-") == 0) { + db_load_from_stream(db, stdin); + filename = "input"; + } else { + result = dns_db_load(db, filename, dns_masterformat_text, 0); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + fatal("can't load %s: %s", filename, + isc_result_totext(result)); + } + } + + result = dns_db_findnode(db, name, false, &node); + if (result != ISC_R_SUCCESS) { + fatal("can't find %s node in %s", setname, filename); + } + + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0, + rdataset, NULL); + + if (result == ISC_R_NOTFOUND) { + fatal("no DNSKEY RR for %s in %s", setname, filename); + } else if (result != ISC_R_SUCCESS) { + fatal("dns_db_findrdataset"); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + return (result); +} + +static isc_result_t +loadkeyset(char *dirname, dns_rdataset_t *rdataset) { + isc_result_t result; + char filename[PATH_MAX + 1]; + isc_buffer_t buf; + + dns_rdataset_init(rdataset); + + isc_buffer_init(&buf, filename, sizeof(filename)); + if (dirname != NULL) { + /* allow room for a trailing slash */ + if (strlen(dirname) >= isc_buffer_availablelength(&buf)) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(&buf, dirname); + if (dirname[strlen(dirname) - 1] != '/') { + isc_buffer_putstr(&buf, "/"); + } + } + + if (isc_buffer_availablelength(&buf) < 7) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(&buf, "keyset-"); + + result = dns_name_tofilenametext(name, false, &buf); + check_result(result, "dns_name_tofilenametext()"); + if (isc_buffer_availablelength(&buf) == 0) { + return (ISC_R_NOSPACE); + } + isc_buffer_putuint8(&buf, 0); + + return (loadset(filename, rdataset)); +} + +static void +loadkey(char *filename, unsigned char *key_buf, unsigned int key_buf_size, + dns_rdata_t *rdata) { + isc_result_t result; + dst_key_t *key = NULL; + isc_buffer_t keyb; + isc_region_t r; + + dns_rdata_init(rdata); + + isc_buffer_init(&keyb, key_buf, key_buf_size); + + result = dst_key_fromnamedfile(filename, NULL, DST_TYPE_PUBLIC, mctx, + &key); + if (result != ISC_R_SUCCESS) { + fatal("can't load %s.key: %s", filename, + isc_result_totext(result)); + } + + if (verbose > 2) { + char keystr[DST_KEY_FORMATSIZE]; + + dst_key_format(key, keystr, sizeof(keystr)); + fprintf(stderr, "%s: %s\n", program, keystr); + } + + result = dst_key_todns(key, &keyb); + if (result != ISC_R_SUCCESS) { + fatal("can't decode key"); + } + + isc_buffer_usedregion(&keyb, &r); + dns_rdata_fromregion(rdata, dst_key_class(key), dns_rdatatype_dnskey, + &r); + + rdclass = dst_key_class(key); + + name = dns_fixedname_initname(&fixed); + dns_name_copynf(dst_key_name(key), name); + + dst_key_free(&key); +} + +static void +logkey(dns_rdata_t *rdata) { + isc_result_t result; + dst_key_t *key = NULL; + isc_buffer_t buf; + char keystr[DST_KEY_FORMATSIZE]; + + isc_buffer_init(&buf, rdata->data, rdata->length); + isc_buffer_add(&buf, rdata->length); + result = dst_key_fromdns(name, rdclass, &buf, mctx, &key); + if (result != ISC_R_SUCCESS) { + return; + } + + dst_key_format(key, keystr, sizeof(keystr)); + fprintf(stderr, "%s: %s\n", program, keystr); + + dst_key_free(&key); +} + +static void +emit(dns_dsdigest_t dt, bool showall, bool cds, dns_rdata_t *rdata) { + isc_result_t result; + unsigned char buf[DNS_DS_BUFFERSIZE]; + char text_buf[DST_KEY_MAXTEXTSIZE]; + char name_buf[DNS_NAME_MAXWIRE]; + char class_buf[10]; + isc_buffer_t textb, nameb, classb; + isc_region_t r; + dns_rdata_t ds; + dns_rdata_dnskey_t dnskey; + + isc_buffer_init(&textb, text_buf, sizeof(text_buf)); + isc_buffer_init(&nameb, name_buf, sizeof(name_buf)); + isc_buffer_init(&classb, class_buf, sizeof(class_buf)); + + dns_rdata_init(&ds); + + result = dns_rdata_tostruct(rdata, &dnskey, NULL); + if (result != ISC_R_SUCCESS) { + fatal("can't convert DNSKEY"); + } + + if ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) { + return; + } + + if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && !showall) { + return; + } + + result = dns_ds_buildrdata(name, rdata, dt, buf, &ds); + if (result != ISC_R_SUCCESS) { + fatal("can't build record"); + } + + result = dns_name_totext(name, false, &nameb); + if (result != ISC_R_SUCCESS) { + fatal("can't print name"); + } + + result = dns_rdata_tofmttext(&ds, (dns_name_t *)NULL, 0, 0, 0, "", + &textb); + + if (result != ISC_R_SUCCESS) { + fatal("can't print rdata"); + } + + result = dns_rdataclass_totext(rdclass, &classb); + if (result != ISC_R_SUCCESS) { + fatal("can't print class"); + } + + isc_buffer_usedregion(&nameb, &r); + printf("%.*s ", (int)r.length, r.base); + + if (emitttl) { + printf("%u ", ttl); + } + + isc_buffer_usedregion(&classb, &r); + printf("%.*s", (int)r.length, r.base); + + if (cds) { + printf(" CDS "); + } else { + printf(" DS "); + } + + isc_buffer_usedregion(&textb, &r); + printf("%.*s\n", (int)r.length, r.base); +} + +static void +emits(bool showall, bool cds, dns_rdata_t *rdata) { + unsigned i, n; + + n = sizeof(dtype) / sizeof(dtype[0]); + for (i = 0; i < n; i++) { + if (dtype[i] != 0) { + emit(dtype[i], showall, cds, rdata); + } + } +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s [options] keyfile\n\n", program); + fprintf(stderr, " %s [options] -f zonefile [zonename]\n\n", program); + fprintf(stderr, " %s [options] -s dnsname\n\n", program); + fprintf(stderr, " %s [-h|-V]\n\n", program); + fprintf(stderr, "Version: %s\n", VERSION); + fprintf(stderr, "Options:\n" + " -1: digest algorithm SHA-1\n" + " -2: digest algorithm SHA-256\n" + " -a algorithm: digest algorithm (SHA-1, SHA-256 or " + "SHA-384)\n" + " -A: include all keys in DS set, not just KSKs (-f " + "only)\n" + " -c class: rdata class for DS set (default IN) (-f " + "or -s only)\n" + " -C: print CDS records\n" + " -f zonefile: read keys from a zone file\n" + " -h: print help information\n" + " -K directory: where to find key or keyset files\n" + " -s: read keys from keyset- file\n" + " -T: TTL of output records (omitted by default)\n" + " -v level: verbosity\n" + " -V: print version information\n"); + fprintf(stderr, "Output: DS or CDS RRs\n"); + + exit(-1); +} + +int +main(int argc, char **argv) { + char *classname = NULL; + char *filename = NULL, *dir = NULL, *namestr; + char *endp, *arg1; + int ch; + bool cds = false; + bool usekeyset = false; + bool showall = false; + isc_result_t result; + isc_log_t *log = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata; + + dns_rdata_init(&rdata); + + if (argc == 1) { + usage(); + } + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + +#define OPTIONS "12Aa:Cc:d:Ff:K:l:sT:v:hV" + while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) { + switch (ch) { + case '1': + add_dtype(DNS_DSDIGEST_SHA1); + break; + case '2': + add_dtype(DNS_DSDIGEST_SHA256); + break; + case 'A': + showall = true; + break; + case 'a': + add_dtype(strtodsdigest(isc_commandline_argument)); + break; + case 'C': + cds = true; + break; + case 'c': + classname = isc_commandline_argument; + break; + case 'd': + fprintf(stderr, + "%s: the -d option is deprecated; " + "use -K\n", + program); + /* fall through */ + case 'K': + dir = isc_commandline_argument; + if (strlen(dir) == 0U) { + fatal("directory must be non-empty string"); + } + break; + case 'f': + filename = isc_commandline_argument; + break; + case 'l': + fatal("-l option (DLV lookaside) is obsolete"); + break; + case 's': + usekeyset = true; + break; + case 'T': + emitttl = true; + ttl = strtottl(isc_commandline_argument); + break; + case 'v': + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("-v must be followed by a number"); + } + break; + case 'F': + /* Reserved for FIPS mode */ + FALLTHROUGH; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + rdclass = strtoclass(classname); + + if (usekeyset && filename != NULL) { + fatal("cannot use both -s and -f"); + } + + /* When not using -f, -A is implicit */ + if (filename == NULL) { + showall = true; + } + + /* Default digest type if none specified. */ + if (dtype[0] == 0) { + dtype[0] = DNS_DSDIGEST_SHA256; + } + + /* + * Use local variable arg1 so that clang can correctly analyse + * reachable paths rather than 'argc < isc_commandline_index + 1'. + */ + arg1 = argv[isc_commandline_index]; + if (arg1 == NULL && filename == NULL) { + fatal("the key file name was not specified"); + } + if (arg1 != NULL && argv[isc_commandline_index + 1] != NULL) { + fatal("extraneous arguments"); + } + + result = dst_lib_init(mctx, NULL); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", + isc_result_totext(result)); + } + + setup_logging(mctx, &log); + + dns_rdataset_init(&rdataset); + + if (usekeyset || filename != NULL) { + if (arg1 == NULL) { + /* using file name as the zone name */ + namestr = filename; + } else { + namestr = arg1; + } + + result = initname(namestr); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize name %s", namestr); + } + + if (usekeyset) { + result = loadkeyset(dir, &rdataset); + } else { + INSIST(filename != NULL); + result = loadset(filename, &rdataset); + } + + if (result != ISC_R_SUCCESS) { + fatal("could not load DNSKEY set: %s\n", + isc_result_totext(result)); + } + + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + + if (verbose > 2) { + logkey(&rdata); + } + + emits(showall, cds, &rdata); + } + } else { + unsigned char key_buf[DST_KEY_MAXSIZE]; + + loadkey(arg1, key_buf, DST_KEY_MAXSIZE, &rdata); + + emits(showall, cds, &rdata); + } + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + cleanup_logging(&log); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_destroy(&mctx); + + fflush(stdout); + if (ferror(stdout)) { + fprintf(stderr, "write error\n"); + return (1); + } else { + return (0); + } +} diff --git a/bin/dnssec/dnssec-dsfromkey.rst b/bin/dnssec/dnssec-dsfromkey.rst new file mode 100644 index 0000000..6396733 --- /dev/null +++ b/bin/dnssec/dnssec-dsfromkey.rst @@ -0,0 +1,144 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-dsfromkey: + +dnssec-dsfromkey - DNSSEC DS RR generation tool +----------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`dnssec-dsfromkey` [ **-1** | **-2** | **-a** alg ] [ **-C** ] [**-T** TTL] [**-v** level] [**-K** directory] {keyfile} + +:program:`dnssec-dsfromkey` [ **-1** | **-2** | **-a** alg ] [ **-C** ] [**-T** TTL] [**-v** level] [**-c** class] [**-A**] {**-f** file} [dnsname] + +:program:`dnssec-dsfromkey` [ **-1** | **-2** | **-a** alg ] [ **-C** ] [**-T** TTL] [**-v** level] [**-c** class] [**-K** directory] {**-s**} {dnsname} + +:program:`dnssec-dsfromkey` [ **-h** | **-V** ] + +Description +~~~~~~~~~~~ + +The ``dnssec-dsfromkey`` command outputs DS (Delegation Signer) resource records +(RRs), or CDS (Child DS) RRs with the ``-C`` option. + +By default, only KSKs are converted (keys with flags = 257). The +``-A`` option includes ZSKs (flags = 256). Revoked keys are never +included. + +The input keys can be specified in a number of ways: + +By default, ``dnssec-dsfromkey`` reads a key file named in the format +``Knnnn.+aaa+iiiii.key``, as generated by ``dnssec-keygen``. + +With the ``-f file`` option, ``dnssec-dsfromkey`` reads keys from a zone +file or partial zone file (which can contain just the DNSKEY records). + +With the ``-s`` option, ``dnssec-dsfromkey`` reads a ``keyset-`` file, +as generated by ``dnssec-keygen`` ``-C``. + +Options +~~~~~~~ + +``-1`` + This option is an abbreviation for ``-a SHA1``. + +``-2`` + This option is an abbreviation for ``-a SHA-256``. + +``-a algorithm`` + This option specifies a digest algorithm to use when converting DNSKEY records to + DS records. This option can be repeated, so that multiple DS records + are created for each DNSKEY record. + + The algorithm must be one of SHA-1, SHA-256, or SHA-384. These values + are case-insensitive, and the hyphen may be omitted. If no algorithm + is specified, the default is SHA-256. + +``-A`` + This option indicates that ZSKs are to be included when generating DS records. Without this option, only + keys which have the KSK flag set are converted to DS records and + printed. This option is only useful in ``-f`` zone file mode. + +``-c class`` + This option specifies the DNS class; the default is IN. This option is only useful in ``-s`` keyset + or ``-f`` zone file mode. + +``-C`` + This option generates CDS records rather than DS records. + +``-f file`` + This option sets zone file mode, in which the final dnsname argument of ``dnssec-dsfromkey`` is the + DNS domain name of a zone whose master file can be read from + ``file``. If the zone name is the same as ``file``, then it may be + omitted. + + If ``file`` is ``-``, then the zone data is read from the standard + input. This makes it possible to use the output of the ``dig`` + command as input, as in: + + ``dig dnskey example.com | dnssec-dsfromkey -f - example.com`` + +``-h`` + This option prints usage information. + +``-K directory`` + This option tells BIND 9 to look for key files or ``keyset-`` files in ``directory``. + +``-s`` + This option enables keyset mode, in which the final dnsname argument from ``dnssec-dsfromkey`` is the DNS + domain name used to locate a ``keyset-`` file. + +``-T TTL`` + This option specifies the TTL of the DS records. By default the TTL is omitted. + +``-v level`` + This option sets the debugging level. + +``-V`` + This option prints version information. + +Example +~~~~~~~ + +To build the SHA-256 DS RR from the ``Kexample.com.+003+26160`` keyfile, +issue the following command: + +``dnssec-dsfromkey -2 Kexample.com.+003+26160`` + +The command returns something similar to: + +``example.com. IN DS 26160 5 2 3A1EADA7A74B8D0BA86726B0C227AA85AB8BBD2B2004F41A868A54F0C5EA0B94`` + +Files +~~~~~ + +The keyfile can be designated by the key identification +``Knnnn.+aaa+iiiii`` or the full file name ``Knnnn.+aaa+iiiii.key``, as +generated by ``dnssec-keygen``. + +The keyset file name is built from the ``directory``, the string +``keyset-``, and the ``dnsname``. + +Caveat +~~~~~~ + +A keyfile error may return "file not found," even if the file exists. + +See Also +~~~~~~~~ + +:manpage:`dnssec-keygen(8)`, :manpage:`dnssec-signzone(8)`, BIND 9 Administrator Reference Manual, +:rfc:`3658` (DS RRs), :rfc:`4509` (SHA-256 for DS RRs), +:rfc:`6605` (SHA-384 for DS RRs), :rfc:`7344` (CDS and CDNSKEY RRs). diff --git a/bin/dnssec/dnssec-importkey.c b/bin/dnssec/dnssec-importkey.c new file mode 100644 index 0000000..8a776c1 --- /dev/null +++ b/bin/dnssec/dnssec-importkey.c @@ -0,0 +1,485 @@ +/* + * 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 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-importkey"; + +static dns_rdataclass_t rdclass; +static dns_fixedname_t fixed; +static dns_name_t *name = NULL; +static isc_mem_t *mctx = NULL; +static bool setpub = false, setdel = false; +static bool setttl = false; +static isc_stdtime_t pub = 0, del = 0; +static dns_ttl_t ttl = 0; +static isc_stdtime_t syncadd = 0, syncdel = 0; +static bool setsyncadd = false; +static bool setsyncdel = false; + +static isc_result_t +initname(char *setname) { + isc_result_t result; + isc_buffer_t buf; + + name = dns_fixedname_initname(&fixed); + + isc_buffer_init(&buf, setname, strlen(setname)); + isc_buffer_add(&buf, strlen(setname)); + result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); + return (result); +} + +static void +db_load_from_stream(dns_db_t *db, FILE *fp) { + isc_result_t result; + dns_rdatacallbacks_t callbacks; + + dns_rdatacallbacks_init(&callbacks); + result = dns_db_beginload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + fatal("dns_db_beginload failed: %s", isc_result_totext(result)); + } + + result = dns_master_loadstream(fp, name, name, rdclass, 0, &callbacks, + mctx); + if (result != ISC_R_SUCCESS) { + fatal("can't load from input: %s", isc_result_totext(result)); + } + + result = dns_db_endload(db, &callbacks); + if (result != ISC_R_SUCCESS) { + fatal("dns_db_endload failed: %s", isc_result_totext(result)); + } +} + +static isc_result_t +loadset(const char *filename, dns_rdataset_t *rdataset) { + isc_result_t result; + dns_db_t *db = NULL; + dns_dbnode_t *node = NULL; + char setname[DNS_NAME_FORMATSIZE]; + + dns_name_format(name, setname, sizeof(setname)); + + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0, + NULL, &db); + if (result != ISC_R_SUCCESS) { + fatal("can't create database"); + } + + if (strcmp(filename, "-") == 0) { + db_load_from_stream(db, stdin); + filename = "input"; + } else { + result = dns_db_load(db, filename, dns_masterformat_text, + DNS_MASTER_NOTTL); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + fatal("can't load %s: %s", filename, + isc_result_totext(result)); + } + } + + result = dns_db_findnode(db, name, false, &node); + if (result != ISC_R_SUCCESS) { + fatal("can't find %s node in %s", setname, filename); + } + + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0, + rdataset, NULL); + + if (result == ISC_R_NOTFOUND) { + fatal("no DNSKEY RR for %s in %s", setname, filename); + } else if (result != ISC_R_SUCCESS) { + fatal("dns_db_findrdataset"); + } + + if (node != NULL) { + dns_db_detachnode(db, &node); + } + if (db != NULL) { + dns_db_detach(&db); + } + return (result); +} + +static void +loadkey(char *filename, unsigned char *key_buf, unsigned int key_buf_size, + dns_rdata_t *rdata) { + isc_result_t result; + dst_key_t *key = NULL; + isc_buffer_t keyb; + isc_region_t r; + + dns_rdata_init(rdata); + + isc_buffer_init(&keyb, key_buf, key_buf_size); + + result = dst_key_fromnamedfile(filename, NULL, DST_TYPE_PUBLIC, mctx, + &key); + if (result != ISC_R_SUCCESS) { + fatal("invalid keyfile name %s: %s", filename, + isc_result_totext(result)); + } + + if (verbose > 2) { + char keystr[DST_KEY_FORMATSIZE]; + + dst_key_format(key, keystr, sizeof(keystr)); + fprintf(stderr, "%s: %s\n", program, keystr); + } + + result = dst_key_todns(key, &keyb); + if (result != ISC_R_SUCCESS) { + fatal("can't decode key"); + } + + isc_buffer_usedregion(&keyb, &r); + dns_rdata_fromregion(rdata, dst_key_class(key), dns_rdatatype_dnskey, + &r); + + rdclass = dst_key_class(key); + + name = dns_fixedname_initname(&fixed); + dns_name_copynf(dst_key_name(key), name); + + dst_key_free(&key); +} + +static void +emit(const char *dir, dns_rdata_t *rdata) { + isc_result_t result; + char keystr[DST_KEY_FORMATSIZE]; + char pubname[1024]; + char priname[1024]; + isc_buffer_t buf; + dst_key_t *key = NULL, *tmp = NULL; + + isc_buffer_init(&buf, rdata->data, rdata->length); + isc_buffer_add(&buf, rdata->length); + result = dst_key_fromdns(name, rdclass, &buf, mctx, &key); + if (result != ISC_R_SUCCESS) { + fatal("dst_key_fromdns: %s", isc_result_totext(result)); + } + + isc_buffer_init(&buf, pubname, sizeof(pubname)); + result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, dir, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build public key filename: %s", + isc_result_totext(result)); + } + isc_buffer_init(&buf, priname, sizeof(priname)); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, dir, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build private key filename: %s", + isc_result_totext(result)); + } + + result = dst_key_fromfile( + dst_key_name(key), dst_key_id(key), dst_key_alg(key), + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, dir, mctx, &tmp); + if (result == ISC_R_SUCCESS) { + if (dst_key_isprivate(tmp) && !dst_key_isexternal(tmp)) { + fatal("Private key already exists in %s", priname); + } + dst_key_free(&tmp); + } + + dst_key_setexternal(key, true); + if (setpub) { + dst_key_settime(key, DST_TIME_PUBLISH, pub); + } + if (setdel) { + dst_key_settime(key, DST_TIME_DELETE, del); + } + if (setsyncadd) { + dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncadd); + } + if (setsyncdel) { + dst_key_settime(key, DST_TIME_SYNCDELETE, syncdel); + } + + if (setttl) { + dst_key_setttl(key, ttl); + } + + result = dst_key_tofile(key, DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, dir); + if (result != ISC_R_SUCCESS) { + dst_key_format(key, keystr, sizeof(keystr)); + fatal("Failed to write key %s: %s", keystr, + isc_result_totext(result)); + } + printf("%s\n", pubname); + + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, dir, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build private key filename: %s", + isc_result_totext(result)); + } + printf("%s\n", priname); + dst_key_free(&key); +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s options [-K dir] keyfile\n\n", program); + fprintf(stderr, " %s options -f file [keyname]\n\n", program); + fprintf(stderr, "Version: %s\n", VERSION); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -f file: read key from zone file\n"); + fprintf(stderr, " -K : directory in which to store " + "the key files\n"); + fprintf(stderr, " -L ttl: set default key TTL\n"); + fprintf(stderr, " -v \n"); + fprintf(stderr, " -V: print version information\n"); + fprintf(stderr, " -h: print usage and exit\n"); + fprintf(stderr, "Timing options:\n"); + fprintf(stderr, " -P date/[+-]offset/none: set/unset key " + "publication date\n"); + fprintf(stderr, " -P sync date/[+-]offset/none: set/unset " + "CDS and CDNSKEY publication date\n"); + fprintf(stderr, " -D date/[+-]offset/none: set/unset key " + "deletion date\n"); + fprintf(stderr, " -D sync date/[+-]offset/none: set/unset " + "CDS and CDNSKEY deletion date\n"); + + exit(-1); +} + +int +main(int argc, char **argv) { + char *classname = NULL; + char *filename = NULL, *dir = NULL, *namestr; + char *endp; + int ch; + isc_result_t result; + isc_log_t *log = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata; + isc_stdtime_t now; + + dns_rdata_init(&rdata); + isc_stdtime_get(&now); + + if (argc == 1) { + usage(); + } + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + +#define CMDLINE_FLAGS "D:f:hK:L:P:v:V" + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'D': + /* -Dsync ? */ + if (isoptarg("sync", argv, usage)) { + if (setsyncdel) { + fatal("-D sync specified more than " + "once"); + } + + syncdel = strtotime(isc_commandline_argument, + now, now, &setsyncdel); + break; + } + /* -Ddnskey ? */ + (void)isoptarg("dnskey", argv, usage); + if (setdel) { + fatal("-D specified more than once"); + } + + del = strtotime(isc_commandline_argument, now, now, + &setdel); + break; + case 'K': + dir = isc_commandline_argument; + if (strlen(dir) == 0U) { + fatal("directory must be non-empty string"); + } + break; + case 'L': + ttl = strtottl(isc_commandline_argument); + setttl = true; + break; + case 'P': + /* -Psync ? */ + if (isoptarg("sync", argv, usage)) { + if (setsyncadd) { + fatal("-P sync specified more than " + "once"); + } + + syncadd = strtotime(isc_commandline_argument, + now, now, &setsyncadd); + break; + } + /* -Pdnskey ? */ + (void)isoptarg("dnskey", argv, usage); + if (setpub) { + fatal("-P specified more than once"); + } + + pub = strtotime(isc_commandline_argument, now, now, + &setpub); + break; + case 'f': + filename = isc_commandline_argument; + break; + case 'v': + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("-v must be followed by a number"); + } + break; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + rdclass = strtoclass(classname); + + if (argc < isc_commandline_index + 1 && filename == NULL) { + fatal("the key file name was not specified"); + } + if (argc > isc_commandline_index + 1) { + fatal("extraneous arguments"); + } + + result = dst_lib_init(mctx, NULL); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", + isc_result_totext(result)); + } + + setup_logging(mctx, &log); + + dns_rdataset_init(&rdataset); + + if (filename != NULL) { + if (argc < isc_commandline_index + 1) { + /* using filename as zone name */ + namestr = filename; + } else { + namestr = argv[isc_commandline_index]; + } + + result = initname(namestr); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize name %s", namestr); + } + + result = loadset(filename, &rdataset); + + if (result != ISC_R_SUCCESS) { + fatal("could not load DNSKEY set: %s\n", + isc_result_totext(result)); + } + + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + emit(dir, &rdata); + } + } else { + unsigned char key_buf[DST_KEY_MAXSIZE]; + + loadkey(argv[isc_commandline_index], key_buf, DST_KEY_MAXSIZE, + &rdata); + + emit(dir, &rdata); + } + + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + cleanup_logging(&log); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_destroy(&mctx); + + fflush(stdout); + if (ferror(stdout)) { + fprintf(stderr, "write error\n"); + return (1); + } else { + return (0); + } +} diff --git a/bin/dnssec/dnssec-importkey.rst b/bin/dnssec/dnssec-importkey.rst new file mode 100644 index 0000000..9e58fce --- /dev/null +++ b/bin/dnssec/dnssec-importkey.rst @@ -0,0 +1,113 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-importkey: + +dnssec-importkey - import DNSKEY records from external systems so they can be managed +------------------------------------------------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`dnssec-importkey` [**-K** directory] [**-L** ttl] [**-P** date/offset] [**-P** sync date/offset] [**-D** date/offset] [**-D** sync date/offset] [**-h**] [**-v** level] [**-V**] {keyfile} + +:program:`dnssec-importkey` {**-f** filename} [**-K** directory] [**-L** ttl] [**-P** date/offset] [**-P** sync date/offset] [**-D** date/offset] [**-D** sync date/offset] [**-h**] [**-v** level] [**-V**] [dnsname] + +Description +~~~~~~~~~~~ + +``dnssec-importkey`` reads a public DNSKEY record and generates a pair +of .key/.private files. The DNSKEY record may be read from an +existing .key file, in which case a corresponding .private file is +generated, or it may be read from any other file or from the standard +input, in which case both .key and .private files are generated. + +The newly created .private file does *not* contain private key data, and +cannot be used for signing. However, having a .private file makes it +possible to set publication (``-P``) and deletion (``-D``) times for the +key, which means the public key can be added to and removed from the +DNSKEY RRset on schedule even if the true private key is stored offline. + +Options +~~~~~~~ + +``-f filename`` + This option indicates the zone file mode. Instead of a public keyfile name, the argument is the + DNS domain name of a zone master file, which can be read from + ``filename``. If the domain name is the same as ``filename``, then it may be + omitted. + + If ``filename`` is set to ``"-"``, then the zone data is read from the + standard input. + +``-K directory`` + This option sets the directory in which the key files are to reside. + +``-L ttl`` + This option sets the default TTL to use for this key when it is converted into a + DNSKEY RR. This is the TTL used when the key is imported into a zone, + unless there was already a DNSKEY RRset in + place, in which case the existing TTL takes precedence. Setting the default TTL to ``0`` or ``none`` + removes it from the key. + +``-h`` + This option emits a usage message and exits. + +``-v level`` + This option sets the debugging level. + +``-V`` + This option prints version information. + +Timing Options +~~~~~~~~~~~~~~ + +Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS. If the +argument begins with a ``+`` or ``-``, it is interpreted as an offset from +the present time. For convenience, if such an offset is followed by one +of the suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, then the offset is +computed in years (defined as 365 24-hour days, ignoring leap years), +months (defined as 30 24-hour days), weeks, days, hours, or minutes, +respectively. Without a suffix, the offset is computed in seconds. To +explicitly prevent a date from being set, use ``none`` or ``never``. + +``-P date/offset`` + This option sets the date on which a key is to be published to the zone. After + that date, the key is included in the zone but is not used + to sign it. + +``-P sync date/offset`` + This option sets the date on which CDS and CDNSKEY records that match this key + are to be published to the zone. + +``-D date/offset`` + This option sets the date on which the key is to be deleted. After that date, the + key is no longer included in the zone. (However, it may remain in the key + repository.) + +``-D sync date/offset`` + This option sets the date on which the CDS and CDNSKEY records that match this + key are to be deleted. + +Files +~~~~~ + +A keyfile can be designed by the key identification ``Knnnn.+aaa+iiiii`` +or the full file name ``Knnnn.+aaa+iiiii.key``, as generated by +``dnssec-keygen``. + +See Also +~~~~~~~~ + +:manpage:`dnssec-keygen(8)`, :manpage:`dnssec-signzone(8)`, BIND 9 Administrator Reference Manual, +:rfc:`5011`. diff --git a/bin/dnssec/dnssec-keyfromlabel.c b/bin/dnssec/dnssec-keyfromlabel.c new file mode 100644 index 0000000..26d8352 --- /dev/null +++ b/bin/dnssec/dnssec-keyfromlabel.c @@ -0,0 +1,782 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +#define MAX_RSA 4096 /* should be long enough... */ + +const char *program = "dnssec-keyfromlabel"; + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s -l label [options] name\n\n", program); + fprintf(stderr, "Version: %s\n", VERSION); + fprintf(stderr, "Required options:\n"); + fprintf(stderr, " -l label: label of the key pair\n"); + fprintf(stderr, " name: owner of the key\n"); + fprintf(stderr, "Other options:\n"); + fprintf(stderr, " -a algorithm: \n" + " DH | RSASHA1 |\n" + " NSEC3RSASHA1 |\n" + " RSASHA256 | RSASHA512 |\n" + " ECDSAP256SHA256 | ECDSAP384SHA384 |\n" + " ED25519 | ED448\n"); + fprintf(stderr, " -3: use NSEC3-capable algorithm\n"); + fprintf(stderr, " -c class (default: IN)\n"); + fprintf(stderr, " -E :\n"); +#if USE_PKCS11 + fprintf(stderr, + " path to PKCS#11 provider library " + "(default is %s)\n", + PK11_LIB_LOCATION); +#else /* if USE_PKCS11 */ + fprintf(stderr, " name of an OpenSSL engine to use\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, " -f keyflag: KSK | REVOKE\n"); + fprintf(stderr, " -K directory: directory in which to place " + "key files\n"); + fprintf(stderr, " -k: generate a TYPE=KEY key\n"); + fprintf(stderr, " -L ttl: default key TTL\n"); + fprintf(stderr, " -n nametype: ZONE | HOST | ENTITY | USER | " + "OTHER\n"); + fprintf(stderr, " (DNSKEY generation defaults to ZONE\n"); + fprintf(stderr, " -p protocol: default: 3 [dnssec]\n"); + fprintf(stderr, " -t type: " + "AUTHCONF | NOAUTHCONF | NOAUTH | NOCONF " + "(default: AUTHCONF)\n"); + fprintf(stderr, " -y: permit keys that might collide\n"); + fprintf(stderr, " -v verbose level\n"); + fprintf(stderr, " -V: print version information\n"); + fprintf(stderr, "Date options:\n"); + fprintf(stderr, " -P date/[+-]offset: set key publication date\n"); + fprintf(stderr, " -P sync date/[+-]offset: set CDS and CDNSKEY " + "publication date\n"); + fprintf(stderr, " -A date/[+-]offset: set key activation date\n"); + fprintf(stderr, " -R date/[+-]offset: set key revocation date\n"); + fprintf(stderr, " -I date/[+-]offset: set key inactivation date\n"); + fprintf(stderr, " -D date/[+-]offset: set key deletion date\n"); + fprintf(stderr, " -D sync date/[+-]offset: set CDS and CDNSKEY " + "deletion date\n"); + fprintf(stderr, " -G: generate key only; do not set -P or -A\n"); + fprintf(stderr, " -C: generate a backward-compatible key, omitting" + " all dates\n"); + fprintf(stderr, " -S : generate a successor to an existing " + "key\n"); + fprintf(stderr, " -i : prepublication interval for " + "successor key " + "(default: 30 days)\n"); + fprintf(stderr, "Output:\n"); + fprintf(stderr, " K++.key, " + "K++.private\n"); + + exit(-1); +} + +int +main(int argc, char **argv) { + char *algname = NULL, *freeit = NULL; + char *nametype = NULL, *type = NULL; + const char *directory = NULL; + const char *predecessor = NULL; + dst_key_t *prevkey = NULL; + const char *engine = NULL; + char *classname = NULL; + char *endp; + dst_key_t *key = NULL; + dns_fixedname_t fname; + dns_name_t *name; + uint16_t flags = 0, kskflag = 0, revflag = 0; + dns_secalg_t alg; + bool oldstyle = false; + isc_mem_t *mctx = NULL; + int ch; + int protocol = -1, signatory = 0; + isc_result_t ret; + isc_textregion_t r; + char filename[255]; + isc_buffer_t buf; + isc_log_t *log = NULL; + dns_rdataclass_t rdclass; + int options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC; + char *label = NULL; + dns_ttl_t ttl = 0; + isc_stdtime_t publish = 0, activate = 0, revoke = 0; + isc_stdtime_t inactive = 0, deltime = 0; + isc_stdtime_t now; + int prepub = -1; + bool setpub = false, setact = false; + bool setrev = false, setinact = false; + bool setdel = false, setttl = false; + bool unsetpub = false, unsetact = false; + bool unsetrev = false, unsetinact = false; + bool unsetdel = false; + bool genonly = false; + bool use_nsec3 = false; + bool avoid_collisions = true; + bool exact; + unsigned char c; + isc_stdtime_t syncadd = 0, syncdel = 0; + bool unsetsyncadd = false, setsyncadd = false; + bool unsetsyncdel = false, setsyncdel = false; + + if (argc == 1) { + usage(); + } + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + isc_stdtime_get(&now); + +#define CMDLINE_FLAGS "3A:a:Cc:D:E:Ff:GhI:i:kK:L:l:n:P:p:R:S:t:v:Vy" + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case '3': + use_nsec3 = true; + break; + case 'a': + algname = isc_commandline_argument; + break; + case 'C': + oldstyle = true; + break; + case 'c': + classname = isc_commandline_argument; + break; + case 'E': + engine = isc_commandline_argument; + break; + case 'f': + c = (unsigned char)(isc_commandline_argument[0]); + if (toupper(c) == 'K') { + kskflag = DNS_KEYFLAG_KSK; + } else if (toupper(c) == 'R') { + revflag = DNS_KEYFLAG_REVOKE; + } else { + fatal("unknown flag '%s'", + isc_commandline_argument); + } + break; + case 'K': + directory = isc_commandline_argument; + ret = try_dir(directory); + if (ret != ISC_R_SUCCESS) { + fatal("cannot open directory %s: %s", directory, + isc_result_totext(ret)); + } + break; + case 'k': + options |= DST_TYPE_KEY; + break; + case 'L': + ttl = strtottl(isc_commandline_argument); + setttl = true; + break; + case 'l': + label = isc_mem_strdup(mctx, isc_commandline_argument); + break; + case 'n': + nametype = isc_commandline_argument; + break; + case 'p': + protocol = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || protocol < 0 || protocol > 255) { + fatal("-p must be followed by a number " + "[0..255]"); + } + break; + case 't': + type = isc_commandline_argument; + break; + case 'v': + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("-v must be followed by a number"); + } + break; + case 'y': + avoid_collisions = false; + break; + case 'G': + genonly = true; + break; + case 'P': + /* -Psync ? */ + if (isoptarg("sync", argv, usage)) { + if (unsetsyncadd || setsyncadd) { + fatal("-P sync specified more than " + "once"); + } + + syncadd = strtotime(isc_commandline_argument, + now, now, &setsyncadd); + unsetsyncadd = !setsyncadd; + break; + } + /* -Pdnskey ? */ + (void)isoptarg("dnskey", argv, usage); + if (setpub || unsetpub) { + fatal("-P specified more than once"); + } + + publish = strtotime(isc_commandline_argument, now, now, + &setpub); + unsetpub = !setpub; + break; + case 'A': + if (setact || unsetact) { + fatal("-A specified more than once"); + } + + activate = strtotime(isc_commandline_argument, now, now, + &setact); + unsetact = !setact; + break; + case 'R': + if (setrev || unsetrev) { + fatal("-R specified more than once"); + } + + revoke = strtotime(isc_commandline_argument, now, now, + &setrev); + unsetrev = !setrev; + break; + case 'I': + if (setinact || unsetinact) { + fatal("-I specified more than once"); + } + + inactive = strtotime(isc_commandline_argument, now, now, + &setinact); + unsetinact = !setinact; + break; + case 'D': + /* -Dsync ? */ + if (isoptarg("sync", argv, usage)) { + if (unsetsyncdel || setsyncdel) { + fatal("-D sync specified more than " + "once"); + } + + syncdel = strtotime(isc_commandline_argument, + now, now, &setsyncdel); + unsetsyncdel = !setsyncdel; + break; + } + /* -Ddnskey ? */ + (void)isoptarg("dnskey", argv, usage); + if (setdel || unsetdel) { + fatal("-D specified more than once"); + } + + deltime = strtotime(isc_commandline_argument, now, now, + &setdel); + unsetdel = !setdel; + break; + case 'S': + predecessor = isc_commandline_argument; + break; + case 'i': + prepub = strtottl(isc_commandline_argument); + break; + case 'F': + /* Reserved for FIPS mode */ + FALLTHROUGH; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + ret = dst_lib_init(mctx, engine); + if (ret != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", isc_result_totext(ret)); + } + + setup_logging(mctx, &log); + + if (predecessor == NULL) { + if (label == NULL) { + fatal("the key label was not specified"); + } + if (argc < isc_commandline_index + 1) { + fatal("the key name was not specified"); + } + if (argc > isc_commandline_index + 1) { + fatal("extraneous arguments"); + } + + name = dns_fixedname_initname(&fname); + isc_buffer_init(&buf, argv[isc_commandline_index], + strlen(argv[isc_commandline_index])); + isc_buffer_add(&buf, strlen(argv[isc_commandline_index])); + ret = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); + if (ret != ISC_R_SUCCESS) { + fatal("invalid key name %s: %s", + argv[isc_commandline_index], + isc_result_totext(ret)); + } + + if (strchr(label, ':') == NULL) { + char *l; + int len; + + len = strlen(label) + 8; + l = isc_mem_allocate(mctx, len); + snprintf(l, len, "pkcs11:%s", label); + isc_mem_free(mctx, label); + label = l; + } + + if (algname == NULL) { + fatal("no algorithm specified"); + } + + r.base = algname; + r.length = strlen(algname); + ret = dns_secalg_fromtext(&alg, &r); + if (ret != ISC_R_SUCCESS) { + fatal("unknown algorithm %s", algname); + } + if (alg == DST_ALG_DH) { + options |= DST_TYPE_KEY; + } + + if (use_nsec3) { + switch (alg) { + case DST_ALG_RSASHA1: + alg = DST_ALG_NSEC3RSASHA1; + break; + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + break; + default: + fatal("%s is incompatible with NSEC3; " + "do not use the -3 option", + algname); + } + } + + if (type != NULL && (options & DST_TYPE_KEY) != 0) { + if (strcasecmp(type, "NOAUTH") == 0) { + flags |= DNS_KEYTYPE_NOAUTH; + } else if (strcasecmp(type, "NOCONF") == 0) { + flags |= DNS_KEYTYPE_NOCONF; + } else if (strcasecmp(type, "NOAUTHCONF") == 0) { + flags |= (DNS_KEYTYPE_NOAUTH | + DNS_KEYTYPE_NOCONF); + } else if (strcasecmp(type, "AUTHCONF") == 0) { + /* nothing */ + } else { + fatal("invalid type %s", type); + } + } + + if (!oldstyle && prepub > 0) { + if (setpub && setact && (activate - prepub) < publish) { + fatal("Activation and publication dates " + "are closer together than the\n\t" + "prepublication interval."); + } + + if (!setpub && !setact) { + setpub = setact = true; + publish = now; + activate = now + prepub; + } else if (setpub && !setact) { + setact = true; + activate = publish + prepub; + } else if (setact && !setpub) { + setpub = true; + publish = activate - prepub; + } + + if ((activate - prepub) < now) { + fatal("Time until activation is shorter " + "than the\n\tprepublication interval."); + } + } + } else { + char keystr[DST_KEY_FORMATSIZE]; + isc_stdtime_t when; + int major, minor; + + if (prepub == -1) { + prepub = (30 * 86400); + } + + if (algname != NULL) { + fatal("-S and -a cannot be used together"); + } + if (nametype != NULL) { + fatal("-S and -n cannot be used together"); + } + if (type != NULL) { + fatal("-S and -t cannot be used together"); + } + if (setpub || unsetpub) { + fatal("-S and -P cannot be used together"); + } + if (setact || unsetact) { + fatal("-S and -A cannot be used together"); + } + if (use_nsec3) { + fatal("-S and -3 cannot be used together"); + } + if (oldstyle) { + fatal("-S and -C cannot be used together"); + } + if (genonly) { + fatal("-S and -G cannot be used together"); + } + + ret = dst_key_fromnamedfile(predecessor, directory, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, + mctx, &prevkey); + if (ret != ISC_R_SUCCESS) { + fatal("Invalid keyfile %s: %s", predecessor, + isc_result_totext(ret)); + } + if (!dst_key_isprivate(prevkey)) { + fatal("%s is not a private key", predecessor); + } + + name = dst_key_name(prevkey); + alg = dst_key_alg(prevkey); + flags = dst_key_flags(prevkey); + + dst_key_format(prevkey, keystr, sizeof(keystr)); + dst_key_getprivateformat(prevkey, &major, &minor); + if (major != DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) { + fatal("Key %s has incompatible format version %d.%d\n\t" + "It is not possible to generate a successor key.", + keystr, major, minor); + } + + ret = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &when); + if (ret != ISC_R_SUCCESS) { + fatal("Key %s has no activation date.\n\t" + "You must use dnssec-settime -A to set one " + "before generating a successor.", + keystr); + } + + ret = dst_key_gettime(prevkey, DST_TIME_INACTIVE, &activate); + if (ret != ISC_R_SUCCESS) { + fatal("Key %s has no inactivation date.\n\t" + "You must use dnssec-settime -I to set one " + "before generating a successor.", + keystr); + } + + publish = activate - prepub; + if (publish < now) { + fatal("Key %s becomes inactive\n\t" + "sooner than the prepublication period " + "for the new key ends.\n\t" + "Either change the inactivation date with " + "dnssec-settime -I,\n\t" + "or use the -i option to set a shorter " + "prepublication interval.", + keystr); + } + + ret = dst_key_gettime(prevkey, DST_TIME_DELETE, &when); + if (ret != ISC_R_SUCCESS) { + fprintf(stderr, + "%s: WARNING: Key %s has no removal " + "date;\n\t it will remain in the zone " + "indefinitely after rollover.\n\t " + "You can use dnssec-settime -D to " + "change this.\n", + program, keystr); + } + + setpub = setact = true; + } + + if (nametype == NULL) { + if ((options & DST_TYPE_KEY) != 0) { /* KEY */ + fatal("no nametype specified"); + } + flags |= DNS_KEYOWNER_ZONE; /* DNSKEY */ + } else if (strcasecmp(nametype, "zone") == 0) { + flags |= DNS_KEYOWNER_ZONE; + } else if ((options & DST_TYPE_KEY) != 0) { /* KEY */ + if (strcasecmp(nametype, "host") == 0 || + strcasecmp(nametype, "entity") == 0) + { + flags |= DNS_KEYOWNER_ENTITY; + } else if (strcasecmp(nametype, "user") == 0) { + flags |= DNS_KEYOWNER_USER; + } else { + fatal("invalid KEY nametype %s", nametype); + } + } else if (strcasecmp(nametype, "other") != 0) { /* DNSKEY */ + fatal("invalid DNSKEY nametype %s", nametype); + } + + rdclass = strtoclass(classname); + + if (directory == NULL) { + directory = "."; + } + + if ((options & DST_TYPE_KEY) != 0) { /* KEY */ + flags |= signatory; + } else if ((flags & DNS_KEYOWNER_ZONE) != 0) { /* DNSKEY */ + flags |= kskflag; + flags |= revflag; + } + + if (protocol == -1) { + protocol = DNS_KEYPROTO_DNSSEC; + } else if ((options & DST_TYPE_KEY) == 0 && + protocol != DNS_KEYPROTO_DNSSEC) + { + fatal("invalid DNSKEY protocol: %d", protocol); + } + + if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { + if ((flags & DNS_KEYFLAG_SIGNATORYMASK) != 0) { + fatal("specified null key with signing authority"); + } + } + + if ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE && + alg == DNS_KEYALG_DH) + { + fatal("a key with algorithm '%s' cannot be a zone key", + algname); + } + + isc_buffer_init(&buf, filename, sizeof(filename) - 1); + + /* associate the key */ + ret = dst_key_fromlabel(name, alg, flags, protocol, rdclass, +#if USE_PKCS11 + "pkcs11", +#else /* if USE_PKCS11 */ + engine, +#endif /* if USE_PKCS11 */ + label, NULL, mctx, &key); + + if (ret != ISC_R_SUCCESS) { + char namestr[DNS_NAME_FORMATSIZE]; + char algstr[DNS_SECALG_FORMATSIZE]; + dns_name_format(name, namestr, sizeof(namestr)); + dns_secalg_format(alg, algstr, sizeof(algstr)); + fatal("failed to get key %s/%s: %s", namestr, algstr, + isc_result_totext(ret)); + UNREACHABLE(); + exit(-1); + } + + /* + * Set key timing metadata (unless using -C) + * + * Publish and activation dates are set to "now" by default, but + * can be overridden. Creation date is always set to "now". + */ + if (!oldstyle) { + dst_key_settime(key, DST_TIME_CREATED, now); + + if (genonly && (setpub || setact)) { + fatal("cannot use -G together with -P or -A options"); + } + + if (setpub) { + dst_key_settime(key, DST_TIME_PUBLISH, publish); + } else if (setact) { + dst_key_settime(key, DST_TIME_PUBLISH, activate); + } else if (!genonly && !unsetpub) { + dst_key_settime(key, DST_TIME_PUBLISH, now); + } + + if (setact) { + dst_key_settime(key, DST_TIME_ACTIVATE, activate); + } else if (!genonly && !unsetact) { + dst_key_settime(key, DST_TIME_ACTIVATE, now); + } + + if (setrev) { + if (kskflag == 0) { + fprintf(stderr, + "%s: warning: Key is " + "not flagged as a KSK, but -R " + "was used. Revoking a ZSK is " + "legal, but undefined.\n", + program); + } + dst_key_settime(key, DST_TIME_REVOKE, revoke); + } + + if (setinact) { + dst_key_settime(key, DST_TIME_INACTIVE, inactive); + } + + if (setdel) { + dst_key_settime(key, DST_TIME_DELETE, deltime); + } + if (setsyncadd) { + dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncadd); + } + if (setsyncdel) { + dst_key_settime(key, DST_TIME_SYNCDELETE, syncdel); + } + } else { + if (setpub || setact || setrev || setinact || setdel || + unsetpub || unsetact || unsetrev || unsetinact || + unsetdel || genonly || setsyncadd || setsyncdel) + { + fatal("cannot use -C together with " + "-P, -A, -R, -I, -D, or -G options"); + } + /* + * Compatibility mode: Private-key-format + * should be set to 1.2. + */ + dst_key_setprivateformat(key, 1, 2); + } + + /* Set default key TTL */ + if (setttl) { + dst_key_setttl(key, ttl); + } + + /* + * Do not overwrite an existing key. Warn LOUDLY if there + * is a risk of ID collision due to this key or another key + * being revoked. + */ + if (key_collision(key, name, directory, mctx, &exact)) { + isc_buffer_clear(&buf); + ret = dst_key_buildfilename(key, 0, directory, &buf); + if (ret != ISC_R_SUCCESS) { + fatal("dst_key_buildfilename returned: %s\n", + isc_result_totext(ret)); + } + if (exact) { + fatal("%s: %s already exists\n", program, filename); + } + + if (avoid_collisions) { + fatal("%s: %s could collide with another key upon " + "revokation\n", + program, filename); + } + + fprintf(stderr, + "%s: WARNING: Key %s could collide with " + "another key upon revokation. If you plan " + "to revoke keys, destroy this key and " + "generate a different one.\n", + program, filename); + } + + ret = dst_key_tofile(key, options, directory); + 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)); + } + + 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); + dst_key_free(&key); + if (prevkey != NULL) { + dst_key_free(&prevkey); + } + + cleanup_logging(&log); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_free(mctx, label); + isc_mem_destroy(&mctx); + + if (freeit != NULL) { + free(freeit); + } + + return (0); +} diff --git a/bin/dnssec/dnssec-keyfromlabel.rst b/bin/dnssec/dnssec-keyfromlabel.rst new file mode 100644 index 0000000..57ab9c8 --- /dev/null +++ b/bin/dnssec/dnssec-keyfromlabel.rst @@ -0,0 +1,262 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-keyfromlabel: + +dnssec-keyfromlabel - DNSSEC key generation tool +------------------------------------------------ + +Synopsis +~~~~~~~~ + +:program:`dnssec-keyfromlabel` {**-l** label} [**-3**] [**-a** algorithm] [**-A** date/offset] [**-c** class] [**-D** date/offset] [**-D** sync date/offset] [**-E** engine] [**-f** flag] [**-G**] [**-I** date/offset] [**-i** interval] [**-k**] [**-K** directory] [**-L** ttl] [**-n** nametype] [**-P** date/offset] [**-P** sync date/offset] [**-p** protocol] [**-R** date/offset] [**-S** key] [**-t** type] [**-v** level] [**-V**] [**-y**] {name} + +Description +~~~~~~~~~~~ + +``dnssec-keyfromlabel`` generates a pair of key files that reference a +key object stored in a cryptographic hardware service module (HSM). The +private key file can be used for DNSSEC signing of zone data as if it +were a conventional signing key created by ``dnssec-keygen``, but the +key material is stored within the HSM and the actual signing takes +place there. + +The ``name`` of the key is specified on the command line. This must +match the name of the zone for which the key is being generated. + +Options +~~~~~~~ + +``-a algorithm`` + This option selects the cryptographic algorithm. The value of ``algorithm`` must + be one of RSASHA1, NSEC3RSASHA1, RSASHA256, RSASHA512, + ECDSAP256SHA256, ECDSAP384SHA384, ED25519, or ED448. + + These values are case-insensitive. In some cases, abbreviations are + supported, such as ECDSA256 for ECDSAP256SHA256 and ECDSA384 for + ECDSAP384SHA384. If RSASHA1 is specified along with the ``-3`` + option, then NSEC3RSASHA1 is used instead. + + This option is mandatory except when using the + ``-S`` option, which copies the algorithm from the predecessory key. + + .. versionchanged:: 9.12.0 + The default value RSASHA1 for newly generated keys was removed. + +``-3`` + This option uses an NSEC3-capable algorithm to generate a DNSSEC key. If this + option is used with an algorithm that has both NSEC and NSEC3 + versions, then the NSEC3 version is used; for example, + ``dnssec-keygen -3a RSASHA1`` specifies the NSEC3RSASHA1 algorithm. + +``-E engine`` + This option specifies the cryptographic hardware to use. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). When BIND is + built with native PKCS#11 cryptography (``--enable-native-pkcs11``), it + defaults to the path of the PKCS#11 provider library specified via + ``--with-pkcs11``. + +``-l label`` + This option specifies the label for a key pair in the crypto hardware. + + When BIND 9 is built with OpenSSL-based PKCS#11 support, the label is + an arbitrary string that identifies a particular key. It may be + preceded by an optional OpenSSL engine name, followed by a colon, as + in ``pkcs11:keylabel``. + + When BIND 9 is built with native PKCS#11 support, the label is a + PKCS#11 URI string in the format + ``pkcs11:keyword\ =value[;\ keyword\ =value;...]``. Keywords + include ``token``, which identifies the HSM; ``object``, which identifies + the key; and ``pin-source``, which identifies a file from which the + HSM's PIN code can be obtained. The label is stored in the + on-disk ``private`` file. + + If the label contains a ``pin-source`` field, tools using the + generated key files are able to use the HSM for signing and other + operations without any need for an operator to manually enter a PIN. + Note: Making the HSM's PIN accessible in this manner may reduce the + security advantage of using an HSM; use caution + with this feature. + +``-n nametype`` + This option specifies the owner type of the key. The value of ``nametype`` must + either be ZONE (for a DNSSEC zone key (KEY/DNSKEY)), HOST or ENTITY + (for a key associated with a host (KEY)), USER (for a key associated + with a user (KEY)), or OTHER (DNSKEY). These values are + case-insensitive. + +``-C`` + This option enables compatibility mode, which generates an old-style key, without any metadata. + By default, ``dnssec-keyfromlabel`` includes the key's creation + date in the metadata stored with the private key; other dates may + be set there as well, including publication date, activation date, etc. Keys + that include this data may be incompatible with older versions of + BIND; the ``-C`` option suppresses them. + +``-c class`` + This option indicates that the DNS record containing the key should have the + specified class. If not specified, class IN is used. + +``-f flag`` + This option sets the specified flag in the ``flag`` field of the KEY/DNSKEY record. + The only recognized flags are KSK (Key-Signing Key) and REVOKE. + +``-G`` + This option generates a key, but does not publish it or sign with it. This option is + incompatible with ``-P`` and ``-A``. + +``-h`` + This option prints a short summary of the options and arguments to + ``dnssec-keyfromlabel``. + +``-K directory`` + This option sets the directory in which the key files are to be written. + +``-k`` + This option generates KEY records rather than DNSKEY records. + +``-L`` ttl + This option sets the default TTL to use for this key when it is converted into a + DNSKEY RR. This is the TTL used when the key is imported into a zone, + unless there was already a DNSKEY RRset in + place, in which case the existing TTL would take precedence. Setting + the default TTL to ``0`` or ``none`` removes it. + +``-p protocol`` + This option sets the protocol value for the key. The protocol is a number between + 0 and 255. The default is 3 (DNSSEC). Other possible values for this + argument are listed in :rfc:`2535` and its successors. + +``-S key`` + This option generates a key as an explicit successor to an existing key. The name, + algorithm, size, and type of the key are set to match the + predecessor. The activation date of the new key is set to the + inactivation date of the existing one. The publication date is + set to the activation date minus the prepublication interval, which + defaults to 30 days. + +``-t type`` + This option indicates the type of the key. ``type`` must be one of AUTHCONF, + NOAUTHCONF, NOAUTH, or NOCONF. The default is AUTHCONF. AUTH refers + to the ability to authenticate data, and CONF to the ability to encrypt + data. + +``-v level`` + This option sets the debugging level. + +``-V`` + This option prints version information. + +``-y`` + This option allows DNSSEC key files to be generated even if the key ID would + collide with that of an existing key, in the event of either key + being revoked. (This is only safe to enable if + :rfc:`5011` trust anchor maintenance is not used with either of the keys + involved.) + +Timing Options +~~~~~~~~~~~~~~ + +Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS. If the +argument begins with a ``+`` or ``-``, it is interpreted as an offset from +the present time. For convenience, if such an offset is followed by one +of the suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, then the offset is +computed in years (defined as 365 24-hour days, ignoring leap years), +months (defined as 30 24-hour days), weeks, days, hours, or minutes, +respectively. Without a suffix, the offset is computed in seconds. To +explicitly prevent a date from being set, use ``none`` or ``never``. + +``-P date/offset`` + This option sets the date on which a key is to be published to the zone. After + that date, the key is included in the zone but is not used + to sign it. If not set, and if the ``-G`` option has not been used, the + default is the current date. + +``-P sync date/offset`` + This option sets the date on which CDS and CDNSKEY records that match this key + are to be published to the zone. + +``-A date/offset`` + This option sets the date on which the key is to be activated. After that date, + the key is included in the zone and used to sign it. If not set, + and if the ``-G`` option has not been used, the default is the current date. + +``-R date/offset`` + This option sets the date on which the key is to be revoked. After that date, the + key is flagged as revoked. It is included in the zone and + is used to sign it. + +``-I date/offset`` + This option sets the date on which the key is to be retired. After that date, the + key is still included in the zone, but it is not used to + sign it. + +``-D date/offset`` + This option sets the date on which the key is to be deleted. After that date, the + key is no longer included in the zone. (However, it may remain in the key + repository.) + +``-D sync date/offset`` + This option sets the date on which the CDS and CDNSKEY records that match this + key are to be deleted. + +``-i interval`` + This option sets the prepublication interval for a key. If set, then the + publication and activation dates must be separated by at least this + much time. If the activation date is specified but the publication + date is not, the publication date defaults to this much time + before the activation date; conversely, if the publication date is + specified but not the activation date, activation is set to + this much time after publication. + + If the key is being created as an explicit successor to another key, + then the default prepublication interval is 30 days; otherwise it is + zero. + + As with date offsets, if the argument is followed by one of the + suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, the interval is + measured in years, months, weeks, days, hours, or minutes, + respectively. Without a suffix, the interval is measured in seconds. + +Generated Key Files +~~~~~~~~~~~~~~~~~~~ + +When ``dnssec-keyfromlabel`` completes successfully, it prints a string +of the form ``Knnnn.+aaa+iiiii`` to the standard output. This is an +identification string for the key files it has generated. + +- ``nnnn`` is the key name. + +- ``aaa`` is the numeric representation of the algorithm. + +- ``iiiii`` is the key identifier (or footprint). + +``dnssec-keyfromlabel`` creates two files, with names based on the +printed string. ``Knnnn.+aaa+iiiii.key`` contains the public key, and +``Knnnn.+aaa+iiiii.private`` contains the private key. + +The ``.key`` file contains a DNS KEY record that can be inserted into a +zone file (directly or with an $INCLUDE statement). + +The ``.private`` file contains algorithm-specific fields. For obvious +security reasons, this file does not have general read permission. + +See Also +~~~~~~~~ + +:manpage:`dnssec-keygen(8)`, :manpage:`dnssec-signzone(8)`, BIND 9 Administrator Reference Manual, +:rfc:`4034`, :rfc:`7512`. diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c new file mode 100644 index 0000000..1c663e2 --- /dev/null +++ b/bin/dnssec/dnssec-keygen.c @@ -0,0 +1,1315 @@ +/* + * Portions 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. + * + * Portions Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +#define MAX_RSA 4096 /* should be long enough... */ + +const char *program = "dnssec-keygen"; + +isc_log_t *lctx = NULL; + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +progress(int p); + +struct keygen_ctx { + const char *predecessor; + const char *policy; + const char *configfile; + const char *directory; + char *algname; + char *nametype; + char *type; + int generator; + int protocol; + int size; + int signatory; + dns_rdataclass_t rdclass; + int options; + int dbits; + dns_ttl_t ttl; + uint16_t kskflag; + uint16_t revflag; + dns_secalg_t alg; + /* timing data */ + int prepub; + isc_stdtime_t now; + isc_stdtime_t publish; + isc_stdtime_t activate; + isc_stdtime_t inactive; + isc_stdtime_t revokekey; + isc_stdtime_t deltime; + isc_stdtime_t syncadd; + isc_stdtime_t syncdel; + bool setpub; + bool setact; + bool setinact; + bool setrev; + bool setdel; + bool setsyncadd; + bool setsyncdel; + bool unsetpub; + bool unsetact; + bool unsetinact; + bool unsetrev; + bool unsetdel; + /* how to generate the key */ + bool setttl; + bool use_nsec3; + bool genonly; + bool showprogress; + bool quiet; + bool oldstyle; + /* state */ + time_t lifetime; + bool ksk; + bool zsk; +}; + +typedef struct keygen_ctx keygen_ctx_t; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s [options] name\n\n", program); + fprintf(stderr, "Version: %s\n", VERSION); + fprintf(stderr, " name: owner of the key\n"); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -K : write keys into directory\n"); + fprintf(stderr, " -k : generate keys for dnssec-policy\n"); + fprintf(stderr, " -l : configuration file with dnssec-policy " + "statement\n"); + fprintf(stderr, " -a :\n"); + fprintf(stderr, " RSASHA1 | NSEC3RSASHA1 |\n"); + fprintf(stderr, " RSASHA256 | RSASHA512 |\n"); + fprintf(stderr, " ECDSAP256SHA256 | ECDSAP384SHA384 |\n"); + fprintf(stderr, " ED25519 | ED448 | DH\n"); + fprintf(stderr, " -3: use NSEC3-capable algorithm\n"); + fprintf(stderr, " -b :\n"); + fprintf(stderr, " RSASHA1:\t[1024..%d]\n", MAX_RSA); + fprintf(stderr, " NSEC3RSASHA1:\t[1024..%d]\n", MAX_RSA); + fprintf(stderr, " RSASHA256:\t[1024..%d]\n", MAX_RSA); + fprintf(stderr, " RSASHA512:\t[1024..%d]\n", MAX_RSA); + fprintf(stderr, " DH:\t\t[128..4096]\n"); + fprintf(stderr, " ECDSAP256SHA256:\tignored\n"); + fprintf(stderr, " ECDSAP384SHA384:\tignored\n"); + fprintf(stderr, " ED25519:\tignored\n"); + fprintf(stderr, " ED448:\tignored\n"); + fprintf(stderr, " (key size defaults are set according to\n" + " algorithm and usage (ZSK or KSK)\n"); + fprintf(stderr, " -n : ZONE | HOST | ENTITY | " + "USER | OTHER\n"); + fprintf(stderr, " (DNSKEY generation defaults to ZONE)\n"); + fprintf(stderr, " -c : (default: IN)\n"); + fprintf(stderr, " -d (0 => max, default)\n"); + fprintf(stderr, " -E :\n"); +#if USE_PKCS11 + fprintf(stderr, + " path to PKCS#11 provider library " + "(default is %s)\n", + PK11_LIB_LOCATION); +#else /* if USE_PKCS11 */ + fprintf(stderr, " name of an OpenSSL engine to use\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, " -f : KSK | REVOKE\n"); + fprintf(stderr, " -g : use specified generator " + "(DH only)\n"); + fprintf(stderr, " -L : default key TTL\n"); + fprintf(stderr, " -p : (default: 3 [dnssec])\n"); + fprintf(stderr, " -s : strength value this key signs DNS " + "records with (default: 0)\n"); + fprintf(stderr, " -T : DNSKEY | KEY (default: DNSKEY; " + "use KEY for SIG(0))\n"); + fprintf(stderr, " -t : " + "AUTHCONF | NOAUTHCONF | NOAUTH | NOCONF " + "(default: AUTHCONF)\n"); + fprintf(stderr, " -h: print usage and exit\n"); + fprintf(stderr, " -m :\n"); + fprintf(stderr, " usage | trace | record | size | mctx\n"); + fprintf(stderr, " -v : set verbosity level (0 - 10)\n"); + fprintf(stderr, " -V: print version information\n"); + fprintf(stderr, "Timing options:\n"); + fprintf(stderr, " -P date/[+-]offset/none: set key publication date " + "(default: now)\n"); + fprintf(stderr, " -P sync date/[+-]offset/none: set CDS and CDNSKEY " + "publication date\n"); + fprintf(stderr, " -A date/[+-]offset/none: set key activation date " + "(default: now)\n"); + fprintf(stderr, " -R date/[+-]offset/none: set key " + "revocation date\n"); + fprintf(stderr, " -I date/[+-]offset/none: set key " + "inactivation date\n"); + fprintf(stderr, " -D date/[+-]offset/none: set key deletion date\n"); + fprintf(stderr, " -D sync date/[+-]offset/none: set CDS and CDNSKEY " + "deletion date\n"); + + fprintf(stderr, " -G: generate key only; do not set -P or -A\n"); + fprintf(stderr, " -C: generate a backward-compatible key, omitting " + "all dates\n"); + fprintf(stderr, " -S : generate a successor to an existing " + "key\n"); + fprintf(stderr, " -i : prepublication interval for " + "successor key " + "(default: 30 days)\n"); + fprintf(stderr, "Output:\n"); + fprintf(stderr, " K++.key, " + "K++.private\n"); + + exit(-1); +} + +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 +kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name, + dns_kasp_t **kaspp) { + const cfg_listelt_t *element; + const cfg_obj_t *kasps = NULL; + dns_kasp_t *kasp = NULL, *kasp_next; + isc_result_t result = ISC_R_NOTFOUND; + dns_kasplist_t kasplist; + + ISC_LIST_INIT(kasplist); + + (void)cfg_map_get(config, "dnssec-policy", &kasps); + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + kasp = NULL; + if (strcmp(cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + name) != 0) + { + continue; + } + + result = cfg_kasp_fromconfig(kconfig, NULL, mctx, lctx, + &kasplist, &kasp); + if (result != ISC_R_SUCCESS) { + fatal("failed to configure dnssec-policy '%s': %s", + cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + isc_result_totext(result)); + } + INSIST(kasp != NULL); + dns_kasp_freeze(kasp); + break; + } + + *kaspp = kasp; + + /* + * Cleanup kasp list. + */ + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; kasp = kasp_next) { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } +} + +static void +keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { + char filename[255]; + char algstr[DNS_SECALG_FORMATSIZE]; + uint16_t flags = 0; + int param = 0; + bool null_key = false; + bool conflict = false; + bool show_progress = false; + isc_buffer_t buf; + dns_name_t *name; + dns_fixedname_t fname; + isc_result_t ret; + dst_key_t *key = NULL; + dst_key_t *prevkey = NULL; + + UNUSED(argc); + + dns_secalg_format(ctx->alg, algstr, sizeof(algstr)); + + if (ctx->predecessor == NULL) { + if (ctx->prepub == -1) { + ctx->prepub = 0; + } + + name = dns_fixedname_initname(&fname); + isc_buffer_init(&buf, argv[isc_commandline_index], + strlen(argv[isc_commandline_index])); + isc_buffer_add(&buf, strlen(argv[isc_commandline_index])); + ret = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL); + if (ret != ISC_R_SUCCESS) { + fatal("invalid key name %s: %s", + argv[isc_commandline_index], + isc_result_totext(ret)); + } + + if (!dst_algorithm_supported(ctx->alg)) { + fatal("unsupported algorithm: %s", algstr); + } + + if (ctx->alg == DST_ALG_DH) { + ctx->options |= DST_TYPE_KEY; + } + + if (ctx->use_nsec3) { + switch (ctx->alg) { + case DST_ALG_RSASHA1: + ctx->alg = DST_ALG_NSEC3RSASHA1; + break; + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + break; + default: + fatal("algorithm %s is incompatible with NSEC3" + ", do not use the -3 option", + algstr); + } + } + + if (ctx->type != NULL && (ctx->options & DST_TYPE_KEY) != 0) { + if (strcasecmp(ctx->type, "NOAUTH") == 0) { + flags |= DNS_KEYTYPE_NOAUTH; + } else if (strcasecmp(ctx->type, "NOCONF") == 0) { + flags |= DNS_KEYTYPE_NOCONF; + } else if (strcasecmp(ctx->type, "NOAUTHCONF") == 0) { + flags |= (DNS_KEYTYPE_NOAUTH | + DNS_KEYTYPE_NOCONF); + if (ctx->size < 0) { + ctx->size = 0; + } + } else if (strcasecmp(ctx->type, "AUTHCONF") == 0) { + /* nothing */ + } else { + fatal("invalid type %s", ctx->type); + } + } + + if (ctx->size < 0) { + switch (ctx->alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + ctx->size = 2048; + if (verbose > 0) { + fprintf(stderr, + "key size not " + "specified; defaulting" + " to %d\n", + ctx->size); + } + break; + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + break; + default: + fatal("key size not specified (-b option)"); + } + } + + if (!ctx->oldstyle && ctx->prepub > 0) { + if (ctx->setpub && ctx->setact && + (ctx->activate - ctx->prepub) < ctx->publish) + { + fatal("Activation and publication dates " + "are closer together than the\n\t" + "prepublication interval."); + } + + if (!ctx->setpub && !ctx->setact) { + ctx->setpub = ctx->setact = true; + ctx->publish = ctx->now; + ctx->activate = ctx->now + ctx->prepub; + } else if (ctx->setpub && !ctx->setact) { + ctx->setact = true; + ctx->activate = ctx->publish + ctx->prepub; + } else if (ctx->setact && !ctx->setpub) { + ctx->setpub = true; + ctx->publish = ctx->activate - ctx->prepub; + } + + if ((ctx->activate - ctx->prepub) < ctx->now) { + fatal("Time until activation is shorter " + "than the\n\tprepublication interval."); + } + } + } else { + char keystr[DST_KEY_FORMATSIZE]; + isc_stdtime_t when; + int major, minor; + + if (ctx->prepub == -1) { + ctx->prepub = (30 * 86400); + } + + if (ctx->alg != 0) { + fatal("-S and -a cannot be used together"); + } + if (ctx->size >= 0) { + fatal("-S and -b cannot be used together"); + } + if (ctx->nametype != NULL) { + fatal("-S and -n cannot be used together"); + } + if (ctx->type != NULL) { + fatal("-S and -t cannot be used together"); + } + if (ctx->setpub || ctx->unsetpub) { + fatal("-S and -P cannot be used together"); + } + if (ctx->setact || ctx->unsetact) { + fatal("-S and -A cannot be used together"); + } + if (ctx->use_nsec3) { + fatal("-S and -3 cannot be used together"); + } + if (ctx->oldstyle) { + fatal("-S and -C cannot be used together"); + } + if (ctx->genonly) { + fatal("-S and -G cannot be used together"); + } + + ret = dst_key_fromnamedfile( + ctx->predecessor, ctx->directory, + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE), + mctx, &prevkey); + if (ret != ISC_R_SUCCESS) { + fatal("Invalid keyfile %s: %s", ctx->predecessor, + isc_result_totext(ret)); + } + if (!dst_key_isprivate(prevkey)) { + fatal("%s is not a private key", ctx->predecessor); + } + + name = dst_key_name(prevkey); + ctx->alg = dst_key_alg(prevkey); + ctx->size = dst_key_size(prevkey); + flags = dst_key_flags(prevkey); + + dst_key_format(prevkey, keystr, sizeof(keystr)); + dst_key_getprivateformat(prevkey, &major, &minor); + if (major != DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) { + fatal("Key %s has incompatible format version %d.%d\n\t" + "It is not possible to generate a successor key.", + keystr, major, minor); + } + + ret = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &when); + if (ret != ISC_R_SUCCESS) { + fatal("Key %s has no activation date.\n\t" + "You must use dnssec-settime -A to set one " + "before generating a successor.", + keystr); + } + + ret = dst_key_gettime(prevkey, DST_TIME_INACTIVE, + &ctx->activate); + if (ret != ISC_R_SUCCESS) { + fatal("Key %s has no inactivation date.\n\t" + "You must use dnssec-settime -I to set one " + "before generating a successor.", + keystr); + } + + ctx->publish = ctx->activate - ctx->prepub; + if (ctx->publish < ctx->now) { + fatal("Key %s becomes inactive\n\t" + "sooner than the prepublication period " + "for the new key ends.\n\t" + "Either change the inactivation date with " + "dnssec-settime -I,\n\t" + "or use the -i option to set a shorter " + "prepublication interval.", + keystr); + } + + ret = dst_key_gettime(prevkey, DST_TIME_DELETE, &when); + if (ret != ISC_R_SUCCESS) { + fprintf(stderr, + "%s: WARNING: Key %s has no removal " + "date;\n\t it will remain in the zone " + "indefinitely after rollover.\n\t " + "You can use dnssec-settime -D to " + "change this.\n", + program, keystr); + } + + ctx->setpub = ctx->setact = true; + } + + switch (ctx->alg) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) + { + fatal("RSA key size %d out of range", ctx->size); + } + break; + case DNS_KEYALG_RSASHA512: + if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) + { + fatal("RSA key size %d out of range", ctx->size); + } + break; + case DNS_KEYALG_DH: + if (ctx->size != 0 && (ctx->size < 128 || ctx->size > 4096)) { + fatal("DH key size %d out of range", ctx->size); + } + break; + case DST_ALG_ECDSA256: + ctx->size = 256; + break; + case DST_ALG_ECDSA384: + ctx->size = 384; + break; + case DST_ALG_ED25519: + ctx->size = 256; + break; + case DST_ALG_ED448: + ctx->size = 456; + break; + } + + if (ctx->alg != DNS_KEYALG_DH && ctx->generator != 0) { + fatal("specified DH generator for a non-DH key"); + } + + if (ctx->nametype == NULL) { + if ((ctx->options & DST_TYPE_KEY) != 0) { /* KEY */ + fatal("no nametype specified"); + } + flags |= DNS_KEYOWNER_ZONE; /* DNSKEY */ + } else if (strcasecmp(ctx->nametype, "zone") == 0) { + flags |= DNS_KEYOWNER_ZONE; + } else if ((ctx->options & DST_TYPE_KEY) != 0) { /* KEY */ + if (strcasecmp(ctx->nametype, "host") == 0 || + strcasecmp(ctx->nametype, "entity") == 0) + { + flags |= DNS_KEYOWNER_ENTITY; + } else if (strcasecmp(ctx->nametype, "user") == 0) { + flags |= DNS_KEYOWNER_USER; + } else { + fatal("invalid KEY nametype %s", ctx->nametype); + } + } else if (strcasecmp(ctx->nametype, "other") != 0) { /* DNSKEY */ + fatal("invalid DNSKEY nametype %s", ctx->nametype); + } + + if (ctx->directory == NULL) { + ctx->directory = "."; + } + + if ((ctx->options & DST_TYPE_KEY) != 0) { /* KEY */ + flags |= ctx->signatory; + } else if ((flags & DNS_KEYOWNER_ZONE) != 0) { /* DNSKEY */ + flags |= ctx->kskflag; + flags |= ctx->revflag; + } + + if (ctx->protocol == -1) { + ctx->protocol = DNS_KEYPROTO_DNSSEC; + } else if ((ctx->options & DST_TYPE_KEY) == 0 && + ctx->protocol != DNS_KEYPROTO_DNSSEC) + { + fatal("invalid DNSKEY protocol: %d", ctx->protocol); + } + + if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { + if (ctx->size > 0) { + fatal("specified null key with non-zero size"); + } + if ((flags & DNS_KEYFLAG_SIGNATORYMASK) != 0) { + fatal("specified null key with signing authority"); + } + } + + if ((flags & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE && + ctx->alg == DNS_KEYALG_DH) + { + fatal("a key with algorithm %s cannot be a zone key", algstr); + } + + switch (ctx->alg) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_RSASHA256: + case DNS_KEYALG_RSASHA512: + show_progress = true; + break; + + case DNS_KEYALG_DH: + param = ctx->generator; + break; + + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: + show_progress = true; + break; + } + + if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY) { + null_key = true; + } + + isc_buffer_init(&buf, filename, sizeof(filename) - 1); + + do { + conflict = false; + + if (!ctx->quiet && show_progress) { + fprintf(stderr, "Generating key pair."); + ret = dst_key_generate(name, ctx->alg, ctx->size, param, + flags, ctx->protocol, + ctx->rdclass, mctx, &key, + &progress); + putc('\n', stderr); + fflush(stderr); + } else { + ret = dst_key_generate(name, ctx->alg, ctx->size, param, + flags, ctx->protocol, + ctx->rdclass, mctx, &key, NULL); + } + + if (ret != ISC_R_SUCCESS) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namestr, sizeof(namestr)); + fatal("failed to generate key %s/%s: %s\n", namestr, + algstr, isc_result_totext(ret)); + } + + dst_key_setbits(key, ctx->dbits); + + /* + * Set key timing metadata (unless using -C) + * + * Creation date is always set to "now". + * + * For a new key without an explicit predecessor, publish + * and activation dates are set to "now" by default, but + * can both be overridden. + * + * For a successor key, activation is set to match the + * predecessor's inactivation date. Publish is set to 30 + * days earlier than that (XXX: this should be configurable). + * If either of the resulting dates are in the past, that's + * an error; the inactivation date of the predecessor key + * must be updated before a successor key can be created. + */ + if (!ctx->oldstyle) { + dst_key_settime(key, DST_TIME_CREATED, ctx->now); + + if (ctx->genonly && (ctx->setpub || ctx->setact)) { + fatal("cannot use -G together with " + "-P or -A options"); + } + + if (ctx->setpub) { + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->publish); + } else if (ctx->setact && !ctx->unsetpub) { + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->activate - ctx->prepub); + } else if (!ctx->genonly && !ctx->unsetpub) { + dst_key_settime(key, DST_TIME_PUBLISH, + ctx->now); + } + + if (ctx->setact) { + dst_key_settime(key, DST_TIME_ACTIVATE, + ctx->activate); + } else if (!ctx->genonly && !ctx->unsetact) { + dst_key_settime(key, DST_TIME_ACTIVATE, + ctx->now); + } + + if (ctx->setrev) { + if (ctx->kskflag == 0) { + fprintf(stderr, + "%s: warning: Key is " + "not flagged as a KSK, but -R " + "was used. Revoking a ZSK is " + "legal, but undefined.\n", + program); + } + dst_key_settime(key, DST_TIME_REVOKE, + ctx->revokekey); + } + + if (ctx->setinact) { + dst_key_settime(key, DST_TIME_INACTIVE, + ctx->inactive); + } + + if (ctx->setdel) { + if (ctx->setinact && + ctx->deltime < ctx->inactive) + { + fprintf(stderr, + "%s: warning: Key is " + "scheduled to be deleted " + "before it is scheduled to be " + "made inactive.\n", + program); + } + dst_key_settime(key, DST_TIME_DELETE, + ctx->deltime); + } + + if (ctx->setsyncadd) { + dst_key_settime(key, DST_TIME_SYNCPUBLISH, + ctx->syncadd); + } + + if (ctx->setsyncdel) { + dst_key_settime(key, DST_TIME_SYNCDELETE, + ctx->syncdel); + } + } else { + if (ctx->setpub || ctx->setact || ctx->setrev || + ctx->setinact || ctx->setdel || ctx->unsetpub || + ctx->unsetact || ctx->unsetrev || ctx->unsetinact || + ctx->unsetdel || ctx->genonly || ctx->setsyncadd || + ctx->setsyncdel) + { + fatal("cannot use -C together with " + "-P, -A, -R, -I, -D, or -G options"); + } + /* + * Compatibility mode: Private-key-format + * should be set to 1.2. + */ + dst_key_setprivateformat(key, 1, 2); + } + + /* Set the default key TTL */ + if (ctx->setttl) { + dst_key_setttl(key, ctx->ttl); + } + + /* Set dnssec-policy related metadata */ + if (ctx->policy != NULL) { + dst_key_setnum(key, DST_NUM_LIFETIME, ctx->lifetime); + dst_key_setbool(key, DST_BOOL_KSK, ctx->ksk); + dst_key_setbool(key, DST_BOOL_ZSK, ctx->zsk); + } + + /* + * Do not overwrite an existing key, or create a key + * if there is a risk of ID collision due to this key + * or another key being revoked. + */ + if (key_collision(key, name, ctx->directory, mctx, NULL)) { + conflict = true; + if (null_key) { + dst_key_free(&key); + break; + } + + if (verbose > 0) { + isc_buffer_clear(&buf); + ret = dst_key_buildfilename( + key, 0, ctx->directory, &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); + + if (conflict) { + fatal("cannot generate a null key due to possible key ID " + "collision"); + } + + if (ctx->predecessor != NULL && prevkey != NULL) { + dst_key_setnum(prevkey, DST_NUM_SUCCESSOR, dst_key_id(key)); + dst_key_setnum(key, DST_NUM_PREDECESSOR, dst_key_id(prevkey)); + + ret = dst_key_tofile(prevkey, ctx->options, ctx->directory); + if (ret != ISC_R_SUCCESS) { + char keystr[DST_KEY_FORMATSIZE]; + dst_key_format(prevkey, keystr, sizeof(keystr)); + fatal("failed to update predecessor %s: %s\n", keystr, + isc_result_totext(ret)); + } + } + + ret = dst_key_tofile(key, ctx->options, ctx->directory); + 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)); + } + + 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); + + dst_key_free(&key); + if (prevkey != NULL) { + dst_key_free(&prevkey); + } +} + +int +main(int argc, char **argv) { + char *algname = NULL, *freeit = NULL; + char *classname = NULL; + char *endp; + isc_mem_t *mctx = NULL; + isc_result_t ret; + isc_textregion_t r; + const char *engine = NULL; + unsigned char c; + int ch; + + keygen_ctx_t ctx = { + .options = DST_TYPE_PRIVATE | DST_TYPE_PUBLIC, + .prepub = -1, + .protocol = -1, + .size = -1, + }; + + if (argc == 1) { + usage(); + } + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + /* + * Process memory debugging argument first. + */ +#define CMDLINE_FLAGS \ + "3A:a:b:Cc:D:d:E:eFf:Gg:hI:i:K:k:L:l:m:n:P:p:qR:r:S:s:" \ + "T:t:v:V" + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'm': + if (strcasecmp(isc_commandline_argument, "record") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + } + if (strcasecmp(isc_commandline_argument, "trace") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGTRACE; + } + if (strcasecmp(isc_commandline_argument, "usage") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; + } + if (strcasecmp(isc_commandline_argument, "size") == 0) { + isc_mem_debugging |= ISC_MEM_DEBUGSIZE; + } + if (strcasecmp(isc_commandline_argument, "mctx") == 0) { + isc_mem_debugging |= ISC_MEM_DEBUGCTX; + } + break; + default: + break; + } + } + isc_commandline_reset = true; + + isc_mem_create(&mctx); + isc_stdtime_get(&ctx.now); + + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case '3': + ctx.use_nsec3 = true; + break; + case 'a': + algname = isc_commandline_argument; + break; + case 'b': + ctx.size = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.size < 0) { + fatal("-b requires a non-negative number"); + } + break; + case 'C': + ctx.oldstyle = true; + break; + case 'c': + classname = isc_commandline_argument; + break; + case 'd': + ctx.dbits = strtol(isc_commandline_argument, &endp, 10); + if (*endp != '\0' || ctx.dbits < 0) { + fatal("-d requires a non-negative number"); + } + break; + case 'E': + engine = isc_commandline_argument; + break; + case 'e': + fprintf(stderr, "phased-out option -e " + "(was 'use (RSA) large exponent')\n"); + break; + case 'f': + c = (unsigned char)(isc_commandline_argument[0]); + if (toupper(c) == 'K') { + ctx.kskflag = DNS_KEYFLAG_KSK; + } else if (toupper(c) == 'R') { + ctx.revflag = DNS_KEYFLAG_REVOKE; + } else { + fatal("unknown flag '%s'", + isc_commandline_argument); + } + break; + case 'g': + ctx.generator = strtol(isc_commandline_argument, &endp, + 10); + if (*endp != '\0' || ctx.generator <= 0) { + fatal("-g requires a positive number"); + } + break; + case 'K': + ctx.directory = isc_commandline_argument; + ret = try_dir(ctx.directory); + if (ret != ISC_R_SUCCESS) { + fatal("cannot open directory %s: %s", + ctx.directory, isc_result_totext(ret)); + } + break; + case 'k': + ctx.policy = isc_commandline_argument; + break; + case 'L': + ctx.ttl = strtottl(isc_commandline_argument); + ctx.setttl = true; + break; + case 'l': + ctx.configfile = isc_commandline_argument; + break; + case 'n': + ctx.nametype = isc_commandline_argument; + break; + case 'm': + break; + case 'p': + ctx.protocol = strtol(isc_commandline_argument, &endp, + 10); + if (*endp != '\0' || ctx.protocol < 0 || + ctx.protocol > 255) + { + fatal("-p must be followed by a number " + "[0..255]"); + } + break; + case 'q': + ctx.quiet = true; + break; + case 'r': + fatal("The -r option has been deprecated.\n" + "System random data is always used.\n"); + break; + case 's': + ctx.signatory = strtol(isc_commandline_argument, &endp, + 10); + if (*endp != '\0' || ctx.signatory < 0 || + ctx.signatory > 15) + { + fatal("-s must be followed by a number " + "[0..15]"); + } + break; + case 'T': + if (strcasecmp(isc_commandline_argument, "KEY") == 0) { + ctx.options |= DST_TYPE_KEY; + } else if (strcasecmp(isc_commandline_argument, + "DNSKE" + "Y") == 0) + { + /* default behavior */ + } else { + fatal("unknown type '%s'", + isc_commandline_argument); + } + break; + case 't': + ctx.type = isc_commandline_argument; + break; + case 'v': + endp = NULL; + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("-v must be followed by a number"); + } + break; + case 'G': + ctx.genonly = true; + break; + case 'P': + /* -Psync ? */ + if (isoptarg("sync", argv, usage)) { + if (ctx.setsyncadd) { + fatal("-P sync specified more than " + "once"); + } + + ctx.syncadd = strtotime( + isc_commandline_argument, ctx.now, + ctx.now, &ctx.setsyncadd); + break; + } + (void)isoptarg("dnskey", argv, usage); + if (ctx.setpub || ctx.unsetpub) { + fatal("-P specified more than once"); + } + + ctx.publish = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setpub); + ctx.unsetpub = !ctx.setpub; + break; + case 'A': + if (ctx.setact || ctx.unsetact) { + fatal("-A specified more than once"); + } + + ctx.activate = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setact); + ctx.unsetact = !ctx.setact; + break; + case 'R': + if (ctx.setrev || ctx.unsetrev) { + fatal("-R specified more than once"); + } + + ctx.revokekey = strtotime(isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setrev); + ctx.unsetrev = !ctx.setrev; + break; + case 'I': + if (ctx.setinact || ctx.unsetinact) { + fatal("-I specified more than once"); + } + + ctx.inactive = strtotime(isc_commandline_argument, + ctx.now, ctx.now, + &ctx.setinact); + ctx.unsetinact = !ctx.setinact; + break; + case 'D': + /* -Dsync ? */ + if (isoptarg("sync", argv, usage)) { + if (ctx.setsyncdel) { + fatal("-D sync specified more than " + "once"); + } + + ctx.syncdel = strtotime( + isc_commandline_argument, ctx.now, + ctx.now, &ctx.setsyncdel); + break; + } + (void)isoptarg("dnskey", argv, usage); + if (ctx.setdel || ctx.unsetdel) { + fatal("-D specified more than once"); + } + + ctx.deltime = strtotime(isc_commandline_argument, + ctx.now, ctx.now, &ctx.setdel); + ctx.unsetdel = !ctx.setdel; + break; + case 'S': + ctx.predecessor = isc_commandline_argument; + break; + case 'i': + ctx.prepub = strtottl(isc_commandline_argument); + break; + case 'F': + /* Reserved for FIPS mode */ + FALLTHROUGH; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + if (!isatty(0)) { + ctx.quiet = true; + } + + ret = dst_lib_init(mctx, engine); + if (ret != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", isc_result_totext(ret)); + } + + setup_logging(mctx, &lctx); + + ctx.rdclass = strtoclass(classname); + + if (ctx.configfile == NULL || ctx.configfile[0] == '\0') { + ctx.configfile = NAMED_CONFFILE; + } + + if (ctx.predecessor == NULL) { + if (argc < isc_commandline_index + 1) { + fatal("the key name was not specified"); + } + if (argc > isc_commandline_index + 1) { + fatal("extraneous arguments"); + } + } + + if (ctx.predecessor == NULL && ctx.policy == NULL) { + if (algname == NULL) { + fatal("no algorithm specified"); + } + r.base = algname; + r.length = strlen(algname); + ret = dns_secalg_fromtext(&ctx.alg, &r); + if (ret != ISC_R_SUCCESS) { + fatal("unknown algorithm %s", algname); + } + if (!dst_algorithm_supported(ctx.alg)) { + fatal("unsupported algorithm: %s", algname); + } + } + + if (ctx.policy != NULL) { + if (ctx.nametype != NULL) { + fatal("-k and -n cannot be used together"); + } + if (ctx.predecessor != NULL) { + fatal("-k and -S cannot be used together"); + } + if (ctx.oldstyle) { + fatal("-k and -C cannot be used together"); + } + if (ctx.setttl) { + fatal("-k and -L cannot be used together"); + } + if (ctx.prepub > 0) { + fatal("-k and -i cannot be used together"); + } + if (ctx.size != -1) { + fatal("-k and -b cannot be used together"); + } + if (ctx.kskflag || ctx.revflag) { + fatal("-k and -f cannot be used together"); + } + if (ctx.options & DST_TYPE_KEY) { + fatal("-k and -T KEY cannot be used together"); + } + if (ctx.use_nsec3) { + fatal("-k and -3 cannot be used together"); + } + + ctx.options |= DST_TYPE_STATE; + + if (strcmp(ctx.policy, "default") == 0) { + ctx.use_nsec3 = false; + ctx.alg = DST_ALG_ECDSA256; + ctx.size = 0; + ctx.kskflag = DNS_KEYFLAG_KSK; + ctx.ttl = 3600; + ctx.setttl = true; + ctx.ksk = true; + ctx.zsk = true; + ctx.lifetime = 0; + + keygen(&ctx, mctx, argc, argv); + } else { + cfg_parser_t *parser = NULL; + cfg_obj_t *config = NULL; + dns_kasp_t *kasp = NULL; + dns_kasp_key_t *kaspkey = NULL; + + RUNTIME_CHECK(cfg_parser_create(mctx, lctx, &parser) == + ISC_R_SUCCESS); + if (cfg_parse_file(parser, ctx.configfile, + &cfg_type_namedconf, + &config) != ISC_R_SUCCESS) + { + fatal("unable to load dnssec-policy '%s' from " + "'%s'", + ctx.policy, ctx.configfile); + } + + kasp_from_conf(config, mctx, ctx.policy, &kasp); + if (kasp == NULL) { + fatal("failed to load dnssec-policy '%s'", + ctx.policy); + } + if (ISC_LIST_EMPTY(dns_kasp_keys(kasp))) { + fatal("dnssec-policy '%s' has no keys " + "configured", + ctx.policy); + } + + ctx.ttl = dns_kasp_dnskeyttl(kasp); + ctx.setttl = true; + + kaspkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); + + while (kaspkey != NULL) { + ctx.use_nsec3 = false; + ctx.alg = dns_kasp_key_algorithm(kaspkey); + ctx.size = dns_kasp_key_size(kaspkey); + ctx.kskflag = dns_kasp_key_ksk(kaspkey) + ? DNS_KEYFLAG_KSK + : 0; + ctx.ksk = dns_kasp_key_ksk(kaspkey); + ctx.zsk = dns_kasp_key_zsk(kaspkey); + ctx.lifetime = dns_kasp_key_lifetime(kaspkey); + + keygen(&ctx, mctx, argc, argv); + + kaspkey = ISC_LIST_NEXT(kaspkey, link); + } + + dns_kasp_detach(&kasp); + cfg_obj_destroy(parser, &config); + cfg_parser_destroy(&parser); + } + } else { + keygen(&ctx, mctx, argc, argv); + } + + cleanup_logging(&lctx); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_destroy(&mctx); + + if (freeit != NULL) { + free(freeit); + } + + return (0); +} diff --git a/bin/dnssec/dnssec-keygen.rst b/bin/dnssec/dnssec-keygen.rst new file mode 100644 index 0000000..3b6d46d --- /dev/null +++ b/bin/dnssec/dnssec-keygen.rst @@ -0,0 +1,318 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-keygen: + +dnssec-keygen: DNSSEC key generation tool +----------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`dnssec-keygen` [**-3**] [**-A** date/offset] [**-a** algorithm] [**-b** keysize] [**-C**] [**-c** class] [**-D** date/offset] [**-d** bits] [**-D** sync date/offset] [**-E** engine] [**-f** flag] [**-G**] [**-g** generator] [**-h**] [**-I** date/offset] [**-i** interval] [**-K** directory] [**-k** policy] [**-L** ttl] [**-l** file] [**-n** nametype] [**-P** date/offset] [**-P** sync date/offset] [**-p** protocol] [**-q**] [**-R** date/offset] [**-S** key] [**-s** strength] [**-T** rrtype] [**-t** type] [**-V**] [**-v** level] {name} + +Description +~~~~~~~~~~~ + +``dnssec-keygen`` generates keys for DNSSEC (Secure DNS), as defined in +:rfc:`2535` and :rfc:`4034`. It can also generate keys for use with TSIG +(Transaction Signatures) as defined in :rfc:`2845`, or TKEY (Transaction +Key) as defined in :rfc:`2930`. + +The ``name`` of the key is specified on the command line. For DNSSEC +keys, this must match the name of the zone for which the key is being +generated. + +The ``dnssec-keymgr`` command acts as a wrapper +around ``dnssec-keygen``, generating and updating keys +as needed to enforce defined security policies such as key rollover +scheduling. Using ``dnssec-keymgr`` may be preferable +to direct use of ``dnssec-keygen``. + +Options +~~~~~~~ + +``-3`` + This option uses an NSEC3-capable algorithm to generate a DNSSEC key. If this + option is used with an algorithm that has both NSEC and NSEC3 + versions, then the NSEC3 version is selected; for example, + ``dnssec-keygen -3 -a RSASHA1`` specifies the NSEC3RSASHA1 algorithm. + +``-a algorithm`` + This option selects the cryptographic algorithm. For DNSSEC keys, the value of + ``algorithm`` must be one of RSASHA1, NSEC3RSASHA1, RSASHA256, + RSASHA512, ECDSAP256SHA256, ECDSAP384SHA384, ED25519, or ED448. For + TKEY, the value must be DH (Diffie-Hellman); specifying this value + automatically sets the ``-T KEY`` option as well. + + These values are case-insensitive. In some cases, abbreviations are + supported, such as ECDSA256 for ECDSAP256SHA256 and ECDSA384 for + ECDSAP384SHA384. If RSASHA1 is specified along with the ``-3`` + option, NSEC3RSASHA1 is used instead. + + This parameter *must* be specified except when using the ``-S`` + option, which copies the algorithm from the predecessor key. + + In prior releases, HMAC algorithms could be generated for use as TSIG + keys, but that feature was removed in BIND 9.13.0. Use + ``tsig-keygen`` to generate TSIG keys. + +``-b keysize`` + This option specifies the number of bits in the key. The choice of key size + depends on the algorithm used: RSA keys must be between 1024 and 4096 + bits; Diffie-Hellman keys must be between 128 and 4096 bits. Elliptic + curve algorithms do not need this parameter. + + If the key size is not specified, some algorithms have pre-defined + defaults. For example, RSA keys for use as DNSSEC zone-signing keys + have a default size of 1024 bits; RSA keys for use as key-signing + keys (KSKs, generated with ``-f KSK``) default to 2048 bits. + +``-C`` + This option enables compatibility mode, which generates an old-style key, without any timing + metadata. By default, ``dnssec-keygen`` includes the key's + creation date in the metadata stored with the private key; other + dates may be set there as well, including publication date, activation date, + etc. Keys that include this data may be incompatible with older + versions of BIND; the ``-C`` option suppresses them. + +``-c class`` + This option indicates that the DNS record containing the key should have the + specified class. If not specified, class IN is used. + +``-d bits`` + This option specifies the key size in bits. For the algorithms RSASHA1, NSEC3RSASA1, RSASHA256, and + RSASHA512 the key size must be between 1024 and 4096 bits; DH size is between 128 + and 4096 bits. This option is ignored for algorithms ECDSAP256SHA256, + ECDSAP384SHA384, ED25519, and ED448. + +``-E engine`` + This option specifies the cryptographic hardware to use, when applicable. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). When BIND is + built with native PKCS#11 cryptography (``--enable-native-pkcs11``), it + defaults to the path of the PKCS#11 provider library specified via + ``--with-pkcs11``. + +``-f flag`` + This option sets the specified flag in the flag field of the KEY/DNSKEY record. + The only recognized flags are KSK (Key-Signing Key) and REVOKE. + +``-G`` + This option generates a key, but does not publish it or sign with it. This option is + incompatible with ``-P`` and ``-A``. + +``-g generator`` + This option indicates the generator to use if generating a Diffie-Hellman key. Allowed + values are 2 and 5. If no generator is specified, a known prime from + :rfc:`2539` is used if possible; otherwise the default is 2. + +``-h`` + This option prints a short summary of the options and arguments to + ``dnssec-keygen``. + +``-K directory`` + This option sets the directory in which the key files are to be written. + +``-k policy`` + This option creates keys for a specific ``dnssec-policy``. If a policy uses multiple keys, + ``dnssec-keygen`` generates multiple keys. This also + creates a ".state" file to keep track of the key state. + + This option creates keys according to the ``dnssec-policy`` configuration, hence + it cannot be used at the same time as many of the other options that + ``dnssec-keygen`` provides. + +``-L ttl`` + This option sets the default TTL to use for this key when it is converted into a + DNSKEY RR. This is the TTL used when the key is imported into a zone, + unless there was already a DNSKEY RRset in + place, in which case the existing TTL takes precedence. If this + value is not set and there is no existing DNSKEY RRset, the TTL + defaults to the SOA TTL. Setting the default TTL to ``0`` or ``none`` + is the same as leaving it unset. + +``-l file`` + This option provides a configuration file that contains a ``dnssec-policy`` statement + (matching the policy set with ``-k``). + +``-n nametype`` + This option specifies the owner type of the key. The value of ``nametype`` must + either be ZONE (for a DNSSEC zone key (KEY/DNSKEY)), HOST or ENTITY + (for a key associated with a host (KEY)), USER (for a key associated + with a user (KEY)), or OTHER (DNSKEY). These values are + case-insensitive. The default is ZONE for DNSKEY generation. + +``-p protocol`` + This option sets the protocol value for the generated key, for use with + ``-T KEY``. The protocol is a number between 0 and 255. The default + is 3 (DNSSEC). Other possible values for this argument are listed in + :rfc:`2535` and its successors. + +``-q`` + This option sets quiet mode, which suppresses unnecessary output, including progress + indication. Without this option, when ``dnssec-keygen`` is run + interactively to generate an RSA or DSA key pair, it prints a + string of symbols to ``stderr`` indicating the progress of the key + generation. A ``.`` indicates that a random number has been found which + passed an initial sieve test; ``+`` means a number has passed a single + round of the Miller-Rabin primality test; and a space ( ) means that the + number has passed all the tests and is a satisfactory key. + +``-S key`` + This option creates a new key which is an explicit successor to an existing key. + The name, algorithm, size, and type of the key are set to match + the existing key. The activation date of the new key is set to + the inactivation date of the existing one. The publication date is + set to the activation date minus the prepublication interval, + which defaults to 30 days. + +``-s strength`` + This option specifies the strength value of the key. The strength is a number + between 0 and 15, and currently has no defined purpose in DNSSEC. + +``-T rrtype`` + This option specifies the resource record type to use for the key. ``rrtype`` + must be either DNSKEY or KEY. The default is DNSKEY when using a + DNSSEC algorithm, but it can be overridden to KEY for use with + SIG(0). + +``-t type`` + This option indicates the type of the key for use with ``-T KEY``. ``type`` + must be one of AUTHCONF, NOAUTHCONF, NOAUTH, or NOCONF. The default + is AUTHCONF. AUTH refers to the ability to authenticate data, and + CONF to the ability to encrypt data. + +``-V`` + This option prints version information. + +``-v level`` + This option sets the debugging level. + +Timing Options +~~~~~~~~~~~~~~ + +Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS. If the +argument begins with a ``+`` or ``-``, it is interpreted as an offset from +the present time. For convenience, if such an offset is followed by one +of the suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, then the offset is +computed in years (defined as 365 24-hour days, ignoring leap years), +months (defined as 30 24-hour days), weeks, days, hours, or minutes, +respectively. Without a suffix, the offset is computed in seconds. To +explicitly prevent a date from being set, use ``none`` or ``never``. + +``-P date/offset`` + This option sets the date on which a key is to be published to the zone. After + that date, the key is included in the zone but is not used + to sign it. If not set, and if the ``-G`` option has not been used, the + default is the current date. + +``-P sync date/offset`` + This option sets the date on which CDS and CDNSKEY records that match this key + are to be published to the zone. + +``-A date/offset`` + This option sets the date on which the key is to be activated. After that date, + the key is included in the zone and used to sign it. If not set, + and if the ``-G`` option has not been used, the default is the current date. If set, + and ``-P`` is not set, the publication date is set to the + activation date minus the prepublication interval. + +``-R date/offset`` + This option sets the date on which the key is to be revoked. After that date, the + key is flagged as revoked. It is included in the zone and + is used to sign it. + +``-I date/offset`` + This option sets the date on which the key is to be retired. After that date, the + key is still included in the zone, but it is not used to + sign it. + +``-D date/offset`` + This option sets the date on which the key is to be deleted. After that date, the + key is no longer included in the zone. (However, it may remain in the key + repository.) + +``-D sync date/offset`` + This option sets the date on which the CDS and CDNSKEY records that match this + key are to be deleted. + +``-i interval`` + This option sets the prepublication interval for a key. If set, then the + publication and activation dates must be separated by at least this + much time. If the activation date is specified but the publication + date is not, the publication date defaults to this much time + before the activation date; conversely, if the publication date is + specified but not the activation date, activation is set to + this much time after publication. + + If the key is being created as an explicit successor to another key, + then the default prepublication interval is 30 days; otherwise it is + zero. + + As with date offsets, if the argument is followed by one of the + suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, the interval is + measured in years, months, weeks, days, hours, or minutes, + respectively. Without a suffix, the interval is measured in seconds. + +Generated Keys +~~~~~~~~~~~~~~ + +When ``dnssec-keygen`` completes successfully, it prints a string of the +form ``Knnnn.+aaa+iiiii`` to the standard output. This is an +identification string for the key it has generated. + +- ``nnnn`` is the key name. + +- ``aaa`` is the numeric representation of the algorithm. + +- ``iiiii`` is the key identifier (or footprint). + +``dnssec-keygen`` creates two files, with names based on the printed +string. ``Knnnn.+aaa+iiiii.key`` contains the public key, and +``Knnnn.+aaa+iiiii.private`` contains the private key. + +The ``.key`` file contains a DNSKEY or KEY record. When a zone is being +signed by ``named`` or ``dnssec-signzone -S``, DNSKEY records are +included automatically. In other cases, the ``.key`` file can be +inserted into a zone file manually or with an ``$INCLUDE`` statement. + +The ``.private`` file contains algorithm-specific fields. For obvious +security reasons, this file does not have general read permission. + +Example +~~~~~~~ + +To generate an ECDSAP256SHA256 zone-signing key for the zone +``example.com``, issue the command: + +``dnssec-keygen -a ECDSAP256SHA256 example.com`` + +The command prints a string of the form: + +``Kexample.com.+013+26160`` + +In this example, ``dnssec-keygen`` creates the files +``Kexample.com.+013+26160.key`` and ``Kexample.com.+013+26160.private``. + +To generate a matching key-signing key, issue the command: + +``dnssec-keygen -a ECDSAP256SHA256 -f KSK example.com`` + +See Also +~~~~~~~~ + +:manpage:`dnssec-signzone(8)`, BIND 9 Administrator Reference Manual, :rfc:`2539`, +:rfc:`2845`, :rfc:`4034`. diff --git a/bin/dnssec/dnssec-revoke.c b/bin/dnssec/dnssec-revoke.c new file mode 100644 index 0000000..c532463 --- /dev/null +++ b/bin/dnssec/dnssec-revoke.c @@ -0,0 +1,278 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-revoke"; + +static isc_mem_t *mctx = NULL; + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s [options] keyfile\n\n", program); + fprintf(stderr, "Version: %s\n", VERSION); +#if USE_PKCS11 + fprintf(stderr, + " -E engine: specify PKCS#11 provider " + "(default: %s)\n", + PK11_LIB_LOCATION); +#else /* if USE_PKCS11 */ + fprintf(stderr, " -E engine: specify OpenSSL engine\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, " -f: force overwrite\n"); + fprintf(stderr, " -h: help\n"); + fprintf(stderr, " -K directory: use directory for key files\n"); + fprintf(stderr, " -r: remove old keyfiles after " + "creating revoked version\n"); + fprintf(stderr, " -v level: set level of verbosity\n"); + fprintf(stderr, " -V: print version information\n"); + fprintf(stderr, "Output:\n"); + fprintf(stderr, " K++.key, " + "K++.private\n"); + + exit(-1); +} + +int +main(int argc, char **argv) { + isc_result_t result; + const char *engine = NULL; + char const *filename = NULL; + char *dir = NULL; + char newname[1024], oldname[1024]; + char keystr[DST_KEY_FORMATSIZE]; + char *endp; + int ch; + dst_key_t *key = NULL; + uint32_t flags; + isc_buffer_t buf; + bool force = false; + bool removefile = false; + bool id = false; + + if (argc == 1) { + usage(); + } + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + while ((ch = isc_commandline_parse(argc, argv, "E:fK:rRhv:V")) != -1) { + switch (ch) { + case 'E': + engine = isc_commandline_argument; + break; + case 'f': + force = true; + break; + case 'K': + /* + * We don't have to copy it here, but do it to + * simplify cleanup later + */ + dir = isc_mem_strdup(mctx, isc_commandline_argument); + break; + case 'r': + removefile = true; + break; + case 'R': + id = true; + break; + case 'v': + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("-v must be followed by a number"); + } + break; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + if (argc < isc_commandline_index + 1 || + argv[isc_commandline_index] == NULL) + { + fatal("The key file name was not specified"); + } + if (argc > isc_commandline_index + 1) { + fatal("Extraneous arguments"); + } + + if (dir != NULL) { + filename = argv[isc_commandline_index]; + } else { + result = isc_file_splitpath(mctx, argv[isc_commandline_index], + &dir, &filename); + if (result != ISC_R_SUCCESS) { + fatal("cannot process filename %s: %s", + argv[isc_commandline_index], + isc_result_totext(result)); + } + if (strcmp(dir, ".") == 0) { + isc_mem_free(mctx, dir); + dir = NULL; + } + } + + result = dst_lib_init(mctx, engine); + if (result != ISC_R_SUCCESS) { + fatal("Could not initialize dst: %s", + isc_result_totext(result)); + } + + result = dst_key_fromnamedfile( + filename, dir, DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, mctx, &key); + if (result != ISC_R_SUCCESS) { + fatal("Invalid keyfile name %s: %s", filename, + isc_result_totext(result)); + } + + if (id) { + fprintf(stdout, "%u\n", dst_key_rid(key)); + goto cleanup; + } + dst_key_format(key, keystr, sizeof(keystr)); + + if (verbose > 2) { + fprintf(stderr, "%s: %s\n", program, keystr); + } + + if (force) { + set_keyversion(key); + } else { + check_keyversion(key, keystr); + } + + flags = dst_key_flags(key); + if ((flags & DNS_KEYFLAG_REVOKE) == 0) { + isc_stdtime_t now; + + if ((flags & DNS_KEYFLAG_KSK) == 0) { + fprintf(stderr, + "%s: warning: Key is not flagged " + "as a KSK. Revoking a ZSK is " + "legal, but undefined.\n", + program); + } + + isc_stdtime_get(&now); + dst_key_settime(key, DST_TIME_REVOKE, now); + + dst_key_setflags(key, flags | DNS_KEYFLAG_REVOKE); + + isc_buffer_init(&buf, newname, sizeof(newname)); + dst_key_buildfilename(key, DST_TYPE_PUBLIC, dir, &buf); + + if (access(newname, F_OK) == 0 && !force) { + fatal("Key file %s already exists; " + "use -f to force overwrite", + newname); + } + + result = dst_key_tofile(key, DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, + dir); + if (result != ISC_R_SUCCESS) { + dst_key_format(key, keystr, sizeof(keystr)); + fatal("Failed to write key %s: %s", keystr, + isc_result_totext(result)); + } + + isc_buffer_clear(&buf); + dst_key_buildfilename(key, 0, dir, &buf); + printf("%s\n", newname); + + /* + * Remove old key file, if told to (and if + * it isn't the same as the new file) + */ + if (removefile) { + isc_buffer_init(&buf, oldname, sizeof(oldname)); + dst_key_setflags(key, flags & ~DNS_KEYFLAG_REVOKE); + dst_key_buildfilename(key, DST_TYPE_PRIVATE, dir, &buf); + if (strcmp(oldname, newname) == 0) { + goto cleanup; + } + (void)unlink(oldname); + isc_buffer_clear(&buf); + dst_key_buildfilename(key, DST_TYPE_PUBLIC, dir, &buf); + (void)unlink(oldname); + } + } else { + dst_key_format(key, keystr, sizeof(keystr)); + fatal("Key %s is already revoked", keystr); + } + +cleanup: + dst_key_free(&key); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + if (dir != NULL) { + isc_mem_free(mctx, dir); + } + isc_mem_destroy(&mctx); + + return (0); +} diff --git a/bin/dnssec/dnssec-revoke.rst b/bin/dnssec/dnssec-revoke.rst new file mode 100644 index 0000000..f664646 --- /dev/null +++ b/bin/dnssec/dnssec-revoke.rst @@ -0,0 +1,71 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-revoke: + +dnssec-revoke - set the REVOKED bit on a DNSSEC key +--------------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`dnssec-revoke` [**-hr**] [**-v** level] [**-V**] [**-K** directory] [**-E** engine] [**-f**] [**-R**] {keyfile} + +Description +~~~~~~~~~~~ + +``dnssec-revoke`` reads a DNSSEC key file, sets the REVOKED bit on the +key as defined in :rfc:`5011`, and creates a new pair of key files +containing the now-revoked key. + +Options +~~~~~~~ + +``-h`` + This option emits a usage message and exits. + +``-K directory`` + This option sets the directory in which the key files are to reside. + +``-r`` + This option indicates to remove the original keyset files after writing the new keyset files. + +``-v level`` + This option sets the debugging level. + +``-V`` + This option prints version information. + +``-E engine`` + This option specifies the cryptographic hardware to use, when applicable. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). When BIND is + built with native PKCS#11 cryptography (``--enable-native-pkcs11``), it + defaults to the path of the PKCS#11 provider library specified via + ``--with-pkcs11``. + +``-f`` + This option indicates a forced overwrite and causes ``dnssec-revoke`` to write the new key pair, + even if a file already exists matching the algorithm and key ID of + the revoked key. + +``-R`` + This option prints the key tag of the key with the REVOKE bit set, but does not + revoke the key. + +See Also +~~~~~~~~ + +:manpage:`dnssec-keygen(8)`, BIND 9 Administrator Reference Manual, :rfc:`5011`. diff --git a/bin/dnssec/dnssec-settime.c b/bin/dnssec/dnssec-settime.c new file mode 100644 index 0000000..1df6af1 --- /dev/null +++ b/bin/dnssec/dnssec-settime.c @@ -0,0 +1,985 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-settime"; + +static isc_mem_t *mctx = NULL; + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, " %s [options] keyfile\n\n", program); + fprintf(stderr, "Version: %s\n", VERSION); + fprintf(stderr, "General options:\n"); +#if USE_PKCS11 + fprintf(stderr, + " -E engine: specify PKCS#11 provider " + "(default: %s)\n", + PK11_LIB_LOCATION); +#elif defined(USE_PKCS11) + fprintf(stderr, " -E engine: specify OpenSSL engine " + "(default \"pkcs11\")\n"); +#else /* if USE_PKCS11 */ + fprintf(stderr, " -E engine: specify OpenSSL engine\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, " -f: force update of old-style " + "keys\n"); + fprintf(stderr, " -K directory: set key file location\n"); + fprintf(stderr, " -L ttl: set default key TTL\n"); + fprintf(stderr, " -v level: set level of verbosity\n"); + fprintf(stderr, " -V: print version information\n"); + fprintf(stderr, " -h: help\n"); + fprintf(stderr, "Timing options:\n"); + fprintf(stderr, " -P date/[+-]offset/none: set/unset key " + "publication date\n"); + fprintf(stderr, " -P ds date/[+-]offset/none: set/unset " + "DS publication date\n"); + fprintf(stderr, " -P sync date/[+-]offset/none: set/unset " + "CDS and CDNSKEY publication date\n"); + fprintf(stderr, " -A date/[+-]offset/none: set/unset key " + "activation date\n"); + fprintf(stderr, " -R date/[+-]offset/none: set/unset key " + "revocation date\n"); + fprintf(stderr, " -I date/[+-]offset/none: set/unset key " + "inactivation date\n"); + fprintf(stderr, " -D date/[+-]offset/none: set/unset key " + "deletion date\n"); + fprintf(stderr, " -D ds date/[+-]offset/none: set/unset " + "DS deletion date\n"); + fprintf(stderr, " -D sync date/[+-]offset/none: set/unset " + "CDS and CDNSKEY deletion date\n"); + fprintf(stderr, " -S : generate a successor to an existing " + "key\n"); + fprintf(stderr, " -i : prepublication interval for " + "successor key " + "(default: 30 days)\n"); + fprintf(stderr, "Key state options:\n"); + fprintf(stderr, " -s: update key state file (default no)\n"); + fprintf(stderr, " -g state: set the goal state for this key\n"); + fprintf(stderr, " -d state date/[+-]offset: set the DS state\n"); + fprintf(stderr, " -k state date/[+-]offset: set the DNSKEY state\n"); + fprintf(stderr, " -r state date/[+-]offset: set the RRSIG (KSK) " + "state\n"); + fprintf(stderr, " -z state date/[+-]offset: set the RRSIG (ZSK) " + "state\n"); + fprintf(stderr, "Printing options:\n"); + fprintf(stderr, " -p C/P/Psync/A/R/I/D/Dsync/all: print a " + "particular time value or values\n"); + fprintf(stderr, " -u: print times in unix epoch " + "format\n"); + fprintf(stderr, "Output:\n"); + fprintf(stderr, " K++.key, " + "K++.private\n"); + + exit(-1); +} + +static void +printtime(dst_key_t *key, int type, const char *tag, bool epoch, FILE *stream) { + isc_result_t result; + isc_stdtime_t when; + + if (tag != NULL) { + fprintf(stream, "%s: ", tag); + } + + result = dst_key_gettime(key, type, &when); + if (result == ISC_R_NOTFOUND) { + fprintf(stream, "UNSET\n"); + } else if (epoch) { + fprintf(stream, "%d\n", (int)when); + } else { + time_t now = when; + struct tm t, *tm = localtime_r(&now, &t); + unsigned int flen; + char timebuf[80]; + + if (tm == NULL) { + fprintf(stream, "INVALID\n"); + return; + } + + flen = strftime(timebuf, sizeof(timebuf), + "%a %b %e %H:%M:%S %Y", tm); + INSIST(flen > 0U && flen < sizeof(timebuf)); + fprintf(stream, "%s\n", timebuf); + } +} + +static void +writekey(dst_key_t *key, const char *directory, bool write_state) { + char newname[1024]; + char keystr[DST_KEY_FORMATSIZE]; + isc_buffer_t buf; + isc_result_t result; + int options = DST_TYPE_PUBLIC | DST_TYPE_PRIVATE; + + if (write_state) { + options |= DST_TYPE_STATE; + } + + isc_buffer_init(&buf, newname, sizeof(newname)); + result = dst_key_buildfilename(key, DST_TYPE_PUBLIC, directory, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build public key filename: %s", + isc_result_totext(result)); + } + + result = dst_key_tofile(key, options, directory); + if (result != ISC_R_SUCCESS) { + dst_key_format(key, keystr, sizeof(keystr)); + fatal("Failed to write key %s: %s", keystr, + isc_result_totext(result)); + } + printf("%s\n", newname); + + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_PRIVATE, directory, &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build private key filename: %s", + isc_result_totext(result)); + } + printf("%s\n", newname); + + if (write_state) { + isc_buffer_clear(&buf); + result = dst_key_buildfilename(key, DST_TYPE_STATE, directory, + &buf); + if (result != ISC_R_SUCCESS) { + fatal("Failed to build key state filename: %s", + isc_result_totext(result)); + } + printf("%s\n", newname); + } +} + +int +main(int argc, char **argv) { + isc_result_t result; + const char *engine = NULL; + const char *filename = NULL; + char *directory = NULL; + char keystr[DST_KEY_FORMATSIZE]; + char *endp, *p; + int ch; + const char *predecessor = NULL; + dst_key_t *prevkey = NULL; + dst_key_t *key = NULL; + dns_name_t *name = NULL; + dns_secalg_t alg = 0; + unsigned int size = 0; + uint16_t flags = 0; + int prepub = -1; + int options; + dns_ttl_t ttl = 0; + isc_stdtime_t now; + isc_stdtime_t dstime = 0, dnskeytime = 0; + isc_stdtime_t krrsigtime = 0, zrrsigtime = 0; + isc_stdtime_t pub = 0, act = 0, rev = 0, inact = 0, del = 0; + isc_stdtime_t prevact = 0, previnact = 0, prevdel = 0; + dst_key_state_t goal = DST_KEY_STATE_NA; + dst_key_state_t ds = DST_KEY_STATE_NA; + dst_key_state_t dnskey = DST_KEY_STATE_NA; + dst_key_state_t krrsig = DST_KEY_STATE_NA; + dst_key_state_t zrrsig = DST_KEY_STATE_NA; + bool setgoal = false, setds = false, setdnskey = false; + bool setkrrsig = false, setzrrsig = false; + bool setdstime = false, setdnskeytime = false; + bool setkrrsigtime = false, setzrrsigtime = false; + bool setpub = false, setact = false; + bool setrev = false, setinact = false; + bool setdel = false, setttl = false; + bool unsetpub = false, unsetact = false; + bool unsetrev = false, unsetinact = false; + bool unsetdel = false; + bool printcreate = false, printpub = false; + bool printact = false, printrev = false; + bool printinact = false, printdel = false; + bool force = false; + bool epoch = false; + bool changed = false; + bool write_state = false; + isc_log_t *log = NULL; + isc_stdtime_t syncadd = 0, syncdel = 0; + bool unsetsyncadd = false, setsyncadd = false; + bool unsetsyncdel = false, setsyncdel = false; + bool printsyncadd = false, printsyncdel = false; + isc_stdtime_t dsadd = 0, dsdel = 0; + bool unsetdsadd = false, setdsadd = false; + bool unsetdsdel = false, setdsdel = false; + bool printdsadd = false, printdsdel = false; + + options = DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE; + + if (argc == 1) { + usage(); + } + + isc_mem_create(&mctx); + + setup_logging(mctx, &log); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + isc_stdtime_get(&now); + +#define CMDLINE_FLAGS "A:D:d:E:fg:hI:i:K:k:L:P:p:R:r:S:suv:Vz:" + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'A': + if (setact || unsetact) { + fatal("-A specified more than once"); + } + + changed = true; + act = strtotime(isc_commandline_argument, now, now, + &setact); + unsetact = !setact; + break; + case 'D': + /* -Dsync ? */ + if (isoptarg("sync", argv, usage)) { + if (unsetsyncdel || setsyncdel) { + fatal("-D sync specified more than " + "once"); + } + + changed = true; + syncdel = strtotime(isc_commandline_argument, + now, now, &setsyncdel); + unsetsyncdel = !setsyncdel; + break; + } + /* -Dds ? */ + if (isoptarg("ds", argv, usage)) { + if (unsetdsdel || setdsdel) { + fatal("-D ds specified more than once"); + } + + changed = true; + dsdel = strtotime(isc_commandline_argument, now, + now, &setdsdel); + unsetdsdel = !setdsdel; + break; + } + /* -Ddnskey ? */ + (void)isoptarg("dnskey", argv, usage); + if (setdel || unsetdel) { + fatal("-D specified more than once"); + } + + changed = true; + del = strtotime(isc_commandline_argument, now, now, + &setdel); + unsetdel = !setdel; + break; + case 'd': + if (setds) { + fatal("-d specified more than once"); + } + + ds = strtokeystate(isc_commandline_argument); + setds = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + dstime = strtotime(isc_commandline_argument, now, now, + &setdstime); + break; + case 'E': + engine = isc_commandline_argument; + break; + case 'f': + force = true; + break; + case 'g': + if (setgoal) { + fatal("-g specified more than once"); + } + + goal = strtokeystate(isc_commandline_argument); + if (goal != DST_KEY_STATE_NA && + goal != DST_KEY_STATE_HIDDEN && + goal != DST_KEY_STATE_OMNIPRESENT) + { + fatal("-g must be either none, hidden, or " + "omnipresent"); + } + setgoal = true; + break; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + case 'I': + if (setinact || unsetinact) { + fatal("-I specified more than once"); + } + + changed = true; + inact = strtotime(isc_commandline_argument, now, now, + &setinact); + unsetinact = !setinact; + break; + case 'i': + prepub = strtottl(isc_commandline_argument); + break; + case 'K': + /* + * We don't have to copy it here, but do it to + * simplify cleanup later + */ + directory = isc_mem_strdup(mctx, + isc_commandline_argument); + break; + case 'k': + if (setdnskey) { + fatal("-k specified more than once"); + } + + dnskey = strtokeystate(isc_commandline_argument); + setdnskey = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + dnskeytime = strtotime(isc_commandline_argument, now, + now, &setdnskeytime); + break; + case 'L': + ttl = strtottl(isc_commandline_argument); + setttl = true; + break; + case 'P': + /* -Psync ? */ + if (isoptarg("sync", argv, usage)) { + if (unsetsyncadd || setsyncadd) { + fatal("-P sync specified more than " + "once"); + } + + changed = true; + syncadd = strtotime(isc_commandline_argument, + now, now, &setsyncadd); + unsetsyncadd = !setsyncadd; + break; + } + /* -Pds ? */ + if (isoptarg("ds", argv, usage)) { + if (unsetdsadd || setdsadd) { + fatal("-P ds specified more than once"); + } + + changed = true; + dsadd = strtotime(isc_commandline_argument, now, + now, &setdsadd); + unsetdsadd = !setdsadd; + break; + } + /* -Pdnskey ? */ + (void)isoptarg("dnskey", argv, usage); + if (setpub || unsetpub) { + fatal("-P specified more than once"); + } + + changed = true; + pub = strtotime(isc_commandline_argument, now, now, + &setpub); + unsetpub = !setpub; + break; + case 'p': + p = isc_commandline_argument; + if (!strcasecmp(p, "all")) { + printcreate = true; + printpub = true; + printact = true; + printrev = true; + printinact = true; + printdel = true; + printsyncadd = true; + printsyncdel = true; + printdsadd = true; + printdsdel = true; + break; + } + + do { + switch (*p++) { + case 'A': + printact = true; + break; + case 'C': + printcreate = true; + break; + case 'D': + if (!strncmp(p, "sync", 4)) { + p += 4; + printsyncdel = true; + break; + } + if (!strncmp(p, "ds", 2)) { + p += 2; + printdsdel = true; + break; + } + printdel = true; + break; + case 'I': + printinact = true; + break; + case 'P': + if (!strncmp(p, "sync", 4)) { + p += 4; + printsyncadd = true; + break; + } + if (!strncmp(p, "ds", 2)) { + p += 2; + printdsadd = true; + break; + } + printpub = true; + break; + case 'R': + printrev = true; + break; + case ' ': + break; + default: + usage(); + break; + } + } while (*p != '\0'); + break; + case 'R': + if (setrev || unsetrev) { + fatal("-R specified more than once"); + } + + changed = true; + rev = strtotime(isc_commandline_argument, now, now, + &setrev); + unsetrev = !setrev; + break; + case 'r': + if (setkrrsig) { + fatal("-r specified more than once"); + } + + krrsig = strtokeystate(isc_commandline_argument); + setkrrsig = true; + /* time */ + (void)isoptarg(isc_commandline_argument, argv, usage); + krrsigtime = strtotime(isc_commandline_argument, now, + now, &setkrrsigtime); + break; + case 'S': + predecessor = isc_commandline_argument; + break; + case 's': + write_state = true; + break; + case 'u': + epoch = true; + break; + case 'V': + /* Does not return. */ + version(program); + case 'v': + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("-v must be followed by a number"); + } + break; + case 'z': + if (setzrrsig) { + fatal("-z specified more than once"); + } + + zrrsig = strtokeystate(isc_commandline_argument); + setzrrsig = true; + (void)isoptarg(isc_commandline_argument, argv, usage); + zrrsigtime = strtotime(isc_commandline_argument, now, + now, &setzrrsigtime); + break; + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + if (argc < isc_commandline_index + 1 || + argv[isc_commandline_index] == NULL) + { + fatal("The key file name was not specified"); + } + if (argc > isc_commandline_index + 1) { + fatal("Extraneous arguments"); + } + + if ((setgoal || setds || setdnskey || setkrrsig || setzrrsig) && + !write_state) + { + fatal("Options -g, -d, -k, -r and -z require -s to be set"); + } + + result = dst_lib_init(mctx, engine); + if (result != ISC_R_SUCCESS) { + fatal("Could not initialize dst: %s", + isc_result_totext(result)); + } + + if (predecessor != NULL) { + int major, minor; + + if (prepub == -1) { + prepub = (30 * 86400); + } + + if (setpub || unsetpub) { + fatal("-S and -P cannot be used together"); + } + if (setact || unsetact) { + fatal("-S and -A cannot be used together"); + } + + result = dst_key_fromnamedfile(predecessor, directory, options, + mctx, &prevkey); + if (result != ISC_R_SUCCESS) { + fatal("Invalid keyfile %s: %s", filename, + isc_result_totext(result)); + } + if (!dst_key_isprivate(prevkey) && !dst_key_isexternal(prevkey)) + { + fatal("%s is not a private key", filename); + } + + name = dst_key_name(prevkey); + alg = dst_key_alg(prevkey); + size = dst_key_size(prevkey); + flags = dst_key_flags(prevkey); + + dst_key_format(prevkey, keystr, sizeof(keystr)); + dst_key_getprivateformat(prevkey, &major, &minor); + if (major != DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) { + fatal("Predecessor has incompatible format " + "version %d.%d\n\t", + major, minor); + } + + result = dst_key_gettime(prevkey, DST_TIME_ACTIVATE, &prevact); + if (result != ISC_R_SUCCESS) { + fatal("Predecessor has no activation date. " + "You must set one before\n\t" + "generating a successor."); + } + + result = dst_key_gettime(prevkey, DST_TIME_INACTIVE, + &previnact); + if (result != ISC_R_SUCCESS) { + fatal("Predecessor has no inactivation date. " + "You must set one before\n\t" + "generating a successor."); + } + + pub = previnact - prepub; + act = previnact; + + if ((previnact - prepub) < now && prepub != 0) { + fatal("Time until predecessor inactivation is\n\t" + "shorter than the prepublication interval. " + "Either change\n\t" + "predecessor inactivation date, or use the -i " + "option to set\n\t" + "a shorter prepublication interval."); + } + + result = dst_key_gettime(prevkey, DST_TIME_DELETE, &prevdel); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, + "%s: warning: Predecessor has no " + "removal date;\n\t" + "it will remain in the zone " + "indefinitely after rollover.\n", + program); + } else if (prevdel < previnact) { + fprintf(stderr, + "%s: warning: Predecessor is " + "scheduled to be deleted\n\t" + "before it is scheduled to be " + "inactive.\n", + program); + } + + changed = setpub = setact = true; + } else { + if (prepub < 0) { + prepub = 0; + } + + if (prepub > 0) { + if (setpub && setact && (act - prepub) < pub) { + fatal("Activation and publication dates " + "are closer together than the\n\t" + "prepublication interval."); + } + + if (setpub && !setact) { + setact = true; + act = pub + prepub; + } else if (setact && !setpub) { + setpub = true; + pub = act - prepub; + } + + if ((act - prepub) < now) { + fatal("Time until activation is shorter " + "than the\n\tprepublication interval."); + } + } + } + + if (directory != NULL) { + filename = argv[isc_commandline_index]; + } else { + result = isc_file_splitpath(mctx, argv[isc_commandline_index], + &directory, &filename); + if (result != ISC_R_SUCCESS) { + fatal("cannot process filename %s: %s", + argv[isc_commandline_index], + isc_result_totext(result)); + } + } + + result = dst_key_fromnamedfile(filename, directory, options, mctx, + &key); + if (result != ISC_R_SUCCESS) { + fatal("Invalid keyfile %s: %s", filename, + isc_result_totext(result)); + } + + if (!dst_key_isprivate(key) && !dst_key_isexternal(key)) { + fatal("%s is not a private key", filename); + } + + dst_key_format(key, keystr, sizeof(keystr)); + + if (predecessor != NULL) { + if (!dns_name_equal(name, dst_key_name(key))) { + fatal("Key name mismatch"); + } + if (alg != dst_key_alg(key)) { + fatal("Key algorithm mismatch"); + } + if (size != dst_key_size(key)) { + fatal("Key size mismatch"); + } + if (flags != dst_key_flags(key)) { + fatal("Key flags mismatch"); + } + } + + prevdel = previnact = 0; + if ((setdel && setinact && del < inact) || + (dst_key_gettime(key, DST_TIME_INACTIVE, &previnact) == + ISC_R_SUCCESS && + setdel && !setinact && !unsetinact && del < previnact) || + (dst_key_gettime(key, DST_TIME_DELETE, &prevdel) == ISC_R_SUCCESS && + setinact && !setdel && !unsetdel && prevdel < inact) || + (!setdel && !unsetdel && !setinact && !unsetinact && prevdel != 0 && + prevdel < previnact)) + { + fprintf(stderr, + "%s: warning: Key is scheduled to " + "be deleted before it is\n\t" + "scheduled to be inactive.\n", + program); + } + + if (force) { + set_keyversion(key); + } else { + check_keyversion(key, keystr); + } + + if (verbose > 2) { + fprintf(stderr, "%s: %s\n", program, keystr); + } + + /* + * Set time values. + */ + if (setpub) { + dst_key_settime(key, DST_TIME_PUBLISH, pub); + } else if (unsetpub) { + dst_key_unsettime(key, DST_TIME_PUBLISH); + } + + if (setact) { + dst_key_settime(key, DST_TIME_ACTIVATE, act); + } else if (unsetact) { + dst_key_unsettime(key, DST_TIME_ACTIVATE); + } + + if (setrev) { + if ((dst_key_flags(key) & DNS_KEYFLAG_REVOKE) != 0) { + fprintf(stderr, + "%s: warning: Key %s is already " + "revoked; changing the revocation date " + "will not affect this.\n", + program, keystr); + } + if ((dst_key_flags(key) & DNS_KEYFLAG_KSK) == 0) { + fprintf(stderr, + "%s: warning: Key %s is not flagged as " + "a KSK, but -R was used. Revoking a " + "ZSK is legal, but undefined.\n", + program, keystr); + } + dst_key_settime(key, DST_TIME_REVOKE, rev); + } else if (unsetrev) { + if ((dst_key_flags(key) & DNS_KEYFLAG_REVOKE) != 0) { + fprintf(stderr, + "%s: warning: Key %s is already " + "revoked; removing the revocation date " + "will not affect this.\n", + program, keystr); + } + dst_key_unsettime(key, DST_TIME_REVOKE); + } + + if (setinact) { + dst_key_settime(key, DST_TIME_INACTIVE, inact); + } else if (unsetinact) { + dst_key_unsettime(key, DST_TIME_INACTIVE); + } + + if (setdel) { + dst_key_settime(key, DST_TIME_DELETE, del); + } else if (unsetdel) { + dst_key_unsettime(key, DST_TIME_DELETE); + } + + if (setsyncadd) { + dst_key_settime(key, DST_TIME_SYNCPUBLISH, syncadd); + } else if (unsetsyncadd) { + dst_key_unsettime(key, DST_TIME_SYNCPUBLISH); + } + + if (setsyncdel) { + dst_key_settime(key, DST_TIME_SYNCDELETE, syncdel); + } else if (unsetsyncdel) { + dst_key_unsettime(key, DST_TIME_SYNCDELETE); + } + + if (setdsadd) { + dst_key_settime(key, DST_TIME_DSPUBLISH, dsadd); + } else if (unsetdsadd) { + dst_key_unsettime(key, DST_TIME_DSPUBLISH); + } + + if (setdsdel) { + dst_key_settime(key, DST_TIME_DSDELETE, dsdel); + } else if (unsetdsdel) { + dst_key_unsettime(key, DST_TIME_DSDELETE); + } + + if (setttl) { + dst_key_setttl(key, ttl); + } + + if (predecessor != NULL && prevkey != NULL) { + dst_key_setnum(prevkey, DST_NUM_SUCCESSOR, dst_key_id(key)); + dst_key_setnum(key, DST_NUM_PREDECESSOR, dst_key_id(prevkey)); + } + + /* + * No metadata changes were made but we're forcing an upgrade + * to the new format anyway: use "-P now -A now" as the default + */ + if (force && !changed) { + dst_key_settime(key, DST_TIME_PUBLISH, now); + dst_key_settime(key, DST_TIME_ACTIVATE, now); + changed = true; + } + + /* + * Make sure the key state goals are written. + */ + if (write_state) { + if (setgoal) { + if (goal == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_GOAL); + } else { + dst_key_setstate(key, DST_KEY_GOAL, goal); + } + changed = true; + } + if (setds) { + if (ds == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_DS); + dst_key_unsettime(key, DST_TIME_DS); + } else { + dst_key_setstate(key, DST_KEY_DS, ds); + dst_key_settime(key, DST_TIME_DS, dstime); + } + changed = true; + } + if (setdnskey) { + if (dnskey == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_DNSKEY); + dst_key_unsettime(key, DST_TIME_DNSKEY); + } else { + dst_key_setstate(key, DST_KEY_DNSKEY, dnskey); + dst_key_settime(key, DST_TIME_DNSKEY, + dnskeytime); + } + changed = true; + } + if (setkrrsig) { + if (krrsig == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_KRRSIG); + dst_key_unsettime(key, DST_TIME_KRRSIG); + } else { + dst_key_setstate(key, DST_KEY_KRRSIG, krrsig); + dst_key_settime(key, DST_TIME_KRRSIG, + krrsigtime); + } + changed = true; + } + if (setzrrsig) { + if (zrrsig == DST_KEY_STATE_NA) { + dst_key_unsetstate(key, DST_KEY_ZRRSIG); + dst_key_unsettime(key, DST_TIME_ZRRSIG); + } else { + dst_key_setstate(key, DST_KEY_ZRRSIG, zrrsig); + dst_key_settime(key, DST_TIME_ZRRSIG, + zrrsigtime); + } + changed = true; + } + } + + if (!changed && setttl) { + changed = true; + } + + /* + * Print out time values, if -p was used. + */ + if (printcreate) { + printtime(key, DST_TIME_CREATED, "Created", epoch, stdout); + } + + if (printpub) { + printtime(key, DST_TIME_PUBLISH, "Publish", epoch, stdout); + } + + if (printact) { + printtime(key, DST_TIME_ACTIVATE, "Activate", epoch, stdout); + } + + if (printrev) { + printtime(key, DST_TIME_REVOKE, "Revoke", epoch, stdout); + } + + if (printinact) { + printtime(key, DST_TIME_INACTIVE, "Inactive", epoch, stdout); + } + + if (printdel) { + printtime(key, DST_TIME_DELETE, "Delete", epoch, stdout); + } + + if (printsyncadd) { + printtime(key, DST_TIME_SYNCPUBLISH, "SYNC Publish", epoch, + stdout); + } + + if (printsyncdel) { + printtime(key, DST_TIME_SYNCDELETE, "SYNC Delete", epoch, + stdout); + } + + if (printdsadd) { + printtime(key, DST_TIME_DSPUBLISH, "DS Publish", epoch, stdout); + } + + if (printdsdel) { + printtime(key, DST_TIME_DSDELETE, "DS Delete", epoch, stdout); + } + + if (changed) { + writekey(key, directory, write_state); + if (predecessor != NULL && prevkey != NULL) { + writekey(prevkey, directory, write_state); + } + } + + if (prevkey != NULL) { + dst_key_free(&prevkey); + } + dst_key_free(&key); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + cleanup_logging(&log); + isc_mem_free(mctx, directory); + isc_mem_destroy(&mctx); + + return (0); +} diff --git a/bin/dnssec/dnssec-settime.rst b/bin/dnssec/dnssec-settime.rst new file mode 100644 index 0000000..9f6c428 --- /dev/null +++ b/bin/dnssec/dnssec-settime.rst @@ -0,0 +1,231 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-settime: + +dnssec-settime: set the key timing metadata for a DNSSEC key +------------------------------------------------------------ + +Synopsis +~~~~~~~~ + +:program:`dnssec-settime` [**-f**] [**-K** directory] [**-L** ttl] [**-P** date/offset] [**-P** ds date/offset] [**-P** sync date/offset] [**-A** date/offset] [**-R** date/offset] [**-I** date/offset] [**-D** date/offset] [**-D** ds date/offset] [**-D** sync date/offset] [**-S** key] [**-i** interval] [**-h**] [**-V**] [**-v** level] [**-E** engine] {keyfile} [**-s**] [**-g** state] [**-d** state date/offset] [**-k** state date/offset] [**-r** state date/offset] [**-z** state date/offset] + +Description +~~~~~~~~~~~ + +``dnssec-settime`` reads a DNSSEC private key file and sets the key +timing metadata as specified by the ``-P``, ``-A``, ``-R``, ``-I``, and +``-D`` options. The metadata can then be used by ``dnssec-signzone`` or +other signing software to determine when a key is to be published, +whether it should be used for signing a zone, etc. + +If none of these options is set on the command line, +``dnssec-settime`` simply prints the key timing metadata already stored +in the key. + +When key metadata fields are changed, both files of a key pair +(``Knnnn.+aaa+iiiii.key`` and ``Knnnn.+aaa+iiiii.private``) are +regenerated. + +Metadata fields are stored in the private file. A +human-readable description of the metadata is also placed in comments in +the key file. The private file's permissions are always set to be +inaccessible to anyone other than the owner (mode 0600). + +When working with state files, it is possible to update the timing metadata in +those files as well with ``-s``. With this option, it is also possible to update key +states with ``-d`` (DS), ``-k`` (DNSKEY), ``-r`` (RRSIG of KSK), or ``-z`` +(RRSIG of ZSK). Allowed states are HIDDEN, RUMOURED, OMNIPRESENT, and +UNRETENTIVE. + +The goal state of the key can also be set with ``-g``. This should be either +HIDDEN or OMNIPRESENT, representing whether the key should be removed from the +zone or published. + +It is NOT RECOMMENDED to manipulate state files manually, except for testing +purposes. + +Options +~~~~~~~ + +``-f`` + This option forces an update of an old-format key with no metadata fields. Without + this option, ``dnssec-settime`` fails when attempting to update a + legacy key. With this option, the key is recreated in the new + format, but with the original key data retained. The key's creation + date is set to the present time. If no other values are + specified, then the key's publication and activation dates are also + set to the present time. + +``-K directory`` + This option sets the directory in which the key files are to reside. + +``-L ttl`` + This option sets the default TTL to use for this key when it is converted into a + DNSKEY RR. This is the TTL used when the key is imported into a zone, + unless there was already a DNSKEY RRset in + place, in which case the existing TTL takes precedence. If this + value is not set and there is no existing DNSKEY RRset, the TTL + defaults to the SOA TTL. Setting the default TTL to ``0`` or ``none`` + removes it from the key. + +``-h`` + This option emits a usage message and exits. + +``-V`` + This option prints version information. + +``-v level`` + This option sets the debugging level. + +``-E engine`` + This option specifies the cryptographic hardware to use, when applicable. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). When BIND is + built with native PKCS#11 cryptography (``--enable-native-pkcs11``), it + defaults to the path of the PKCS#11 provider library specified via + ``--with-pkcs11``. + +Timing Options +~~~~~~~~~~~~~~ + +Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS. If the +argument begins with a ``+`` or ``-``, it is interpreted as an offset from +the present time. For convenience, if such an offset is followed by one +of the suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, then the offset is +computed in years (defined as 365 24-hour days, ignoring leap years), +months (defined as 30 24-hour days), weeks, days, hours, or minutes, +respectively. Without a suffix, the offset is computed in seconds. To +explicitly prevent a date from being set, use ``none`` or ``never``. + +``-P date/offset`` + This option sets the date on which a key is to be published to the zone. After + that date, the key is included in the zone but is not used + to sign it. + +``-P ds date/offset`` + This option Sets the date on which DS records that match this key have been + seen in the parent zone. + +``-P sync date/offset`` + This option sets the date on which CDS and CDNSKEY records that match this key + are to be published to the zone. + +``-A date/offset`` + This option sets the date on which the key is to be activated. After that date, + the key is included in the zone and used to sign it. + +``-R date/offset`` + This option sets the date on which the key is to be revoked. After that date, the + key is flagged as revoked. It is included in the zone and + is used to sign it. + +``-I date/offset`` + This option sets the date on which the key is to be retired. After that date, the + key is still included in the zone, but it is not used to + sign it. + +``-D date/offset`` + This option sets the date on which the key is to be deleted. After that date, the + key is no longer included in the zone. (However, it may remain in the key + repository.) + +``-D ds date/offset`` + This option sets the date on which the DS records that match this key have + been seen removed from the parent zone. + +``-D sync date/offset`` + This option sets the date on which the CDS and CDNSKEY records that match this + key are to be deleted. + +``-S predecessor key`` + This option selects a key for which the key being modified is an explicit + successor. The name, algorithm, size, and type of the predecessor key + must exactly match those of the key being modified. The activation + date of the successor key is set to the inactivation date of the + predecessor. The publication date is set to the activation date + minus the prepublication interval, which defaults to 30 days. + +``-i interval`` + This option sets the prepublication interval for a key. If set, then the + publication and activation dates must be separated by at least this + much time. If the activation date is specified but the publication + date is not, the publication date defaults to this much time + before the activation date; conversely, if the publication date is + specified but not the activation date, activation is set to + this much time after publication. + + If the key is being created as an explicit successor to another key, + then the default prepublication interval is 30 days; otherwise it is + zero. + + As with date offsets, if the argument is followed by one of the + suffixes ``y``, ``mo``, ``w``, ``d``, ``h``, or ``mi``, the interval is + measured in years, months, weeks, days, hours, or minutes, + respectively. Without a suffix, the interval is measured in seconds. + +Key State Options +~~~~~~~~~~~~~~~~~ + +To test dnssec-policy it may be necessary to construct keys with artificial +state information; these options are used by the testing framework for that +purpose, but should never be used in production. + +Known key states are HIDDEN, RUMOURED, OMNIPRESENT, and UNRETENTIVE. + +``-s`` + This option indicates that when setting key timing data, the state file should also be updated. + +``-g state`` + This option sets the goal state for this key. Must be HIDDEN or OMNIPRESENT. + +``-d state date/offset`` + This option sets the DS state for this key as of the specified date, offset from the current date. + +``-k state date/offset`` + This option sets the DNSKEY state for this key as of the specified date, offset from the current date. + +``-r state date/offset`` + This option sets the RRSIG (KSK) state for this key as of the specified date, offset from the current date. + +``-z state date/offset`` + This option sets the RRSIG (ZSK) state for this key as of the specified date, offset from the current date. + +Printing Options +~~~~~~~~~~~~~~~~ + +``dnssec-settime`` can also be used to print the timing metadata +associated with a key. + +``-u`` + This option indicates that times should be printed in Unix epoch format. + +``-p C/P/Pds/Psync/A/R/I/D/Dds/Dsync/all`` + This option prints a specific metadata value or set of metadata values. + The ``-p`` option may be followed by one or more of the following letters or + strings to indicate which value or values to print: ``C`` for the + creation date, ``P`` for the publication date, ``Pds` for the DS publication + date, ``Psync`` for the CDS and CDNSKEY publication date, ``A`` for the + activation date, ``R`` for the revocation date, ``I`` for the inactivation + date, ``D`` for the deletion date, ``Dds`` for the DS deletion date, + and ``Dsync`` for the CDS and CDNSKEY deletion date. To print all of the + metadata, use ``all``. + +See Also +~~~~~~~~ + +:manpage:`dnssec-keygen(8)`, :manpage:`dnssec-signzone(8)`, BIND 9 Administrator Reference Manual, +:rfc:`5011`. diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c new file mode 100644 index 0000000..2d2c158 --- /dev/null +++ b/bin/dnssec/dnssec-signzone.c @@ -0,0 +1,4197 @@ +/* + * Portions 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. + * + * Portions Copyright (C) Network Associates, Inc. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/*! \file */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-signzone"; + +typedef struct hashlist hashlist_t; + +static int nsec_datatype = dns_rdatatype_nsec; + +#define check_dns_dbiterator_current(result) \ + check_result((result == DNS_R_NEWORIGIN) ? ISC_R_SUCCESS : result, \ + "dns_dbiterator_current()") + +#define IS_NSEC3 (nsec_datatype == dns_rdatatype_nsec3) +#define OPTOUT(x) (((x)&DNS_NSEC3FLAG_OPTOUT) != 0) + +#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0) + +#define BUFSIZE 2048 +#define MAXDSKEYS 8 + +#define SIGNER_EVENTCLASS ISC_EVENTCLASS(0x4453) +#define SIGNER_EVENT_WRITE (SIGNER_EVENTCLASS + 0) +#define SIGNER_EVENT_WORK (SIGNER_EVENTCLASS + 1) + +#define SOA_SERIAL_KEEP 0 +#define SOA_SERIAL_INCREMENT 1 +#define SOA_SERIAL_UNIXTIME 2 +#define SOA_SERIAL_DATE 3 + +typedef struct signer_event sevent_t; +struct signer_event { + ISC_EVENT_COMMON(sevent_t); + dns_fixedname_t *fname; + dns_dbnode_t *node; +}; + +static dns_dnsseckeylist_t keylist; +static unsigned int keycount = 0; +static isc_rwlock_t keylist_lock; +static isc_stdtime_t starttime = 0, endtime = 0, dnskey_endtime = 0, now; +static int cycle = -1; +static int jitter = 0; +static bool tryverify = false; +static bool printstats = false; +static isc_mem_t *mctx = NULL; +static dns_ttl_t zone_soa_min_ttl; +static dns_ttl_t soa_ttl; +static FILE *outfp = NULL; +static char *tempfile = NULL; +static const dns_master_style_t *masterstyle; +static dns_masterformat_t inputformat = dns_masterformat_text; +static dns_masterformat_t outputformat = dns_masterformat_text; +static uint32_t rawversion = 1, serialnum = 0; +static bool snset = false; +static unsigned int nsigned = 0, nretained = 0, ndropped = 0; +static unsigned int nverified = 0, nverifyfailed = 0; +static const char *directory = NULL, *dsdir = NULL; +static isc_mutex_t namelock, statslock; +static isc_nm_t *netmgr = NULL; +static isc_taskmgr_t *taskmgr = NULL; +static dns_db_t *gdb; /* The database */ +static dns_dbversion_t *gversion; /* The database version */ +static dns_dbiterator_t *gdbiter; /* The database iterator */ +static dns_rdataclass_t gclass; /* The class */ +static dns_name_t *gorigin; /* The database origin */ +static int nsec3flags = 0; +static dns_iterations_t nsec3iter = 10U; +static unsigned char saltbuf[255]; +static unsigned char *gsalt = saltbuf; +static size_t salt_length = 0; +static isc_task_t *master = NULL; +static unsigned int ntasks = 0; +static atomic_bool shuttingdown; +static atomic_bool finished; +static bool nokeys = false; +static bool removefile = false; +static bool generateds = false; +static bool ignore_kskflag = false; +static bool keyset_kskonly = false; +static dns_master_style_t *dsstyle = NULL; +static unsigned int serialformat = SOA_SERIAL_KEEP; +static unsigned int hash_length = 0; +static bool unknownalg = false; +static bool disable_zone_check = false; +static bool update_chain = false; +static bool set_keyttl = false; +static dns_ttl_t keyttl; +static bool smartsign = false; +static bool remove_orphansigs = false; +static bool remove_inactkeysigs = false; +static bool output_dnssec_only = false; +static bool output_stdout = false; +static bool set_maxttl = false; +static dns_ttl_t maxttl = 0; +static bool no_max_check = false; + +#define INCSTAT(counter) \ + if (printstats) { \ + LOCK(&statslock); \ + counter++; \ + UNLOCK(&statslock); \ + } + +static void +sign(isc_task_t *task, isc_event_t *event); + +/*% + * Store a copy of 'name' in 'fzonecut' and return a pointer to that copy. + */ +static dns_name_t * +savezonecut(dns_fixedname_t *fzonecut, dns_name_t *name) { + dns_name_t *result; + + result = dns_fixedname_initname(fzonecut); + dns_name_copynf(name, result); + + return (result); +} + +static void +dumpnode(dns_name_t *name, dns_dbnode_t *node) { + dns_rdataset_t rds; + dns_rdatasetiter_t *iter = NULL; + isc_buffer_t *buffer = NULL; + isc_region_t r; + isc_result_t result; + unsigned bufsize = 4096; + + if (outputformat != dns_masterformat_text) { + return; + } + + if (!output_dnssec_only) { + result = dns_master_dumpnodetostream(mctx, gdb, gversion, node, + name, masterstyle, outfp); + check_result(result, "dns_master_dumpnodetostream"); + return; + } + + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, &iter); + check_result(result, "dns_db_allrdatasets"); + + dns_rdataset_init(&rds); + + isc_buffer_allocate(mctx, &buffer, bufsize); + + for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iter)) + { + dns_rdatasetiter_current(iter, &rds); + + if (rds.type != dns_rdatatype_rrsig && + rds.type != dns_rdatatype_nsec && + rds.type != dns_rdatatype_nsec3 && + rds.type != dns_rdatatype_nsec3param && + (!smartsign || rds.type != dns_rdatatype_dnskey)) + { + dns_rdataset_disassociate(&rds); + continue; + } + + for (;;) { + result = dns_master_rdatasettotext( + name, &rds, masterstyle, NULL, buffer); + if (result != ISC_R_NOSPACE) { + break; + } + + bufsize <<= 1; + isc_buffer_free(&buffer); + isc_buffer_allocate(mctx, &buffer, bufsize); + } + check_result(result, "dns_master_rdatasettotext"); + + isc_buffer_usedregion(buffer, &r); + result = isc_stdio_write(r.base, 1, r.length, outfp, NULL); + check_result(result, "isc_stdio_write"); + isc_buffer_clear(buffer); + + dns_rdataset_disassociate(&rds); + } + + isc_buffer_free(&buffer); + dns_rdatasetiter_destroy(&iter); +} + +/*% + * Sign the given RRset with given key, and add the signature record to the + * given tuple. + */ +static void +signwithkey(dns_name_t *name, dns_rdataset_t *rdataset, dst_key_t *key, + dns_ttl_t ttl, dns_diff_t *add, const char *logmsg) { + isc_result_t result; + isc_stdtime_t jendtime, expiry; + char keystr[DST_KEY_FORMATSIZE]; + dns_rdata_t trdata = DNS_RDATA_INIT; + unsigned char array[BUFSIZE]; + isc_buffer_t b; + dns_difftuple_t *tuple; + + dst_key_format(key, keystr, sizeof(keystr)); + vbprintf(1, "\t%s %s\n", logmsg, keystr); + + if (rdataset->type == dns_rdatatype_dnskey) { + expiry = dnskey_endtime; + } else { + expiry = endtime; + } + + jendtime = (jitter != 0) ? expiry - isc_random_uniform(jitter) : expiry; + isc_buffer_init(&b, array, sizeof(array)); + result = dns_dnssec_sign(name, rdataset, key, &starttime, &jendtime, + mctx, &b, &trdata); + if (result != ISC_R_SUCCESS) { + fatal("dnskey '%s' failed to sign data: %s", keystr, + isc_result_totext(result)); + } + INCSTAT(nsigned); + + if (tryverify) { + result = dns_dnssec_verify(name, rdataset, key, true, 0, mctx, + &trdata, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { + vbprintf(3, "\tsignature verified\n"); + INCSTAT(nverified); + } else { + vbprintf(3, "\tsignature failed to verify\n"); + INCSTAT(nverifyfailed); + } + } + + tuple = NULL; + result = dns_difftuple_create(mctx, DNS_DIFFOP_ADDRESIGN, name, ttl, + &trdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); +} + +static bool +issigningkey(dns_dnsseckey_t *key) { + return (key->force_sign || key->hint_sign); +} + +static bool +ispublishedkey(dns_dnsseckey_t *key) { + return ((key->force_publish || key->hint_publish) && !key->hint_remove); +} + +static bool +iszonekey(dns_dnsseckey_t *key) { + return (dns_name_equal(dst_key_name(key->key), gorigin) && + dst_key_iszonekey(key->key)); +} + +static bool +isksk(dns_dnsseckey_t *key) { + return (key->ksk); +} + +static bool +iszsk(dns_dnsseckey_t *key) { + return (ignore_kskflag || !key->ksk); +} + +/*% + * Find the key that generated an RRSIG, if it is in the key list. If + * so, return a pointer to it, otherwise return NULL. + * + * No locking is performed here, this must be done by the caller. + */ +static dns_dnsseckey_t * +keythatsigned_unlocked(dns_rdata_rrsig_t *rrsig) { + dns_dnsseckey_t *key; + + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (rrsig->keyid == dst_key_id(key->key) && + rrsig->algorithm == dst_key_alg(key->key) && + dns_name_equal(&rrsig->signer, dst_key_name(key->key))) + { + return (key); + } + } + return (NULL); +} + +/*% + * Finds the key that generated a RRSIG, if possible. First look at the keys + * that we've loaded already, and then see if there's a key on disk. + */ +static dns_dnsseckey_t * +keythatsigned(dns_rdata_rrsig_t *rrsig) { + isc_result_t result; + dst_key_t *pubkey = NULL, *privkey = NULL; + dns_dnsseckey_t *key = NULL; + + RWLOCK(&keylist_lock, isc_rwlocktype_read); + key = keythatsigned_unlocked(rrsig); + RWUNLOCK(&keylist_lock, isc_rwlocktype_read); + if (key != NULL) { + return (key); + } + + /* + * We did not find the key in our list. Get a write lock now, since + * we may be modifying the bits. We could do the tryupgrade() dance, + * but instead just get a write lock and check once again to see if + * it is on our list. It's possible someone else may have added it + * after all. + */ + isc_rwlock_lock(&keylist_lock, isc_rwlocktype_write); + key = keythatsigned_unlocked(rrsig); + if (key != NULL) { + isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write); + return (key); + } + + result = dst_key_fromfile(&rrsig->signer, rrsig->keyid, + rrsig->algorithm, DST_TYPE_PUBLIC, directory, + mctx, &pubkey); + if (result != ISC_R_SUCCESS) { + isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write); + return (NULL); + } + + result = dst_key_fromfile( + &rrsig->signer, rrsig->keyid, rrsig->algorithm, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, directory, mctx, &privkey); + if (result == ISC_R_SUCCESS) { + dst_key_free(&pubkey); + result = dns_dnsseckey_create(mctx, &privkey, &key); + } else { + result = dns_dnsseckey_create(mctx, &pubkey, &key); + } + + if (result == ISC_R_SUCCESS) { + key->force_publish = false; + key->force_sign = false; + key->index = keycount++; + ISC_LIST_APPEND(keylist, key, link); + } + + isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write); + return (key); +} + +/*% + * Check to see if we expect to find a key at this name. If we see a RRSIG + * and can't find the signing key that we expect to find, we drop the rrsig. + * I'm not sure if this is completely correct, but it seems to work. + */ +static bool +expecttofindkey(dns_name_t *name) { + unsigned int options = DNS_DBFIND_NOWILD; + dns_fixedname_t fname; + isc_result_t result; + char namestr[DNS_NAME_FORMATSIZE]; + + dns_fixedname_init(&fname); + result = dns_db_find(gdb, name, gversion, dns_rdatatype_dnskey, options, + 0, NULL, dns_fixedname_name(&fname), NULL, NULL); + switch (result) { + case ISC_R_SUCCESS: + case DNS_R_NXDOMAIN: + case DNS_R_NXRRSET: + return (true); + case DNS_R_DELEGATION: + case DNS_R_CNAME: + case DNS_R_DNAME: + return (false); + } + dns_name_format(name, namestr, sizeof(namestr)); + fatal("failure looking for '%s DNSKEY' in database: %s", namestr, + isc_result_totext(result)); + UNREACHABLE(); + return (false); /* removes a warning */ +} + +static bool +setverifies(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + dns_rdata_t *rrsig) { + isc_result_t result; + result = dns_dnssec_verify(name, set, key, false, 0, mctx, rrsig, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { + INCSTAT(nverified); + return (true); + } else { + INCSTAT(nverifyfailed); + return (false); + } +} + +/*% + * Signs a set. Goes through contortions to decide if each RRSIG should + * be dropped or retained, and then determines if any new SIGs need to + * be generated. + */ +static void +signset(dns_diff_t *del, dns_diff_t *add, dns_dbnode_t *node, dns_name_t *name, + dns_rdataset_t *set) { + dns_rdataset_t sigset; + dns_rdata_t sigrdata = DNS_RDATA_INIT; + dns_rdata_rrsig_t rrsig; + dns_dnsseckey_t *key; + isc_result_t result; + bool nosigs = false; + bool *wassignedby, *nowsignedby; + int arraysize; + dns_difftuple_t *tuple; + dns_ttl_t ttl; + int i; + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + char sigstr[SIG_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + dns_rdatatype_format(set->type, typestr, sizeof(typestr)); + + ttl = ISC_MIN(set->ttl, endtime - starttime); + + dns_rdataset_init(&sigset); + result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_rrsig, + set->type, 0, &sigset, NULL); + if (result == ISC_R_NOTFOUND) { + vbprintf(2, "no existing signatures for %s/%s\n", namestr, + typestr); + result = ISC_R_SUCCESS; + nosigs = true; + } + if (result != ISC_R_SUCCESS) { + fatal("failed while looking for '%s RRSIG %s': %s", namestr, + typestr, isc_result_totext(result)); + } + + vbprintf(1, "%s/%s:\n", namestr, typestr); + + arraysize = keycount; + if (!nosigs) { + arraysize += dns_rdataset_count(&sigset); + } + wassignedby = isc_mem_get(mctx, arraysize * sizeof(bool)); + nowsignedby = isc_mem_get(mctx, arraysize * sizeof(bool)); + + for (i = 0; i < arraysize; i++) { + wassignedby[i] = nowsignedby[i] = false; + } + + if (nosigs) { + result = ISC_R_NOMORE; + } else { + result = dns_rdataset_first(&sigset); + } + + while (result == ISC_R_SUCCESS) { + bool expired, future; + bool keep = false, resign = false; + + dns_rdataset_current(&sigset, &sigrdata); + + result = dns_rdata_tostruct(&sigrdata, &rrsig, NULL); + check_result(result, "dns_rdata_tostruct"); + + future = isc_serial_lt(now, rrsig.timesigned); + + key = keythatsigned(&rrsig); + sig_format(&rrsig, sigstr, sizeof(sigstr)); + expired = isc_serial_gt(now + cycle, rrsig.timeexpire); + + if (isc_serial_gt(rrsig.timesigned, rrsig.timeexpire)) { + /* rrsig is dropped and not replaced */ + vbprintf(2, + "\trrsig by %s dropped - " + "invalid validity period\n", + sigstr); + } else if (key == NULL && !future && + expecttofindkey(&rrsig.signer)) + { + /* rrsig is dropped and not replaced */ + vbprintf(2, + "\trrsig by %s dropped - " + "private dnskey not found\n", + sigstr); + } else if (key == NULL || future) { + keep = (!expired && !remove_orphansigs); + vbprintf(2, "\trrsig by %s %s - dnskey not found\n", + keep ? "retained" : "dropped", sigstr); + } else if (!dns_dnssec_keyactive(key->key, now) && + remove_inactkeysigs) + { + keep = false; + vbprintf(2, "\trrsig by %s dropped - key inactive\n", + sigstr); + } else if (issigningkey(key)) { + wassignedby[key->index] = true; + + if (!expired && rrsig.originalttl == set->ttl && + setverifies(name, set, key->key, &sigrdata)) + { + vbprintf(2, "\trrsig by %s retained\n", sigstr); + keep = true; + } else { + vbprintf(2, "\trrsig by %s dropped - %s\n", + sigstr, + expired ? "expired" + : rrsig.originalttl != set->ttl + ? "ttl change" + : "failed to " + "verify"); + resign = true; + } + } else if (!ispublishedkey(key) && remove_orphansigs) { + vbprintf(2, "\trrsig by %s dropped - dnskey removed\n", + sigstr); + } else if (iszonekey(key)) { + wassignedby[key->index] = true; + + if (!expired && rrsig.originalttl == set->ttl && + setverifies(name, set, key->key, &sigrdata)) + { + vbprintf(2, "\trrsig by %s retained\n", sigstr); + keep = true; + } else { + vbprintf(2, "\trrsig by %s dropped - %s\n", + sigstr, + expired ? "expired" + : rrsig.originalttl != set->ttl + ? "ttl change" + : "failed to " + "verify"); + } + } else if (!expired) { + vbprintf(2, "\trrsig by %s retained\n", sigstr); + keep = true; + } else { + vbprintf(2, "\trrsig by %s expired\n", sigstr); + } + + if (keep) { + if (key != NULL) { + nowsignedby[key->index] = true; + } + INCSTAT(nretained); + if (sigset.ttl != ttl) { + vbprintf(2, "\tfixing ttl %s\n", sigstr); + tuple = NULL; + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + sigset.ttl, &sigrdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, name, ttl, + &sigrdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); + } + } else { + tuple = NULL; + vbprintf(2, "\tremoving signature by %s\n", sigstr); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, sigset.ttl, + &sigrdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + INCSTAT(ndropped); + } + + if (resign) { + INSIST(!keep); + + signwithkey(name, set, key->key, ttl, add, + "resigning with dnskey"); + nowsignedby[key->index] = true; + } + + dns_rdata_reset(&sigrdata); + dns_rdata_freestruct(&rrsig); + result = dns_rdataset_next(&sigset); + } + if (result == ISC_R_NOMORE) { + result = ISC_R_SUCCESS; + } + + check_result(result, "dns_rdataset_first/next"); + if (dns_rdataset_isassociated(&sigset)) { + dns_rdataset_disassociate(&sigset); + } + + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (nowsignedby[key->index]) { + continue; + } + + if (!issigningkey(key)) { + continue; + } + + if ((set->type == dns_rdatatype_cds || + set->type == dns_rdatatype_cdnskey || + set->type == dns_rdatatype_dnskey) && + dns_name_equal(name, gorigin)) + { + bool have_ksk; + dns_dnsseckey_t *curr; + + have_ksk = isksk(key); + for (curr = ISC_LIST_HEAD(keylist); curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + if (dst_key_alg(key->key) != + dst_key_alg(curr->key)) + { + continue; + } + if (REVOKE(curr->key)) { + continue; + } + if (isksk(curr)) { + have_ksk = true; + } + } + if (isksk(key) || !have_ksk || + (iszsk(key) && !keyset_kskonly)) + { + signwithkey(name, set, key->key, ttl, add, + "signing with dnskey"); + } + } else if (iszsk(key)) { + /* + * Sign with the ZSK unless there is a predecessor + * key that already signs this RRset. + */ + bool have_pre_sig = false; + dns_dnsseckey_t *curr; + uint32_t pre; + isc_result_t ret = dst_key_getnum( + key->key, DST_NUM_PREDECESSOR, &pre); + if (ret == ISC_R_SUCCESS) { + /* + * This key has a predecessor, look for the + * corresponding key in the keylist. The + * key we are looking for must be: + * - From the same cryptographic algorithm. + * - Have the ZSK type (iszsk). + * - Have key ID equal to the predecessor id. + * - Have a successor that matches 'key' id. + */ + for (curr = ISC_LIST_HEAD(keylist); + curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + uint32_t suc; + + if (dst_key_alg(key->key) != + dst_key_alg(curr->key) || + !iszsk(curr) || + dst_key_id(curr->key) != pre) + { + continue; + } + ret = dst_key_getnum(curr->key, + DST_NUM_SUCCESSOR, + &suc); + if (ret != ISC_R_SUCCESS || + dst_key_id(key->key) != suc) + { + continue; + } + + /* + * curr is the predecessor we were + * looking for. Check if this key + * signs this RRset. + */ + if (nowsignedby[curr->index]) { + have_pre_sig = true; + } + } + } + + /* + * If we have a signature of a predecessor key, + * skip signing with this key. + */ + if (!have_pre_sig) { + signwithkey(name, set, key->key, ttl, add, + "signing with dnskey"); + } + } + } + + isc_mem_put(mctx, wassignedby, arraysize * sizeof(bool)); + isc_mem_put(mctx, nowsignedby, arraysize * sizeof(bool)); +} + +struct hashlist { + unsigned char *hashbuf; + size_t entries; + size_t size; + size_t length; +}; + +static void +hashlist_init(hashlist_t *l, unsigned int nodes, unsigned int length) { + l->entries = 0; + l->length = length + 1; + + if (nodes != 0) { + l->size = nodes; + l->hashbuf = malloc(l->size * l->length); + if (l->hashbuf == NULL) { + l->size = 0; + } + } else { + l->size = 0; + l->hashbuf = NULL; + } +} + +static void +hashlist_free(hashlist_t *l) { + if (l->hashbuf) { + free(l->hashbuf); + l->hashbuf = NULL; + l->entries = 0; + l->length = 0; + l->size = 0; + } +} + +static void +hashlist_add(hashlist_t *l, const unsigned char *hash, size_t len) { + REQUIRE(len <= l->length); + + if (l->entries == l->size) { + l->size = l->size * 2 + 100; + l->hashbuf = realloc(l->hashbuf, l->size * l->length); + if (l->hashbuf == NULL) { + fatal("unable to grow hashlist: out of memory"); + } + } + memset(l->hashbuf + l->entries * l->length, 0, l->length); + memmove(l->hashbuf + l->entries * l->length, hash, len); + l->entries++; +} + +static void +hashlist_add_dns_name(hashlist_t *l, + /*const*/ dns_name_t *name, unsigned int hashalg, + unsigned int iterations, const unsigned char *salt, + size_t salt_len, bool speculative) { + char nametext[DNS_NAME_FORMATSIZE]; + unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1]; + unsigned int len; + size_t i; + + len = isc_iterated_hash(hash, hashalg, iterations, salt, (int)salt_len, + name->ndata, name->length); + if (verbose) { + dns_name_format(name, nametext, sizeof nametext); + for (i = 0; i < len; i++) { + fprintf(stderr, "%02x", hash[i]); + } + fprintf(stderr, " %s\n", nametext); + } + hash[len++] = speculative ? 1 : 0; + hashlist_add(l, hash, len); +} + +static int +hashlist_comp(const void *a, const void *b) { + return (memcmp(a, b, hash_length + 1)); +} + +static void +hashlist_sort(hashlist_t *l) { + INSIST(l->hashbuf != NULL || l->length == 0); + if (l->length > 0) { + qsort(l->hashbuf, l->entries, l->length, hashlist_comp); + } +} + +static bool +hashlist_hasdup(hashlist_t *l) { + unsigned char *current; + unsigned char *next = l->hashbuf; + size_t entries = l->entries; + + /* + * Skip initial speculative wild card hashes. + */ + while (entries > 0U && next[l->length - 1] != 0U) { + next += l->length; + entries--; + } + + current = next; + while (entries-- > 1U) { + next += l->length; + if (next[l->length - 1] != 0) { + continue; + } + if (isc_safe_memequal(current, next, l->length - 1)) { + return (true); + } + current = next; + } + return (false); +} + +static const unsigned char * +hashlist_findnext(const hashlist_t *l, + const unsigned char hash[NSEC3_MAX_HASH_LENGTH]) { + size_t entries = l->entries; + const unsigned char *next = bsearch(hash, l->hashbuf, l->entries, + l->length, hashlist_comp); + INSIST(next != NULL); + + do { + if (next < l->hashbuf + (l->entries - 1) * l->length) { + next += l->length; + } else { + next = l->hashbuf; + } + if (next[l->length - 1] == 0) { + break; + } + } while (entries-- > 1U); + INSIST(entries != 0U); + return (next); +} + +static bool +hashlist_exists(const hashlist_t *l, + const unsigned char hash[NSEC3_MAX_HASH_LENGTH]) { + if (bsearch(hash, l->hashbuf, l->entries, l->length, hashlist_comp)) { + return (true); + } else { + return (false); + } +} + +static void +addnowildcardhash(hashlist_t *l, + /*const*/ dns_name_t *name, unsigned int hashalg, + unsigned int iterations, const unsigned char *salt, + size_t salt_len) { + dns_fixedname_t fixed; + dns_name_t *wild; + dns_dbnode_t *node = NULL; + isc_result_t result; + char namestr[DNS_NAME_FORMATSIZE]; + + wild = dns_fixedname_initname(&fixed); + + result = dns_name_concatenate(dns_wildcardname, name, wild, NULL); + if (result == ISC_R_NOSPACE) { + return; + } + check_result(result, "addnowildcardhash: dns_name_concatenate()"); + + result = dns_db_findnode(gdb, wild, false, &node); + if (result == ISC_R_SUCCESS) { + dns_db_detachnode(gdb, &node); + return; + } + + if (verbose) { + dns_name_format(wild, namestr, sizeof(namestr)); + fprintf(stderr, "adding no-wildcardhash for %s\n", namestr); + } + + hashlist_add_dns_name(l, wild, hashalg, iterations, salt, salt_len, + true); +} + +static void +opendb(const char *prefix, dns_name_t *name, dns_rdataclass_t rdclass, + dns_db_t **dbp) { + char filename[PATH_MAX]; + isc_buffer_t b; + isc_result_t result; + + isc_buffer_init(&b, filename, sizeof(filename)); + if (dsdir != NULL) { + /* allow room for a trailing slash */ + if (strlen(dsdir) >= isc_buffer_availablelength(&b)) { + fatal("path '%s' is too long", dsdir); + } + isc_buffer_putstr(&b, dsdir); + if (dsdir[strlen(dsdir) - 1] != '/') { + isc_buffer_putstr(&b, "/"); + } + } + if (strlen(prefix) > isc_buffer_availablelength(&b)) { + fatal("path '%s' is too long", dsdir); + } + isc_buffer_putstr(&b, prefix); + result = dns_name_tofilenametext(name, false, &b); + check_result(result, "dns_name_tofilenametext()"); + if (isc_buffer_availablelength(&b) == 0) { + char namestr[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namestr, sizeof(namestr)); + fatal("name '%s' is too long", namestr); + } + isc_buffer_putuint8(&b, 0); + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + rdclass, 0, NULL, dbp); + check_result(result, "dns_db_create()"); + + result = dns_db_load(*dbp, filename, inputformat, DNS_MASTER_HINT); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + dns_db_detach(dbp); + } +} + +/*% + * Load the DS set for a child zone, if a dsset-* file can be found. + * If not, try to find a keyset-* file from an earlier version of + * dnssec-signzone, and build DS records from that. + */ +static isc_result_t +loadds(dns_name_t *name, uint32_t ttl, dns_rdataset_t *dsset) { + dns_db_t *db = NULL; + dns_dbversion_t *ver = NULL; + dns_dbnode_t *node = NULL; + isc_result_t result; + dns_rdataset_t keyset; + dns_rdata_t key, ds; + unsigned char dsbuf[DNS_DS_BUFFERSIZE]; + dns_diff_t diff; + dns_difftuple_t *tuple = NULL; + + opendb("dsset-", name, gclass, &db); + if (db != NULL) { + result = dns_db_findnode(db, name, false, &node); + if (result == ISC_R_SUCCESS) { + dns_rdataset_init(dsset); + result = dns_db_findrdataset(db, node, NULL, + dns_rdatatype_ds, 0, 0, + dsset, NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_SUCCESS) { + vbprintf(2, "found DS records\n"); + dsset->ttl = ttl; + dns_db_detach(&db); + return (result); + } + } + dns_db_detach(&db); + } + + /* No DS records found; try again, looking for DNSKEY records */ + opendb("keyset-", name, gclass, &db); + if (db == NULL) { + return (ISC_R_NOTFOUND); + } + + result = dns_db_findnode(db, name, false, &node); + if (result != ISC_R_SUCCESS) { + dns_db_detach(&db); + return (result); + } + + dns_rdataset_init(&keyset); + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0, + &keyset, NULL); + if (result != ISC_R_SUCCESS) { + dns_db_detachnode(db, &node); + dns_db_detach(&db); + return (result); + } + vbprintf(2, "found DNSKEY records\n"); + + result = dns_db_newversion(db, &ver); + check_result(result, "dns_db_newversion"); + dns_diff_init(mctx, &diff); + + for (result = dns_rdataset_first(&keyset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&keyset)) + { + dns_rdata_init(&key); + dns_rdata_init(&ds); + dns_rdataset_current(&keyset, &key); + result = dns_ds_buildrdata(name, &key, DNS_DSDIGEST_SHA256, + dsbuf, &ds); + check_result(result, "dns_ds_buildrdata"); + + result = dns_difftuple_create(mctx, DNS_DIFFOP_ADDRESIGN, name, + ttl, &ds, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(&diff, &tuple); + } + + result = dns_diff_apply(&diff, db, ver); + check_result(result, "dns_diff_apply"); + dns_diff_clear(&diff); + + dns_db_closeversion(db, &ver, true); + + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ds, 0, 0, + dsset, NULL); + check_result(result, "dns_db_findrdataset"); + + dns_rdataset_disassociate(&keyset); + dns_db_detachnode(db, &node); + dns_db_detach(&db); + return (result); +} + +static bool +secure(dns_name_t *name, dns_dbnode_t *node) { + dns_rdataset_t dsset; + isc_result_t result; + + if (dns_name_equal(name, gorigin)) { + return (false); + } + + dns_rdataset_init(&dsset); + result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_ds, 0, + 0, &dsset, NULL); + if (dns_rdataset_isassociated(&dsset)) { + dns_rdataset_disassociate(&dsset); + } + + return (result == ISC_R_SUCCESS); +} + +static bool +is_delegation(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, + dns_name_t *name, dns_dbnode_t *node, uint32_t *ttlp) { + dns_rdataset_t nsset; + isc_result_t result; + + if (dns_name_equal(name, origin)) { + return (false); + } + + dns_rdataset_init(&nsset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_ns, 0, 0, + &nsset, NULL); + if (dns_rdataset_isassociated(&nsset)) { + if (ttlp != NULL) { + *ttlp = nsset.ttl; + } + dns_rdataset_disassociate(&nsset); + } + + return ((result == ISC_R_SUCCESS)); +} + +/*% + * Return true if version 'ver' of database 'db' contains a DNAME RRset at + * 'node'; return false otherwise. + */ +static bool +has_dname(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node) { + dns_rdataset_t dnameset; + isc_result_t result; + + dns_rdataset_init(&dnameset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dname, 0, 0, + &dnameset, NULL); + if (dns_rdataset_isassociated(&dnameset)) { + dns_rdataset_disassociate(&dnameset); + } + + return ((result == ISC_R_SUCCESS)); +} + +/*% + * Signs all records at a name. + */ +static void +signname(dns_dbnode_t *node, dns_name_t *name) { + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdsiter; + bool isdelegation = false; + dns_diff_t del, add; + char namestr[DNS_NAME_FORMATSIZE]; + + dns_rdataset_init(&rdataset); + dns_name_format(name, namestr, sizeof(namestr)); + + /* + * Determine if this is a delegation point. + */ + if (is_delegation(gdb, gversion, gorigin, name, node, NULL)) { + isdelegation = true; + } + + /* + * Now iterate through the rdatasets. + */ + dns_diff_init(mctx, &del); + dns_diff_init(mctx, &add); + rdsiter = NULL; + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + + /* If this is a RRSIG set, skip it. */ + if (rdataset.type == dns_rdatatype_rrsig) { + goto skip; + } + + /* + * If this name is a delegation point, skip all records + * except NSEC and DS sets. Otherwise check that there + * isn't a DS record. + */ + if (isdelegation) { + if (rdataset.type != nsec_datatype && + rdataset.type != dns_rdatatype_ds) + { + goto skip; + } + } else if (rdataset.type == dns_rdatatype_ds) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof(namebuf)); + fatal("'%s': found DS RRset without NS RRset\n", + namebuf); + } + + signset(&del, &add, node, name, &rdataset); + + skip: + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + if (result != ISC_R_NOMORE) { + fatal("rdataset iteration for name '%s' failed: %s", namestr, + isc_result_totext(result)); + } + + dns_rdatasetiter_destroy(&rdsiter); + + result = dns_diff_applysilently(&del, gdb, gversion); + if (result != ISC_R_SUCCESS) { + fatal("failed to delete SIGs at node '%s': %s", namestr, + isc_result_totext(result)); + } + + result = dns_diff_applysilently(&add, gdb, gversion); + if (result != ISC_R_SUCCESS) { + fatal("failed to add SIGs at node '%s': %s", namestr, + isc_result_totext(result)); + } + + dns_diff_clear(&del); + dns_diff_clear(&add); +} + +/* + * See if the node contains any non RRSIG/NSEC records and report to + * caller. Clean out extraneous RRSIG records for node. + */ +static bool +active_node(dns_dbnode_t *node) { + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdatasetiter_t *rdsiter2 = NULL; + bool active = false; + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdatatype_t type; + dns_rdatatype_t covers; + bool found; + + dns_rdataset_init(&rdataset); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + if (rdataset.type != dns_rdatatype_nsec && + rdataset.type != dns_rdatatype_nsec3 && + rdataset.type != dns_rdatatype_rrsig) + { + active = true; + } + dns_rdataset_disassociate(&rdataset); + if (!active) { + result = dns_rdatasetiter_next(rdsiter); + } else { + result = ISC_R_NOMORE; + } + } + if (result != ISC_R_NOMORE) { + fatal("rdataset iteration failed: %s", + isc_result_totext(result)); + } + + if (!active && nsec_datatype == dns_rdatatype_nsec) { + /*% + * The node is empty of everything but NSEC / RRSIG records. + */ + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + result = dns_db_deleterdataset(gdb, node, gversion, + rdataset.type, + rdataset.covers); + check_result(result, "dns_db_deleterdataset()"); + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOMORE) { + fatal("rdataset iteration failed: %s", + isc_result_totext(result)); + } + } else { + /* + * Delete RRSIGs for types that no longer exist. + */ + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, + &rdsiter2); + check_result(result, "dns_db_allrdatasets()"); + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + /* + * Delete the NSEC chain if we are signing with + * NSEC3. + */ + if (nsec_datatype == dns_rdatatype_nsec3 && + (type == dns_rdatatype_nsec || + covers == dns_rdatatype_nsec)) + { + result = dns_db_deleterdataset( + gdb, node, gversion, type, covers); + check_result(result, "dns_db_deleterdataset(" + "nsec/rrsig)"); + continue; + } + if (type != dns_rdatatype_rrsig) { + continue; + } + found = false; + for (result = dns_rdatasetiter_first(rdsiter2); + !found && result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter2)) + { + dns_rdatasetiter_current(rdsiter2, &rdataset); + if (rdataset.type == covers) { + found = true; + } + dns_rdataset_disassociate(&rdataset); + } + if (!found) { + if (result != ISC_R_NOMORE) { + fatal("rdataset iteration failed: %s", + isc_result_totext(result)); + } + result = dns_db_deleterdataset( + gdb, node, gversion, type, covers); + check_result(result, "dns_db_deleterdataset(" + "rrsig)"); + } else if (result != ISC_R_NOMORE && + result != ISC_R_SUCCESS) + { + fatal("rdataset iteration failed: %s", + isc_result_totext(result)); + } + } + if (result != ISC_R_NOMORE) { + fatal("rdataset iteration failed: %s", + isc_result_totext(result)); + } + dns_rdatasetiter_destroy(&rdsiter2); + } + dns_rdatasetiter_destroy(&rdsiter); + + return (active); +} + +/*% + * Extracts the minimum TTL from the SOA record, and the SOA record's TTL. + */ +static void +get_soa_ttls(void) { + dns_rdataset_t soaset; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + name = dns_fixedname_initname(&fname); + dns_rdataset_init(&soaset); + result = dns_db_find(gdb, gorigin, gversion, dns_rdatatype_soa, 0, 0, + NULL, name, &soaset, NULL); + if (result != ISC_R_SUCCESS) { + fatal("failed to find an SOA at the zone apex: %s", + isc_result_totext(result)); + } + + result = dns_rdataset_first(&soaset); + check_result(result, "dns_rdataset_first"); + dns_rdataset_current(&soaset, &rdata); + soa_ttl = soaset.ttl; + zone_soa_min_ttl = ISC_MIN(dns_soa_getminimum(&rdata), soa_ttl); + if (set_maxttl) { + zone_soa_min_ttl = ISC_MIN(zone_soa_min_ttl, maxttl); + soa_ttl = ISC_MIN(soa_ttl, maxttl); + } + dns_rdataset_disassociate(&soaset); +} + +/*% + * Increment (or set if nonzero) the SOA serial + */ +static isc_result_t +setsoaserial(uint32_t serial, dns_updatemethod_t method) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + uint32_t old_serial, new_serial = 0; + dns_updatemethod_t used = dns_updatemethod_none; + + result = dns_db_getoriginnode(gdb, &node); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdataset_init(&rdataset); + + result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_soa, 0, + 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rdataset_first(&rdataset); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + dns_rdataset_current(&rdataset, &rdata); + + old_serial = dns_soa_getserial(&rdata); + + if (method == dns_updatemethod_date || + method == dns_updatemethod_unixtime) + { + new_serial = dns_update_soaserial(old_serial, method, &used); + } else if (serial != 0 || method == dns_updatemethod_none) { + /* Set SOA serial to the value provided. */ + new_serial = serial; + used = method; + } else { + new_serial = dns_update_soaserial(old_serial, method, &used); + } + + if (method != used) { + fprintf(stderr, + "%s: warning: Serial number would not advance, " + "using increment method instead\n", + program); + } + + /* If the new serial is not likely to cause a zone transfer + * (a/ixfr) from servers having the old serial, warn the user. + * + * RFC1982 section 7 defines the maximum increment to be + * (2^(32-1))-1. Using u_int32_t arithmetic, we can do a single + * comparison. (5 - 6 == (2^32)-1, not negative-one) + */ + if (new_serial == old_serial || (new_serial - old_serial) > 0x7fffffffU) + { + fprintf(stderr, + "%s: warning: Serial number not advanced, " + "zone may not transfer\n", + program); + } + + dns_soa_setserial(new_serial, &rdata); + + result = dns_db_deleterdataset(gdb, node, gversion, dns_rdatatype_soa, + 0); + check_result(result, "dns_db_deleterdataset"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_db_addrdataset(gdb, node, gversion, 0, &rdataset, 0, NULL); + check_result(result, "dns_db_addrdataset"); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + +cleanup: + dns_rdataset_disassociate(&rdataset); + if (node != NULL) { + dns_db_detachnode(gdb, &node); + } + dns_rdata_reset(&rdata); + + return (result); +} + +/*% + * Delete any RRSIG records at a node. + */ +static void +cleannode(dns_db_t *db, dns_dbversion_t *dbversion, dns_dbnode_t *node) { + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t set; + isc_result_t result, dresult; + + if (outputformat != dns_masterformat_text || !disable_zone_check) { + return; + } + + dns_rdataset_init(&set); + result = dns_db_allrdatasets(db, node, dbversion, 0, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets"); + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + bool destroy = false; + dns_rdatatype_t covers = 0; + dns_rdatasetiter_current(rdsiter, &set); + if (set.type == dns_rdatatype_rrsig) { + covers = set.covers; + destroy = true; + } + dns_rdataset_disassociate(&set); + result = dns_rdatasetiter_next(rdsiter); + if (destroy) { + dresult = dns_db_deleterdataset(db, node, dbversion, + dns_rdatatype_rrsig, + covers); + check_result(dresult, "dns_db_deleterdataset"); + } + } + if (result != ISC_R_NOMORE) { + fatal("rdataset iteration failed: %s", + isc_result_totext(result)); + } + dns_rdatasetiter_destroy(&rdsiter); +} + +/*% + * Set up the iterator and global state before starting the tasks. + */ +static void +presign(void) { + isc_result_t result; + + gdbiter = NULL; + result = dns_db_createiterator(gdb, 0, &gdbiter); + check_result(result, "dns_db_createiterator()"); +} + +/*% + * Clean up the iterator and global state after the tasks complete. + */ +static void +postsign(void) { + dns_dbiterator_destroy(&gdbiter); +} + +/*% + * Sign the apex of the zone. + * Note the origin may not be the first node if there are out of zone + * records. + */ +static void +signapex(void) { + dns_dbnode_t *node = NULL; + dns_fixedname_t fixed; + dns_name_t *name; + isc_result_t result; + + name = dns_fixedname_initname(&fixed); + result = dns_dbiterator_seek(gdbiter, gorigin); + check_result(result, "dns_dbiterator_seek()"); + result = dns_dbiterator_current(gdbiter, &node, name); + check_dns_dbiterator_current(result); + signname(node, name); + dumpnode(name, node); + cleannode(gdb, gversion, node); + dns_db_detachnode(gdb, &node); + result = dns_dbiterator_first(gdbiter); + if (result == ISC_R_NOMORE) { + atomic_store(&finished, true); + } else if (result != ISC_R_SUCCESS) { + fatal("failure iterating database: %s", + isc_result_totext(result)); + } +} + +/*% + * Assigns a node to a worker thread. This is protected by the master task's + * lock. + */ +static void +assignwork(isc_task_t *task, isc_task_t *worker) { + dns_fixedname_t *fname; + dns_name_t *name; + dns_dbnode_t *node; + sevent_t *sevent; + dns_rdataset_t nsec; + bool found; + isc_result_t result; + static dns_name_t *zonecut = NULL; /* Protected by namelock. */ + static dns_fixedname_t fzonecut; /* Protected by namelock. */ + static unsigned int ended = 0; /* Protected by namelock. */ + + if (atomic_load(&shuttingdown)) { + return; + } + + LOCK(&namelock); + if (atomic_load(&finished)) { + ended++; + if (ended == ntasks) { + isc_task_detach(&task); + isc_app_shutdown(); + } + goto unlock; + } + + fname = isc_mem_get(mctx, sizeof(dns_fixedname_t)); + name = dns_fixedname_initname(fname); + node = NULL; + found = false; + while (!found) { + result = dns_dbiterator_current(gdbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * The origin was handled by signapex(). + */ + if (dns_name_equal(name, gorigin)) { + dns_db_detachnode(gdb, &node); + goto next; + } + /* + * Sort the zone data from the glue and out-of-zone data. + * For NSEC zones nodes with zone data have NSEC records. + * For NSEC3 zones the NSEC3 nodes are zone data but + * outside of the zone name space. For the rest we need + * to track the bottom of zone cuts. + * Nodes which don't need to be signed are dumped here. + */ + dns_rdataset_init(&nsec); + result = dns_db_findrdataset(gdb, node, gversion, nsec_datatype, + 0, 0, &nsec, NULL); + if (dns_rdataset_isassociated(&nsec)) { + dns_rdataset_disassociate(&nsec); + } + if (result == ISC_R_SUCCESS) { + found = true; + } else if (nsec_datatype == dns_rdatatype_nsec3) { + if (dns_name_issubdomain(name, gorigin) && + (zonecut == NULL || + !dns_name_issubdomain(name, zonecut))) + { + if (is_delegation(gdb, gversion, gorigin, name, + node, NULL)) + { + zonecut = savezonecut(&fzonecut, name); + if (!OPTOUT(nsec3flags) || + secure(name, node)) + { + found = true; + } + } else if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + found = true; + } else { + found = true; + } + } + } + + if (!found) { + dumpnode(name, node); + dns_db_detachnode(gdb, &node); + } + + next: + result = dns_dbiterator_next(gdbiter); + if (result == ISC_R_NOMORE) { + atomic_store(&finished, true); + break; + } else if (result != ISC_R_SUCCESS) { + fatal("failure iterating database: %s", + isc_result_totext(result)); + } + } + if (!found) { + ended++; + if (ended == ntasks) { + isc_task_detach(&task); + isc_app_shutdown(); + } + isc_mem_put(mctx, fname, sizeof(dns_fixedname_t)); + goto unlock; + } + sevent = (sevent_t *)isc_event_allocate(mctx, task, SIGNER_EVENT_WORK, + sign, NULL, sizeof(sevent_t)); + + sevent->node = node; + sevent->fname = fname; + isc_task_send(worker, ISC_EVENT_PTR(&sevent)); +unlock: + UNLOCK(&namelock); +} + +/*% + * Start a worker task + */ +static void +startworker(isc_task_t *task, isc_event_t *event) { + isc_task_t *worker; + + worker = (isc_task_t *)event->ev_arg; + assignwork(task, worker); + isc_event_free(&event); +} + +/*% + * Write a node to the output file, and restart the worker task. + */ +static void +writenode(isc_task_t *task, isc_event_t *event) { + isc_task_t *worker; + sevent_t *sevent = (sevent_t *)event; + + worker = (isc_task_t *)event->ev_sender; + dumpnode(dns_fixedname_name(sevent->fname), sevent->node); + cleannode(gdb, gversion, sevent->node); + dns_db_detachnode(gdb, &sevent->node); + isc_mem_put(mctx, sevent->fname, sizeof(dns_fixedname_t)); + assignwork(task, worker); + isc_event_free(&event); +} + +/*% + * Sign a database node. + */ +static void +sign(isc_task_t *task, isc_event_t *event) { + dns_fixedname_t *fname; + dns_dbnode_t *node; + sevent_t *sevent, *wevent; + + sevent = (sevent_t *)event; + node = sevent->node; + fname = sevent->fname; + isc_event_free(&event); + + signname(node, dns_fixedname_name(fname)); + wevent = (sevent_t *)isc_event_allocate(mctx, task, SIGNER_EVENT_WRITE, + writenode, NULL, + sizeof(sevent_t)); + wevent->node = node; + wevent->fname = fname; + isc_task_send(master, ISC_EVENT_PTR(&wevent)); +} + +/*% + * Update / remove the DS RRset. Preserve RRSIG(DS) if possible. + */ +static void +add_ds(dns_name_t *name, dns_dbnode_t *node, uint32_t nsttl) { + dns_rdataset_t dsset; + dns_rdataset_t sigdsset; + isc_result_t result; + + dns_rdataset_init(&dsset); + dns_rdataset_init(&sigdsset); + result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_ds, 0, + 0, &dsset, &sigdsset); + if (result == ISC_R_SUCCESS) { + dns_rdataset_disassociate(&dsset); + result = dns_db_deleterdataset(gdb, node, gversion, + dns_rdatatype_ds, 0); + check_result(result, "dns_db_deleterdataset"); + } + + result = loadds(name, nsttl, &dsset); + if (result == ISC_R_SUCCESS) { + result = dns_db_addrdataset(gdb, node, gversion, 0, &dsset, 0, + NULL); + check_result(result, "dns_db_addrdataset"); + dns_rdataset_disassociate(&dsset); + if (dns_rdataset_isassociated(&sigdsset)) { + dns_rdataset_disassociate(&sigdsset); + } + } else if (dns_rdataset_isassociated(&sigdsset)) { + result = dns_db_deleterdataset(gdb, node, gversion, + dns_rdatatype_rrsig, + dns_rdatatype_ds); + check_result(result, "dns_db_deleterdataset"); + dns_rdataset_disassociate(&sigdsset); + } +} + +/* + * Remove records of the given type and their signatures. + */ +static void +remove_records(dns_dbnode_t *node, dns_rdatatype_t which, bool checknsec) { + isc_result_t result; + dns_rdatatype_t type, covers; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + + /* + * Delete any records of the given type at the apex. + */ + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + if (type == which || covers == which) { + if (which == dns_rdatatype_nsec && checknsec && + !update_chain) + { + fatal("Zone contains NSEC records. Use -u " + "to update to NSEC3."); + } + if (which == dns_rdatatype_nsec3param && checknsec && + !update_chain) + { + fatal("Zone contains NSEC3 chains. Use -u " + "to update to NSEC."); + } + result = dns_db_deleterdataset(gdb, node, gversion, + type, covers); + check_result(result, "dns_db_deleterdataset()"); + } + } + dns_rdatasetiter_destroy(&rdsiter); +} + +/* + * Remove signatures covering the given type. If type == 0, + * then remove all signatures, unless this is a delegation, in + * which case remove all signatures except for DS or nsec_datatype + */ +static void +remove_sigs(dns_dbnode_t *node, bool delegation, dns_rdatatype_t which) { + isc_result_t result; + dns_rdatatype_t type, covers; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + for (result = dns_rdatasetiter_first(rdsiter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + + if (type != dns_rdatatype_rrsig) { + continue; + } + + if (which == 0 && delegation && + (dns_rdatatype_atparent(covers) || + (nsec_datatype == dns_rdatatype_nsec && + covers == nsec_datatype))) + { + continue; + } + + if (which != 0 && covers != which) { + continue; + } + + result = dns_db_deleterdataset(gdb, node, gversion, type, + covers); + check_result(result, "dns_db_deleterdataset()"); + } + dns_rdatasetiter_destroy(&rdsiter); +} + +/*% + * Generate NSEC records for the zone and remove NSEC3/NSEC3PARAM records. + */ +static void +nsecify(void) { + dns_dbiterator_t *dbiter = NULL; + dns_dbnode_t *node = NULL, *nextnode = NULL; + dns_fixedname_t fname, fnextname, fzonecut; + dns_name_t *name, *nextname, *zonecut; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdatatype_t type, covers; + bool done = false; + isc_result_t result; + uint32_t nsttl = 0; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fname); + nextname = dns_fixedname_initname(&fnextname); + zonecut = NULL; + + /* + * Remove any NSEC3 chains. + */ + result = dns_db_createiterator(gdb, DNS_DB_NSEC3ONLY, &dbiter); + check_result(result, "dns_db_createiterator()"); + for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, + &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + result = dns_db_deleterdataset(gdb, node, gversion, + type, covers); + check_result(result, "dns_db_deleterdataset(nsec3param/" + "rrsig)"); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(gdb, &node); + } + dns_dbiterator_destroy(&dbiter); + + result = dns_db_createiterator(gdb, DNS_DB_NONSEC3, &dbiter); + check_result(result, "dns_db_createiterator()"); + + result = dns_dbiterator_first(dbiter); + check_result(result, "dns_dbiterator_first()"); + + while (!done) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (dns_name_equal(name, gorigin)) { + remove_records(node, dns_rdatatype_nsec3param, true); + /* Clean old rrsigs at apex. */ + (void)active_node(node); + } + + if (is_delegation(gdb, gversion, gorigin, name, node, &nsttl)) { + zonecut = savezonecut(&fzonecut, name); + remove_sigs(node, true, 0); + if (generateds) { + add_ds(name, node, nsttl); + } + } else if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + bool active = false; + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + active = active_node(nextnode); + if (!active) { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (!dns_name_issubdomain(nextname, gorigin) || + (zonecut != NULL && + dns_name_issubdomain(nextname, zonecut))) + { + remove_sigs(nextnode, false, 0); + remove_records(nextnode, dns_rdatatype_nsec, + false); + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + dns_db_detachnode(gdb, &nextnode); + break; + } + if (result == ISC_R_NOMORE) { + dns_name_clone(gorigin, nextname); + done = true; + } else if (result != ISC_R_SUCCESS) { + fatal("iterating through the database failed: %s", + isc_result_totext(result)); + } + dns_dbiterator_pause(dbiter); + result = dns_nsec_build(gdb, gversion, node, nextname, + zone_soa_min_ttl); + check_result(result, "dns_nsec_build()"); + dns_db_detachnode(gdb, &node); + } + + dns_dbiterator_destroy(&dbiter); +} + +static void +addnsec3param(const unsigned char *salt, size_t salt_len, + dns_iterations_t iterations) { + dns_dbnode_t *node = NULL; + dns_rdata_nsec3param_t nsec3param; + unsigned char nsec3parambuf[5 + 255]; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_buffer_t b; + isc_result_t result; + + dns_rdataset_init(&rdataset); + + nsec3param.common.rdclass = gclass; + nsec3param.common.rdtype = dns_rdatatype_nsec3param; + ISC_LINK_INIT(&nsec3param.common, link); + nsec3param.mctx = NULL; + nsec3param.flags = 0; + nsec3param.hash = unknownalg ? DNS_NSEC3_UNKNOWNALG : dns_hash_sha1; + nsec3param.iterations = iterations; + nsec3param.salt_length = (unsigned char)salt_len; + DE_CONST(salt, nsec3param.salt); + + isc_buffer_init(&b, nsec3parambuf, sizeof(nsec3parambuf)); + result = dns_rdata_fromstruct(&rdata, gclass, dns_rdatatype_nsec3param, + &nsec3param, &b); + check_result(result, "dns_rdata_fromstruct()"); + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + check_result(result, "dns_rdatalist_tordataset()"); + + result = dns_db_findnode(gdb, gorigin, true, &node); + check_result(result, "dns_db_findnode(gorigin)"); + + /* + * Delete any current NSEC3PARAM records. + */ + result = dns_db_deleterdataset(gdb, node, gversion, + dns_rdatatype_nsec3param, 0); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + check_result(result, "dddnsec3param: dns_db_deleterdataset()"); + + result = dns_db_addrdataset(gdb, node, gversion, 0, &rdataset, + DNS_DBADD_MERGE, NULL); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + check_result(result, "addnsec3param: dns_db_addrdataset()"); + dns_db_detachnode(gdb, &node); +} + +static void +addnsec3(dns_name_t *name, dns_dbnode_t *node, const unsigned char *salt, + size_t salt_len, unsigned int iterations, hashlist_t *hashlist, + dns_ttl_t ttl) { + unsigned char hash[NSEC3_MAX_HASH_LENGTH]; + const unsigned char *nexthash; + unsigned char nsec3buffer[DNS_NSEC3_BUFFERSIZE]; + dns_fixedname_t hashname; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dns_dbnode_t *nsec3node = NULL; + char namebuf[DNS_NAME_FORMATSIZE]; + size_t hash_len; + + dns_name_format(name, namebuf, sizeof(namebuf)); + + dns_fixedname_init(&hashname); + dns_rdataset_init(&rdataset); + + dns_name_downcase(name, name, NULL); + result = dns_nsec3_hashname(&hashname, hash, &hash_len, name, gorigin, + dns_hash_sha1, iterations, salt, salt_len); + check_result(result, "addnsec3: dns_nsec3_hashname()"); + nexthash = hashlist_findnext(hashlist, hash); + result = dns_nsec3_buildrdata( + gdb, gversion, node, + unknownalg ? DNS_NSEC3_UNKNOWNALG : dns_hash_sha1, nsec3flags, + iterations, salt, salt_len, nexthash, ISC_SHA1_DIGESTLENGTH, + nsec3buffer, &rdata); + check_result(result, "addnsec3: dns_nsec3_buildrdata()"); + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + rdatalist.ttl = ttl; + ISC_LIST_APPEND(rdatalist.rdata, &rdata, link); + result = dns_rdatalist_tordataset(&rdatalist, &rdataset); + check_result(result, "dns_rdatalist_tordataset()"); + result = dns_db_findnsec3node(gdb, dns_fixedname_name(&hashname), true, + &nsec3node); + check_result(result, "addnsec3: dns_db_findnode()"); + result = dns_db_addrdataset(gdb, nsec3node, gversion, 0, &rdataset, 0, + NULL); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + check_result(result, "addnsec3: dns_db_addrdataset()"); + dns_db_detachnode(gdb, &nsec3node); +} + +/*% + * Clean out NSEC3 record and RRSIG(NSEC3) that are not in the hash list. + * + * Extract the hash from the first label of 'name' then see if it + * is in hashlist. If 'name' is not in the hashlist then delete the + * any NSEC3 records which have the same parameters as the chain we + * are building. + * + * XXXMPA Should we also check that it of the form <hash>.<origin>? + */ +static void +nsec3clean(dns_name_t *name, dns_dbnode_t *node, unsigned int hashalg, + unsigned int iterations, const unsigned char *salt, size_t salt_len, + hashlist_t *hashlist) { + dns_label_t label; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata, delrdata; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset, delrdataset; + bool delete_rrsigs = false; + isc_buffer_t target; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1]; + bool exists; + + /* + * Get the first label. + */ + dns_name_getlabel(name, 0, &label); + + /* + * We want just the label contents. + */ + isc_region_consume(&label, 1); + + /* + * Decode base32hex string. + */ + isc_buffer_init(&target, hash, sizeof(hash) - 1); + result = isc_base32hex_decoderegion(&label, &target); + if (result != ISC_R_SUCCESS) { + return; + } + + hash[isc_buffer_usedlength(&target)] = 0; + + exists = hashlist_exists(hashlist, hash); + + /* + * Verify that the NSEC3 parameters match the current ones + * otherwise we are dealing with a different NSEC3 chain. + */ + dns_rdataset_init(&rdataset); + dns_rdataset_init(&delrdataset); + + result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_nsec3, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + return; + } + + /* + * Delete any NSEC3 records which are not part of the current + * NSEC3 chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + check_result(result, "dns_rdata_tostruct"); + if (exists && nsec3.hash == hashalg && + nsec3.iterations == iterations && + nsec3.salt_length == salt_len && + isc_safe_memequal(nsec3.salt, salt, salt_len)) + { + continue; + } + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + if (set_maxttl) { + rdatalist.ttl = ISC_MIN(rdataset.ttl, maxttl); + } + dns_rdata_init(&delrdata); + dns_rdata_clone(&rdata, &delrdata); + ISC_LIST_APPEND(rdatalist.rdata, &delrdata, link); + result = dns_rdatalist_tordataset(&rdatalist, &delrdataset); + check_result(result, "dns_rdatalist_tordataset()"); + result = dns_db_subtractrdataset(gdb, node, gversion, + &delrdataset, 0, NULL); + dns_rdataset_disassociate(&delrdataset); + if (result != ISC_R_SUCCESS && result != DNS_R_NXRRSET) { + check_result(result, "dns_db_subtractrdataset(NSEC3)"); + } + delete_rrsigs = true; + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) { + check_result(result, "dns_rdataset_first/next"); + } + + if (!delete_rrsigs) { + return; + } + /* + * Delete the NSEC3 RRSIGs + */ + result = dns_db_deleterdataset(gdb, node, gversion, dns_rdatatype_rrsig, + dns_rdatatype_nsec3); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + check_result(result, "dns_db_deleterdataset(RRSIG(NSEC3))"); + } +} + +static void +rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, dns_diff_t *add, + dns_diff_t *del) { + isc_result_t result; + unsigned int count1 = 0; + dns_rdataset_t tmprdataset; + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + dns_rdatatype_format(rdataset->type, typestr, sizeof(typestr)); + + dns_rdataset_init(&tmprdataset); + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + unsigned int count2 = 0; + + count1++; + dns_rdataset_current(rdataset, &rdata1); + dns_rdataset_clone(rdataset, &tmprdataset); + for (result = dns_rdataset_first(&tmprdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&tmprdataset)) + { + dns_rdata_t rdata2 = DNS_RDATA_INIT; + dns_difftuple_t *tuple = NULL; + count2++; + dns_rdataset_current(&tmprdataset, &rdata2); + if (count1 < count2 && + dns_rdata_casecompare(&rdata1, &rdata2) == 0) + { + vbprintf(2, "removing duplicate at %s/%s\n", + namestr, typestr); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + rdataset->ttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + } else if (set_maxttl && rdataset->ttl > maxttl) { + vbprintf(2, + "reducing ttl of %s/%s " + "from %d to %d\n", + namestr, typestr, rdataset->ttl, + maxttl); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + rdataset->ttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + tuple = NULL; + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, name, + maxttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); + } + } + dns_rdataset_disassociate(&tmprdataset); + } +} + +static void +cleanup_zone(void) { + isc_result_t result; + dns_dbiterator_t *dbiter = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_diff_t add, del; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_fixedname_t fname; + dns_name_t *name; + + dns_diff_init(mctx, &add); + dns_diff_init(mctx, &del); + name = dns_fixedname_initname(&fname); + dns_rdataset_init(&rdataset); + + result = dns_db_createiterator(gdb, 0, &dbiter); + check_result(result, "dns_db_createiterator()"); + + for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, + &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + rrset_cleanup(name, &rdataset, &add, &del); + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOMORE) { + fatal("rdatasets iteration failed."); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(gdb, &node); + } + if (result != ISC_R_NOMORE) { + fatal("zone iteration failed."); + } + + result = dns_diff_applysilently(&del, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + result = dns_diff_applysilently(&add, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + dns_diff_clear(&del); + dns_diff_clear(&add); + dns_dbiterator_destroy(&dbiter); +} + +/* + * Generate NSEC3 records for the zone. + */ +static void +nsec3ify(unsigned int hashalg, dns_iterations_t iterations, + const unsigned char *salt, size_t salt_len, hashlist_t *hashlist) { + dns_dbiterator_t *dbiter = NULL; + dns_dbnode_t *node = NULL, *nextnode = NULL; + dns_fixedname_t fname, fnextname, fzonecut; + dns_name_t *name, *nextname, *zonecut; + dns_rdataset_t rdataset; + int order; + bool active; + bool done = false; + isc_result_t result; + uint32_t nsttl = 0; + unsigned int count, nlabels; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fname); + nextname = dns_fixedname_initname(&fnextname); + zonecut = NULL; + + /* + * Walk the zone generating the hash names. + */ + result = dns_db_createiterator(gdb, DNS_DB_NONSEC3, &dbiter); + check_result(result, "dns_db_createiterator()"); + + result = dns_dbiterator_first(dbiter); + check_result(result, "dns_dbiterator_first()"); + + while (!done) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (dns_name_equal(name, gorigin)) { + remove_records(node, dns_rdatatype_nsec, true); + /* Clean old rrsigs at apex. */ + (void)active_node(node); + } + + if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + active = active_node(nextnode); + if (!active) { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (!dns_name_issubdomain(nextname, gorigin) || + (zonecut != NULL && + dns_name_issubdomain(nextname, zonecut))) + { + remove_sigs(nextnode, false, 0); + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (is_delegation(gdb, gversion, gorigin, nextname, + nextnode, &nsttl)) + { + zonecut = savezonecut(&fzonecut, nextname); + remove_sigs(nextnode, true, 0); + if (generateds) { + add_ds(nextname, nextnode, nsttl); + } + if (OPTOUT(nsec3flags) && + !secure(nextname, nextnode)) + { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + } else if (has_dname(gdb, gversion, nextnode)) { + zonecut = savezonecut(&fzonecut, nextname); + } + dns_db_detachnode(gdb, &nextnode); + break; + } + if (result == ISC_R_NOMORE) { + dns_name_copynf(gorigin, nextname); + done = true; + } else if (result != ISC_R_SUCCESS) { + fatal("iterating through the database failed: %s", + isc_result_totext(result)); + } + dns_name_downcase(name, name, NULL); + hashlist_add_dns_name(hashlist, name, hashalg, iterations, salt, + salt_len, false); + dns_db_detachnode(gdb, &node); + /* + * Add hashes for empty nodes. Use closest encloser logic. + * The closest encloser either has data or is a empty + * node for another span so we don't add + * it here. Empty labels on nextname are within the span. + */ + dns_name_downcase(nextname, nextname, NULL); + dns_name_fullcompare(name, nextname, &order, &nlabels); + addnowildcardhash(hashlist, name, hashalg, iterations, salt, + salt_len); + count = dns_name_countlabels(nextname); + while (count > nlabels + 1) { + count--; + dns_name_split(nextname, count, NULL, nextname); + hashlist_add_dns_name(hashlist, nextname, hashalg, + iterations, salt, salt_len, + false); + addnowildcardhash(hashlist, nextname, hashalg, + iterations, salt, salt_len); + } + } + dns_dbiterator_destroy(&dbiter); + + /* + * We have all the hashes now so we can sort them. + */ + hashlist_sort(hashlist); + + /* + * Check for duplicate hashes. If found the salt needs to + * be changed. + */ + if (hashlist_hasdup(hashlist)) { + fatal("Duplicate hash detected. Pick a different salt."); + } + + /* + * Generate the nsec3 records. + */ + zonecut = NULL; + done = false; + + addnsec3param(salt, salt_len, iterations); + + /* + * Clean out NSEC3 records which don't match this chain. + */ + result = dns_db_createiterator(gdb, DNS_DB_NSEC3ONLY, &dbiter); + check_result(result, "dns_db_createiterator()"); + + for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + nsec3clean(name, node, hashalg, iterations, salt, salt_len, + hashlist); + dns_db_detachnode(gdb, &node); + } + dns_dbiterator_destroy(&dbiter); + + /* + * Generate / complete the new chain. + */ + result = dns_db_createiterator(gdb, DNS_DB_NONSEC3, &dbiter); + check_result(result, "dns_db_createiterator()"); + + result = dns_dbiterator_first(dbiter); + check_result(result, "dns_dbiterator_first()"); + + while (!done) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + active = active_node(nextnode); + if (!active) { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (!dns_name_issubdomain(nextname, gorigin) || + (zonecut != NULL && + dns_name_issubdomain(nextname, zonecut))) + { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (is_delegation(gdb, gversion, gorigin, nextname, + nextnode, NULL)) + { + zonecut = savezonecut(&fzonecut, nextname); + if (OPTOUT(nsec3flags) && + !secure(nextname, nextnode)) + { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + } else if (has_dname(gdb, gversion, nextnode)) { + zonecut = savezonecut(&fzonecut, nextname); + } + dns_db_detachnode(gdb, &nextnode); + break; + } + if (result == ISC_R_NOMORE) { + dns_name_copynf(gorigin, nextname); + done = true; + } else if (result != ISC_R_SUCCESS) { + fatal("iterating through the database failed: %s", + isc_result_totext(result)); + } + /* + * We need to pause here to release the lock on the database. + */ + dns_dbiterator_pause(dbiter); + addnsec3(name, node, salt, salt_len, iterations, hashlist, + zone_soa_min_ttl); + dns_db_detachnode(gdb, &node); + /* + * Add NSEC3's for empty nodes. Use closest encloser logic. + */ + dns_name_fullcompare(name, nextname, &order, &nlabels); + count = dns_name_countlabels(nextname); + while (count > nlabels + 1) { + count--; + dns_name_split(nextname, count, NULL, nextname); + addnsec3(nextname, NULL, salt, salt_len, iterations, + hashlist, zone_soa_min_ttl); + } + } + dns_dbiterator_destroy(&dbiter); +} + +/*% + * Load the zone file from disk + */ +static void +loadzone(char *file, char *origin, dns_rdataclass_t rdclass, dns_db_t **db) { + isc_buffer_t b; + int len; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + len = strlen(origin); + isc_buffer_init(&b, origin, len); + isc_buffer_add(&b, len); + + name = dns_fixedname_initname(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + fatal("failed converting name '%s' to dns format: %s", origin, + isc_result_totext(result)); + } + + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0, + NULL, db); + check_result(result, "dns_db_create()"); + + result = dns_db_load(*db, file, inputformat, 0); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + fatal("failed loading zone from '%s': %s", file, + isc_result_totext(result)); + } +} + +/*% + * Finds all public zone keys in the zone, and attempts to load the + * private keys from disk. + */ +static void +loadzonekeys(bool preserve_keys, bool load_public) { + dns_dbnode_t *node; + dns_dbversion_t *currentversion = NULL; + isc_result_t result; + dns_rdataset_t rdataset, keysigs, soasigs; + + node = NULL; + result = dns_db_findnode(gdb, gorigin, false, &node); + if (result != ISC_R_SUCCESS) { + fatal("failed to find the zone's origin: %s", + isc_result_totext(result)); + } + + dns_db_currentversion(gdb, ¤tversion); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&soasigs); + dns_rdataset_init(&keysigs); + + /* Make note of the keys which signed the SOA, if any */ + result = dns_db_findrdataset(gdb, node, currentversion, + dns_rdatatype_soa, 0, 0, &rdataset, + &soasigs); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Preserve the TTL of the DNSKEY RRset, if any */ + dns_rdataset_disassociate(&rdataset); + result = dns_db_findrdataset(gdb, node, currentversion, + dns_rdatatype_dnskey, 0, 0, &rdataset, + &keysigs); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (set_keyttl && keyttl != rdataset.ttl) { + fprintf(stderr, + "User-specified TTL %u conflicts " + "with existing DNSKEY RRset TTL.\n", + keyttl); + fprintf(stderr, + "Imported keys will use the RRSet " + "TTL %u instead.\n", + rdataset.ttl); + } + keyttl = rdataset.ttl; + + /* Load keys corresponding to the existing DNSKEY RRset. */ + result = dns_dnssec_keylistfromrdataset( + gorigin, directory, mctx, &rdataset, &keysigs, &soasigs, + preserve_keys, load_public, &keylist); + if (result != ISC_R_SUCCESS) { + fatal("failed to load the zone keys: %s", + isc_result_totext(result)); + } + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (dns_rdataset_isassociated(&keysigs)) { + dns_rdataset_disassociate(&keysigs); + } + if (dns_rdataset_isassociated(&soasigs)) { + dns_rdataset_disassociate(&soasigs); + } + dns_db_detachnode(gdb, &node); + dns_db_closeversion(gdb, ¤tversion, false); +} + +static void +loadexplicitkeys(char *keyfiles[], int n, bool setksk) { + isc_result_t result; + int i; + + for (i = 0; i < n; i++) { + dns_dnsseckey_t *key = NULL; + dst_key_t *newkey = NULL; + + result = dst_key_fromnamedfile( + keyfiles[i], directory, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, mctx, &newkey); + if (result != ISC_R_SUCCESS) { + fatal("cannot load dnskey %s: %s", keyfiles[i], + isc_result_totext(result)); + } + + if (!dns_name_equal(gorigin, dst_key_name(newkey))) { + fatal("key %s not at origin\n", keyfiles[i]); + } + + if (!dst_key_isprivate(newkey)) { + fatal("cannot sign zone with non-private dnskey %s", + keyfiles[i]); + } + + /* Skip any duplicates */ + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (dst_key_id(key->key) == dst_key_id(newkey) && + dst_key_alg(key->key) == dst_key_alg(newkey)) + { + break; + } + } + + if (key == NULL) { + /* We haven't seen this key before */ + dns_dnsseckey_create(mctx, &newkey, &key); + ISC_LIST_APPEND(keylist, key, link); + key->source = dns_keysource_user; + } else { + dst_key_free(&key->key); + key->key = newkey; + } + + key->force_publish = true; + key->force_sign = true; + + if (setksk) { + key->ksk = true; + } + } +} + +static void +report(const char *format, ...) { + if (!quiet) { + FILE *out = output_stdout ? stderr : stdout; + char buf[4096]; + va_list args; + + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + fprintf(out, "%s\n", buf); + } +} + +static void +clear_keylist(dns_dnsseckeylist_t *list) { + dns_dnsseckey_t *key; + while (!ISC_LIST_EMPTY(*list)) { + key = ISC_LIST_HEAD(*list); + ISC_LIST_UNLINK(*list, key, link); + dns_dnsseckey_destroy(mctx, &key); + } +} + +static void +build_final_keylist(void) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + dns_dnsseckeylist_t rmkeys, matchkeys; + char name[DNS_NAME_FORMATSIZE]; + dns_rdataset_t cdsset, cdnskeyset, soaset; + + ISC_LIST_INIT(rmkeys); + ISC_LIST_INIT(matchkeys); + + dns_rdataset_init(&soaset); + dns_rdataset_init(&cdsset); + dns_rdataset_init(&cdnskeyset); + + /* + * Find keys that match this zone in the key repository. + */ + result = dns_dnssec_findmatchingkeys(gorigin, directory, now, mctx, + &matchkeys); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + check_result(result, "dns_dnssec_findmatchingkeys"); + + result = dns_db_newversion(gdb, &ver); + check_result(result, "dns_db_newversion"); + + result = dns_db_getoriginnode(gdb, &node); + check_result(result, "dns_db_getoriginnode"); + + /* Get the CDS rdataset */ + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_cds, + dns_rdatatype_none, 0, &cdsset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + + /* Get the CDNSKEY rdataset */ + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_cdnskey, + dns_rdatatype_none, 0, &cdnskeyset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + dns_diff_init(mctx, &diff); + + /* + * Update keylist with information from from the key repository. + */ + dns_dnssec_updatekeys(&keylist, &matchkeys, NULL, gorigin, keyttl, + &diff, mctx, report); + + /* + * Update keylist with sync records. + */ + dns_dnssec_syncupdate(&keylist, &rmkeys, &cdsset, &cdnskeyset, now, + keyttl, &diff, mctx); + + dns_name_format(gorigin, name, sizeof(name)); + + result = dns_diff_applysilently(&diff, gdb, ver); + if (result != ISC_R_SUCCESS) { + fatal("failed to update DNSKEY RRset at node '%s': %s", name, + isc_result_totext(result)); + } + + dns_db_detachnode(gdb, &node); + dns_db_closeversion(gdb, &ver, true); + + dns_diff_clear(&diff); + + if (dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + if (dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + clear_keylist(&rmkeys); + clear_keylist(&matchkeys); +} + +static void +warnifallksk(dns_db_t *db) { + dns_dbversion_t *currentversion = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dns_rdata_dnskey_t dnskey; + bool have_non_ksk = false; + + dns_db_currentversion(db, ¤tversion); + + result = dns_db_findnode(db, gorigin, false, &node); + if (result != ISC_R_SUCCESS) { + fatal("failed to find the zone's origin: %s", + isc_result_totext(result)); + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, currentversion, + dns_rdatatype_dnskey, 0, 0, &rdataset, + NULL); + if (result != ISC_R_SUCCESS) { + fatal("failed to find keys at the zone apex: %s", + isc_result_totext(result)); + } + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dnskey, NULL); + check_result(result, "dns_rdata_tostruct"); + if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) { + have_non_ksk = true; + result = ISC_R_NOMORE; + } else { + result = dns_rdataset_next(&rdataset); + } + dns_rdata_freestruct(&dnskey); + } + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + dns_db_closeversion(db, ¤tversion, false); + if (!have_non_ksk && !ignore_kskflag) { + if (disable_zone_check) { + fprintf(stderr, + "%s: warning: No non-KSK DNSKEY found; " + "supply a ZSK or use '-z'.\n", + program); + } else { + fatal("No non-KSK DNSKEY found; " + "supply a ZSK or use '-z'."); + } + } +} + +static void +set_nsec3params(bool update, bool set_salt, bool set_optout, bool set_iter) { + isc_result_t result; + dns_dbversion_t *ver = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3_t nsec3; + dns_fixedname_t fname; + dns_name_t *hashname; + unsigned char orig_salt[255]; + size_t orig_saltlen; + dns_hash_t orig_hash; + uint16_t orig_iter; + + dns_db_currentversion(gdb, &ver); + dns_rdataset_init(&rdataset); + + orig_saltlen = sizeof(orig_salt); + result = dns_db_getnsec3parameters(gdb, ver, &orig_hash, NULL, + &orig_iter, orig_salt, + &orig_saltlen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + nsec_datatype = dns_rdatatype_nsec3; + + if (!update && set_salt) { + if (salt_length != orig_saltlen || + !isc_safe_memequal(saltbuf, orig_salt, salt_length)) + { + fatal("An NSEC3 chain exists with a different salt. " + "Use -u to update it."); + } + } else if (!set_salt) { + salt_length = orig_saltlen; + memmove(saltbuf, orig_salt, orig_saltlen); + gsalt = saltbuf; + } + + if (!update && set_iter) { + if (nsec3iter != orig_iter) { + fatal("An NSEC3 chain exists with different " + "iterations. Use -u to update it."); + } + } else if (!set_iter) { + nsec3iter = orig_iter; + } + + /* + * Find an NSEC3 record to get the current OPTOUT value. + * (This assumes all NSEC3 records agree.) + */ + + hashname = dns_fixedname_initname(&fname); + result = dns_nsec3_hashname(&fname, NULL, NULL, gorigin, gorigin, + dns_hash_sha1, orig_iter, orig_salt, + orig_saltlen); + check_result(result, "dns_nsec3_hashname"); + + result = dns_db_findnsec3node(gdb, hashname, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_nsec3, 0, 0, + &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + check_result(result, "dns_rdata_tostruct"); + + if (!update && set_optout) { + if (nsec3flags != nsec3.flags) { + fatal("An NSEC3 chain exists with%s OPTOUT. " + "Use -u -%s to %s it.", + OPTOUT(nsec3.flags) ? "" : "out", + OPTOUT(nsec3.flags) ? "AA" : "A", + OPTOUT(nsec3.flags) ? "clear" : "set"); + } + } else if (!set_optout) { + nsec3flags = nsec3.flags; + } + + dns_rdata_freestruct(&nsec3); + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(gdb, &node); + } + dns_db_closeversion(gdb, &ver, false); +} + +static void +writeset(const char *prefix, dns_rdatatype_t type) { + char *filename; + char namestr[DNS_NAME_FORMATSIZE]; + dns_db_t *db = NULL; + dns_dbversion_t *dbversion = NULL; + dns_diff_t diff; + dns_difftuple_t *tuple = NULL; + dns_name_t *name; + dns_rdata_t rdata, ds; + bool have_ksk = false; + bool have_non_ksk = false; + isc_buffer_t b; + isc_buffer_t namebuf; + isc_region_t r; + isc_result_t result; + dns_dnsseckey_t *key, *curr; + unsigned char dsbuf[DNS_DS_BUFFERSIZE]; + unsigned char keybuf[DST_KEY_MAXSIZE]; + unsigned int filenamelen; + const dns_master_style_t *style = (type == dns_rdatatype_dnskey) + ? masterstyle + : dsstyle; + + isc_buffer_init(&namebuf, namestr, sizeof(namestr)); + result = dns_name_tofilenametext(gorigin, false, &namebuf); + check_result(result, "dns_name_tofilenametext"); + isc_buffer_putuint8(&namebuf, 0); + filenamelen = strlen(prefix) + strlen(namestr) + 1; + if (dsdir != NULL) { + filenamelen += strlen(dsdir) + 1; + } + filename = isc_mem_get(mctx, filenamelen); + if (dsdir != NULL) { + snprintf(filename, filenamelen, "%s/", dsdir); + } else { + filename[0] = 0; + } + strlcat(filename, prefix, filenamelen); + strlcat(filename, namestr, filenamelen); + + dns_diff_init(mctx, &diff); + + name = gorigin; + + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (REVOKE(key->key)) { + continue; + } + if (isksk(key)) { + have_ksk = true; + have_non_ksk = false; + } else { + have_ksk = false; + have_non_ksk = true; + } + for (curr = ISC_LIST_HEAD(keylist); curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + if (dst_key_alg(key->key) != dst_key_alg(curr->key)) { + continue; + } + if (REVOKE(curr->key)) { + continue; + } + if (isksk(curr)) { + have_ksk = true; + } else { + have_non_ksk = true; + } + } + if (have_ksk && have_non_ksk && !isksk(key)) { + continue; + } + dns_rdata_init(&rdata); + dns_rdata_init(&ds); + isc_buffer_init(&b, keybuf, sizeof(keybuf)); + result = dst_key_todns(key->key, &b); + check_result(result, "dst_key_todns"); + isc_buffer_usedregion(&b, &r); + dns_rdata_fromregion(&rdata, gclass, dns_rdatatype_dnskey, &r); + if (type != dns_rdatatype_dnskey) { + result = dns_ds_buildrdata(gorigin, &rdata, + DNS_DSDIGEST_SHA256, dsbuf, + &ds); + check_result(result, "dns_ds_buildrdata"); + result = dns_difftuple_create(mctx, + DNS_DIFFOP_ADDRESIGN, + name, 0, &ds, &tuple); + } else { + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, gorigin, + zone_soa_min_ttl, &rdata, &tuple); + } + check_result(result, "dns_difftuple_create"); + dns_diff_append(&diff, &tuple); + } + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + gclass, 0, NULL, &db); + check_result(result, "dns_db_create"); + + result = dns_db_newversion(db, &dbversion); + check_result(result, "dns_db_newversion"); + + result = dns_diff_apply(&diff, db, dbversion); + check_result(result, "dns_diff_apply"); + dns_diff_clear(&diff); + + result = dns_master_dump(mctx, db, dbversion, style, filename, + dns_masterformat_text, NULL); + check_result(result, "dns_master_dump"); + + isc_mem_put(mctx, filename, filenamelen); + + dns_db_closeversion(db, &dbversion, false); + dns_db_detach(&db); +} + +static void +print_time(FILE *fp) { + time_t currenttime = time(NULL); + struct tm t, *tm = localtime_r(¤ttime, &t); + unsigned int flen; + char timebuf[80]; + + if (tm == NULL || outputformat != dns_masterformat_text) { + return; + } + + flen = strftime(timebuf, sizeof(timebuf), "%a %b %e %H:%M:%S %Y", tm); + INSIST(flen > 0U && flen < sizeof(timebuf)); + fprintf(fp, "; File written on %s\n", timebuf); +} + +static void +print_version(FILE *fp) { + if (outputformat != dns_masterformat_text) { + return; + } + + fprintf(fp, "; dnssec_signzone version " VERSION "\n"); +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\t%s [options] zonefile [keys]\n", program); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Version: %s\n", VERSION); + + fprintf(stderr, "Options: (default value in parenthesis) \n"); + fprintf(stderr, "\t-S:\tsmart signing: automatically finds key files\n" + "\t\tfor the zone and determines how they are to " + "be used\n"); + fprintf(stderr, "\t-K directory:\n"); + fprintf(stderr, "\t\tdirectory to find key files (.)\n"); + fprintf(stderr, "\t-d directory:\n"); + fprintf(stderr, "\t\tdirectory to find dsset-* files (.)\n"); + fprintf(stderr, "\t-g:\t"); + fprintf(stderr, "update DS records based on child zones' " + "dsset-* files\n"); + fprintf(stderr, "\t-s [YYYYMMDDHHMMSS|+offset]:\n"); + fprintf(stderr, "\t\tRRSIG start time " + "- absolute|offset (now - 1 hour)\n"); + fprintf(stderr, "\t-e [YYYYMMDDHHMMSS|+offset|\"now\"+offset]:\n"); + fprintf(stderr, "\t\tRRSIG end time " + "- absolute|from start|from now " + "(now + 30 days)\n"); + fprintf(stderr, "\t-X [YYYYMMDDHHMMSS|+offset|\"now\"+offset]:\n"); + fprintf(stderr, "\t\tDNSKEY RRSIG end " + "- absolute|from start|from now " + "(matches -e)\n"); + fprintf(stderr, "\t-i interval:\n"); + fprintf(stderr, "\t\tcycle interval - resign " + "if < interval from end ( (end-start)/4 )\n"); + fprintf(stderr, "\t-j jitter:\n"); + fprintf(stderr, "\t\trandomize signature end time up to jitter " + "seconds\n"); + fprintf(stderr, "\t-v debuglevel (0)\n"); + fprintf(stderr, "\t-q quiet\n"); + fprintf(stderr, "\t-V:\tprint version information\n"); + fprintf(stderr, "\t-o origin:\n"); + fprintf(stderr, "\t\tzone origin (name of zonefile)\n"); + fprintf(stderr, "\t-f outfile:\n"); + fprintf(stderr, "\t\tfile the signed zone is written in " + "(zonefile + .signed)\n"); + fprintf(stderr, "\t-I format:\n"); + fprintf(stderr, "\t\tfile format of input zonefile (text)\n"); + fprintf(stderr, "\t-O format:\n"); + fprintf(stderr, "\t\tfile format of signed zone file (text)\n"); + fprintf(stderr, "\t-N format:\n"); + fprintf(stderr, "\t\tsoa serial format of signed zone file (keep)\n"); + fprintf(stderr, "\t-D:\n"); + fprintf(stderr, "\t\toutput only DNSSEC-related records\n"); + fprintf(stderr, "\t-a:\t"); + fprintf(stderr, "verify generated signatures\n"); + fprintf(stderr, "\t-c class (IN)\n"); + fprintf(stderr, "\t-E engine:\n"); +#if USE_PKCS11 + fprintf(stderr, + "\t\tpath to PKCS#11 provider library " + "(default is %s)\n", + PK11_LIB_LOCATION); +#else /* if USE_PKCS11 */ + fprintf(stderr, "\t\tname of an OpenSSL engine to use\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, "\t-P:\t"); + fprintf(stderr, "disable post-sign verification\n"); + fprintf(stderr, "\t-Q:\t"); + fprintf(stderr, "remove signatures from keys that are no " + "longer active\n"); + fprintf(stderr, "\t-R:\t"); + fprintf(stderr, "remove signatures from keys that no longer exist\n"); + fprintf(stderr, "\t-T TTL:\tTTL for newly added DNSKEYs\n"); + fprintf(stderr, "\t-t:\t"); + fprintf(stderr, "print statistics\n"); + fprintf(stderr, "\t-u:\t"); + fprintf(stderr, "update or replace an existing NSEC/NSEC3 chain\n"); + fprintf(stderr, "\t-x:\tsign DNSKEY record with KSKs only, not ZSKs\n"); + fprintf(stderr, "\t-z:\tsign all records with KSKs\n"); + fprintf(stderr, "\t-C:\tgenerate a keyset file, for compatibility\n" + "\t\twith older versions of dnssec-signzone -g\n"); + fprintf(stderr, "\t-n ncpus (number of cpus present)\n"); + fprintf(stderr, "\t-k key_signing_key\n"); + fprintf(stderr, "\t-3 NSEC3 salt\n"); + fprintf(stderr, "\t-H NSEC3 iterations (10)\n"); + fprintf(stderr, "\t-A NSEC3 optout\n"); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Signing Keys: "); + fprintf(stderr, "(default: all zone keys that have private keys)\n"); + fprintf(stderr, "\tkeyfile (Kname+alg+tag)\n"); + + exit(0); +} + +static void +removetempfile(void) { + if (removefile) { + isc_file_remove(tempfile); + } +} + +static void +print_stats(isc_time_t *timer_start, isc_time_t *timer_finish, + isc_time_t *sign_start, isc_time_t *sign_finish) { + uint64_t time_us; /* Time in microseconds */ + uint64_t time_ms; /* Time in milliseconds */ + uint64_t sig_ms; /* Signatures per millisecond */ + FILE *out = output_stdout ? stderr : stdout; + + fprintf(out, "Signatures generated: %10u\n", nsigned); + fprintf(out, "Signatures retained: %10u\n", nretained); + fprintf(out, "Signatures dropped: %10u\n", ndropped); + fprintf(out, "Signatures successfully verified: %10u\n", nverified); + fprintf(out, + "Signatures unsuccessfully " + "verified: %10u\n", + nverifyfailed); + + time_us = isc_time_microdiff(sign_finish, sign_start); + time_ms = time_us / 1000; + fprintf(out, "Signing time in seconds: %7u.%03u\n", + (unsigned int)(time_ms / 1000), (unsigned int)(time_ms % 1000)); + if (time_us > 0) { + sig_ms = ((uint64_t)nsigned * 1000000000) / time_us; + fprintf(out, "Signatures per second: %7u.%03u\n", + (unsigned int)sig_ms / 1000, + (unsigned int)sig_ms % 1000); + } + + time_us = isc_time_microdiff(timer_finish, timer_start); + time_ms = time_us / 1000; + fprintf(out, "Runtime in seconds: %7u.%03u\n", + (unsigned int)(time_ms / 1000), (unsigned int)(time_ms % 1000)); +} + +int +main(int argc, char *argv[]) { + int i, ch; + char *startstr = NULL, *endstr = NULL, *classname = NULL; + char *dnskey_endstr = NULL; + char *origin = NULL, *file = NULL, *output = NULL; + char *inputformatstr = NULL, *outputformatstr = NULL; + char *serialformatstr = NULL; + char *dskeyfile[MAXDSKEYS]; + int ndskeys = 0; + char *endp; + isc_time_t timer_start, timer_finish; + isc_time_t sign_start, sign_finish; + dns_dnsseckey_t *key; + isc_result_t result, vresult; + isc_log_t *log = NULL; + const char *engine = NULL; + bool free_output = false; + int tempfilelen = 0; + dns_rdataclass_t rdclass; + isc_task_t **tasks = NULL; + hashlist_t hashlist; + bool make_keyset = false; + bool set_salt = false; + bool set_optout = false; + bool set_iter = false; + bool nonsecify = false; + + atomic_init(&shuttingdown, false); + atomic_init(&finished, false); + + /* Unused letters: Bb G J q Yy (and F is reserved). */ +#define CMDLINE_FLAGS \ + "3:AaCc:Dd:E:e:f:FghH:i:I:j:K:k:L:l:m:M:n:N:o:O:PpQqRr:s:ST:tuUv:VX:" \ + "xzZ:" + + /* + * Process memory debugging argument first. + */ + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'm': + if (strcasecmp(isc_commandline_argument, "record") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + } + if (strcasecmp(isc_commandline_argument, "trace") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGTRACE; + } + if (strcasecmp(isc_commandline_argument, "usage") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; + } + if (strcasecmp(isc_commandline_argument, "size") == 0) { + isc_mem_debugging |= ISC_MEM_DEBUGSIZE; + } + if (strcasecmp(isc_commandline_argument, "mctx") == 0) { + isc_mem_debugging |= ISC_MEM_DEBUGCTX; + } + break; + default: + break; + } + } + isc_commandline_reset = true; + +#ifdef _WIN32 + InitSockets(); +#endif /* ifdef _WIN32 */ + + masterstyle = &dns_master_style_explicitttl; + + check_result(isc_app_start(), "isc_app_start"); + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case '3': + set_salt = true; + nsec_datatype = dns_rdatatype_nsec3; + if (strcmp(isc_commandline_argument, "-") != 0) { + isc_buffer_t target; + char *sarg; + + sarg = isc_commandline_argument; + isc_buffer_init(&target, saltbuf, + sizeof(saltbuf)); + result = isc_hex_decodestring(sarg, &target); + check_result(result, "isc_hex_decodestring(" + "salt)"); + salt_length = isc_buffer_usedlength(&target); + } + break; + + case 'A': + set_optout = true; + if (OPTOUT(nsec3flags)) { + nsec3flags &= ~DNS_NSEC3FLAG_OPTOUT; + } else { + nsec3flags |= DNS_NSEC3FLAG_OPTOUT; + } + break; + + case 'a': + tryverify = true; + break; + + case 'C': + make_keyset = true; + break; + + case 'c': + classname = isc_commandline_argument; + break; + + case 'd': + dsdir = isc_commandline_argument; + if (strlen(dsdir) == 0U) { + fatal("DS directory must be non-empty string"); + } + result = try_dir(dsdir); + if (result != ISC_R_SUCCESS) { + fatal("cannot open directory %s: %s", dsdir, + isc_result_totext(result)); + } + break; + + case 'D': + output_dnssec_only = true; + break; + + case 'E': + engine = isc_commandline_argument; + break; + + case 'e': + endstr = isc_commandline_argument; + break; + + case 'f': + output = isc_commandline_argument; + if (strcmp(output, "-") == 0) { + output_stdout = true; + } + break; + + case 'g': + generateds = true; + break; + + case 'H': + set_iter = true; + /* too-many is NOT DOCUMENTED */ + if (strcmp(isc_commandline_argument, "too-many") == 0) { + nsec3iter = 151; + no_max_check = true; + break; + } + nsec3iter = strtoul(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("iterations must be numeric"); + } + if (nsec3iter > 0xffffU) { + fatal("iterations too big"); + } + break; + + case 'I': + inputformatstr = isc_commandline_argument; + break; + + case 'i': + endp = NULL; + cycle = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0' || cycle < 0) { + fatal("cycle period must be numeric and " + "positive"); + } + break; + + case 'j': + endp = NULL; + jitter = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0' || jitter < 0) { + fatal("jitter must be numeric and positive"); + } + break; + + case 'K': + directory = isc_commandline_argument; + break; + + case 'k': + if (ndskeys == MAXDSKEYS) { + fatal("too many key-signing keys specified"); + } + dskeyfile[ndskeys++] = isc_commandline_argument; + break; + + case 'L': + snset = true; + endp = NULL; + serialnum = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fprintf(stderr, "source serial number " + "must be numeric"); + exit(1); + } + break; + + case 'l': + fatal("-l option (DLV lookaside) is obsolete"); + break; + + case 'M': + endp = NULL; + set_maxttl = true; + maxttl = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fprintf(stderr, "maximum TTL " + "must be numeric"); + exit(1); + } + break; + + case 'm': + break; + + case 'N': + serialformatstr = isc_commandline_argument; + break; + + case 'n': + endp = NULL; + ntasks = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0' || ntasks > INT32_MAX) { + fatal("number of cpus must be numeric"); + } + break; + + case 'O': + outputformatstr = isc_commandline_argument; + break; + + case 'o': + origin = isc_commandline_argument; + break; + + case 'P': + disable_zone_check = true; + break; + + case 'p': + fatal("The -p option has been deprecated.\n"); + break; + + case 'Q': + remove_inactkeysigs = true; + break; + + case 'R': + remove_orphansigs = true; + break; + + case 'r': + fatal("The -r options has been deprecated.\n"); + break; + + case 'S': + smartsign = true; + break; + + case 's': + startstr = isc_commandline_argument; + break; + + case 'T': + endp = NULL; + set_keyttl = true; + keyttl = strtottl(isc_commandline_argument); + break; + + case 't': + printstats = true; + break; + + case 'U': /* Undocumented for testing only. */ + unknownalg = true; + break; + + case 'u': + update_chain = true; + break; + + case 'v': + endp = NULL; + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("verbose level must be numeric"); + } + break; + + case 'q': + quiet = true; + break; + + case 'X': + dnskey_endstr = isc_commandline_argument; + break; + + case 'x': + keyset_kskonly = true; + break; + + case 'z': + ignore_kskflag = true; + break; + + case 'F': + /* Reserved for FIPS mode */ + FALLTHROUGH; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + case 'Z': /* Undocumented test options */ + if (!strcmp(isc_commandline_argument, "nonsecify")) { + nonsecify = true; + } + break; + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + result = dst_lib_init(mctx, engine); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", + isc_result_totext(result)); + } + + isc_stdtime_get(&now); + + if (startstr != NULL) { + starttime = strtotime(startstr, now, now, NULL); + } else { + starttime = now - 3600; /* Allow for some clock skew. */ + } + + if (endstr != NULL) { + endtime = strtotime(endstr, now, starttime, NULL); + } else { + endtime = starttime + (30 * 24 * 60 * 60); + } + + if (dnskey_endstr != NULL) { + dnskey_endtime = strtotime(dnskey_endstr, now, starttime, NULL); + if (endstr != NULL && dnskey_endtime == endtime) { + fprintf(stderr, "WARNING: -e and -X were both set, " + "but have identical values.\n"); + } + } else { + dnskey_endtime = endtime; + } + + if (cycle == -1) { + cycle = (endtime - starttime) / 4; + } + + if (ntasks == 0) { + ntasks = isc_os_ncpus() * 2; + } + vbprintf(4, "using %d cpus\n", ntasks); + + rdclass = strtoclass(classname); + + if (directory == NULL) { + directory = "."; + } + + setup_logging(mctx, &log); + + argc -= isc_commandline_index; + argv += isc_commandline_index; + + if (argc < 1) { + usage(); + } + + file = argv[0]; + + argc -= 1; + argv += 1; + + if (origin == NULL) { + origin = file; + } + + if (output == NULL) { + size_t size; + free_output = true; + size = strlen(file) + strlen(".signed") + 1; + output = isc_mem_allocate(mctx, size); + snprintf(output, size, "%s.signed", file); + } + + if (inputformatstr != NULL) { + if (strcasecmp(inputformatstr, "text") == 0) { + inputformat = dns_masterformat_text; + } else if (strcasecmp(inputformatstr, "map") == 0) { + inputformat = dns_masterformat_map; + } else if (strcasecmp(inputformatstr, "raw") == 0) { + inputformat = dns_masterformat_raw; + } else if (strncasecmp(inputformatstr, "raw=", 4) == 0) { + inputformat = dns_masterformat_raw; + fprintf(stderr, "WARNING: input format version " + "ignored\n"); + } else { + fatal("unknown file format: %s", inputformatstr); + } + } + + if (outputformatstr != NULL) { + if (strcasecmp(outputformatstr, "text") == 0) { + outputformat = dns_masterformat_text; + } else if (strcasecmp(outputformatstr, "full") == 0) { + outputformat = dns_masterformat_text; + masterstyle = &dns_master_style_full; + } else if (strcasecmp(outputformatstr, "map") == 0) { + outputformat = dns_masterformat_map; + } else if (strcasecmp(outputformatstr, "raw") == 0) { + outputformat = dns_masterformat_raw; + } else if (strncasecmp(outputformatstr, "raw=", 4) == 0) { + char *end; + + outputformat = dns_masterformat_raw; + rawversion = strtol(outputformatstr + 4, &end, 10); + if (end == outputformatstr + 4 || *end != '\0' || + rawversion > 1U) + { + fprintf(stderr, "unknown raw format version\n"); + exit(1); + } + } else { + fatal("unknown file format: %s", outputformatstr); + } + } + + if (serialformatstr != NULL) { + if (strcasecmp(serialformatstr, "keep") == 0) { + serialformat = SOA_SERIAL_KEEP; + } else if (strcasecmp(serialformatstr, "increment") == 0 || + strcasecmp(serialformatstr, "incr") == 0) + { + serialformat = SOA_SERIAL_INCREMENT; + } else if (strcasecmp(serialformatstr, "unixtime") == 0) { + serialformat = SOA_SERIAL_UNIXTIME; + } else if (strcasecmp(serialformatstr, "date") == 0) { + serialformat = SOA_SERIAL_DATE; + } else { + fatal("unknown soa serial format: %s", serialformatstr); + } + } + + if (output_dnssec_only && outputformat != dns_masterformat_text) { + fatal("option -D can only be used with \"-O text\""); + } + + if (output_dnssec_only && serialformat != SOA_SERIAL_KEEP) { + fatal("option -D can only be used with \"-N keep\""); + } + + if (output_dnssec_only && set_maxttl) { + fatal("option -D cannot be used with -M"); + } + + result = dns_master_stylecreate(&dsstyle, DNS_STYLEFLAG_NO_TTL, 0, 24, + 0, 0, 0, 8, 0xffffffff, mctx); + check_result(result, "dns_master_stylecreate"); + + gdb = NULL; + TIME_NOW(&timer_start); + loadzone(file, origin, rdclass, &gdb); + gorigin = dns_db_origin(gdb); + gclass = dns_db_class(gdb); + get_soa_ttls(); + + if (set_maxttl && set_keyttl && keyttl > maxttl) { + fprintf(stderr, + "%s: warning: Specified key TTL %u " + "exceeds maximum zone TTL; reducing to %u\n", + program, keyttl, maxttl); + keyttl = maxttl; + } + + if (!set_keyttl) { + keyttl = soa_ttl; + } + + /* + * Check for any existing NSEC3 parameters in the zone, + * and use them as defaults if -u was not specified. + */ + if (update_chain && !set_optout && !set_iter && !set_salt) { + nsec_datatype = dns_rdatatype_nsec; + } else { + set_nsec3params(update_chain, set_salt, set_optout, set_iter); + } + + /* + * We need to do this early on, as we start messing with the list + * of keys rather early. + */ + ISC_LIST_INIT(keylist); + isc_rwlock_init(&keylist_lock, 0, 0); + + /* + * Fill keylist with: + * 1) Keys listed in the DNSKEY set that have + * private keys associated, *if* no keys were + * set on the command line. + * 2) ZSKs set on the command line + * 3) KSKs set on the command line + * 4) Any keys remaining in the DNSKEY set which + * do not have private keys associated and were + * not specified on the command line. + */ + if (argc == 0 || smartsign) { + loadzonekeys(!smartsign, false); + } + loadexplicitkeys(argv, argc, false); + loadexplicitkeys(dskeyfile, ndskeys, true); + loadzonekeys(!smartsign, true); + + /* + * If we're doing smart signing, look in the key repository for + * key files with metadata, and merge them with the keylist + * we have now. + */ + if (smartsign) { + build_final_keylist(); + } + + /* Now enumerate the key list */ + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + key->index = keycount++; + } + + if (keycount == 0) { + if (disable_zone_check) { + fprintf(stderr, + "%s: warning: No keys specified " + "or found\n", + program); + } else { + fatal("No signing keys specified or found."); + } + nokeys = true; + } + + warnifallksk(gdb); + + if (IS_NSEC3) { + bool answer; + + hash_length = dns_nsec3_hashlength(dns_hash_sha1); + hashlist_init(&hashlist, dns_db_nodecount(gdb) * 2, + hash_length); + result = dns_nsec_nseconly(gdb, gversion, &answer); + if (result == ISC_R_NOTFOUND) { + fprintf(stderr, + "%s: warning: NSEC3 generation " + "requested with no DNSKEY; ignoring\n", + program); + } else if (result != ISC_R_SUCCESS) { + check_result(result, "dns_nsec_nseconly"); + } else if (answer) { + fatal("NSEC3 generation requested with " + "NSEC-only DNSKEY"); + } + + if (nsec3iter > dns_nsec3_maxiterations()) { + if (no_max_check) { + fprintf(stderr, + "Ignoring max iterations check.\n"); + } else { + fatal("NSEC3 iterations too big. Maximum " + "iterations allowed %u.", + dns_nsec3_maxiterations()); + } + } + } else { + hashlist_init(&hashlist, 0, 0); /* silence clang */ + } + + gversion = NULL; + result = dns_db_newversion(gdb, &gversion); + check_result(result, "dns_db_newversion()"); + + switch (serialformat) { + case SOA_SERIAL_INCREMENT: + setsoaserial(0, dns_updatemethod_increment); + break; + case SOA_SERIAL_UNIXTIME: + setsoaserial(now, dns_updatemethod_unixtime); + break; + case SOA_SERIAL_DATE: + setsoaserial(now, dns_updatemethod_date); + break; + case SOA_SERIAL_KEEP: + default: + /* do nothing */ + break; + } + + /* Remove duplicates and cap TTLs at maxttl */ + cleanup_zone(); + + if (!nonsecify) { + if (IS_NSEC3) { + nsec3ify(dns_hash_sha1, nsec3iter, gsalt, salt_length, + &hashlist); + } else { + nsecify(); + } + } + + if (!nokeys) { + writeset("dsset-", dns_rdatatype_ds); + if (make_keyset) { + writeset("keyset-", dns_rdatatype_dnskey); + } + } + + if (output_stdout) { + outfp = stdout; + if (outputformatstr == NULL) { + masterstyle = &dns_master_style_full; + } + } else { + tempfilelen = strlen(output) + 20; + tempfile = isc_mem_get(mctx, tempfilelen); + + result = isc_file_mktemplate(output, tempfile, tempfilelen); + check_result(result, "isc_file_mktemplate"); + + if (outputformat == dns_masterformat_text) { + result = isc_file_openunique(tempfile, &outfp); + } else { + result = isc_file_bopenunique(tempfile, &outfp); + } + if (result != ISC_R_SUCCESS) { + fatal("failed to open temporary output file: %s", + isc_result_totext(result)); + } + removefile = true; + setfatalcallback(&removetempfile); + } + + print_time(outfp); + print_version(outfp); + + result = isc_managers_create(mctx, ntasks, 0, &netmgr, &taskmgr); + if (result != ISC_R_SUCCESS) { + fatal("failed to create task manager: %s", + isc_result_totext(result)); + } + + master = NULL; + result = isc_task_create(taskmgr, 0, &master); + if (result != ISC_R_SUCCESS) { + fatal("failed to create task: %s", isc_result_totext(result)); + } + + tasks = isc_mem_get(mctx, ntasks * sizeof(isc_task_t *)); + for (i = 0; i < (int)ntasks; i++) { + tasks[i] = NULL; + result = isc_task_create(taskmgr, 0, &tasks[i]); + if (result != ISC_R_SUCCESS) { + fatal("failed to create task: %s", + isc_result_totext(result)); + } + } + + isc_mutex_init(&namelock); + + if (printstats) { + isc_mutex_init(&statslock); + } + + presign(); + TIME_NOW(&sign_start); + signapex(); + if (!atomic_load(&finished)) { + /* + * There is more work to do. Spread it out over multiple + * processors if possible. + */ + for (i = 0; i < (int)ntasks; i++) { + result = isc_app_onrun(mctx, master, startworker, + tasks[i]); + if (result != ISC_R_SUCCESS) { + fatal("failed to start task: %s", + isc_result_totext(result)); + } + } + (void)isc_app_run(); + if (!atomic_load(&finished)) { + fatal("process aborted by user"); + } + } else { + isc_task_detach(&master); + } + atomic_store(&shuttingdown, true); + for (i = 0; i < (int)ntasks; i++) { + isc_task_detach(&tasks[i]); + } + isc_managers_destroy(&netmgr, &taskmgr); + isc_mem_put(mctx, tasks, ntasks * sizeof(isc_task_t *)); + postsign(); + TIME_NOW(&sign_finish); + + if (disable_zone_check) { + vresult = ISC_R_SUCCESS; + } else { + vresult = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, + NULL, mctx, ignore_kskflag, + keyset_kskonly, report); + if (vresult != ISC_R_SUCCESS) { + fprintf(output_stdout ? stderr : stdout, + "Zone verification failed (%s)\n", + isc_result_totext(vresult)); + } + } + + if (outputformat != dns_masterformat_text) { + dns_masterrawheader_t header; + dns_master_initrawheader(&header); + if (rawversion == 0U) { + header.flags = DNS_MASTERRAW_COMPAT; + } else if (snset) { + header.flags = DNS_MASTERRAW_SOURCESERIALSET; + header.sourceserial = serialnum; + } + result = dns_master_dumptostream(mctx, gdb, gversion, + masterstyle, outputformat, + &header, outfp); + check_result(result, "dns_master_dumptostream3"); + } + + isc_mutex_destroy(&namelock); + if (printstats) { + isc_mutex_destroy(&statslock); + } + + if (!output_stdout) { + result = isc_stdio_close(outfp); + check_result(result, "isc_stdio_close"); + removefile = false; + + if (vresult == ISC_R_SUCCESS) { + result = isc_file_rename(tempfile, output); + if (result != ISC_R_SUCCESS) { + fatal("failed to rename temp file to %s: %s", + output, isc_result_totext(result)); + } + printf("%s\n", output); + } else { + isc_file_remove(tempfile); + } + } + + dns_db_closeversion(gdb, &gversion, false); + dns_db_detach(&gdb); + + hashlist_free(&hashlist); + + while (!ISC_LIST_EMPTY(keylist)) { + key = ISC_LIST_HEAD(keylist); + ISC_LIST_UNLINK(keylist, key, link); + dns_dnsseckey_destroy(mctx, &key); + } + + if (tempfilelen != 0) { + isc_mem_put(mctx, tempfile, tempfilelen); + } + + if (free_output) { + isc_mem_free(mctx, output); + } + + dns_master_styledestroy(&dsstyle, mctx); + + cleanup_logging(&log); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_destroy(&mctx); + + (void)isc_app_finish(); + + if (printstats) { + TIME_NOW(&timer_finish); + print_stats(&timer_start, &timer_finish, &sign_start, + &sign_finish); + } + +#ifdef _WIN32 + DestroySockets(); +#endif /* ifdef _WIN32 */ + return (vresult == ISC_R_SUCCESS ? 0 : 1); +} diff --git a/bin/dnssec/dnssec-signzone.rst b/bin/dnssec/dnssec-signzone.rst new file mode 100644 index 0000000..f5056bb --- /dev/null +++ b/bin/dnssec/dnssec-signzone.rst @@ -0,0 +1,396 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-signzone: + +dnssec-signzone - DNSSEC zone signing tool +------------------------------------------ + +Synopsis +~~~~~~~~ + +:program:`dnssec-signzone` [**-a**] [**-c** class] [**-d** directory] [**-D**] [**-E** engine] [**-e** end-time] [**-f** output-file] [**-g**] [**-h**] [**-i** interval] [**-I** input-format] [**-j** jitter] [**-K** directory] [**-k** key] [**-L** serial] [**-M** maxttl] [**-N** soa-serial-format] [**-o** origin] [**-O** output-format] [**-P**] [**-Q**] [**-q**] [**-R**] [**-S**] [**-s** start-time] [**-T** ttl] [**-t**] [**-u**] [**-v** level] [**-V**] [**-X** extended end-time] [**-x**] [**-z**] [**-3** salt] [**-H** iterations] [**-A**] {zonefile} [key...] + +Description +~~~~~~~~~~~ + +``dnssec-signzone`` signs a zone; it generates NSEC and RRSIG records +and produces a signed version of the zone. The security status of +delegations from the signed zone (that is, whether the child zones are +secure) is determined by the presence or absence of a ``keyset`` +file for each child zone. + +Options +~~~~~~~ + +``-a`` + This option verifies all generated signatures. + +``-c class`` + This option specifies the DNS class of the zone. + +``-C`` + This option sets compatibility mode, in which a ``keyset-zonename`` file is generated in addition + to ``dsset-zonename`` when signing a zone, for use by older versions + of ``dnssec-signzone``. + +``-d directory`` + This option indicates the directory where BIND 9 should look for ``dsset-`` or ``keyset-`` files. + +``-D`` + This option indicates that only those record types automatically managed by + ``dnssec-signzone``, i.e., RRSIG, NSEC, NSEC3 and NSEC3PARAM records, should be included in the output. + If smart signing (``-S``) is used, DNSKEY records are also included. + The resulting file can be included in the original zone file with + ``$INCLUDE``. This option cannot be combined with ``-O raw``, + ``-O map``, or serial-number updating. + +``-E engine`` + This option specifies the hardware to use for cryptographic + operations, such as a secure key store used for signing, when applicable. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). When BIND is + built with native PKCS#11 cryptography (``--enable-native-pkcs11``), it + defaults to the path of the PKCS#11 provider library specified via + ``--with-pkcs11``. + +``-g`` + This option indicates that DS records for child zones should be generated from a ``dsset-`` or ``keyset-`` + file. Existing DS records are removed. + +``-K directory`` + This option specifies the directory to search for DNSSEC keys. If not + specified, it defaults to the current directory. + +``-k key`` + This option tells BIND 9 to treat the specified key as a key-signing key, ignoring any key flags. This + option may be specified multiple times. + +``-M maxttl`` + This option sets the maximum TTL for the signed zone. Any TTL higher than ``maxttl`` + in the input zone is reduced to ``maxttl`` in the output. This + provides certainty as to the largest possible TTL in the signed zone, + which is useful to know when rolling keys. The maxttl is the longest + possible time before signatures that have been retrieved by resolvers + expire from resolver caches. Zones that are signed with this + option should be configured to use a matching ``max-zone-ttl`` in + ``named.conf``. (Note: This option is incompatible with ``-D``, + because it modifies non-DNSSEC data in the output zone.) + +``-s start-time`` + This option specifies the date and time when the generated RRSIG records become + valid. This can be either an absolute or relative time. An absolute + start time is indicated by a number in YYYYMMDDHHMMSS notation; + 20000530144500 denotes 14:45:00 UTC on May 30th, 2000. A relative + start time is indicated by ``+N``, which is N seconds from the current + time. If no ``start-time`` is specified, the current time minus 1 + hour (to allow for clock skew) is used. + +``-e end-time`` + This option specifies the date and time when the generated RRSIG records expire. As + with ``start-time``, an absolute time is indicated in YYYYMMDDHHMMSS + notation. A time relative to the start time is indicated with ``+N``, + which is N seconds from the start time. A time relative to the + current time is indicated with ``now+N``. If no ``end-time`` is + specified, 30 days from the start time is the default. + ``end-time`` must be later than ``start-time``. + +``-X extended end-time`` + This option specifies the date and time when the generated RRSIG records for the + DNSKEY RRset expire. This is to be used in cases when the DNSKEY + signatures need to persist longer than signatures on other records; + e.g., when the private component of the KSK is kept offline and the + KSK signature is to be refreshed manually. + + As with ``end-time``, an absolute time is indicated in + YYYYMMDDHHMMSS notation. A time relative to the start time is + indicated with ``+N``, which is N seconds from the start time. A time + relative to the current time is indicated with ``now+N``. If no + ``extended end-time`` is specified, the value of ``end-time`` is used + as the default. (``end-time``, in turn, defaults to 30 days from the + start time.) ``extended end-time`` must be later than ``start-time``. + +``-f output-file`` + This option indicates the name of the output file containing the signed zone. The default + is to append ``.signed`` to the input filename. If ``output-file`` is + set to ``-``, then the signed zone is written to the standard + output, with a default output format of ``full``. + +``-h`` + This option prints a short summary of the options and arguments to + ``dnssec-signzone``. + +``-V`` + This option prints version information. + +``-i interval`` + This option indicates that, when a previously signed zone is passed as input, records may be + re-signed. The ``interval`` option specifies the cycle interval as an + offset from the current time, in seconds. If a RRSIG record expires + after the cycle interval, it is retained; otherwise, it is considered + to be expiring soon and it is replaced. + + The default cycle interval is one quarter of the difference between + the signature end and start times. So if neither ``end-time`` nor + ``start-time`` is specified, ``dnssec-signzone`` generates + signatures that are valid for 30 days, with a cycle interval of 7.5 + days. Therefore, if any existing RRSIG records are due to expire in + less than 7.5 days, they are replaced. + +``-I input-format`` + This option sets the format of the input zone file. Possible formats are ``text`` + (the default), ``raw``, and ``map``. This option is primarily + intended to be used for dynamic signed zones, so that the dumped zone + file in a non-text format containing updates can be signed directly. + This option is not useful for non-dynamic zones. + +``-j jitter`` + When signing a zone with a fixed signature lifetime, all RRSIG + records issued at the time of signing expire simultaneously. If the + zone is incrementally signed, i.e., a previously signed zone is passed + as input to the signer, all expired signatures must be regenerated + at approximately the same time. The ``jitter`` option specifies a jitter + window that is used to randomize the signature expire time, thus + spreading incremental signature regeneration over time. + + Signature lifetime jitter also, to some extent, benefits validators and + servers by spreading out cache expiration, i.e., if large numbers of + RRSIGs do not expire at the same time from all caches, there is + less congestion than if all validators need to refetch at around the + same time. + +``-L serial`` + When writing a signed zone to "raw" or "map" format, this option sets the "source + serial" value in the header to the specified ``serial`` number. (This is + expected to be used primarily for testing purposes.) + +``-n ncpus`` + This option specifies the number of threads to use. By default, one thread is + started for each detected CPU. + +``-N soa-serial-format`` + This option sets the SOA serial number format of the signed zone. Possible formats are + ``keep`` (the default), ``increment``, ``unixtime``, and + ``date``. + + **keep** + This format indicates that the SOA serial number should not be modified. + + **increment** + This format increments the SOA serial number using :rfc:`1982` arithmetic. + + **unixtime** + This format sets the SOA serial number to the number of seconds + since the beginning of the Unix epoch, unless the serial + number is already greater than or equal to that value, in + which case it is simply incremented by one. + + **date** + This format sets the SOA serial number to today's date, in + YYYYMMDDNN format, unless the serial number is already greater + than or equal to that value, in which case it is simply + incremented by one. + +``-o origin`` + This option sets the zone origin. If not specified, the name of the zone file is + assumed to be the origin. + +``-O output-format`` + This option sets the format of the output file containing the signed zone. Possible + formats are ``text`` (the default), which is the standard textual + representation of the zone; ``full``, which is text output in a + format suitable for processing by external scripts; and ``map``, + ``raw``, and ``raw=N``, which store the zone in binary formats + for rapid loading by ``named``. ``raw=N`` specifies the format + version of the raw zone file: if N is 0, the raw file can be read by + any version of ``named``; if N is 1, the file can be read by release + 9.9.0 or higher. The default is 1. + +``-P`` + This option disables post-sign verification tests. + + The post-sign verification tests ensure that for each algorithm in + use there is at least one non-revoked self-signed KSK key, that all + revoked KSK keys are self-signed, and that all records in the zone + are signed by the algorithm. This option skips these tests. + +``-Q`` + This option removes signatures from keys that are no longer active. + + Normally, when a previously signed zone is passed as input to the + signer, and a DNSKEY record has been removed and replaced with a new + one, signatures from the old key that are still within their validity + period are retained. This allows the zone to continue to validate + with cached copies of the old DNSKEY RRset. The ``-Q`` option forces + ``dnssec-signzone`` to remove signatures from keys that are no longer + active. This enables ZSK rollover using the procedure described in + :rfc:`4641#4.2.1.1` ("Pre-Publish Key Rollover"). + +``-q`` + This option enables quiet mode, which suppresses unnecessary output. Without this option, when + ``dnssec-signzone`` is run it prints three pieces of information to standard output: the number of + keys in use; the algorithms used to verify the zone was signed correctly and + other status information; and the filename containing the signed + zone. With the option that output is suppressed, leaving only the filename. + +``-R`` + This option removes signatures from keys that are no longer published. + + This option is similar to ``-Q``, except it forces + ``dnssec-signzone`` to remove signatures from keys that are no longer + published. This enables ZSK rollover using the procedure described in + :rfc:`4641#4.2.1.2` ("Double Signature Zone Signing Key + Rollover"). + +``-S`` + This option enables smart signing, which instructs ``dnssec-signzone`` to search the key + repository for keys that match the zone being signed, and to include + them in the zone if appropriate. + + When a key is found, its timing metadata is examined to determine how + it should be used, according to the following rules. Each successive + rule takes priority over the prior ones: + + If no timing metadata has been set for the key, the key is + published in the zone and used to sign the zone. + + If the key's publication date is set and is in the past, the key + is published in the zone. + + If the key's activation date is set and is in the past, the key is + published (regardless of publication date) and used to sign the + zone. + + If the key's revocation date is set and is in the past, and the key + is published, then the key is revoked, and the revoked key is used + to sign the zone. + + If either the key's unpublication or deletion date is set and + in the past, the key is NOT published or used to sign the zone, + regardless of any other metadata. + + If the key's sync publication date is set and is in the past, + synchronization records (type CDS and/or CDNSKEY) are created. + + If the key's sync deletion date is set and is in the past, + synchronization records (type CDS and/or CDNSKEY) are removed. + +``-T ttl`` + This option specifies a TTL to be used for new DNSKEY records imported into the + zone from the key repository. If not specified, the default is the + TTL value from the zone's SOA record. This option is ignored when + signing without ``-S``, since DNSKEY records are not imported from + the key repository in that case. It is also ignored if there are any + pre-existing DNSKEY records at the zone apex, in which case new + records' TTL values are set to match them, or if any of the + imported DNSKEY records had a default TTL value. In the event of a + conflict between TTL values in imported keys, the shortest one is + used. + +``-t`` + This option prints statistics at completion. + +``-u`` + This option updates the NSEC/NSEC3 chain when re-signing a previously signed zone. + With this option, a zone signed with NSEC can be switched to NSEC3, + or a zone signed with NSEC3 can be switched to NSEC or to NSEC3 with + different parameters. Without this option, ``dnssec-signzone`` + retains the existing chain when re-signing. + +``-v level`` + This option sets the debugging level. + +``-x`` + This option indicates that BIND 9 should only sign the DNSKEY, CDNSKEY, and CDS RRsets with key-signing keys, + and should omit signatures from zone-signing keys. (This is similar to the + ``dnssec-dnskey-kskonly yes;`` zone option in ``named``.) + +``-z`` + This option indicates that BIND 9 should ignore the KSK flag on keys when determining what to sign. This causes + KSK-flagged keys to sign all records, not just the DNSKEY RRset. + (This is similar to the ``update-check-ksk no;`` zone option in + ``named``.) + +``-3 salt`` + This option generates an NSEC3 chain with the given hex-encoded salt. A dash + (-) can be used to indicate that no salt is to be used when + generating the NSEC3 chain. + + .. note:: + ``-3 -`` is the recommended configuration. Adding salt provides no practical benefits. + +``-H iterations`` + This option indicates that, when generating an NSEC3 chain, BIND 9 should use this many iterations. The default + is 10. + + .. warning:: + Values greater than 0 cause interoperability issues and also increase the risk of CPU-exhausting DoS attacks. The default value has not been changed because the best practices has changed only after BIND 9.16 reached Extended Support Version status. + +``-A`` + This option indicates that, when generating an NSEC3 chain, BIND 9 should set the OPTOUT flag on all NSEC3 + records and should not generate NSEC3 records for insecure delegations. + + .. warning:: + Do not use this option unless all its implications are fully understood. This option is intended only for extremely large zones (comparable to ``com.``) with sparse secure delegations. + + Using this option twice (i.e., ``-AA``) turns the OPTOUT flag off for + all records. This is useful when using the ``-u`` option to modify an + NSEC3 chain which previously had OPTOUT set. + +``zonefile`` + This option sets the file containing the zone to be signed. + +``key`` + This option specifies which keys should be used to sign the zone. If no keys are + specified, the zone is examined for DNSKEY records at the + zone apex. If these records are found and there are matching private keys in + the current directory, they are used for signing. + +Example +~~~~~~~ + +The following command signs the ``example.com`` zone with the +ECDSAP256SHA256 key generated by ``dnssec-keygen`` +(Kexample.com.+013+17247). Because the ``-S`` option is not being used, +the zone's keys must be in the master file (``db.example.com``). This +invocation looks for ``dsset`` files in the current directory, so that +DS records can be imported from them (``-g``). + +:: + + % dnssec-signzone -g -o example.com db.example.com \ + Kexample.com.+013+17247 + db.example.com.signed + % + +In the above example, ``dnssec-signzone`` creates the file +``db.example.com.signed``. This file should be referenced in a zone +statement in the ``named.conf`` file. + +This example re-signs a previously signed zone with default parameters. +The private keys are assumed to be in the current directory. + +:: + + % cp db.example.com.signed db.example.com + % dnssec-signzone -o example.com db.example.com + db.example.com.signed + % + +See Also +~~~~~~~~ + +:manpage:`dnssec-keygen(8)`, BIND 9 Administrator Reference Manual, :rfc:`4033`, +:rfc:`4641`. diff --git a/bin/dnssec/dnssec-verify.c b/bin/dnssec/dnssec-verify.c new file mode 100644 index 0000000..85cc54b --- /dev/null +++ b/bin/dnssec/dnssec-verify.c @@ -0,0 +1,366 @@ +/* + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#if USE_PKCS11 +#include +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-verify"; + +static isc_stdtime_t now; +static isc_mem_t *mctx = NULL; +static dns_masterformat_t inputformat = dns_masterformat_text; +static dns_db_t *gdb; /* The database */ +static dns_dbversion_t *gversion; /* The database version */ +static dns_rdataclass_t gclass; /* The class */ +static dns_name_t *gorigin; /* The database origin */ +static bool ignore_kskflag = false; +static bool keyset_kskonly = false; + +static void +report(const char *format, ...) { + if (!quiet) { + char buf[4096]; + va_list args; + + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + fprintf(stdout, "%s\n", buf); + } +} + +/*% + * Load the zone file from disk + */ +static void +loadzone(char *file, char *origin, dns_rdataclass_t rdclass, dns_db_t **db) { + isc_buffer_t b; + int len; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + + len = strlen(origin); + isc_buffer_init(&b, origin, len); + isc_buffer_add(&b, len); + + name = dns_fixedname_initname(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + fatal("failed converting name '%s' to dns format: %s", origin, + isc_result_totext(result)); + } + + result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0, + NULL, db); + check_result(result, "dns_db_create()"); + + result = dns_db_load(*db, file, inputformat, 0); + switch (result) { + case DNS_R_SEENINCLUDE: + case ISC_R_SUCCESS: + break; + case DNS_R_NOTZONETOP: + /* + * Comparing pointers (vs. using strcmp()) is intentional: we + * want to check whether -o was supplied on the command line, + * not whether origin and file contain the same string. + */ + if (origin == file) { + fatal("failed loading zone '%s' from file '%s': " + "use -o to specify a different zone origin", + origin, file); + } + FALLTHROUGH; + default: + fatal("failed loading zone from '%s': %s", file, + isc_result_totext(result)); + } +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +static void +usage(void) { + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\t%s [options] zonefile [keys]\n", program); + + fprintf(stderr, "\n"); + + fprintf(stderr, "Version: %s\n", VERSION); + + fprintf(stderr, "Options: (default value in parenthesis) \n"); + fprintf(stderr, "\t-v debuglevel (0)\n"); + fprintf(stderr, "\t-q quiet\n"); + fprintf(stderr, "\t-V:\tprint version information\n"); + fprintf(stderr, "\t-o origin:\n"); + fprintf(stderr, "\t\tzone origin (name of zonefile)\n"); + fprintf(stderr, "\t-I format:\n"); + fprintf(stderr, "\t\tfile format of input zonefile (text)\n"); + fprintf(stderr, "\t-c class (IN)\n"); + fprintf(stderr, "\t-E engine:\n"); +#if USE_PKCS11 + fprintf(stderr, + "\t\tpath to PKCS#11 provider library " + "(default is %s)\n", + PK11_LIB_LOCATION); +#else /* if USE_PKCS11 */ + fprintf(stderr, "\t\tname of an OpenSSL engine to use\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, "\t-x:\tDNSKEY record signed with KSKs only, " + "not ZSKs\n"); + fprintf(stderr, "\t-z:\tAll records signed with KSKs\n"); + exit(0); +} + +int +main(int argc, char *argv[]) { + char *origin = NULL, *file = NULL; + char *inputformatstr = NULL; + isc_result_t result; + isc_log_t *log = NULL; + const char *engine = NULL; + char *classname = NULL; + dns_rdataclass_t rdclass; + char *endp; + int ch; + +#define CMDLINE_FLAGS "c:E:hm:o:I:qv:Vxz" + + /* + * Process memory debugging argument first. + */ + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'm': + if (strcasecmp(isc_commandline_argument, "record") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGRECORD; + } + if (strcasecmp(isc_commandline_argument, "trace") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGTRACE; + } + if (strcasecmp(isc_commandline_argument, "usage") == 0) + { + isc_mem_debugging |= ISC_MEM_DEBUGUSAGE; + } + if (strcasecmp(isc_commandline_argument, "size") == 0) { + isc_mem_debugging |= ISC_MEM_DEBUGSIZE; + } + if (strcasecmp(isc_commandline_argument, "mctx") == 0) { + isc_mem_debugging |= ISC_MEM_DEBUGCTX; + } + break; + default: + break; + } + } + isc_commandline_reset = true; + check_result(isc_app_start(), "isc_app_start"); + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case 'c': + classname = isc_commandline_argument; + break; + + case 'E': + engine = isc_commandline_argument; + break; + + case 'I': + inputformatstr = isc_commandline_argument; + break; + + case 'm': + break; + + case 'o': + origin = isc_commandline_argument; + break; + + case 'v': + endp = NULL; + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("verbose level must be numeric"); + } + break; + + case 'q': + quiet = true; + break; + + case 'x': + keyset_kskonly = true; + break; + + case 'z': + ignore_kskflag = true; + break; + + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + result = dst_lib_init(mctx, engine); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", + isc_result_totext(result)); + } + + isc_stdtime_get(&now); + + rdclass = strtoclass(classname); + + setup_logging(mctx, &log); + + argc -= isc_commandline_index; + argv += isc_commandline_index; + + if (argc < 1) { + usage(); + } + + file = argv[0]; + + argc -= 1; + argv += 1; + + POST(argc); + POST(argv); + + if (origin == NULL) { + origin = file; + } + + if (inputformatstr != NULL) { + if (strcasecmp(inputformatstr, "text") == 0) { + inputformat = dns_masterformat_text; + } else if (strcasecmp(inputformatstr, "raw") == 0) { + inputformat = dns_masterformat_raw; + } else { + fatal("unknown file format: %s\n", inputformatstr); + } + } + + gdb = NULL; + report("Loading zone '%s' from file '%s'\n", origin, file); + loadzone(file, origin, rdclass, &gdb); + gorigin = dns_db_origin(gdb); + gclass = dns_db_class(gdb); + + gversion = NULL; + result = dns_db_newversion(gdb, &gversion); + check_result(result, "dns_db_newversion()"); + + result = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, NULL, mctx, + ignore_kskflag, keyset_kskonly, report); + + dns_db_closeversion(gdb, &gversion, false); + dns_db_detach(&gdb); + + cleanup_logging(&log); + dst_lib_destroy(); + if (verbose > 10) { + isc_mem_stats(mctx, stdout); + } + isc_mem_destroy(&mctx); + + (void)isc_app_finish(); + + return (result == ISC_R_SUCCESS ? 0 : 1); +} diff --git a/bin/dnssec/dnssec-verify.rst b/bin/dnssec/dnssec-verify.rst new file mode 100644 index 0000000..4a0c8e7 --- /dev/null +++ b/bin/dnssec/dnssec-verify.rst @@ -0,0 +1,98 @@ +.. 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. + +.. highlight: console + +.. _man_dnssec-verify: + +dnssec-verify - DNSSEC zone verification tool +--------------------------------------------- + +Synopsis +~~~~~~~~ + +:program:`dnssec-verify` [**-c** class] [**-E** engine] [**-I** input-format] [**-o** origin] [**-q**] [**-v** level] [**-V**] [**-x**] [**-z**] {zonefile} + +Description +~~~~~~~~~~~ + +``dnssec-verify`` verifies that a zone is fully signed for each +algorithm found in the DNSKEY RRset for the zone, and that the +NSEC/NSEC3 chains are complete. + +Options +~~~~~~~ + +``-c class`` + This option specifies the DNS class of the zone. + +``-E engine`` + This option specifies the cryptographic hardware to use, when applicable. + + When BIND 9 is built with OpenSSL, this needs to be set to the OpenSSL + engine identifier that drives the cryptographic accelerator or + hardware service module (usually ``pkcs11``). When BIND is + built with native PKCS#11 cryptography (``--enable-native-pkcs11``), it + defaults to the path of the PKCS#11 provider library specified via + ``--with-pkcs11``. + +``-I input-format`` + This option sets the format of the input zone file. Possible formats are ``text`` + (the default) and ``raw``. This option is primarily intended to be used + for dynamic signed zones, so that the dumped zone file in a non-text + format containing updates can be verified independently. + This option is not useful for non-dynamic zones. + +``-o origin`` + This option indicates the zone origin. If not specified, the name of the zone file is + assumed to be the origin. + +``-v level`` + This option sets the debugging level. + +``-V`` + This option prints version information. + +``-q`` + This option sets quiet mode, which suppresses output. Without this option, when ``dnssec-verify`` + is run it prints to standard output the number of keys in use, the + algorithms used to verify the zone was signed correctly, and other status + information. With this option, all non-error output is suppressed, and only the exit + code indicates success. + +``-x`` + This option verifies only that the DNSKEY RRset is signed with key-signing keys. + Without this flag, it is assumed that the DNSKEY RRset is signed + by all active keys. When this flag is set, it is not an error if + the DNSKEY RRset is not signed by zone-signing keys. This corresponds + to the ``-x`` option in ``dnssec-signzone``. + +``-z`` + This option indicates that the KSK flag on the keys should be ignored when determining whether the zone is + correctly signed. Without this flag, it is assumed that there is + a non-revoked, self-signed DNSKEY with the KSK flag set for each + algorithm, and that RRsets other than DNSKEY RRset are signed with + a different DNSKEY without the KSK flag set. + + With this flag set, BIND 9 only requires that for each algorithm, there + be at least one non-revoked, self-signed DNSKEY, regardless of + the KSK flag state, and that other RRsets be signed by a + non-revoked key for the same algorithm that includes the self-signed + key; the same key may be used for both purposes. This corresponds to + the ``-z`` option in ``dnssec-signzone``. + +``zonefile`` + This option indicates the file containing the zone to be signed. + +See Also +~~~~~~~~ + +:manpage:`dnssec-signzone(8)`, BIND 9 Administrator Reference Manual, :rfc:`4033`. diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c new file mode 100644 index 0000000..ce903ca --- /dev/null +++ b/bin/dnssec/dnssectool.c @@ -0,0 +1,594 @@ +/* + * 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 */ + +/*% + * DNSSEC Support Routines. + */ + +#include +#include +#include + +#ifdef _WIN32 +#include +#endif /* ifdef _WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dnssectool.h" + +#define KEYSTATES_NVALUES 4 +static const char *keystates[KEYSTATES_NVALUES] = { + "hidden", + "rumoured", + "omnipresent", + "unretentive", +}; + +int verbose = 0; +bool quiet = false; +uint8_t dtype[8]; + +static fatalcallback_t *fatalcallback = NULL; + +void +fatal(const char *format, ...) { + va_list args; + + fprintf(stderr, "%s: fatal: ", program); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + fprintf(stderr, "\n"); + if (fatalcallback != NULL) { + (*fatalcallback)(); + } + exit(1); +} + +void +setfatalcallback(fatalcallback_t *callback) { + fatalcallback = callback; +} + +void +check_result(isc_result_t result, const char *message) { + if (result != ISC_R_SUCCESS) { + fatal("%s: %s", message, isc_result_totext(result)); + } +} + +void +vbprintf(int level, const char *fmt, ...) { + va_list ap; + if (level > verbose) { + return; + } + va_start(ap, fmt); + fprintf(stderr, "%s: ", program); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +void +version(const char *name) { + fprintf(stderr, "%s %s\n", name, VERSION); + exit(0); +} + +void +sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size) { + char namestr[DNS_NAME_FORMATSIZE]; + char algstr[DNS_NAME_FORMATSIZE]; + + dns_name_format(&sig->signer, namestr, sizeof(namestr)); + dns_secalg_format(sig->algorithm, algstr, sizeof(algstr)); + snprintf(cp, size, "%s/%s/%d", namestr, algstr, sig->keyid); +} + +void +setup_logging(isc_mem_t *mctx, isc_log_t **logp) { + isc_logdestination_t destination; + isc_logconfig_t *logconfig = NULL; + isc_log_t *log = NULL; + int level; + + if (verbose < 0) { + verbose = 0; + } + switch (verbose) { + case 0: + /* + * We want to see warnings about things like out-of-zone + * data in the master file even when not verbose. + */ + level = ISC_LOG_WARNING; + break; + case 1: + level = ISC_LOG_INFO; + break; + default: + level = ISC_LOG_DEBUG(verbose - 2 + 1); + break; + } + + isc_log_create(mctx, &log, &logconfig); + isc_log_setcontext(log); + dns_log_init(log); + dns_log_setcontext(log); + isc_log_settag(logconfig, program); + + /* + * Set up a channel similar to default_stderr except: + * - the logging level is passed in + * - the program name and logging level are printed + * - no time stamp is printed + */ + destination.file.stream = stderr; + destination.file.name = NULL; + destination.file.versions = ISC_LOG_ROLLNEVER; + destination.file.maximum_size = 0; + isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, level, + &destination, + ISC_LOG_PRINTTAG | ISC_LOG_PRINTLEVEL); + + RUNTIME_CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL) == + ISC_R_SUCCESS); + + *logp = log; +} + +void +cleanup_logging(isc_log_t **logp) { + isc_log_t *log; + + REQUIRE(logp != NULL); + + log = *logp; + *logp = NULL; + + if (log == NULL) { + return; + } + + isc_log_destroy(&log); + isc_log_setcontext(NULL); + dns_log_setcontext(NULL); +} + +static isc_stdtime_t +time_units(isc_stdtime_t offset, char *suffix, const char *str) { + switch (suffix[0]) { + case 'Y': + case 'y': + return (offset * (365 * 24 * 3600)); + case 'M': + case 'm': + switch (suffix[1]) { + case 'O': + case 'o': + return (offset * (30 * 24 * 3600)); + case 'I': + case 'i': + return (offset * 60); + case '\0': + fatal("'%s' ambiguous: use 'mi' for minutes " + "or 'mo' for months", + str); + default: + fatal("time value %s is invalid", str); + } + UNREACHABLE(); + break; + case 'W': + case 'w': + return (offset * (7 * 24 * 3600)); + case 'D': + case 'd': + return (offset * (24 * 3600)); + case 'H': + case 'h': + return (offset * 3600); + case 'S': + case 's': + case '\0': + return (offset); + default: + fatal("time value %s is invalid", str); + } + UNREACHABLE(); + return (0); /* silence compiler warning */ +} + +static bool +isnone(const char *str) { + return ((strcasecmp(str, "none") == 0) || + (strcasecmp(str, "never") == 0)); +} + +dns_ttl_t +strtottl(const char *str) { + const char *orig = str; + dns_ttl_t ttl; + char *endp; + + if (isnone(str)) { + return ((dns_ttl_t)0); + } + + ttl = strtol(str, &endp, 0); + if (ttl == 0 && endp == str) { + fatal("TTL must be numeric"); + } + ttl = time_units(ttl, endp, orig); + return (ttl); +} + +dst_key_state_t +strtokeystate(const char *str) { + if (isnone(str)) { + return (DST_KEY_STATE_NA); + } + + for (int i = 0; i < KEYSTATES_NVALUES; i++) { + if (keystates[i] != NULL && strcasecmp(str, keystates[i]) == 0) + { + return ((dst_key_state_t)i); + } + } + fatal("unknown key state %s", str); +} + +isc_stdtime_t +strtotime(const char *str, int64_t now, int64_t base, bool *setp) { + int64_t val, offset; + isc_result_t result; + const char *orig = str; + char *endp; + size_t n; + + if (isnone(str)) { + if (setp != NULL) { + *setp = false; + } + return ((isc_stdtime_t)0); + } + + if (setp != NULL) { + *setp = true; + } + + if ((str[0] == '0' || str[0] == '-') && str[1] == '\0') { + return ((isc_stdtime_t)0); + } + + /* + * We accept times in the following formats: + * now([+-]offset) + * YYYYMMDD([+-]offset) + * YYYYMMDDhhmmss([+-]offset) + * [+-]offset + */ + n = strspn(str, "0123456789"); + if ((n == 8u || n == 14u) && + (str[n] == '\0' || str[n] == '-' || str[n] == '+')) + { + char timestr[15]; + + strlcpy(timestr, str, sizeof(timestr)); + timestr[n] = 0; + if (n == 8u) { + strlcat(timestr, "000000", sizeof(timestr)); + } + result = dns_time64_fromtext(timestr, &val); + if (result != ISC_R_SUCCESS) { + fatal("time value %s is invalid: %s", orig, + isc_result_totext(result)); + } + base = val; + str += n; + } else if (strncmp(str, "now", 3) == 0) { + base = now; + str += 3; + } + + if (str[0] == '\0') { + return ((isc_stdtime_t)base); + } else if (str[0] == '+') { + offset = strtol(str + 1, &endp, 0); + offset = time_units((isc_stdtime_t)offset, endp, orig); + val = base + offset; + } else if (str[0] == '-') { + offset = strtol(str + 1, &endp, 0); + offset = time_units((isc_stdtime_t)offset, endp, orig); + val = base - offset; + } else { + fatal("time value %s is invalid", orig); + } + + return ((isc_stdtime_t)val); +} + +dns_rdataclass_t +strtoclass(const char *str) { + isc_textregion_t r; + dns_rdataclass_t rdclass; + isc_result_t result; + + if (str == NULL) { + return (dns_rdataclass_in); + } + DE_CONST(str, r.base); + r.length = strlen(str); + result = dns_rdataclass_fromtext(&rdclass, &r); + if (result != ISC_R_SUCCESS) { + fatal("unknown class %s", str); + } + return (rdclass); +} + +unsigned int +strtodsdigest(const char *str) { + isc_textregion_t r; + dns_dsdigest_t alg; + isc_result_t result; + + DE_CONST(str, r.base); + r.length = strlen(str); + result = dns_dsdigest_fromtext(&alg, &r); + if (result != ISC_R_SUCCESS) { + fatal("unknown DS algorithm %s", str); + } + return (alg); +} + +static int +cmp_dtype(const void *ap, const void *bp) { + int a = *(const uint8_t *)ap; + int b = *(const uint8_t *)bp; + return (a - b); +} + +void +add_dtype(unsigned int dt) { + unsigned i, n; + + /* ensure there is space for a zero terminator */ + n = sizeof(dtype) / sizeof(dtype[0]) - 1; + for (i = 0; i < n; i++) { + if (dtype[i] == dt) { + return; + } + if (dtype[i] == 0) { + dtype[i] = dt; + qsort(dtype, i + 1, 1, cmp_dtype); + return; + } + } + fatal("too many -a digest type arguments"); +} + +isc_result_t +try_dir(const char *dirname) { + isc_result_t result; + isc_dir_t d; + + isc_dir_init(&d); + result = isc_dir_open(&d, dirname); + if (result == ISC_R_SUCCESS) { + isc_dir_close(&d); + } + return (result); +} + +/* + * Check private key version compatibility. + */ +void +check_keyversion(dst_key_t *key, char *keystr) { + int major, minor; + dst_key_getprivateformat(key, &major, &minor); + INSIST(major <= DST_MAJOR_VERSION); /* invalid private key */ + + if (major < DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) { + fatal("Key %s has incompatible format version %d.%d, " + "use -f to force upgrade to new version.", + keystr, major, minor); + } + if (minor > DST_MINOR_VERSION) { + fatal("Key %s has incompatible format version %d.%d, " + "use -f to force downgrade to current version.", + keystr, major, minor); + } +} + +void +set_keyversion(dst_key_t *key) { + int major, minor; + dst_key_getprivateformat(key, &major, &minor); + INSIST(major <= DST_MAJOR_VERSION); + + if (major != DST_MAJOR_VERSION || minor != DST_MINOR_VERSION) { + dst_key_setprivateformat(key, DST_MAJOR_VERSION, + DST_MINOR_VERSION); + } + + /* + * If the key is from a version older than 1.3, set + * set the creation date + */ + if (major < 1 || (major == 1 && minor <= 2)) { + isc_stdtime_t now; + isc_stdtime_get(&now); + dst_key_settime(key, DST_TIME_CREATED, now); + } +} + +bool +key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir, + isc_mem_t *mctx, bool *exact) { + isc_result_t result; + bool conflict = false; + dns_dnsseckeylist_t matchkeys; + dns_dnsseckey_t *key = NULL; + uint16_t id, oldid; + uint32_t rid, roldid; + dns_secalg_t alg; + char filename[NAME_MAX]; + isc_buffer_t fileb; + isc_stdtime_t now; + + if (exact != NULL) { + *exact = false; + } + + id = dst_key_id(dstkey); + rid = dst_key_rid(dstkey); + alg = dst_key_alg(dstkey); + + /* + * For Diffie Hellman just check if there is a direct collision as + * they can't be revoked. Additionally dns_dnssec_findmatchingkeys + * only handles DNSKEY which is not used for HMAC. + */ + if (alg == DST_ALG_DH) { + isc_buffer_init(&fileb, filename, sizeof(filename)); + result = dst_key_buildfilename(dstkey, DST_TYPE_PRIVATE, dir, + &fileb); + if (result != ISC_R_SUCCESS) { + return (true); + } + return (isc_file_exists(filename)); + } + + ISC_LIST_INIT(matchkeys); + isc_stdtime_get(&now); + result = dns_dnssec_findmatchingkeys(name, dir, now, mctx, &matchkeys); + if (result == ISC_R_NOTFOUND) { + return (false); + } + + while (!ISC_LIST_EMPTY(matchkeys) && !conflict) { + key = ISC_LIST_HEAD(matchkeys); + if (dst_key_alg(key->key) != alg) { + goto next; + } + + oldid = dst_key_id(key->key); + roldid = dst_key_rid(key->key); + + if (oldid == rid || roldid == id || id == oldid) { + conflict = true; + if (id != oldid) { + if (verbose > 1) { + fprintf(stderr, + "Key ID %d could " + "collide with %d\n", + id, oldid); + } + } else { + if (exact != NULL) { + *exact = true; + } + if (verbose > 1) { + fprintf(stderr, "Key ID %d exists\n", + id); + } + } + } + + next: + ISC_LIST_UNLINK(matchkeys, key, link); + dns_dnsseckey_destroy(mctx, &key); + } + + /* Finish freeing the list */ + while (!ISC_LIST_EMPTY(matchkeys)) { + key = ISC_LIST_HEAD(matchkeys); + ISC_LIST_UNLINK(matchkeys, key, link); + dns_dnsseckey_destroy(mctx, &key); + } + + return (conflict); +} + +bool +isoptarg(const char *arg, char **argv, void (*usage)(void)) { + if (!strcasecmp(isc_commandline_argument, arg)) { + if (argv[isc_commandline_index] == NULL) { + fprintf(stderr, "%s: missing argument -%c %s\n", + program, isc_commandline_option, + isc_commandline_argument); + usage(); + } + isc_commandline_argument = argv[isc_commandline_index]; + /* skip to next argument */ + isc_commandline_index++; + return (true); + } + return (false); +} + +#ifdef _WIN32 +void +InitSockets(void) { + WORD wVersionRequested; + WSADATA wsaData; + int err; + + wVersionRequested = MAKEWORD(2, 0); + + err = WSAStartup(wVersionRequested, &wsaData); + if (err != 0) { + fprintf(stderr, "WSAStartup() failed: %d\n", err); + exit(1); + } +} + +void +DestroySockets(void) { + WSACleanup(); +} +#endif /* ifdef _WIN32 */ diff --git a/bin/dnssec/dnssectool.h b/bin/dnssec/dnssectool.h new file mode 100644 index 0000000..4ce8490 --- /dev/null +++ b/bin/dnssec/dnssectool.h @@ -0,0 +1,119 @@ +/* + * 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. + */ + +#ifndef DNSSECTOOL_H +#define DNSSECTOOL_H 1 + +#include +#include + +#include +#include +#include + +#include + +#include + +/*! verbosity: set by -v and -q option in each program, defined in dnssectool.c + */ +extern int verbose; +extern bool quiet; + +/*! program name, statically initialized in each program */ +extern const char *program; + +/*! + * List of DS digest types used by dnssec-cds and dnssec-dsfromkey, + * defined in dnssectool.c. Filled in by add_dtype() from -a + * arguments, sorted (so that DS records are in a canonical order) and + * terminated by a zero. The size of the array is an arbitrary limit + * which should be greater than the number of known digest types. + */ +extern uint8_t dtype[8]; + +typedef void(fatalcallback_t)(void); + +#ifndef CPPCHECK +ISC_PLATFORM_NORETURN_PRE void +fatal(const char *format, ...) + ISC_FORMAT_PRINTF(1, 2) ISC_PLATFORM_NORETURN_POST; +#else /* CPPCHECK */ +#define fatal(...) exit(1) +#endif + +void +setfatalcallback(fatalcallback_t *callback); + +void +check_result(isc_result_t result, const char *message); + +void +vbprintf(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); + +ISC_PLATFORM_NORETURN_PRE void +version(const char *program) ISC_PLATFORM_NORETURN_POST; + +void +sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size); +#define SIG_FORMATSIZE \ + (DNS_NAME_FORMATSIZE + DNS_SECALG_FORMATSIZE + sizeof("65535")) + +void +setup_logging(isc_mem_t *mctx, isc_log_t **logp); + +void +cleanup_logging(isc_log_t **logp); + +dns_ttl_t +strtottl(const char *str); + +dst_key_state_t +strtokeystate(const char *str); + +isc_stdtime_t +strtotime(const char *str, int64_t now, int64_t base, bool *setp); + +dns_rdataclass_t +strtoclass(const char *str); + +unsigned int +strtodsdigest(const char *str); + +void +add_dtype(unsigned int dt); + +isc_result_t +try_dir(const char *dirname); + +void +check_keyversion(dst_key_t *key, char *keystr); + +void +set_keyversion(dst_key_t *key); + +bool +key_collision(dst_key_t *key, dns_name_t *name, const char *dir, + isc_mem_t *mctx, bool *exact); + +bool +isoptarg(const char *arg, char **argv, void (*usage)(void)); + +#ifdef _WIN32 +void +InitSockets(void); +void +DestroySockets(void); +#endif /* ifdef _WIN32 */ + +#endif /* DNSSEC_DNSSECTOOL_H */ diff --git a/bin/dnssec/win32/cds.vcxproj.filters.in b/bin/dnssec/win32/cds.vcxproj.filters.in new file mode 100644 index 0000000..b6893db --- /dev/null +++ b/bin/dnssec/win32/cds.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + diff --git a/bin/dnssec/win32/cds.vcxproj.in b/bin/dnssec/win32/cds.vcxproj.in new file mode 100644 index 0000000..0a543eb --- /dev/null +++ b/bin/dnssec/win32/cds.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {0EB1727E-2BBD-47A6-AD12-418F9DEB0531} + Win32Proj + cds + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + diff --git a/bin/dnssec/win32/cds.vcxproj.user b/bin/dnssec/win32/cds.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/cds.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/dnssectool.vcxproj.filters.in b/bin/dnssec/win32/dnssectool.vcxproj.filters.in new file mode 100644 index 0000000..1743f84 --- /dev/null +++ b/bin/dnssec/win32/dnssectool.vcxproj.filters.in @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/dnssectool.vcxproj.in b/bin/dnssec/win32/dnssectool.vcxproj.in new file mode 100644 index 0000000..be87bca --- /dev/null +++ b/bin/dnssec/win32/dnssectool.vcxproj.in @@ -0,0 +1,118 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + + + + + + + {2CB7DC75-023B-4AA3-AF3A-AE5046A4EE70} + Win32Proj + dnssectool + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + StaticLibrary + true + MultiByte + @PLATFORM_TOOLSET@ + + + StaticLibrary + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + .\$(Configuration)\ + + + .\$(Configuration)\ + None + + + .\$(Configuration)\ + + + .\$(Configuration)\ + None + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(TargetName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\include;..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Windows + true + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(TargetName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\include;..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Windows + true + true + true + false + + + + + + diff --git a/bin/dnssec/win32/dnssectool.vcxproj.user b/bin/dnssec/win32/dnssectool.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/dnssectool.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/dsfromkey.vcxproj.filters.in b/bin/dnssec/win32/dsfromkey.vcxproj.filters.in new file mode 100644 index 0000000..7d0a58e --- /dev/null +++ b/bin/dnssec/win32/dsfromkey.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/dsfromkey.vcxproj.in b/bin/dnssec/win32/dsfromkey.vcxproj.in new file mode 100644 index 0000000..5b2b51d --- /dev/null +++ b/bin/dnssec/win32/dsfromkey.vcxproj.in @@ -0,0 +1,147 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {6E6297F4-69D7-4533-85E1-BD17C30017C8} + Win32Proj + dsfromkey + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + +@IF PYTHON + + cd ..\..\python +copy /Y dnssec-checkds.py ..\..\Build\$(Configuration)\dnssec-checkds.py +copy /Y dnssec-coverage.py ..\..\Build\$(Configuration)\dnssec-coverage.py +copy /Y dnssec-keymgr.py ..\..\Build\$(Configuration)\dnssec-keymgr.py +cd isc +@PYTHON@ policy.py parse \dev\nul > nul +set PYTHONPATH=. +@PYTHON@ -m parsetab + + +@END PYTHON + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + +@IF PYTHON + + cd ..\..\python +copy /Y dnssec-checkds.py ..\..\Build\$(Configuration)\dnssec-checkds.py +copy /Y dnssec-coverage.py ..\..\Build\$(Configuration)\dnssec-coverage.py +copy /Y dnssec-keymgr.py ..\..\Build\$(Configuration)\dnssec-keymgr.py +cd isc +@PYTHON@ policy.py parse \dev\nul > nul +set PYTHONPATH=. +@PYTHON@ -m parsetab + + +@END PYTHON + + + + + + + + diff --git a/bin/dnssec/win32/dsfromkey.vcxproj.user b/bin/dnssec/win32/dsfromkey.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/dsfromkey.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/importkey.vcxproj.filters.in b/bin/dnssec/win32/importkey.vcxproj.filters.in new file mode 100644 index 0000000..0bced36 --- /dev/null +++ b/bin/dnssec/win32/importkey.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + diff --git a/bin/dnssec/win32/importkey.vcxproj.in b/bin/dnssec/win32/importkey.vcxproj.in new file mode 100644 index 0000000..f1d10d0 --- /dev/null +++ b/bin/dnssec/win32/importkey.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {AB6690A0-055E-458f-BAC5-BF38BCC5834F} + Win32Proj + importkey + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + diff --git a/bin/dnssec/win32/importkey.vcxproj.user b/bin/dnssec/win32/importkey.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/importkey.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/keyfromlabel.vcxproj.filters.in b/bin/dnssec/win32/keyfromlabel.vcxproj.filters.in new file mode 100644 index 0000000..bb54f81 --- /dev/null +++ b/bin/dnssec/win32/keyfromlabel.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/keyfromlabel.vcxproj.in b/bin/dnssec/win32/keyfromlabel.vcxproj.in new file mode 100644 index 0000000..496e2b9 --- /dev/null +++ b/bin/dnssec/win32/keyfromlabel.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {17455DC6-5FBB-47C3-8F44-7DB574A188D3} + Win32Proj + keyfromlabel + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + diff --git a/bin/dnssec/win32/keyfromlabel.vcxproj.user b/bin/dnssec/win32/keyfromlabel.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/keyfromlabel.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/keygen.vcxproj.filters.in b/bin/dnssec/win32/keygen.vcxproj.filters.in new file mode 100644 index 0000000..5d1fa4c --- /dev/null +++ b/bin/dnssec/win32/keygen.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/keygen.vcxproj.in b/bin/dnssec/win32/keygen.vcxproj.in new file mode 100644 index 0000000..6051dbf --- /dev/null +++ b/bin/dnssec/win32/keygen.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {0BF11E21-168C-4CAA-B784-429D126BBAE5} + Win32Proj + keygen + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\isccfg\win32;..\..\..\lib\isccfg\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libisccfg.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\isccfg\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\isccfg\win32;..\..\..\lib\isccfg\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libisccfg.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\isccfg\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + + + + + + + + + diff --git a/bin/dnssec/win32/keygen.vcxproj.user b/bin/dnssec/win32/keygen.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/keygen.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/revoke.vcxproj.filters.in b/bin/dnssec/win32/revoke.vcxproj.filters.in new file mode 100644 index 0000000..46e7310 --- /dev/null +++ b/bin/dnssec/win32/revoke.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/revoke.vcxproj.in b/bin/dnssec/win32/revoke.vcxproj.in new file mode 100644 index 0000000..28e6868 --- /dev/null +++ b/bin/dnssec/win32/revoke.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {D171F185-D3C2-4463-9CF3-ED1D0B1D6832} + Win32Proj + revoke + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + diff --git a/bin/dnssec/win32/revoke.vcxproj.user b/bin/dnssec/win32/revoke.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/revoke.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/settime.vcxproj.filters.in b/bin/dnssec/win32/settime.vcxproj.filters.in new file mode 100644 index 0000000..62b0e82 --- /dev/null +++ b/bin/dnssec/win32/settime.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/settime.vcxproj.in b/bin/dnssec/win32/settime.vcxproj.in new file mode 100644 index 0000000..f5a6152 --- /dev/null +++ b/bin/dnssec/win32/settime.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {03FB7588-C5A7-4572-968F-14F1206BC69C} + Win32Proj + settime + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + + + + + + + + + diff --git a/bin/dnssec/win32/settime.vcxproj.user b/bin/dnssec/win32/settime.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/settime.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/signzone.vcxproj.filters.in b/bin/dnssec/win32/signzone.vcxproj.filters.in new file mode 100644 index 0000000..682ae55 --- /dev/null +++ b/bin/dnssec/win32/signzone.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/signzone.vcxproj.in b/bin/dnssec/win32/signzone.vcxproj.in new file mode 100644 index 0000000..2afa4dd --- /dev/null +++ b/bin/dnssec/win32/signzone.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {205ED8A9-2E4C-41CC-9385-F3613402AA90} + Win32Proj + signzone + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + diff --git a/bin/dnssec/win32/signzone.vcxproj.user b/bin/dnssec/win32/signzone.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/signzone.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/bin/dnssec/win32/verify.vcxproj.filters.in b/bin/dnssec/win32/verify.vcxproj.filters.in new file mode 100644 index 0000000..3b194bd --- /dev/null +++ b/bin/dnssec/win32/verify.vcxproj.filters.in @@ -0,0 +1,18 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file diff --git a/bin/dnssec/win32/verify.vcxproj.in b/bin/dnssec/win32/verify.vcxproj.in new file mode 100644 index 0000000..971eccd --- /dev/null +++ b/bin/dnssec/win32/verify.vcxproj.in @@ -0,0 +1,121 @@ + + + + + Debug + @PLATFORM@ + + + Release + @PLATFORM@ + + + + {FD653434-F1A8-44A9-85B2-A7468491DA6D} + Win32Proj + verify + @WINDOWS_TARGET_PLATFORM_VERSION@ + + + + Application + true + MultiByte + @PLATFORM_TOOLSET@ + + + Application + false + true + MultiByte + @PLATFORM_TOOLSET@ + + + + + + + + + + + + + true + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + false + ..\..\..\Build\$(Configuration)\ + .\$(Configuration)\ + None + dnssec-$(ProjectName) + + + + + + Level4 + false + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + true + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + + + + + Level1 + true + + + MaxSpeed + true + @INTRINSIC@ + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + OnlyExplicitInline + false + true + .\$(Configuration)\$(ProjectName).pch + .\$(Configuration)\ + .\$(Configuration)\ + $(OutDir)$(TargetName).pdb + ..\..\..\config.h + .\;..\..\..\;@LIBXML2_INC@@OPENSSL_INC@..\..\..\lib\isc\win32;..\..\..\lib\isc\win32\include;..\..\..\lib\isc\include;..\..\..\lib\dns\include;%(AdditionalIncludeDirectories) + CompileAsC + + + Console + false + true + true + ..\..\..\Build\$(Configuration)\$(TargetName)$(TargetExt) + Default + @OPENSSL_LIBCRYPTO@@OPENSSL_LIBSSL@dnssectool.lib;libisc.lib;libdns.lib;ws2_32.lib;%(AdditionalDependencies) + $(Configuration);..\..\..\lib\isc\win32\$(Configuration);..\..\..\lib\dns\win32\$(Configuration);%(AdditionalLibraryDirectories) + + + + + + + + + diff --git a/bin/dnssec/win32/verify.vcxproj.user b/bin/dnssec/win32/verify.vcxproj.user new file mode 100644 index 0000000..ace9a86 --- /dev/null +++ b/bin/dnssec/win32/verify.vcxproj.user @@ -0,0 +1,3 @@ + + + \ No newline at end of file -- cgit v1.2.3