]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
Reject RSA DNSKEYs with degenerate modulus at parse time 11929/head
authorOndřej Surý <ondrej@isc.org>
Thu, 30 Apr 2026 09:55:14 +0000 (11:55 +0200)
committerOndřej Surý <ondrej@isc.org>
Thu, 30 Apr 2026 13:50:32 +0000 (15:50 +0200)
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
lib/dns/opensslrsa_link.c
tests/dns/rsa_test.c

index c3843190bfc944d2595aec0c63ee85e49c90c336..b0d0d950ed69684ca257815564e701b6de3f8cf5 100644 (file)
@@ -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);
index 83364e1e9125cb0817cfe0e59c76b8e61c0a4c29..c00bda6c9f1fbd8a91bd7ed8a273bd1cdef7c0fe 100644 (file)
@@ -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