Reject RSA DNSKEYs with oversize public exponents at parse time

The wire-format RSA DNSKEY parser was the only key path with no upper
bound on the public exponent — opensslrsa_parse and opensslrsa_fromlabel
already cap at RSA_MAX_PUBEXP_BITS.  An attacker-controlled DNSKEY could
therefore force a validator to compute s^e mod n with e up to ~|n| bits,
amplifying every verify by ~120x for typical 2048-bit moduli (OpenSSL
itself only caps the exponent for moduli above 3072 bits).  Apply the
same bit-count cap to wire-format keys.

Assisted-by: Claude:claude-opus-4-7
(cherry picked from commit ab8c1a77e0)
This commit is contained in:
Ondřej Surý 2026-04-30 07:01:53 +02:00 committed by Ondřej Surý
parent 9a969bf1bc
commit ca6ea809b0
No known key found for this signature in database
GPG key ID: 2820F37E873DEA41
2 changed files with 50 additions and 0 deletions

View file

@ -711,6 +711,9 @@ opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
if (e == NULL || n == NULL) {
DST_RET(ISC_R_NOMEMORY);
}
if (BN_num_bits(e) > RSA_MAX_PUBEXP_BITS) {
DST_RET(ISC_R_RANGE);
}
key->key_size = BN_num_bits(n);

View file

@ -226,8 +226,55 @@ ISC_RUN_TEST_IMPL(isc_rsa_verify) {
dst_key_free(&key);
}
/* dst_key_fromdns rejects oversized RSA public exponents */
ISC_RUN_TEST_IMPL(isc_rsa_fromdns_oversized_exponent) {
isc_result_t result;
dns_fixedname_t fname;
dns_name_t *name;
dst_key_t *key = NULL;
isc_buffer_t buf;
unsigned char rdata[300] = { 0 };
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, NULL);
assert_int_equal(result, ISC_R_SUCCESS);
/* DNSKEY rdata: flags(2) + proto(1) + alg(1) + key */
rdata[i++] = 0x01; /* flags hi (KSK) */
rdata[i++] = 0x00; /* flags lo */
rdata[i++] = 0x03; /* protocol */
rdata[i++] = DST_ALG_RSASHA256;
/* RSA wire key: e_bytes + e + n. Use a 6-byte (48-bit) e
* with a non-zero leading byte so it exceeds the 35-bit cap. */
rdata[i++] = 6;
rdata[i++] = 0x01;
rdata[i++] = 0x02;
rdata[i++] = 0x03;
rdata[i++] = 0x04;
rdata[i++] = 0x05;
rdata[i++] = 0x06;
/* 256 bytes of arbitrary modulus (2048-bit). */
for (size_t j = 0; j < 256; j++) {
rdata[i++] = 0xAB;
}
isc_buffer_init(&buf, rdata, i);
isc_buffer_add(&buf, i);
result = dst_key_fromdns(name, dns_rdataclass_in, &buf, mctx, &key);
assert_int_equal(result, ISC_R_RANGE);
assert_null(key);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY_CUSTOM(isc_rsa_verify, setup_test, teardown_test)
ISC_TEST_ENTRY_CUSTOM(isc_rsa_fromdns_oversized_exponent, setup_test,
teardown_test)
ISC_TEST_LIST_END
ISC_TEST_MAIN