mirror of
https://github.com/isc-projects/bind9.git
synced 2026-06-10 16:40:00 -04:00
fix: usr: Reject unsupported RSA DNSKEY shapes during DNSSEC validation
An authoritative server publishing an RSA DNSKEY with an unusually large modulus or an exotic public exponent could make each DNSSEC signature check on a validating recursive resolver noticeably more expensive than for a normally sized key. Such DNSKEYs are now treated as invalid. Closes #6008 Merge branch '6008-reject-oversized-rsa-dnskeys' into 'main' See merge request isc-projects/bind9!12054
This commit is contained in:
commit
c7a0a6af4d
8 changed files with 239 additions and 32 deletions
|
|
@ -237,6 +237,8 @@ dst__lib_initialize(void) {
|
|||
|
||||
void
|
||||
dst__lib_shutdown(void) {
|
||||
dst__opensslrsa_shutdown();
|
||||
|
||||
isc_mem_detach(&dst__mctx);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -192,6 +192,8 @@ dst__hmacsha512_init(struct dst_func **funcp);
|
|||
void
|
||||
dst__opensslrsa_init(struct dst_func **funcp, unsigned short algorithm);
|
||||
void
|
||||
dst__opensslrsa_shutdown(void);
|
||||
void
|
||||
dst__opensslecdsa_init(struct dst_func **funcp);
|
||||
void
|
||||
dst__openssleddsa_init(struct dst_func **funcp, unsigned char algorithm);
|
||||
|
|
|
|||
|
|
@ -21,13 +21,6 @@
|
|||
#include <openssl/opensslv.h>
|
||||
#include <openssl/rsa.h>
|
||||
|
||||
/*
|
||||
* Limit the size of public exponents.
|
||||
*/
|
||||
#ifndef RSA_MAX_PUBEXP_BITS
|
||||
#define RSA_MAX_PUBEXP_BITS 35
|
||||
#endif /* ifndef RSA_MAX_PUBEXP_BITS */
|
||||
|
||||
#if !HAVE_EVP_PKEY_EQ
|
||||
#define EVP_PKEY_eq EVP_PKEY_cmp
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -36,6 +36,30 @@
|
|||
#define OPENSSLRSA_MAX_MODULUS_BITS 4096
|
||||
#define OPENSSLRSA_MIN_MODULUS_BITS 512
|
||||
|
||||
static BIGNUM *rsa_exponent_min = NULL;
|
||||
static BIGNUM *rsa_exponent_max = NULL;
|
||||
|
||||
/*
|
||||
* Accept odd public exponents in [3, 2^32 + 1]. That covers every Fermat
|
||||
* prime up to F5 and the odd intermediate values seen on the wire.
|
||||
*/
|
||||
static bool
|
||||
rsa_exponent_in_range(const BIGNUM *e) {
|
||||
if (!BN_is_odd(e)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BN_cmp(e, rsa_exponent_min) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (BN_cmp(e, rsa_exponent_max) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* 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 };
|
||||
|
|
@ -233,7 +257,10 @@ opensslrsa_verify(dst_context_t *dctx, const isc_region_t *sig) {
|
|||
evp_md_ctx = dctx->ctxdata.evp_md_ctx;
|
||||
pkey = key->keydata.pkeypair.pub;
|
||||
|
||||
if (!isc_ossl_wrap_rsa_key_bits_leq(pkey, OPENSSLRSA_MAX_MODULUS_BITS))
|
||||
if (!isc_ossl_wrap_rsa_modulus_bits_in_range(
|
||||
pkey, OPENSSLRSA_MIN_MODULUS_BITS,
|
||||
OPENSSLRSA_MAX_MODULUS_BITS) ||
|
||||
!isc_ossl_wrap_rsa_exponent_is_allowed(pkey))
|
||||
{
|
||||
return DST_R_VERIFYFAILURE;
|
||||
}
|
||||
|
|
@ -463,10 +490,12 @@ opensslrsa_fromdns(dst_key_t *key, isc_buffer_t *data) {
|
|||
if (c.e == NULL || c.n == NULL) {
|
||||
CLEANUP(ISC_R_NOMEMORY);
|
||||
}
|
||||
if (BN_num_bits(c.e) > RSA_MAX_PUBEXP_BITS) {
|
||||
if (BN_num_bits(c.n) < OPENSSLRSA_MIN_MODULUS_BITS ||
|
||||
BN_num_bits(c.n) > OPENSSLRSA_MAX_MODULUS_BITS)
|
||||
{
|
||||
CLEANUP(ISC_R_RANGE);
|
||||
}
|
||||
if (BN_num_bits(c.n) < OPENSSLRSA_MIN_MODULUS_BITS) {
|
||||
if (!rsa_exponent_in_range(c.e)) {
|
||||
CLEANUP(ISC_R_RANGE);
|
||||
}
|
||||
isc_buffer_forward(data, length);
|
||||
|
|
@ -703,10 +732,12 @@ 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) {
|
||||
if (BN_num_bits(c.n) < OPENSSLRSA_MIN_MODULUS_BITS ||
|
||||
BN_num_bits(c.n) > OPENSSLRSA_MAX_MODULUS_BITS)
|
||||
{
|
||||
CLEANUP(ISC_R_RANGE);
|
||||
}
|
||||
if (BN_num_bits(c.e) > RSA_MAX_PUBEXP_BITS) {
|
||||
if (!rsa_exponent_in_range(c.e)) {
|
||||
CLEANUP(ISC_R_RANGE);
|
||||
}
|
||||
|
||||
|
|
@ -744,10 +775,13 @@ opensslrsa_fromlabel(dst_key_t *key, const char *label, const char *pin) {
|
|||
CHECK(dst__openssl_fromlabel(EVP_PKEY_RSA, label, pin, &pubpkey,
|
||||
&privpkey));
|
||||
|
||||
if (!isc_ossl_wrap_rsa_key_bits_leq(pubpkey, RSA_MAX_PUBEXP_BITS)) {
|
||||
if (!isc_ossl_wrap_rsa_exponent_is_allowed(pubpkey)) {
|
||||
CLEANUP(ISC_R_RANGE);
|
||||
}
|
||||
if (EVP_PKEY_bits(pubpkey) < OPENSSLRSA_MIN_MODULUS_BITS) {
|
||||
if (!isc_ossl_wrap_rsa_modulus_bits_in_range(
|
||||
pubpkey, OPENSSLRSA_MIN_MODULUS_BITS,
|
||||
OPENSSLRSA_MAX_MODULUS_BITS))
|
||||
{
|
||||
CLEANUP(ISC_R_RANGE);
|
||||
}
|
||||
|
||||
|
|
@ -926,4 +960,28 @@ dst__opensslrsa_init(dst_func_t **funcp, unsigned short algorithm) {
|
|||
*funcp = &opensslrsa_functions;
|
||||
}
|
||||
}
|
||||
|
||||
if (rsa_exponent_min == NULL) {
|
||||
rsa_exponent_min = BN_new();
|
||||
INSIST(rsa_exponent_min != NULL);
|
||||
|
||||
RUNTIME_CHECK(BN_set_word(rsa_exponent_min, 3) == 1);
|
||||
}
|
||||
|
||||
if (rsa_exponent_max == NULL) {
|
||||
rsa_exponent_max = BN_new();
|
||||
INSIST(rsa_exponent_max != NULL);
|
||||
|
||||
RUNTIME_CHECK(BN_set_bit(rsa_exponent_max, 0) == 1);
|
||||
RUNTIME_CHECK(BN_set_bit(rsa_exponent_max, 32) == 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
dst__opensslrsa_shutdown(void) {
|
||||
REQUIRE(rsa_exponent_min != NULL);
|
||||
REQUIRE(rsa_exponent_max != NULL);
|
||||
|
||||
BN_free(rsa_exponent_min);
|
||||
BN_free(rsa_exponent_max);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -251,7 +251,22 @@ isc_ossl_wrap_generate_pkcs11_rsa_key(char *uri, size_t bit_size,
|
|||
*/
|
||||
|
||||
bool
|
||||
isc_ossl_wrap_rsa_key_bits_leq(EVP_PKEY *pkey, size_t limit);
|
||||
isc_ossl_wrap_rsa_exponent_is_allowed(EVP_PKEY *pkey);
|
||||
/*%
|
||||
* Returns true if the RSA public exponent of `pkey` is odd and lies
|
||||
* within the closed range [3, 2^32 + 1]. This covers every Fermat
|
||||
* prime up to F5 plus all odd intermediate values seen in deployed
|
||||
* DNSSEC keys. Returns false if the exponent cannot be retrieved or
|
||||
* falls outside that range.
|
||||
*/
|
||||
|
||||
bool
|
||||
isc_ossl_wrap_rsa_modulus_bits_in_range(EVP_PKEY *pkey, size_t min, size_t max);
|
||||
/*%
|
||||
* Returns true if the RSA modulus bit length of `pkey` is between `min`
|
||||
* and `max` inclusive. Returns false if the modulus bit length cannot
|
||||
* be determined.
|
||||
*/
|
||||
|
||||
isc_result_t
|
||||
isc_ossl_wrap_rsa_public_components(EVP_PKEY *pkey,
|
||||
|
|
|
|||
|
|
@ -440,24 +440,48 @@ isc_ossl_wrap_generate_pkcs11_ed448_key(char *uri, EVP_PKEY **pkeyp) {
|
|||
}
|
||||
|
||||
bool
|
||||
isc_ossl_wrap_rsa_key_bits_leq(EVP_PKEY *pkey, size_t limit) {
|
||||
isc_ossl_wrap_rsa_exponent_is_allowed(EVP_PKEY *pkey) {
|
||||
const RSA *rsa;
|
||||
const BIGNUM *ce;
|
||||
BIGNUM *emin = NULL;
|
||||
BIGNUM *emax = NULL;
|
||||
bool ok = false;
|
||||
|
||||
REQUIRE(pkey != NULL);
|
||||
|
||||
rsa = EVP_PKEY_get0_RSA(pkey);
|
||||
if (rsa != NULL) {
|
||||
ce = NULL;
|
||||
RSA_get0_key(rsa, NULL, &ce, NULL);
|
||||
if (ce != NULL) {
|
||||
int bits = BN_num_bits(ce);
|
||||
|
||||
return bits > 0 && (size_t)bits <= limit;
|
||||
}
|
||||
if (rsa == NULL) {
|
||||
return false;
|
||||
}
|
||||
ce = NULL;
|
||||
RSA_get0_key(rsa, NULL, &ce, NULL);
|
||||
if (ce == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
emin = BN_new();
|
||||
if (emin == NULL || !BN_set_word(emin, 3)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (BN_hex2bn(&emax, "100000001") == 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ok = BN_is_odd(ce) && BN_cmp(ce, emin) >= 0 && BN_cmp(ce, emax) <= 0;
|
||||
|
||||
cleanup:
|
||||
BN_free(emin);
|
||||
BN_free(emax);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool
|
||||
isc_ossl_wrap_rsa_modulus_bits_in_range(EVP_PKEY *pkey, size_t min,
|
||||
size_t max) {
|
||||
REQUIRE(pkey != NULL);
|
||||
|
||||
int bits = EVP_PKEY_bits(pkey);
|
||||
return bits > 0 && (size_t)bits >= min && (size_t)bits <= max;
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
|
|
|
|||
|
|
@ -628,15 +628,40 @@ cleanup:
|
|||
}
|
||||
|
||||
bool
|
||||
isc_ossl_wrap_rsa_key_bits_leq(EVP_PKEY *pkey, size_t limit) {
|
||||
isc_ossl_wrap_rsa_exponent_is_allowed(EVP_PKEY *pkey) {
|
||||
BIGNUM *e = NULL;
|
||||
if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e) == 1) {
|
||||
int bits = BN_num_bits(e);
|
||||
BN_free(e);
|
||||
BIGNUM *emin = NULL;
|
||||
BIGNUM *emax = NULL;
|
||||
bool ok = false;
|
||||
|
||||
return bits > 0 && (size_t)bits <= limit;
|
||||
if (EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e) != 1) {
|
||||
goto cleanup;
|
||||
}
|
||||
return false;
|
||||
|
||||
emin = BN_new();
|
||||
if (emin == NULL || !BN_set_word(emin, 3)) {
|
||||
goto cleanup;
|
||||
}
|
||||
if (BN_hex2bn(&emax, "100000001") == 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
ok = BN_is_odd(e) && BN_cmp(e, emin) >= 0 && BN_cmp(e, emax) <= 0;
|
||||
|
||||
cleanup:
|
||||
BN_free(e);
|
||||
BN_free(emin);
|
||||
BN_free(emax);
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool
|
||||
isc_ossl_wrap_rsa_modulus_bits_in_range(EVP_PKEY *pkey, size_t min,
|
||||
size_t max) {
|
||||
REQUIRE(pkey != NULL);
|
||||
|
||||
int bits = EVP_PKEY_bits(pkey);
|
||||
return bits > 0 && (size_t)bits >= min && (size_t)bits <= max;
|
||||
}
|
||||
|
||||
isc_result_t
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ ISC_RUN_TEST_IMPL(isc_rsa_fromdns_oversized_exponent) {
|
|||
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. */
|
||||
* with a non-zero leading byte; outside the allowlist. */
|
||||
rdata[i++] = 6;
|
||||
rdata[i++] = 0x01;
|
||||
rdata[i++] = 0x02;
|
||||
|
|
@ -250,6 +250,92 @@ ISC_RUN_TEST_IMPL(isc_rsa_fromdns_oversized_exponent) {
|
|||
assert_null(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* dst_key_fromdns rejects RSA DNSKEYs whose public exponent is in the
|
||||
* accepted numeric range but even (and therefore not a valid RSA exponent).
|
||||
*/
|
||||
ISC_RUN_TEST_IMPL(isc_rsa_fromdns_even_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);
|
||||
assert_int_equal(result, ISC_R_SUCCESS);
|
||||
|
||||
/* DNSKEY rdata: flags(2) + proto(1) + alg(1) + RSA wire pubkey. */
|
||||
rdata[i++] = 0x01; /* flags hi (KSK) */
|
||||
rdata[i++] = 0x00; /* flags lo */
|
||||
rdata[i++] = 0x03; /* protocol */
|
||||
rdata[i++] = DST_ALG_RSASHA256;
|
||||
/* e_bytes=1, exponent=0x04 (even, mathematically invalid). */
|
||||
rdata[i++] = 1;
|
||||
rdata[i++] = 0x04;
|
||||
/* 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, isc_g_mctx,
|
||||
&key);
|
||||
assert_int_equal(result, ISC_R_RANGE);
|
||||
assert_null(key);
|
||||
}
|
||||
|
||||
/* dst_key_fromdns rejects RSA DNSKEYs whose modulus exceeds the cap */
|
||||
ISC_RUN_TEST_IMPL(isc_rsa_fromdns_oversized_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[1100] = { 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);
|
||||
assert_int_equal(result, ISC_R_SUCCESS);
|
||||
|
||||
/* DNSKEY rdata: flags(2) + proto(1) + alg(1) + RSA wire pubkey. */
|
||||
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. 1-byte exponent (0x03) and a
|
||||
* 1024-byte modulus (8192 bits, leading byte 0xAB so the high bit
|
||||
* is set) — twice the 4096-bit maximum.
|
||||
*/
|
||||
rdata[i++] = 1;
|
||||
rdata[i++] = 0x03;
|
||||
for (size_t j = 0; j < 1024; j++) {
|
||||
rdata[i++] = 0xAB;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/* dst_key_fromdns rejects RSA DNSKEYs with a degenerate modulus */
|
||||
ISC_RUN_TEST_IMPL(isc_rsa_fromdns_short_modulus) {
|
||||
isc_result_t result;
|
||||
|
|
@ -291,6 +377,8 @@ ISC_RUN_TEST_IMPL(isc_rsa_fromdns_short_modulus) {
|
|||
ISC_TEST_LIST_START
|
||||
ISC_TEST_ENTRY(isc_rsa_verify)
|
||||
ISC_TEST_ENTRY(isc_rsa_fromdns_oversized_exponent)
|
||||
ISC_TEST_ENTRY(isc_rsa_fromdns_even_exponent)
|
||||
ISC_TEST_ENTRY(isc_rsa_fromdns_oversized_modulus)
|
||||
ISC_TEST_ENTRY(isc_rsa_fromdns_short_modulus)
|
||||
ISC_TEST_LIST_END
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue