Invalidated cached RSA, DH, and DSA state when changing keys

As part of getting a handle on RSA state, I would like for RSA keys
created from parsing to come pre-"frozen". This reduces some contention
on first use. But that potentially breaks an existing use case: today,
you're allowed to parse a private key and then override one field
without problems, because none of the cached state has materialized yet.

If the caller modified the RSA key by reaching into the struct, it's
hopeless. If they used the setters, we actually can handle it correctly,
so go ahead and make this work.

DH and DSA aren't of particular interest to us, but fix them while I'm
at it.

This also avoids having to later document that something doesn't work,
just that it's a terrible API.

Bug: 316
Change-Id: Idf02c777b932a62df9396e21de459381455950e0
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/59385
Auto-Submit: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
This commit is contained in:
David Benjamin 2023-04-26 17:38:31 -04:00 committed by Boringssl LUCI CQ
parent 2f6409e888
commit 9939e14cff
8 changed files with 273 additions and 14 deletions

View File

@ -366,3 +366,64 @@ TEST(DHTest, LeadingZeros) {
ASSERT_GT(len, 0);
EXPECT_EQ(Bytes(buf.data(), len), Bytes(padded));
}
TEST(DHTest, Overwrite) {
// Generate a DH key with the 1536-bit MODP group.
bssl::UniquePtr<BIGNUM> p(BN_get_rfc3526_prime_1536(nullptr));
ASSERT_TRUE(p);
bssl::UniquePtr<BIGNUM> g(BN_new());
ASSERT_TRUE(g);
ASSERT_TRUE(BN_set_word(g.get(), 2));
bssl::UniquePtr<DH> key1(DH_new());
ASSERT_TRUE(key1);
ASSERT_TRUE(DH_set0_pqg(key1.get(), p.get(), /*q=*/nullptr, g.get()));
p.release();
g.release();
ASSERT_TRUE(DH_generate_key(key1.get()));
bssl::UniquePtr<BIGNUM> peer_key(BN_new());
ASSERT_TRUE(peer_key);
ASSERT_TRUE(BN_set_word(peer_key.get(), 42));
// Use the key to fill in cached values.
std::vector<uint8_t> buf1(DH_size(key1.get()));
ASSERT_GT(DH_compute_key_padded(buf1.data(), peer_key.get(), key1.get()), 0);
// Generate a different key with a different group.
p.reset(BN_get_rfc3526_prime_2048(nullptr));
ASSERT_TRUE(p);
g.reset(BN_new());
ASSERT_TRUE(g);
ASSERT_TRUE(BN_set_word(g.get(), 2));
bssl::UniquePtr<DH> key2(DH_new());
ASSERT_TRUE(key2);
ASSERT_TRUE(DH_set0_pqg(key2.get(), p.get(), /*q=*/nullptr, g.get()));
p.release();
g.release();
ASSERT_TRUE(DH_generate_key(key2.get()));
// Overwrite |key1|'s contents with |key2|.
p.reset(BN_dup(DH_get0_p(key2.get())));
ASSERT_TRUE(p);
g.reset(BN_dup(DH_get0_g(key2.get())));
ASSERT_TRUE(g);
bssl::UniquePtr<BIGNUM> pub(BN_dup(DH_get0_pub_key(key2.get())));
ASSERT_TRUE(pub);
bssl::UniquePtr<BIGNUM> priv(BN_dup(DH_get0_priv_key(key2.get())));
ASSERT_TRUE(priv);
ASSERT_TRUE(DH_set0_pqg(key1.get(), p.get(), /*q=*/nullptr, g.get()));
p.release();
g.release();
ASSERT_TRUE(DH_set0_key(key1.get(), pub.get(), priv.get()));
pub.release();
priv.release();
// Verify that |key1| and |key2| behave equivalently.
buf1.resize(DH_size(key1.get()));
ASSERT_GT(DH_compute_key_padded(buf1.data(), peer_key.get(), key1.get()), 0);
std::vector<uint8_t> buf2(DH_size(key2.get()));
ASSERT_GT(DH_compute_key_padded(buf2.data(), peer_key.get(), key2.get()), 0);
EXPECT_EQ(Bytes(buf1), Bytes(buf2));
}

View File

@ -202,6 +202,10 @@ int DSA_set0_pqg(DSA *dsa, BIGNUM *p, BIGNUM *q, BIGNUM *g) {
dsa->g = g;
}
BN_MONT_CTX_free(dsa->method_mont_p);
dsa->method_mont_p = NULL;
BN_MONT_CTX_free(dsa->method_mont_q);
dsa->method_mont_q = NULL;
return 1;
}

View File

@ -336,3 +336,77 @@ Epvg
EXPECT_FALSE(DSA_sign(0, fips_digest, sizeof(fips_digest), sig.data(),
&sig_len, dsa.get()));
}
TEST(DSATest, Overwrite) {
// Load an arbitrary DSA private key and use it.
static const char kPEM[] = R"(
-----BEGIN DSA PRIVATE KEY-----
MIIDTgIBAAKCAQEAyH68EuravtF+7PTFBtWJkwjmp0YJmh8e2Cdpu8ci3dZf87rk
GwXzfqYkAEkW5H4Hp0cxdICKFiqfxjSaiEauOrNV+nXWZS634hZ9H47I8HnAVS0p
5MmSmPJ7NNUowymMpyB6M6hfqHl/1pZd7avbTmnzb2SZ0kw0WLWJo6vMekepYWv9
3o1Xove4ci00hnkr7Qo9Bh/+z84jgeT2/MTdsCVtbuMv/mbcYLhCKVWPBozDZr/D
qwhGTlomsTRvP3WIbem3b5eYhQaPuMsKiAzntcinoxQXWrIoZB+xJyF/sI013uBI
i9ePSxY3704U4QGxVM0aR/6fzORz5kh8ZjhhywIdAI9YBUR6eoGevUaLq++qXiYW
TgXBXlyqE32ESbkCggEBAL/c5GerO5g25D0QsfgVIJtlZHQOwYauuWoUudaQiyf6
VhWLBNNTAGldkFGdtxsA42uqqZSXCki25LvN6PscGGvFy8oPWaa9TGt+l9Z5ZZiV
ShNpg71V9YuImsPB3BrQ4L6nZLfhBt6InzJ6KqjDNdg7u6lgnFKue7l6khzqNxbM
RgxHWMq7PkhMcl+RzpqbiGcxSHqraxldutqCWsnZzhKh4d4GdunuRY8GiFo0Axkb
Kn0Il3zm81ewv08F/ocu+IZQEzxTyR8YRQ99MLVbnwhVxndEdLjjetCX82l+/uEY
5fdUy0thR8odcDsvUc/tT57I+yhnno80HbpUUNw2+/sCggEAdh1wp/9CifYIp6T8
P/rIus6KberZ2Pv/n0bl+Gv8AoToA0zhZXIfY2l0TtanKmdLqPIvjqkN0v6zGSs+
+ahR1QzMQnK718mcsQmB4X6iP5LKgJ/t0g8LrDOxc/cNycmHq76MmF9RN5NEBz4+
PAnRIftm/b0UQflP6uy3gRQP2X7P8ZebCytOPKTZC4oLyCtvPevSkCiiauq/RGjL
k6xqRgLxMtmuyhT+dcVbtllV1p1xd9Bppnk17/kR5VCefo/e/7DHu163izRDW8tx
SrEmiVyVkRijY3bVZii7LPfMz5eEAWEDJRuFwyNv3i6j7CKeZw2d/hzu370Ua28F
s2lmkAIcLIFUDFrbC2nViaB5ATM9ARKk6F2QwnCfGCyZ6A==
-----END DSA PRIVATE KEY-----
)";
bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(kPEM, sizeof(kPEM)));
ASSERT_TRUE(bio);
bssl::UniquePtr<DSA> dsa(
PEM_read_bio_DSAPrivateKey(bio.get(), nullptr, nullptr, nullptr));
ASSERT_TRUE(dsa);
std::vector<uint8_t> sig(DSA_size(dsa.get()));
unsigned sig_len;
ASSERT_TRUE(DSA_sign(0, fips_digest, sizeof(fips_digest), sig.data(),
&sig_len, dsa.get()));
sig.resize(sig_len);
EXPECT_EQ(1, DSA_verify(0, fips_digest, sizeof(fips_digest), sig.data(),
sig.size(), dsa.get()));
// Overwrite it with the sample key.
bssl::UniquePtr<BIGNUM> p(BN_bin2bn(fips_p, sizeof(fips_p), nullptr));
ASSERT_TRUE(p);
bssl::UniquePtr<BIGNUM> q(BN_bin2bn(fips_q, sizeof(fips_q), nullptr));
ASSERT_TRUE(q);
bssl::UniquePtr<BIGNUM> g(BN_bin2bn(fips_g, sizeof(fips_g), nullptr));
ASSERT_TRUE(g);
ASSERT_TRUE(DSA_set0_pqg(dsa.get(), p.get(), q.get(), g.get()));
// |DSA_set0_pqg| takes ownership on success.
p.release();
q.release();
g.release();
bssl::UniquePtr<BIGNUM> pub_key(BN_bin2bn(fips_y, sizeof(fips_y), nullptr));
ASSERT_TRUE(pub_key);
bssl::UniquePtr<BIGNUM> priv_key(BN_bin2bn(fips_x, sizeof(fips_x), nullptr));
ASSERT_TRUE(priv_key);
ASSERT_TRUE(DSA_set0_key(dsa.get(), pub_key.get(), priv_key.get()));
// |DSA_set0_key| takes ownership on success.
pub_key.release();
priv_key.release();
// The key should now work correctly for the new parameters.
EXPECT_EQ(1, DSA_verify(0, fips_digest, sizeof(fips_digest), fips_sig,
sizeof(fips_sig), dsa.get()));
// Test signing by verifying it round-trips through the real key.
sig.resize(DSA_size(dsa.get()));
ASSERT_TRUE(DSA_sign(0, fips_digest, sizeof(fips_digest), sig.data(),
&sig_len, dsa.get()));
sig.resize(sig_len);
dsa = GetFIPSDSA();
ASSERT_TRUE(dsa);
EXPECT_EQ(1, DSA_verify(0, fips_digest, sizeof(fips_digest), sig.data(),
sig.size(), dsa.get()));
}

