From: Ondřej Surý Date: Thu, 30 Apr 2026 09:55:14 +0000 (+0200) Subject: Reject RSA DNSKEYs with degenerate modulus at parse time X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=045d5d04559a41b71e22a6e06b132c2851354e9c;p=thirdparty%2Fbind9.git Reject RSA DNSKEYs with degenerate modulus at parse time The wire-format RSA DNSKEY parser used the residual rdata length after the exponent as the modulus length, with no positive lower bound. A crafted DNSKEY whose declared exponent length consumed the whole buffer produced n = 0; the BN_bin2bn(_, 0, _) returned a non-NULL BIGNUM, the NULL-check passed, and dnssec-importkey -f wrote out a "valid" key with no key material. RSASHA1 also bypassed the algorithm-specific lower bound in opensslrsa_createctx (which only checks an upper bound for the SHA1 algorithms), so the degenerate key reached the verify path with whatever behaviour the linked OpenSSL exhibits for n = 0. Add OPENSSLRSA_MIN_MODULUS_BITS = 512 (the lowest legitimate modulus across the RSA DNSSEC algorithms per RFC 5702) and reject smaller moduli at parse time in opensslrsa_fromdns, opensslrsa_parse, and opensslrsa_fromlabel — the same three load paths where the existing exponent upper-bound check lives. Assisted-by: Claude:claude-opus-4-7 --- diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c index c3843190bfc..b0d0d950ed6 100644 --- a/lib/dns/opensslrsa_link.c +++ b/lib/dns/opensslrsa_link.c @@ -34,6 +34,7 @@ #include "openssl_shim.h" #define OPENSSLRSA_MAX_MODULUS_BITS 4096 +#define OPENSSLRSA_MIN_MODULUS_BITS 512 /* length byte + 1.2.840.113549.1.1.11 BER encoded RFC 4055 */ static unsigned char oid_rsasha256[] = { 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, @@ -465,6 +466,9 @@ opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) { if (BN_num_bits(c.e) > RSA_MAX_PUBEXP_BITS) { CLEANUP(ISC_R_RANGE); } + if (BN_num_bits(c.n) < OPENSSLRSA_MIN_MODULUS_BITS) { + CLEANUP(ISC_R_RANGE); + } isc_buffer_forward(data, length); key->key_size = BN_num_bits(c.n); @@ -699,6 +703,9 @@ opensslrsa_parse(dst_key_t *key, isc_lex_t *lexer, dst_key_t *pub) { if (c.n == NULL || c.e == NULL) { CLEANUP(DST_R_INVALIDPRIVATEKEY); } + if (BN_num_bits(c.n) < OPENSSLRSA_MIN_MODULUS_BITS) { + CLEANUP(ISC_R_RANGE); + } if (BN_num_bits(c.e) > RSA_MAX_PUBEXP_BITS) { CLEANUP(ISC_R_RANGE); } @@ -740,6 +747,9 @@ opensslrsa_fromlabel(dst_key_t *key, const char *label, const char *pin) { if (!isc_ossl_wrap_rsa_key_bits_leq(pubpkey, RSA_MAX_PUBEXP_BITS)) { CLEANUP(ISC_R_RANGE); } + if (EVP_PKEY_bits(pubpkey) < OPENSSLRSA_MIN_MODULUS_BITS) { + CLEANUP(ISC_R_RANGE); + } key->label = isc_mem_strdup(key->mctx, label); key->key_size = EVP_PKEY_bits(privpkey); diff --git a/tests/dns/rsa_test.c b/tests/dns/rsa_test.c index 83364e1e912..c00bda6c9f1 100644 --- a/tests/dns/rsa_test.c +++ b/tests/dns/rsa_test.c @@ -250,9 +250,48 @@ ISC_RUN_TEST_IMPL(isc_rsa_fromdns_oversized_exponent) { assert_null(key); } +/* dst_key_fromdns rejects RSA DNSKEYs with a degenerate modulus */ +ISC_RUN_TEST_IMPL(isc_rsa_fromdns_short_modulus) { + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *name; + dst_key_t *key = NULL; + isc_buffer_t buf; + unsigned char rdata[8]; + size_t i = 0; + + UNUSED(state); + + name = dns_fixedname_initname(&fname); + isc_buffer_constinit(&buf, "rsa.", 4); + isc_buffer_add(&buf, 4); + result = dns_name_fromtext(name, &buf, NULL, 0); + assert_int_equal(result, ISC_R_SUCCESS); + + /* + * DNSKEY rdata: flags(2) + proto(1) + alg(1) + RSA wire pubkey + * 'AQM=' (0x01 0x03): e_bytes=1, exponent=0x03, modulus tail empty. + */ + rdata[i++] = 0x01; + rdata[i++] = 0x00; + rdata[i++] = 0x03; + rdata[i++] = DST_ALG_RSASHA256; + rdata[i++] = 0x01; + rdata[i++] = 0x03; + + isc_buffer_init(&buf, rdata, i); + isc_buffer_add(&buf, i); + + result = dst_key_fromdns(name, dns_rdataclass_in, &buf, isc_g_mctx, + &key); + assert_int_equal(result, ISC_R_RANGE); + assert_null(key); +} + ISC_TEST_LIST_START ISC_TEST_ENTRY(isc_rsa_verify) ISC_TEST_ENTRY(isc_rsa_fromdns_oversized_exponent) +ISC_TEST_ENTRY(isc_rsa_fromdns_short_modulus) ISC_TEST_LIST_END ISC_TEST_MAIN