diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index 561e12a1a4..6b57beb525 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -237,6 +237,8 @@ dst__lib_initialize(void) { void dst__lib_shutdown(void) { + dst__opensslrsa_shutdown(); + isc_mem_detach(&dst__mctx); } diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index bc48c9fec4..550951e984 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -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); diff --git a/lib/dns/openssl_shim.h b/lib/dns/openssl_shim.h index 215cd363a8..3a50685f8f 100644 --- a/lib/dns/openssl_shim.h +++ b/lib/dns/openssl_shim.h @@ -21,13 +21,6 @@ #include #include -/* - * 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 diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c index b0d0d950ed..1b6dcb4034 100644 --- a/lib/dns/opensslrsa_link.c +++ b/lib/dns/opensslrsa_link.c @@ -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); } diff --git a/lib/isc/include/isc/ossl_wrap.h b/lib/isc/include/isc/ossl_wrap.h index cfb1cf2faf..fe5efe5ac1 100644 --- a/lib/isc/include/isc/ossl_wrap.h +++ b/lib/isc/include/isc/ossl_wrap.h @@ -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, diff --git a/lib/isc/ossl_wrap/ossl1_1.c b/lib/isc/ossl_wrap/ossl1_1.c index 6a5740a3b8..03ff2f6599 100644 --- a/lib/isc/ossl_wrap/ossl1_1.c +++ b/lib/isc/ossl_wrap/ossl1_1.c @@ -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 diff --git a/lib/isc/ossl_wrap/ossl3.c b/lib/isc/ossl_wrap/ossl3.c index 7021ff5f5d..f486b5c70d 100644 --- a/lib/isc/ossl_wrap/ossl3.c +++ b/lib/isc/ossl_wrap/ossl3.c @@ -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 diff --git a/tests/dns/rsa_test.c b/tests/dns/rsa_test.c index c00bda6c9f..954c10576b 100644 --- a/tests/dns/rsa_test.c +++ b/tests/dns/rsa_test.c @@ -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