diff options
Diffstat (limited to '')
-rw-r--r-- | bin/dnssec/Makefile.am | 38 | ||||
-rw-r--r-- | bin/dnssec/Makefile.in | 976 | ||||
-rw-r--r-- | bin/dnssec/dnssec-cds.c | 1361 | ||||
-rw-r--r-- | bin/dnssec/dnssec-cds.rst | 221 | ||||
-rw-r--r-- | bin/dnssec/dnssec-dsfromkey.c | 561 | ||||
-rw-r--r-- | bin/dnssec/dnssec-dsfromkey.rst | 159 | ||||
-rw-r--r-- | bin/dnssec/dnssec-importkey.c | 477 | ||||
-rw-r--r-- | bin/dnssec/dnssec-importkey.rst | 142 | ||||
-rw-r--r-- | bin/dnssec/dnssec-keyfromlabel.c | 760 | ||||
-rw-r--r-- | bin/dnssec/dnssec-keyfromlabel.rst | 289 | ||||
-rw-r--r-- | bin/dnssec/dnssec-keygen.c | 1292 | ||||
-rw-r--r-- | bin/dnssec/dnssec-keygen.rst | 357 | ||||
-rw-r--r-- | bin/dnssec/dnssec-revoke.c | 263 | ||||
-rw-r--r-- | bin/dnssec/dnssec-revoke.rst | 78 | ||||
-rw-r--r-- | bin/dnssec/dnssec-settime.c | 967 | ||||
-rw-r--r-- | bin/dnssec/dnssec-settime.rst | 271 | ||||
-rw-r--r-- | bin/dnssec/dnssec-signzone.c | 4165 | ||||
-rw-r--r-- | bin/dnssec/dnssec-signzone.rst | 436 | ||||
-rw-r--r-- | bin/dnssec/dnssec-verify.c | 345 | ||||
-rw-r--r-- | bin/dnssec/dnssec-verify.rst | 107 | ||||
-rw-r--r-- | bin/dnssec/dnssectool.c | 585 | ||||
-rw-r--r-- | bin/dnssec/dnssectool.h | 104 |
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 <hash>.<origin>? + */ +static void +nsec3clean(dns_name_t *name, dns_dbnode_t *node, unsigned int hashalg, + unsigned int iterations, const unsigned char *salt, size_t salt_len, + hashlist_t *hashlist) { + dns_label_t label; + dns_rdata_nsec3_t nsec3; + dns_rdata_t rdata, delrdata; + dns_rdatalist_t rdatalist; + dns_rdataset_t rdataset, delrdataset; + bool delete_rrsigs = false; + isc_buffer_t target; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1]; + bool exists; + + /* + * Get the first label. + */ + dns_name_getlabel(name, 0, &label); + + /* + * We want just the label contents. + */ + isc_region_consume(&label, 1); + + /* + * Decode base32hex string. + */ + isc_buffer_init(&target, hash, sizeof(hash) - 1); + result = isc_base32hex_decoderegion(&label, &target); + if (result != ISC_R_SUCCESS) { + return; + } + + hash[isc_buffer_usedlength(&target)] = 0; + + exists = hashlist_exists(hashlist, hash); + + /* + * Verify that the NSEC3 parameters match the current ones + * otherwise we are dealing with a different NSEC3 chain. + */ + dns_rdataset_init(&rdataset); + dns_rdataset_init(&delrdataset); + + result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_nsec3, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + return; + } + + /* + * Delete any NSEC3 records which are not part of the current + * NSEC3 chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + check_result(result, "dns_rdata_tostruct"); + if (exists && nsec3.hash == hashalg && + nsec3.iterations == iterations && + nsec3.salt_length == salt_len && + isc_safe_memequal(nsec3.salt, salt, salt_len)) + { + continue; + } + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + if (set_maxttl) { + rdatalist.ttl = ISC_MIN(rdataset.ttl, maxttl); + } + dns_rdata_init(&delrdata); + dns_rdata_clone(&rdata, &delrdata); + ISC_LIST_APPEND(rdatalist.rdata, &delrdata, link); + result = dns_rdatalist_tordataset(&rdatalist, &delrdataset); + check_result(result, "dns_rdatalist_tordataset()"); + result = dns_db_subtractrdataset(gdb, node, gversion, + &delrdataset, 0, NULL); + dns_rdataset_disassociate(&delrdataset); + if (result != ISC_R_SUCCESS && result != DNS_R_NXRRSET) { + check_result(result, "dns_db_subtractrdataset(NSEC3)"); + } + delete_rrsigs = true; + } + dns_rdataset_disassociate(&rdataset); + if (result != ISC_R_NOMORE) { + check_result(result, "dns_rdataset_first/next"); + } + + if (!delete_rrsigs) { + return; + } + /* + * Delete the NSEC3 RRSIGs + */ + result = dns_db_deleterdataset(gdb, node, gversion, dns_rdatatype_rrsig, + dns_rdatatype_nsec3); + if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED) { + check_result(result, "dns_db_deleterdataset(RRSIG(NSEC3))"); + } +} + +static void +rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, dns_diff_t *add, + dns_diff_t *del) { + isc_result_t result; + unsigned int count1 = 0; + dns_rdataset_t tmprdataset; + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + dns_rdatatype_format(rdataset->type, typestr, sizeof(typestr)); + + dns_rdataset_init(&tmprdataset); + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + unsigned int count2 = 0; + + count1++; + dns_rdataset_current(rdataset, &rdata1); + dns_rdataset_clone(rdataset, &tmprdataset); + for (result = dns_rdataset_first(&tmprdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&tmprdataset)) + { + dns_rdata_t rdata2 = DNS_RDATA_INIT; + dns_difftuple_t *tuple = NULL; + count2++; + dns_rdataset_current(&tmprdataset, &rdata2); + if (count1 < count2 && + dns_rdata_casecompare(&rdata1, &rdata2) == 0) + { + vbprintf(2, "removing duplicate at %s/%s\n", + namestr, typestr); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + rdataset->ttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + } else if (set_maxttl && rdataset->ttl > maxttl) { + vbprintf(2, + "reducing ttl of %s/%s " + "from %d to %d\n", + namestr, typestr, rdataset->ttl, + maxttl); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + rdataset->ttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + tuple = NULL; + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, name, + maxttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); + } + } + dns_rdataset_disassociate(&tmprdataset); + } +} + +static void +cleanup_zone(void) { + isc_result_t result; + dns_dbiterator_t *dbiter = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_diff_t add, del; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_fixedname_t fname; + dns_name_t *name; + + dns_diff_init(mctx, &add); + dns_diff_init(mctx, &del); + name = dns_fixedname_initname(&fname); + dns_rdataset_init(&rdataset); + + result = dns_db_createiterator(gdb, 0, &dbiter); + check_result(result, "dns_db_createiterator()"); + + for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, + &rdsiter); + check_result(result, "dns_db_allrdatasets()"); + for (result = dns_rdatasetiter_first(rdsiter); + result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(rdsiter)) + { + dns_rdatasetiter_current(rdsiter, &rdataset); + rrset_cleanup(name, &rdataset, &add, &del); + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOMORE) { + fatal("rdatasets iteration failed."); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(gdb, &node); + } + if (result != ISC_R_NOMORE) { + fatal("zone iteration failed."); + } + + result = dns_diff_applysilently(&del, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + result = dns_diff_applysilently(&add, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + dns_diff_clear(&del); + dns_diff_clear(&add); + dns_dbiterator_destroy(&dbiter); +} + +/* + * Generate NSEC3 records for the zone. + */ +static void +nsec3ify(unsigned int hashalg, dns_iterations_t iterations, + const unsigned char *salt, size_t salt_len, hashlist_t *hashlist) { + dns_dbiterator_t *dbiter = NULL; + dns_dbnode_t *node = NULL, *nextnode = NULL; + dns_fixedname_t fname, fnextname, fzonecut; + dns_name_t *name, *nextname, *zonecut; + dns_rdataset_t rdataset; + int order; + bool active; + bool done = false; + isc_result_t result; + uint32_t nsttl = 0; + unsigned int count, nlabels; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fname); + nextname = dns_fixedname_initname(&fnextname); + zonecut = NULL; + + /* + * Walk the zone generating the hash names. + */ + result = dns_db_createiterator(gdb, DNS_DB_NONSEC3, &dbiter); + check_result(result, "dns_db_createiterator()"); + + result = dns_dbiterator_first(dbiter); + check_result(result, "dns_dbiterator_first()"); + + while (!done) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (dns_name_equal(name, gorigin)) { + remove_records(node, dns_rdatatype_nsec, true); + /* Clean old rrsigs at apex. */ + (void)active_node(node); + } + + if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + active = active_node(nextnode); + if (!active) { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (!dns_name_issubdomain(nextname, gorigin) || + (zonecut != NULL && + dns_name_issubdomain(nextname, zonecut))) + { + remove_sigs(nextnode, false, 0); + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (is_delegation(gdb, gversion, gorigin, nextname, + nextnode, &nsttl)) + { + zonecut = savezonecut(&fzonecut, nextname); + remove_sigs(nextnode, true, 0); + if (generateds) { + add_ds(nextname, nextnode, nsttl); + } + if (OPTOUT(nsec3flags) && + !secure(nextname, nextnode)) + { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + } else if (has_dname(gdb, gversion, nextnode)) { + zonecut = savezonecut(&fzonecut, nextname); + } + dns_db_detachnode(gdb, &nextnode); + break; + } + if (result == ISC_R_NOMORE) { + dns_name_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, ¤tversion); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&soasigs); + dns_rdataset_init(&keysigs); + + /* Make note of the keys which signed the SOA, if any */ + result = dns_db_findrdataset(gdb, node, currentversion, + dns_rdatatype_soa, 0, 0, &rdataset, + &soasigs); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Preserve the TTL of the DNSKEY RRset, if any */ + dns_rdataset_disassociate(&rdataset); + result = dns_db_findrdataset(gdb, node, currentversion, + dns_rdatatype_dnskey, 0, 0, &rdataset, + &keysigs); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (set_keyttl && keyttl != rdataset.ttl) { + fprintf(stderr, + "User-specified TTL %u conflicts " + "with existing DNSKEY RRset TTL.\n", + keyttl); + fprintf(stderr, + "Imported keys will use the RRSet " + "TTL %u instead.\n", + rdataset.ttl); + } + keyttl = rdataset.ttl; + + /* Load keys corresponding to the existing DNSKEY RRset. */ + result = dns_dnssec_keylistfromrdataset( + gorigin, directory, mctx, &rdataset, &keysigs, &soasigs, + preserve_keys, load_public, &keylist); + if (result != ISC_R_SUCCESS) { + fatal("failed to load the zone keys: %s", + isc_result_totext(result)); + } + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (dns_rdataset_isassociated(&keysigs)) { + dns_rdataset_disassociate(&keysigs); + } + if (dns_rdataset_isassociated(&soasigs)) { + dns_rdataset_disassociate(&soasigs); + } + dns_db_detachnode(gdb, &node); + dns_db_closeversion(gdb, ¤tversion, false); +} + +static void +loadexplicitkeys(char *keyfiles[], int n, bool setksk) { + isc_result_t result; + int i; + + for (i = 0; i < n; i++) { + dns_dnsseckey_t *key = NULL; + dst_key_t *newkey = NULL; + + result = dst_key_fromnamedfile( + keyfiles[i], directory, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, mctx, &newkey); + if (result != ISC_R_SUCCESS) { + fatal("cannot load dnskey %s: %s", keyfiles[i], + isc_result_totext(result)); + } + + if (!dns_name_equal(gorigin, dst_key_name(newkey))) { + fatal("key %s not at origin\n", keyfiles[i]); + } + + if (!dst_key_isprivate(newkey)) { + fatal("cannot sign zone with non-private dnskey %s", + keyfiles[i]); + } + + /* Skip any duplicates */ + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (dst_key_id(key->key) == dst_key_id(newkey) && + dst_key_alg(key->key) == dst_key_alg(newkey)) + { + break; + } + } + + if (key == NULL) { + /* We haven't seen this key before */ + dns_dnsseckey_create(mctx, &newkey, &key); + ISC_LIST_APPEND(keylist, key, link); + key->source = dns_keysource_user; + } else { + dst_key_free(&key->key); + key->key = newkey; + } + + key->force_publish = true; + key->force_sign = true; + + if (setksk) { + key->ksk = true; + } + } +} + +static void +report(const char *format, ...) { + if (!quiet) { + FILE *out = output_stdout ? stderr : stdout; + char buf[4096]; + va_list args; + + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + fprintf(out, "%s\n", buf); + } +} + +static void +clear_keylist(dns_dnsseckeylist_t *list) { + dns_dnsseckey_t *key; + while (!ISC_LIST_EMPTY(*list)) { + key = ISC_LIST_HEAD(*list); + ISC_LIST_UNLINK(*list, key, link); + dns_dnsseckey_destroy(mctx, &key); + } +} + +static void +build_final_keylist(void) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + dns_dnsseckeylist_t rmkeys, matchkeys; + char name[DNS_NAME_FORMATSIZE]; + dns_rdataset_t cdsset, cdnskeyset, soaset; + + ISC_LIST_INIT(rmkeys); + ISC_LIST_INIT(matchkeys); + + dns_rdataset_init(&soaset); + dns_rdataset_init(&cdsset); + dns_rdataset_init(&cdnskeyset); + + /* + * Find keys that match this zone in the key repository. + */ + result = dns_dnssec_findmatchingkeys(gorigin, directory, now, mctx, + &matchkeys); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + check_result(result, "dns_dnssec_findmatchingkeys"); + + result = dns_db_newversion(gdb, &ver); + check_result(result, "dns_db_newversion"); + + result = dns_db_getoriginnode(gdb, &node); + check_result(result, "dns_db_getoriginnode"); + + /* Get the CDS rdataset */ + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_cds, + dns_rdatatype_none, 0, &cdsset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + + /* Get the CDNSKEY rdataset */ + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_cdnskey, + dns_rdatatype_none, 0, &cdnskeyset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + dns_diff_init(mctx, &diff); + + /* + * Update keylist with information from from the key repository. + */ + dns_dnssec_updatekeys(&keylist, &matchkeys, NULL, gorigin, keyttl, + &diff, mctx, report); + + /* + * Update keylist with sync records. + */ + dns_dnssec_syncupdate(&keylist, &rmkeys, &cdsset, &cdnskeyset, now, + keyttl, &diff, mctx); + + dns_name_format(gorigin, name, sizeof(name)); + + result = dns_diff_applysilently(&diff, gdb, ver); + if (result != ISC_R_SUCCESS) { + fatal("failed to update DNSKEY RRset at node '%s': %s", name, + isc_result_totext(result)); + } + + dns_db_detachnode(gdb, &node); + dns_db_closeversion(gdb, &ver, true); + + dns_diff_clear(&diff); + + if (dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + if (dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + clear_keylist(&rmkeys); + clear_keylist(&matchkeys); +} + +static void +warnifallksk(dns_db_t *db) { + dns_dbversion_t *currentversion = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dns_rdata_dnskey_t dnskey; + bool have_non_ksk = false; + + dns_db_currentversion(db, ¤tversion); + + result = dns_db_findnode(db, gorigin, false, &node); + if (result != ISC_R_SUCCESS) { + fatal("failed to find the zone's origin: %s", + isc_result_totext(result)); + } + + dns_rdataset_init(&rdataset); + result = dns_db_findrdataset(db, node, currentversion, + dns_rdatatype_dnskey, 0, 0, &rdataset, + NULL); + if (result != ISC_R_SUCCESS) { + fatal("failed to find keys at the zone apex: %s", + isc_result_totext(result)); + } + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + while (result == ISC_R_SUCCESS) { + dns_rdata_reset(&rdata); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &dnskey, NULL); + check_result(result, "dns_rdata_tostruct"); + if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) { + have_non_ksk = true; + result = ISC_R_NOMORE; + } else { + result = dns_rdataset_next(&rdataset); + } + dns_rdata_freestruct(&dnskey); + } + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + dns_db_closeversion(db, ¤tversion, false); + if (!have_non_ksk && !ignore_kskflag) { + if (disable_zone_check) { + fprintf(stderr, + "%s: warning: No non-KSK DNSKEY found; " + "supply a ZSK or use '-z'.\n", + program); + } else { + fatal("No non-KSK DNSKEY found; " + "supply a ZSK or use '-z'."); + } + } +} + +static void +set_nsec3params(bool update, bool set_salt, bool set_optout, bool set_iter) { + isc_result_t result; + dns_dbversion_t *ver = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3_t nsec3; + dns_fixedname_t fname; + dns_name_t *hashname; + unsigned char orig_salt[255]; + size_t orig_saltlen; + dns_hash_t orig_hash; + uint16_t orig_iter; + + dns_db_currentversion(gdb, &ver); + dns_rdataset_init(&rdataset); + + orig_saltlen = sizeof(orig_salt); + result = dns_db_getnsec3parameters(gdb, ver, &orig_hash, NULL, + &orig_iter, orig_salt, + &orig_saltlen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + nsec_datatype = dns_rdatatype_nsec3; + + if (!update && set_salt) { + if (salt_length != orig_saltlen || + !isc_safe_memequal(saltbuf, orig_salt, salt_length)) + { + fatal("An NSEC3 chain exists with a different salt. " + "Use -u to update it."); + } + } else if (!set_salt) { + salt_length = orig_saltlen; + memmove(saltbuf, orig_salt, orig_saltlen); + gsalt = saltbuf; + } + + if (!update && set_iter) { + if (nsec3iter != orig_iter) { + fatal("An NSEC3 chain exists with different " + "iterations. Use -u to update it."); + } + } else if (!set_iter) { + nsec3iter = orig_iter; + } + + /* + * Find an NSEC3 record to get the current OPTOUT value. + * (This assumes all NSEC3 records agree.) + */ + + hashname = dns_fixedname_initname(&fname); + result = dns_nsec3_hashname(&fname, NULL, NULL, gorigin, gorigin, + dns_hash_sha1, orig_iter, orig_salt, + orig_saltlen); + check_result(result, "dns_nsec3_hashname"); + + result = dns_db_findnsec3node(gdb, hashname, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_nsec3, 0, 0, + &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + check_result(result, "dns_rdata_tostruct"); + + if (!update && set_optout) { + if (nsec3flags != nsec3.flags) { + fatal("An NSEC3 chain exists with%s OPTOUT. " + "Use -u -%s to %s it.", + OPTOUT(nsec3.flags) ? "" : "out", + OPTOUT(nsec3.flags) ? "AA" : "A", + OPTOUT(nsec3.flags) ? "clear" : "set"); + } + } else if (!set_optout) { + nsec3flags = nsec3.flags; + } + + dns_rdata_freestruct(&nsec3); + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(gdb, &node); + } + dns_db_closeversion(gdb, &ver, false); +} + +static void +writeset(const char *prefix, dns_rdatatype_t type) { + char *filename; + char namestr[DNS_NAME_FORMATSIZE]; + dns_db_t *db = NULL; + dns_dbversion_t *dbversion = NULL; + dns_diff_t diff; + dns_difftuple_t *tuple = NULL; + dns_name_t *name; + dns_rdata_t rdata, ds; + bool have_ksk = false; + bool have_non_ksk = false; + isc_buffer_t b; + isc_buffer_t namebuf; + isc_region_t r; + isc_result_t result; + dns_dnsseckey_t *key, *curr; + unsigned char dsbuf[DNS_DS_BUFFERSIZE]; + unsigned char keybuf[DST_KEY_MAXSIZE]; + unsigned int filenamelen; + const dns_master_style_t *style = (type == dns_rdatatype_dnskey) + ? masterstyle + : dsstyle; + + isc_buffer_init(&namebuf, namestr, sizeof(namestr)); + result = dns_name_tofilenametext(gorigin, false, &namebuf); + check_result(result, "dns_name_tofilenametext"); + isc_buffer_putuint8(&namebuf, 0); + filenamelen = strlen(prefix) + strlen(namestr) + 1; + if (dsdir != NULL) { + filenamelen += strlen(dsdir) + 1; + } + filename = isc_mem_get(mctx, filenamelen); + if (dsdir != NULL) { + snprintf(filename, filenamelen, "%s/", dsdir); + } else { + filename[0] = 0; + } + strlcat(filename, prefix, filenamelen); + strlcat(filename, namestr, filenamelen); + + dns_diff_init(mctx, &diff); + + name = gorigin; + + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (REVOKE(key->key)) { + continue; + } + if (isksk(key)) { + have_ksk = true; + have_non_ksk = false; + } else { + have_ksk = false; + have_non_ksk = true; + } + for (curr = ISC_LIST_HEAD(keylist); curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + if (dst_key_alg(key->key) != dst_key_alg(curr->key)) { + continue; + } + if (REVOKE(curr->key)) { + continue; + } + if (isksk(curr)) { + have_ksk = true; + } else { + have_non_ksk = true; + } + } + if (have_ksk && have_non_ksk && !isksk(key)) { + continue; + } + dns_rdata_init(&rdata); + dns_rdata_init(&ds); + isc_buffer_init(&b, keybuf, sizeof(keybuf)); + result = dst_key_todns(key->key, &b); + check_result(result, "dst_key_todns"); + isc_buffer_usedregion(&b, &r); + dns_rdata_fromregion(&rdata, gclass, dns_rdatatype_dnskey, &r); + if (type != dns_rdatatype_dnskey) { + result = dns_ds_buildrdata(gorigin, &rdata, + DNS_DSDIGEST_SHA256, dsbuf, + &ds); + check_result(result, "dns_ds_buildrdata"); + result = dns_difftuple_create(mctx, + DNS_DIFFOP_ADDRESIGN, + name, 0, &ds, &tuple); + } else { + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, gorigin, + zone_soa_min_ttl, &rdata, &tuple); + } + check_result(result, "dns_difftuple_create"); + dns_diff_append(&diff, &tuple); + } + + result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone, + gclass, 0, NULL, &db); + check_result(result, "dns_db_create"); + + result = dns_db_newversion(db, &dbversion); + check_result(result, "dns_db_newversion"); + + result = dns_diff_apply(&diff, db, dbversion); + check_result(result, "dns_diff_apply"); + dns_diff_clear(&diff); + + result = dns_master_dump(mctx, db, dbversion, style, filename, + dns_masterformat_text, NULL); + check_result(result, "dns_master_dump"); + + isc_mem_put(mctx, filename, filenamelen); + + dns_db_closeversion(db, &dbversion, false); + dns_db_detach(&db); +} + +static void +print_time(FILE *fp) { + time_t currenttime = time(NULL); + struct tm t, *tm = localtime_r(¤ttime, &t); + unsigned int flen; + char timebuf[80]; + + if (tm == NULL || outputformat != dns_masterformat_text) { + return; + } + + flen = strftime(timebuf, sizeof(timebuf), "%a %b %e %H:%M:%S %Y", tm); + INSIST(flen > 0U && flen < sizeof(timebuf)); + fprintf(fp, "; File written on %s\n", timebuf); +} + +static void +print_version(FILE *fp) { + if (outputformat != dns_masterformat_text) { + return; + } + + fprintf(fp, "; dnssec_signzone version %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)); |