View File

@ -177,6 +177,9 @@ int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) {
dh->g = g;
}
// Invalidate the cached Montgomery parameters.
BN_MONT_CTX_free(dh->method_mont_p);
dh->method_mont_p = NULL;
return 1;
}

View File

@ -60,6 +60,7 @@
#include <openssl/base.h>
#include <openssl/bn.h>
#include <openssl/rsa.h>
#if defined(__cplusplus)
@ -116,6 +117,12 @@ int rsa_private_transform_no_self_test(RSA *rsa, uint8_t *out,
int rsa_private_transform(RSA *rsa, uint8_t *out, const uint8_t *in,
size_t len);
// rsa_invalidate_key is called after |rsa| has been mutated, to invalidate
// fields derived from the original structure. This function assumes exclusive
// access to |rsa|. In particular, no other thread may be concurrently signing,
// etc., with |rsa|.
void rsa_invalidate_key(RSA *rsa);
// This constant is exported for test purposes.
extern const BN_ULONG kBoringSSLRSASqrtTwo[];

View File

@ -119,8 +119,6 @@ RSA *RSA_new_method(const ENGINE *engine) {
}
void RSA_free(RSA *rsa) {
unsigned u;
if (rsa == NULL) {
return;
}
@ -144,18 +142,7 @@ void RSA_free(RSA *rsa) {
BN_free(rsa->dmp1);
BN_free(rsa->dmq1);
BN_free(rsa->iqmp);
BN_MONT_CTX_free(rsa->mont_n);
BN_MONT_CTX_free(rsa->mont_p);
BN_MONT_CTX_free(rsa->mont_q);
BN_free(rsa->d_fixed);
BN_free(rsa->dmp1_fixed);
BN_free(rsa->dmq1_fixed);
BN_free(rsa->inv_small_mod_large_mont);
for (u = 0; u < rsa->num_blindings; u++) {
BN_BLINDING_free(rsa->blindings[u]);
}
OPENSSL_free(rsa->blindings);
OPENSSL_free(rsa->blindings_inuse);
rsa_invalidate_key(rsa);
CRYPTO_MUTEX_cleanup(&rsa->lock);
OPENSSL_free(rsa);
}
@ -244,6 +231,7 @@ int RSA_set0_key(RSA *rsa, BIGNUM *n, BIGNUM *e, BIGNUM *d) {
rsa->d = d;
}
rsa_invalidate_key(rsa);
return 1;
}
@ -262,6 +250,7 @@ int RSA_set0_factors(RSA *rsa, BIGNUM *p, BIGNUM *q) {
rsa->q = q;
}
rsa_invalidate_key(rsa);
return 1;
}
@ -285,6 +274,7 @@ int RSA_set0_crt_params(RSA *rsa, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) {
rsa->iqmp = iqmp;
}
rsa_invalidate_key(rsa);
return 1;
}

