Add APIs to support RSA keys with large e.
We cap e in RSA for DoS reasons. draft-amjad-cfrg-partially-blind-rsa
needs to create RSA keys with very large e. To support this, add an API
which disables this check.
Also add some missing checks for negative n and negative e. (Already
rejected by the parser, just not at this layer.)
Change-Id: Ia996bb1b46fc8b73db704f492b3df72b254a15a4
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59645
Auto-Submit: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/fipsmodule/rsa/rsa.c b/crypto/fipsmodule/rsa/rsa.c
index 5b687b7..77ab6c6 100644
--- a/crypto/fipsmodule/rsa/rsa.c
+++ b/crypto/fipsmodule/rsa/rsa.c
@@ -160,6 +160,49 @@
return rsa;
}
+RSA *RSA_new_public_key_large_e(const BIGNUM *n, const BIGNUM *e) {
+ RSA *rsa = RSA_new();
+ if (rsa == NULL) {
+ return NULL;
+ }
+
+ rsa->flags |= RSA_FLAG_LARGE_PUBLIC_EXPONENT;
+ if (!bn_dup_into(&rsa->n, n) || //
+ !bn_dup_into(&rsa->e, e) || //
+ !RSA_check_key(rsa)) {
+ RSA_free(rsa);
+ return NULL;
+ }
+
+ return rsa;
+}
+
+RSA *RSA_new_private_key_large_e(const BIGNUM *n, const BIGNUM *e,
+ const BIGNUM *d, const BIGNUM *p,
+ const BIGNUM *q, const BIGNUM *dmp1,
+ const BIGNUM *dmq1, const BIGNUM *iqmp) {
+ RSA *rsa = RSA_new();
+ if (rsa == NULL) {
+ return NULL;
+ }
+
+ rsa->flags |= RSA_FLAG_LARGE_PUBLIC_EXPONENT;
+ if (!bn_dup_into(&rsa->n, n) || //
+ !bn_dup_into(&rsa->e, e) || //
+ !bn_dup_into(&rsa->d, d) || //
+ !bn_dup_into(&rsa->p, p) || //
+ !bn_dup_into(&rsa->q, q) || //
+ !bn_dup_into(&rsa->dmp1, dmp1) || //
+ !bn_dup_into(&rsa->dmq1, dmq1) || //
+ !bn_dup_into(&rsa->iqmp, iqmp) || //
+ !RSA_check_key(rsa)) {
+ RSA_free(rsa);
+ return NULL;
+ }
+
+ return rsa;
+}
+
RSA *RSA_new(void) { return RSA_new_method(NULL); }
RSA *RSA_new_method(const ENGINE *engine) {
diff --git a/crypto/fipsmodule/rsa/rsa_impl.c b/crypto/fipsmodule/rsa/rsa_impl.c
index e9379d9..b907ae4 100644
--- a/crypto/fipsmodule/rsa/rsa_impl.c
+++ b/crypto/fipsmodule/rsa/rsa_impl.c
@@ -85,32 +85,44 @@
return 0;
}
- // RSA moduli must be odd. In addition to being necessary for RSA in general,
- // we cannot setup Montgomery reduction with even moduli.
- if (!BN_is_odd(rsa->n)) {
+ // RSA moduli must be positive and odd. In addition to being necessary for RSA
+ // in general, we cannot setup Montgomery reduction with even moduli.
+ if (!BN_is_odd(rsa->n) || BN_is_negative(rsa->n)) {
OPENSSL_PUT_ERROR(RSA, RSA_R_BAD_RSA_PARAMETERS);
return 0;
}
- // Mitigate DoS attacks by limiting the exponent size. 33 bits was chosen as
- // the limit based on the recommendations in [1] and [2]. Windows CryptoAPI
- // doesn't support values larger than 32 bits [3], so it is unlikely that
- // exponents larger than 32 bits are being used for anything Windows commonly
- // does.
- //
- // [1] https://www.imperialviolet.org/2012/03/16/rsae.html
- // [2] https://www.imperialviolet.org/2012/03/17/rsados.html
- // [3] https://msdn.microsoft.com/en-us/library/aa387685(VS.85).aspx
static const unsigned kMaxExponentBits = 33;
if (rsa->e != NULL) {
+ // Reject e = 1, negative e, and even e. e must be odd to be relatively
+ // prime with phi(n).
unsigned e_bits = BN_num_bits(rsa->e);
- if (e_bits > kMaxExponentBits ||
- // Additionally reject e = 1 or even e. e must be odd to be relatively
- // prime with phi(n).
- e_bits < 2 || !BN_is_odd(rsa->e)) {
+ if (e_bits < 2 || BN_is_negative(rsa->e) || !BN_is_odd(rsa->e)) {
OPENSSL_PUT_ERROR(RSA, RSA_R_BAD_E_VALUE);
return 0;
}
+ if (rsa->flags & RSA_FLAG_LARGE_PUBLIC_EXPONENT) {
+ // The caller has requested disabling DoS protections. Still, e must be
+ // less than n.
+ if (BN_ucmp(rsa->n, rsa->e) <= 0) {
+ OPENSSL_PUT_ERROR(RSA, RSA_R_BAD_E_VALUE);
+ return 0;
+ }
+ } else {
+ // Mitigate DoS attacks by limiting the exponent size. 33 bits was chosen
+ // as the limit based on the recommendations in [1] and [2]. Windows
+ // CryptoAPI doesn't support values larger than 32 bits [3], so it is
+ // unlikely that exponents larger than 32 bits are being used for anything
+ // Windows commonly does.
+ //
+ // [1] https://www.imperialviolet.org/2012/03/16/rsae.html
+ // [2] https://www.imperialviolet.org/2012/03/17/rsados.html
+ // [3] https://msdn.microsoft.com/en-us/library/aa387685(VS.85).aspx
+ if (e_bits > kMaxExponentBits) {
+ OPENSSL_PUT_ERROR(RSA, RSA_R_BAD_E_VALUE);
+ return 0;
+ }
+ }
} else if (!(rsa->flags & RSA_FLAG_NO_PUBLIC_EXPONENT)) {
OPENSSL_PUT_ERROR(RSA, RSA_R_VALUE_MISSING);
return 0;
diff --git a/crypto/rsa_extra/rsa_test.cc b/crypto/rsa_extra/rsa_test.cc
index 4a450e5..48f48d8 100644
--- a/crypto/rsa_extra/rsa_test.cc
+++ b/crypto/rsa_extra/rsa_test.cc
@@ -64,6 +64,7 @@
#include <openssl/bn.h>
#include <openssl/bytestring.h>
#include <openssl/crypto.h>
+#include <openssl/digest.h>
#include <openssl/err.h>
#include <openssl/nid.h>
@@ -1187,6 +1188,112 @@
kPlaintextLen, RSA_PKCS1_OAEP_PADDING));
}
+TEST(RSATest, Negative) {
+ auto dup_neg = [](const BIGNUM *bn) -> bssl::UniquePtr<BIGNUM> {
+ bssl::UniquePtr<BIGNUM> ret(BN_dup(bn));
+ if (!ret) {
+ return nullptr;
+ }
+ BN_set_negative(ret.get(), 1);
+ return ret;
+ };
+
+ bssl::UniquePtr<RSA> key(
+ RSA_private_key_from_bytes(kFIPSKey, sizeof(kFIPSKey) - 1));
+ ASSERT_TRUE(key);
+ const BIGNUM *n = RSA_get0_n(key.get());
+ bssl::UniquePtr<BIGNUM> neg_n = dup_neg(n);
+ ASSERT_TRUE(neg_n);
+ const BIGNUM *e = RSA_get0_e(key.get());
+ bssl::UniquePtr<BIGNUM> neg_e = dup_neg(e);
+ ASSERT_TRUE(neg_e);
+ const BIGNUM *d = RSA_get0_d(key.get());
+ bssl::UniquePtr<BIGNUM> neg_d = dup_neg(d);
+ ASSERT_TRUE(neg_d);
+ const BIGNUM *p = RSA_get0_p(key.get());
+ bssl::UniquePtr<BIGNUM> neg_p = dup_neg(p);
+ ASSERT_TRUE(neg_p);
+ const BIGNUM *q = RSA_get0_q(key.get());
+ bssl::UniquePtr<BIGNUM> neg_q = dup_neg(q);
+ ASSERT_TRUE(neg_q);
+ const BIGNUM *dmp1 = RSA_get0_dmp1(key.get());
+ bssl::UniquePtr<BIGNUM> neg_dmp1 = dup_neg(dmp1);
+ ASSERT_TRUE(neg_dmp1);
+ const BIGNUM *dmq1 = RSA_get0_dmq1(key.get());
+ bssl::UniquePtr<BIGNUM> neg_dmq1 = dup_neg(dmq1);
+ ASSERT_TRUE(neg_dmq1);
+ const BIGNUM *iqmp = RSA_get0_iqmp(key.get());
+ bssl::UniquePtr<BIGNUM> neg_iqmp = dup_neg(iqmp);
+ ASSERT_TRUE(neg_iqmp);
+
+ EXPECT_FALSE(RSA_new_public_key(neg_n.get(), e));
+ EXPECT_FALSE(RSA_new_public_key(n, neg_e.get()));
+ EXPECT_FALSE(RSA_new_private_key(neg_n.get(), e, d, p, q, dmp1, dmq1, iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, neg_e.get(), d, p, q, dmp1, dmq1, iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, e, neg_d.get(), p, q, dmp1, dmq1, iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, e, d, neg_p.get(), q, dmp1, dmq1, iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, e, d, p, neg_q.get(), dmp1, dmq1, iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, e, d, p, q, neg_dmp1.get(), dmq1, iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, e, d, p, q, dmp1, neg_dmq1.get(), iqmp));
+ EXPECT_FALSE(RSA_new_private_key(n, e, d, p, q, dmp1, dmq1, neg_iqmp.get()));
+}
+
+TEST(RSATest, LargeE) {
+ // Test an RSA key with large e by swapping d and e in kFIPSKey. Since e is
+ // small, e mod (p-1) and e mod (q-1) will simply be e.
+ bssl::UniquePtr<RSA> key(
+ RSA_private_key_from_bytes(kFIPSKey, sizeof(kFIPSKey) - 1));
+ ASSERT_TRUE(key);
+ const BIGNUM *n = RSA_get0_n(key.get());
+ const BIGNUM *e = RSA_get0_e(key.get());
+ const BIGNUM *d = RSA_get0_d(key.get());
+ const BIGNUM *p = RSA_get0_p(key.get());
+ const BIGNUM *q = RSA_get0_q(key.get());
+ const BIGNUM *iqmp = RSA_get0_iqmp(key.get());
+
+ // By default, the large exponent is not allowed as e.
+ bssl::UniquePtr<RSA> pub(RSA_new_public_key(n, /*e=*/d));
+ EXPECT_FALSE(pub);
+ bssl::UniquePtr<RSA> priv(RSA_new_private_key(n, /*e=*/d, /*d=*/e, p, q,
+ /*dmp1=*/e, /*dmq1=*/e, iqmp));
+ EXPECT_FALSE(priv);
+
+ // But the "large e" APIs tolerate it.
+ pub.reset(RSA_new_public_key_large_e(n, /*e=*/d));
+ ASSERT_TRUE(pub);
+ priv.reset(RSA_new_private_key_large_e(n, /*e=*/d, /*d=*/e, p, q, /*dmp1=*/e,
+ /*dmq1=*/e, iqmp));
+ ASSERT_TRUE(priv);
+
+ // Test that operations work correctly.
+ static const uint8_t kDigest[32] = {0};
+ std::vector<uint8_t> sig(RSA_size(priv.get()));
+ size_t len;
+ ASSERT_TRUE(RSA_sign_pss_mgf1(priv.get(), &len, sig.data(), sig.size(),
+ kDigest, sizeof(kDigest), EVP_sha256(),
+ EVP_sha256(), /*salt_len=*/32));
+ sig.resize(len);
+
+ EXPECT_TRUE(RSA_verify_pss_mgf1(pub.get(), kDigest, sizeof(kDigest),
+ EVP_sha256(), EVP_sha256(), /*salt_len=*/32,
+ sig.data(), sig.size()));
+
+ // e = 1 is still invalid.
+ EXPECT_FALSE(RSA_new_public_key_large_e(n, BN_value_one()));
+
+ // e must still be odd.
+ bssl::UniquePtr<BIGNUM> bad_e(BN_dup(d));
+ ASSERT_TRUE(bad_e);
+ ASSERT_TRUE(BN_add_word(bad_e.get(), 1));
+ EXPECT_FALSE(RSA_new_public_key_large_e(n, bad_e.get()));
+
+ // e must still be bounded by n.
+ bad_e.reset(BN_dup(n));
+ ASSERT_TRUE(bad_e);
+ ASSERT_TRUE(BN_add_word(bad_e.get(), 2)); // Preserve parity.
+ EXPECT_FALSE(RSA_new_public_key_large_e(n, bad_e.get()));
+}
+
#if !defined(BORINGSSL_SHARED_LIBRARY)
TEST(RSATest, SqrtTwo) {
bssl::UniquePtr<BIGNUM> sqrt(BN_new()), pow2(BN_new());
diff --git a/include/openssl/rsa.h b/include/openssl/rsa.h
index 49a6aa6..9b21d83 100644
--- a/include/openssl/rsa.h
+++ b/include/openssl/rsa.h
@@ -620,6 +620,26 @@
// attacks.
OPENSSL_EXPORT RSA *RSA_new_private_key_no_e(const BIGNUM *n, const BIGNUM *d);
+// RSA_new_public_key_large_e behaves like |RSA_new_public_key| but allows any
+// |e| up to |n|.
+//
+// BoringSSL typically bounds public exponents as a denial-of-service
+// mitigation. Keys created by this function may perform worse than those
+// created by |RSA_new_public_key|.
+OPENSSL_EXPORT RSA *RSA_new_public_key_large_e(const BIGNUM *n,
+ const BIGNUM *e);
+
+// RSA_new_private_key_large_e behaves like |RSA_new_private_key| but allows any
+// |e| up to |n|.
+//
+// BoringSSL typically bounds public exponents as a denial-of-service
+// mitigation. Keys created by this function may perform worse than those
+// created by |RSA_new_private_key|.
+OPENSSL_EXPORT RSA *RSA_new_private_key_large_e(
+ const BIGNUM *n, const BIGNUM *e, const BIGNUM *d, const BIGNUM *p,
+ const BIGNUM *q, const BIGNUM *dmp1, const BIGNUM *dmq1,
+ const BIGNUM *iqmp);
+
// ex_data functions.
//
@@ -656,6 +676,12 @@
// |RSA_new_private_key_no_e| to construct such keys.
#define RSA_FLAG_NO_PUBLIC_EXPONENT 0x40
+// RSA_FLAG_LARGE_PUBLIC_EXPONENT indicates that keys with a large public
+// exponent are allowed. This is an internal constant. Use
+// |RSA_new_public_key_large_e| and |RSA_new_private_key_large_e| to construct
+// such keys.
+#define RSA_FLAG_LARGE_PUBLIC_EXPONENT 0x80
+
// RSA public exponent values.