diff --git a/doc/configuration.rst b/doc/configuration.rst index e15e9df05380ee3af0b297df7a234b602a9f0e42..b2fe0da8d15b06f3547d3d4c81a68fbac2a67368 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -293,7 +293,6 @@ only and there are some limitations: (even for verification only). * There cannot be more than eight keys per zone. Keys which are not published are not included in this number. -* Single-Type Signing Scheme is not supported. Example how to generate NSEC3 capable zone signing key (ZSK) and key signing key (KSK) for zone ``example.com``:: @@ -325,8 +324,9 @@ The signing process consists of the following steps: for any keys that are present in keydir, but missing in zone file. * Removing expired signatures, invalid signatures, signatures expiring in a short time, and signatures with unknown key. -* Creating any missing signatures. ``DNSKEY`` records are signed by - both ZSK and KSK keys, other records are signed only by ZSK keys. +* Creating missing signatures. Unless the Single-Type Signing Scheme + is used, ``DNSKEY`` records in a zone apex are signed by KSK keys and + all other records are signed by ZSK keys. * SOA record is updated and resigned if any changes were performed. The zone signing is performed when the zone is loaded into server, on diff --git a/src/knot/dnssec/zone-keys.c b/src/knot/dnssec/zone-keys.c index f67f6c3db46e12bd4346a11e93b8c1d5af75bc5c..33c97135fb65ad9476c598dbde8a63e4ae242132 100644 --- a/src/knot/dnssec/zone-keys.c +++ b/src/knot/dnssec/zone-keys.c @@ -98,6 +98,7 @@ static void set_zone_key_flags(const knot_key_params_t *params, key->next_event = next_event; key->is_ksk = params->flags & KNOT_RDATA_DNSKEY_FLAG_KSK; + key->is_zsk = !key->is_ksk; key->is_active = params->time_activate <= now && (params->time_inactive == 0 || now < params->time_inactive); @@ -106,6 +107,36 @@ static void set_zone_key_flags(const knot_key_params_t *params, (params->time_delete == 0 || now < params->time_delete); } +/*! + * \brief Enable STSS if all keys are KSK/ZSK exclusively. + * + * \return STSS was enabled. + */ +static bool enable_single_type_signing(knot_zone_keys_t *keys) +{ + assert(keys); + + int num_keys = 0; + int num_zone = 0; + + knot_zone_key_t *key = NULL; + WALK_LIST(key, keys->list) { + if (key->is_ksk) { num_keys += 1; } + if (key->is_zsk) { num_zone += 1; } + } + + if ((num_keys + num_zone == 0) || (num_keys > 0 && num_zone > 0)) { + return false; + } + + WALK_LIST(key, keys->list) { + key->is_ksk = true; + key->is_zsk = true; + } + + return true; +} + /*! * \brief Check if there is a functional KSK and ZSK for each used algorithm. */ @@ -134,11 +165,8 @@ static int check_keys_validity(const knot_zone_keys_t *keys) // need fully enabled ZSK and KSK for each algorithm if (key->is_active) { - if (key->is_ksk) { - algorithms[a].ksk_enabled = true; - } else { - algorithms[a].zsk_enabled = true; - } + if (key->is_ksk) { algorithms[a].ksk_enabled = true; } + if (key->is_zsk) { algorithms[a].zsk_enabled = true; } } } } @@ -283,6 +311,10 @@ int knot_load_zone_keys(const char *keydir_name, const knot_dname_t *zone_name, closedir(keydir); + if (enable_single_type_signing(keys)) { + log_zone_info(zone_name, "DNSSEC, using Single-Type Signing Scheme"); + } + if (result == KNOT_EOK) { result = check_keys_validity(keys); } diff --git a/src/knot/dnssec/zone-keys.h b/src/knot/dnssec/zone-keys.h index e1c22e2e72411a8362c277f1b01e28ee241c88b4..ff40be60d92f0a5968f787917d48a00ed2017a31 100644 --- a/src/knot/dnssec/zone-keys.h +++ b/src/knot/dnssec/zone-keys.h @@ -40,7 +40,8 @@ typedef struct { knot_dnssec_key_t dnssec_key; knot_dnssec_sign_context_t *context; uint32_t next_event; //!< Timestamp of next key event. - bool is_ksk; //!< Is KSK key. + bool is_ksk; //!< Is key-signing. + bool is_zsk; //!< Is zone-signing. bool is_public; //!< Currently in zone. bool is_active; //!< Currently used for signing. } knot_zone_key_t; diff --git a/src/knot/dnssec/zone-sign.c b/src/knot/dnssec/zone-sign.c index 63438f9207dea4b5b6ddb178f61cfe64be9c7a0e..75c5572c228623e229528a73de5bfd55491d95df 100644 --- a/src/knot/dnssec/zone-sign.c +++ b/src/knot/dnssec/zone-sign.c @@ -110,18 +110,10 @@ static bool use_key(const knot_zone_key_t *key, const knot_rrset_t *covered) return false; } - if (key->is_ksk) { - if (covered->type != KNOT_RRTYPE_DNSKEY) { - return false; - } + bool is_zone_key = covered->type == KNOT_RRTYPE_DNSKEY && + knot_dname_is_equal(key->dnssec_key.name, covered->owner); - // use KSK only in the zone apex - if (!knot_dname_is_equal(key->dnssec_key.name, covered->owner)) { - return false; - } - } - - return true; + return (key->is_ksk && is_zone_key) || (key->is_zsk && !is_zone_key); } /*! diff --git a/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh b/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh index cfd52b0ec02b745eeec14c7939865b0b4bd47844..b6db1f0314344601f1c8896b1ed9d32f3e372a3d 100755 --- a/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh +++ b/tests-extra/tests/dnssec/dnskey_algorithms/data/generate_keys.sh @@ -34,6 +34,14 @@ keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_roll_ok keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_ecdsa_roll_ok keygen -a ECDSAP256SHA256 -P $TIME_FUTURE -A $TIME_PAST rsa_ecdsa_roll_ok +# +# valid single-type signing scheme scenarios +# + +keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_stss_ksk + +keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST rsa_stss_zsk + # # invalid scenarios # @@ -53,9 +61,6 @@ keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_FUTURE rsa_inactive_zsk keygen -a RSASHA256 -b 2048 -P $TIME_FUTURE -A $TIME_FUTURE -f KSK rsa_no_zsk keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_no_zsk -keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_twice_ksk -keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_twice_ksk - keygen -a RSASHA256 -b 2048 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_ksk_only keygen -a RSASHA256 -b 1024 -P $TIME_PAST -A $TIME_PAST rsa_ecdsa_ksk_only keygen -a ECDSAP256SHA256 -P $TIME_PAST -A $TIME_PAST -f KSK rsa_ecdsa_ksk_only diff --git a/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz b/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz index 25aef643afd6f39ff8f00ea290eb7016b4300bac..0161a309a833c4628c036bd6f4d0a880e840406e 100644 Binary files a/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz and b/tests-extra/tests/dnssec/dnskey_algorithms/data/keys.tgz differ diff --git a/tests-extra/tests/dnssec/dnskey_algorithms/test.py b/tests-extra/tests/dnssec/dnskey_algorithms/test.py index 20b81d4c4c10535f0cfb2c377971067a3fa8c736..b1ad20de210c755d7d0702f60644459e709dcde7 100644 --- a/tests-extra/tests/dnssec/dnskey_algorithms/test.py +++ b/tests-extra/tests/dnssec/dnskey_algorithms/test.py @@ -14,13 +14,15 @@ TEST_CASES = { "rsa_ok": True, "rsa_ecdsa_ok": True, "rsa_ecdsa_roll_ok": True, + # valid single-type signing + "rsa_stss_ksk": True, + "rsa_stss_zsk": True, # invalid cases "rsa_future_all": False, "rsa_future_publish": False, "rsa_future_active": False, "rsa_inactive_zsk": False, "rsa_no_zsk": False, - "rsa_twice_ksk": False, "rsa_ecdsa_ksk_only": False, "rsa256_rsa512": False, } diff --git a/tests-extra/tests/dnssec/single_type_signing/test.py b/tests-extra/tests/dnssec/single_type_signing/test.py new file mode 100644 index 0000000000000000000000000000000000000000..06b354240b5b11c362b1571a7917adea80a2c3b7 --- /dev/null +++ b/tests-extra/tests/dnssec/single_type_signing/test.py @@ -0,0 +1,36 @@ +#!/usr/bin/env python3 +""" +DNSSEC Single-Type Signing Scheme, RFC 6781 +""" +from dnstest.utils import * +from dnstest.test import Test + +t = Test() + +knot = t.server("knot") +zones = t.zone_rnd(3, dnssec=False, records=10) +t.link(zones, knot) +t.start() + +# one KSK +knot.gen_key(zones[0], ksk=True, alg="RSASHA256", key_len="512") + +# one ZSK +knot.gen_key(zones[1], ksk=False, alg="RSASHA512", key_len="1024") + +# multiple KSKs +knot.gen_key(zones[2], ksk=True, alg="RSASHA512", key_len="1024") +knot.gen_key(zones[2], ksk=True, alg="RSASHA256", key_len="512") + +knot.dnssec_enable = True +knot.gen_confile() +knot.reload() +t.sleep(2) +knot.flush() +t.sleep(2) +knot.stop() + +for zone in zones: + knot.zone_verify(zone) + +t.end() diff --git a/tests-extra/tools/dnstest/zonefile.py b/tests-extra/tools/dnstest/zonefile.py index 19c6f45e374ac055bfbc1174ccc25c16687f1bde..aa894c7562abd402118a03ae6a5ed1ce996c8ab5 100644 --- a/tests-extra/tools/dnstest/zonefile.py +++ b/tests-extra/tools/dnstest/zonefile.py @@ -118,7 +118,7 @@ class ZoneFile(object): # note: convert origin to lower case due to a bug in dnssec-verify origin = self.name.lower() - cmd = Popen(["dnssec-verify", "-o", origin, self.path], + cmd = Popen(["dnssec-verify", "-z", "-o", origin, self.path], stdout=PIPE, stderr=PIPE, universal_newlines=True) (out, err) = cmd.communicate()