View File

@ -262,6 +262,36 @@ err:
return ret;
}
void rsa_invalidate_key(RSA *rsa) {
rsa->private_key_frozen = 0;
BN_MONT_CTX_free(rsa->mont_n);
rsa->mont_n = NULL;
BN_MONT_CTX_free(rsa->mont_p);
rsa->mont_p = NULL;
BN_MONT_CTX_free(rsa->mont_q);
rsa->mont_q = NULL;
BN_free(rsa->d_fixed);
rsa->d_fixed = NULL;
BN_free(rsa->dmp1_fixed);
rsa->dmp1_fixed = NULL;
BN_free(rsa->dmq1_fixed);
rsa->dmq1_fixed = NULL;
BN_free(rsa->inv_small_mod_large_mont);
rsa->inv_small_mod_large_mont = NULL;
for (size_t i = 0; i < rsa->num_blindings; i++) {
BN_BLINDING_free(rsa->blindings[i]);
}
OPENSSL_free(rsa->blindings);
rsa->blindings = NULL;
rsa->num_blindings = 0;
OPENSSL_free(rsa->blindings_inuse);
rsa->blindings_inuse = NULL;
rsa->blinding_fork_generation = 0;
}
size_t rsa_default_size(const RSA *rsa) {
return BN_num_bytes(rsa->n);
}
@ -1229,6 +1259,7 @@ static int RSA_generate_key_ex_maybe_fips(RSA *rsa, int bits,
goto out;
}
rsa_invalidate_key(rsa);
replace_bignum(&rsa->n, &tmp->n);
replace_bignum(&rsa->e, &tmp->e);
replace_bignum(&rsa->d, &tmp->d);

View File

