From 2db0806b6c44e28cd0a63c8964cadc1914708b23 Mon Sep 17 00:00:00 2001 From: Mark Andrews Date: Thu, 5 Jun 2025 14:49:10 +1000 Subject: [PATCH] Warn about deprecated DNSKEY and DS algorithms / digest types DNSKEY algorithms RSASHA1 and RSASHA-NSEC3-SHA1 and DS digest type SHA1 are deprecated. Log when these are present in primary zone files and when generating new DNSKEYs, DS and CDS records. (cherry picked from commit cb6903c55e9ff6135a37c987fb6cee327967f0db) --- bin/dnssec/dnssec-dsfromkey.c | 11 +- bin/dnssec/dnssec-keyfromlabel.c | 19 ++- bin/dnssec/dnssec-keygen.c | 32 +++-- bin/tests/system/checkconf/tests.sh | 2 +- lib/dns/dnssec.c | 2 +- lib/dns/include/dns/ds.h | 2 + lib/dns/zone.c | 185 +++++++++++++++++++++++++++- lib/isccfg/kaspconf.c | 12 ++ 8 files changed, 244 insertions(+), 21 deletions(-) diff --git a/bin/dnssec/dnssec-dsfromkey.c b/bin/dnssec/dnssec-dsfromkey.c index d0d32b3552..d4d8f3cc2e 100644 --- a/bin/dnssec/dnssec-dsfromkey.c +++ b/bin/dnssec/dnssec-dsfromkey.c @@ -318,6 +318,11 @@ emits(bool showall, bool cds, dns_rdata_t *rdata) { n = sizeof(dtype) / sizeof(dtype[0]); for (i = 0; i < n; i++) { + if (dtype[i] == DNS_DSDIGEST_SHA1) { + fprintf(stderr, + "WARNING: DS digest type %u is deprecated\n", + i); + } if (dtype[i] != 0) { emit(dtype[i], showall, cds, rdata); } @@ -336,10 +341,10 @@ usage(void) { fprintf(stderr, " %s [-h|-V]\n\n", program); fprintf(stderr, "Version: %s\n", PACKAGE_VERSION); fprintf(stderr, "Options:\n" - " -1: digest algorithm SHA-1\n" + " -1: digest algorithm SHA-1 (deprecated)\n" " -2: digest algorithm SHA-256\n" - " -a algorithm: digest algorithm (SHA-1, SHA-256 or " - "SHA-384)\n" + " -a algorithm: digest algorithm (SHA-1 " + "(deprecated), SHA-256 or SHA-384)\n" " -A: include all keys in DS set, not just KSKs (-f " "only)\n" " -c class: rdata class for DS set (default IN) (-f " diff --git a/bin/dnssec/dnssec-keyfromlabel.c b/bin/dnssec/dnssec-keyfromlabel.c index 6cf9ec98ad..a42969d3ff 100644 --- a/bin/dnssec/dnssec-keyfromlabel.c +++ b/bin/dnssec/dnssec-keyfromlabel.c @@ -57,8 +57,8 @@ usage(void) { fprintf(stderr, " name: owner of the key\n"); fprintf(stderr, "Other options:\n"); fprintf(stderr, " -a algorithm: \n" - " DH | RSASHA1 |\n" - " NSEC3RSASHA1 |\n" + " RSASHA1 (deprecated) |\n" + " NSEC3RSASHA1 (deprecated) |\n" " RSASHA256 | RSASHA512 |\n" " ECDSAP256SHA256 | ECDSAP384SHA384 |\n" " ED25519 | ED448\n"); @@ -571,6 +571,21 @@ main(int argc, char **argv) { fatal("invalid DNSKEY nametype %s", nametype); } + switch (alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: { + char algstr[DNS_SECALG_FORMATSIZE]; + dns_secalg_format(alg, algstr, sizeof(algstr)); + fprintf(stderr, + "WARNING: DNSKEY algorithm '%s' is deprecated. Please " + "migrate to another algorithm\n", + algstr); + break; + } + default: + break; + } + rdclass = strtoclass(classname); if (directory == NULL) { diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 5871cc9d3e..c768c766aa 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -141,14 +141,17 @@ usage(void) { fprintf(stderr, " -l : configuration file with dnssec-policy " "statement\n"); fprintf(stderr, " -a :\n"); - fprintf(stderr, " RSASHA1 | NSEC3RSASHA1 |\n"); + fprintf(stderr, + " RSASHA1 (deprecated) | NSEC3RSASHA1 (deprecated) |\n"); fprintf(stderr, " RSASHA256 | RSASHA512 |\n"); fprintf(stderr, " ECDSAP256SHA256 | ECDSAP384SHA384 |\n"); fprintf(stderr, " ED25519 | ED448 | DH\n"); fprintf(stderr, " -3: use NSEC3-capable algorithm\n"); fprintf(stderr, " -b :\n"); - fprintf(stderr, " RSASHA1:\t[1024..%d]\n", MAX_RSA); - fprintf(stderr, " NSEC3RSASHA1:\t[1024..%d]\n", MAX_RSA); + fprintf(stderr, " RSASHA1 (deprecated) :\t[1024..%d]\n", + MAX_RSA); + fprintf(stderr, " NSEC3RSASHA1 (deprecated) :\t[1024..%d]\n", + MAX_RSA); fprintf(stderr, " RSASHA256:\t[1024..%d]\n", MAX_RSA); fprintf(stderr, " RSASHA512:\t[1024..%d]\n", MAX_RSA); fprintf(stderr, " DH:\t\t[128..4096]\n"); @@ -522,21 +525,34 @@ 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 DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + dns_secalg_format(ctx->alg, algstr, sizeof(algstr)); + fprintf(stderr, + "WARNING: DNSKEY algorithm '%s' is deprecated. Please " + "migrate to another algorithm\n", + algstr); + break; + default: + break; + } + + switch (ctx->alg) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + case DST_ALG_RSASHA256: if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) { fatal("RSA key size %d out of range", ctx->size); } break; - case DNS_KEYALG_RSASHA512: + case DST_ALG_RSASHA512: if (ctx->size != 0 && (ctx->size < 1024 || ctx->size > MAX_RSA)) { fatal("RSA key size %d out of range", ctx->size); } break; - case DNS_KEYALG_DH: + case DST_ALG_DH: if (ctx->size != 0 && (ctx->size < 128 || ctx->size > 4096)) { fatal("DH key size %d out of range", ctx->size); } diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index 7d222ed863..695067fe74 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -650,7 +650,7 @@ ret=0 $CHECKCONF kasp-bad-nsec3-iter.conf >checkconf.out$n 2>&1 && ret=1 grep "dnssec-policy: nsec3 iterations value 151 out of range" /dev/null || ret=1 lines=$(wc -l <"checkconf.out$n") -if [ $lines -ne 3 ]; then ret=1; fi +if [ $lines -ne 5 ]; then ret=1; fi if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status + ret)) diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 954c914221..400a41f16f 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -411,7 +411,7 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, } /* - * NS, SOA and DNSSKEY records are signed by their owner. + * NS, SOA and DNSKEY records are signed by their owner. * DS records are signed by the parent. */ switch (set->type) { diff --git a/lib/dns/include/dns/ds.h b/lib/dns/include/dns/ds.h index 629729bc3a..649ca80065 100644 --- a/lib/dns/include/dns/ds.h +++ b/lib/dns/include/dns/ds.h @@ -23,6 +23,8 @@ #define DNS_DSDIGEST_GOST (3) #define DNS_DSDIGEST_SHA384 (4) +#define DNS_DSDIGEST_MAX (255) + /* * Assuming SHA-384 digest type. */ diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 542403e489..8c22f0f8ec 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -3327,16 +3327,17 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { dns_rdata_mx_t mx; dns_rdata_ns_t ns; dns_rdata_in_srv_t srv; - dns_rdata_t rdata; dns_name_t *name; dns_name_t *bottom; isc_result_t result; bool ok = true, have_spf, have_txt; + char namebuf[DNS_NAME_FORMATSIZE]; + bool logged_algorithm[DST_MAX_ALGS]; + bool logged_digest_type[DNS_DSDIGEST_MAX + 1]; name = dns_fixedname_initname(&fixed); bottom = dns_fixedname_initname(&fixedbottom); dns_rdataset_init(&rdataset); - dns_rdata_init(&rdata); result = dns_db_createiterator(db, 0, &dbiterator); if (result != ISC_R_SUCCESS) { @@ -3362,6 +3363,55 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { dns_dbiterator_pause(dbiterator); + /* + * Check for deprecated KEY algorithms + */ + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_key, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto checkforns; + } + + memset(logged_algorithm, 0, sizeof(logged_algorithm)); + for (result = dns_rdataset_first(&rdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_key_t key; + dns_rdataset_current(&rdataset, &rdata); + + result = dns_rdata_tostruct(&rdata, &key, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + + /* + * If we ever deprecate a private algorithm use + * dst_algorithm_fromdata() here. + */ + switch (key.algorithm) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + if (!logged_algorithm[key.algorithm]) { + char algbuf[DNS_SECALG_FORMATSIZE]; + dns_name_format(name, namebuf, + sizeof(namebuf)); + dns_secalg_format(key.algorithm, algbuf, + sizeof(algbuf)); + dnssec_log(zone, ISC_LOG_WARNING, + "%s/KEY deprecated " + "algorithm %u (%s)", + namebuf, key.algorithm, + algbuf); + logged_algorithm[key.algorithm] = true; + } + break; + default: + break; + } + } + dns_rdataset_disassociate(&rdataset); + + checkforns: /* * Don't check the NS records at the origin. */ @@ -3374,6 +3424,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { if (result != ISC_R_SUCCESS) { goto checkfordname; } + /* * Remember bottom of zone due to NS. */ @@ -3381,6 +3432,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &ns, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3391,6 +3443,73 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { result = dns_rdataset_next(&rdataset); } dns_rdataset_disassociate(&rdataset); + + /* + * Check for deprecated DS digest types. + */ + result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ds, + 0, 0, &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto next; + } + + memset(logged_algorithm, 0, sizeof(logged_algorithm)); + memset(logged_digest_type, 0, sizeof(logged_digest_type)); + 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); + dns_rdata_ds_t ds; + + result = dns_rdata_tostruct(&rdata, &ds, NULL); + RUNTIME_CHECK(result == ISC_R_SUCCESS); + switch (ds.digest_type) { + case DNS_DSDIGEST_SHA1: + if (!logged_digest_type[ds.digest_type]) { + char algbuf[DNS_DSDIGEST_FORMATSIZE]; + dns_name_format(name, namebuf, + sizeof(namebuf)); + dns_dsdigest_format(ds.digest_type, + algbuf, + sizeof(algbuf)); + dnssec_log(zone, ISC_LOG_WARNING, + "%s/DS deprecated digest " + "type %u (%s)", + namebuf, ds.digest_type, + algbuf); + logged_digest_type[ds.digest_type] = + true; + } + break; + } + + /* + * If we ever deprecate a private algorithm use + * dst_algorithm_fromdata() here. + */ + switch (ds.algorithm) { + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3RSASHA1: + if (!logged_algorithm[ds.algorithm]) { + char algbuf[DNS_SECALG_FORMATSIZE]; + dns_name_format(name, namebuf, + sizeof(namebuf)); + dns_secalg_format(ds.algorithm, algbuf, + sizeof(algbuf)); + dnssec_log(zone, ISC_LOG_WARNING, + "%s/DS deprecated algorithm " + "%u (%s)", + namebuf, ds.algorithm, + algbuf); + logged_algorithm[ds.algorithm] = true; + } + break; + } + } + dns_rdataset_disassociate(&rdataset); + goto next; checkfordname: @@ -3412,6 +3531,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &mx, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3434,6 +3554,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &srv, NULL); RUNTIME_CHECK(result == ISC_R_SUCCESS); @@ -3470,6 +3591,7 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { } result = dns_rdataset_first(&rdataset); while (result == ISC_R_SUCCESS) { + dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); have_txt = isspf(&rdata); dns_rdata_reset(&rdata); @@ -3482,8 +3604,6 @@ integrity_checks(dns_zone_t *zone, dns_db_t *db) { notxt: if (have_spf && !have_txt) { - char namebuf[DNS_NAME_FORMATSIZE]; - dns_name_format(name, namebuf, sizeof(namebuf)); dns_zone_log(zone, ISC_LOG_WARNING, "'%s' found type " @@ -3516,9 +3636,10 @@ zone_check_dnskeys(dns_zone_t *zone, dns_db_t *db) { dns_dbnode_t *node = NULL; dns_dbversion_t *version = NULL; dns_rdata_dnskey_t dnskey; - dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_t rdataset; isc_result_t result; + bool logged_algorithm[DST_MAX_ALGS] = { 0 }; + bool alldeprecated = true; result = dns_db_findnode(db, &zone->origin, false, &node); if (result != ISC_R_SUCCESS) { @@ -3536,6 +3657,8 @@ zone_check_dnskeys(dns_zone_t *zone, dns_db_t *db) { for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; result = dns_rdataset_next(&rdataset)) { + char algbuf[DNS_SECALG_FORMATSIZE]; + dns_rdata_t rdata = DNS_RDATA_INIT; dns_rdataset_current(&rdataset, &rdata); result = dns_rdata_tostruct(&rdata, &dnskey, NULL); INSIST(result == ISC_R_SUCCESS); @@ -3577,10 +3700,36 @@ zone_check_dnskeys(dns_zone_t *zone, dns_db_t *db) { algorithm, dnskey.algorithm, dst_region_computeid(&r)); } - dns_rdata_reset(&rdata); + + switch (dnskey.algorithm) { + case DNS_KEYALG_RSAMD5: + case DNS_KEYALG_DSA: + case DNS_KEYALG_RSASHA1: + case DNS_KEYALG_NSEC3DSA: + case DNS_KEYALG_NSEC3RSASHA1: + case DNS_KEYALG_ECCGOST: + if (!logged_algorithm[dnskey.algorithm]) { + dns_secalg_format(dnskey.algorithm, algbuf, + sizeof(algbuf)); + dnssec_log(zone, ISC_LOG_WARNING, + "deprecated DNSKEY algorithm found: " + "%u (%s)\n", + dnskey.algorithm, algbuf); + logged_algorithm[dnskey.algorithm] = true; + } + break; + default: + alldeprecated = false; + break; + } } dns_rdataset_disassociate(&rdataset); + if (alldeprecated) { + dnssec_log(zone, ISC_LOG_WARNING, + "all DNSKEY algorithms found are deprecated"); + } + cleanup: if (node != NULL) { dns_db_detachnode(db, &node); @@ -22873,6 +23022,7 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { * record which must be by itself. */ if (dns_rdataset_isassociated(&cds)) { + bool logged_digest_type[DNS_DSDIGEST_MAX + 1] = { 0 }; bool delete = false; memset(algorithms, notexpected, sizeof(algorithms)); for (result = dns_rdataset_first(&cds); result == ISC_R_SUCCESS; @@ -22900,6 +23050,29 @@ dns_zone_cdscheck(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *version) { } CHECK(dns_rdata_tostruct(&crdata, &structcds, NULL)); + + /* + * Log deprecated CDS digest types. + */ + switch (structcds.digest_type) { + case DNS_DSDIGEST_SHA1: + if (!logged_digest_type[structcds.digest_type]) + { + char algbuf[DNS_DSDIGEST_FORMATSIZE]; + dns_dsdigest_format( + structcds.digest_type, algbuf, + sizeof(algbuf)); + dnssec_log(zone, ISC_LOG_WARNING, + "deprecated CDS digest type " + "%u (%s)", + structcds.digest_type, + algbuf); + logged_digest_type[structcds.digest_type] = + true; + } + break; + } + if (algorithms[structcds.algorithm] == 0) { algorithms[structcds.algorithm] = expected; } diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 47e47cd841..b8491a8f1f 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -171,6 +171,18 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, goto cleanup; } + switch (key->algorithm) { + case DST_ALG_RSASHA1: + case DST_ALG_NSEC3RSASHA1: + cfg_obj_log(obj, logctx, ISC_LOG_WARNING, + "dnssec-policy: DNSSEC algorithm %s is " + "deprecated", + alg.base); + break; + default: + break; + } + obj = cfg_tuple_get(config, "length"); if (cfg_obj_isuint32(obj)) { uint32_t min, size;