From dfee0e64ed54aad2d82758ec86559d44570d7220 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 28 May 2025 13:59:04 +1000 Subject: [PATCH 01/15] Check that the specified DS digest is supported dnssec-dsfromkey and dnssec-cds failed to properly check if the specified DS digest type is supported or not. --- bin/dnssec/dnssectool.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index cb42bb710a..91a6c47554 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -361,7 +361,10 @@ strtodsdigest(const char *str) { r.length = strlen(str); result = dns_dsdigest_fromtext(&alg, &r); if (result != ISC_R_SUCCESS) { - fatal("unknown DS algorithm %s", str); + fatal("unknown DS digest %s", str); + } + if (!dst_ds_digest_supported(alg)) { + fatal("unsupported DS digest %s", str); } return alg; } From 9ab4160be67230168ebb437abbf6c927dab5f1ff Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 28 May 2025 11:06:41 +1000 Subject: [PATCH 02/15] Add DS digest type code points SM3 and GOST-2012 Provide mapping between mnemonic and value. --- lib/dns/include/dns/ds.h | 10 ++++++---- lib/dns/rcode.c | 23 +++++++++++++++-------- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h index a10d9355c1..a1cad49683 100644 --- a/lib/dns/include/dns/ds.h +++ b/lib/dns/include/dns/ds.h @@ -16,10 +16,12 @@ #include #include -#define DNS_DSDIGEST_SHA1 (1) -#define DNS_DSDIGEST_SHA256 (2) -#define DNS_DSDIGEST_GOST (3) -#define DNS_DSDIGEST_SHA384 (4) +#define DNS_DSDIGEST_SHA1 (1) +#define DNS_DSDIGEST_SHA256 (2) +#define DNS_DSDIGEST_GOST (3) +#define DNS_DSDIGEST_SHA384 (4) +#define DNS_DSDIGEST_GOST2012 (5) +#define DNS_DSDIGEST_SM3 (6) /* * Assuming SHA-384 digest type. diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c index 6c957e870c..8c803d7bb3 100644 --- a/lib/dns/rcode.c +++ b/lib/dns/rcode.c @@ -47,6 +47,10 @@ #define TOTEXTONLY 0x01 +/* clang-format off */ +#define SENTINEL { 0, NULL, 0 } +/* clang-format on */ + #define RCODENAMES \ /* standard rcodes */ \ { dns_rcode_noerror, "NOERROR", 0 }, \ @@ -69,7 +73,7 @@ #define ERCODENAMES \ /* extended rcodes */ \ { dns_rcode_badvers, "BADVERS", 0 }, \ - { dns_rcode_badcookie, "BADCOOKIE", 0 }, { 0, NULL, 0 } + { dns_rcode_badcookie, "BADCOOKIE", 0 }, SENTINEL #define TSIGRCODENAMES \ /* extended rcodes */ \ @@ -79,7 +83,7 @@ { dns_tsigerror_badmode, "BADMODE", 0 }, \ { dns_tsigerror_badname, "BADNAME", 0 }, \ { dns_tsigerror_badalg, "BADALG", 0 }, \ - { dns_tsigerror_badtrunc, "BADTRUNC", 0 }, { 0, NULL, 0 } + { dns_tsigerror_badtrunc, "BADTRUNC", 0 }, SENTINEL /* RFC4398 section 2.1 */ @@ -87,7 +91,7 @@ { 1, "PKIX", 0 }, { 2, "SPKI", 0 }, { 3, "PGP", 0 }, \ { 4, "IPKIX", 0 }, { 5, "ISPKI", 0 }, { 6, "IPGP", 0 }, \ { 7, "ACPKIX", 0 }, { 8, "IACPKIX", 0 }, { 253, "URI", 0 }, \ - { 254, "OID", 0 }, { 0, NULL, 0 } + { 254, "OID", 0 }, SENTINEL /* RFC2535 section 7, RFC3110 */ @@ -109,26 +113,29 @@ { DNS_KEYALG_ED448, "ED448", 0 }, \ { DNS_KEYALG_INDIRECT, "INDIRECT", 0 }, \ { DNS_KEYALG_PRIVATEDNS, "PRIVATEDNS", 0 }, \ - { DNS_KEYALG_PRIVATEOID, "PRIVATEOID", 0 }, { 0, NULL, 0 } + { DNS_KEYALG_PRIVATEOID, "PRIVATEOID", 0 }, SENTINEL /* RFC2535 section 7.1 */ #define SECPROTONAMES \ { 0, "NONE", 0 }, { 1, "TLS", 0 }, { 2, "EMAIL", 0 }, \ { 3, "DNSSEC", 0 }, { 4, "IPSEC", 0 }, { 255, "ALL", 0 }, \ - { 0, NULL, 0 } + SENTINEL -#define HASHALGNAMES { 1, "SHA-1", 0 }, { 0, NULL, 0 } +#define HASHALGNAMES { 1, "SHA-1", 0 }, SENTINEL -/* RFC3658, RFC4509, RFC5933, RFC6605 */ +/* RFC3658, RFC4509, RFC5933, RFC6605, RFC9558, RFC9563 */ #define DSDIGESTNAMES \ { DNS_DSDIGEST_SHA1, "SHA-1", 0 }, { DNS_DSDIGEST_SHA1, "SHA1", 0 }, \ { DNS_DSDIGEST_SHA256, "SHA-256", 0 }, \ { DNS_DSDIGEST_SHA256, "SHA256", 0 }, \ { DNS_DSDIGEST_GOST, "GOST", 0 }, \ + { DNS_DSDIGEST_SM3, "SM3", 0 }, \ { DNS_DSDIGEST_SHA384, "SHA-384", 0 }, \ - { DNS_DSDIGEST_SHA384, "SHA384", 0 }, { 0, NULL, 0 } + { DNS_DSDIGEST_SHA384, "SHA384", 0 }, \ + { DNS_DSDIGEST_GOST2012, "GOST-2012", 0 }, \ + { DNS_DSDIGEST_GOST2012, "GOST2012", 0 }, SENTINEL struct tbl { unsigned int value; From 215bde91c6558cb28422af58f8071a8f76fa4762 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Fri, 16 May 2025 16:14:14 +1000 Subject: [PATCH 03/15] Check the DS trust anchor algorithm is supported This make DS based trust anchors consistent with DNSKEY based trust anchors. --- bin/named/server.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bin/named/server.c b/bin/named/server.c index db395e20ef..b39bf1b107 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -853,8 +853,13 @@ ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, ds->length = r.length; ds->digest = digest; + INSIST(r.length <= ISC_MAX_MD_SIZE); memmove(ds->digest, r.base, r.length); + if (!dst_algorithm_supported(ds->algorithm)) { + CHECK(DST_R_UNSUPPORTEDALG); + } + break; default: From e6f1363964ab02941dfc376822f066cc9aef46b9 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 23 Apr 2025 14:52:23 +1000 Subject: [PATCH 04/15] Make system test changes needed for PRIVATEOID/PRIVATEDNS - When the algorithm value for a DNSSEC key is set to PRIVATEOID or PRIVATEDNS, that's a placeholder value indicating that the real algorithm identifier is encoded into the key or signature data. That means the DNSKEY algorithm value and the DST algorithm value may not be identical, so we must now add environment variables DEFAULT_ALGORITHM_DST_NUMBER, ALTERNATIVE_ALGORITHM_DST_NUMBER and DISABLED_ALGORITHM_DST_NUMBER to the test suite, with support for mapping from DST algorithm value to PRIVATEDNS or PRIVATEOID. - Some test cases use RRSIGs that have been modified to force validation to fail. When making those modifications, we now preserve the first part of the signature, so that PRIVATEDNS and PRIVATEOID algorithm identifier values will still work. (This assumes that the identifiers are short and fit into the first base64 block.) --- bin/tests/system/conf.sh | 3 +++ bin/tests/system/dnssec/ns2/sign.sh | 4 ++-- bin/tests/system/isctest/kasp.py | 6 +++++- bin/tests/system/isctest/vars/algorithms.py | 19 ++++++++++++------- bin/tests/system/ksr/tests_ksr.py | 18 +++++++++--------- 5 files changed, 31 insertions(+), 19 deletions(-) diff --git a/bin/tests/system/conf.sh b/bin/tests/system/conf.sh index fa5b17b703..e17e83116c 100644 --- a/bin/tests/system/conf.sh +++ b/bin/tests/system/conf.sh @@ -518,13 +518,16 @@ copy_setports() { -e "s/@CONTROLPORT@/${CONTROLPORT}/g" \ -e "s/@DEFAULT_ALGORITHM@/${DEFAULT_ALGORITHM}/g" \ -e "s/@DEFAULT_ALGORITHM_NUMBER@/${DEFAULT_ALGORITHM_NUMBER}/g" \ + -e "s/@DEFAULT_ALGORITHM_DST_NUMBER@/${DEFAULT_ALGORITHM_DST_NUMBER}/g" \ -e "s/@DEFAULT_BITS@/${DEFAULT_BITS}/g" \ -e "s/@ALTERNATIVE_ALGORITHM@/${ALTERNATIVE_ALGORITHM}/g" \ -e "s/@ALTERNATIVE_ALGORITHM_NUMBER@/${ALTERNATIVE_ALGORITHM_NUMBER}/g" \ + -e "s/@ALTERNATIVE_ALGORITHM_DST_NUMBER@/${ALTERNATIVE_ALGORITHM_DST_NUMBER}/g" \ -e "s/@ALTERNATIVE_BITS@/${ALTERNATIVE_BITS}/g" \ -e "s/@DEFAULT_HMAC@/${DEFAULT_HMAC}/g" \ -e "s/@DISABLED_ALGORITHM@/${DISABLED_ALGORITHM}/g" \ -e "s/@DISABLED_ALGORITHM_NUMBER@/${DISABLED_ALGORITHM_NUMBER}/g" \ + -e "s/@DISABLED_ALGORITHM_NUMBER@/${DISABLED_ALGORITHM_DST_NUMBER}/g" \ -e "s/@DISABLED_BITS@/${DISABLED_BITS}/g" \ $1 >$2 } diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 08f4d89e27..3149422265 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -87,7 +87,7 @@ zonefiletmp=$(mktemp "$zonefile.XXXXXX") || exit 1 | awk ' tolower($1) == "bad-cname.example." && $4 == "RRSIG" && $5 == "CNAME" { for (i = 1; i <= NF; i++ ) { - if (i <= 12) { + if (i <= 13) { printf("%s ", $i); continue; } @@ -106,7 +106,7 @@ tolower($1) == "bad-cname.example." && $4 == "RRSIG" && $5 == "CNAME" { tolower($1) == "bad-dname.example." && $4 == "RRSIG" && $5 == "DNAME" { for (i = 1; i <= NF; i++ ) { - if (i <= 12) { + if (i <= 13) { printf("%s ", $i); continue; } diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index 1aec27c3f5..e334ee3ac5 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -390,6 +390,10 @@ class Key: return ksigning, zsigning + def get_dnsalg(self) -> int: + alg = int(self.get_metadata("Algorithm")) + return alg + def ttl(self) -> int: with open(self.keyfile, "r", encoding="utf-8") as file: for line in file: @@ -810,7 +814,7 @@ def _check_signatures( offline_ksk=offline_ksk, zsk_missing=zsk_missing, smooth=smooth ) - alg = key.get_metadata("Algorithm") + alg = key.get_dnsalg() rtype = dns.rdatatype.to_text(covers) expect = rf"IN RRSIG {rtype} {alg} (\d) (\d+) (\d+) (\d+) {key.tag} {fqdn}" diff --git a/bin/tests/system/isctest/vars/algorithms.py b/bin/tests/system/isctest/vars/algorithms.py index 446ab09d69..86741e14a2 100644 --- a/bin/tests/system/isctest/vars/algorithms.py +++ b/bin/tests/system/isctest/vars/algorithms.py @@ -30,17 +30,20 @@ ALG_VARS = { "ALGORITHM_SET": "none", "DEFAULT_ALGORITHM": "", "DEFAULT_ALGORITHM_NUMBER": "", + "DEFAULT_ALGORITHM_DST_NUMBER": "", "DEFAULT_BITS": "", # Alternative algorithm for test cases that require more than one algorithm # (for example algorithm rollover). Must be different from # DEFAULT_ALGORITHM. "ALTERNATIVE_ALGORITHM": "", "ALTERNATIVE_ALGORITHM_NUMBER": "", + "ALTERNATIVE_ALGORITHM_DST_NUMBER": "", "ALTERNATIVE_BITS": "", # Algorithm that is used for tests against the "disable-algorithms" # configuration option. Must be different from above algorithms. "DISABLED_ALGORITHM": "", "DISABLED_ALGORITHM_NUMBER": "", + "DISABLED_ALGORITHM_DST_NUMBER": "", "DISABLED_BITS": "", # Default HMAC algorithm. Must match the rndc configuration in # bin/tests/system/_common (rndc.conf, rndc.key) @@ -54,6 +57,7 @@ STABLE_PERIOD = 3600 * 3 class Algorithm(NamedTuple): name: str number: int + dst: int bits: int @@ -72,13 +76,13 @@ class AlgorithmSet(NamedTuple): "disable-algorithms" configuration option.""" -RSASHA1 = Algorithm("RSASHA1", 5, 2048) -RSASHA256 = Algorithm("RSASHA256", 8, 2048) -RSASHA512 = Algorithm("RSASHA512", 10, 2048) -ECDSAP256SHA256 = Algorithm("ECDSAP256SHA256", 13, 256) -ECDSAP384SHA384 = Algorithm("ECDSAP384SHA384", 14, 384) -ED25519 = Algorithm("ED25519", 15, 256) -ED448 = Algorithm("ED448", 16, 456) +RSASHA1 = Algorithm("RSASHA1", 5, 5, 2048) +RSASHA256 = Algorithm("RSASHA256", 8, 8, 2048) +RSASHA512 = Algorithm("RSASHA512", 10, 10, 2048) +ECDSAP256SHA256 = Algorithm("ECDSAP256SHA256", 13, 13, 256) +ECDSAP384SHA384 = Algorithm("ECDSAP384SHA384", 14, 14, 384) +ED25519 = Algorithm("ED25519", 15, 15, 256) +ED448 = Algorithm("ED448", 16, 16, 456) ALL_ALGORITHMS = [ RSASHA1, @@ -250,6 +254,7 @@ def _algorithms_env(algs: AlgorithmSet, name: str) -> Dict[str, str]: def set_alg_env(alg: Algorithm, prefix): algs_env[f"{prefix}_ALGORITHM"] = alg.name algs_env[f"{prefix}_ALGORITHM_NUMBER"] = str(alg.number) + algs_env[f"{prefix}_ALGORITHM_DST_NUMBER"] = str(alg.dst) algs_env[f"{prefix}_BITS"] = str(alg.bits) assert isinstance(algs.default, Algorithm) diff --git a/bin/tests/system/ksr/tests_ksr.py b/bin/tests/system/ksr/tests_ksr.py index a5aefa0b21..fa09e85d84 100644 --- a/bin/tests/system/ksr/tests_ksr.py +++ b/bin/tests/system/ksr/tests_ksr.py @@ -105,7 +105,7 @@ def ksr(zone, policy, action, options="", raise_on_exception=True): def check_keys( keys, lifetime, - alg=os.environ["DEFAULT_ALGORITHM_NUMBER"], + alg=os.environ["DEFAULT_ALGORITHM_DST_NUMBER"], size=os.environ["DEFAULT_BITS"], offset=0, with_state=False, @@ -246,7 +246,7 @@ def check_rrsig_bundle(bundle_keys, bundle_lines, zone, rrtype, sigend, sigstart count = 0 for key in bundle_keys: found = False - alg = key.get_metadata("Algorithm") + alg = key.get_dnsalg() expect = f"{zone}. 3600 IN RRSIG {rrtype} {alg} 2 3600 {sigend} {sigstart} {key.tag} {zone}." # there must be a signature of this ksk for line in bundle_lines: @@ -1125,9 +1125,9 @@ def test_ksr_twotone(servers): ksks_altalg = [] for ksk in ksks: alg = ksk.get_metadata("Algorithm") - if alg == os.environ.get("DEFAULT_ALGORITHM_NUMBER"): + if alg == os.environ.get("DEFAULT_ALGORITHM_DST_NUMBER"): ksks_defalg.append(ksk) - elif alg == os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER"): + elif alg == os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER"): ksks_altalg.append(ksk) assert len(ksks_defalg) == 1 @@ -1135,7 +1135,7 @@ def test_ksr_twotone(servers): check_keys(ksks_defalg, None) - alg = os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER") + alg = os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER") size = os.environ.get("ALTERNATIVE_BITS") check_keys(ksks_altalg, None, alg, size) @@ -1154,9 +1154,9 @@ def test_ksr_twotone(servers): zsks_altalg = [] for zsk in zsks: alg = zsk.get_metadata("Algorithm") - if alg == os.environ.get("DEFAULT_ALGORITHM_NUMBER"): + if alg == os.environ.get("DEFAULT_ALGORITHM_DST_NUMBER"): zsks_defalg.append(zsk) - elif alg == os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER"): + elif alg == os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER"): zsks_altalg.append(zsk) assert len(zsks_defalg) == 4 @@ -1165,7 +1165,7 @@ def test_ksr_twotone(servers): lifetime = timedelta(days=31 * 3) check_keys(zsks_defalg, lifetime) - alg = os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER") + alg = os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER") size = os.environ.get("ALTERNATIVE_BITS") lifetime = timedelta(days=31 * 5) check_keys(zsks_altalg, lifetime, alg, size) @@ -1216,7 +1216,7 @@ def test_ksr_twotone(servers): lifetime = timedelta(days=31 * 3) check_keys(zsks_defalg, lifetime, with_state=True) - alg = os.environ.get("ALTERNATIVE_ALGORITHM_NUMBER") + alg = os.environ.get("ALTERNATIVE_ALGORITHM_DST_NUMBER") size = os.environ.get("ALTERNATIVE_BITS") lifetime = timedelta(days=31 * 5) check_keys(zsks_altalg, lifetime, alg, size, with_state=True) From 6fe09d85ab0bfffac0f9555e3b153fd1964b7a1f Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 16 Apr 2025 11:31:41 +1000 Subject: [PATCH 05/15] Support for DST_ALG_PRIVATEDNS and DST_ALG_PRIVATEOID The algorithm values PRIVATEDNS and PRIVATEOID are placeholders, signifying that the actual algorithm identifier is encoded into the key data. Keys using this mechanism are now supported. - The algorithm values PRIVATEDNS and PRIVATEOID cannot be used to build a key file name; dst_key_buildfilename() will assert if they are used. - The DST key values for private algorithms are higher than 255. Since DST_ALG_MAXALG now exceeds 256, algorithm arrays that were previously hardcoded to size 256 have been resized. - New mnemonic/text conversion functions have been added. dst_algorithm_{fromtext,totext,format} can handle algorithm identifiers encoded in PRIVATEDNS and PRIVATEOID keys, as well as the traditional algorithm identifiers. (Note: The existing dns_secalg_{fromtext,totext,format} functions are similar, but do *not* support PRIVATEDNS and PRIVATEOID. In most cases, the new functions have taken the place of the old ones, but in a few cases the old version is still appropriate.) - dns_private{oid,dns}_{fromtext,totext,format} converts between DST algorithm values and the mnemonic strings for algorithms implemented using PRIVATEDNS or PRIVATEOID. (E.g., "RSASHA256OID"). - dst_algorithm_tosecalg() returns the DNSSEC algorithm identifier that applies for a given DST algorithm. For PRIVATEDNS- or PRIVATEOID- based algorithms, the result will be PRIVATEDNS or PRIVATEOID, respectively. - dst_algorithm_fromprivatedns() and dst_algorithm_fromprivateoid() return the DST algorithm identifier for an encoded algorithm in wire format, represented as in DNS name or an object identifier, respectively. - dst_algorithm_fromdata() is a front-end for the above; it extracts the private algorithm identifier encoded at the begining of a block of key or signature data, and returns the matching DST algorithm number. - dst_key_fromdns() and dst_key_frombuffer() now work with keys that have PRIVATEDNS and PRIVATEOID algorithm identifiers at the beginning. --- lib/dns/dst_api.c | 82 +++++++++++++++++++++++++++++ lib/dns/include/dst/dst.h | 107 +++++++++++++++++++++++++++++++++++++- lib/dns/rcode.c | 102 ++++++++++++++++++++++++++++++++++++ lib/dns/resolver.c | 2 +- lib/dns/validator.c | 1 - lib/dns/zone.c | 2 +- lib/dns/zoneverify.c | 18 +++---- lib/isccfg/kaspconf.c | 4 +- 8 files changed, 303 insertions(+), 15 deletions(-) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 2851e3d0a7..4e0e3741d0 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -2107,6 +2107,8 @@ buildfilename(dns_name_t *name, dns_keytag_t id, unsigned int alg, isc_result_t result; REQUIRE(out != NULL); + REQUIRE(alg != 0 && alg != DST_ALG_PRIVATEOID && + alg != DST_ALG_PRIVATEDNS); if ((type & DST_TYPE_PRIVATE) != 0) { suffix = ".private"; @@ -2172,6 +2174,22 @@ frombuffer(const dns_name_t *name, unsigned int alg, unsigned int flags, REQUIRE(mctx != NULL); REQUIRE(keyp != NULL && *keyp == NULL); + if (alg == DNS_KEYALG_PRIVATEDNS) { + isc_buffer_t b = *source; + alg = dst_algorithm_fromprivatedns(&b); + if (alg == 0) { + return DST_R_UNSUPPORTEDALG; + } + } + + if (alg == DNS_KEYALG_PRIVATEOID) { + isc_buffer_t b = *source; + alg = dst_algorithm_fromprivateoid(&b); + if (alg == 0) { + return DST_R_UNSUPPORTEDALG; + } + } + key = get_key_struct(name, alg, flags, protocol, 0, rdclass, 0, mctx); if (isc_buffer_remaininglength(source) > 0) { @@ -2636,3 +2654,67 @@ dst_hmac_algorithm_totext(dst_algorithm_t alg) { return "unknown"; } } + +dns_secalg_t +dst_algorithm_tosecalg(dst_algorithm_t dst_alg) { + static dns_secalg_t dns_alg[DST_MAX_ALGS] = { 0 }; + + if (dst_alg < 256) { + return dst_alg; + } + if (dst_alg < DST_MAX_ALGS) { + return dns_alg[dst_alg]; + } + return 0; +} + +dst_algorithm_t +dst_algorithm_fromprivatedns(isc_buffer_t *buffer) { + dns_fixedname_t fixed; + dns_name_t *name = dns_fixedname_initname(&fixed); + isc_result_t result; + + result = dns_name_fromwire(name, buffer, DNS_DECOMPRESS_DEFAULT, NULL); + if (result != ISC_R_SUCCESS) { + return 0; + } + + /* + * Do name to dst_algorithm number mapping here. + */ + return 0; +} + +dst_algorithm_t +dst_algorithm_fromprivateoid(isc_buffer_t *buffer) { + isc_region_t r; + + isc_buffer_remainingregion(buffer, &r); + + /* + * Do OID to dst_algorithm number mapping here. There is a + * length byte followed by the OID of that length. + */ + if (r.length > 0 && ((unsigned int)r.base[0] + 1) <= r.length) { + return 0; + } + return 0; +} + +dst_algorithm_t +dst_algorithm_fromdata(dns_secalg_t algorithm, unsigned char *data, + unsigned int length) { + isc_buffer_t b; + switch (algorithm) { + case DNS_KEYALG_PRIVATEDNS: + isc_buffer_init(&b, data, length); + isc_buffer_add(&b, length); + return dst_algorithm_fromprivatedns(&b); + case DNS_KEYALG_PRIVATEOID: + isc_buffer_init(&b, data, length); + isc_buffer_add(&b, length); + return dst_algorithm_fromprivateoid(&b); + default: + return algorithm; + } +} diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 0a9472b284..2e959461b4 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -110,7 +110,12 @@ typedef enum dst_algorithm { DST_ALG_HMAC_LAST = DST_ALG_HMACSHA512, DST_ALG_INDIRECT = 252, - DST_ALG_PRIVATE = 254, + DST_ALG_PRIVATEDNS = 253, + DST_ALG_PRIVATEOID = 254, + DST_ALG_RESERVED = 255, + /* + * Put PRIVATE DNS and PRIVATE OID identifiers here. + */ DST_MAX_ALGS = 256, } dst_algorithm_t; @@ -206,6 +211,20 @@ dst_algorithm_supported(unsigned int alg); * \li false */ +dst_algorithm_t +dst_algorithm_fromprivateoid(isc_buffer_t *buffer); +/* + * Extract the dst algorithm identifier that matches + * the OID value found at the start of 'buffer'. + */ + +dst_algorithm_t +dst_algorithm_fromprivatedns(isc_buffer_t *buf); +/* + * Extract the dst algorithm identifier that matches + * the DNS name found at the start of 'buffer'. + */ + bool dst_ds_digest_supported(unsigned int digest_type); /*%< @@ -1160,3 +1179,89 @@ dst_hmac_algorithm_totext(dst_algorithm_t alg); * Return the name associtated with the HMAC algorithm 'alg' * or return "unknown". */ + +isc_result_t +dst_algorithm_fromtext(dst_algorithm_t *algp, isc_textregion_t *source); +/*%< + * Convert the text 'source' refers to into a DST security algorithm value. + * The text may contain either a mnemonic algorithm name or a decimal algorithm + * number. This supports more algorithms than 'dns_secalg_fromtext' as it + * supports private algorithms used with PRIVATEDNS and PRIVATEOID. + * + * Requires: + *\li 'algp' is a valid pointer. + * + *\li 'source' is a valid text region. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_RANGE numeric type is out of range + *\li DNS_R_UNKNOWN mnemonic type is unknown + */ + +isc_result_t +dst_algorithm_totext(dst_algorithm_t alg, isc_buffer_t *target); +/*%< + * Put a textual representation of DST security algorithm 'alg' + * into 'target'. This supports a superset of dns_secalg_totext. + * + * Requires: + *\li 'alg' is a valid dst_algorithm_t. + * + *\li 'target' is a valid text buffer. + * + * Ensures, + * if the result is success: + *\li The used space in 'target' is updated. + * + * Returns: + *\li ISC_R_SUCCESS on success + *\li ISC_R_NOSPACE target buffer is too small + */ + +#define DST_ALGORITHM_FORMATSIZE 20 +void +dst_algorithm_format(dst_algorithm_t dst_alg, char *data, unsigned int length); +/*%< + * Wrapper for dst_algorithm_totext(), writing text into 'cp' + */ + +dns_secalg_t +dst_algorithm_tosecalg(dst_algorithm_t dst_alg); +/*%< + * Return the DNSSEC algorithm identifier that applies for the DST + * algorithm. For PRIVATEDNS and PRIVATEOID based algorithms, this + * is PRIVATEDNS and PRIVATEOID respectively. + * + * Zero is returned when there is no mapping. + */ + +isc_result_t +dst_privatedns_fromtext(dst_algorithm_t *algp, isc_textregion_t *source); + +isc_result_t +dns_privatedns_totext(dst_algorithm_t alg, isc_buffer_t *b); + +void +dns_privatedns_format(dst_algorithm_t alg, char *buf, unsigned int size); + +isc_result_t +dst_privateoid_fromtext(dst_algorithm_t *algp, isc_textregion_t *source); + +isc_result_t +dns_privateoid_totext(dst_algorithm_t alg, isc_buffer_t *b); + +void +dns_privateoid_format(dst_algorithm_t alg, char *buf, unsigned int size); + +dst_algorithm_t +dst_algorithm_fromdata(dns_secalg_t algorithm, unsigned char *data, + unsigned int length); +/*%< + * If 'algorithm' is PRIVATEOID or PRIVATEDNS, extract the DNSSEC private + * algorithm encoded at the begining of data and return the DST algorithm + * number that corresponds to it; if the algorithm is unknown to DST, + * return 0. + * + * If 'algorithm' is any other value, return it directly. + */ diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c index 8c803d7bb3..f77daa4141 100644 --- a/lib/dns/rcode.c +++ b/lib/dns/rcode.c @@ -36,6 +36,8 @@ #include #include +#include + #define RETERR(x) \ do { \ isc_result_t _r = (x); \ @@ -115,6 +117,16 @@ { DNS_KEYALG_PRIVATEDNS, "PRIVATEDNS", 0 }, \ { DNS_KEYALG_PRIVATEOID, "PRIVATEOID", 0 }, SENTINEL +/* + * PRIVATEDNS subtypes we support. + */ +#define PRIVATEDNSS /* currently empty */ + +/* + * PRIVATEOID subtypes we support. + */ +#define PRIVATEOIDS /* currently empty */ + /* RFC2535 section 7.1 */ #define SECPROTONAMES \ @@ -150,6 +162,9 @@ static struct tbl secalgs[] = { SECALGNAMES }; static struct tbl secprotos[] = { SECPROTONAMES }; static struct tbl hashalgs[] = { HASHALGNAMES }; static struct tbl dsdigests[] = { DSDIGESTNAMES }; +static struct tbl privatednss[] = { PRIVATEDNSS SENTINEL }; +static struct tbl privateoids[] = { PRIVATEOIDS SENTINEL }; +static struct tbl dstalgorithms[] = { PRIVATEDNSS PRIVATEOIDS SECALGNAMES }; static struct keyflag { const char *name; @@ -353,6 +368,64 @@ dns_secalg_format(dns_secalg_t alg, char *cp, unsigned int size) { } } +isc_result_t +dst_privatedns_fromtext(dst_algorithm_t *dstalgp, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, privatednss, 0)); + *dstalgp = value; + return ISC_R_SUCCESS; +} + +isc_result_t +dns_privatedns_totext(dst_algorithm_t alg, isc_buffer_t *target) { + return dns_mnemonic_totext(alg, target, privatednss); +} + +void +dns_privatedns_format(dst_algorithm_t alg, char *cp, unsigned int size) { + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + REQUIRE(cp != NULL && size > 0); + isc_buffer_init(&b, cp, size - 1); + result = dns_privatedns_totext(alg, &b); + isc_buffer_usedregion(&b, &r); + r.base[r.length] = 0; + if (result != ISC_R_SUCCESS) { + r.base[0] = 0; + } +} + +isc_result_t +dst_privateoid_fromtext(dst_algorithm_t *dstalgp, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, privateoids, 0)); + *dstalgp = value; + return ISC_R_SUCCESS; +} + +isc_result_t +dns_privateoid_totext(dst_algorithm_t alg, isc_buffer_t *target) { + return dns_mnemonic_totext(alg, target, privateoids); +} + +void +dns_privateoid_format(dst_algorithm_t alg, char *cp, unsigned int size) { + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + REQUIRE(cp != NULL && size > 0); + isc_buffer_init(&b, cp, size - 1); + result = dns_privateoid_totext(alg, &b); + isc_buffer_usedregion(&b, &r); + r.base[r.length] = 0; + if (result != ISC_R_SUCCESS) { + r.base[0] = 0; + } +} + isc_result_t dns_secproto_fromtext(dns_secproto_t *secprotop, isc_textregion_t *source) { unsigned int value; @@ -579,3 +652,32 @@ dns_rdataclass_format(dns_rdataclass_t rdclass, char *array, strlcpy(array, "", size); } } + +isc_result_t +dst_algorithm_fromtext(dst_algorithm_t *dstalgp, isc_textregion_t *source) { + unsigned int value; + RETERR(dns_mnemonic_fromtext(&value, source, dstalgorithms, 255)); + *dstalgp = value; + return ISC_R_SUCCESS; +} + +isc_result_t +dst_algorithm_totext(dst_algorithm_t alg, isc_buffer_t *target) { + return dns_mnemonic_totext(alg, target, dstalgorithms); +} + +void +dst_algorithm_format(dst_algorithm_t alg, char *cp, unsigned int size) { + isc_buffer_t b; + isc_region_t r; + isc_result_t result; + + REQUIRE(cp != NULL && size > 0); + isc_buffer_init(&b, cp, size - 1); + result = dst_algorithm_totext(alg, &b); + isc_buffer_usedregion(&b, &r); + r.base[r.length] = 0; + if (result != ISC_R_SUCCESS) { + r.base[0] = 0; + } +} diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 08f0533fe2..93d1919796 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -10593,7 +10593,7 @@ dns_resolver_disable_algorithm(dns_resolver_t *resolver, const dns_name_t *name, unsigned int alg) { REQUIRE(VALID_RESOLVER(resolver)); - if (alg > 255) { + if (alg >= DST_MAX_ALGS) { return ISC_R_RANGE; } diff --git a/lib/dns/validator.c b/lib/dns/validator.c index f2c8fb11be..4882262b95 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -2910,7 +2910,6 @@ check_ds_algs(dns_validator_t *val, dns_name_t *name, isc_result_t result; dns_rdata_t dsrdata = DNS_RDATA_INIT; dns_rdataset_current(rdataset, &dsrdata); - result = dns_rdata_tostruct(&dsrdata, &ds, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); diff --git a/lib/dns/zone.c b/lib/dns/zone.c index edb2a9d12d..ef14c483d8 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -22804,7 +22804,7 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { isc_result_t result; dns_dbnode_t *node = NULL; dns_rdataset_t dnskey, cds, cdnskey; - unsigned char algorithms[256]; + unsigned char algorithms[DST_MAX_ALGS]; unsigned int i; bool empty = false; diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c index aeb010746a..fdd46f3bbe 100644 --- a/lib/dns/zoneverify.c +++ b/lib/dns/zoneverify.c @@ -68,14 +68,14 @@ typedef struct vctx { dns_rdataset_t nsecsigs; dns_rdataset_t nsec3paramset; dns_rdataset_t nsec3paramsigs; - unsigned char revoked_ksk[256]; - unsigned char revoked_zsk[256]; - unsigned char standby_ksk[256]; - unsigned char standby_zsk[256]; - unsigned char ksk_algorithms[256]; - unsigned char zsk_algorithms[256]; - unsigned char bad_algorithms[256]; - unsigned char act_algorithms[256]; + unsigned char revoked_ksk[DST_MAX_ALGS]; + unsigned char revoked_zsk[DST_MAX_ALGS]; + unsigned char standby_ksk[DST_MAX_ALGS]; + unsigned char standby_zsk[DST_MAX_ALGS]; + unsigned char ksk_algorithms[DST_MAX_ALGS]; + unsigned char zsk_algorithms[DST_MAX_ALGS]; + unsigned char bad_algorithms[DST_MAX_ALGS]; + unsigned char act_algorithms[DST_MAX_ALGS]; isc_heap_t *expected_chains; isc_heap_t *found_chains; } vctx_t; @@ -792,7 +792,7 @@ verifynsec3s(const vctx_t *vctx, const dns_name_t *name, static isc_result_t verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name, dns_dbnode_t *node, dst_key_t **dstkeys, size_t nkeys) { - unsigned char set_algorithms[256] = { 0 }; + unsigned char set_algorithms[DST_MAX_ALGS] = { 0 }; char namebuf[DNS_NAME_FORMATSIZE]; char algbuf[DNS_SECALG_FORMATSIZE]; char typebuf[DNS_RDATATYPE_FORMATSIZE]; diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 8b5bd53f6d..0e29e8d39e 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -624,8 +624,8 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, (void)confget(maps, "keys", &keys); if (keys != NULL) { - char role[256] = { 0 }; - bool warn[256][2] = { { false } }; + char role[DST_MAX_ALGS] = { 0 }; + bool warn[DST_MAX_ALGS][2] = { { false } }; CFG_LIST_FOREACH (keys, element) { cfg_obj_t *kobj = cfg_listelt_value(element); From 71801ab123412b1de4c93f105eb3dd1998054b7b Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 1 Apr 2025 00:12:52 +1100 Subject: [PATCH 06/15] Use DST algorithm values instead of dns_secalg where needed DST algorithm and DNSSEC algorithm values are not necessarily the same anymore: if the DNSSEC algorithm value is PRIVATEOID or PRIVATEDNS, then the DST algorithm will be mapped to something else. The conversion is now done correctly where necessary. --- bin/dnssec/dnssec-keyfromlabel.c | 4 +-- bin/dnssec/dnssec-keygen.c | 26 ++++++++------- bin/dnssec/dnssec-ksr.c | 4 +-- bin/dnssec/dnssec-signzone.c | 5 ++- bin/dnssec/dnssectool.c | 4 ++- bin/named/main.c | 2 +- bin/named/server.c | 14 +++----- lib/dns/dnssec.c | 20 +++++++++--- lib/dns/dst_api.c | 7 ++-- lib/dns/dst_internal.h | 2 +- lib/dns/include/dns/kasp.h | 2 +- lib/dns/kasp.c | 18 +++++------ lib/dns/opensslrsa_link.c | 43 ++++++++++++++++++++++--- lib/dns/zone.c | 53 ++++++++++++++++++++---------- lib/dns/zoneverify.c | 55 ++++++++++++++++++++------------ lib/isccfg/check.c | 4 +-- lib/isccfg/kaspconf.c | 35 ++++++++++---------- lib/ns/query.c | 15 ++++++--- 18 files changed, 201 insertions(+), 112 deletions(-) diff --git a/bin/dnssec/dnssec-keyfromlabel.c b/bin/dnssec/dnssec-keyfromlabel.c index 6d57204034..49ddb8ad7a 100644 --- a/bin/dnssec/dnssec-keyfromlabel.c +++ b/bin/dnssec/dnssec-keyfromlabel.c @@ -113,7 +113,7 @@ main(int argc, char **argv) { dns_fixedname_t fname; dns_name_t *name; uint16_t flags = 0, kskflag = 0, revflag = 0; - dns_secalg_t alg; + dst_algorithm_t alg; bool oldstyle = false; isc_mem_t *mctx = NULL; int ch; @@ -382,7 +382,7 @@ main(int argc, char **argv) { r.base = algname; r.length = strlen(algname); - ret = dns_secalg_fromtext(&alg, &r); + ret = dst_algorithm_fromtext(&alg, &r); if (ret != ISC_R_SUCCESS) { fatal("unknown algorithm %s", algname); } diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 12bbbe0d85..500b11fd03 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -89,7 +89,7 @@ struct keygen_ctx { bool wantzsk; bool wantksk; bool wantrev; - dns_secalg_t alg; + dst_algorithm_t alg; /* timing data */ int prepub; isc_stdtime_t now; @@ -245,7 +245,7 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { UNUSED(argc); - dns_secalg_format(ctx->alg, algstr, sizeof(algstr)); + dst_algorithm_format(ctx->alg, algstr, sizeof(algstr)); if (ctx->predecessor == NULL) { if (ctx->prepub == -1) { @@ -454,14 +454,14 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { } switch (ctx->alg) { - case DNS_KEYALG_RSASHA1: - case DNS_KEYALG_NSEC3RSASHA1: + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: if (isc_crypto_fips_mode()) { fatal("SHA1 based keys not supported in FIPS mode"); } FALLTHROUGH; - case DNS_KEYALG_RSASHA256: - case DNS_KEYALG_RSASHA512: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: if (ctx->size != 0 && (ctx->size < min_rsa || ctx->size > MAX_RSA)) { @@ -480,6 +480,8 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { case DST_ALG_ED448: ctx->size = 456; break; + default: + fatal("not a dnskey algorithm %u\n", ctx->alg); } if ((ctx->options & DST_TYPE_KEY) == 0) { @@ -502,10 +504,10 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { } switch (ctx->alg) { - case DNS_KEYALG_RSASHA1: - case DNS_KEYALG_NSEC3RSASHA1: - case DNS_KEYALG_RSASHA256: - case DNS_KEYALG_RSASHA512: + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: show_progress = true; break; @@ -515,6 +517,8 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { case DST_ALG_ED448: show_progress = true; break; + default: + break; } if ((flags & DNS_KEYFLAG_TYPEMASK) == DNS_KEYTYPE_NOKEY && @@ -1072,7 +1076,7 @@ main(int argc, char **argv) { } r.base = algname; r.length = strlen(algname); - ret = dns_secalg_fromtext(&ctx.alg, &r); + ret = dst_algorithm_fromtext(&ctx.alg, &r); if (ret != ISC_R_SUCCESS) { fatal("unknown algorithm %s", algname); } diff --git a/bin/dnssec/dnssec-ksr.c b/bin/dnssec/dnssec-ksr.c index 189f8994fa..b4a4e5736a 100644 --- a/bin/dnssec/dnssec-ksr.c +++ b/bin/dnssec/dnssec-ksr.c @@ -64,7 +64,7 @@ struct ksr_ctx { /* keygen */ bool ksk; dns_ttl_t ttl; - dns_secalg_t alg; + dst_algorithm_t alg; int size; time_t lifetime; time_t parentpropagation; @@ -341,7 +341,7 @@ create_key(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_kasp_key_t *kaspkey, } /* Check algorithm and size. */ - dns_secalg_format(ksr->alg, algstr, sizeof(algstr)); + dst_algorithm_format(ksr->alg, algstr, sizeof(algstr)); if (!dst_algorithm_supported(ksr->alg)) { fatal("unsupported algorithm: %s", algstr); } diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 68a6cc5c2c..e2a01429e0 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -348,9 +348,12 @@ iszsk(dns_dnsseckey_t *key) { */ static dns_dnsseckey_t * keythatsigned_unlocked(dns_rdata_rrsig_t *rrsig) { + dst_algorithm_t algorithm = dst_algorithm_fromdata( + rrsig->algorithm, rrsig->signature, rrsig->siglen); + ISC_LIST_FOREACH (keylist, key, link) { if (rrsig->keyid == dst_key_id(key->key) && - rrsig->algorithm == dst_key_alg(key->key) && + algorithm == dst_key_alg(key->key) && dns_name_equal(&rrsig->signer, dst_key_name(key->key))) { return key; diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index 91a6c47554..88c398400b 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -120,9 +120,11 @@ void sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size) { char namestr[DNS_NAME_FORMATSIZE]; char algstr[DNS_NAME_FORMATSIZE]; + dst_algorithm_t algorithm = dst_algorithm_fromdata( + sig->algorithm, sig->signature, sig->siglen); dns_name_format(&sig->signer, namestr, sizeof(namestr)); - dns_secalg_format(sig->algorithm, algstr, sizeof(algstr)); + dst_algorithm_format(algorithm, algstr, sizeof(algstr)); snprintf(cp, size, "%s/%s/%d", namestr, algstr, sig->keyid); } diff --git a/bin/named/main.c b/bin/named/main.c index fb8062588d..903598e707 100644 --- a/bin/named/main.c +++ b/bin/named/main.c @@ -424,7 +424,7 @@ list_dnssec_algorithms(isc_buffer_t *b) { } if (dst_algorithm_supported(i)) { isc_buffer_putstr(b, " "); - (void)dns_secalg_totext(i, b); + dst_algorithm_totext(i, b); } } } diff --git a/bin/named/server.c b/bin/named/server.c index b39bf1b107..397a89df89 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -1597,17 +1597,11 @@ disable_algorithms(const cfg_obj_t *disabled, dns_resolver_t *resolver) { algorithms = cfg_tuple_get(disabled, "algorithms"); CFG_LIST_FOREACH (algorithms, element) { isc_textregion_t r; - dns_secalg_t alg; + dst_algorithm_t alg; r.base = UNCONST(cfg_obj_asstring(cfg_listelt_value(element))); r.length = strlen(r.base); - - result = dns_secalg_fromtext(&alg, &r); - if (result != ISC_R_SUCCESS) { - uint8_t ui; - result = isc_parse_uint8(&ui, r.base, 10); - alg = ui; - } + result = dst_algorithm_fromtext(&alg, &r); if (result != ISC_R_SUCCESS) { cfg_obj_log(cfg_listelt_value(element), ISC_LOG_ERROR, "invalid algorithm"); @@ -14363,7 +14357,7 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, /* variables for -key */ bool use_keyid = false; dns_keytag_t keyid = 0; - uint8_t algorithm = 0; + dst_algorithm_t algorithm = 0; /* variables for -status */ bool status = false; char output[4096]; @@ -14419,7 +14413,7 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, } alg.base = ptr; alg.length = strlen(alg.base); - result = dns_secalg_fromtext( + result = dst_algorithm_fromtext( &algorithm, (isc_textregion_t *)&alg); if (result != ISC_R_SUCCESS) { msg = "Bad algorithm"; diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 3632f86178..6e73674d2b 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -224,7 +224,7 @@ dns_dnssec_sign(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, dns_name_clone(dns_fixedname_name(&fsigner), &sig.signer); sig.covered = set->type; - sig.algorithm = dst_key_alg(key); + sig.algorithm = dst_algorithm_tosecalg(dst_key_alg(key)); sig.labels = dns_name_countlabels(name) - 1; if (dns_name_iswildcard(name)) { sig.labels--; @@ -762,7 +762,7 @@ dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key) { ISC_LINK_INIT(&sig.common, link); sig.covered = 0; - sig.algorithm = dst_key_alg(key); + sig.algorithm = dst_algorithm_tosecalg(dst_key_alg(key)); sig.labels = 0; /* the root name */ sig.originalttl = 0; @@ -1469,7 +1469,7 @@ mark_active_keys(dns_dnsseckeylist_t *keylist, dns_rdataset_t *rrsigs) { dns_rdataset_clone(rrsigs, &sigs); ISC_LIST_FOREACH (*keylist, key, link) { uint16_t keyid, sigid; - dns_secalg_t keyalg, sigalg; + dst_algorithm_t keyalg, sigalg; keyid = dst_key_id(key->key); keyalg = dst_key_alg(key->key); @@ -1480,7 +1480,8 @@ mark_active_keys(dns_dnsseckeylist_t *keylist, dns_rdataset_t *rrsigs) { dns_rdataset_current(&sigs, &rdata); result = dns_rdata_tostruct(&rdata, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); - sigalg = sig.algorithm; + sigalg = dst_algorithm_fromdata( + sig.algorithm, sig.signature, sig.siglen); sigid = sig.keyid; if (keyid == sigid && keyalg == sigalg) { key->is_active = true; @@ -1544,14 +1545,23 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, dns_kasp_t *kasp, dns_rdataset_clone(keyset, &keys); DNS_RDATASET_FOREACH (&keys) { dns_rdata_t rdata = DNS_RDATA_INIT; + dst_algorithm_t algorithm; + dns_rdata_dnskey_t keystruct; + dns_rdataset_current(&keys, &rdata); REQUIRE(rdata.type == dns_rdatatype_key || rdata.type == dns_rdatatype_dnskey); REQUIRE(rdata.length > 3); + result = dns_rdata_tostruct(&rdata, &keystruct, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + algorithm = dst_algorithm_fromdata( + keystruct.algorithm, keystruct.data, keystruct.datalen); + /* Skip unsupported algorithms */ - if (!dst_algorithm_supported(rdata.data[3])) { + if (!dst_algorithm_supported(algorithm)) { goto skip; } diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 4e0e3741d0..19fd35d0ae 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -645,7 +645,8 @@ dst_key_todns(const dst_key_t *key, isc_buffer_t *target) { } isc_buffer_putuint16(target, (uint16_t)(key->key_flags & 0xffff)); isc_buffer_putuint8(target, (uint8_t)key->key_proto); - isc_buffer_putuint8(target, (uint8_t)key->key_alg); + isc_buffer_putuint8(target, + (uint8_t)dst_algorithm_tosecalg(key->key_alg)); if ((key->key_flags & DNS_KEYFLAG_EXTENDED) != 0) { if (isc_buffer_availablelength(target) < 2) { @@ -1357,10 +1358,10 @@ void dst_key_format(const dst_key_t *key, char *cp, unsigned int size) { char namestr[DNS_NAME_FORMATSIZE]; char algstr[DNS_NAME_FORMATSIZE]; + dst_algorithm_t algorithm = dst_key_alg(key); dns_name_format(dst_key_name(key), namestr, sizeof(namestr)); - dns_secalg_format((dns_secalg_t)dst_key_alg(key), algstr, - sizeof(algstr)); + dst_algorithm_format(algorithm, algstr, sizeof(algstr)); snprintf(cp, size, "%s/%s/%d", namestr, algstr, dst_key_id(key)); } diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index 049d634987..21dc8cb06a 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -193,7 +193,7 @@ dst__hmacsha384_init(struct dst_func **funcp); void dst__hmacsha512_init(struct dst_func **funcp); void -dst__opensslrsa_init(struct dst_func **funcp, unsigned char algorithm); +dst__opensslrsa_init(struct dst_func **funcp, unsigned short algorithm); void dst__opensslecdsa_init(struct dst_func **funcp); void diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 609edbe949..e2b0ea94df 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -52,7 +52,7 @@ struct dns_kasp_key { /* Configuration */ dns_keystore_t *keystore; uint32_t lifetime; - uint8_t algorithm; + dst_algorithm_t algorithm; int length; uint8_t role; uint16_t tag_min; diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 3cd44ea74c..f0b61fd499 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -426,11 +426,11 @@ dns_kasp_key_size(dns_kasp_key_t *key) { REQUIRE(key != NULL); switch (key->algorithm) { - case DNS_KEYALG_RSASHA1: - case DNS_KEYALG_NSEC3RSASHA1: - case DNS_KEYALG_RSASHA256: - case DNS_KEYALG_RSASHA512: - min = (key->algorithm == DNS_KEYALG_RSASHA512) ? 1024 : 512; + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: + min = (key->algorithm == DST_ALG_RSASHA512) ? 1024 : 512; if (key->length > -1) { size = (unsigned int)key->length; if (size < min) { @@ -443,16 +443,16 @@ dns_kasp_key_size(dns_kasp_key_t *key) { size = 2048; } break; - case DNS_KEYALG_ECDSA256: + case DST_ALG_ECDSA256: size = 256; break; - case DNS_KEYALG_ECDSA384: + case DST_ALG_ECDSA384: size = 384; break; - case DNS_KEYALG_ED25519: + case DST_ALG_ED25519: size = 256; break; - case DNS_KEYALG_ED448: + case DST_ALG_ED448: size = 456; break; default: diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c index cc945a20ea..bfeb7bba76 100644 --- a/lib/dns/opensslrsa_link.c +++ b/lib/dns/opensslrsa_link.c @@ -264,6 +264,7 @@ opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { unsigned int siglen = 0; EVP_MD_CTX *evp_md_ctx = NULL; EVP_PKEY *pkey = NULL; + unsigned int len = 0; REQUIRE(dctx != NULL && dctx->key != NULL); REQUIRE(opensslrsa_valid_key_alg(dctx->key->key_alg)); @@ -272,17 +273,28 @@ opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { evp_md_ctx = dctx->ctxdata.evp_md_ctx; pkey = key->keydata.pkeypair.priv; + /* + * Account to the space the OIDs and DNS names consume. + */ + switch (key->key_alg) { + } + isc_buffer_availableregion(sig, &r); - if (r.length < (unsigned int)EVP_PKEY_size(pkey)) { + if (r.length < (unsigned int)EVP_PKEY_size(pkey) + len) { return ISC_R_NOSPACE; } + /* + * Add OID and DNS names to start of signature. + */ + switch (key->key_alg) { + } + if (!EVP_SignFinal(evp_md_ctx, r.base, &siglen, pkey)) { return dst__openssl_toresult3(dctx->category, "EVP_SignFinal", ISC_R_FAILURE); } - isc_buffer_add(sig, siglen); return ISC_R_SUCCESS; @@ -317,6 +329,8 @@ opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) { int status = 0; EVP_MD_CTX *evp_md_ctx = NULL; EVP_PKEY *pkey = NULL; + const unsigned char *base = sig->base; + unsigned int length = sig->length; REQUIRE(dctx != NULL && dctx->key != NULL); REQUIRE(opensslrsa_valid_key_alg(dctx->key->key_alg)); @@ -330,7 +344,13 @@ opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) { return DST_R_VERIFYFAILURE; } - status = EVP_VerifyFinal(evp_md_ctx, sig->base, sig->length, pkey); + /* + * Check identifying OID in front of public key material. + */ + switch (key->key_alg) { + } + + status = EVP_VerifyFinal(evp_md_ctx, base, length, pkey); switch (status) { case 1: return ISC_R_SUCCESS; @@ -740,6 +760,12 @@ opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data) { isc_buffer_availableregion(data, &r); + /* + * Add identifying OID and DNS names to front of public key material. + */ + switch (key->key_alg) { + } + ret = opensslrsa_components_get(key, &c, false); if (ret != ISC_R_SUCCESS) { goto err; @@ -794,6 +820,13 @@ opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) { if (r.length == 0) { DST_RET(ISC_R_SUCCESS); } + + /* + * Check identifying OID in front of public key material. + */ + switch (key->key_alg) { + } + length = r.length; if (r.length < 1) { DST_RET(DST_R_INVALIDPUBLICKEY); @@ -1214,7 +1247,7 @@ static const unsigned char sha512_sig[] = "\xf1"; static isc_result_t -check_algorithm(unsigned char algorithm) { +check_algorithm(unsigned short algorithm) { rsa_components_t c = { .bnfree = true }; EVP_MD_CTX *evp_md_ctx = EVP_MD_CTX_create(); EVP_PKEY *pkey = NULL; @@ -1272,7 +1305,7 @@ err: } void -dst__opensslrsa_init(dst_func_t **funcp, unsigned char algorithm) { +dst__opensslrsa_init(dst_func_t **funcp, unsigned short algorithm) { REQUIRE(funcp != NULL); if (*funcp == NULL) { diff --git a/lib/dns/zone.c b/lib/dns/zone.c index ef14c483d8..d3e62bbb0c 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -139,6 +139,7 @@ #define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0) #define ID(x) dst_key_id(x) #define ALG(x) dst_key_alg(x) +#define DNSALG(x) dst_algorithm_tosecalg(dst_key_alg(x)) /*% * KASP flags @@ -6806,6 +6807,10 @@ delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, isc_result_t ret; bool have_ksk = false, have_zsk = false; bool have_pksk = false, have_pzsk = false; + dst_algorithm_t algorithm; + + algorithm = dst_algorithm_fromdata( + rrsig_ptr->algorithm, rrsig_ptr->signature, rrsig_ptr->siglen); for (i = 0; i < nkeys; i++) { bool ksk, zsk; @@ -6814,7 +6819,7 @@ delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, break; } - if (rrsig_ptr->algorithm != dst_key_alg(keys[i])) { + if (algorithm != dst_key_alg(keys[i])) { continue; } @@ -6873,7 +6878,7 @@ delsig_ok(dns_rdata_rrsig_t *rrsig_ptr, dst_key_t **keys, unsigned int nkeys, * if the associated public key is still in the DNSKEY RRset */ for (i = 0; i < nkeys; i++) { - if ((rrsig_ptr->algorithm == dst_key_alg(keys[i])) && + if ((algorithm == dst_key_alg(keys[i])) && (rrsig_ptr->keyid == dst_key_id(keys[i]))) { return false; @@ -6936,10 +6941,13 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, DNS_RDATASET_FOREACH (&rdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; + dst_algorithm_t algorithm; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); + algorithm = dst_algorithm_fromdata( + rrsig.algorithm, rrsig.signature, rrsig.siglen); if (!dns_rdatatype_iskeymaterial(type)) { bool warn = false, deleted = false; @@ -6983,9 +6991,8 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, char algbuf[DNS_NAME_FORMATSIZE]; dns_name_format(&zone->origin, origin, sizeof(origin)); - dns_secalg_format(rrsig.algorithm, - algbuf, - sizeof(algbuf)); + dst_algorithm_format(algorithm, algbuf, + sizeof(algbuf)); dns_zone_log(zone, ISC_LOG_WARNING, "Key %s/%s/%d " "missing or inactive " @@ -7005,7 +7012,7 @@ del_sigs(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name, */ found = false; for (i = 0; i < nkeys; i++) { - if (rrsig.algorithm == dst_key_alg(keys[i]) && + if (algorithm == dst_key_alg(keys[i]) && rrsig.keyid == dst_key_id(keys[i])) { found = true; @@ -7618,13 +7625,16 @@ signed_with_good_key(dns_zone_t *zone, dns_db_t *db, dns_dbnode_t *node, dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); INSIST(result == ISC_R_SUCCESS); - if (rrsig.algorithm == dst_key_alg(key) && + dst_algorithm_t algorithm; + algorithm = dst_algorithm_fromdata( + rrsig.algorithm, rrsig.signature, rrsig.siglen); + if (algorithm == dst_key_alg(key) && rrsig.keyid == dst_key_id(key)) { dns_rdataset_disassociate(&rdataset); return true; } - if (rrsig.algorithm == dst_key_alg(key)) { + if (algorithm == dst_key_alg(key)) { count++; } } @@ -9677,7 +9687,8 @@ zone_sign(dns_zone_t *zone) { /* * Find the key we want to remove. */ - if (ALG(zone_keys[i]) == signing->algorithm && + if (DNSALG(zone_keys[i]) == + signing->algorithm && dst_key_id(zone_keys[i]) == signing->keyid) { dst_key_free(&zone_keys[i]); @@ -9752,7 +9763,7 @@ zone_sign(dns_zone_t *zone) { * When adding look for the specific key. */ if (!signing->deleteit && - (dst_key_alg(zone_keys[i]) != signing->algorithm || + (DNSALG(zone_keys[i]) != signing->algorithm || dst_key_id(zone_keys[i]) != signing->keyid)) { continue; @@ -9763,7 +9774,7 @@ zone_sign(dns_zone_t *zone) { * with the algorithm that was being removed. */ if (signing->deleteit && - ALG(zone_keys[i]) != signing->algorithm) + DNSALG(zone_keys[i]) != signing->algorithm) { continue; } @@ -10288,6 +10299,7 @@ revocable(dns_keyfetch_t *kfetch, dns_rdata_keydata_t *keydata) { unsigned char key_buf[4096]; isc_buffer_t keyb; bool answer = false; + dst_algorithm_t algorithm; REQUIRE(kfetch != NULL && keydata != NULL); REQUIRE(dns_rdataset_isassociated(&kfetch->dnskeysigset)); @@ -10315,7 +10327,9 @@ revocable(dns_keyfetch_t *kfetch, dns_rdata_keydata_t *keydata) { result = dns_rdata_tostruct(&sigrr, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (dst_key_alg(dstkey) == sig.algorithm && + algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature, + sig.siglen); + if (dst_key_alg(dstkey) == algorithm && dst_key_rid(dstkey) == sig.keyid) { result = dns_dnssec_verify(keyname, &kfetch->dnskeyset, @@ -16440,7 +16454,8 @@ cds_inuse(dns_zone_t *zone, dns_rdata_t *rdata, dns_dnsseckeylist_t *keylist, unsigned char cdsbuf[DNS_DS_BUFFERSIZE]; if (dst_key_id(k->key) != cds.key_tag || - dst_key_alg(k->key) != cds.algorithm) + dst_algorithm_tosecalg(dst_key_alg(k->key)) != + cds.algorithm) { continue; } @@ -20609,7 +20624,9 @@ failure: * are any signatures using that algorithm. */ static bool -signed_with_alg(dns_rdataset_t *rdataset, dns_secalg_t alg) { +signed_with_alg(dns_rdataset_t *rdataset, dst_algorithm_t alg) { + dst_algorithm_t sigalg; + REQUIRE(rdataset == NULL || rdataset->type == dns_rdatatype_rrsig); if (rdataset == NULL || !dns_rdataset_isassociated(rdataset)) { return false; @@ -20621,7 +20638,9 @@ signed_with_alg(dns_rdataset_t *rdataset, dns_secalg_t alg) { dns_rdataset_current(rdataset, &rdata); dns_rdata_tostruct(&rdata, &rrsig, NULL); - if (rrsig.algorithm == alg) { + sigalg = dst_algorithm_fromdata(rrsig.algorithm, + rrsig.signature, rrsig.siglen); + if (sigalg == alg) { return true; } } @@ -20967,7 +20986,9 @@ checkds_done(void *arg) { if (dst_key_id(key->key) != ds.key_tag) { continue; } - if (dst_key_alg(key->key) != ds.algorithm) { + if (dst_algorithm_tosecalg(dst_key_alg(key->key)) != + ds.algorithm) + { continue; } /* Derive DS from DNSKEY, see if the rdata is equal. */ diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c index fdd46f3bbe..d6cce894eb 100644 --- a/lib/dns/zoneverify.c +++ b/lib/dns/zoneverify.c @@ -173,12 +173,16 @@ goodsig(const vctx_t *vctx, dns_rdata_t *sigrdata, const dns_name_t *name, dst_key_t **dstkeys, size_t nkeys, dns_rdataset_t *rdataset) { dns_rdata_rrsig_t sig; isc_result_t result; + dst_algorithm_t algorithm; result = dns_rdata_tostruct(sigrdata, &sig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); + algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature, + sig.siglen); + for (size_t key = 0; key < nkeys; key++) { - if (sig.algorithm != dst_key_alg(dstkeys[key]) || + if (algorithm != dst_key_alg(dstkeys[key]) || sig.keyid != dst_key_id(dstkeys[key]) || !dns_name_equal(&sig.signer, vctx->origin)) { @@ -835,6 +839,7 @@ verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name, DNS_RDATASET_FOREACH (&sigrdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_rrsig_t sig; + dst_algorithm_t algorithm; dns_rdataset_current(&sigrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &sig, NULL); @@ -849,15 +854,17 @@ verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name, namebuf, typebuf, sig.keyid); continue; } - if ((set_algorithms[sig.algorithm] != 0) || - (vctx->act_algorithms[sig.algorithm] == 0)) + algorithm = dst_algorithm_fromdata(sig.algorithm, sig.signature, + sig.siglen); + if ((set_algorithms[algorithm] != 0) || + (vctx->act_algorithms[algorithm] == 0)) { continue; } if (goodsig(vctx, &rdata, name, dstkeys, nkeys, rdataset)) { dns_rdataset_settrust(rdataset, dns_trust_secure); dns_rdataset_settrust(&sigrdataset, dns_trust_secure); - set_algorithms[sig.algorithm] = 1; + set_algorithms[algorithm] = 1; } } result = ISC_R_SUCCESS; @@ -871,7 +878,7 @@ verifyset(vctx_t *vctx, dns_rdataset_t *rdataset, const dns_name_t *name, if ((vctx->act_algorithms[i] != 0) && (set_algorithms[i] == 0)) { - dns_secalg_format(i, algbuf, sizeof(algbuf)); + dst_algorithm_format(i, algbuf, sizeof(algbuf)); zoneverify_log_error(vctx, "No correct %s signature " "for %s %s", @@ -1425,10 +1432,13 @@ check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey, dst_key_t *key = NULL; isc_result_t result; dns_rdataset_t dsset; + dst_algorithm_t algorithm; active_keys = (is_ksk ? vctx->ksk_algorithms : vctx->zsk_algorithms); standby_keys = (is_ksk ? vctx->standby_ksk : vctx->standby_zsk); goodkey = (is_ksk ? &vctx->goodksk : &vctx->goodzsk); + algorithm = dst_algorithm_fromdata(dnskey->algorithm, dnskey->data, + dnskey->datalen); /* * First, does this key sign the DNSKEY rrset? @@ -1440,19 +1450,19 @@ check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey, dns_dnssec_signs(keyrdata, vctx->origin, &vctx->soaset, &vctx->soasigs, false, vctx->mctx)) { - if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { - active_keys[dnskey->algorithm]++; + if (active_keys[algorithm] != DNS_KEYALG_MAX) { + active_keys[algorithm]++; } } else { - if (standby_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { - standby_keys[dnskey->algorithm]++; + if (standby_keys[algorithm] != DNS_KEYALG_MAX) { + standby_keys[algorithm]++; } } return; } - if (active_keys[dnskey->algorithm] != DNS_KEYALG_MAX) { - active_keys[dnskey->algorithm]++; + if (active_keys[algorithm] != DNS_KEYALG_MAX) { + active_keys[algorithm]++; } /* @@ -1503,7 +1513,8 @@ check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey, RUNTIME_CHECK(result == ISC_R_SUCCESS); if (ds.key_tag != dst_key_id(key) || - ds.algorithm != dst_key_alg(key)) + ds.algorithm != + dst_algorithm_tosecalg(dst_key_alg(key))) { continue; } @@ -1558,6 +1569,7 @@ check_dnskey(vctx_t *vctx) { if ((dnskey.flags & DNS_KEYOWNER_ZONE) != 0 && (dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) { + dst_algorithm_t algorithm; if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && !dns_dnssec_selfsigns(&rdata, vctx->origin, &vctx->keyset, &vctx->keysigs, @@ -1586,16 +1598,17 @@ check_dnskey(vctx_t *vctx) { buffer); return ISC_R_FAILURE; } + algorithm = dst_algorithm_fromdata( + dnskey.algorithm, dnskey.data, dnskey.datalen); if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 && - vctx->revoked_ksk[dnskey.algorithm] != - DNS_KEYALG_MAX) + vctx->revoked_ksk[algorithm] != DNS_KEYALG_MAX) { - vctx->revoked_ksk[dnskey.algorithm]++; + vctx->revoked_ksk[algorithm]++; } else if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0 && - vctx->revoked_zsk[dnskey.algorithm] != + vctx->revoked_zsk[algorithm] != DNS_KEYALG_MAX) { - vctx->revoked_zsk[dnskey.algorithm]++; + vctx->revoked_zsk[algorithm]++; } } else { check_dnskey_sigs(vctx, &dnskey, &rdata, is_ksk); @@ -1627,7 +1640,7 @@ determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag, : 0; } if (vctx->act_algorithms[i] != 0) { - dns_secalg_format(i, algbuf, sizeof(algbuf)); + dst_algorithm_format(i, algbuf, sizeof(algbuf)); report("- %s", algbuf); } } @@ -1646,7 +1659,7 @@ determine_active_algorithms(vctx_t *vctx, bool ignore_kskflag, { continue; } - dns_secalg_format(i, algbuf, sizeof(algbuf)); + dst_algorithm_format(i, algbuf, sizeof(algbuf)); zoneverify_log_error(vctx, "Missing %s for algorithm %s", (vctx->ksk_algorithms[i] != 0) ? "ZSK" : "self-" @@ -1889,7 +1902,7 @@ check_bad_algorithms(const vctx_t *vctx, void (*report)(const char *, ...)) { report("The zone is not fully signed " "for the following algorithms:"); } - dns_secalg_format(i, algbuf, sizeof(algbuf)); + dst_algorithm_format(i, algbuf, sizeof(algbuf)); report(" %s", algbuf); first = false; } @@ -1916,7 +1929,7 @@ print_summary(const vctx_t *vctx, bool keyset_kskonly, { continue; } - dns_secalg_format(i, algbuf, sizeof(algbuf)); + dst_algorithm_format(i, algbuf, sizeof(algbuf)); report("Algorithm: %s: KSKs: " "%u active, %u stand-by, %u revoked", algbuf, vctx->ksk_algorithms[i], vctx->standby_ksk[i], diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index 1e82acf9d3..af78c88c42 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -365,12 +365,12 @@ disabled_algorithms(const cfg_obj_t *disabled) { CFG_LIST_FOREACH (obj, element) { isc_textregion_t r; - dns_secalg_t alg; + dst_algorithm_t alg; r.base = UNCONST(cfg_obj_asstring(cfg_listelt_value(element))); r.length = strlen(r.base); - tresult = dns_secalg_fromtext(&alg, &r); + tresult = dst_algorithm_fromtext(&alg, &r); if (tresult != ISC_R_SUCCESS) { cfg_obj_log(cfg_listelt_value(element), ISC_LOG_ERROR, "invalid algorithm '%s'", r.base); diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 0e29e8d39e..21e09b079e 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -31,6 +31,8 @@ #include #include +#include + #include #include #include @@ -127,7 +129,7 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, INSIST(!offline_ksk); key->role |= DNS_KASP_KEY_ROLE_KSK | DNS_KASP_KEY_ROLE_ZSK; key->lifetime = 0; /* unlimited */ - key->algorithm = DNS_KEYALG_ECDSA256; + key->algorithm = DST_ALG_ECDSA256; key->length = -1; result = dns_keystorelist_find(keystorelist, DNS_KEYSTORE_KEYDIRECTORY, @@ -217,8 +219,8 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, obj = cfg_tuple_get(config, "algorithm"); alg.base = cfg_obj_asstring(obj); alg.length = strlen(alg.base); - result = dns_secalg_fromtext(&key->algorithm, - (isc_textregion_t *)&alg); + result = dst_algorithm_fromtext(&key->algorithm, + (isc_textregion_t *)&alg); if (result != ISC_R_SUCCESS) { cfg_obj_log(obj, ISC_LOG_ERROR, "dnssec-policy: bad algorithm %s", @@ -226,10 +228,9 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, result = DNS_R_BADALG; goto cleanup; } - if (check_algorithms && isc_crypto_fips_mode() && - (key->algorithm == DNS_KEYALG_RSASHA1 || - key->algorithm == DNS_KEYALG_NSEC3RSASHA1)) + (key->algorithm == DST_ALG_RSASHA1 || + key->algorithm == DST_ALG_NSEC3RSASHA1)) { cfg_obj_log(obj, ISC_LOG_ERROR, "dnssec-policy: algorithm %s not supported " @@ -255,14 +256,14 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, size = cfg_obj_asuint32(obj); switch (key->algorithm) { - case DNS_KEYALG_RSASHA1: - case DNS_KEYALG_NSEC3RSASHA1: - case DNS_KEYALG_RSASHA256: - case DNS_KEYALG_RSASHA512: + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: + case DST_ALG_RSASHA512: if (isc_crypto_fips_mode()) { min = 2048; } else { - min = DNS_KEYALG_RSASHA512 ? 1024 : 512; + min = DST_ALG_RSASHA512 ? 1024 : 512; } if (size < min || size > 4096) { cfg_obj_log(obj, ISC_LOG_ERROR, @@ -274,10 +275,10 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, goto cleanup; } break; - case DNS_KEYALG_ECDSA256: - case DNS_KEYALG_ECDSA384: - case DNS_KEYALG_ED25519: - case DNS_KEYALG_ED448: + case DST_ALG_ECDSA256: + case DST_ALG_ECDSA384: + case DST_ALG_ED25519: + case DST_ALG_ED448: cfg_obj_log(obj, ISC_LOG_WARNING, "dnssec-policy: key algorithm %s " "has predefined length; ignoring " @@ -356,8 +357,8 @@ cfg_nsec3param_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp) { } /* NSEC3 cannot be used with certain key algorithms. */ - if (keyalg == DNS_KEYALG_RSAMD5 || keyalg == DNS_KEYALG_DSA || - keyalg == DNS_KEYALG_RSASHA1) + if (keyalg == DST_ALG_RSAMD5 || keyalg == DST_ALG_DSA || + keyalg == DST_ALG_RSASHA1) { badalg = keyalg; } diff --git a/lib/ns/query.c b/lib/ns/query.c index 9ccda552f8..85e01f9f83 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -2375,6 +2375,7 @@ get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig, bool secure = false; dns_clientinfomethods_t cm; dns_clientinfo_t ci; + dst_algorithm_t sigalg, keyalg; dns_clientinfomethods_init(&cm, ns_client_sourceip); dns_clientinfo_init(&ci, client, NULL); @@ -2403,6 +2404,9 @@ get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig, result = dns_rdataset_next(keyrdataset); } + sigalg = dst_algorithm_fromdata(rrsig->algorithm, rrsig->signature, + rrsig->siglen); + for (; result == ISC_R_SUCCESS; result = dns_rdataset_next(keyrdataset)) { dns_rdata_t rdata = DNS_RDATA_INIT; @@ -2411,10 +2415,10 @@ get_key(ns_client_t *client, dns_db_t *db, dns_rdata_rrsig_t *rrsig, dns_rdataset_current(keyrdataset, &rdata); dns_rdata_tostruct(&rdata, &key, NULL); /* can't fail */ + keyalg = dst_algorithm_fromdata(key.algorithm, key.data, + key.datalen); - if (rrsig->algorithm != key.algorithm || - !dns_dnssec_iszonekey(&key)) - { + if (sigalg != keyalg || !dns_dnssec_iszonekey(&key)) { continue; } @@ -2481,9 +2485,12 @@ validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, { char txt[DNS_NAME_FORMATSIZE + 32]; isc_buffer_t buffer; + dst_algorithm_t alg; + alg = dst_algorithm_fromdata( + rrsig.algorithm, rrsig.signature, rrsig.siglen); isc_buffer_init(&buffer, txt, sizeof(txt)); - dns_secalg_totext(rrsig.algorithm, &buffer); + dst_algorithm_totext(alg, &buffer); isc_buffer_putstr(&buffer, " "); dns_name_totext(name, DNS_NAME_OMITFINALDOT, &buffer); isc_buffer_putstr(&buffer, " (cached)"); From 62f3c0b5012098d5a0690b05148f6ea862e05426 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Fri, 16 May 2025 14:46:22 +1000 Subject: [PATCH 07/15] Extend the trust anchor supported algorithm checks When a DNSKEY based trust anchor is used extract the algorithm from the key's data to determine if the private algorithm is supported. --- bin/named/server.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index 397a89df89..ddfd7a3855 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -154,11 +154,11 @@ #endif /* HAVE_LMDB */ #ifndef SIZE_MAX -#define SIZE_MAX ((size_t)-1) +#define SIZE_MAX ((size_t)(-1)) #endif /* ifndef SIZE_MAX */ #ifndef SIZE_AS_PERCENT -#define SIZE_AS_PERCENT ((size_t)-2) +#define SIZE_AS_PERCENT ((size_t)(-2)) #endif /* ifndef SIZE_AS_PERCENT */ /* RFC7828 defines timeout as 16-bit value specified in units of 100 @@ -699,6 +699,7 @@ ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, STATIC_DS, TRUSTED } anchortype; + dst_algorithm_t algorithm; REQUIRE(namestrp != NULL && *namestrp == NULL); REQUIRE(ds != NULL); @@ -787,17 +788,19 @@ ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, keystruct.flags = (uint16_t)rdata1; keystruct.protocol = (uint8_t)rdata2; keystruct.algorithm = (uint8_t)rdata3; - - if (!dst_algorithm_supported(keystruct.algorithm)) { - CHECK(DST_R_UNSUPPORTEDALG); - } - datastr = cfg_obj_asstring(cfg_tuple_get(key, "data")); CHECK(isc_base64_decodestring(datastr, &databuf)); isc_buffer_usedregion(&databuf, &r); keystruct.datalen = r.length; keystruct.data = r.base; + algorithm = dst_algorithm_fromdata( + keystruct.algorithm, keystruct.data, keystruct.datalen); + + if (!dst_algorithm_supported(algorithm)) { + CHECK(DST_R_UNSUPPORTEDALG); + } + CHECK(dns_rdata_fromstruct(&rdata, keystruct.common.rdclass, keystruct.common.rdtype, &keystruct, &rrdatabuf)); From eb184b864c18c2e6a045c0006f4dd20a7bb46160 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 16 Apr 2025 11:31:41 +1000 Subject: [PATCH 08/15] Support PRIVATEOID/PRIVATEDNS in the resolver dns_resolver_algorithm_supported() has been extended so in addition to an algorithm number, it can also take a pointer to an RRSIG signature field in which key information is encoded. --- bin/named/server.c | 2 +- lib/dns/include/dns/resolver.h | 8 +++++++- lib/dns/resolver.c | 26 +++++++++++++++++++++++++- lib/dns/validator.c | 12 +++++++----- lib/ns/query.c | 5 +++-- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/bin/named/server.c b/bin/named/server.c index ddfd7a3855..5a333cc937 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -958,7 +958,7 @@ process_key(const cfg_obj_t *key, dns_keytable_t *secroots, * warning, but do not prevent further keys from being processed. */ if (!dns_resolver_algorithm_supported(view->resolver, keyname, - ds.algorithm)) + ds.algorithm, NULL, 0)) { cfg_obj_log(key, ISC_LOG_WARNING, "ignoring %s for '%s': algorithm is disabled", diff --git a/lib/dns/include/dns/resolver.h b/lib/dns/include/dns/resolver.h index 459e451731..a16d730619 100644 --- a/lib/dns/include/dns/resolver.h +++ b/lib/dns/include/dns/resolver.h @@ -434,12 +434,18 @@ dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name, bool dns_resolver_algorithm_supported(dns_resolver_t *resolver, - const dns_name_t *name, unsigned int alg); + const dns_name_t *name, unsigned int alg, + unsigned char *private, size_t len); /*%< * Check if the given algorithm is supported by this resolver. * This checks whether the algorithm has been disabled via * dns_resolver_disable_algorithm(), then checks the underlying * crypto libraries if it was not specifically disabled. + * + * The algorithm is specified with the value 'alg' or, if + * 'alg' is PRIVATEOID or PRIVATEDNS, then the algorithm is + * encoded as a DNS name or OID in the first 'len' bytes of + * 'private'. */ bool diff --git a/lib/dns/resolver.c b/lib/dns/resolver.c index 93d1919796..8947973236 100644 --- a/lib/dns/resolver.c +++ b/lib/dns/resolver.c @@ -10614,13 +10614,37 @@ dns_resolver_disable_ds_digest(dns_resolver_t *resolver, const dns_name_t *name, bool dns_resolver_algorithm_supported(dns_resolver_t *resolver, - const dns_name_t *name, unsigned int alg) { + const dns_name_t *name, unsigned int alg, + unsigned char *private, size_t len) { REQUIRE(VALID_RESOLVER(resolver)); if ((alg == DST_ALG_DH) || (alg == DST_ALG_INDIRECT)) { return false; } + /* + * Look up the DST algorithm identifier for private-OID + * and private-DNS keys. + */ + if (alg == DST_ALG_PRIVATEDNS && private != NULL) { + isc_buffer_t b; + isc_buffer_init(&b, private, len); + isc_buffer_add(&b, len); + alg = dst_algorithm_fromprivatedns(&b); + if (alg == 0) { + return false; + } + } + + if (alg == DST_ALG_PRIVATEOID && private != NULL) { + isc_buffer_t b; + isc_buffer_init(&b, private, len); + isc_buffer_add(&b, len); + alg = dst_algorithm_fromprivateoid(&b); + if (alg == 0) { + return false; + } + } if (dns_nametree_covered(resolver->algorithms, name, NULL, alg)) { return false; } diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 4882262b95..d4eff70580 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -1641,8 +1641,9 @@ validate_answer_process(void *arg) { * At this point we could check that the signature algorithm * was known and "sufficiently good". */ - if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, - val->siginfo->algorithm)) + if (!dns_resolver_algorithm_supported( + val->view->resolver, val->name, val->siginfo->algorithm, + val->siginfo->signature, val->siginfo->siglen)) { if (val->unsupported_algorithm == 0) { val->unsupported_algorithm = val->siginfo->algorithm; @@ -2011,7 +2012,7 @@ validate_dnskey_dsset(dns_validator_t *val) { } if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, - ds.algorithm)) + ds.algorithm, NULL, 0)) { if (val->unsupported_algorithm == 0) { val->unsupported_algorithm = ds.algorithm; @@ -2213,7 +2214,8 @@ validate_dnskey(void *arg) { } if (!dns_resolver_algorithm_supported(val->view->resolver, - val->name, ds.algorithm)) + val->name, ds.algorithm, + NULL, 0)) { continue; } @@ -2916,7 +2918,7 @@ check_ds_algs(dns_validator_t *val, dns_name_t *name, if (dns_resolver_ds_digest_supported(val->view->resolver, name, ds.digest_type) && dns_resolver_algorithm_supported(val->view->resolver, name, - ds.algorithm)) + ds.algorithm, NULL, 0)) { return true; } diff --git a/lib/ns/query.c b/lib/ns/query.c index 85e01f9f83..e4e769d85b 100644 --- a/lib/ns/query.c +++ b/lib/ns/query.c @@ -2480,8 +2480,9 @@ validate(ns_client_t *client, dns_db_t *db, dns_name_t *name, dns_rdataset_current(sigrdataset, &rdata); result = dns_rdata_tostruct(&rdata, &rrsig, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); - if (!dns_resolver_algorithm_supported(client->view->resolver, - name, rrsig.algorithm)) + if (!dns_resolver_algorithm_supported( + client->view->resolver, name, rrsig.algorithm, + rrsig.signature, rrsig.siglen)) { char txt[DNS_NAME_FORMATSIZE + 32]; isc_buffer_t buffer; From 05c5f79d5805eb5857f566e77d882ddf4cd84bdf Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Fri, 28 Mar 2025 14:08:06 +1100 Subject: [PATCH 09/15] Support PRIVATEOID/PRIVATEDNS in the validator DS records need to checked against the DNSKEY RRset to find the private algorithm they correspond to. --- lib/dns/include/dns/validator.h | 1 + lib/dns/validator.c | 360 ++++++++++++++++++++++++++------ 2 files changed, 302 insertions(+), 59 deletions(-) diff --git a/lib/dns/include/dns/validator.h b/lib/dns/include/dns/validator.h index 00d56d6866..6132ad88ec 100644 --- a/lib/dns/include/dns/validator.h +++ b/lib/dns/include/dns/validator.h @@ -137,6 +137,7 @@ struct dns_validator { dns_rdataset_t fdsset; dns_rdataset_t frdataset; dns_rdataset_t fsigrdataset; + dns_rdataset_t dsrdataset; dns_fixedname_t fname; dns_fixedname_t wild; dns_fixedname_t closest; diff --git a/lib/dns/validator.c b/lib/dns/validator.c index d4eff70580..a7da3f4cdb 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -146,7 +146,8 @@ static isc_result_t validate_nx(dns_validator_t *val, bool resume); static isc_result_t -proveunsecure(dns_validator_t *val, bool have_ds, bool resume); +proveunsecure(dns_validator_t *val, bool have_ds, bool have_dnskey, + bool resume); static void validator_logv(dns_validator_t *val, isc_logcategory_t category, @@ -397,6 +398,13 @@ fetch_callback_dnskey(void *arg) { dns_rdataset_t *rdataset = &val->frdataset; isc_result_t eresult = resp->result; isc_result_t result; + bool trustchain; + + /* + * Set 'trustchain' to true if we're walking a chain of + * trust; false if we're attempting to prove insecurity. + */ + trustchain = ((val->attributes & VALATTR_INSECURITY) == 0); /* Free resources which are not of interest. */ if (resp->node != NULL) { @@ -418,34 +426,55 @@ fetch_callback_dnskey(void *arg) { goto cleanup; } - switch (eresult) { - case ISC_R_SUCCESS: - case DNS_R_NCACHENXRRSET: - /* - * We have an answer to our DNSKEY query. Either the DNSKEY - * RRset or a NODATA response. - */ - validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s", - eresult == ISC_R_SUCCESS ? "keyset" - : "NCACHENXRRSET", - dns_trust_totext(rdataset->trust)); - /* - * Only extract the dst key if the keyset exists and is secure. - */ - if (eresult == ISC_R_SUCCESS && - rdataset->trust >= dns_trust_secure) - { - result = validate_helper_run(val, - resume_answer_with_key); - } else { - result = validate_async_run(val, resume_answer); + if (trustchain) { + switch (eresult) { + case ISC_R_SUCCESS: + case DNS_R_NCACHENXRRSET: + /* + * We have an answer to our DNSKEY query. Either the + * DNSKEY RRset or a NODATA response. + */ + validator_log(val, ISC_LOG_DEBUG(3), "%s with trust %s", + eresult == ISC_R_SUCCESS + ? "keyset" + : "NCACHENXRRSET", + dns_trust_totext(rdataset->trust)); + /* + * Only extract the dst key if the keyset exists and is + * secure. + */ + if (eresult == ISC_R_SUCCESS && + rdataset->trust >= dns_trust_secure) + { + result = validate_helper_run( + val, resume_answer_with_key); + } else { + result = validate_async_run(val, resume_answer); + } + break; + default: + validator_log(val, ISC_LOG_DEBUG(3), + "fetch_callback_dnskey: got %s", + isc_result_totext(eresult)); + result = DNS_R_BROKENCHAIN; + break; + } + } else { + switch (eresult) { + case ISC_R_SUCCESS: + /* + * We have a DS (val->dsrdataset) and + * DNSKEY (val->fdataset). + */ + result = proveunsecure(val, false, true, true); + break; + default: + validator_log(val, ISC_LOG_DEBUG(3), + "fetch_callback_dnskey: got %s", + isc_result_totext(eresult)); + result = DNS_R_BROKENCHAIN; + break; } - break; - default: - validator_log(val, ISC_LOG_DEBUG(3), - "fetch_callback_dnskey: got %s", - isc_result_totext(eresult)); - result = DNS_R_BROKENCHAIN; } cleanup: @@ -518,7 +547,7 @@ fetch_callback_ds(void *arg) { validator_log(val, ISC_LOG_DEBUG(3), "falling back to insecurity proof (%s)", isc_result_totext(eresult)); - result = proveunsecure(val, false, false); + result = proveunsecure(val, false, false, false); break; default: validator_log(val, ISC_LOG_DEBUG(3), @@ -537,7 +566,7 @@ fetch_callback_ds(void *arg) { * trust. */ - result = proveunsecure(val, false, true); + result = proveunsecure(val, false, false, true); break; case ISC_R_SUCCESS: /* @@ -546,7 +575,7 @@ fetch_callback_ds(void *arg) { * so keep looking for the break in the chain * of trust. */ - result = proveunsecure(val, true, true); + result = proveunsecure(val, true, false, true); break; case DNS_R_NXRRSET: case DNS_R_NCACHENXRRSET: @@ -567,13 +596,14 @@ fetch_callback_ds(void *arg) { * Not a zone cut, so we have to keep looking for * the break point in the chain of trust. */ - result = proveunsecure(val, false, true); + result = proveunsecure(val, false, false, true); break; default: validator_log(val, ISC_LOG_DEBUG(3), "fetch_callback_ds: got %s", isc_result_totext(eresult)); result = DNS_R_BROKENCHAIN; + break; } } @@ -673,7 +703,7 @@ validator_callback_ds(void *arg) { { result = markanswer(val, "validator_callback_ds"); } else if ((val->attributes & VALATTR_INSECURITY) != 0) { - result = proveunsecure(val, have_dsset, true); + result = proveunsecure(val, have_dsset, false, true); } else { result = validate_async_run(val, validate_dnskey); } @@ -724,7 +754,7 @@ validator_callback_cname(void *arg) { if (eresult == ISC_R_SUCCESS) { validator_log(val, ISC_LOG_DEBUG(3), "cname with trust %s", dns_trust_totext(val->frdataset.trust)); - result = proveunsecure(val, false, true); + result = proveunsecure(val, false, false, true); } else { if (eresult != DNS_R_BROKENCHAIN) { expire_rdatasets(val); @@ -832,6 +862,7 @@ validator_callback_nsec(void *arg) { FALLTHROUGH; default: result = validate_nx(val, true); + break; } } @@ -1364,6 +1395,7 @@ selfsigned_dnskey(dns_validator_t *val) { return ISC_R_QUOTA; } consume_validation_fail(val); + break; } } else if (rdataset->trust >= dns_trust_secure) { /* @@ -1469,6 +1501,7 @@ again: break; } consume_validation_fail(val); + break; } return result; } @@ -1815,7 +1848,7 @@ validate_async_done(dns_validator_t *val, isc_result_t result) { isc_result_t saved_result = result; validator_log(val, ISC_LOG_DEBUG(3), "falling back to insecurity proof"); - result = proveunsecure(val, false, false); + result = proveunsecure(val, false, false, false); if (result == DNS_R_NOTINSECURE) { result = saved_result; } @@ -1976,6 +2009,7 @@ validate_dnskey_dsset_done(dns_validator_t *val, isc_result_t result) { validator_log(val, ISC_LOG_INFO, "no valid signature found (DS)"); result = DNS_R_NOVALIDSIG; + break; } if (val->dsset == &val->fdsset) { @@ -1992,6 +2026,7 @@ validate_dnskey_dsset(dns_validator_t *val) { dns_rdata_t keyrdata = DNS_RDATA_INIT; isc_result_t result; dns_rdata_ds_t ds; + dns_rdata_dnskey_t key; dns_rdata_reset(&dsrdata); dns_rdataset_current(val->dsset, &dsrdata); @@ -2011,13 +2046,18 @@ validate_dnskey_dsset(dns_validator_t *val) { return DNS_R_BADALG; } - if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, - ds.algorithm, NULL, 0)) + if (ds.algorithm != DNS_KEYALG_PRIVATEDNS && + ds.algorithm != DNS_KEYALG_PRIVATEOID) { - if (val->unsupported_algorithm == 0) { - val->unsupported_algorithm = ds.algorithm; + if (!dns_resolver_algorithm_supported(val->view->resolver, + val->name, ds.algorithm, + NULL, 0)) + { + if (val->unsupported_algorithm == 0) { + val->unsupported_algorithm = ds.algorithm; + } + return DNS_R_BADALG; } - return DNS_R_BADALG; } /* @@ -2030,6 +2070,25 @@ validate_dnskey_dsset(dns_validator_t *val) { return DNS_R_NOKEYMATCH; } + /* + * Figure out if the private algorithm is supported now that we have + * found a matching dnskey. + */ + dns_rdata_tostruct(&keyrdata, &key, NULL); + if (ds.algorithm == DNS_KEYALG_PRIVATEDNS || + ds.algorithm == DNS_KEYALG_PRIVATEOID) + { + if (!dns_resolver_algorithm_supported(val->view->resolver, + val->name, key.algorithm, + key.data, key.datalen)) + { + if (val->unsupported_algorithm == 0) { + val->unsupported_algorithm = key.algorithm; + } + return DNS_R_BADALG; + } + } + /* * ... and check that it signed the DNSKEY RRset. */ @@ -2896,7 +2955,31 @@ validate_nx(dns_validator_t *val, bool resume) { return DNS_R_BROKENCHAIN; } - return proveunsecure(val, false, false); + return proveunsecure(val, false, false, false); +} + +/* + * Check if any of the DS records has a private DNSSEC algorithm. + */ +static bool +check_ds_private(dns_rdataset_t *rdataset) { + dns_rdata_ds_t ds; + isc_result_t result; + + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdataset_current(rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + if (ds.algorithm == DNS_KEYALG_PRIVATEDNS || + ds.algorithm == DNS_KEYALG_PRIVATEOID) + { + return true; + } + } + return false; } /*% @@ -2905,20 +2988,65 @@ validate_nx(dns_validator_t *val, bool resume) { */ static bool check_ds_algs(dns_validator_t *val, dns_name_t *name, - dns_rdataset_t *rdataset) { + dns_rdataset_t *dsrdataset, dns_rdataset_t *dnskeyset) { dns_rdata_ds_t ds; + dns_rdata_dnskey_t key; + bool seen_private = false; + uint16_t key_tag = 0; + uint8_t algorithm = 0; - DNS_RDATASET_FOREACH (rdataset) { + DNS_RDATASET_FOREACH (dsrdataset) { isc_result_t result; dns_rdata_t dsrdata = DNS_RDATA_INIT; - dns_rdataset_current(rdataset, &dsrdata); + dns_rdata_t keyrdata = DNS_RDATA_INIT; + unsigned char *data = NULL; + size_t datalen = 0; + + dns_rdataset_current(dsrdataset, &dsrdata); result = dns_rdata_tostruct(&dsrdata, &ds, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); + /* + * Look for a matching DNSKEY to find the PRIVATE + * DNSSEC algorithm. + */ + if (ds.algorithm == DNS_KEYALG_PRIVATEOID || + ds.algorithm == DNS_KEYALG_PRIVATEDNS) + { + switch (ds.digest_type) { + case DNS_DSDIGEST_SHA1: + case DNS_DSDIGEST_SHA256: + case DNS_DSDIGEST_SHA384: + if (dnskeyset == NULL) { + algorithm = ds.algorithm; + key_tag = ds.key_tag; + seen_private = true; + continue; + } + result = dns_dnssec_matchdskey( + name, &dsrdata, dnskeyset, &keyrdata); + if (result != ISC_R_SUCCESS) { + algorithm = ds.algorithm; + key_tag = ds.key_tag; + seen_private = true; + continue; + } + result = dns_rdata_tostruct(&keyrdata, &key, + NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + data = key.data; + datalen = key.datalen; + break; + default: + break; + } + } + if (dns_resolver_ds_digest_supported(val->view->resolver, name, ds.digest_type) && dns_resolver_algorithm_supported(val->view->resolver, name, - ds.algorithm, NULL, 0)) + ds.algorithm, data, + datalen)) { return true; } @@ -2929,8 +3057,25 @@ check_ds_algs(dns_validator_t *val, dns_name_t *name, * unsecure flow always runs after a validate/validatenx flow. So if an * unsupported alg/digest was found while building the chain of trust, * it would be raised already. + * + * If we have seen a private algorithm for which we couldn't find a + * DNSKEY we must assume the child zone is secure. With PRIVATEDNS and + * PRIVATEOID we can only make that determination if we match a DNSKEY + * for every DS with these algorithms. Since we don't know whether the + * private algorithm is unsupported or not, we are required to treat it + * as supported. */ - return false; + if (seen_private) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(name, namebuf, sizeof(namebuf)); + validator_log(val, ISC_LOG_INFO, + "No DNSKEY for %s/DS with %s algorithm, tag %u", + namebuf, + algorithm == DNS_KEYALG_PRIVATEDNS ? "PRIVATEDNS" + : "PRIVATEOID", + key_tag); + } + return seen_private; } /*% @@ -2973,7 +3118,49 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) { * validated, continue walking down labels. */ if (val->frdataset.trust >= dns_trust_secure) { - if (!check_ds_algs(val, tname, &val->frdataset)) { + dns_rdataset_t *dssetp = &val->frdataset, + *keysetp = NULL; + + if (check_ds_private(&val->frdataset)) { + if (dns_rdataset_isassociated(&val->dsrdataset)) + { + dns_rdataset_disassociate( + &val->dsrdataset); + } + dns_rdataset_clone(&val->frdataset, + &val->dsrdataset); + dssetp = &val->dsrdataset; + dns_rdataset_disassociate(&val->frdataset); + result = view_find(val, tname, + dns_rdatatype_dnskey); + switch (result) { + case ISC_R_SUCCESS: + keysetp = &val->frdataset; + break; + case ISC_R_NOTFOUND: + /* + * We don't know anything about the + * DNSKEY. Find it. + */ + *resp = DNS_R_WAIT; + result = create_fetch( + val, tname, + dns_rdatatype_dnskey, + fetch_callback_dnskey, + "seek_ds"); + if (result != ISC_R_SUCCESS) { + *resp = result; + } + return ISC_R_COMPLETE; + break; + default: + validator_log(val, ISC_LOG_DEBUG(3), + "no DNSKEY found (%s/DS)", + namebuf); + break; + } + } + if (!check_ds_algs(val, tname, dssetp, keysetp)) { validator_log( val, ISC_LOG_DEBUG(3), "no supported algorithm/digest (%s/DS)", @@ -3162,13 +3349,16 @@ seek_ds(dns_validator_t *val, isc_result_t *resp) { * \li DNS_R_BROKENCHAIN */ static isc_result_t -proveunsecure(dns_validator_t *val, bool have_ds, bool resume) { +proveunsecure(dns_validator_t *val, bool have_ds, bool have_dnskey, + bool resume) { isc_result_t result; char namebuf[DNS_NAME_FORMATSIZE]; dns_fixedname_t fixedsecroot; dns_name_t *secroot = dns_fixedname_initname(&fixedsecroot); unsigned int labels; + INSIST(!(have_ds && have_dnskey)); + /* * We're attempting to prove insecurity. */ @@ -3208,17 +3398,65 @@ proveunsecure(dns_validator_t *val, bool have_ds, bool resume) { * it has a supported algorithm combination. If not, this is * an insecure delegation as far as this resolver is concerned. */ - if (have_ds && val->frdataset.trust >= dns_trust_secure && - !check_ds_algs(val, dns_fixedname_name(&val->fname), - &val->frdataset)) + if (have_dnskey || + (have_ds && val->frdataset.trust >= dns_trust_secure)) { - dns_name_format(dns_fixedname_name(&val->fname), - namebuf, sizeof(namebuf)); - validator_log(val, ISC_LOG_DEBUG(3), - "no supported algorithm/digest (%s/DS)", - namebuf); - result = markanswer(val, "proveunsecure (2)"); - goto out; + dns_rdataset_t *dssetp = NULL, *keysetp = NULL; + dns_name_t *fname = dns_fixedname_name(&val->fname); + if (have_dnskey) { + dssetp = &val->dsrdataset; + keysetp = &val->frdataset; + } else { + dssetp = &val->frdataset; + } + + if (!have_dnskey && check_ds_private(&val->frdataset)) { + if (dns_rdataset_isassociated(&val->dsrdataset)) + { + dns_rdataset_disassociate( + &val->dsrdataset); + } + dns_rdataset_clone(&val->frdataset, + &val->dsrdataset); + dssetp = &val->dsrdataset; + dns_rdataset_disassociate(&val->frdataset); + result = view_find(val, fname, + dns_rdatatype_dnskey); + switch (result) { + case ISC_R_SUCCESS: + keysetp = &val->frdataset; + break; + case ISC_R_NOTFOUND: + /* + * We don't know anything about the + * DNSKEY. Find it. + */ + result = create_fetch( + val, fname, + dns_rdatatype_dnskey, + fetch_callback_dnskey, + "seek_ds"); + if (result == ISC_R_SUCCESS) { + result = DNS_R_WAIT; + } + goto out; + default: + validator_log(val, ISC_LOG_DEBUG(3), + "no DNSKEY found (%s/DS)", + namebuf); + break; + } + } + if (!check_ds_algs(val, fname, dssetp, keysetp)) { + dns_name_format(fname, namebuf, + sizeof(namebuf)); + validator_log( + val, ISC_LOG_DEBUG(3), + "no supported algorithm/digest (%s/DS)", + namebuf); + result = markanswer(val, "proveunsecure (2)"); + goto out; + } } val->labels++; } @@ -3309,7 +3547,7 @@ validator_start(void *arg) { validator_log(val, ISC_LOG_DEBUG(3), "attempting insecurity proof"); - result = proveunsecure(val, false, false); + result = proveunsecure(val, false, false, false); if (result == DNS_R_NOTINSECURE) { validator_log(val, ISC_LOG_INFO, "got insecure response; " @@ -3422,6 +3660,7 @@ dns_validator_create(dns_view_t *view, dns_name_t *name, dns_rdatatype_t type, dns_rdataset_init(&val->fdsset); dns_rdataset_init(&val->frdataset); dns_rdataset_init(&val->fsigrdataset); + dns_rdataset_init(&val->dsrdataset); dns_fixedname_init(&val->wild); dns_fixedname_init(&val->closest); val->start = isc_stdtime_now(); @@ -3497,6 +3736,9 @@ destroy_validator(dns_validator_t *val) { dns_keytable_detach(&val->keytable); } disassociate_rdatasets(val); + if (dns_rdataset_isassociated(&val->dsrdataset)) { + dns_rdataset_disassociate(&val->dsrdataset); + } mctx = val->view->mctx; if (val->siginfo != NULL) { isc_mem_put(mctx, val->siginfo, sizeof(*val->siginfo)); From c428af5e7a36763bd7c42e040b9934a742ceea0e Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 8 Apr 2025 13:26:55 -0500 Subject: [PATCH 10/15] Support PRIVATEOID/PRIVATEDNS in zone.c - dns_zone_cdscheck() has been extended to extract the key algorithms from DNSKEY data when the CDS algorithm is PRIVATEOID or PRIVATEDNS. - dns_zone_signwithkey() has been extended to support signing with PRIVATEDNS and PRIVATEOID algorithms. The signing record (type 65534) added at the zone apex to indicate the current state of automatic zone signing can now contain an additional two-byte field for the DST algorithm value, when the DNS secalg value isn't enough information. --- bin/tests/system/autosign/tests.sh | 5 +- bin/tests/system/conf.sh | 7 +- bin/tests/system/dnssec/tests.sh | 26 ++-- lib/dns/include/dns/zone.h | 4 +- lib/dns/private.c | 28 ++++ lib/dns/validator.c | 6 + lib/dns/zone.c | 206 ++++++++++++++++++++--------- 7 files changed, 203 insertions(+), 79 deletions(-) diff --git a/bin/tests/system/autosign/tests.sh b/bin/tests/system/autosign/tests.sh index 14064affe2..298de9196c 100755 --- a/bin/tests/system/autosign/tests.sh +++ b/bin/tests/system/autosign/tests.sh @@ -31,12 +31,13 @@ showprivate() { $DIG $DIGOPTS +nodnssec +short @$2 -t ${4:-type65534} $1 | cut -f3 -d' ' \ | while read record; do $PERL -e 'my $rdata = pack("H*", @ARGV[0]); - die "invalid record" unless length($rdata) == 5; - my ($alg, $key, $remove, $complete) = unpack("CnCC", $rdata); + die "invalid record" unless length($rdata) == 5 || length($rdata) == 7; + my ($dns, $key, $remove, $complete, $alg) = unpack("CnCCn", $rdata); my $action = "signing"; $action = "removing" if $remove; my $state = " (incomplete)"; $state = " (complete)" if $complete; + $alg = $dns if ! defined($alg); print ("$action: alg: $alg, key: $key$state\n");' $record done } diff --git a/bin/tests/system/conf.sh b/bin/tests/system/conf.sh index e17e83116c..86059196dd 100644 --- a/bin/tests/system/conf.sh +++ b/bin/tests/system/conf.sh @@ -211,10 +211,15 @@ private_type_record() { _zone=$1 _algorithm=$2 _keyfile=$3 + _secalg=$2 _id=$(keyfile_to_key_id "$_keyfile") - printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_algorithm" "$_id" + if test "$_algorithm" -lt 256; then + printf "%s. 0 IN TYPE65534 %s 5 %02x%04x0000\n" "$_zone" "\\#" "$_secalg" "$_id" + else + printf "%s. 0 IN TYPE65534 %s 7 %02x%04x0000%04x\n" "$_zone" "\\#" "$_secalg" "$_id" "$_algorithm" + fi } # nextpart*() - functions for reading files incrementally diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 845adc306d..746dc57ed9 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -53,18 +53,20 @@ dnssec_loadkeys_on() { # convert private-type records to readable form showprivate() { echo "-- $* --" - dig_with_opts +nodnssec +short "@$2" -t type65534 "$1" | cut -f3 -d' ' \ - | while read -r record; do - # shellcheck disable=SC2016 - $PERL -e 'my $rdata = pack("H*", @ARGV[0]); - die "invalid record" unless length($rdata) == 5; - my ($alg, $key, $remove, $complete) = unpack("CnCC", $rdata); - my $action = "signing"; - $action = "removing" if $remove; - my $state = " (incomplete)"; - $state = " (complete)" if $complete; - print ("$action: alg: $alg, key: $key$state\n");' "$record" - done + dig_with_opts +nodnssec +short "@$2" -t type65534 "$1" >dig.out.$1.test$n + cut -f3 -d' ' length == 5) { + /* Old Form */ unsigned char alg = private->data[0]; dns_keytag_t keyid = (private->data[2] | private->data[1] << 8); char keybuf[DNS_SECALG_FORMATSIZE + BUFSIZ], @@ -371,6 +372,33 @@ dns_private_totext(dns_rdata_t *private, isc_buffer_t *buf) { isc_buffer_putstr(buf, "Signing with "); } + dns_secalg_format(alg, algbuf, sizeof(algbuf)); + snprintf(keybuf, sizeof(keybuf), "key %d/%s", keyid, algbuf); + isc_buffer_putstr(buf, keybuf); + } else if (private->length == 7) { + /* New Form - supports private types */ + dns_keytag_t keyid = private->data[2] | (private->data[1] << 8); + char keybuf[DNS_SECALG_FORMATSIZE + BUFSIZ], + algbuf[DNS_SECALG_FORMATSIZE]; + bool del = private->data[3]; + bool complete = private->data[4]; + dst_algorithm_t alg = private->data[6] | + (private->data[5] << 8); + + if (dst_algorithm_tosecalg(alg) != private->data[0]) { + return ISC_R_NOTFOUND; + } + + if (del && complete) { + isc_buffer_putstr(buf, "Done removing signatures for "); + } else if (del) { + isc_buffer_putstr(buf, "Removing signatures for "); + } else if (complete) { + isc_buffer_putstr(buf, "Done signing with "); + } else { + isc_buffer_putstr(buf, "Signing with "); + } + dns_secalg_format(alg, algbuf, sizeof(algbuf)); snprintf(keybuf, sizeof(keybuf), "key %d/%s", keyid, algbuf); isc_buffer_putstr(buf, keybuf); diff --git a/lib/dns/validator.c b/lib/dns/validator.c index a7da3f4cdb..369b4316f3 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -1680,6 +1680,9 @@ validate_answer_process(void *arg) { { if (val->unsupported_algorithm == 0) { val->unsupported_algorithm = val->siginfo->algorithm; + /* + * XXXMPA save PRIVATEOID/PRIVATEDNS identifier here + */ } goto next_key; } @@ -2084,6 +2087,9 @@ validate_dnskey_dsset(dns_validator_t *val) { { if (val->unsupported_algorithm == 0) { val->unsupported_algorithm = key.algorithm; + /* + * XXXMPA Save PRIVATEOID / PRIVATEDNS here. + */ } return DNS_R_BADALG; } diff --git a/lib/dns/zone.c b/lib/dns/zone.c index d3e62bbb0c..1e3ae54d18 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -139,7 +139,6 @@ #define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0) #define ID(x) dst_key_id(x) #define ALG(x) dst_key_alg(x) -#define DNSALG(x) dst_algorithm_tosecalg(dst_key_alg(x)) /*% * KASP flags @@ -729,7 +728,7 @@ struct dns_signing { unsigned int magic; dns_db_t *db; dns_dbiterator_t *dbiterator; - dns_secalg_t algorithm; + dst_algorithm_t algorithm; uint16_t keyid; bool deleteit; bool done; @@ -973,7 +972,7 @@ zone_notify(dns_zone_t *zone, isc_time_t *now); static void dump_done(void *arg, isc_result_t result); static isc_result_t -zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, +zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, uint16_t keyid, bool deleteit); static isc_result_t delete_nsec(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, @@ -3756,6 +3755,9 @@ cleanup: } } +#define OLD_SIGNING_RECORD_SIZE 5 +#define SIGNING_RECORD_SIZE 7 + static void resume_signingwithkey(dns_zone_t *zone) { dns_dbnode_t *node = NULL; @@ -3789,14 +3791,22 @@ resume_signingwithkey(dns_zone_t *zone) { DNS_RDATASET_FOREACH (&rdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; + dst_algorithm_t alg; + dns_rdataset_current(&rdataset, &rdata); - if (rdata.length != 5 || rdata.data[0] == 0 || - rdata.data[4] != 0) + /* + * Old or New Forms + */ + if ((rdata.length != OLD_SIGNING_RECORD_SIZE && + rdata.length != SIGNING_RECORD_SIZE) || + rdata.data[0] == 0 || rdata.data[4] != 0) { continue; } - - result = zone_signwithkey(zone, rdata.data[0], + alg = (rdata.length == OLD_SIGNING_RECORD_SIZE) + ? rdata.data[0] + : ((rdata.data[5] << 8) | rdata.data[6]); + result = zone_signwithkey(zone, alg, (rdata.data[1] << 8) | rdata.data[2], rdata.data[3]); if (result != ISC_R_SUCCESS) { @@ -7968,18 +7978,25 @@ updatesignwithkey(dns_zone_t *zone, dns_signing_t *signing, INSIST(!dns_rdataset_isassociated(&rdataset)); goto failure; } + DNS_RDATASET_FOREACH (&rdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; + unsigned char alg = dst_algorithm_tosecalg(signing->algorithm); + dns_rdataset_current(&rdataset, &rdata); /* * If we don't match the algorithm or keyid skip the record. */ - if (rdata.length != 5 || rdata.data[0] != signing->algorithm || + if ((rdata.length != SIGNING_RECORD_SIZE && + rdata.length != OLD_SIGNING_RECORD_SIZE) || + rdata.data[0] == 0 || rdata.data[0] != alg || rdata.data[1] != ((signing->keyid >> 8) & 0xff) || - rdata.data[2] != (signing->keyid & 0xff)) + rdata.data[2] != (signing->keyid & 0xff) || + (rdata.length == SIGNING_RECORD_SIZE && + (rdata.data[5] != (signing->algorithm >> 8 & 0xff) || + rdata.data[6] != (signing->algorithm & 0xff)))) { have_rr = true; - dns_rdata_reset(&rdata); continue; } /* @@ -8009,20 +8026,32 @@ updatesignwithkey(dns_zone_t *zone, dns_signing_t *signing, * finished signing the zone with this key. If it is already * there we don't need to add it a second time. */ - unsigned char data[5] = { - signing->algorithm, + unsigned char data[SIGNING_RECORD_SIZE] = { + dst_algorithm_tosecalg(signing->algorithm), (signing->keyid >> 8) & 0xff, signing->keyid & 0xff, 0, 1, + (signing->algorithm >> 8) & 0xff, + signing->algorithm & 0xff, }; dns_rdata_t rdata = (dns_rdata_t){ - .length = sizeof(data), + .length = signing->algorithm < 256 + ? OLD_SIGNING_RECORD_SIZE + : sizeof(data), .data = data, .type = zone->privatetype, .rdclass = dns_db_class(signing->db), .link = ISC_LINK_INITIALIZER, }; + /* + * data[0] can't be 0 as that is used to signal that the + * record is being used to for NSEC/NSEC3 chains generation. + * Set it to 255 instead. + */ + if (data[0] == 0) { + data[0] = 255; + } CHECK(update_one_rr(signing->db, version, diff, DNS_DIFFOP_ADD, &zone->origin, rdataset.ttl, &rdata)); } else if (!have_rr) { @@ -9351,7 +9380,7 @@ failure: */ static isc_result_t del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, - dns_dbnode_t *node, unsigned int nkeys, dns_secalg_t algorithm, + dns_dbnode_t *node, unsigned int nkeys, dst_algorithm_t algorithm, uint16_t keyid, bool *has_algp, dns_diff_t *diff) { dns_rdata_rrsig_t rrsig; dns_rdataset_t rdataset; @@ -9392,12 +9421,17 @@ del_sig(dns_db_t *db, dns_dbversion_t *version, dns_name_t *name, } DNS_RDATASET_FOREACH (&rdataset) { dns_rdata_t rdata = DNS_RDATA_INIT; + dst_algorithm_t sigalg; + dns_rdataset_current(&rdataset, &rdata); CHECK(dns_rdata_tostruct(&rdata, &rrsig, NULL)); - if (nkeys != 0 && (rrsig.algorithm != algorithm || - rrsig.keyid != keyid)) + + sigalg = dst_algorithm_fromdata( + rrsig.algorithm, rrsig.signature, rrsig.siglen); + if (nkeys != 0 && + (sigalg != algorithm || rrsig.keyid != keyid)) { - if (rrsig.algorithm == algorithm) { + if (sigalg == algorithm) { has_alg = true; } continue; @@ -9687,8 +9721,7 @@ zone_sign(dns_zone_t *zone) { /* * Find the key we want to remove. */ - if (DNSALG(zone_keys[i]) == - signing->algorithm && + if (ALG(zone_keys[i]) == signing->algorithm && dst_key_id(zone_keys[i]) == signing->keyid) { dst_key_free(&zone_keys[i]); @@ -9763,7 +9796,7 @@ zone_sign(dns_zone_t *zone) { * When adding look for the specific key. */ if (!signing->deleteit && - (DNSALG(zone_keys[i]) != signing->algorithm || + (ALG(zone_keys[i]) != signing->algorithm || dst_key_id(zone_keys[i]) != signing->keyid)) { continue; @@ -9774,7 +9807,7 @@ zone_sign(dns_zone_t *zone) { * with the algorithm that was being removed. */ if (signing->deleteit && - DNSALG(zone_keys[i]) != signing->algorithm) + ALG(zone_keys[i]) != signing->algorithm) { continue; } @@ -20108,8 +20141,8 @@ dns_zone_setnotifydelay(dns_zone_t *zone, uint32_t delay) { } isc_result_t -dns_zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, - bool deleteit) { +dns_zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, + uint16_t keyid, bool deleteit) { isc_result_t result; REQUIRE(DNS_ZONE_VALID(zone)); @@ -20193,7 +20226,7 @@ dns_zone_getprivatetype(dns_zone_t *zone) { } static isc_result_t -zone_signwithkey(dns_zone_t *zone, dns_secalg_t algorithm, uint16_t keyid, +zone_signwithkey(dns_zone_t *zone, dst_algorithm_t algorithm, uint16_t keyid, bool deleteit) { dns_signing_t *signing = NULL; isc_result_t result = ISC_R_SUCCESS; @@ -20350,7 +20383,7 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, isc_region_t r; isc_result_t result = ISC_R_SUCCESS; uint16_t keyid; - unsigned char buf[5]; + unsigned char data[SIGNING_RECORD_SIZE]; dns_name_t *name = dns_db_origin(db); dns_difftuplelist_t add = ISC_LIST_INITIALIZER; dns_difftuplelist_t del = ISC_LIST_INITIALIZER; @@ -20425,17 +20458,23 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, * or added. */ ISC_LIST_FOREACH (tuples, tuple, link) { + dst_algorithm_t algorithm; dns_rdata_toregion(&tuple->rdata, &r); keyid = dst_region_computeid(&r); - buf[0] = dnskey.algorithm; - buf[1] = (keyid & 0xff00) >> 8; - buf[2] = (keyid & 0xff); - buf[3] = (tuple->op == DNS_DIFFOP_ADD) ? 0 : 1; - buf[4] = 0; - rdata.data = buf; - rdata.length = sizeof(buf); + algorithm = dst_algorithm_fromdata(dnskey.algorithm, + dnskey.data, dnskey.datalen); + data[0] = dnskey.algorithm; + data[1] = (keyid & 0xff00) >> 8; + data[2] = (keyid & 0xff); + data[3] = (tuple->op == DNS_DIFFOP_ADD) ? 0 : 1; + data[4] = 0; + data[5] = (algorithm & 0xff00) >> 8; + data[6] = (algorithm & 0xff); + rdata.data = data; + rdata.length = algorithm < 256 ? OLD_SIGNING_RECORD_SIZE + : sizeof(data); rdata.type = privatetype; rdata.rdclass = tuple->rdata.rdclass; @@ -20455,7 +20494,7 @@ add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype, * Remove any record which says this operation has already * completed. */ - buf[4] = 1; + data[4] = 1; CHECK(rr_exists(db, ver, name, &rdata, &flag)); if (flag) { dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL, name, @@ -22900,21 +22939,49 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { } CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL)); - if (algorithms[structcds.algorithm] == 0) { - algorithms[structcds.algorithm] = expected; - } - DNS_RDATASET_FOREACH (&dnskey) { + if (structcds.algorithm != DNS_KEYALG_PRIVATEDNS && + structcds.algorithm != DNS_KEYALG_PRIVATEOID) + { + if (algorithms[structcds.algorithm] == 0) { + algorithms[structcds.algorithm] = + expected; + } + DNS_RDATASET_FOREACH (&dnskey) { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_dnskey_t structdnskey; + + dns_rdataset_current(&dnskey, &rdata); + dns_rdata_tostruct(&rdata, + &structdnskey, NULL); + + if (structdnskey.algorithm == + structcds.algorithm) + { + algorithms[structcds.algorithm] = + found; + } + } + } else { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_dnskey_t structdnskey; + dst_algorithm_t dnskeyalg; - dns_rdataset_current(&dnskey, &rdata); - dns_rdata_tostruct(&rdata, &structdnskey, NULL); - - if (structdnskey.algorithm == - structcds.algorithm) - { - algorithms[structcds.algorithm] = found; + /* Convert CDS to DS */ + crdata.type = dns_rdatatype_ds; + result = dns_dnssec_matchdskey(&zone->origin, + &crdata, &dnskey, + &rdata); + if (result != ISC_R_SUCCESS) { + result = DNS_R_BADCDS; + goto failure; } + CHECK(dns_rdata_tostruct(&rdata, &structdnskey, + NULL)); + dnskeyalg = dst_algorithm_fromdata( + structdnskey.algorithm, + structdnskey.data, + structdnskey.datalen); + algorithms[dnskeyalg] = found; } } for (i = 0; i < sizeof(algorithms); i++) { @@ -22941,6 +23008,7 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { DNS_RDATASET_FOREACH (&cdnskey) { dns_rdata_t crdata = DNS_RDATA_INIT; dns_rdata_cdnskey_t structcdnskey; + dst_algorithm_t cdnskeyalg; dns_rdataset_current(&cdnskey, &crdata); /* @@ -22963,22 +23031,27 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { CHECK(dns_rdata_tostruct(&crdata, &structcdnskey, NULL)); - if (algorithms[structcdnskey.algorithm] == 0) { - algorithms[structcdnskey.algorithm] = expected; + cdnskeyalg = dst_algorithm_fromdata( + structcdnskey.algorithm, structcdnskey.data, + structcdnskey.datalen); + if (algorithms[cdnskeyalg] == 0) { + algorithms[cdnskeyalg] = expected; } DNS_RDATASET_FOREACH (&dnskey) { dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdata_dnskey_t structdnskey; + dst_algorithm_t dnskeyalg; dns_rdataset_current(&dnskey, &rdata); CHECK(dns_rdata_tostruct(&rdata, &structdnskey, NULL)); + dnskeyalg = dst_algorithm_fromdata( + structdnskey.algorithm, + structdnskey.data, + structdnskey.datalen); - if (structdnskey.algorithm == - structcdnskey.algorithm) - { - algorithms[structcdnskey.algorithm] = - found; + if (dnskeyalg == cdnskeyalg) { + algorithms[cdnskeyalg] = found; } } } @@ -23232,7 +23305,7 @@ dns_zone_issecure(dns_zone_t *zone) { struct keydone { bool all; - unsigned char data[5]; + unsigned char data[SIGNING_RECORD_SIZE]; dns_zone_t *zone; }; @@ -23300,8 +23373,11 @@ keydone(void *arg) { dns_rdataset_current(&rdataset, &rdata); if (kd->all) { - if (rdata.length == 5 && rdata.data[0] != 0 && - rdata.data[3] == 0 && rdata.data[4] == 1) + /* Old (5) and new (7) forms */ + if ((rdata.length == OLD_SIGNING_RECORD_SIZE || + rdata.length == SIGNING_RECORD_SIZE) && + rdata.data[0] != 0 && rdata.data[3] == 0 && + rdata.data[4] == 1) { found = true; } else if (rdata.data[0] == 0 && @@ -23310,8 +23386,14 @@ keydone(void *arg) { found = true; clear_pending = true; } - } else if (rdata.length == 5 && - memcmp(rdata.data, kd->data, 5) == 0) + } else if (rdata.length == OLD_SIGNING_RECORD_SIZE && + memcmp(rdata.data, kd->data, + OLD_SIGNING_RECORD_SIZE) == 0) + { + found = true; + } else if (rdata.length == SIGNING_RECORD_SIZE && + memcmp(rdata.data, kd->data, SIGNING_RECORD_SIZE) == + 0) { found = true; } @@ -23388,7 +23470,7 @@ dns_zone_keydone(dns_zone_t *zone, const char *keystr) { isc_textregion_t r; const char *algstr = NULL; dns_keytag_t keyid; - dns_secalg_t alg; + dst_algorithm_t alg; size_t n; n = sscanf(keystr, "%hu/", &keyid); @@ -23403,20 +23485,20 @@ dns_zone_keydone(dns_zone_t *zone, const char *keystr) { CHECK(ISC_R_FAILURE); } - n = sscanf(algstr, "%hhu", &alg); + n = sscanf(algstr, "%u", &alg); if (n == 0U) { r.base = UNCONST(algstr); r.length = strlen(algstr); - CHECK(dns_secalg_fromtext(&alg, &r)); + CHECK(dst_algorithm_fromtext(&alg, &r)); } /* construct a private-type rdata */ isc_buffer_init(&b, kd->data, sizeof(kd->data)); - isc_buffer_putuint8(&b, alg); - isc_buffer_putuint8(&b, (keyid & 0xff00) >> 8); - isc_buffer_putuint8(&b, (keyid & 0xff)); + isc_buffer_putuint8(&b, dst_algorithm_tosecalg(alg)); + isc_buffer_putuint16(&b, keyid); isc_buffer_putuint8(&b, 0); isc_buffer_putuint8(&b, 1); + isc_buffer_putuint16(&b, alg); } zone_iattach(zone, &kd->zone); From 10d094a289e43eb82425b03a1c23b91876de9205 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 21 May 2025 10:13:04 +1000 Subject: [PATCH 11/15] Future: DS private algorithm support Add support for proposed DS digest types that encode the private algorithm identifier at the start of the DS digest as is done for DNSKEY and RRSIG. This allows a DS record to identify the specific DNSSEC algorithm, rather than a set of algorithms, when the algorithm field is set to PRIVATEDNS or PRIVATEOID. --- bin/dnssec/dnssec-cds.c | 4 +- bin/dnssec/dnssec-dsfromkey.c | 2 +- bin/dnssec/dnssec-ksr.c | 2 +- bin/dnssec/dnssec-signzone.c | 4 +- bin/named/server.c | 61 ++++++++++++++++++++++--- bin/tests/system/feature-test.c | 10 +++++ lib/dns/dnssec.c | 8 ++-- lib/dns/ds.c | 79 ++++++++++++++++++++++++++++++--- lib/dns/dst_api.c | 6 +++ lib/dns/include/dns/ds.h | 18 ++++++-- lib/dns/keytable.c | 2 +- lib/dns/rcode.c | 17 ++++++- lib/dns/validator.c | 49 +++++++++++++++----- lib/dns/view.c | 10 ++--- lib/dns/zone.c | 10 +++-- lib/dns/zoneverify.c | 2 +- tests/dns/keytable_test.c | 37 +++++++++------ 17 files changed, 260 insertions(+), 61 deletions(-) diff --git a/bin/dnssec/dnssec-cds.c b/bin/dnssec/dnssec-cds.c index 3c7db4d0ed..1d8c25185a 100644 --- a/bin/dnssec/dnssec-cds.c +++ b/bin/dnssec/dnssec-cds.c @@ -482,7 +482,7 @@ match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness) { } result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type, - dsbuf, &newdsrdata); + dsbuf, sizeof(dsbuf), &newdsrdata); if (result != ISC_R_SUCCESS) { vbprintf(3, "dns_ds_buildrdata(" @@ -769,7 +769,7 @@ ds_from_cdnskey(isc_buffer_t *buf, dns_rdata_t *ds, dns_dsdigest_t dt, return ISC_R_NOSPACE; } - result = dns_ds_buildrdata(name, cdnskey, dt, r.base, ds); + result = dns_ds_buildrdata(name, cdnskey, dt, r.base, r.length, ds); if (result == ISC_R_SUCCESS) { isc_buffer_add(buf, DNS_DS_BUFFERSIZE); } diff --git a/bin/dnssec/dnssec-dsfromkey.c b/bin/dnssec/dnssec-dsfromkey.c index 6fd0d35dcb..15acead642 100644 --- a/bin/dnssec/dnssec-dsfromkey.c +++ b/bin/dnssec/dnssec-dsfromkey.c @@ -270,7 +270,7 @@ emit(dns_dsdigest_t dt, bool showall, bool cds, dns_rdata_t *rdata) { return; } - result = dns_ds_buildrdata(name, rdata, dt, buf, &ds); + result = dns_ds_buildrdata(name, rdata, dt, buf, sizeof(buf), &ds); if (result != ISC_R_SUCCESS) { fatal("can't build record"); } diff --git a/bin/dnssec/dnssec-ksr.c b/bin/dnssec/dnssec-ksr.c index b4a4e5736a..14def76219 100644 --- a/bin/dnssec/dnssec-ksr.c +++ b/bin/dnssec/dnssec-ksr.c @@ -851,7 +851,7 @@ get_keymaterial(ksr_ctx_t *ksr, dns_kasp_t *kasp, isc_stdtime_t inception, dns_rdata_init(rdata2); CHECK(dns_ds_buildrdata(name, rdata, alg->digest, - cdsbuf, &cds)); + cdsbuf, sizeof(cdsbuf), &cds)); cds.type = dns_rdatatype_cds; dns_rdata_toregion(&cds, &rcds); isc_buffer_allocate(mctx, &newbuf2, rcds.length); diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index e2a01429e0..4ceb483bde 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -1073,7 +1073,7 @@ loadds(dns_name_t *name, uint32_t ttl, dns_rdataset_t *dsset) { dns_rdata_t ds = DNS_RDATA_INIT; dns_rdataset_current(&keyset, &key); result = dns_ds_buildrdata(name, &key, DNS_DSDIGEST_SHA256, - dsbuf, &ds); + dsbuf, sizeof(dsbuf), &ds); check_result(result, "dns_ds_buildrdata"); dns_difftuple_create(mctx, DNS_DIFFOP_ADDRESIGN, name, ttl, &ds, @@ -3055,7 +3055,7 @@ writeset(const char *prefix, dns_rdatatype_t type) { if (type != dns_rdatatype_dnskey) { result = dns_ds_buildrdata(gorigin, &rdata, DNS_DSDIGEST_SHA256, dsbuf, - &ds); + sizeof(dsbuf), &ds); check_result(result, "dns_ds_buildrdata"); dns_difftuple_create(mctx, DNS_DIFFOP_ADDRESIGN, name, 0, &ds, &tuple); diff --git a/bin/named/server.c b/bin/named/server.c index 5a333cc937..6cbfc245c8 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -677,7 +677,7 @@ cleanup: static isc_result_t ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, - unsigned char *digest, dns_rdata_ds_t *ds) { + unsigned char *digest, size_t digest_len, dns_rdata_ds_t *ds) { isc_result_t result; dns_rdata_dnskey_t keystruct; dns_rdata_t rdata = DNS_RDATA_INIT; @@ -805,7 +805,7 @@ ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, keystruct.common.rdtype, &keystruct, &rrdatabuf)); CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, - digest, ds)); + digest, digest_len, ds)); break; case INIT_DS: @@ -844,6 +844,20 @@ ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, CHECK(ISC_R_UNEXPECTEDEND); } break; +#if defined(DNS_DSDIGEST_SHA256PRIVATE) + case DNS_DSDIGEST_SHA256PRIVATE: + if (r.length < ISC_SHA256_DIGESTLENGTH) { + CHECK(ISC_R_UNEXPECTEDEND); + } + break; +#endif +#if defined(DNS_DSDIGEST_SHA384PRIVATE) + case DNS_DSDIGEST_SHA384PRIVATE: + if (r.length < ISC_SHA384_DIGESTLENGTH) { + CHECK(ISC_R_UNEXPECTEDEND); + } + break; +#endif default: cfg_obj_log(key, ISC_LOG_ERROR, "key '%s': " @@ -854,12 +868,46 @@ ta_fromconfig(const cfg_obj_t *key, bool *initialp, const char **namestrp, break; } + if (r.length > digest_len) { + CHECK(ISC_R_NOSPACE); + } ds->length = r.length; ds->digest = digest; - INSIST(r.length <= ISC_MAX_MD_SIZE); memmove(ds->digest, r.base, r.length); - if (!dst_algorithm_supported(ds->algorithm)) { + algorithm = ds->algorithm; + +#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE) + /* + * Extract the private algorithm from the start + * of the hash field. + */ + switch (ds->digest_type) { + /* + * Digest types that do not encode the private DNSSEC algorithm + * at the start of the digest field. + */ + case DNS_DSDIGEST_SHA1: + case DNS_DSDIGEST_SHA256: + case DNS_DSDIGEST_SHA384: + break; + /* + * Digest types that encode the private DNSSEC algorithm + * at the start of the digest field. + */ + case DNS_DSDIGEST_SHA256PRIVATE: + case DNS_DSDIGEST_SHA384PRIVATE: + algorithm = dst_algorithm_fromdata( + ds->algorithm, ds->digest, ds->length); + break; + /* + * Unknown digest types. + */ + default: + break; + } +#endif + if (!dst_algorithm_supported(algorithm)) { CHECK(DST_R_UNSUPPORTEDALG); } @@ -902,10 +950,11 @@ process_key(const cfg_obj_t *key, dns_keytable_t *secroots, dns_rdata_ds_t ds; isc_result_t result; bool initializing = managed; - unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned char digest[DNS_DS_BUFFERSIZE]; isc_buffer_t b; - result = ta_fromconfig(key, &initializing, &namestr, digest, &ds); + result = ta_fromconfig(key, &initializing, &namestr, digest, + sizeof(digest), &ds); switch (result) { case ISC_R_SUCCESS: diff --git a/bin/tests/system/feature-test.c b/bin/tests/system/feature-test.c index 8f58e133f1..73d0e73e66 100644 --- a/bin/tests/system/feature-test.c +++ b/bin/tests/system/feature-test.c @@ -30,6 +30,7 @@ #include #include +#include #include #include @@ -42,6 +43,7 @@ usage(void) { fprintf(stderr, "\t--edns-version\n"); fprintf(stderr, "\t--enable-dnstap\n"); fprintf(stderr, "\t--enable-querytrace\n"); + fprintf(stderr, "\t--extended-ds-digest\n"); fprintf(stderr, "\t--fips-provider\n"); fprintf(stderr, "\t--gethostname\n"); fprintf(stderr, "\t--gssapi\n"); @@ -93,6 +95,14 @@ main(int argc, char **argv) { #endif /* ifdef WANT_QUERYTRACE */ } + if (strcasecmp(argv[1], "--extended-ds-digest") == 0) { +#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE) + return 0; +#else + return 1; +#endif + } + if (strcasecmp(argv[1], "--fips-provider") == 0) { #if OPENSSL_VERSION_NUMBER >= 0x30000000L OSSL_PROVIDER *fips = OSSL_PROVIDER_load(NULL, "fips"); diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 6e73674d2b..993168e3dc 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -1841,7 +1841,8 @@ add_cds(dns_dnsseckey_t *key, dns_rdata_t *keyrdata, const char *keystr, dns_rdata_t cdsrdata = DNS_RDATA_INIT; dns_name_t *origin = dst_key_name(key->key); - r = dns_ds_buildrdata(origin, keyrdata, digesttype, dsbuf, &cdsrdata); + r = dns_ds_buildrdata(origin, keyrdata, digesttype, dsbuf, + sizeof(dsbuf), &cdsrdata); if (r != ISC_R_SUCCESS) { char algbuf[DNS_DSDIGEST_FORMATSIZE]; dns_dsdigest_format(digesttype, algbuf, @@ -1876,7 +1877,8 @@ delete_cds(dns_dnsseckey_t *key, dns_rdata_t *keyrdata, const char *keystr, dns_rdata_t cdsrdata = DNS_RDATA_INIT; dns_name_t *origin = dst_key_name(key->key); - r = dns_ds_buildrdata(origin, keyrdata, digesttype, dsbuf, &cdsrdata); + r = dns_ds_buildrdata(origin, keyrdata, digesttype, dsbuf, + sizeof(dsbuf), &cdsrdata); if (r != ISC_R_SUCCESS) { return r; } @@ -2364,7 +2366,7 @@ dns_dnssec_matchdskey(dns_name_t *name, dns_rdata_t *dsrdata, } result = dns_ds_buildrdata(name, keyrdata, ds.digest_type, buf, - &newdsrdata); + sizeof(buf), &newdsrdata); if (result != ISC_R_SUCCESS) { continue; } diff --git a/lib/dns/ds.c b/lib/dns/ds.c index c56691bf19..773a880127 100644 --- a/lib/dns/ds.c +++ b/lib/dns/ds.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -32,11 +33,12 @@ isc_result_t dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, dns_dsdigest_t digest_type, unsigned char *digest, - dns_rdata_ds_t *dsrdata) { + size_t len, dns_rdata_ds_t *dsrdata) { isc_result_t result; dns_fixedname_t fname; dns_name_t *name; - unsigned int digestlen; + unsigned int digestlen = 0; + unsigned int privatelen = 0; isc_region_t r; isc_md_t *md; const isc_md_type_t *md_type = NULL; @@ -44,6 +46,7 @@ dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, REQUIRE(key != NULL); REQUIRE(key->type == dns_rdatatype_dnskey || key->type == dns_rdatatype_cdnskey); + REQUIRE(digest != NULL); if (!dst_ds_digest_supported(digest_type)) { return ISC_R_NOTIMPLEMENTED; @@ -55,10 +58,16 @@ dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, break; case DNS_DSDIGEST_SHA384: +#ifdef DNS_DSDIGEST_SHA384PRIVATE + case DNS_DSDIGEST_SHA384PRIVATE: +#endif md_type = ISC_MD_SHA384; break; case DNS_DSDIGEST_SHA256: +#ifdef DNS_DSDIGEST_SHA256PRIVATE + case DNS_DSDIGEST_SHA256PRIVATE: +#endif md_type = ISC_MD_SHA256; break; @@ -91,6 +100,64 @@ dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, goto end; } +#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE) + /* + * Insert PRIVATE algorithm identify at start of digest. + */ + switch (digest_type) { + case DNS_DSDIGEST_SHA1: + case DNS_DSDIGEST_SHA256: + case DNS_DSDIGEST_SHA384: + break; + case DNS_DSDIGEST_SHA256PRIVATE: + case DNS_DSDIGEST_SHA384PRIVATE: + switch (r.base[3]) { + case DNS_KEYALG_PRIVATEDNS: { + isc_region_t r2 = r; + INSIST(r2.length >= 5); + isc_region_consume(&r2, 4); + dns_name_fromregion(name, &r2); + dns_name_toregion(name, &r2); + privatelen = r2.length; + if (r2.length > len) { + result = ISC_R_NOSPACE; + goto end; + } + memmove(digest, r2.base, privatelen); + digest += privatelen; + len -= privatelen; + break; + } + case DNS_KEYALG_PRIVATEOID: { + isc_region_t r2 = r; + INSIST(r2.length >= 5); + isc_region_consume(&r2, 4); + privatelen = r2.base[0] + 1; + if (r2.base[0] > len) { + result = ISC_R_NOSPACE; + goto end; + } + INSIST(r2.length >= privatelen); + memmove(digest, r2.base, privatelen); + digest += privatelen; + len -= privatelen; + break; + } + default: + break; + } + break; + default: + break; + } +#endif + + size_t mdsize = isc_md_get_size(md); + if (mdsize > len) { + result = ISC_R_NOSPACE; + goto end; + } + result = isc_md_final(md, digest, &digestlen); if (result != ISC_R_SUCCESS) { goto end; @@ -102,8 +169,8 @@ dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, dsrdata->algorithm = r.base[3]; dsrdata->key_tag = dst_region_computeid(&r); dsrdata->digest_type = digest_type; - dsrdata->digest = digest; - dsrdata->length = digestlen; + dsrdata->digest = digest - privatelen; + dsrdata->length = digestlen + privatelen; end: isc_md_free(md); @@ -112,14 +179,14 @@ end: isc_result_t dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key, - dns_dsdigest_t digest_type, unsigned char *buffer, + dns_dsdigest_t digest_type, unsigned char *buffer, size_t len, dns_rdata_t *rdata) { isc_result_t result; unsigned char digest[ISC_MAX_MD_SIZE]; dns_rdata_ds_t ds; isc_buffer_t b; - result = dns_ds_fromkeyrdata(owner, key, digest_type, digest, &ds); + result = dns_ds_fromkeyrdata(owner, key, digest_type, digest, len, &ds); if (result != ISC_R_SUCCESS) { return result; } diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 19fd35d0ae..4f8f9dc958 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -233,7 +233,13 @@ dst_algorithm_supported(unsigned int alg) { bool dst_ds_digest_supported(unsigned int digest_type) { return digest_type == DNS_DSDIGEST_SHA1 || +#if defined(DNS_DSDIGEST_SHA256PRIVATE) + digest_type == DNS_DSDIGEST_SHA256PRIVATE || +#endif digest_type == DNS_DSDIGEST_SHA256 || +#if defined(DNS_DSDIGEST_SHA256PRIVATE) + digest_type == DNS_DSDIGEST_SHA384PRIVATE || +#endif digest_type == DNS_DSDIGEST_SHA384; } diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h index a1cad49683..734d93e647 100644 --- a/lib/dns/include/dns/ds.h +++ b/lib/dns/include/dns/ds.h @@ -23,15 +23,25 @@ #define DNS_DSDIGEST_GOST2012 (5) #define DNS_DSDIGEST_SM3 (6) +#if TEST_PRIVATE_DSDIGEST /* - * Assuming SHA-384 digest type. + * Possible future digest types that encode the PRIVATEDNS and + * PRIVATEOID identifiers before the cryptographic digest value. */ -#define DNS_DS_BUFFERSIZE (52) +#define DNS_DSDIGEST_SHA256PRIVATE (7) +#define DNS_DSDIGEST_SHA384PRIVATE (8) +#define DNS_DSDIGEST_SM3PRIVATE (9) +#endif + +/* + * Assuming SHA-384 digest type + maximal PRIVATEDNS name. + */ +#define DNS_DS_BUFFERSIZE (52 + 255) isc_result_t dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, dns_dsdigest_t digest_type, unsigned char *digest, - dns_rdata_ds_t *dsrdata); + size_t len, dns_rdata_ds_t *dsrdata); /*%< * Build a DS rdata structure from a key. * @@ -43,7 +53,7 @@ dns_ds_fromkeyrdata(const dns_name_t *owner, dns_rdata_t *key, isc_result_t dns_ds_buildrdata(dns_name_t *owner, dns_rdata_t *key, - dns_dsdigest_t digest_type, unsigned char *buffer, + dns_dsdigest_t digest_type, unsigned char *buffer, size_t len, dns_rdata_t *rdata); /*%< * Similar to dns_ds_fromkeyrdata(), but copies the DS into a diff --git a/lib/dns/keytable.c b/lib/dns/keytable.c index 2caae9612c..280740b991 100644 --- a/lib/dns/keytable.c +++ b/lib/dns/keytable.c @@ -453,7 +453,7 @@ dns_keytable_deletekey(dns_keytable_t *keytable, const dns_name_t *keyname, } result = dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256, - digest, &ds); + digest, sizeof(digest), &ds); if (result != ISC_R_SUCCESS) { goto finish; } diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c index f77daa4141..414a791538 100644 --- a/lib/dns/rcode.c +++ b/lib/dns/rcode.c @@ -138,6 +138,20 @@ /* RFC3658, RFC4509, RFC5933, RFC6605, RFC9558, RFC9563 */ +#if defined(DNS_DSDIGEST_SHA256PRIVATE) && \ + defined(DNS_DSDIGEST_SHA384PRIVATE) && \ + defined(DNS_DSDIGEST_SM3PRIVATE) +#define DSDIGESTPRIVATENAMES \ + { DNS_DSDIGEST_SHA256PRIVATE, "SHA-256-PRIVATE", 0 }, \ + { DNS_DSDIGEST_SHA256PRIVATE, "SHA256PRIVATE", 0 }, \ + { DNS_DSDIGEST_SHA384PRIVATE, "SHA-384-PRIVATE", 0 }, \ + { DNS_DSDIGEST_SHA384PRIVATE, "SHA384PRIVATE", 0 }, \ + { DNS_DSDIGEST_SM3PRIVATE, "SM3-PRIVATE", 0 }, \ + { DNS_DSDIGEST_SM3PRIVATE, "SM3PRIVATE", 0 }, +#else +#define DSDIGESTPRIVATENAMES +#endif + #define DSDIGESTNAMES \ { DNS_DSDIGEST_SHA1, "SHA-1", 0 }, { DNS_DSDIGEST_SHA1, "SHA1", 0 }, \ { DNS_DSDIGEST_SHA256, "SHA-256", 0 }, \ @@ -147,7 +161,8 @@ { DNS_DSDIGEST_SHA384, "SHA-384", 0 }, \ { DNS_DSDIGEST_SHA384, "SHA384", 0 }, \ { DNS_DSDIGEST_GOST2012, "GOST-2012", 0 }, \ - { DNS_DSDIGEST_GOST2012, "GOST2012", 0 }, SENTINEL + { DNS_DSDIGEST_GOST2012, "GOST2012", 0 }, \ + DSDIGESTPRIVATENAMES SENTINEL struct tbl { unsigned int value; diff --git a/lib/dns/validator.c b/lib/dns/validator.c index 369b4316f3..82a839977a 100644 --- a/lib/dns/validator.c +++ b/lib/dns/validator.c @@ -2030,6 +2030,8 @@ validate_dnskey_dsset(dns_validator_t *val) { isc_result_t result; dns_rdata_ds_t ds; dns_rdata_dnskey_t key; + unsigned char *data = 0; + unsigned int datalen = 0; dns_rdata_reset(&dsrdata); dns_rdataset_current(val->dsset, &dsrdata); @@ -2049,12 +2051,31 @@ validate_dnskey_dsset(dns_validator_t *val) { return DNS_R_BADALG; } - if (ds.algorithm != DNS_KEYALG_PRIVATEDNS && - ds.algorithm != DNS_KEYALG_PRIVATEOID) + switch (ds.algorithm) { + case DNS_KEYALG_PRIVATEDNS: + case DNS_KEYALG_PRIVATEOID: + switch (ds.digest_type) { +#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE) + case DNS_DSDIGEST_SHA256PRIVATE: + case DNS_DSDIGEST_SHA384PRIVATE: + data = ds.digest; + datalen = ds.length; + break; +#endif + default: + break; + } + break; + default: + break; + } + + if (data != NULL || (ds.algorithm != DNS_KEYALG_PRIVATEDNS && + ds.algorithm != DNS_KEYALG_PRIVATEOID)) { if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, ds.algorithm, - NULL, 0)) + data, datalen)) { if (val->unsupported_algorithm == 0) { val->unsupported_algorithm = ds.algorithm; @@ -2078,8 +2099,8 @@ validate_dnskey_dsset(dns_validator_t *val) { * found a matching dnskey. */ dns_rdata_tostruct(&keyrdata, &key, NULL); - if (ds.algorithm == DNS_KEYALG_PRIVATEDNS || - ds.algorithm == DNS_KEYALG_PRIVATEOID) + if (data == NULL && (ds.algorithm == DNS_KEYALG_PRIVATEDNS || + ds.algorithm == DNS_KEYALG_PRIVATEOID)) { if (!dns_resolver_algorithm_supported(val->view->resolver, val->name, key.algorithm, @@ -3020,6 +3041,13 @@ check_ds_algs(dns_validator_t *val, dns_name_t *name, ds.algorithm == DNS_KEYALG_PRIVATEDNS) { switch (ds.digest_type) { +#if defined(DNS_DSDIGEST_SHA256PRIVATE) && defined(DNS_DSDIGEST_SHA384PRIVATE) + case DNS_DSDIGEST_SHA256PRIVATE: + case DNS_DSDIGEST_SHA384PRIVATE: + data = ds.digest; + datalen = ds.length; + break; +#endif case DNS_DSDIGEST_SHA1: case DNS_DSDIGEST_SHA256: case DNS_DSDIGEST_SHA384: @@ -3065,11 +3093,12 @@ check_ds_algs(dns_validator_t *val, dns_name_t *name, * it would be raised already. * * If we have seen a private algorithm for which we couldn't find a - * DNSKEY we must assume the child zone is secure. With PRIVATEDNS and - * PRIVATEOID we can only make that determination if we match a DNSKEY - * for every DS with these algorithms. Since we don't know whether the - * private algorithm is unsupported or not, we are required to treat it - * as supported. + * DNSKEY nor extract it from the digest field we must assume the child + * zone is secure. With PRIVATEDNS and PRIVATEOID we can make that + * determination if we match a DNSKEY for every DS with these algorithms + * or extract the algorithm from the digest field. Since we don't know + * whether the private algorithm is unsupported or not, we are required + * to treat it as supported. */ if (seen_private) { char namebuf[DNS_NAME_FORMATSIZE]; diff --git a/lib/dns/view.c b/lib/dns/view.c index 12b02669e5..36619b2989 100644 --- a/lib/dns/view.c +++ b/lib/dns/view.c @@ -1648,9 +1648,9 @@ dns_view_istrusted(dns_view_t *view, const dns_name_t *keyname, goto finish; } - result = dns_ds_fromkeyrdata(keyname, &rdata, - DNS_DSDIGEST_SHA256, - digest, &ds); + result = dns_ds_fromkeyrdata( + keyname, &rdata, DNS_DSDIGEST_SHA256, digest, + sizeof(digest), &ds); if (result != ISC_R_SUCCESS) { goto finish; } @@ -2311,7 +2311,7 @@ dns_view_addtrustedkey(dns_view_t *view, dns_rdatatype_t rdtype, isc_result_t result; dns_name_t *name = UNCONST(keyname); char rdatabuf[DST_KEY_MAXSIZE]; - unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned char digest[DNS_DS_BUFFERSIZE]; dns_rdata_ds_t ds; dns_rdata_t rdata; isc_buffer_t b; @@ -2334,7 +2334,7 @@ dns_view_addtrustedkey(dns_view_t *view, dns_rdatatype_t rdtype, CHECK(dns_rdata_tostruct(&rdata, &ds, NULL)); } else { CHECK(dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, - digest, &ds)); + digest, sizeof(digest), &ds)); } CHECK(dns_keytable_add(view->secroots_priv, false, false, name, &ds, diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 1e3ae54d18..ffe1b65887 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -4433,7 +4433,7 @@ trust_key(dns_zone_t *zone, dns_name_t *keyname, dns_rdata_dnskey_t *dnskey, bool initial) { isc_result_t result; dns_rdata_t rdata = DNS_RDATA_INIT; - unsigned char data[4096], digest[ISC_MAX_MD_SIZE]; + unsigned char data[4096], digest[DNS_DS_BUFFERSIZE]; isc_buffer_t buffer; dns_keytable_t *sr = NULL; dns_rdata_ds_t ds; @@ -4448,7 +4448,7 @@ trust_key(dns_zone_t *zone, dns_name_t *keyname, dns_rdata_dnskey_t *dnskey, dns_rdata_fromstruct(&rdata, dnskey->common.rdclass, dns_rdatatype_dnskey, dnskey, &buffer); CHECK(dns_ds_fromkeyrdata(keyname, &rdata, DNS_DSDIGEST_SHA256, digest, - &ds)); + sizeof(digest), &ds)); CHECK(dns_keytable_add(sr, true, initial, keyname, &ds, sfd_add, zone->view)); @@ -16501,7 +16501,8 @@ cds_inuse(dns_zone_t *zone, dns_rdata_t *rdata, dns_dnsseckeylist_t *keylist, return result; } result = dns_ds_buildrdata(dns_zone_getorigin(zone), &dnskey, - cds.digest_type, cdsbuf, &cdsrdata); + cds.digest_type, cdsbuf, + sizeof(cdsbuf), &cdsrdata); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, "dns_ds_buildrdata(keytag=%d, algo=%d, " @@ -21033,7 +21034,8 @@ checkds_done(void *arg) { /* Derive DS from DNSKEY, see if the rdata is equal. */ make_dnskey(key->key, keybuf, sizeof(keybuf), &dnskey); r = dns_ds_buildrdata(&zone->origin, &dnskey, - ds.digest_type, dsbuf, &dsrdata); + ds.digest_type, dsbuf, + sizeof(dsbuf), &dsrdata); if (r != ISC_R_SUCCESS) { continue; } diff --git a/lib/dns/zoneverify.c b/lib/dns/zoneverify.c index d6cce894eb..9acab371e7 100644 --- a/lib/dns/zoneverify.c +++ b/lib/dns/zoneverify.c @@ -1521,7 +1521,7 @@ check_dnskey_sigs(vctx_t *vctx, const dns_rdata_dnskey_t *dnskey, result = dns_ds_buildrdata(vctx->origin, keyrdata, ds.digest_type, buf, - &newdsrdata); + sizeof(buf), &newdsrdata); if (result != ISC_R_SUCCESS) { continue; } diff --git a/tests/dns/keytable_test.c b/tests/dns/keytable_test.c index 538a98bfeb..c125c18c65 100644 --- a/tests/dns/keytable_test.c +++ b/tests/dns/keytable_test.c @@ -130,7 +130,7 @@ create_keystruct(uint16_t flags, uint8_t proto, uint8_t alg, const char *keystr, static void create_dsstruct(dns_name_t *name, uint16_t flags, uint8_t proto, uint8_t alg, - const char *keystr, unsigned char *digest, + const char *keystr, unsigned char *digest, size_t digest_len, dns_rdata_ds_t *dsstruct) { isc_result_t result; unsigned char rrdata[4096]; @@ -156,7 +156,7 @@ create_dsstruct(dns_name_t *name, uint16_t flags, uint8_t proto, uint8_t alg, * Build DS rdata struct. */ result = dns_ds_fromkeyrdata(name, &rdata, DNS_DSDIGEST_SHA256, digest, - dsstruct); + digest_len, dsstruct); assert_int_equal(result, ISC_R_SUCCESS); dns_rdata_freestruct(&dnskey); @@ -165,7 +165,7 @@ create_dsstruct(dns_name_t *name, uint16_t flags, uint8_t proto, uint8_t alg, /* Common setup: create a keytable and ntatable to test with a few keys */ static void create_tables(void) { - unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned char digest[DNS_DS_BUFFERSIZE]; dns_rdata_ds_t ds; dns_fixedname_t fn; dns_name_t *keyname = dns_fixedname_name(&fn); @@ -179,14 +179,16 @@ create_tables(void) { /* Add a normal key */ dns_test_namefromstring("example.com.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); /* Add an initializing managed key */ dns_test_namefromstring("managed.com.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -220,7 +222,7 @@ destroy_tables(void) { ISC_LOOP_TEST_IMPL(add) { dns_keynode_t *keynode = NULL; dns_keynode_t *null_keynode = NULL; - unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned char digest[DNS_DS_BUFFERSIZE]; dns_rdata_ds_t ds; dns_fixedname_t fn; dns_name_t *keyname = dns_fixedname_name(&fn); @@ -241,7 +243,8 @@ ISC_LOOP_TEST_IMPL(add) { * report success. */ dns_test_namefromstring("example.com.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -252,7 +255,8 @@ ISC_LOOP_TEST_IMPL(add) { /* Add another key (different keydata) */ dns_keynode_detach(&keynode); - create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -280,7 +284,8 @@ ISC_LOOP_TEST_IMPL(add) { * node, the node should *not* be marked as initializing. */ dns_test_namefromstring("managed.com.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -310,7 +315,8 @@ ISC_LOOP_TEST_IMPL(add) { * initializing key. */ dns_test_namefromstring("two.com.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, true, true, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -326,7 +332,8 @@ ISC_LOOP_TEST_IMPL(add) { * trust anchor for two.com and we haven't run dns_keynode_trust(), * the initialization status should not change. */ - create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, true, false, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -344,7 +351,8 @@ ISC_LOOP_TEST_IMPL(add) { &null_keynode), ISC_R_SUCCESS); dns_test_namefromstring("null.example.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr2, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr2, digest, sizeof(digest), + &ds); assert_int_equal(dns_keytable_add(keytable, false, false, keyname, &ds, NULL, NULL), ISC_R_SUCCESS); @@ -598,7 +606,7 @@ ISC_LOOP_TEST_IMPL(nta) { bool issecure, covered; dns_fixedname_t fn; dns_name_t *keyname = dns_fixedname_name(&fn); - unsigned char digest[ISC_MAX_MD_SIZE]; + unsigned char digest[DNS_DS_BUFFERSIZE]; dns_rdata_ds_t ds; dns_view_t *myview = NULL; isc_stdtime_t now = isc_stdtime_now(); @@ -616,7 +624,8 @@ ISC_LOOP_TEST_IMPL(nta) { assert_int_equal(result, ISC_R_SUCCESS); dns_test_namefromstring("example.", &fn); - create_dsstruct(keyname, 257, 3, 5, keystr1, digest, &ds); + create_dsstruct(keyname, 257, 3, 5, keystr1, digest, sizeof(digest), + &ds); result = dns_keytable_add(keytable, false, false, keyname, &ds, NULL, NULL), assert_int_equal(result, ISC_R_SUCCESS); From e687710dc7f0c215b1184f2cad56ecbfbc938b5f Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Tue, 1 Apr 2025 00:12:52 +1100 Subject: [PATCH 12/15] Add PRIVATEOIDs for RSASHA256 and RSASHA512 Use the existing RSASHA256 and RSASHA512 implementation to provide working PRIVATEOID example implementations. We are using the OID values normally associated with RSASHA256 (1.2.840.113549.1.1.11) and RSASHA512 (1.2.840.113549.1.1.13). --- bin/dnssec/dnssec-keygen.c | 6 ++ bin/dnssec/dnssec-ksr.c | 2 + bin/tests/system/conf.sh | 4 + bin/tests/system/dnssec/tests.sh | 12 ++- bin/tests/system/isctest/kasp.py | 4 + bin/tests/system/isctest/vars/algorithms.py | 6 ++ lib/dns/dst_api.c | 44 ++++++++++- lib/dns/dst_parse.c | 8 ++ lib/dns/include/dst/dst.h | 4 +- lib/dns/kasp.c | 2 + lib/dns/opensslrsa_link.c | 82 +++++++++++++++++++++ lib/dns/rcode.c | 4 +- lib/isccfg/kaspconf.c | 2 + 13 files changed, 175 insertions(+), 5 deletions(-) diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 500b11fd03..c3e24a39e7 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -286,6 +286,8 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: case DST_ALG_ECDSA256: case DST_ALG_ECDSA384: case DST_ALG_ED25519: @@ -309,6 +311,8 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { FALLTHROUGH; case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: ctx->size = 2048; if (verbose > 0) { fprintf(stderr, @@ -462,6 +466,8 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { FALLTHROUGH; case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: if (ctx->size != 0 && (ctx->size < min_rsa || ctx->size > MAX_RSA)) { diff --git a/bin/dnssec/dnssec-ksr.c b/bin/dnssec/dnssec-ksr.c index 14def76219..ea96d41c11 100644 --- a/bin/dnssec/dnssec-ksr.c +++ b/bin/dnssec/dnssec-ksr.c @@ -356,6 +356,8 @@ create_key(ksr_ctx_t *ksr, dns_kasp_t *kasp, dns_kasp_key_t *kaspkey, FALLTHROUGH; case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: if (ksr->size != 0 && (ksr->size < min_rsa || ksr->size > MAX_RSA)) { diff --git a/bin/tests/system/conf.sh b/bin/tests/system/conf.sh index 86059196dd..dddf5dac20 100644 --- a/bin/tests/system/conf.sh +++ b/bin/tests/system/conf.sh @@ -212,6 +212,10 @@ private_type_record() { _algorithm=$2 _keyfile=$3 _secalg=$2 + case $_secalg in + 256) _secalg=254 ;; # RSASHA256OID + 257) _secalg=254 ;; # RSASHA512OID + esac _id=$(keyfile_to_key_id "$_keyfile") diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 746dc57ed9..3d24846952 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -3481,7 +3481,7 @@ status=$((status + ret)) echo_i "check that 'dnssec-keygen -S' works for all supported algorithms ($n)" ret=0 alg=1 -until test $alg -eq 256; do +until test $alg -eq 258; do zone="keygen-$alg." case $alg in 2) # Diffie Helman @@ -3498,10 +3498,20 @@ until test $alg -eq 256; do 15 | 16) key1=$($KEYGEN -a "$alg" "$zone" 2>"keygen-$alg.err" || true) ;; + 256) + key1=$($KEYGEN -a "RSASHA256OID" "$zone" 2>"keygen-$alg.err" || true) + ;; + 257) + key1=$($KEYGEN -a "RSASHA512OID" "$zone" 2>"keygen-$alg.err" || true) + ;; *) key1=$($KEYGEN -a "$alg" "$zone" 2>"keygen-$alg.err" || true) ;; esac + if grep "unknown algorithm" "keygen-$alg.err" >/dev/null; then + alg=$((alg + 1)) + continue + fi if grep "unsupported algorithm" "keygen-$alg.err" >/dev/null; then alg=$((alg + 1)) continue diff --git a/bin/tests/system/isctest/kasp.py b/bin/tests/system/isctest/kasp.py index e334ee3ac5..86e05ebac9 100644 --- a/bin/tests/system/isctest/kasp.py +++ b/bin/tests/system/isctest/kasp.py @@ -392,6 +392,10 @@ class Key: def get_dnsalg(self) -> int: alg = int(self.get_metadata("Algorithm")) + if alg == isctest.vars.algorithms.RSASHA256OID.dst: + return isctest.vars.algorithms.RSASHA256OID.number + if alg == isctest.vars.algorithms.RSASHA512OID.dst: + return isctest.vars.algorithms.RSASHA512OID.number return alg def ttl(self) -> int: diff --git a/bin/tests/system/isctest/vars/algorithms.py b/bin/tests/system/isctest/vars/algorithms.py index 86741e14a2..58b5a9a652 100644 --- a/bin/tests/system/isctest/vars/algorithms.py +++ b/bin/tests/system/isctest/vars/algorithms.py @@ -83,6 +83,8 @@ ECDSAP256SHA256 = Algorithm("ECDSAP256SHA256", 13, 13, 256) ECDSAP384SHA384 = Algorithm("ECDSAP384SHA384", 14, 14, 384) ED25519 = Algorithm("ED25519", 15, 15, 256) ED448 = Algorithm("ED448", 16, 16, 456) +RSASHA256OID = Algorithm("RSASHA256OID", 254, 256, 2048) +RSASHA512OID = Algorithm("RSASHA512OID", 254, 257, 2048) ALL_ALGORITHMS = [ RSASHA1, @@ -92,6 +94,8 @@ ALL_ALGORITHMS = [ ECDSAP384SHA384, ED25519, ED448, + RSASHA256OID, + RSASHA512OID, ] ALL_ALGORITHMS_BY_NUM = {alg.number: alg for alg in ALL_ALGORITHMS} @@ -153,6 +157,8 @@ CRYPTO_SUPPORTED_VARS = { "ECDSAP384SHA384_SUPPORTED": "0", "ED25519_SUPPORTED": "0", "ED448_SUPPORTED": "0", + "RSASHA256OID_SUPPORTED": "0", + "RSASHA512OID_SUPPORTED": "0", } SUPPORTED_ALGORITHMS: List[Algorithm] = [] diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 4f8f9dc958..e3d7082706 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -140,6 +140,14 @@ static const char *keystates[KEYSTATES_NVALUES] = { static dst_func_t *dst_t_func[DST_MAX_ALGS] = { 0 }; +/* length byte + 1.2.840.113549.1.1.11 BER encoded RFC 4055 */ +static unsigned char oid_rsasha256[] = { 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b }; + +/* length byte + 1.2.840.113549.1.1.13 BER encoded RFC 4055 */ +static unsigned char oid_rsasha512[] = { 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d }; + void gss_log(int level, const char *fmt, ...) ISC_FORMAT_PRINTF(2, 3); @@ -215,6 +223,18 @@ dst__lib_initialize(void) { #if HAVE_GSSAPI dst__gssapi_init(&dst_t_func[DST_ALG_GSSAPI]); #endif /* HAVE_GSSAPI */ + /* + * RSASHA256 using assigned OID 1.2.840.113549.1.1.11 as + * a private OID example. + */ + dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA256PRIVATEOID], + DST_ALG_RSASHA256PRIVATEOID); + /* + * RSASHA512 using assigned OID 1.2.840.113549.1.1.13 as + * a private OID example. + */ + dst__opensslrsa_init(&dst_t_func[DST_ALG_RSASHA512PRIVATEOID], + DST_ALG_RSASHA512PRIVATEOID); } void @@ -1311,6 +1331,12 @@ dst_key_sigsize(const dst_key_t *key, unsigned int *n) { case DST_ALG_RSASHA512: *n = (key->key_size + 7) / 8; break; + case DST_ALG_RSASHA256PRIVATEOID: + *n = (key->key_size + 7) / 8 + sizeof(oid_rsasha256); + break; + case DST_ALG_RSASHA512PRIVATEOID: + *n = (key->key_size + 7) / 8 + sizeof(oid_rsasha512); + break; case DST_ALG_ECDSA256: *n = DNS_SIG_ECDSA256SIZE; break; @@ -2664,7 +2690,10 @@ dst_hmac_algorithm_totext(dst_algorithm_t alg) { dns_secalg_t dst_algorithm_tosecalg(dst_algorithm_t dst_alg) { - static dns_secalg_t dns_alg[DST_MAX_ALGS] = { 0 }; + static dns_secalg_t dns_alg[DST_MAX_ALGS] = { + [DST_ALG_RSASHA256PRIVATEOID] = DNS_KEYALG_PRIVATEOID, + [DST_ALG_RSASHA512PRIVATEOID] = DNS_KEYALG_PRIVATEOID, + }; if (dst_alg < 256) { return dst_alg; @@ -2703,8 +2732,19 @@ dst_algorithm_fromprivateoid(isc_buffer_t *buffer) { * length byte followed by the OID of that length. */ if (r.length > 0 && ((unsigned int)r.base[0] + 1) <= r.length) { - return 0; + if (r.base[0] + 1 == sizeof(oid_rsasha256) && + memcmp(oid_rsasha256, r.base, sizeof(oid_rsasha256)) == 0) + { + return DST_ALG_RSASHA256PRIVATEOID; + } + + if (r.base[0] + 1 == sizeof(oid_rsasha512) && + memcmp(oid_rsasha512, r.base, sizeof(oid_rsasha512)) == 0) + { + return DST_ALG_RSASHA512PRIVATEOID; + } } + return 0; } diff --git a/lib/dns/dst_parse.c b/lib/dns/dst_parse.c index b897407c41..5e53a19e49 100644 --- a/lib/dns/dst_parse.c +++ b/lib/dns/dst_parse.c @@ -332,6 +332,8 @@ check_data(const dst_private_t *priv, const unsigned int alg, bool old, case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: return check_rsa(priv, external); case DST_ALG_ECDSA256: case DST_ALG_ECDSA384: @@ -694,6 +696,12 @@ dst__privstruct_writefile(const dst_key_t *key, const dst_private_t *priv, case DST_ALG_HMACSHA512: fprintf(fp, "(HMAC_SHA512)\n"); break; + case DST_ALG_RSASHA256PRIVATEOID: + fprintf(fp, "(OID:RSASHA256)\n"); + break; + case DST_ALG_RSASHA512PRIVATEOID: + fprintf(fp, "(OID:RSASHA512)\n"); + break; default: fprintf(fp, "(?)\n"); break; diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index 2e959461b4..8ba5fe1360 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -116,7 +116,9 @@ typedef enum dst_algorithm { /* * Put PRIVATE DNS and PRIVATE OID identifiers here. */ - DST_MAX_ALGS = 256, + DST_ALG_RSASHA256PRIVATEOID = 256, /* 1.2.840.113549.1.1.11 */ + DST_ALG_RSASHA512PRIVATEOID = 257, /* 1.2.840.113549.1.1.13 */ + DST_MAX_ALGS = 258, } dst_algorithm_t; /*% A buffer of this size is large enough to hold any key */ diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index f0b61fd499..8fac378ce6 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -430,6 +430,8 @@ dns_kasp_key_size(dns_kasp_key_t *key) { case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: min = (key->algorithm == DST_ALG_RSASHA512) ? 1024 : 512; if (key->length > -1) { size = (unsigned int)key->length; diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c index bfeb7bba76..e31ba6b15d 100644 --- a/lib/dns/opensslrsa_link.c +++ b/lib/dns/opensslrsa_link.c @@ -50,6 +50,14 @@ typedef struct rsa_components { const BIGNUM *e, *n, *d, *p, *q, *dmp1, *dmq1, *iqmp; } rsa_components_t; +/* length byte + 1.2.840.113549.1.1.11 BER encoded RFC 4055 */ +static unsigned char oid_rsasha256[] = { 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b }; + +/* length byte + 1.2.840.113549.1.1.13 BER encoded RFC 4055 */ +static unsigned char oid_rsasha512[] = { 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, + 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0d }; + static isc_result_t opensslrsa_components_get(const dst_key_t *key, rsa_components_t *c, bool private) { @@ -154,6 +162,8 @@ opensslrsa_valid_key_alg(unsigned int key_alg) { case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: return true; default: return false; @@ -181,12 +191,14 @@ opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) { } break; case DST_ALG_RSASHA256: + case DST_ALG_RSASHA256PRIVATEOID: /* From RFC 5702 */ if (dctx->key->key_size < 512 || dctx->key->key_size > 4096) { return ISC_R_FAILURE; } break; case DST_ALG_RSASHA512: + case DST_ALG_RSASHA512PRIVATEOID: /* From RFC 5702 */ if (dctx->key->key_size < 1024 || dctx->key->key_size > 4096) { return ISC_R_FAILURE; @@ -207,9 +219,11 @@ opensslrsa_createctx(dst_key_t *key, dst_context_t *dctx) { type = isc__crypto_sha1; /* SHA1 + RSA */ break; case DST_ALG_RSASHA256: + case DST_ALG_RSASHA256PRIVATEOID: type = isc__crypto_sha256; /* SHA256 + RSA */ break; case DST_ALG_RSASHA512: + case DST_ALG_RSASHA512PRIVATEOID: type = isc__crypto_sha512; break; default: @@ -277,6 +291,12 @@ opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { * Account to the space the OIDs and DNS names consume. */ switch (key->key_alg) { + case DST_ALG_RSASHA256PRIVATEOID: + len = sizeof(oid_rsasha256); + break; + case DST_ALG_RSASHA512PRIVATEOID: + len = sizeof(oid_rsasha512); + break; } isc_buffer_availableregion(sig, &r); @@ -289,6 +309,14 @@ opensslrsa_sign(dst_context_t *dctx, isc_buffer_t *sig) { * Add OID and DNS names to start of signature. */ switch (key->key_alg) { + case DST_ALG_RSASHA256PRIVATEOID: + isc_buffer_putmem(sig, oid_rsasha256, sizeof(oid_rsasha256)); + isc_region_consume(&r, sizeof(oid_rsasha256)); + break; + case DST_ALG_RSASHA512PRIVATEOID: + isc_buffer_putmem(sig, oid_rsasha512, sizeof(oid_rsasha512)); + isc_region_consume(&r, sizeof(oid_rsasha512)); + break; } if (!EVP_SignFinal(evp_md_ctx, r.base, &siglen, pkey)) { @@ -348,6 +376,24 @@ opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) { * Check identifying OID in front of public key material. */ switch (key->key_alg) { + case DST_ALG_RSASHA256PRIVATEOID: + if (length < sizeof(oid_rsasha256) || + memcmp(base, oid_rsasha256, sizeof(oid_rsasha256)) != 0) + { + return DST_R_VERIFYFAILURE; + } + base += sizeof(oid_rsasha256); + length -= sizeof(oid_rsasha256); + break; + case DST_ALG_RSASHA512PRIVATEOID: + if (length < sizeof(oid_rsasha512) || + memcmp(base, oid_rsasha512, sizeof(oid_rsasha512)) != 0) + { + return DST_R_VERIFYFAILURE; + } + base += sizeof(oid_rsasha512); + length -= sizeof(oid_rsasha512); + break; } status = EVP_VerifyFinal(evp_md_ctx, base, length, pkey); @@ -712,12 +758,14 @@ opensslrsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { } break; case DST_ALG_RSASHA256: + case DST_ALG_RSASHA256PRIVATEOID: /* From RFC 5702 */ if (key->key_size < 512 || key->key_size > 4096) { DST_RET(DST_R_INVALIDPARAM); } break; case DST_ALG_RSASHA512: + case DST_ALG_RSASHA512PRIVATEOID: /* From RFC 5702 */ if (key->key_size < 1024 || key->key_size > 4096) { DST_RET(DST_R_INVALIDPARAM); @@ -764,6 +812,20 @@ opensslrsa_todns(const dst_key_t *key, isc_buffer_t *data) { * Add identifying OID and DNS names to front of public key material. */ switch (key->key_alg) { + case DST_ALG_RSASHA256PRIVATEOID: + if (r.length < sizeof(oid_rsasha256)) { + DST_RET(ISC_R_NOSPACE); + } + isc_buffer_putmem(data, oid_rsasha256, sizeof(oid_rsasha256)); + isc_region_consume(&r, sizeof(oid_rsasha256)); + break; + case DST_ALG_RSASHA512PRIVATEOID: + if (r.length < sizeof(oid_rsasha512)) { + DST_RET(ISC_R_NOSPACE); + } + isc_buffer_putmem(data, oid_rsasha512, sizeof(oid_rsasha512)); + isc_region_consume(&r, sizeof(oid_rsasha512)); + break; } ret = opensslrsa_components_get(key, &c, false); @@ -825,6 +887,24 @@ opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) { * Check identifying OID in front of public key material. */ switch (key->key_alg) { + case DST_ALG_RSASHA256PRIVATEOID: + if (r.length < sizeof(oid_rsasha256) || + memcmp(r.base, oid_rsasha256, sizeof(oid_rsasha256)) != 0) + { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + isc_region_consume(&r, sizeof(oid_rsasha256)); + isc_buffer_forward(data, sizeof(oid_rsasha256)); + break; + case DST_ALG_RSASHA512PRIVATEOID: + if (r.length < sizeof(oid_rsasha512) || + memcmp(r.base, oid_rsasha512, sizeof(oid_rsasha512)) != 0) + { + DST_RET(DST_R_INVALIDPUBLICKEY); + } + isc_region_consume(&r, sizeof(oid_rsasha512)); + isc_buffer_forward(data, sizeof(oid_rsasha512)); + break; } length = r.length; @@ -1264,11 +1344,13 @@ check_algorithm(unsigned short algorithm) { len = sizeof(sha1_sig) - 1; break; case DST_ALG_RSASHA256: + case DST_ALG_RSASHA256PRIVATEOID: type = isc__crypto_sha256; /* SHA256 + RSA */ sig = sha256_sig; len = sizeof(sha256_sig) - 1; break; case DST_ALG_RSASHA512: + case DST_ALG_RSASHA512PRIVATEOID: type = isc__crypto_sha512; sig = sha512_sig; len = sizeof(sha512_sig) - 1; diff --git a/lib/dns/rcode.c b/lib/dns/rcode.c index 414a791538..133f49a99e 100644 --- a/lib/dns/rcode.c +++ b/lib/dns/rcode.c @@ -125,7 +125,9 @@ /* * PRIVATEOID subtypes we support. */ -#define PRIVATEOIDS /* currently empty */ +#define PRIVATEOIDS \ + { DST_ALG_RSASHA256PRIVATEOID, "RSASHA256OID", 0 }, \ + { DST_ALG_RSASHA512PRIVATEOID, "RSASHA512OID", 0 }, /* RFC2535 section 7.1 */ diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 21e09b079e..4b424d4bfb 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -260,6 +260,8 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, case DST_ALG_NSEC3RSASHA1: case DST_ALG_RSASHA256: case DST_ALG_RSASHA512: + case DST_ALG_RSASHA256PRIVATEOID: + case DST_ALG_RSASHA512PRIVATEOID: if (isc_crypto_fips_mode()) { min = 2048; } else { From 92393f3c97a7b473a043401a25f6074bb7cbc9ba Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 15 May 2025 08:47:39 +1000 Subject: [PATCH 13/15] Add example PRIVATEDNS algorithm identifiers to DS --- lib/dns/dst_api.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index e3d7082706..5e52548d29 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -2704,6 +2704,19 @@ dst_algorithm_tosecalg(dst_algorithm_t dst_alg) { return 0; } +#if TEST_PRIVATEDNS +/* + * These are examples of specifying an algorithm using + * PRIVATEDNS. When creating such an algorithm, use your + * organisation's domain name instead of "example.org" + * so the identifier will be globally unique. + */ +static unsigned char rsasha256dns_data[] = "\011rsasha256\007example\003org"; +static dns_name_t const rsasha256dns = DNS_NAME_INITABSOLUTE(rsasha256dns_data); +static unsigned char rsasha512dns_data[] = "\011rsasha512\007example\003org"; +static dns_name_t const rsasha512dns = DNS_NAME_INITABSOLUTE(rsasha512dns_data); +#endif + dst_algorithm_t dst_algorithm_fromprivatedns(isc_buffer_t *buffer) { dns_fixedname_t fixed; @@ -2718,6 +2731,27 @@ dst_algorithm_fromprivatedns(isc_buffer_t *buffer) { /* * Do name to dst_algorithm number mapping here. */ + switch (name->length) { +#if TEST_PRIVATEDNS + case 23: + switch (name->ndata[7]) { + case '2': + if (dns_name_equal(name, &rsasha256dns)) { + return DST_ALG_RSASHA256PRIVATEDNS; + } + break; + case '5': + if (dns_name_equal(name, &rsasha512dns)) { + return DST_ALG_RSASHA512PRIVATEDNS; + } + break; + } + break; +#endif + default: + break; + } + return 0; } From 8d554c0c030f6941ac6bc8274f872342d4f64985 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Fri, 16 May 2025 15:50:53 +1000 Subject: [PATCH 14/15] Add tests using PRIVATEOID algorithms There are 4 tests: 1) a zone using a known private OID. Validations should succeed and return AD=1. 2) a zone using an unknown private OID. Validation should succeed and return AD=0 as the DS to DNSKEY has provably unsupported algorithm. 3) a zone using a known private OID and an extra DS record. Validation should succeed as there is DS to DNSKEY with a known algorithm linkage. 4) a zone using an unknown private OID and an extra DS record. Validation should fail as only one of the DS records can be matched to a provable unknown algorithm. The algorithm of the second DS is indeterminate. --- bin/tests/system/dnssec/ns2/example.db.in | 15 +++ bin/tests/system/dnssec/ns2/sign.sh | 3 +- .../dnssec/ns3/extradsoid.example.db.in | 28 +++++ .../ns3/extradsunknownoid.example.db.in | 28 +++++ bin/tests/system/dnssec/ns3/named.conf.in | 25 +++++ .../dnssec/ns3/rsasha256oid.example.db.in | 28 +++++ .../dnssec/ns3/rsasha512oid.example.db.in | 28 +++++ bin/tests/system/dnssec/ns3/sign.sh | 104 ++++++++++++++++++ .../dnssec/ns3/unknownoid.example.db.in | 28 +++++ bin/tests/system/dnssec/tests.sh | 60 ++++++++++ bin/tests/system/dnssec/tests_sh_dnssec.py | 13 ++- 11 files changed, 357 insertions(+), 3 deletions(-) create mode 100644 bin/tests/system/dnssec/ns3/extradsoid.example.db.in create mode 100644 bin/tests/system/dnssec/ns3/extradsunknownoid.example.db.in create mode 100644 bin/tests/system/dnssec/ns3/rsasha256oid.example.db.in create mode 100644 bin/tests/system/dnssec/ns3/rsasha512oid.example.db.in create mode 100644 bin/tests/system/dnssec/ns3/unknownoid.example.db.in diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index a7ec0e471c..3f32821454 100644 --- a/bin/tests/system/dnssec/ns2/example.db.in +++ b/bin/tests/system/dnssec/ns2/example.db.in @@ -181,3 +181,18 @@ rsasha1-1024 NS ns.rsasha1-1024 ns.rsasha1-1024 A 10.53.0.3 dname-at-apex-nsec3 NS ns3 + +rsasha256oid NS ns.rsasha256oid +ns.rsasha256oid A 10.53.0.3 + +rsasha512oid NS ns.rsasha512oid +ns.rsasha512oid A 10.53.0.3 + +unknownoid NS ns.unknownoid +ns.unknownoid A 10.53.0.3 + +extradsoid NS ns.extradsoid +ns.extradsoid A 10.53.0.3 + +extradsunknownoid NS ns.extradsunknownoid +ns.extradsunknownoid A 10.53.0.3 diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index 3149422265..e6ddad16a1 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -65,7 +65,8 @@ for subdomain in digest-alg-unsupported ds-unsupported secure badds \ ttlpatch split-dnssec split-smart expired expiring upper lower \ dnskey-unknown dnskey-unsupported dnskey-unsupported-2 \ dnskey-nsec3-unknown managed-future future revkey \ - dname-at-apex-nsec3 occluded rsasha1 rsasha1-1024; do + dname-at-apex-nsec3 occluded rsasha1 rsasha1-1024 \ + rsasha256oid rsasha512oid unknownoid extradsoid extradsunknownoid; do cp "../ns3/dsset-$subdomain.example." . done diff --git a/bin/tests/system/dnssec/ns3/extradsoid.example.db.in b/bin/tests/system/dnssec/ns3/extradsoid.example.db.in new file mode 100644 index 0000000000..f6c4fabdd3 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/extradsoid.example.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/dnssec/ns3/extradsunknownoid.example.db.in b/bin/tests/system/dnssec/ns3/extradsunknownoid.example.db.in new file mode 100644 index 0000000000..f6c4fabdd3 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/extradsunknownoid.example.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/dnssec/ns3/named.conf.in b/bin/tests/system/dnssec/ns3/named.conf.in index c7f76b4638..9bb2ad8a53 100644 --- a/bin/tests/system/dnssec/ns3/named.conf.in +++ b/bin/tests/system/dnssec/ns3/named.conf.in @@ -429,11 +429,36 @@ zone "rsasha1-1024.example" { file "rsasha1-1024.example.db"; }; +zone "rsasha256oid.example" { + type primary; + file "rsasha256oid.example.db.signed"; +}; + +zone "rsasha512oid.example" { + type primary; + file "rsasha512oid.example.db.signed"; +}; + +zone "unknownoid.example" { + type primary; + file "unknownoid.example.db.signed"; +}; + zone "target.peer-ns-spoof" { type primary; file "target.peer-ns-spoof.db.signed"; }; +zone "extradsoid.example" { + type primary; + file "extradsoid.example.db.signed"; +}; + +zone "extradsunknownoid.example" { + type primary; + file "extradsunknownoid.example.db.signed"; +}; + dnssec-policy "siginterval1" { keys { ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; diff --git a/bin/tests/system/dnssec/ns3/rsasha256oid.example.db.in b/bin/tests/system/dnssec/ns3/rsasha256oid.example.db.in new file mode 100644 index 0000000000..f6c4fabdd3 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/rsasha256oid.example.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/dnssec/ns3/rsasha512oid.example.db.in b/bin/tests/system/dnssec/ns3/rsasha512oid.example.db.in new file mode 100644 index 0000000000..f6c4fabdd3 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/rsasha512oid.example.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh index 8f52e1f514..9ff2cdd0d3 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -424,6 +424,110 @@ cat "$infile" "$keyname.key" >"$zonefile" "$SIGNER" -P -o "$zone" "$zonefile" >/dev/null +# +# A RSASHA256OID zone. +# +zone=rsasha256oid.example. +infile=rsasha256oid.example.db.in +zonefile=rsasha256oid.example.db + +keyname=$("$KEYGEN" -q -a RSASHA256OID "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +"$SIGNER" -z -o "$zone" "$zonefile" >/dev/null + +# +# A RSASHA512OID zone. +# +zone=rsasha512oid.example. +infile=rsasha512oid.example.db.in +zonefile=rsasha512oid.example.db + +keyname=$("$KEYGEN" -q -a RSASHA512OID "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +"$SIGNER" -z -o "$zone" "$zonefile" >/dev/null + +# +# A UNKNOWNOID zone. Sign the zone using RSASHA512OID then +# update the OID in the DNSKEY and RRSIGS to the unknown OID +# 1.2.840.113549.1.1.14 +# +zone=unknownoid.example +infile=unknownoid.example.db.in +zonefile=unknownoid.example.db + +keyname=$("$KEYGEN" -q -a RSASHA512OID "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +# Sign with known OID RSASHA512OID +"$SIGNER" -z -o "$zone" -f "${zonefile}.stage1" "$zonefile" >/dev/null + +# Change OID from 1.2.840.113549.1.1.13 to 1.2.840.113549.1.1.14 +sed 's/CwYJKoZIhvcN/CwYJKoZIhvcO/' <"${zonefile}.stage1" >"${zonefile}.stage2" + +"$DSFROMKEY" -2A -f "${zonefile}.stage2" "$zone" >"dsset-${zone}." + +# extract the updated DNSKEY's tag +tag=$(awk '{print $4}' "dsset-${zone}.") + +# Update RRSIG tags +sed "s/\(2[0-9]* 2[0-9]*\) [1-9][0-9]* unknownoid.example./\1 ${tag} unknownoid.example./" <"${zonefile}.stage2" >"${zonefile}.signed" + +# +# A PRIVATEOID zone with a extra DS record for a non-existent DNSKEY. +# +zone=extradsoid.example. +infile=extradsoid.example.db.in +zonefile=extradsoid.example.db + +keyname=$("$KEYGEN" -q -a RSASHA512OID "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +"$SIGNER" -z -o "$zone" "$zonefile" >/dev/null + +# add a DS for a second key with the same algorithm +keyname=$("$KEYGEN" -q -a RSASHA512OID "$zone") + +"$DSFROMKEY" -2A "$keyname.key" >>"dsset-$zone" + +# +# A UNKNOWNOID with an extra DS zone. Sign the zone using RSASHA512OID +# then update the OID in the DNSKEY and RRSIGS to the unknown OID +# 1.2.840.113549.1.1.14. Add an additional DS which does not match +# the DNSKEY RRset with using this unknown OID. +# +zone=extradsunknownoid.example +infile=extradsunknownoid.example.db.in +zonefile=extradsunknownoid.example.db + +keyname=$("$KEYGEN" -q -a RSASHA512OID "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +# Sign with known OID RSASHA512OID +"$SIGNER" -z -o "$zone" -f "${zonefile}.stage1" "$zonefile" >/dev/null + +# Change OID from 1.2.840.113549.1.1.13 to 1.2.840.113549.1.1.14 +sed 's/CwYJKoZIhvcN/CwYJKoZIhvcO/' <"${zonefile}.stage1" >"${zonefile}.stage2" + +"$DSFROMKEY" -2A -f "${zonefile}.stage2" "$zone" >"dsset-${zone}." +tag=$(awk '{print $4}' "dsset-${zone}.") + +# Update RRSIG tags +sed "s/\(2[0-9]* 2[0-9]*\) [1-9][0-9]* extradsunknownoid.example./\1 ${tag} extradsunknownoid.example./" <"${zonefile}.stage2" >"${zonefile}.signed" + +# add a DS for a second key with the same algorithm +keyname=$("$KEYGEN" -L 300 -q -a RSASHA512OID "$zone") + +# Change OID from 1.2.840.113549.1.1.13 to 1.2.840.113549.1.1.14 and +# add the resulting DS to the dsset. +sed 's/CwYJKoZIhvcN/CwYJKoZIhvcO/' <"$keyname.key" | "$DSFROMKEY" -2A -f - "$zone" >>"dsset-${zone}." + # # A zone with the DNSKEY set only signed by the KSK # diff --git a/bin/tests/system/dnssec/ns3/unknownoid.example.db.in b/bin/tests/system/dnssec/ns3/unknownoid.example.db.in new file mode 100644 index 0000000000..f6c4fabdd3 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/unknownoid.example.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 3d24846952..65f0a1d61f 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -960,6 +960,66 @@ n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) +echo_i "checking positive validation with private algorithm works ($n)" +ret=0 +dig_with_opts +noauth a.rsasha256oid.example. \ + @10.53.0.3 a >dig.out.ns3.test$n || ret=1 +dig_with_opts +noauth a.rsasha256oid.example. \ + @10.53.0.4 a >dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns3.test$n dig.out.ns4.test$n || ret=1 +grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +if [ -x "${DELV}" ]; then + ret=0 + echo_i "checking positive validation NSEC3 using dns_client ($n)" + delv_with_opts @10.53.0.4 a a.nsec3.example >delv.out$n || ret=1 + grep "a.nsec3.example..*10.0.0.1" delv.out$n >/dev/null || ret=1 + grep "a.nsec3.example..*RRSIG.A [0-9][0-9]* 3 300.*" delv.out$n >/dev/null || ret=1 + n=$((n + 1)) + test "$ret" -eq 0 || echo_i "failed" + status=$((status + ret)) +fi + +echo_i "checking positive validation with unknown private algorithm works ($n)" +ret=0 +dig_with_opts +noauth a.unknownoid.example. \ + @10.53.0.3 a >dig.out.ns3.test$n || ret=1 +dig_with_opts +noauth a.unknownoid.example. \ + @10.53.0.4 a >dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns3.test$n dig.out.ns4.test$n || ret=1 +grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_i "checking positive validation with extra ds for private algorithm ($n)" +ret=0 +dig_with_opts +noauth a.extradsoid.example. \ + @10.53.0.3 a >dig.out.ns3.test$n || ret=1 +dig_with_opts +noauth a.extradsoid.example. \ + @10.53.0.4 a >dig.out.ns4.test$n || ret=1 +digcomp dig.out.ns3.test$n dig.out.ns4.test$n || ret=1 +grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + +echo_i "checking positive validation with extra ds for unknown private algorithm fails ($n)" +ret=0 +dig_with_opts +noauth a.extradsunknownoid.example. \ + @10.53.0.3 a >dig.out.ns3.test$n || ret=1 +dig_with_opts +noauth a.extradsunknownoid.example. \ + @10.53.0.4 a >dig.out.ns4.test$n || ret=1 +grep "status: NOERROR" dig.out.ns3.test$n >/dev/null || ret=1 +grep "status: SERVFAIL" dig.out.ns4.test$n >/dev/null || ret=1 +grep 'No DNSKEY for extradsunknownoid.example/DS with PRIVATEOID algorithm, tag [1-9][0-9]*$' ns4/named.run >/dev/null || ret=1 +n=$((n + 1)) +test "$ret" -eq 0 || echo_i "failed" +status=$((status + ret)) + # Check the bogus domain echo_i "checking failed validation ($n)" diff --git a/bin/tests/system/dnssec/tests_sh_dnssec.py b/bin/tests/system/dnssec/tests_sh_dnssec.py index ac95282452..0599c8eb69 100644 --- a/bin/tests/system/dnssec/tests_sh_dnssec.py +++ b/bin/tests/system/dnssec/tests_sh_dnssec.py @@ -84,6 +84,7 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/auto-nsec3.example.db", "ns3/badds.example.db", "ns3/bogus.example.db", + "ns3/digest-alg-unsupported.example.db", "ns3/disabled.managed.db", "ns3/disabled.trusted.db", "ns3/dname-at-apex-nsec3.example.db", @@ -94,13 +95,17 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/dnskey-unsupported-2.example.db", "ns3/dnskey-unsupported.example.db", "ns3/dnskey-unsupported.example.db.tmp", + "ns3/ds-unsupported.example.db", "ns3/dynamic.example.db", - "ns3/digest-alg-unsupported.example.db", "ns3/enabled.managed.db", "ns3/enabled.trusted.db", "ns3/example.bk", "ns3/expired.example.db", "ns3/expiring.example.db", + "ns3/extradsoid.example.db", + "ns3/extradsunknownoid.example.db", + "ns3/extradsunknownoid.example.db.stage1", + "ns3/extradsunknownoid.example.db.stage2", "ns3/future.example.db", "ns3/keyless.example.db", "ns3/kskonly.example.db", @@ -123,7 +128,9 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/revoked.trusted.db", "ns3/rfc2335.example.bk", "ns3/rsasha256.example.db", + "ns3/rsasha256oid.example.db", "ns3/rsasha512.example.db", + "ns3/rsasha512oid.example.db", "ns3/secure.below-cname.example.db", "ns3/secure.example.db", "ns3/secure.managed.db", @@ -138,6 +145,9 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/trusted-future.key", "ns3/ttlpatch.example.db", "ns3/ttlpatch.example.db.patched", + "ns3/unknownoid.example.db", + "ns3/unknownoid.example.db.stage1", + "ns3/unknownoid.example.db.stage2", "ns3/unsupported.managed.db", "ns3/unsupported.managed.db.tmp", "ns3/unsupported.trusted.db", @@ -146,7 +156,6 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/update-nsec3.example.db.signed", "ns3/upper.example.db", "ns3/upper.example.db.lower", - "ns3/ds-unsupported.example.db", "ns4/broken.conf", "ns4/managed.conf", "ns4/managed-keys.bind", From 38ef960fd0acd7889e6ed014def72b52f523c207 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Wed, 28 May 2025 20:02:48 +1000 Subject: [PATCH 15/15] Test extended DS digest type support Add a zone using DS records that embed the private algorithm identifier in the digest field. There are 2 DS record for an unsupported DNSSEC algorithm one of which that doesn't have a matching DNSKEY. This zone should validate as insecure as the validator can establish that both DS records are for unsupported DNSSEC algorithms. --- bin/tests/system/dnssec/ns2/example.db.in | 3 ++ bin/tests/system/dnssec/ns2/sign.sh | 3 +- .../ns3/extended-ds-unknown-oid.example.db.in | 28 +++++++++++++++ bin/tests/system/dnssec/ns3/named.conf.in | 5 +++ bin/tests/system/dnssec/ns3/sign.sh | 36 +++++++++++++++++++ bin/tests/system/dnssec/tests.sh | 14 ++++++++ bin/tests/system/dnssec/tests_sh_dnssec.py | 3 ++ 7 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 bin/tests/system/dnssec/ns3/extended-ds-unknown-oid.example.db.in diff --git a/bin/tests/system/dnssec/ns2/example.db.in b/bin/tests/system/dnssec/ns2/example.db.in index 3f32821454..1efb1755b4 100644 --- a/bin/tests/system/dnssec/ns2/example.db.in +++ b/bin/tests/system/dnssec/ns2/example.db.in @@ -196,3 +196,6 @@ ns.extradsoid A 10.53.0.3 extradsunknownoid NS ns.extradsunknownoid ns.extradsunknownoid A 10.53.0.3 + +extended-ds-unknown-oid NS ns.extended-ds-unknown-oid +ns.extended-ds-unknown-oid A 10.53.0.3 diff --git a/bin/tests/system/dnssec/ns2/sign.sh b/bin/tests/system/dnssec/ns2/sign.sh index e6ddad16a1..2658fd10b3 100644 --- a/bin/tests/system/dnssec/ns2/sign.sh +++ b/bin/tests/system/dnssec/ns2/sign.sh @@ -66,7 +66,8 @@ for subdomain in digest-alg-unsupported ds-unsupported secure badds \ dnskey-unknown dnskey-unsupported dnskey-unsupported-2 \ dnskey-nsec3-unknown managed-future future revkey \ dname-at-apex-nsec3 occluded rsasha1 rsasha1-1024 \ - rsasha256oid rsasha512oid unknownoid extradsoid extradsunknownoid; do + rsasha256oid rsasha512oid unknownoid extradsoid extradsunknownoid \ + extended-ds-unknown-oid; do cp "../ns3/dsset-$subdomain.example." . done diff --git a/bin/tests/system/dnssec/ns3/extended-ds-unknown-oid.example.db.in b/bin/tests/system/dnssec/ns3/extended-ds-unknown-oid.example.db.in new file mode 100644 index 0000000000..f6c4fabdd3 --- /dev/null +++ b/bin/tests/system/dnssec/ns3/extended-ds-unknown-oid.example.db.in @@ -0,0 +1,28 @@ +; 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. + +$TTL 300 ; 5 minutes +@ IN SOA mname1. . ( + 2009102722 ; serial + 20 ; refresh (20 seconds) + 20 ; retry (20 seconds) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.3 + +a A 10.0.0.1 +b A 10.0.0.2 +d A 10.0.0.4 +z A 10.0.0.26 +a.a.a.a.a.a.a.a.a.a.e A 10.0.0.27 +x CNAME a diff --git a/bin/tests/system/dnssec/ns3/named.conf.in b/bin/tests/system/dnssec/ns3/named.conf.in index 9bb2ad8a53..a9a1b207bf 100644 --- a/bin/tests/system/dnssec/ns3/named.conf.in +++ b/bin/tests/system/dnssec/ns3/named.conf.in @@ -459,6 +459,11 @@ zone "extradsunknownoid.example" { file "extradsunknownoid.example.db.signed"; }; +zone "extended-ds-unknown-oid.example" { + type primary; + file "extended-ds-unknown-oid.example.db.signed"; +}; + dnssec-policy "siginterval1" { keys { ksk key-directory lifetime unlimited algorithm @DEFAULT_ALGORITHM@; diff --git a/bin/tests/system/dnssec/ns3/sign.sh b/bin/tests/system/dnssec/ns3/sign.sh index 9ff2cdd0d3..8cf46a2f06 100644 --- a/bin/tests/system/dnssec/ns3/sign.sh +++ b/bin/tests/system/dnssec/ns3/sign.sh @@ -528,6 +528,42 @@ keyname=$("$KEYGEN" -L 300 -q -a RSASHA512OID "$zone") # add the resulting DS to the dsset. sed 's/CwYJKoZIhvcN/CwYJKoZIhvcO/' <"$keyname.key" | "$DSFROMKEY" -2A -f - "$zone" >>"dsset-${zone}." +# +# A UNKNOWNOID with an extra DS zone. Sign the zone using RSASHA512OID +# then update the OID in the DNSKEY and RRSIGS to the unknown OID +# 1.2.840.113549.1.1.14. Add an additional DS with an extended digest +# type that encoded the DNSKEY's private type identifier which does not +# match the DNSKEY RRset with using this unknown OID. +# +zone=extended-ds-unknown-oid.example +infile=extended-ds-unknown-oid.example.db.in +zonefile=extended-ds-unknown-oid.example.db + +keyname=$("$KEYGEN" -q -a RSASHA512OID "$zone") + +cat "$infile" "$keyname.key" >"$zonefile" + +# Sign with known OID RSASHA512OID +"$SIGNER" -z -o "$zone" -f "${zonefile}.stage1" "$zonefile" >/dev/null + +# Change OID from 1.2.840.113549.1.1.13 to 1.2.840.113549.1.1.14 +sed 's/CwYJKoZIhvcN/CwYJKoZIhvcO/' <"${zonefile}.stage1" >"${zonefile}.stage2" + +"$DSFROMKEY" -2A -f "${zonefile}.stage2" "$zone" >"dsset-${zone}." +tag=$(awk '{print $4}' "dsset-${zone}.") + +# Update RRSIG tags +sed "s/\(2[0-9]* 2[0-9]*\) [1-9][0-9]* ${zone}./\1 ${tag} ${zone}./" <"${zonefile}.stage2" >"${zonefile}.signed" + +if $FEATURETEST --extended-ds-digest; then + # add a DS for a second key with the same algorithm + keyname=$("$KEYGEN" -L 300 -q -a RSASHA512OID "$zone") + + # Change OID from 1.2.840.113549.1.1.13 to 1.2.840.113549.1.1.14 and + # add the resulting DS using digest type SHA-256-PRIVATE to the dsset. + sed 's/CwYJKoZIhvcN/CwYJKoZIhvcO/' <"$keyname.key" | "$DSFROMKEY" -a SHA-256-PRIVATE -A -f - "$zone" >>"dsset-${zone}." +fi + # # A zone with the DNSKEY set only signed by the KSK # diff --git a/bin/tests/system/dnssec/tests.sh b/bin/tests/system/dnssec/tests.sh index 65f0a1d61f..86441c6f94 100644 --- a/bin/tests/system/dnssec/tests.sh +++ b/bin/tests/system/dnssec/tests.sh @@ -1020,6 +1020,20 @@ n=$((n + 1)) test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) +if $FEATURETEST --extended-ds-digest; then + echo_i "checking positive validation with extra ds using extended digest type for unknown private algorithm succeeds ($n)" + ret=0 + dig_with_opts +noauth a.extended-ds-unknown-oid.example. \ + @10.53.0.3 a >dig.out.ns3.test$n || ret=1 + dig_with_opts +noauth a.extended-ds-unknown-oid.example. \ + @10.53.0.4 a >dig.out.ns4.test$n || ret=1 + digcomp dig.out.ns3.test$n dig.out.ns4.test$n || ret=1 + grep "flags:.*ad.*QUERY" dig.out.ns4.test$n >/dev/null && ret=1 + n=$((n + 1)) + test "$ret" -eq 0 || echo_i "failed" + status=$((status + ret)) +fi + # Check the bogus domain echo_i "checking failed validation ($n)" diff --git a/bin/tests/system/dnssec/tests_sh_dnssec.py b/bin/tests/system/dnssec/tests_sh_dnssec.py index 0599c8eb69..e4e0a085f5 100644 --- a/bin/tests/system/dnssec/tests_sh_dnssec.py +++ b/bin/tests/system/dnssec/tests_sh_dnssec.py @@ -102,6 +102,9 @@ pytestmark = pytest.mark.extra_artifacts( "ns3/example.bk", "ns3/expired.example.db", "ns3/expiring.example.db", + "ns3/extended-ds-unknown-oid.example.db", + "ns3/extended-ds-unknown-oid.example.db.stage1", + "ns3/extended-ds-unknown-oid.example.db.stage2", "ns3/extradsoid.example.db", "ns3/extradsunknownoid.example.db", "ns3/extradsunknownoid.example.db.stage1",