@ -1022,6 +1022,95 @@ TEST(RSATest, KeygenInternalRetry) {
EXPECT_TRUE(RSA_generate_key_ex(rsa.get(), 2048, e.get(), &cb));
}
// Test that, after a key has been used, it can still be modified into another
// key.
TEST(RSATest, OverwriteKey) {
// Make a key and perform public and private key operations with it, so that
// all derived values are filled in.
bssl::UniquePtr<RSA> key1(
RSA_private_key_from_bytes(kKey1, sizeof(kKey1) - 1));
ASSERT_TRUE(key1);
ASSERT_TRUE(RSA_check_key(key1.get()));
size_t len;
std::vector<uint8_t> ciphertext(RSA_size(key1.get()));
ASSERT_TRUE(RSA_encrypt(key1.get(), &len, ciphertext.data(),
ciphertext.size(), kPlaintext, kPlaintextLen,
RSA_PKCS1_OAEP_PADDING));
ciphertext.resize(len);
std::vector<uint8_t> plaintext(RSA_size(key1.get()));
ASSERT_TRUE(RSA_decrypt(key1.get(), &len, plaintext.data(),
plaintext.size(), ciphertext.data(), ciphertext.size(),
RSA_PKCS1_OAEP_PADDING));
plaintext.resize(len);
EXPECT_EQ(Bytes(plaintext), Bytes(kPlaintext, kPlaintextLen));
// Overwrite |key1| with the contents of |key2|.
bssl::UniquePtr<RSA> key2(
RSA_private_key_from_bytes(kKey2, sizeof(kKey2) - 1));
ASSERT_TRUE(key2);
auto copy_rsa_fields = [](RSA *dst, const RSA *src) {
bssl::UniquePtr<BIGNUM> n(BN_dup(RSA_get0_n(src)));
ASSERT_TRUE(n);
bssl::UniquePtr<BIGNUM> e(BN_dup(RSA_get0_e(src)));
ASSERT_TRUE(e);
bssl::UniquePtr<BIGNUM> d(BN_dup(RSA_get0_d(src)));
ASSERT_TRUE(d);
bssl::UniquePtr<BIGNUM> p(BN_dup(RSA_get0_p(src)));
ASSERT_TRUE(p);
bssl::UniquePtr<BIGNUM> q(BN_dup(RSA_get0_q(src)));
ASSERT_TRUE(q);
bssl::UniquePtr<BIGNUM> dmp1(BN_dup(RSA_get0_dmp1(src)));
ASSERT_TRUE(dmp1);
bssl::UniquePtr<BIGNUM> dmq1(BN_dup(RSA_get0_dmq1(src)));
ASSERT_TRUE(dmq1);
bssl::UniquePtr<BIGNUM> iqmp(BN_dup(RSA_get0_iqmp(src)));
ASSERT_TRUE(iqmp);
ASSERT_TRUE(RSA_set0_key(dst, n.release(), e.release(), d.release()));
ASSERT_TRUE(RSA_set0_factors(dst, p.release(), q.release()));
ASSERT_TRUE(RSA_set0_crt_params(dst, dmp1.release(), dmq1.release(),
iqmp.release()));
};
ASSERT_NO_FATAL_FAILURE(copy_rsa_fields(key1.get(), key2.get()));
auto check_rsa_compatible = [&](RSA *enc, RSA *dec) {
ciphertext.resize(RSA_size(enc));
ASSERT_TRUE(RSA_encrypt(enc, &len, ciphertext.data(),
ciphertext.size(), kPlaintext, kPlaintextLen,
RSA_PKCS1_OAEP_PADDING));
ciphertext.resize(len);
plaintext.resize(RSA_size(dec));
ASSERT_TRUE(RSA_decrypt(dec, &len, plaintext.data(),
plaintext.size(), ciphertext.data(),
ciphertext.size(), RSA_PKCS1_OAEP_PADDING));
plaintext.resize(len);
EXPECT_EQ(Bytes(plaintext), Bytes(kPlaintext, kPlaintextLen));
};
ASSERT_NO_FATAL_FAILURE(
check_rsa_compatible(/*enc=*/key1.get(), /*dec=*/key2.get()));
ASSERT_NO_FATAL_FAILURE(
check_rsa_compatible(/*enc=*/key2.get(), /*dec=*/key1.get()));
// If we generate a new key on top of |key1|, it should be usable and
// self-consistent. We test this by making a new key with the same parameters
// and checking they behave the same.
ASSERT_TRUE(
RSA_generate_key_ex(key1.get(), 1024, RSA_get0_e(key2.get()), nullptr));
EXPECT_NE(0, BN_cmp(RSA_get0_n(key1.get()), RSA_get0_n(key2.get())));
key2.reset(RSA_new());
ASSERT_TRUE(key2);
ASSERT_NO_FATAL_FAILURE(copy_rsa_fields(key2.get(), key1.get()));
ASSERT_NO_FATAL_FAILURE(
check_rsa_compatible(/*enc=*/key1.get(), /*dec=*/key2.get()));
ASSERT_NO_FATAL_FAILURE(
check_rsa_compatible(/*enc=*/key2.get(), /*dec=*/key1.get()));
}
#if !defined(BORINGSSL_SHARED_LIBRARY)
TEST(RSATest, SqrtTwo) {
bssl::UniquePtr<BIGNUM> sqrt(BN_new()), pow2(BN_new());