summaryrefslogtreecommitdiffstats
path: root/bin/dnssec
diff options
context:
space:
mode:
Diffstat (limited to 'bin/dnssec')
-rw-r--r--bin/dnssec/Makefile.am38
-rw-r--r--bin/dnssec/Makefile.in976
-rw-r--r--bin/dnssec/dnssec-cds.c1361
-rw-r--r--bin/dnssec/dnssec-cds.rst221
-rw-r--r--bin/dnssec/dnssec-dsfromkey.c561
-rw-r--r--bin/dnssec/dnssec-dsfromkey.rst159
-rw-r--r--bin/dnssec/dnssec-importkey.c477
-rw-r--r--bin/dnssec/dnssec-importkey.rst142
-rw-r--r--bin/dnssec/dnssec-keyfromlabel.c760
-rw-r--r--bin/dnssec/dnssec-keyfromlabel.rst289
-rw-r--r--bin/dnssec/dnssec-keygen.c1292
-rw-r--r--bin/dnssec/dnssec-keygen.rst357
-rw-r--r--bin/dnssec/dnssec-revoke.c263
-rw-r--r--bin/dnssec/dnssec-revoke.rst78
-rw-r--r--bin/dnssec/dnssec-settime.c967
-rw-r--r--bin/dnssec/dnssec-settime.rst271
-rw-r--r--bin/dnssec/dnssec-signzone.c4165
-rw-r--r--bin/dnssec/dnssec-signzone.rst436
-rw-r--r--bin/dnssec/dnssec-verify.c345
-rw-r--r--bin/dnssec/dnssec-verify.rst107
-rw-r--r--bin/dnssec/dnssectool.c585
-rw-r--r--bin/dnssec/dnssectool.h104
22 files changed, 13954 insertions, 0 deletions
diff --git a/bin/dnssec/Makefile.am b/bin/dnssec/Makefile.am
new file mode 100644
index 0000000..32c4626
--- /dev/null
+++ b/bin/dnssec/Makefile.am
@@ -0,0 +1,38 @@
+include $(top_srcdir)/Makefile.top
+
+AM_CPPFLAGS += \
+ $(LIBISC_CFLAGS) \
+ $(LIBDNS_CFLAGS)
+
+AM_CPPFLAGS += \
+ -DNAMED_CONFFILE=\"${sysconfdir}/named.conf\"
+
+noinst_LTLIBRARIES = libdnssectool.la
+
+LDADD += \
+ libdnssectool.la \
+ $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+
+bin_PROGRAMS = \
+ dnssec-cds \
+ dnssec-dsfromkey \
+ dnssec-importkey \
+ dnssec-keyfromlabel \
+ dnssec-keygen \
+ dnssec-revoke \
+ dnssec-settime \
+ dnssec-signzone \
+ dnssec-verify
+
+libdnssectool_la_SOURCES = \
+ dnssectool.h \
+ dnssectool.c
+
+dnssec_keygen_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISCCFG_CFLAGS)
+
+dnssec_keygen_LDADD = \
+ $(LDADD) \
+ $(LIBISCCFG_LIBS)
diff --git a/bin/dnssec/Makefile.in b/bin/dnssec/Makefile.in
new file mode 100644
index 0000000..47c7352
--- /dev/null
+++ b/bin/dnssec/Makefile.in
@@ -0,0 +1,976 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+# Hey Emacs, this is -*- makefile-automake -*- file!
+# vim: filetype=automake
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+target_triplet = @target@
+@HOST_MACOS_TRUE@am__append_1 = \
+@HOST_MACOS_TRUE@ -Wl,-flat_namespace
+
+bin_PROGRAMS = 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)
+subdir = bin/dnssec
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_check_compile_flag.m4 \
+ $(top_srcdir)/m4/ax_check_link_flag.m4 \
+ $(top_srcdir)/m4/ax_check_openssl.m4 \
+ $(top_srcdir)/m4/ax_gcc_func_attribute.m4 \
+ $(top_srcdir)/m4/ax_jemalloc.m4 \
+ $(top_srcdir)/m4/ax_lib_lmdb.m4 \
+ $(top_srcdir)/m4/ax_perl_module.m4 \
+ $(top_srcdir)/m4/ax_posix_shell.m4 \
+ $(top_srcdir)/m4/ax_prog_cc_for_build.m4 \
+ $(top_srcdir)/m4/ax_pthread.m4 \
+ $(top_srcdir)/m4/ax_python_module.m4 \
+ $(top_srcdir)/m4/ax_restore_flags.m4 \
+ $(top_srcdir)/m4/ax_save_flags.m4 $(top_srcdir)/m4/ax_tls.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+LTLIBRARIES = $(noinst_LTLIBRARIES)
+libdnssectool_la_LIBADD =
+am_libdnssectool_la_OBJECTS = dnssectool.lo
+libdnssectool_la_OBJECTS = $(am_libdnssectool_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+dnssec_cds_SOURCES = dnssec-cds.c
+dnssec_cds_OBJECTS = dnssec-cds.$(OBJEXT)
+dnssec_cds_LDADD = $(LDADD)
+dnssec_cds_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_dsfromkey_SOURCES = dnssec-dsfromkey.c
+dnssec_dsfromkey_OBJECTS = dnssec-dsfromkey.$(OBJEXT)
+dnssec_dsfromkey_LDADD = $(LDADD)
+dnssec_dsfromkey_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_importkey_SOURCES = dnssec-importkey.c
+dnssec_importkey_OBJECTS = dnssec-importkey.$(OBJEXT)
+dnssec_importkey_LDADD = $(LDADD)
+dnssec_importkey_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_keyfromlabel_SOURCES = dnssec-keyfromlabel.c
+dnssec_keyfromlabel_OBJECTS = dnssec-keyfromlabel.$(OBJEXT)
+dnssec_keyfromlabel_LDADD = $(LDADD)
+dnssec_keyfromlabel_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_keygen_SOURCES = dnssec-keygen.c
+dnssec_keygen_OBJECTS = dnssec_keygen-dnssec-keygen.$(OBJEXT)
+dnssec_keygen_DEPENDENCIES = $(LDADD) $(LIBISCCFG_LIBS)
+dnssec_revoke_SOURCES = dnssec-revoke.c
+dnssec_revoke_OBJECTS = dnssec-revoke.$(OBJEXT)
+dnssec_revoke_LDADD = $(LDADD)
+dnssec_revoke_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_settime_SOURCES = dnssec-settime.c
+dnssec_settime_OBJECTS = dnssec-settime.$(OBJEXT)
+dnssec_settime_LDADD = $(LDADD)
+dnssec_settime_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_signzone_SOURCES = dnssec-signzone.c
+dnssec_signzone_OBJECTS = dnssec-signzone.$(OBJEXT)
+dnssec_signzone_LDADD = $(LDADD)
+dnssec_signzone_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+dnssec_verify_SOURCES = dnssec-verify.c
+dnssec_verify_OBJECTS = dnssec-verify.$(OBJEXT)
+dnssec_verify_LDADD = $(LDADD)
+dnssec_verify_DEPENDENCIES = libdnssectool.la $(LIBISC_LIBS) \
+ $(LIBDNS_LIBS)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/dnssec-cds.Po \
+ ./$(DEPDIR)/dnssec-dsfromkey.Po \
+ ./$(DEPDIR)/dnssec-importkey.Po \
+ ./$(DEPDIR)/dnssec-keyfromlabel.Po \
+ ./$(DEPDIR)/dnssec-revoke.Po ./$(DEPDIR)/dnssec-settime.Po \
+ ./$(DEPDIR)/dnssec-signzone.Po ./$(DEPDIR)/dnssec-verify.Po \
+ ./$(DEPDIR)/dnssec_keygen-dnssec-keygen.Po \
+ ./$(DEPDIR)/dnssectool.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libdnssectool_la_SOURCES) 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
+DIST_SOURCES = $(libdnssectool_la_SOURCES) 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
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__extra_recursive_targets = test-recursive unit-recursive \
+ doc-recursive
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/Makefile.top \
+ $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_OBJEXT = @BUILD_OBJEXT@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CC_FOR_BUILD = @CC_FOR_BUILD@
+CFLAGS = @CFLAGS@
+CFLAGS_FOR_BUILD = @CFLAGS_FOR_BUILD@
+CMOCKA_CFLAGS = @CMOCKA_CFLAGS@
+CMOCKA_LIBS = @CMOCKA_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CPPFLAGS_FOR_BUILD = @CPPFLAGS_FOR_BUILD@
+CPP_FOR_BUILD = @CPP_FOR_BUILD@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CURL = @CURL@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DEVELOPER_MODE = @DEVELOPER_MODE@
+DLLTOOL = @DLLTOOL@
+DNSTAP_CFLAGS = @DNSTAP_CFLAGS@
+DNSTAP_LIBS = @DNSTAP_LIBS@
+DOXYGEN = @DOXYGEN@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FSTRM_CAPTURE = @FSTRM_CAPTURE@
+FUZZ_LDFLAGS = @FUZZ_LDFLAGS@
+FUZZ_LOG_COMPILER = @FUZZ_LOG_COMPILER@
+GREP = @GREP@
+GSSAPI_CFLAGS = @GSSAPI_CFLAGS@
+GSSAPI_LIBS = @GSSAPI_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+JEMALLOC_CFLAGS = @JEMALLOC_CFLAGS@
+JEMALLOC_LIBS = @JEMALLOC_LIBS@
+JSON_C_CFLAGS = @JSON_C_CFLAGS@
+JSON_C_LIBS = @JSON_C_LIBS@
+KRB5_CFLAGS = @KRB5_CFLAGS@
+KRB5_CONFIG = @KRB5_CONFIG@
+KRB5_LIBS = @KRB5_LIBS@
+LATEXMK = @LATEXMK@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LDFLAGS_FOR_BUILD = @LDFLAGS_FOR_BUILD@
+LIBCAP_LIBS = @LIBCAP_LIBS@
+LIBIDN2_CFLAGS = @LIBIDN2_CFLAGS@
+LIBIDN2_LIBS = @LIBIDN2_LIBS@
+LIBNGHTTP2_CFLAGS = @LIBNGHTTP2_CFLAGS@
+LIBNGHTTP2_LIBS = @LIBNGHTTP2_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUV_CFLAGS = @LIBUV_CFLAGS@
+LIBUV_LIBS = @LIBUV_LIBS@
+LIBXML2_CFLAGS = @LIBXML2_CFLAGS@
+LIBXML2_LIBS = @LIBXML2_LIBS@
+LIPO = @LIPO@
+LMDB_CFLAGS = @LMDB_CFLAGS@
+LMDB_LIBS = @LMDB_LIBS@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MAXMINDDB_CFLAGS = @MAXMINDDB_CFLAGS@
+MAXMINDDB_LIBS = @MAXMINDDB_LIBS@
+MAXMINDDB_PREFIX = @MAXMINDDB_PREFIX@
+MKDIR_P = @MKDIR_P@
+NC = @NC@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LDFLAGS = @OPENSSL_LDFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL = @PERL@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROTOC_C = @PROTOC_C@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_CXX = @PTHREAD_CXX@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+PYTEST = @PYTEST@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RANLIB = @RANLIB@
+READLINE_CFLAGS = @READLINE_CFLAGS@
+READLINE_LIBS = @READLINE_LIBS@
+RELEASE_DATE = @RELEASE_DATE@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SPHINX_BUILD = @SPHINX_BUILD@
+STD_CFLAGS = @STD_CFLAGS@
+STD_CPPFLAGS = @STD_CPPFLAGS@
+STD_LDFLAGS = @STD_LDFLAGS@
+STRIP = @STRIP@
+TEST_CFLAGS = @TEST_CFLAGS@
+VERSION = @VERSION@
+XELATEX = @XELATEX@
+XSLTPROC = @XSLTPROC@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_CC_FOR_BUILD = @ac_ct_CC_FOR_BUILD@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target = @target@
+target_alias = @target_alias@
+target_cpu = @target_cpu@
+target_os = @target_os@
+target_vendor = @target_vendor@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+ACLOCAL_AMFLAGS = -I $(top_srcdir)/m4
+AM_CFLAGS = \
+ $(STD_CFLAGS)
+
+AM_CPPFLAGS = $(STD_CPPFLAGS) -include $(top_builddir)/config.h \
+ -I$(srcdir)/include $(LIBISC_CFLAGS) $(LIBDNS_CFLAGS) \
+ -DNAMED_CONFFILE=\"${sysconfdir}/named.conf\"
+AM_LDFLAGS = $(STD_LDFLAGS) $(am__append_1)
+LDADD = libdnssectool.la $(LIBISC_LIBS) $(LIBDNS_LIBS)
+LIBISC_CFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/lib/isc/include \
+ -I$(top_builddir)/lib/isc/include
+
+LIBISC_LIBS = $(top_builddir)/lib/isc/libisc.la
+LIBDNS_CFLAGS = \
+ -I$(top_srcdir)/lib/dns/include \
+ -I$(top_builddir)/lib/dns/include
+
+LIBDNS_LIBS = \
+ $(top_builddir)/lib/dns/libdns.la
+
+LIBNS_CFLAGS = \
+ -I$(top_srcdir)/lib/ns/include
+
+LIBNS_LIBS = \
+ $(top_builddir)/lib/ns/libns.la
+
+LIBIRS_CFLAGS = \
+ -I$(top_srcdir)/lib/irs/include
+
+LIBIRS_LIBS = \
+ $(top_builddir)/lib/irs/libirs.la
+
+LIBISCCFG_CFLAGS = \
+ -I$(top_srcdir)/lib/isccfg/include
+
+LIBISCCFG_LIBS = \
+ $(top_builddir)/lib/isccfg/libisccfg.la
+
+LIBISCCC_CFLAGS = \
+ -I$(top_srcdir)/lib/isccc/include/
+
+LIBISCCC_LIBS = \
+ $(top_builddir)/lib/isccc/libisccc.la
+
+LIBBIND9_CFLAGS = \
+ -I$(top_srcdir)/lib/bind9/include
+
+LIBBIND9_LIBS = \
+ $(top_builddir)/lib/bind9/libbind9.la
+
+noinst_LTLIBRARIES = libdnssectool.la
+libdnssectool_la_SOURCES = \
+ dnssectool.h \
+ dnssectool.c
+
+dnssec_keygen_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ $(LIBISCCFG_CFLAGS)
+
+dnssec_keygen_LDADD = \
+ $(LDADD) \
+ $(LIBISCCFG_LIBS)
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(top_srcdir)/Makefile.top $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign bin/dnssec/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign bin/dnssec/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+$(top_srcdir)/Makefile.top $(am__empty):
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libdnssectool.la: $(libdnssectool_la_OBJECTS) $(libdnssectool_la_DEPENDENCIES) $(EXTRA_libdnssectool_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(libdnssectool_la_OBJECTS) $(libdnssectool_la_LIBADD) $(LIBS)
+
+dnssec-cds$(EXEEXT): $(dnssec_cds_OBJECTS) $(dnssec_cds_DEPENDENCIES) $(EXTRA_dnssec_cds_DEPENDENCIES)
+ @rm -f dnssec-cds$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_cds_OBJECTS) $(dnssec_cds_LDADD) $(LIBS)
+
+dnssec-dsfromkey$(EXEEXT): $(dnssec_dsfromkey_OBJECTS) $(dnssec_dsfromkey_DEPENDENCIES) $(EXTRA_dnssec_dsfromkey_DEPENDENCIES)
+ @rm -f dnssec-dsfromkey$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_dsfromkey_OBJECTS) $(dnssec_dsfromkey_LDADD) $(LIBS)
+
+dnssec-importkey$(EXEEXT): $(dnssec_importkey_OBJECTS) $(dnssec_importkey_DEPENDENCIES) $(EXTRA_dnssec_importkey_DEPENDENCIES)
+ @rm -f dnssec-importkey$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_importkey_OBJECTS) $(dnssec_importkey_LDADD) $(LIBS)
+
+dnssec-keyfromlabel$(EXEEXT): $(dnssec_keyfromlabel_OBJECTS) $(dnssec_keyfromlabel_DEPENDENCIES) $(EXTRA_dnssec_keyfromlabel_DEPENDENCIES)
+ @rm -f dnssec-keyfromlabel$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_keyfromlabel_OBJECTS) $(dnssec_keyfromlabel_LDADD) $(LIBS)
+
+dnssec-keygen$(EXEEXT): $(dnssec_keygen_OBJECTS) $(dnssec_keygen_DEPENDENCIES) $(EXTRA_dnssec_keygen_DEPENDENCIES)
+ @rm -f dnssec-keygen$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_keygen_OBJECTS) $(dnssec_keygen_LDADD) $(LIBS)
+
+dnssec-revoke$(EXEEXT): $(dnssec_revoke_OBJECTS) $(dnssec_revoke_DEPENDENCIES) $(EXTRA_dnssec_revoke_DEPENDENCIES)
+ @rm -f dnssec-revoke$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_revoke_OBJECTS) $(dnssec_revoke_LDADD) $(LIBS)
+
+dnssec-settime$(EXEEXT): $(dnssec_settime_OBJECTS) $(dnssec_settime_DEPENDENCIES) $(EXTRA_dnssec_settime_DEPENDENCIES)
+ @rm -f dnssec-settime$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_settime_OBJECTS) $(dnssec_settime_LDADD) $(LIBS)
+
+dnssec-signzone$(EXEEXT): $(dnssec_signzone_OBJECTS) $(dnssec_signzone_DEPENDENCIES) $(EXTRA_dnssec_signzone_DEPENDENCIES)
+ @rm -f dnssec-signzone$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_signzone_OBJECTS) $(dnssec_signzone_LDADD) $(LIBS)
+
+dnssec-verify$(EXEEXT): $(dnssec_verify_OBJECTS) $(dnssec_verify_DEPENDENCIES) $(EXTRA_dnssec_verify_DEPENDENCIES)
+ @rm -f dnssec-verify$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(dnssec_verify_OBJECTS) $(dnssec_verify_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-cds.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-dsfromkey.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-importkey.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-keyfromlabel.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-revoke.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-settime.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-signzone.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec-verify.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssec_keygen-dnssec-keygen.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dnssectool.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+dnssec_keygen-dnssec-keygen.o: dnssec-keygen.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dnssec_keygen_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dnssec_keygen-dnssec-keygen.o -MD -MP -MF $(DEPDIR)/dnssec_keygen-dnssec-keygen.Tpo -c -o dnssec_keygen-dnssec-keygen.o `test -f 'dnssec-keygen.c' || echo '$(srcdir)/'`dnssec-keygen.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dnssec_keygen-dnssec-keygen.Tpo $(DEPDIR)/dnssec_keygen-dnssec-keygen.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnssec-keygen.c' object='dnssec_keygen-dnssec-keygen.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dnssec_keygen_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dnssec_keygen-dnssec-keygen.o `test -f 'dnssec-keygen.c' || echo '$(srcdir)/'`dnssec-keygen.c
+
+dnssec_keygen-dnssec-keygen.obj: dnssec-keygen.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dnssec_keygen_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT dnssec_keygen-dnssec-keygen.obj -MD -MP -MF $(DEPDIR)/dnssec_keygen-dnssec-keygen.Tpo -c -o dnssec_keygen-dnssec-keygen.obj `if test -f 'dnssec-keygen.c'; then $(CYGPATH_W) 'dnssec-keygen.c'; else $(CYGPATH_W) '$(srcdir)/dnssec-keygen.c'; fi`
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/dnssec_keygen-dnssec-keygen.Tpo $(DEPDIR)/dnssec_keygen-dnssec-keygen.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnssec-keygen.c' object='dnssec_keygen-dnssec-keygen.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(dnssec_keygen_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o dnssec_keygen-dnssec-keygen.obj `if test -f 'dnssec-keygen.c'; then $(CYGPATH_W) 'dnssec-keygen.c'; else $(CYGPATH_W) '$(srcdir)/dnssec-keygen.c'; fi`
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+test-local:
+unit-local:
+doc-local:
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/dnssec-cds.Po
+ -rm -f ./$(DEPDIR)/dnssec-dsfromkey.Po
+ -rm -f ./$(DEPDIR)/dnssec-importkey.Po
+ -rm -f ./$(DEPDIR)/dnssec-keyfromlabel.Po
+ -rm -f ./$(DEPDIR)/dnssec-revoke.Po
+ -rm -f ./$(DEPDIR)/dnssec-settime.Po
+ -rm -f ./$(DEPDIR)/dnssec-signzone.Po
+ -rm -f ./$(DEPDIR)/dnssec-verify.Po
+ -rm -f ./$(DEPDIR)/dnssec_keygen-dnssec-keygen.Po
+ -rm -f ./$(DEPDIR)/dnssectool.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+doc: doc-am
+
+doc-am: doc-local
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/dnssec-cds.Po
+ -rm -f ./$(DEPDIR)/dnssec-dsfromkey.Po
+ -rm -f ./$(DEPDIR)/dnssec-importkey.Po
+ -rm -f ./$(DEPDIR)/dnssec-keyfromlabel.Po
+ -rm -f ./$(DEPDIR)/dnssec-revoke.Po
+ -rm -f ./$(DEPDIR)/dnssec-settime.Po
+ -rm -f ./$(DEPDIR)/dnssec-signzone.Po
+ -rm -f ./$(DEPDIR)/dnssec-verify.Po
+ -rm -f ./$(DEPDIR)/dnssec_keygen-dnssec-keygen.Po
+ -rm -f ./$(DEPDIR)/dnssectool.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+test: test-am
+
+test-am: test-local
+
+uninstall-am: uninstall-binPROGRAMS
+
+unit: unit-am
+
+unit-am: unit-local
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libtool \
+ clean-noinstLTLIBRARIES cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir doc-am doc-local dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-ps install-ps-am install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am test-am test-local uninstall uninstall-am \
+ uninstall-binPROGRAMS unit-am unit-local
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/bin/dnssec/dnssec-cds.c b/bin/dnssec/dnssec-cds.c
new file mode 100644
index 0000000..06b1f8c
--- /dev/null
+++ b/bin/dnssec/dnssec-cds.c
@@ -0,0 +1,1361 @@
+/*
+ * 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 <dot@dotat.at> <fanf2@cam.ac.uk>
+ * at Cambridge University Information Services
+ */
+
+/*! \file */
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/serial.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dnssec.h>
+#include <dns/ds.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatatype.h>
+#include <dns/time.h>
+
+#include <dst/dst.h>
+
+#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(isc_buffer_t *buf, dns_rdata_t *ds, dns_dsdigest_t dt,
+ dns_rdata_t *crdata);
+
+static dns_rdataset_t cdnskey_set = DNS_RDATASET_INIT;
+static dns_rdataset_t cdnskey_sig = DNS_RDATASET_INIT;
+static dns_rdataset_t cds_set = DNS_RDATASET_INIT;
+static dns_rdataset_t cds_sig = DNS_RDATASET_INIT;
+static dns_rdataset_t dnskey_set = DNS_RDATASET_INIT;
+static dns_rdataset_t dnskey_sig = DNS_RDATASET_INIT;
+static dns_rdataset_t old_ds_set = DNS_RDATASET_INIT;
+static dns_rdataset_t new_ds_set = DNS_RDATASET_INIT;
+
+static keyinfo_t *old_key_tbl = NULL, *new_key_tbl = NULL;
+
+isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */
+
+static dns_db_t *child_db = NULL;
+static dns_dbnode_t *child_node = NULL;
+static dns_db_t *parent_db = NULL;
+static dns_dbnode_t *parent_node = NULL;
+static dns_db_t *update_db = NULL;
+static dns_dbnode_t *update_node = NULL;
+static dns_dbversion_t *update_version = NULL;
+static bool cleanup_dst = false;
+static bool print_mem_stats = false;
+
+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_dbversion_t **versionp) {
+ if (*dbp != NULL) {
+ if (*nodep != NULL) {
+ dns_db_detachnode(*dbp, nodep);
+ }
+ if (versionp != NULL && *versionp != NULL) {
+ dns_db_closeversion(*dbp, versionp, false);
+ }
+ dns_db_detach(dbp);
+ }
+}
+
+static void
+load_child_sets(const char *file) {
+ load_db(file, &child_db, &child_node);
+ findset(child_db, child_node, dns_rdatatype_dnskey, &dnskey_set,
+ &dnskey_sig);
+ findset(child_db, child_node, dns_rdatatype_cdnskey, &cdnskey_set,
+ &cdnskey_sig);
+ findset(child_db, child_node, dns_rdatatype_cds, &cds_set, &cds_sig);
+ free_db(&child_db, &child_node, NULL);
+}
+
+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;
+ 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, &parent_db, &parent_node);
+ findset(parent_db, parent_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(&parent_db, &parent_node, NULL);
+}
+
+#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);
+ dns_master_styledestroy(&style, mctx);
+
+ if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) {
+ result = ISC_R_NOSPACE;
+ }
+
+ if (result != ISC_R_SUCCESS) {
+ isc_buffer_free(&buf);
+ check_result(result, "dns_rdataset_totext()");
+ }
+
+ isc_buffer_putuint8(buf, 0);
+ 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) {
+ isc_buffer_free(&buf);
+ 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,
+ isc_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, *ki;
+ int i;
+
+ nkey = dns_rdataset_count(keyset);
+
+ keytable = isc_mem_get(mctx, sizeof(keyinfo_t) * nkey);
+
+ for (result = dns_rdataset_first(keyset), i = 0, ki = keytable;
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(keyset), i++, ki++)
+ {
+ dns_rdata_dnskey_t dnskey;
+ dns_rdata_t *keyrdata;
+ isc_region_t r;
+
+ INSIST(i < nkey);
+ 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, isc_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;
+
+ REQUIRE(keytable != NULL);
+
+ for (i = 0, ki = keytable; i < nkey; i++, ki++) {
+ 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;
+
+ REQUIRE(keytbl != NULL);
+
+ 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);
+}
+
+/*
+ * This basically copies the rdata into the buffer, but going via the
+ * unpacked struct lets us change the rdatatype. (The dns_rdata_cds_t
+ * and dns_rdata_ds_t types are aliases.)
+ */
+static isc_result_t
+ds_from_cds(isc_buffer_t *buf, dns_rdata_t *rds, dns_dsdigest_t dt,
+ dns_rdata_t *cds) {
+ isc_result_t result;
+ dns_rdata_ds_t ds;
+
+ REQUIRE(buf != NULL);
+
+ result = dns_rdata_tostruct(cds, &ds, NULL);
+ check_result(result, "dns_rdata_tostruct(CDS)");
+ ds.common.rdtype = dns_rdatatype_ds;
+
+ if (ds.digest_type != dt) {
+ return (ISC_R_IGNORE);
+ }
+
+ return (dns_rdata_fromstruct(rds, rdclass, dns_rdatatype_ds, &ds, buf));
+}
+
+static isc_result_t
+ds_from_cdnskey(isc_buffer_t *buf, dns_rdata_t *ds, dns_dsdigest_t dt,
+ dns_rdata_t *cdnskey) {
+ isc_result_t result;
+ isc_region_t r;
+
+ REQUIRE(buf != NULL);
+
+ isc_buffer_availableregion(buf, &r);
+ if (r.length < DNS_DS_BUFFERSIZE) {
+ return (ISC_R_NOSPACE);
+ }
+
+ result = dns_ds_buildrdata(name, cdnskey, dt, r.base, ds);
+ if (result == ISC_R_SUCCESS) {
+ isc_buffer_add(buf, DNS_DS_BUFFERSIZE);
+ }
+
+ return (result);
+}
+
+static isc_result_t
+append_new_ds_set(ds_maker_func_t *ds_from_rdata, isc_buffer_t *buf,
+ dns_rdatalist_t *dslist, dns_dsdigest_t dt,
+ dns_rdataset_t *crdset) {
+ isc_result_t result;
+
+ for (result = dns_rdataset_first(crdset); result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(crdset))
+ {
+ dns_rdata_t crdata = DNS_RDATA_INIT;
+ dns_rdata_t *ds = NULL;
+
+ dns_rdataset_current(crdset, &crdata);
+
+ ds = isc_mem_get(mctx, sizeof(*ds));
+ dns_rdata_init(ds);
+
+ result = ds_from_rdata(buf, ds, dt, &crdata);
+
+ switch (result) {
+ case ISC_R_SUCCESS:
+ ISC_LIST_APPEND(dslist->rdata, ds, link);
+ break;
+ case ISC_R_IGNORE:
+ isc_mem_put(mctx, ds, sizeof(*ds));
+ continue;
+ case ISC_R_NOSPACE:
+ isc_mem_put(mctx, ds, sizeof(*ds));
+ return (result);
+ default:
+ isc_mem_put(mctx, ds, sizeof(*ds));
+ check_result(result, "ds_from_rdata()");
+ }
+ }
+
+ return (ISC_R_SUCCESS);
+}
+
+static void
+make_new_ds_set(ds_maker_func_t *ds_from_rdata, uint32_t ttl,
+ dns_rdataset_t *crdset) {
+ isc_result_t result;
+ dns_rdatalist_t *dslist;
+ unsigned int size = 16;
+ unsigned i, n;
+
+ for (;;) {
+ 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);
+
+ n = sizeof(dtype) / sizeof(dtype[0]);
+ for (i = 0; i < n && dtype[i] != 0; i++) {
+ result = append_new_ds_set(ds_from_rdata, new_ds_buf,
+ dslist, dtype[i], crdset);
+ if (result != ISC_R_SUCCESS) {
+ break;
+ }
+ }
+ if (result == ISC_R_SUCCESS) {
+ return;
+ }
+
+ vbprintf(2, "doubling DS list buffer size from %u\n", size);
+ freelist(&new_ds_set);
+ isc_buffer_free(&new_ds_buf);
+ size *= 2;
+ }
+}
+
+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_rdataset_t diffset;
+ uint32_t save;
+
+ result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0,
+ NULL, &update_db);
+ check_result(result, "dns_db_create()");
+
+ result = dns_db_newversion(update_db, &update_version);
+ check_result(result, "dns_db_newversion()");
+
+ result = dns_db_findnode(update_db, name, true, &update_node);
+ check_result(result, "dns_db_findnode()");
+
+ dns_rdataset_init(&diffset);
+
+ result = dns_db_addrdataset(update_db, update_node, update_version, 0,
+ addset, DNS_DBADD_MERGE, NULL);
+ check_result(result, "dns_db_addrdataset()");
+
+ result = dns_db_subtractrdataset(update_db, update_node, update_version,
+ 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);
+ }
+
+ free_db(&update_db, &update_node, &update_version);
+}
+
+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));
+ }
+}
+
+noreturn static void
+usage(void);
+
+static void
+usage(void) {
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr,
+ " %s options [options] -f <file> -d <path> <domain>\n",
+ program);
+ fprintf(stderr, "Version: %s\n", PACKAGE_VERSION);
+ fprintf(stderr, "Options:\n"
+ " -a <algorithm> digest algorithm (SHA-1 / "
+ "SHA-256 / SHA-384)\n"
+ " -c <class> of domain (default IN)\n"
+ " -D prefer CDNSKEY records instead "
+ "of CDS\n"
+ " -d <file|dir> where to find parent dsset- "
+ "file\n"
+ " -f <file> child DNSKEY+CDNSKEY+CDS+RRSIG "
+ "records\n"
+ " -i[extension] update dsset- file in place\n"
+ " -s <start-time> oldest permitted child "
+ "signatures\n"
+ " -u emit nsupdate script\n"
+ " -T <ttl> TTL of DS records\n"
+ " -V print version\n"
+ " -v <verbosity>\n");
+ exit(1);
+}
+
+static void
+cleanup(void) {
+ free_db(&child_db, &child_node, NULL);
+ free_db(&parent_db, &parent_node, NULL);
+ free_db(&update_db, &update_node, &update_version);
+ if (old_key_tbl != NULL) {
+ free_keytable(&old_key_tbl);
+ }
+ if (new_key_tbl != NULL) {
+ free_keytable(&new_key_tbl);
+ }
+ free_all_sets();
+ if (lctx != NULL) {
+ cleanup_logging(&lctx);
+ }
+ if (cleanup_dst) {
+ dst_lib_destroy();
+ }
+ if (mctx != NULL) {
+ if (print_mem_stats && verbose > 10) {
+ isc_mem_stats(mctx, stdout);
+ }
+ isc_mem_destroy(&mctx);
+ }
+}
+
+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;
+
+ setfatalcallback(cleanup);
+
+ isc_mem_create(&mctx);
+
+ 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));
+ }
+ cleanup_dst = true;
+
+ 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);
+ goto cleanup;
+ }
+
+ /*
+ * 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);
+ }
+
+ /*
+ * Try to use CDNSKEY records if the CDS records are missing
+ * or did not match.
+ */
+ if (dns_rdataset_count(&new_ds_set) == 0 &&
+ dns_rdataset_isassociated(&cdnskey_set))
+ {
+ vbprintf(1, "CDS records have no allowed digest types; "
+ "using CDNSKEY instead\n");
+ freelist(&new_ds_set);
+ isc_buffer_free(&new_ds_buf);
+ make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
+ }
+ if (dns_rdataset_count(&new_ds_set) == 0) {
+ fatal("CDS records at %s do not match any -a digest types",
+ namestr);
+ }
+
+ /*
+ * 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);
+
+cleanup:
+ print_mem_stats = true;
+ cleanup();
+ exit(0);
+}
diff --git a/bin/dnssec/dnssec-cds.rst b/bin/dnssec/dnssec-cds.rst
new file mode 100644
index 0000000..09960e9
--- /dev/null
+++ b/bin/dnssec/dnssec-cds.rst
@@ -0,0 +1,221 @@
+.. 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
+
+.. iscman:: dnssec-cds
+.. program:: dnssec-cds
+.. _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 :program:`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 :program:`dnssec-cds`, the
+parent can keep the DS records up-to-date and enable automatic rolling
+of KSKs.
+
+Two input files are required. The :option:`-f child-file <-f>` 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 :option:`-d path <-d>` option
+specifies the location of a file containing the current DS records. For
+example, this could be a ``dsset-`` file generated by
+:iscman:`dnssec-signzone`, or the output of :iscman:`dnssec-dsfromkey`, or the
+output of a previous run of :program:`dnssec-cds`.
+
+The :program:`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
+:program:`dnssec-cds`. Their age is obtained from the modification time of the
+``dsset-`` file, or from the :option:`-s` option.
+
+To protect against breaking the delegation, :program:`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 :option:`-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 :program:`dnssec-cds` fails!
+
+Alternatively, :option`dnssec-cds -u` writes an :iscman:`nsupdate` script to the
+standard output. The :option:`-u` and :option:`-i` options can be used together to
+maintain a ``dsset-`` file as well as emit an :iscman:`nsupdate` script.
+
+Options
+~~~~~~~
+
+.. option:: -a algorithm
+
+ When converting CDS records to DS records, this option specifies
+ the acceptable digest algorithms. This option can be repeated, so
+ that multiple digest types are allowed. If none of the CDS records
+ use an acceptable digest type, :program:`dnssec-cds` will try to use CDNSKEY
+ records instead; if there are no CDNSKEY records, it reports an error.
+
+ When converting CDNSKEY records to DS records, this option specifies the
+ digest algorithm to use. It can be repeated, so that multiple DS records
+ are created for each CDNSKEY 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 only.
+
+.. option:: -c class
+
+ This option specifies the DNS class of the zones.
+
+.. option:: -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.
+
+.. option:: -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, :program:`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 :option:`-s` option.
+
+.. 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.
+
+.. option:: -i extension
+
+ This option updates the ``dsset-`` file in place, instead of writing DS records to
+ the standard output.
+
+ There must be no space between the :option:`-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.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -u
+
+ This option writes an :iscman:`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 :option:`-T` option, or using the
+ :iscman:`nsupdate` ``ttl`` command.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -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 :program:`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 :iscman:`dnssec-signzone`, ensure that the delegations
+are up-to-date by running :program:`dnssec-cds` on every ``dsset-`` file.
+
+To fetch the child records required by :program:`dnssec-cds`, invoke
+:iscman:`dig` as in the script below. It is acceptable if the :iscman:`dig` fails, since
+:program:`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 :iscman:`named`,
+:program:`dnssec-cds` can be used with :iscman:`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
+~~~~~~~~
+
+:iscman:`dig(1) <dig>`, :iscman:`dnssec-settime(8) <dnssec-settime>`, :iscman:`dnssec-signzone(8) <dnssec-signzone>`, :iscman:`nsupdate(1) <nsupdate>`, 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..42aa9e5
--- /dev/null
+++ b/bin/dnssec/dnssec-dsfromkey.c
@@ -0,0 +1,561 @@
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/dir.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/ds.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatatype.h>
+
+#include <dst/dst.h>
+
+#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_copy(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);
+ }
+ }
+}
+
+noreturn static void
+usage(void);
+
+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", PACKAGE_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-<dnsname> 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);
+
+ 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..9ca025a
--- /dev/null
+++ b/bin/dnssec/dnssec-dsfromkey.rst
@@ -0,0 +1,159 @@
+.. 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
+
+.. iscman:: dnssec-dsfromkey
+.. program:: dnssec-dsfromkey
+.. _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 :program:`dnssec-dsfromkey` command outputs DS (Delegation Signer) resource records
+(RRs), or CDS (Child DS) RRs with the :option:`-C` option.
+
+By default, only KSKs are converted (keys with flags = 257). The
+:option:`-A` option includes ZSKs (flags = 256). Revoked keys are never
+included.
+
+The input keys can be specified in a number of ways:
+
+By default, :program:`dnssec-dsfromkey` reads a key file named in the format
+``Knnnn.+aaa+iiiii.key``, as generated by :iscman:`dnssec-keygen`.
+
+With the :option:`-f file <-f>` option, :program:`dnssec-dsfromkey` reads keys from a zone
+file or partial zone file (which can contain just the DNSKEY records).
+
+With the :option:`-s` option, :program:`dnssec-dsfromkey` reads a ``keyset-`` file,
+as generated by :iscman:`dnssec-keygen` :option:`-C`.
+
+Options
+~~~~~~~
+
+.. option:: -1
+
+ This option is an abbreviation for :option:`-a SHA1 <-a>`.
+
+.. option:: -2
+
+ This option is an abbreviation for :option:`-a SHA-256 <-a>`.
+
+.. option:: -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.
+
+.. option:: -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 :option:`-f` zone file mode.
+
+.. option:: -c class
+
+ This option specifies the DNS class; the default is IN. This option is only useful in :option:`-s` keyset
+ or :option:`-f` zone file mode.
+
+.. option:: -C
+
+ This option generates CDS records rather than DS records.
+
+.. option:: -f file
+
+ This option sets zone file mode, in which the final dnsname argument of :program:`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 :iscman:`dig`
+ command as input, as in:
+
+ ``dig dnskey example.com | dnssec-dsfromkey -f - example.com``
+
+.. option:: -h
+
+ This option prints usage information.
+
+.. option:: -K directory
+
+ This option tells BIND 9 to look for key files or ``keyset-`` files in ``directory``.
+
+.. option:: -s
+
+ This option enables keyset mode, in which the final dnsname argument from :program:`dnssec-dsfromkey` is the DNS
+ domain name used to locate a ``keyset-`` file.
+
+.. option:: -T TTL
+
+ This option specifies the TTL of the DS records. By default the TTL is omitted.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -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 :iscman:`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
+~~~~~~~~
+
+:iscman:`dnssec-keygen(8) <dnssec-keygen>`, :iscman:`dnssec-signzone(8) <dnssec-signzone>`, 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..441f7c3
--- /dev/null
+++ b/bin/dnssec/dnssec-importkey.c
@@ -0,0 +1,477 @@
+/*
+ * 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 <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/callbacks.h>
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/ds.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatatype.h>
+
+#include <dst/dst.h>
+
+#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_copy(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);
+}
+
+noreturn static void
+usage(void);
+
+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", PACKAGE_VERSION);
+ fprintf(stderr, "Options:\n");
+ fprintf(stderr, " -f file: read key from zone file\n");
+ fprintf(stderr, " -K <directory>: directory in which to store "
+ "the key files\n");
+ fprintf(stderr, " -L ttl: set default key TTL\n");
+ fprintf(stderr, " -v <verbose level>\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);
+
+ 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..8f6a6b3
--- /dev/null
+++ b/bin/dnssec/dnssec-importkey.rst
@@ -0,0 +1,142 @@
+.. 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
+
+.. iscman:: dnssec-importkey
+.. program:: dnssec-importkey
+.. _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
+~~~~~~~~~~~
+
+:program:`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 (:option:`-P`) and deletion (:option:`-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
+~~~~~~~
+
+.. option:: -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.
+
+.. option:: -K directory
+
+ This option sets the directory in which the key files are to reside.
+
+.. option:: -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.
+
+.. option:: -h
+
+ This option emits a usage message and exits.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -V
+
+ This option prints version information.
+
+Timing Options
+~~~~~~~~~~~~~~
+
+Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS.
+(which is the format used inside key files),
+or 'Day Mon DD HH:MM:SS YYYY' (as printed by ``dnssec-settime -p``),
+or UNIX epoch time (as printed by ``dnssec-settime -up``),
+or the literal ``now``.
+
+The argument can be followed by ``+`` or ``-`` and an offset from the
+given time. The literal ``now`` can be omitted before an offset. The
+offset can be followed by one of the suffixes ``y``, ``mo``, ``w``,
+``d``, ``h``, or ``mi``, so that it 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``, ``never``,
+or ``unset``.
+
+All these formats are case-insensitive.
+
+.. option:: -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.
+
+ .. program:: dnssec-importkey -P
+ .. option:: 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.
+
+.. program:: dnssec-importkey
+
+.. option:: -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.)
+
+ .. program:: dnssec-importkey -D
+ .. option:: sync date/offset
+
+ This option sets the date on which the CDS and CDNSKEY records that match this
+ key are to be deleted.
+
+.. program:: dnssec-importkey
+
+
+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
+:iscman:`dnssec-keygen`.
+
+See Also
+~~~~~~~~
+
+:iscman:`dnssec-keygen(8) <dnssec-keygen>`, :iscman:`dnssec-signzone(8) <dnssec-signzone>`, 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..53ca718
--- /dev/null
+++ b/bin/dnssec/dnssec-keyfromlabel.c
@@ -0,0 +1,760 @@
+/*
+ * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
+ *
+ * SPDX-License-Identifier: MPL-2.0
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at https://mozilla.org/MPL/2.0/.
+ *
+ * See the COPYRIGHT file distributed with this work for additional
+ * information regarding copyright ownership.
+ */
+
+/*! \file */
+
+#include <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/secalg.h>
+
+#include <dst/dst.h>
+
+#include "dnssectool.h"
+
+#define MAX_RSA 4096 /* should be long enough... */
+
+const char *program = "dnssec-keyfromlabel";
+
+noreturn static void
+usage(void);
+
+static void
+usage(void) {
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " %s -l label [options] name\n\n", program);
+ fprintf(stderr, "Version: %s\n", PACKAGE_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 <engine>:\n");
+ fprintf(stderr, " name of an OpenSSL engine to use\n");
+ 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 <key>: generate a successor to an existing "
+ "key\n");
+ fprintf(stderr, " -i <interval>: prepublication interval for "
+ "successor key "
+ "(default: 30 days)\n");
+ fprintf(stderr, "Output:\n");
+ fprintf(stderr, " K<name>+<alg>+<id>.key, "
+ "K<name>+<alg>+<id>.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);
+
+ 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, engine,
+ 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..098feb9
--- /dev/null
+++ b/bin/dnssec/dnssec-keyfromlabel.rst
@@ -0,0 +1,289 @@
+.. 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
+
+.. iscman:: dnssec-keyfromlabel
+.. program:: dnssec-keyfromlabel
+.. _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
+~~~~~~~~~~~
+
+:program:`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 :iscman:`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
+~~~~~~~
+
+.. option:: -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 :option:`-3`
+ option, then NSEC3RSASHA1 is used instead.
+
+ This option is mandatory except when using the
+ :option:`-S` option, which copies the algorithm from the predecessory key.
+
+ .. versionchanged:: 9.12.0
+ The default value RSASHA1 for newly generated keys was removed.
+
+.. option:: -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.
+
+.. option:: -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``).
+
+.. option:: -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``.
+
+.. option:: -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.
+
+.. option:: -C
+
+ This option enables compatibility mode, which generates an old-style key, without any metadata.
+ By default, :program:`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 :option:`-C` option suppresses them.
+
+.. option:: -c class
+
+ This option indicates that the DNS record containing the key should have the
+ specified class. If not specified, class IN is used.
+
+.. option:: -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.
+
+.. option:: -G
+
+ This option generates a key, but does not publish it or sign with it. This option is
+ incompatible with :option:`-P` and :option:`-A`.
+
+.. option:: -h
+
+ This option prints a short summary of the options and arguments to
+ :program:`dnssec-keyfromlabel`.
+
+.. option:: -K directory
+
+ This option sets the directory in which the key files are to be written.
+
+.. option:: -k
+
+ This option generates KEY records rather than DNSKEY records.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -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
+(which is the format used inside key files),
+or 'Day Mon DD HH:MM:SS YYYY' (as printed by ``dnssec-settime -p``),
+or UNIX epoch time (as printed by ``dnssec-settime -up``),
+or the literal ``now``.
+
+The argument can be followed by ``+`` or ``-`` and an offset from the
+given time. The literal ``now`` can be omitted before an offset. The
+offset can be followed by one of the suffixes ``y``, ``mo``, ``w``,
+``d``, ``h``, or ``mi``, so that it 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``, ``never``,
+or ``unset``.
+
+All these formats are case-insensitive.
+
+.. option:: -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 :option:`-G` option has not been used, the
+ default is the current date.
+
+ .. program:: dnssec-keyfromlabel -P
+ .. option:: 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.
+
+.. program:: dnssec-keyfromlabel
+
+.. option:: -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 :option:`-G` option has not been used, the default is the current date.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -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.)
+
+ .. program:: dnssec-keyfromlabel -D
+ .. option:: sync date/offset
+
+ This option sets the date on which the CDS and CDNSKEY records that match this
+ key are to be deleted.
+
+.. program:: dnssec-keyfromlabel
+
+.. option:: -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 :program:`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).
+
+:program:`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
+~~~~~~~~
+
+:iscman:`dnssec-keygen(8) <dnssec-keygen>`, :iscman:`dnssec-signzone(8) <dnssec-signzone>`, 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..3d77976
--- /dev/null
+++ b/bin/dnssec/dnssec-keygen.c
@@ -0,0 +1,1292 @@
+/*
+ * 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 <ctype.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/region.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/kasp.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/secalg.h>
+
+#include <dst/dst.h>
+
+#include <isccfg/cfg.h>
+#include <isccfg/grammar.h>
+#include <isccfg/kaspconf.h>
+#include <isccfg/namedconf.h>
+
+#include "dnssectool.h"
+
+#define MAX_RSA 4096 /* should be long enough... */
+
+const char *program = "dnssec-keygen";
+
+isc_log_t *lctx = NULL;
+
+noreturn static void
+usage(void);
+
+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", PACKAGE_VERSION);
+ fprintf(stderr, " name: owner of the key\n");
+ fprintf(stderr, "Options:\n");
+ fprintf(stderr, " -K <directory>: write keys into directory\n");
+ fprintf(stderr, " -k <policy>: generate keys for dnssec-policy\n");
+ fprintf(stderr, " -l <file>: configuration file with dnssec-policy "
+ "statement\n");
+ fprintf(stderr, " -a <algorithm>:\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 <key size in bits>:\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 <nametype>: ZONE | HOST | ENTITY | "
+ "USER | OTHER\n");
+ fprintf(stderr, " (DNSKEY generation defaults to ZONE)\n");
+ fprintf(stderr, " -c <class>: (default: IN)\n");
+ fprintf(stderr, " -d <digest bits> (0 => max, default)\n");
+ fprintf(stderr, " -E <engine>:\n");
+ fprintf(stderr, " name of an OpenSSL engine to use\n");
+ fprintf(stderr, " -f <keyflag>: KSK | REVOKE\n");
+ fprintf(stderr, " -g <generator>: use specified generator "
+ "(DH only)\n");
+ fprintf(stderr, " -L <ttl>: default key TTL\n");
+ fprintf(stderr, " -p <protocol>: (default: 3 [dnssec])\n");
+ fprintf(stderr, " -s <strength>: strength value this key signs DNS "
+ "records with (default: 0)\n");
+ fprintf(stderr, " -T <rrtype>: DNSKEY | KEY (default: DNSKEY; "
+ "use KEY for SIG(0))\n");
+ fprintf(stderr, " -t <type>: "
+ "AUTHCONF | NOAUTHCONF | NOAUTH | NOCONF "
+ "(default: AUTHCONF)\n");
+ fprintf(stderr, " -h: print usage and exit\n");
+ fprintf(stderr, " -m <memory debugging mode>:\n");
+ fprintf(stderr, " usage | trace | record | size | mctx\n");
+ fprintf(stderr, " -v <level>: 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 <key>: generate a successor to an existing "
+ "key\n");
+ fprintf(stderr, " -i <interval>: prepublication interval for "
+ "successor key "
+ "(default: 30 days)\n");
+ fprintf(stderr, "Output:\n");
+ fprintf(stderr, " K<name>+<alg>+<id>.key, "
+ "K<name>+<alg>+<id>.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();
+ }
+
+ 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;
+ }
+ 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..a06027c
--- /dev/null
+++ b/bin/dnssec/dnssec-keygen.rst
@@ -0,0 +1,357 @@
+.. 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
+
+.. iscman:: dnssec-keygen
+.. program:: dnssec-keygen
+.. _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
+~~~~~~~~~~~
+
+:program:`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.
+
+Options
+~~~~~~~
+
+.. option:: -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.
+
+.. option:: -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 :option:`-T KEY <-T>` 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 :option:`-3`
+ option, NSEC3RSASHA1 is used instead.
+
+ This parameter *must* be specified except when using the :option:`-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
+ :iscman:`tsig-keygen` to generate TSIG keys.
+
+.. option:: -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 :option:`-f KSK <-f>`) default to 2048 bits.
+
+.. option:: -C
+
+ This option enables compatibility mode, which generates an old-style key, without any timing
+ metadata. By default, :program:`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 :option:`-C` option suppresses them.
+
+.. option:: -c class
+
+ This option indicates that the DNS record containing the key should have the
+ specified class. If not specified, class IN is used.
+
+.. option:: -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.
+
+.. option:: -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``).
+
+.. option:: -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.
+
+.. option:: -G
+
+ This option generates a key, but does not publish it or sign with it. This option is
+ incompatible with :option:`-P` and :option:`-A`.
+
+.. option:: -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.
+
+.. option:: -h
+
+ This option prints a short summary of the options and arguments to
+ :program:`dnssec-keygen`.
+
+.. option:: -K directory
+
+ This option sets the directory in which the key files are to be written.
+
+.. option:: -k policy
+
+ This option creates keys for a specific ``dnssec-policy``. If a policy uses multiple keys,
+ :program:`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
+ :program:`dnssec-keygen` provides.
+
+.. option:: -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.
+
+.. option:: -l file
+
+ This option provides a configuration file that contains a ``dnssec-policy`` statement
+ (matching the policy set with :option:`-k`).
+
+.. option:: -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.
+
+.. option:: -p protocol
+
+ This option sets the protocol value for the generated key, for use with
+ :option:`-T KEY <-T>`. 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.
+
+.. option:: -q
+
+ This option sets quiet mode, which suppresses unnecessary output, including progress
+ indication. Without this option, when :program:`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.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -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).
+
+.. option:: -t type
+
+ This option indicates the type of the key for use with :option:`-T KEY <-T>`. ``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.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+Timing Options
+~~~~~~~~~~~~~~
+
+Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS
+(which is the format used inside key files),
+or 'Day Mon DD HH:MM:SS YYYY' (as printed by ``dnssec-settime -p``),
+or UNIX epoch time (as printed by ``dnssec-settime -up``),
+or the literal ``now``.
+
+The argument can be followed by ``+`` or ``-`` and an offset from the
+given time. The literal ``now`` can be omitted before an offset. The
+offset can be followed by one of the suffixes ``y``, ``mo``, ``w``,
+``d``, ``h``, or ``mi``, so that it 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 unset a date, use ``none``, ``never``, or ``unset``.
+
+.. option:: -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 :option:`-G` option has not been used, the
+ default is the current date.
+
+ .. program:: dnssec-keygen -P
+ .. option:: 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.
+
+.. program:: dnssec-keygen
+
+.. option:: -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 :option:`-G` option has not been used, the default is the current date. If set,
+ and :option:`-P` is not set, the publication date is set to the
+ activation date minus the prepublication interval.
+
+.. option:: -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.
+
+.. option:: -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.
+
+
+.. option:: -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.)
+
+ .. program:: dnssec-keygen -D
+ .. option:: sync date/offset
+
+ This option sets the date on which the CDS and CDNSKEY records that match this
+ key are to be deleted.
+
+.. program:: dnssec-keygen
+
+.. option:: -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 :program:`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).
+
+:program:`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 :iscman:`named` or :option:`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, :program:`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
+~~~~~~~~
+
+:iscman:`dnssec-signzone(8) <dnssec-signzone>`, 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..93ee20c
--- /dev/null
+++ b/bin/dnssec/dnssec-revoke.c
@@ -0,0 +1,263 @@
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+
+#include <dst/dst.h>
+
+#include "dnssectool.h"
+
+const char *program = "dnssec-revoke";
+
+static isc_mem_t *mctx = NULL;
+
+noreturn static void
+usage(void);
+
+static void
+usage(void) {
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " %s [options] keyfile\n\n", program);
+ fprintf(stderr, "Version: %s\n", PACKAGE_VERSION);
+ fprintf(stderr, " -E engine: specify OpenSSL engine\n");
+ 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<name>+<alg>+<new id>.key, "
+ "K<name>+<alg>+<new id>.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);
+
+ 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..052865f
--- /dev/null
+++ b/bin/dnssec/dnssec-revoke.rst
@@ -0,0 +1,78 @@
+.. 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
+
+.. iscman:: dnssec-revoke
+.. program:: dnssec-revoke
+.. _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
+~~~~~~~~~~~
+
+:program:`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
+~~~~~~~
+
+.. option:: -h
+
+ This option emits a usage message and exits.
+
+.. option:: -K directory
+
+ This option sets the directory in which the key files are to reside.
+
+.. option:: -r
+
+ This option indicates to remove the original keyset files after writing the new keyset files.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -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``).
+
+.. option:: -f
+
+ This option indicates a forced overwrite and causes :program:`dnssec-revoke` to write the new key pair,
+ even if a file already exists matching the algorithm and key ID of
+ the revoked key.
+
+.. option:: -R
+
+ This option prints the key tag of the key with the REVOKE bit set, but does not
+ revoke the key.
+
+See Also
+~~~~~~~~
+
+:iscman:`dnssec-keygen(8) <dnssec-keygen>`, 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..100ff88
--- /dev/null
+++ b/bin/dnssec/dnssec-settime.c
@@ -0,0 +1,967 @@
+/*
+ * 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 <errno.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/attributes.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+
+#include <dst/dst.h>
+
+#include "dnssectool.h"
+
+const char *program = "dnssec-settime";
+
+static isc_mem_t *mctx = NULL;
+
+noreturn static void
+usage(void);
+
+static void
+usage(void) {
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, " %s [options] keyfile\n\n", program);
+ fprintf(stderr, "Version: %s\n", PACKAGE_VERSION);
+ fprintf(stderr, "General options:\n");
+ fprintf(stderr, " -E engine: specify OpenSSL engine\n");
+ 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 <key>: generate a successor to an existing "
+ "key\n");
+ fprintf(stderr, " -i <interval>: 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<name>+<alg>+<new id>.key, "
+ "K<name>+<alg>+<new id>.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);
+
+ 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..5cb4ea8
--- /dev/null
+++ b/bin/dnssec/dnssec-settime.rst
@@ -0,0 +1,271 @@
+.. 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
+
+.. iscman:: dnssec-settime
+.. program:: dnssec-settime
+.. _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
+~~~~~~~~~~~
+
+:program:`dnssec-settime` reads a DNSSEC private key file and sets the key
+timing metadata as specified by the :option:`-P`, :option:`-A`, :option:`-R`,
+:option:`-I`, and :option:`-D` options. The metadata can then be used by
+:iscman:`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,
+:program:`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 :option:`-s`. With this option, it is also possible
+to update key states with :option:`-d` (DS), :option:`-k` (DNSKEY), :option:`-r`
+(RRSIG of KSK), or :option:`-z` (RRSIG of ZSK). Allowed states are HIDDEN,
+RUMOURED, OMNIPRESENT, and UNRETENTIVE.
+
+The goal state of the key can also be set with :option:`-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
+~~~~~~~
+
+.. option:: -f
+
+ This option forces an update of an old-format key with no metadata fields. Without
+ this option, :program:`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.
+
+.. option:: -K directory
+
+ This option sets the directory in which the key files are to reside.
+
+.. option:: -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.
+
+.. option:: -h
+
+ This option emits a usage message and exits.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -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``).
+
+Timing Options
+~~~~~~~~~~~~~~
+
+Dates can be expressed in the format YYYYMMDD or YYYYMMDDHHMMSS
+(which is the format used inside key files),
+or 'Day Mon DD HH:MM:SS YYYY' (as printed by ``dnssec-settime -p``),
+or UNIX epoch time (as printed by ``dnssec-settime -up``),
+or the literal ``now``.
+
+The argument can be followed by ``+`` or ``-`` and an offset from the
+given time. The literal ``now`` can be omitted before an offset. The
+offset can be followed by one of the suffixes ``y``, ``mo``, ``w``,
+``d``, ``h``, or ``mi``, so that it 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 unset a date, use ``none``, ``never``, or ``unset``.
+
+All these formats are case-insensitive.
+
+.. option:: -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.
+
+ .. program:: dnssec-settime -P
+ .. option:: ds date/offset
+
+ This option sets the date on which DS records that match this key have been
+ seen in the parent zone.
+
+ .. option:: 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.
+
+.. program:: dnssec-settime
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -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.)
+
+ .. program:: dnssec-settime -D
+ .. option:: 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.
+
+ .. option:: sync date/offset
+
+ This option sets the date on which the CDS and CDNSKEY records that match this
+ key are to be deleted.
+
+.. program:: dnssec-settime
+
+.. option:: -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.
+
+.. option:: -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.
+
+.. option:: -s
+
+ This option indicates that when setting key timing data, the state file should also be updated.
+
+.. option:: -g state
+
+ This option sets the goal state for this key. Must be HIDDEN or OMNIPRESENT.
+
+.. option:: -d state date/offset
+
+ This option sets the DS state for this key as of the specified date, offset from the current date.
+
+.. option:: -k state date/offset
+
+ This option sets the DNSKEY state for this key as of the specified date, offset from the current date.
+
+.. option:: -r state date/offset
+
+ This option sets the RRSIG (KSK) state for this key as of the specified date, offset from the current date.
+
+.. option:: -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
+~~~~~~~~~~~~~~~~
+
+:program:`dnssec-settime` can also be used to print the timing metadata
+associated with a key.
+
+.. option:: -u
+
+ This option indicates that times should be printed in Unix epoch format.
+
+.. option:: -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 :option:`-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
+~~~~~~~~
+
+:iscman:`dnssec-keygen(8) <dnssec-keygen>`, :iscman:`dnssec-signzone(8) <dnssec-signzone>`, 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..f52457c
--- /dev/null
+++ b/bin/dnssec/dnssec-signzone.c
@@ -0,0 +1,4165 @@
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <isc/app.h>
+#include <isc/atomic.h>
+#include <isc/attributes.h>
+#include <isc/base32.h>
+#include <isc/commandline.h>
+#include <isc/dir.h>
+#include <isc/event.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/hex.h>
+#include <isc/managers.h>
+#include <isc/md.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/safe.h>
+#include <isc/serial.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/task.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/diff.h>
+#include <dns/dnssec.h>
+#include <dns/ds.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/soa.h>
+#include <dns/time.h>
+#include <dns/update.h>
+#include <dns/zoneverify.h>
+
+#include <dst/dst.h>
+
+#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 = 0U;
+static unsigned char saltbuf[255];
+static unsigned char *gsalt = saltbuf;
+static size_t salt_length = 0;
+static isc_task_t *main_task = 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_copy(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);
+ default:
+ break;
+ }
+ 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 main 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(main_task, 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 &lt;hash&gt;.&lt;origin&gt;?
+ */
+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_copy(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 <name,nextname> 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_copy(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, &currentversion);
+
+ 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, &currentversion, 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, &currentversion);
+
+ 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, &currentversion, 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(&currenttime, &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 %s\n", PACKAGE_VERSION);
+}
+
+noreturn static void
+usage(void);
+
+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", PACKAGE_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");
+ fprintf(stderr, "\t\tname of an OpenSSL engine to use\n");
+ 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;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ isc_commandline_reset = true;
+
+ masterstyle = &dns_master_style_explicitttl;
+
+ check_result(isc_app_start(), "isc_app_start");
+
+ isc_mem_create(&mctx);
+
+ 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, "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, "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, dns_dbtree_main) * 2,
+ hash_length);
+ result = dns_nsec_nseconly(gdb, gversion, NULL, &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);
+
+ isc_managers_create(mctx, ntasks, 0, &netmgr, &taskmgr, NULL);
+
+ main_task = NULL;
+ result = isc_task_create(taskmgr, 0, &main_task);
+ 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, main_task, 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(&main_task);
+ }
+ atomic_store(&shuttingdown, true);
+ for (i = 0; i < (int)ntasks; i++) {
+ isc_task_detach(&tasks[i]);
+ }
+ isc_managers_destroy(&netmgr, &taskmgr, NULL);
+ 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);
+ }
+
+ 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..668d7f3
--- /dev/null
+++ b/bin/dnssec/dnssec-signzone.rst
@@ -0,0 +1,436 @@
+.. 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
+
+.. iscman:: dnssec-signzone
+.. program:: dnssec-signzone
+.. _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
+~~~~~~~~~~~
+
+:program:`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
+~~~~~~~
+
+.. option:: -a
+
+ This option verifies all generated signatures.
+
+.. option:: -c class
+
+ This option specifies the DNS class of the zone.
+
+.. option:: -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 :program:`dnssec-signzone`.
+
+.. option:: -d directory
+
+ This option indicates the directory where BIND 9 should look for ``dsset-`` or ``keyset-`` files.
+
+.. option:: -D
+
+ This option indicates that only those record types automatically managed by
+ :program:`dnssec-signzone`, i.e., RRSIG, NSEC, NSEC3 and NSEC3PARAM records, should be included in the output.
+ If smart signing (:option:`-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 :option:`-O raw <-O>`
+ or serial-number updating.
+
+.. option:: -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``).
+
+.. option:: -g
+
+ This option indicates that DS records for child zones should be generated from a ``dsset-`` or ``keyset-``
+ file. Existing DS records are removed.
+
+.. option:: -K directory
+
+ This option specifies the directory to search for DNSSEC keys. If not
+ specified, it defaults to the current directory.
+
+.. option:: -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.
+
+.. option:: -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
+ :iscman:`named.conf`. (Note: This option is incompatible with :option:`-D`,
+ because it modifies non-DNSSEC data in the output zone.)
+
+.. option:: -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.
+
+.. option:: -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``.
+
+.. option:: -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``.
+
+.. option:: -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``.
+
+.. option:: -h
+
+ This option prints a short summary of the options and arguments to
+ :program:`dnssec-signzone`.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -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, :program:`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.
+
+.. option:: -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 signed directly.
+ This option is not useful for non-dynamic zones.
+
+.. option:: -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.
+
+.. option:: -L serial
+
+ When writing a signed zone to "raw" 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.)
+
+.. option:: -n ncpus
+
+ This option specifies the number of threads to use. By default, one thread is
+ started for each detected CPU.
+
+.. option:: -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.
+
+.. option:: -o origin
+
+ This option sets the zone origin. If not specified, the name of the zone file is
+ assumed to be the origin.
+
+.. option:: -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 ``raw`` and
+ ``raw=N``, which store the zone in binary formats for rapid loading by
+ :iscman:`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 :iscman:`named`; if N is
+ 1, the file can be read by release 9.9.0 or higher. The default is 1.
+
+.. option:: -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.
+
+.. option:: -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 :option:`-Q` option forces
+ :program:`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").
+
+.. option:: -q
+
+ This option enables quiet mode, which suppresses unnecessary output. Without this option, when
+ :program:`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.
+
+.. option:: -R
+
+ This option removes signatures from keys that are no longer published.
+
+ This option is similar to :option:`-Q`, except it forces
+ :program:`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").
+
+.. option:: -S
+
+ This option enables smart signing, which instructs :program:`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.
+
+.. option:: -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 :option:`-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.
+
+.. option:: -t
+
+ This option prints statistics at completion.
+
+.. option:: -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, :program:`dnssec-signzone`
+ retains the existing chain when re-signing.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -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 :iscman:`named`.)
+
+.. option:: -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
+ :iscman:`named`.)
+
+.. option:: -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.
+
+.. option:: -H iterations
+
+ This option indicates that, when generating an NSEC3 chain, BIND 9 should use this many iterations. The default
+ is 0.
+
+ .. warning::
+ Values greater than 0 cause interoperability issues and also increase the risk of CPU-exhausting DoS attacks.
+
+.. option:: -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.
+
+.. option:: -AA
+
+ This option turns the OPTOUT flag off for
+ all records. This is useful when using the :option:`-u` option to modify an
+ NSEC3 chain which previously had OPTOUT set.
+
+.. option:: zonefile
+
+ This option sets the file containing the zone to be signed.
+
+.. option:: 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 :iscman:`dnssec-keygen`
+(Kexample.com.+013+17247). Because the :option:`-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 (:option:`-g`).
+
+::
+
+ % dnssec-signzone -g -o example.com db.example.com \
+ Kexample.com.+013+17247
+ db.example.com.signed
+ %
+
+In the above example, :program:`dnssec-signzone` creates the file
+``db.example.com.signed``. This file should be referenced in a zone
+statement in the :iscman:`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
+~~~~~~~~
+
+:iscman:`dnssec-keygen(8) <dnssec-keygen>`, 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..5f2f2d1
--- /dev/null
+++ b/bin/dnssec/dnssec-verify.c
@@ -0,0 +1,345 @@
+/*
+ * 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 <stdbool.h>
+#include <stdlib.h>
+#include <time.h>
+
+#include <isc/app.h>
+#include <isc/attributes.h>
+#include <isc/base32.h>
+#include <isc/commandline.h>
+#include <isc/event.h>
+#include <isc/file.h>
+#include <isc/hash.h>
+#include <isc/hex.h>
+#include <isc/mem.h>
+#include <isc/mutex.h>
+#include <isc/os.h>
+#include <isc/print.h>
+#include <isc/random.h>
+#include <isc/result.h>
+#include <isc/rwlock.h>
+#include <isc/serial.h>
+#include <isc/stdio.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/diff.h>
+#include <dns/dnssec.h>
+#include <dns/ds.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/master.h>
+#include <dns/masterdump.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/rdatalist.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/soa.h>
+#include <dns/time.h>
+#include <dns/zoneverify.h>
+
+#include <dst/dst.h>
+
+#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));
+ }
+}
+
+noreturn static void
+usage(void);
+
+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", PACKAGE_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");
+ fprintf(stderr, "\t\tname of an OpenSSL engine to use\n");
+ 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;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ isc_commandline_reset = true;
+ check_result(isc_app_start(), "isc_app_start");
+
+ isc_mem_create(&mctx);
+
+ 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..8700894
--- /dev/null
+++ b/bin/dnssec/dnssec-verify.rst
@@ -0,0 +1,107 @@
+.. 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
+
+.. iscman:: dnssec-verify
+.. program:: dnssec-verify
+.. _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
+~~~~~~~~~~~
+
+:program:`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
+~~~~~~~
+
+.. option:: -c class
+
+ This option specifies the DNS class of the zone.
+
+.. option:: -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``).
+
+.. option:: -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.
+
+.. option:: -o origin
+
+ This option indicates the zone origin. If not specified, the name of the zone file is
+ assumed to be the origin.
+
+.. option:: -v level
+
+ This option sets the debugging level.
+
+.. option:: -V
+
+ This option prints version information.
+
+.. option:: -q
+
+ This option sets quiet mode, which suppresses output. Without this option, when :program:`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.
+
+.. option:: -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 :option:`-x option in dnssec-signzone <dnssec-signzone -x>`.
+
+.. option:: -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 :option:`-z option in dnssec-signzone <dnssec-signzone -z>`.
+
+.. option:: zonefile
+
+ This option indicates the file containing the zone to be signed.
+
+See Also
+~~~~~~~~
+
+:iscman:`dnssec-signzone(8) <dnssec-signzone>`, 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..c07e091
--- /dev/null
+++ b/bin/dnssec/dnssectool.c
@@ -0,0 +1,585 @@
+/*
+ * 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 <inttypes.h>
+#include <stdbool.h>
+#include <stdlib.h>
+
+#include <isc/base32.h>
+#include <isc/buffer.h>
+#include <isc/commandline.h>
+#include <isc/dir.h>
+#include <isc/file.h>
+#include <isc/heap.h>
+#include <isc/list.h>
+#include <isc/mem.h>
+#include <isc/print.h>
+#include <isc/result.h>
+#include <isc/string.h>
+#include <isc/time.h>
+#include <isc/tm.h>
+#include <isc/util.h>
+
+#include <dns/db.h>
+#include <dns/dbiterator.h>
+#include <dns/dnssec.h>
+#include <dns/fixedname.h>
+#include <dns/keyvalues.h>
+#include <dns/log.h>
+#include <dns/name.h>
+#include <dns/nsec.h>
+#include <dns/nsec3.h>
+#include <dns/rdataclass.h>
+#include <dns/rdataset.h>
+#include <dns/rdatasetiter.h>
+#include <dns/rdatastruct.h>
+#include <dns/rdatatype.h>
+#include <dns/secalg.h>
+#include <dns/time.h>
+
+#include "dnssectool.h"
+
+#define KEYSTATES_NVALUES 4
+static const char *keystates[KEYSTATES_NVALUES] = {
+ "hidden",
+ "rumoured",
+ "omnipresent",
+ "unretentive",
+};
+
+int verbose = 0;
+bool quiet = false;
+dns_dsdigest_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, PACKAGE_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) ||
+ (strcasecmp(str, "unset") == 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;
+ struct tm tm;
+
+ 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)
+ * Day Mon DD HH:MM:SS YYYY([+-]offset)
+ * 1234567890([+-]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 (n == 10u &&
+ (str[n] == '\0' || str[n] == '-' || str[n] == '+'))
+ {
+ base = strtoll(str, &endp, 0);
+ str += 10;
+ } else if (strncmp(str, "now", 3) == 0) {
+ base = now;
+ str += 3;
+ } else if (str[0] >= 'A' && str[0] <= 'Z') {
+ /* parse ctime() format as written by `dnssec-settime -p` */
+ endp = isc_tm_strptime(str, "%a %b %d %H:%M:%S %Y", &tm);
+ if (endp != str + 24) {
+ fatal("time value %s is invalid", orig);
+ }
+ base = mktime(&tm);
+ str += 24;
+ }
+
+ 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);
+}
diff --git a/bin/dnssec/dnssectool.h b/bin/dnssec/dnssectool.h
new file mode 100644
index 0000000..f38bc55
--- /dev/null
+++ b/bin/dnssec/dnssectool.h
@@ -0,0 +1,104 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <inttypes.h>
+#include <stdbool.h>
+
+#include <isc/attributes.h>
+#include <isc/log.h>
+#include <isc/stdtime.h>
+
+#include <dns/rdatastruct.h>
+
+#include <dst/dst.h>
+
+/*! 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);
+
+noreturn void
+fatal(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
+
+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);
+
+noreturn void
+version(const char *program);
+
+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));