From c890ae519582d988d93d333dca9a39ee44f413ee Mon Sep 17 00:00:00 2001 From: David Benjamin Date: Sun, 6 Jun 2021 13:32:29 -0400 Subject: [PATCH] Make ECH server APIs take EVP_HPKE_KEY. Previously we would extract the KEM ID from the ECHConfig and then parse the private key using the corresponding KEM type. This CL makes it take a pre-pared EVP_HPKE_KEY and checks it matches. This does require the caller pass the key type through externally, which is probably prudent? (On the other hand we are still inferring config from the rest of the ECHConfig... maybe we can add an API to extract the EVP_HPKE_KEM from a serialized ECHConfig if it becomes a problem. I could see runner or tool wanting that out of convenience.) The immediate motivation is to add APIs to programmatically construct ECHConfigs. I'm thinking we can pass a const EVP_HPKE_KEY * to specify the key, at which point it's weird for SSL_ECH_KEYS_add to look different. Bug: 275 Change-Id: I2d424323885103d3fe0a99a9012c160baa8653bd Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48002 Commit-Queue: David Benjamin Reviewed-by: Adam Langley --- crypto/hpke/hpke.c | 50 +++++++++-- crypto/hpke/hpke_test.cc | 156 ++++++++++++++++++---------------- fuzz/ssl_ctx_api.cc | 14 ++- include/openssl/hpke.h | 30 ++++++- include/openssl/ssl.h | 14 +-- ssl/encrypted_client_hello.cc | 31 +++---- ssl/internal.h | 8 +- ssl/ssl_test.cc | 81 +++++++++--------- ssl/test/fuzzer.h | 16 ++-- ssl/test/test_config.cc | 12 ++- tool/server.cc | 44 +++++----- 11 files changed, 264 insertions(+), 192 deletions(-) diff --git a/crypto/hpke/hpke.c b/crypto/hpke/hpke.c index 6840e8340..fe50bc2fa 100644 --- a/crypto/hpke/hpke.c +++ b/crypto/hpke/hpke.c @@ -38,9 +38,11 @@ struct evp_hpke_kem_st { uint16_t id; size_t public_key_len; + size_t private_key_len; size_t seed_len; - int (*init_key)(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem, - const uint8_t *priv_key, size_t priv_key_len); + int (*init_key)(EVP_HPKE_KEY *key, const uint8_t *priv_key, + size_t priv_key_len); + int (*generate_key)(EVP_HPKE_KEY *key); int (*encap_with_seed)(const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret, size_t *out_shared_secret_len, uint8_t *out_enc, size_t *out_enc_len, size_t max_enc, @@ -130,8 +132,8 @@ static int dhkem_extract_and_expand(uint16_t kem_id, const EVP_MD *hkdf_md, kem_context_len); } -static int x25519_init_key(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem, - const uint8_t *priv_key, size_t priv_key_len) { +static int x25519_init_key(EVP_HPKE_KEY *key, const uint8_t *priv_key, + size_t priv_key_len) { if (priv_key_len != X25519_PRIVATE_KEY_LEN) { OPENSSL_PUT_ERROR(EVP, EVP_R_DECODE_ERROR); return 0; @@ -142,6 +144,11 @@ static int x25519_init_key(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem, return 1; } +static int x25519_generate_key(EVP_HPKE_KEY *key) { + X25519_keypair(key->public_key, key->private_key); + return 1; +} + static int x25519_encap_with_seed( const EVP_HPKE_KEM *kem, uint8_t *out_shared_secret, size_t *out_shared_secret_len, uint8_t *out_enc, size_t *out_enc_len, @@ -207,8 +214,10 @@ const EVP_HPKE_KEM *EVP_hpke_x25519_hkdf_sha256(void) { static const EVP_HPKE_KEM kKEM = { /*id=*/EVP_HPKE_DHKEM_X25519_HKDF_SHA256, /*public_key_len=*/X25519_PUBLIC_VALUE_LEN, + /*private_key_len=*/X25519_PRIVATE_KEY_LEN, /*seed_len=*/X25519_PRIVATE_KEY_LEN, x25519_init_key, + x25519_generate_key, x25519_encap_with_seed, x25519_decap, }; @@ -226,17 +235,37 @@ void EVP_HPKE_KEY_cleanup(EVP_HPKE_KEY *key) { // future. } +int EVP_HPKE_KEY_copy(EVP_HPKE_KEY *dst, const EVP_HPKE_KEY *src) { + // For now, |EVP_HPKE_KEY| is trivially copyable. + OPENSSL_memcpy(dst, src, sizeof(EVP_HPKE_KEY)); + return 1; +} + int EVP_HPKE_KEY_init(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem, const uint8_t *priv_key, size_t priv_key_len) { EVP_HPKE_KEY_zero(key); key->kem = kem; - if (!kem->init_key(key, kem, priv_key, priv_key_len)) { + if (!kem->init_key(key, priv_key, priv_key_len)) { key->kem = NULL; return 0; } return 1; } +int EVP_HPKE_KEY_generate(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem) { + EVP_HPKE_KEY_zero(key); + key->kem = kem; + if (!kem->generate_key(key)) { + key->kem = NULL; + return 0; + } + return 1; +} + +const EVP_HPKE_KEM *EVP_HPKE_KEY_kem(const EVP_HPKE_KEY *key) { + return key->kem; +} + int EVP_HPKE_KEY_public_key(const EVP_HPKE_KEY *key, uint8_t *out, size_t *out_len, size_t max_out) { if (max_out < key->kem->public_key_len) { @@ -248,6 +277,17 @@ int EVP_HPKE_KEY_public_key(const EVP_HPKE_KEY *key, uint8_t *out, return 1; } +int EVP_HPKE_KEY_private_key(const EVP_HPKE_KEY *key, uint8_t *out, + size_t *out_len, size_t max_out) { + if (max_out < key->kem->private_key_len) { + OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE); + return 0; + } + OPENSSL_memcpy(out, key->private_key, key->kem->private_key_len); + *out_len = key->kem->private_key_len; + return 1; +} + // Supported KDFs and AEADs. diff --git a/crypto/hpke/hpke_test.cc b/crypto/hpke/hpke_test.cc index 1b4ccdd21..a7bfe752c 100644 --- a/crypto/hpke/hpke_test.cc +++ b/crypto/hpke/hpke_test.cc @@ -63,7 +63,7 @@ class HPKETestVector { const EVP_HPKE_KDF *kdf = GetKDF(); ASSERT_TRUE(kdf); - // Set up the sender. + // Test the sender. ScopedEVP_HPKE_CTX sender_ctx; uint8_t enc[EVP_HPKE_MAX_ENC_LENGTH]; size_t enc_len; @@ -72,26 +72,41 @@ class HPKETestVector { public_key_r_.data(), public_key_r_.size(), info_.data(), info_.size(), secret_key_e_.data(), secret_key_e_.size())); EXPECT_EQ(Bytes(enc, enc_len), Bytes(public_key_e_)); + VerifySender(sender_ctx.get()); - // Import the receiver key. - ScopedEVP_HPKE_KEY key; - ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), kem, secret_key_r_.data(), + // Test the recipient. + ScopedEVP_HPKE_KEY base_key; + ASSERT_TRUE(EVP_HPKE_KEY_init(base_key.get(), kem, secret_key_r_.data(), secret_key_r_.size())); - uint8_t public_key[EVP_HPKE_MAX_PUBLIC_KEY_LENGTH]; - size_t public_key_len; - ASSERT_TRUE(EVP_HPKE_KEY_public_key(key.get(), public_key, &public_key_len, - sizeof(public_key))); - EXPECT_EQ(Bytes(public_key, public_key_len), Bytes(public_key_r_)); - - // Set up the receiver. - ScopedEVP_HPKE_CTX receiver_ctx; - ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient(receiver_ctx.get(), key.get(), kdf, - aead, enc, enc_len, info_.data(), - info_.size())); - - VerifyEncryptions(sender_ctx.get(), receiver_ctx.get()); - VerifyExports(sender_ctx.get()); - VerifyExports(receiver_ctx.get()); + for (bool copy : {false, true}) { + SCOPED_TRACE(copy); + const EVP_HPKE_KEY *key = base_key.get(); + ScopedEVP_HPKE_KEY key_copy; + if (copy) { + ASSERT_TRUE(EVP_HPKE_KEY_copy(key_copy.get(), base_key.get())); + key = key_copy.get(); + } + + uint8_t public_key[EVP_HPKE_MAX_PUBLIC_KEY_LENGTH]; + size_t public_key_len; + ASSERT_TRUE(EVP_HPKE_KEY_public_key(key, public_key, &public_key_len, + sizeof(public_key))); + EXPECT_EQ(Bytes(public_key, public_key_len), Bytes(public_key_r_)); + + uint8_t private_key[EVP_HPKE_MAX_PRIVATE_KEY_LENGTH]; + size_t private_key_len; + ASSERT_TRUE(EVP_HPKE_KEY_private_key(key, private_key, &private_key_len, + sizeof(private_key))); + EXPECT_EQ(Bytes(private_key, private_key_len), Bytes(secret_key_r_)); + + // Set up the recipient. + ScopedEVP_HPKE_CTX recipient_ctx; + ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient(recipient_ctx.get(), key, kdf, + aead, enc, enc_len, info_.data(), + info_.size())); + + VerifyRecipient(recipient_ctx.get()); + } } private: @@ -113,28 +128,33 @@ class HPKETestVector { return nullptr; } - void VerifyEncryptions(EVP_HPKE_CTX *sender_ctx, - EVP_HPKE_CTX *receiver_ctx) const { + void VerifySender(EVP_HPKE_CTX *ctx) const { for (const Encryption &task : encryptions_) { std::vector encrypted(task.plaintext.size() + - EVP_HPKE_CTX_max_overhead(sender_ctx)); + EVP_HPKE_CTX_max_overhead(ctx)); size_t encrypted_len; - ASSERT_TRUE(EVP_HPKE_CTX_seal( - sender_ctx, encrypted.data(), &encrypted_len, encrypted.size(), - task.plaintext.data(), task.plaintext.size(), task.aad.data(), - task.aad.size())); + ASSERT_TRUE(EVP_HPKE_CTX_seal(ctx, encrypted.data(), &encrypted_len, + encrypted.size(), task.plaintext.data(), + task.plaintext.size(), task.aad.data(), + task.aad.size())); ASSERT_EQ(Bytes(encrypted.data(), encrypted_len), Bytes(task.ciphertext)); + } + VerifyExports(ctx); + } + void VerifyRecipient(EVP_HPKE_CTX *ctx) const { + for (const Encryption &task : encryptions_) { std::vector decrypted(task.ciphertext.size()); size_t decrypted_len; - ASSERT_TRUE(EVP_HPKE_CTX_open( - receiver_ctx, decrypted.data(), &decrypted_len, decrypted.size(), - task.ciphertext.data(), task.ciphertext.size(), task.aad.data(), - task.aad.size())); + ASSERT_TRUE(EVP_HPKE_CTX_open(ctx, decrypted.data(), &decrypted_len, + decrypted.size(), task.ciphertext.data(), + task.ciphertext.size(), task.aad.data(), + task.aad.size())); ASSERT_EQ(Bytes(decrypted.data(), decrypted_len), Bytes(task.plaintext)); } + VerifyExports(ctx); } void VerifyExports(EVP_HPKE_CTX *ctx) const { @@ -262,16 +282,13 @@ TEST(HPKETest, RoundTrip) { Span info_values[] = {{nullptr, 0}, info_a, info_b}; Span ad_values[] = {{nullptr, 0}, ad_a, ad_b}; - // Generate the receiver's keypair. - uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN]; - RAND_bytes(secret_key_r, sizeof(secret_key_r)); + // Generate the recipient's keypair. ScopedEVP_HPKE_KEY key; - ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), - secret_key_r, sizeof(secret_key_r))); + ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256())); uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN]; size_t public_key_r_len; - ASSERT_TRUE(EVP_HPKE_KEY_public_key(key.get(), public_key_r, &public_key_r_len, - sizeof(public_key_r))); + ASSERT_TRUE(EVP_HPKE_KEY_public_key(key.get(), public_key_r, + &public_key_r_len, sizeof(public_key_r))); for (const auto kdf : kAllKDFs) { SCOPED_TRACE(EVP_HPKE_KDF_id(kdf())); @@ -290,15 +307,15 @@ TEST(HPKETest, RoundTrip) { EVP_hpke_x25519_hkdf_sha256(), kdf(), aead(), public_key_r, public_key_r_len, info.data(), info.size())); - // Set up the receiver. - ScopedEVP_HPKE_CTX receiver_ctx; - ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient(receiver_ctx.get(), key.get(), - kdf(), aead(), enc, enc_len, - info.data(), info.size())); + // Set up the recipient. + ScopedEVP_HPKE_CTX recipient_ctx; + ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient( + recipient_ctx.get(), key.get(), kdf(), aead(), enc, enc_len, + info.data(), info.size())); const char kCleartextPayload[] = "foobar"; - // Have sender encrypt message for the receiver. + // Have sender encrypt message for the recipient. std::vector ciphertext( sizeof(kCleartextPayload) + EVP_HPKE_CTX_max_overhead(sender_ctx.get())); @@ -309,10 +326,10 @@ TEST(HPKETest, RoundTrip) { reinterpret_cast(kCleartextPayload), sizeof(kCleartextPayload), ad.data(), ad.size())); - // Have receiver decrypt the message. + // Have recipient decrypt the message. std::vector cleartext(ciphertext.size()); size_t cleartext_len; - ASSERT_TRUE(EVP_HPKE_CTX_open(receiver_ctx.get(), cleartext.data(), + ASSERT_TRUE(EVP_HPKE_CTX_open(recipient_ctx.get(), cleartext.data(), &cleartext_len, cleartext.size(), ciphertext.data(), ciphertext_len, ad.data(), ad.size())); @@ -336,12 +353,8 @@ TEST(HPKETest, X25519EncapSmallOrderPoint) { 0xb1, 0xfd, 0x86, 0x62, 0x05, 0x16, 0x5f, 0x49, 0xb8, }; - // Generate a valid keypair for the receiver. - uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN]; - RAND_bytes(secret_key_r, sizeof(secret_key_r)); ScopedEVP_HPKE_KEY key; - ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), - secret_key_r, sizeof(secret_key_r))); + ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256())); for (const auto kdf : kAllKDFs) { SCOPED_TRACE(EVP_HPKE_KDF_id(kdf())); @@ -356,38 +369,34 @@ TEST(HPKETest, X25519EncapSmallOrderPoint) { EVP_hpke_x25519_hkdf_sha256(), kdf(), aead(), kSmallOrderPoint, sizeof(kSmallOrderPoint), nullptr, 0)); - // Set up the receiver, passing in kSmallOrderPoint as |enc|. - ScopedEVP_HPKE_CTX receiver_ctx; + // Set up the recipient, passing in kSmallOrderPoint as |enc|. + ScopedEVP_HPKE_CTX recipient_ctx; ASSERT_FALSE(EVP_HPKE_CTX_setup_recipient( - receiver_ctx.get(), key.get(), kdf(), aead(), kSmallOrderPoint, + recipient_ctx.get(), key.get(), kdf(), aead(), kSmallOrderPoint, sizeof(kSmallOrderPoint), nullptr, 0)); } } } -// Test that Seal() fails when the context has been initialized as a receiver. -TEST(HPKETest, ReceiverInvalidSeal) { +// Test that Seal() fails when the context has been initialized as a recipient. +TEST(HPKETest, RecipientInvalidSeal) { const uint8_t kMockEnc[X25519_PUBLIC_VALUE_LEN] = {0xff}; const char kCleartextPayload[] = "foobar"; - // Generate the receiver's keypair. - uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN]; - RAND_bytes(secret_key_r, sizeof(secret_key_r)); ScopedEVP_HPKE_KEY key; - ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), - secret_key_r, sizeof(secret_key_r))); + ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256())); - // Set up the receiver. - ScopedEVP_HPKE_CTX receiver_ctx; + // Set up the recipient. + ScopedEVP_HPKE_CTX recipient_ctx; ASSERT_TRUE(EVP_HPKE_CTX_setup_recipient( - receiver_ctx.get(), key.get(), EVP_hpke_hkdf_sha256(), EVP_hpke_aes_128_gcm(), - kMockEnc, sizeof(kMockEnc), nullptr, 0)); + recipient_ctx.get(), key.get(), EVP_hpke_hkdf_sha256(), + EVP_hpke_aes_128_gcm(), kMockEnc, sizeof(kMockEnc), nullptr, 0)); - // Call Seal() on the receiver. + // Call Seal() on the recipient. size_t ciphertext_len; uint8_t ciphertext[100]; ASSERT_FALSE(EVP_HPKE_CTX_seal( - receiver_ctx.get(), ciphertext, &ciphertext_len, sizeof(ciphertext), + recipient_ctx.get(), ciphertext, &ciphertext_len, sizeof(ciphertext), reinterpret_cast(kCleartextPayload), sizeof(kCleartextPayload), nullptr, 0)); } @@ -397,7 +406,7 @@ TEST(HPKETest, SenderInvalidOpen) { const uint8_t kMockCiphertext[100] = {0xff}; const size_t kMockCiphertextLen = 80; - // Generate the receiver's keypair. + // Generate the recipient's keypair. uint8_t secret_key_r[X25519_PRIVATE_KEY_LEN]; uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN]; X25519_keypair(public_key_r, secret_key_r); @@ -454,18 +463,15 @@ TEST(HPKETest, SetupSenderBufferTooLarge) { EXPECT_EQ(size_t{X25519_PUBLIC_VALUE_LEN}, enc_len); } -TEST(HPKETest, SetupReceiverWrongLengthEnc) { - uint8_t private_key[X25519_PRIVATE_KEY_LEN]; - RAND_bytes(private_key, sizeof(private_key)); +TEST(HPKETest, SetupRecipientWrongLengthEnc) { ScopedEVP_HPKE_KEY key; - ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), - private_key, sizeof(private_key))); + ASSERT_TRUE(EVP_HPKE_KEY_generate(key.get(), EVP_hpke_x25519_hkdf_sha256())); const uint8_t bogus_enc[X25519_PUBLIC_VALUE_LEN + 5] = {0xff}; - ScopedEVP_HPKE_CTX receiver_ctx; + ScopedEVP_HPKE_CTX recipient_ctx; ASSERT_FALSE(EVP_HPKE_CTX_setup_recipient( - receiver_ctx.get(), key.get(), EVP_hpke_hkdf_sha256(), + recipient_ctx.get(), key.get(), EVP_hpke_hkdf_sha256(), EVP_hpke_aes_128_gcm(), bogus_enc, sizeof(bogus_enc), nullptr, 0)); uint32_t err = ERR_get_error(); EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err)); @@ -489,7 +495,7 @@ TEST(HPKETest, SetupSenderWrongLengthPeerPublicValue) { ERR_clear_error(); } -TEST(HPKETest, InvalidReceiverKey) { +TEST(HPKETest, InvalidRecipientKey) { const uint8_t private_key[X25519_PUBLIC_VALUE_LEN + 5] = {0xff}; ScopedEVP_HPKE_KEY key; EXPECT_FALSE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), diff --git a/fuzz/ssl_ctx_api.cc b/fuzz/ssl_ctx_api.cc index 3739e872b..da0a2d3fd 100644 --- a/fuzz/ssl_ctx_api.cc +++ b/fuzz/ssl_ctx_api.cc @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -503,10 +504,15 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) { !CBS_get_u16_length_prefixed(cbs, &private_key)) { return; } - SSL_ECH_KEYS_add(keys.get(), is_retry_config, CBS_data(&ech_config), - CBS_len(&ech_config), CBS_data(&private_key), - CBS_len(&private_key)); - SSL_CTX_set1_ech_keys(ctx, keys.get()); + bssl::ScopedEVP_HPKE_KEY key; + if (!EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), + CBS_data(&private_key), CBS_len(&private_key)) || + !SSL_ECH_KEYS_add(keys.get(), is_retry_config, + CBS_data(&ech_config), CBS_len(&ech_config), + key.get()) || + !SSL_CTX_set1_ech_keys(ctx, keys.get())) { + return; + } }, }; diff --git a/include/openssl/hpke.h b/include/openssl/hpke.h index 358ca2306..2f14f777f 100644 --- a/include/openssl/hpke.h +++ b/include/openssl/hpke.h @@ -80,7 +80,8 @@ OPENSSL_EXPORT uint16_t EVP_HPKE_AEAD_id(const EVP_HPKE_AEAD *aead); // with the |EVP_HPKE_KEY| type. // EVP_HPKE_KEY_zero sets an uninitialized |EVP_HPKE_KEY| to the zero state. The -// caller should then use |EVP_HPKE_KEY_init| to finish initializing |key|. +// caller should then use |EVP_HPKE_KEY_init|, |EVP_HPKE_KEY_copy|, or +// |EVP_HPKE_KEY_generate| to finish initializing |key|. // // It is safe, but not necessary to call |EVP_HPKE_KEY_cleanup| in this state. // This may be used for more uniform cleanup of |EVP_HPKE_KEY|. @@ -89,6 +90,13 @@ OPENSSL_EXPORT void EVP_HPKE_KEY_zero(EVP_HPKE_KEY *key); // EVP_HPKE_KEY_cleanup releases memory referenced by |key|. OPENSSL_EXPORT void EVP_HPKE_KEY_cleanup(EVP_HPKE_KEY *key); +// EVP_HPKE_KEY_copy sets |dst| to a copy of |src|. It returns one on success +// and zero on error. On success, the caller must call |EVP_HPKE_KEY_cleanup| to +// release |dst|. On failure, calling |EVP_HPKE_KEY_cleanup| is safe, but not +// necessary. +OPENSSL_EXPORT int EVP_HPKE_KEY_copy(EVP_HPKE_KEY *dst, + const EVP_HPKE_KEY *src); + // EVP_HPKE_KEY_init decodes |priv_key| as a private key for |kem| and // initializes |key| with the result. It returns one on success and zero if // |priv_key| was invalid. On success, the caller must call @@ -98,6 +106,13 @@ OPENSSL_EXPORT int EVP_HPKE_KEY_init(EVP_HPKE_KEY *key, const EVP_HPKE_KEM *kem, const uint8_t *priv_key, size_t priv_key_len); +// EVP_HPKE_KEY_generate sets |key| to a newly-generated key using |kem|. +OPENSSL_EXPORT int EVP_HPKE_KEY_generate(EVP_HPKE_KEY *key, + const EVP_HPKE_KEM *kem); + +// EVP_HPKE_KEY_kem returns the HPKE KEM used by |key|. +OPENSSL_EXPORT const EVP_HPKE_KEM *EVP_HPKE_KEY_kem(const EVP_HPKE_KEY *key); + // EVP_HPKE_MAX_PUBLIC_KEY_LENGTH is the maximum length of a public key for all // KEMs supported by this library. #define EVP_HPKE_MAX_PUBLIC_KEY_LENGTH 32 @@ -111,6 +126,19 @@ OPENSSL_EXPORT int EVP_HPKE_KEY_public_key(const EVP_HPKE_KEY *key, uint8_t *out, size_t *out_len, size_t max_out); +// EVP_HPKE_MAX_PRIVATE_KEY_LENGTH is the maximum length of a private key for +// all KEMs supported by this library. +#define EVP_HPKE_MAX_PRIVATE_KEY_LENGTH 32 + +// EVP_HPKE_KEY_private_key writes |key|'s private key to |out| and sets +// |*out_len| to the number of bytes written. On success, it returns one and +// writes at most |max_out| bytes. If |max_out| is too small, it returns zero. +// Setting |max_out| to |EVP_HPKE_MAX_PRIVATE_KEY_LENGTH| will ensure the +// private key fits. +OPENSSL_EXPORT int EVP_HPKE_KEY_private_key(const EVP_HPKE_KEY *key, + uint8_t *out, size_t *out_len, + size_t max_out); + // Encryption contexts. // diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h index 191cf4b2e..5b07ebffd 100644 --- a/include/openssl/ssl.h +++ b/include/openssl/ssl.h @@ -3574,21 +3574,21 @@ OPENSSL_EXPORT void SSL_ECH_KEYS_up_ref(SSL_ECH_KEYS *keys); // SSL_ECH_KEYS_free releases memory associated with |keys|. OPENSSL_EXPORT void SSL_ECH_KEYS_free(SSL_ECH_KEYS *keys); -// SSL_ECH_KEYS_add appends an ECHConfig in |ech_config| and its -// corresponding private key in |private_key| to |keys|. When |is_retry_config| -// is non-zero, this config will be returned to the client on configuration -// mismatch. It returns one on success and zero on error. See also -// |SSL_CTX_set1_ech_keys|. +// SSL_ECH_KEYS_add decodes |ech_config| as an ECHConfig and appends it with +// |key| to |keys|. If |is_retry_config| is non-zero, this config will be +// returned to the client on configuration mismatch. It returns one on success +// and zero on error. // // This function should be called successively to register each ECHConfig in // decreasing order of preference. This configuration must be completed before // setting |keys| on an |SSL_CTX| with |SSL_CTX_set1_ech_keys|. After that // point, |keys| is immutable; no more ECHConfig values may be added. +// +// See also |SSL_CTX_set1_ech_keys|. OPENSSL_EXPORT int SSL_ECH_KEYS_add(SSL_ECH_KEYS *keys, int is_retry_config, const uint8_t *ech_config, size_t ech_config_len, - const uint8_t *private_key, - size_t private_key_len); + const EVP_HPKE_KEY *key); // SSL_CTX_set1_ech_keys configures |ctx| to use |keys| to decrypt encrypted // ClientHellos. It returns one on success, and zero on failure. If |keys| does diff --git a/ssl/encrypted_client_hello.cc b/ssl/encrypted_client_hello.cc index 46590bd7d..d1adc684b 100644 --- a/ssl/encrypted_client_hello.cc +++ b/ssl/encrypted_client_hello.cc @@ -352,8 +352,7 @@ bool ssl_client_hello_decrypt( bool ECHServerConfig::Init(Span ech_config, - Span private_key, - bool is_retry_config) { + const EVP_HPKE_KEY *key, bool is_retry_config) { assert(!initialized_); is_retry_config_ = is_retry_config; @@ -420,30 +419,25 @@ bool ECHServerConfig::Init(Span ech_config, } } - // We only support one KEM. - if (kem_id != EVP_HPKE_DHKEM_X25519_HKDF_SHA256) { - OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG); - return false; - } - if (!EVP_HPKE_KEY_init(key_.get(), EVP_hpke_x25519_hkdf_sha256(), - private_key.data(), private_key.size())) { - OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); - return false; - } - // Check the public key in the ECHConfig matches the private key. + // Check the public key in the ECHConfig matches |key|. uint8_t expected_public_key[EVP_HPKE_MAX_PUBLIC_KEY_LENGTH]; size_t expected_public_key_len; - if (!EVP_HPKE_KEY_public_key(key_.get(), expected_public_key, + if (!EVP_HPKE_KEY_public_key(key, expected_public_key, &expected_public_key_len, sizeof(expected_public_key))) { return false; } - if (MakeConstSpan(expected_public_key, expected_public_key_len) != - public_key) { + if (kem_id != EVP_HPKE_KEM_id(EVP_HPKE_KEY_kem(key)) || + MakeConstSpan(expected_public_key, expected_public_key_len) != + public_key) { OPENSSL_PUT_ERROR(SSL, SSL_R_ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH); return false; } + if (!EVP_HPKE_KEY_copy(key_.get(), key)) { + return false; + } + initialized_ = true; return true; } @@ -517,13 +511,12 @@ void SSL_ECH_KEYS_free(SSL_ECH_KEYS *keys) { int SSL_ECH_KEYS_add(SSL_ECH_KEYS *configs, int is_retry_config, const uint8_t *ech_config, size_t ech_config_len, - const uint8_t *private_key, size_t private_key_len) { + const EVP_HPKE_KEY *key) { UniquePtr parsed_config = MakeUnique(); if (!parsed_config) { return 0; } - if (!parsed_config->Init(MakeConstSpan(ech_config, ech_config_len), - MakeConstSpan(private_key, private_key_len), + if (!parsed_config->Init(MakeConstSpan(ech_config, ech_config_len), key, !!is_retry_config)) { OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR); return 0; diff --git a/ssl/internal.h b/ssl/internal.h index 8aceac703..62a9a067d 100644 --- a/ssl/internal.h +++ b/ssl/internal.h @@ -1438,11 +1438,9 @@ class ECHServerConfig { ~ECHServerConfig() = default; ECHServerConfig &operator=(ECHServerConfig &&) = delete; - // Init parses |ech_config| as an ECHConfig and saves a copy of |private_key|. - // It returns true on success and false on error. It will also error if - // |private_key| is not a valid X25519 private key or it does not correspond - // to the parsed public key. - bool Init(Span ech_config, Span private_key, + // Init parses |ech_config| as an ECHConfig and saves a copy of |key|. + // It returns true on success and false on error. + bool Init(Span ech_config, const EVP_HPKE_KEY *key, bool is_retry_config); // SetupContext sets up |ctx| for a new connection, given the specified diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index f939bfe0a..a8e6a0c2a 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -1539,40 +1540,48 @@ bool MakeECHConfig(std::vector *out, uint8_t config_id, } TEST(SSLTest, ECHKeys) { - // kWrongPrivateKey is an unrelated, but valid X25519 private key. - const uint8_t kWrongPrivateKey[X25519_PRIVATE_KEY_LEN] = { - 0xbb, 0xfe, 0x08, 0xf7, 0x31, 0xde, 0x9c, 0x8a, 0xf2, 0x06, 0x4a, - 0x18, 0xd7, 0x8b, 0x79, 0x31, 0xe2, 0x53, 0xdd, 0x63, 0x8f, 0x58, - 0x42, 0xda, 0x21, 0x0e, 0x61, 0x97, 0x29, 0xcc, 0x17, 0x71}; - bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); ASSERT_TRUE(ctx); bssl::UniquePtr keys(SSL_ECH_KEYS_new()); ASSERT_TRUE(keys); - // Adding an ECHConfig with the wrong private key is an error. + // Adding an ECHConfig with the wrong public key is an error. + bssl::ScopedEVP_HPKE_KEY wrong_key; + ASSERT_TRUE( + EVP_HPKE_KEY_generate(wrong_key.get(), EVP_hpke_x25519_hkdf_sha256())); ASSERT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, kECHConfig, - sizeof(kECHConfig), kWrongPrivateKey, - sizeof(kWrongPrivateKey))); + sizeof(kECHConfig), wrong_key.get())); uint32_t err = ERR_get_error(); EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err)); EXPECT_EQ(SSL_R_ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH, ERR_GET_REASON(err)); ERR_clear_error(); - // Adding an ECHConfig with the matching private key succeeds. + // Adding an ECHConfig with the right public key, but wrong KEM ID, is an + // error. + bssl::ScopedEVP_HPKE_KEY key; + ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), + kECHPrivateKey, sizeof(kECHPrivateKey))); + std::vector ech_config; + ASSERT_TRUE(MakeECHConfig( + &ech_config, 0x42, 0x0010 /* DHKEM(P-256, HKDF-SHA256) */, kECHPublicKey, + std::vector{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM}, + /*extensions=*/{})); + EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, + ech_config.data(), ech_config.size(), + key.get())); + + // Adding an ECHConfig with the matching public key succeeds. ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, kECHConfig, - sizeof(kECHConfig), kECHPrivateKey, - sizeof(kECHPrivateKey))); + sizeof(kECHConfig), key.get())); ASSERT_TRUE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get())); // Build a new config list and replace the old one on |ctx|. bssl::UniquePtr next_keys(SSL_ECH_KEYS_new()); ASSERT_TRUE(SSL_ECH_KEYS_add(next_keys.get(), /*is_retry_config=*/1, - kECHConfig, sizeof(kECHConfig), kECHPrivateKey, - sizeof(kECHPrivateKey))); + kECHConfig, sizeof(kECHConfig),key.get())); ASSERT_TRUE(SSL_CTX_set1_ech_keys(ctx.get(), next_keys.get())); } @@ -1587,11 +1596,14 @@ TEST(SSLTest, ECHServerConfigListTruncatedPublicKey) { bssl::UniquePtr ctx(SSL_CTX_new(TLS_method())); ASSERT_TRUE(ctx); + bssl::ScopedEVP_HPKE_KEY key; + ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), + kECHPrivateKey, sizeof(kECHPrivateKey))); bssl::UniquePtr keys(SSL_ECH_KEYS_new()); ASSERT_TRUE(keys); ASSERT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config.data(), ech_config.size(), - kECHPrivateKey, sizeof(kECHPrivateKey))); + key.get())); uint32_t err = ERR_peek_error(); EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err)); @@ -1609,10 +1621,11 @@ TEST(SSLTest, ECHServerConfigsWithoutRetryConfigs) { bssl::UniquePtr keys(SSL_ECH_KEYS_new()); ASSERT_TRUE(keys); - // Adding an ECHConfig with the matching private key succeeds. + bssl::ScopedEVP_HPKE_KEY key; + ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), + kECHPrivateKey, sizeof(kECHPrivateKey))); ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/0, kECHConfig, - sizeof(kECHConfig), kECHPrivateKey, - sizeof(kECHPrivateKey))); + sizeof(kECHConfig), key.get())); ASSERT_FALSE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get())); uint32_t err = ERR_peek_error(); @@ -1623,8 +1636,7 @@ TEST(SSLTest, ECHServerConfigsWithoutRetryConfigs) { // Add the same ECHConfig to the list, but this time mark it as a retry // config. ASSERT_TRUE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, kECHConfig, - sizeof(kECHConfig), kECHPrivateKey, - sizeof(kECHPrivateKey))); + sizeof(kECHConfig), key.get())); ASSERT_TRUE(SSL_CTX_set1_ech_keys(ctx.get(), keys.get())); } @@ -1632,12 +1644,15 @@ TEST(SSLTest, ECHServerConfigsWithoutRetryConfigs) { TEST(SSLTest, UnsupportedECHConfig) { bssl::UniquePtr keys(SSL_ECH_KEYS_new()); ASSERT_TRUE(keys); + bssl::ScopedEVP_HPKE_KEY key; + ASSERT_TRUE(EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), + kECHPrivateKey, sizeof(kECHPrivateKey))); // Unsupported versions are rejected. static const uint8_t kUnsupportedVersion[] = {0xff, 0xff, 0x00, 0x00}; EXPECT_FALSE(SSL_ECH_KEYS_add( keys.get(), /*is_retry_config=*/1, kUnsupportedVersion, - sizeof(kUnsupportedVersion), kECHPrivateKey, sizeof(kECHPrivateKey))); + sizeof(kUnsupportedVersion), key.get())); // Unsupported cipher suites are rejected. (We only support HKDF-SHA256.) std::vector ech_config; @@ -1647,27 +1662,7 @@ TEST(SSLTest, UnsupportedECHConfig) { /*extensions=*/{})); EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config.data(), ech_config.size(), - kECHPrivateKey, sizeof(kECHPrivateKey))); - - // Unsupported KEMs are rejected. - static const uint8_t kP256PublicKey[] = { - 0x04, 0xe6, 0x2b, 0x69, 0xe2, 0xbf, 0x65, 0x9f, 0x97, 0xbe, 0x2f, - 0x1e, 0x0d, 0x94, 0x8a, 0x4c, 0xd5, 0x97, 0x6b, 0xb7, 0xa9, 0x1e, - 0x0d, 0x46, 0xfb, 0xdd, 0xa9, 0xa9, 0x1e, 0x9d, 0xdc, 0xba, 0x5a, - 0x01, 0xe7, 0xd6, 0x97, 0xa8, 0x0a, 0x18, 0xf9, 0xc3, 0xc4, 0xa3, - 0x1e, 0x56, 0xe2, 0x7c, 0x83, 0x48, 0xdb, 0x16, 0x1a, 0x1c, 0xf5, - 0x1d, 0x7e, 0xf1, 0x94, 0x2d, 0x4b, 0xcf, 0x72, 0x22, 0xc1}; - static const uint8_t kP256PrivateKey[] = { - 0x07, 0x0f, 0x08, 0x72, 0x7a, 0xd4, 0xa0, 0x4a, 0x9c, 0xdd, 0x59, - 0xc9, 0x4d, 0x89, 0x68, 0x77, 0x08, 0xb5, 0x6f, 0xc9, 0x5d, 0x30, - 0x77, 0x0e, 0xe8, 0xd1, 0xc9, 0xce, 0x0a, 0x8b, 0xb4, 0x6a}; - ASSERT_TRUE(MakeECHConfig( - &ech_config, 0x42, 0x0010 /* DHKEM(P-256, HKDF-SHA256) */, kP256PublicKey, - std::vector{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM}, - /*extensions=*/{})); - EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, - ech_config.data(), ech_config.size(), - kP256PrivateKey, sizeof(kP256PrivateKey))); + key.get())); // Unsupported extensions are rejected. static const uint8_t kExtensions[] = {0x00, 0x01, 0x00, 0x00}; @@ -1677,7 +1672,7 @@ TEST(SSLTest, UnsupportedECHConfig) { kExtensions)); EXPECT_FALSE(SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/1, ech_config.data(), ech_config.size(), - kECHPrivateKey, sizeof(kECHPrivateKey))); + key.get())); } static void AppendSession(SSL_SESSION *session, void *arg) { diff --git a/ssl/test/fuzzer.h b/ssl/test/fuzzer.h index 07c2d6d12..509cfdb38 100644 --- a/ssl/test/fuzzer.h +++ b/ssl/test/fuzzer.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -456,14 +457,13 @@ class TLSFuzzer { if (role_ == kServer) { bssl::UniquePtr keys(SSL_ECH_KEYS_new()); - if (!keys) { - return false; - } - if (!SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/true, kECHConfig, - sizeof(kECHConfig), kECHKey, sizeof(kECHKey))) { - return false; - } - if (!SSL_CTX_set1_ech_keys(ctx_.get(), keys.get())) { + bssl::ScopedEVP_HPKE_KEY key; + if (!keys || + !EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), kECHKey, + sizeof(kECHKey)) || + !SSL_ECH_KEYS_add(keys.get(), /*is_retry_config=*/true, kECHConfig, + sizeof(kECHConfig), key.get()) || + !SSL_CTX_set1_ech_keys(ctx_.get(), keys.get())) { return false; } } diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc index 9e682295d..7978a70be 100644 --- a/ssl/test/test_config.cc +++ b/ssl/test/test_config.cc @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -1737,12 +1738,15 @@ bssl::UniquePtr TestConfig::NewSSL( const std::string &ech_config = ech_server_configs[i]; const std::string &ech_private_key = ech_server_keys[i]; const int is_retry_config = ech_is_retry_config[i]; - if (!SSL_ECH_KEYS_add( + bssl::ScopedEVP_HPKE_KEY key; + if (!EVP_HPKE_KEY_init( + key.get(), EVP_hpke_x25519_hkdf_sha256(), + reinterpret_cast(ech_private_key.data()), + ech_private_key.size()) || + !SSL_ECH_KEYS_add( keys.get(), is_retry_config, reinterpret_cast(ech_config.data()), - ech_config.size(), - reinterpret_cast(ech_private_key.data()), - ech_private_key.size())) { + ech_config.size(), key.get())) { return nullptr; } } diff --git a/tool/server.cc b/tool/server.cc index 858e8a1e8..18b692d32 100644 --- a/tool/server.cc +++ b/tool/server.cc @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -61,12 +62,12 @@ static const struct argument kArguments[] = { "-ocsp-response", kOptionalArgument, "OCSP response file to send", }, { - "-echconfig-key", + "-ech-key", kOptionalArgument, "File containing the private key corresponding to the ECHConfig.", }, { - "-echconfig", + "-ech-config", kOptionalArgument, "File containing one ECHConfig.", }, @@ -271,40 +272,41 @@ bool Server(const std::vector &args) { } } - if (args_map.count("-echconfig-key") + args_map.count("-echconfig") == 1) { + if (args_map.count("-ech-key") + args_map.count("-ech-config") == 1) { fprintf(stderr, - "-echconfig and -echconfig-key must be specified together.\n"); + "-ech-config and -ech-key must be specified together.\n"); return false; } - if (args_map.count("-echconfig-key") != 0) { - std::string echconfig_key_path = args_map["-echconfig-key"]; - std::string echconfig_path = args_map["-echconfig"]; - + if (args_map.count("-ech-key") != 0) { // Load the ECH private key. - ScopedFILE echconfig_key_file(fopen(echconfig_key_path.c_str(), "rb")); - std::vector echconfig_key; - if (echconfig_key_file == nullptr || - !ReadAll(&echconfig_key, echconfig_key_file.get())) { - fprintf(stderr, "Error reading %s\n", echconfig_key_path.c_str()); + std::string ech_key_path = args_map["-ech-key"]; + ScopedFILE ech_key_file(fopen(ech_key_path.c_str(), "rb")); + std::vector ech_key; + if (ech_key_file == nullptr || + !ReadAll(&ech_key, ech_key_file.get())) { + fprintf(stderr, "Error reading %s\n", ech_key_path.c_str()); return false; } // Load the ECHConfig. - ScopedFILE echconfig_file(fopen(echconfig_path.c_str(), "rb")); - std::vector echconfig; - if (echconfig_file == nullptr || - !ReadAll(&echconfig, echconfig_file.get())) { - fprintf(stderr, "Error reading %s\n", echconfig_path.c_str()); + std::string ech_config_path = args_map["-ech-config"]; + ScopedFILE ech_config_file(fopen(ech_config_path.c_str(), "rb")); + std::vector ech_config; + if (ech_config_file == nullptr || + !ReadAll(&ech_config, ech_config_file.get())) { + fprintf(stderr, "Error reading %s\n", ech_config_path.c_str()); return false; } bssl::UniquePtr keys(SSL_ECH_KEYS_new()); + bssl::ScopedEVP_HPKE_KEY key; if (!keys || + !EVP_HPKE_KEY_init(key.get(), EVP_hpke_x25519_hkdf_sha256(), + ech_key.data(), ech_key.size()) || !SSL_ECH_KEYS_add(keys.get(), - /*is_retry_config=*/1, echconfig.data(), - echconfig.size(), echconfig_key.data(), - echconfig_key.size()) || + /*is_retry_config=*/1, ech_config.data(), + ech_config.size(), key.get()) || !SSL_CTX_set1_ech_keys(ctx.get(), keys.get())) { fprintf(stderr, "Error setting server's ECHConfig and private key\n"); return false;