Introduce EVP_HPKE_{AEAD,KDF} types.

This replaces the ID-based API with one that is more static linker
friendly. For ECH, it doesn't make a difference because we currently
pull in all the options we've implemented. But this means other HPKE
uses need not pull in everything ECH needs and vice versa.

Along the way, fix an inconsistency: we prefixed all the AEAD constants
with "AEAD", but not the others. Since the rest of the name already
determines everything, go with the shorter version.

Bug: 410
Change-Id: I56e46c13b43c97e15eeb45204cde7019dd21e250
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/47327
Commit-Queue: David Benjamin <davidben@google.com>
Reviewed-by: Adam Langley <agl@google.com>
grpc-202302
David Benjamin 4 years ago committed by CQ bot account: commit-bot@chromium.org
parent 1d842c65e6
commit f39c81d524
  1. 231
      crypto/hpke/hpke.c
  2. 108
      crypto/hpke/hpke_test.cc
  3. 116
      crypto/hpke/internal.h
  4. 71
      ssl/encrypted_client_hello.cc
  5. 31
      ssl/handshake_server.cc
  6. 24
      ssl/internal.h
  7. 8
      ssl/ssl_test.cc
  8. 19
      ssl/t1_lib.cc
  9. 8
      ssl/tls13_server.cc

@ -31,12 +31,18 @@
// This file implements draft-irtf-cfrg-hpke-08.
#define KEM_CONTEXT_LEN (2 * X25519_PUBLIC_VALUE_LEN)
struct evp_hpke_kdf_st {
uint16_t id;
// We only support HKDF-based KDFs.
const EVP_MD *(*hkdf_md_func)(void);
};
// This is strlen("HPKE") + 3 * sizeof(uint16_t).
#define HPKE_SUITE_ID_LEN 10
struct evp_hpke_aead_st {
uint16_t id;
const EVP_AEAD *(*aead_func)(void);
};
#define HPKE_MODE_BASE 0
#define KEM_CONTEXT_LEN (2 * X25519_PUBLIC_VALUE_LEN)
static const char kHpkeVersionId[] = "HPKE-v1";
@ -51,20 +57,6 @@ static const uint8_t kX25519SuiteID[] = {
'K', 'E', 'M', EVP_HPKE_DHKEM_X25519_HKDF_SHA256 >> 8,
EVP_HPKE_DHKEM_X25519_HKDF_SHA256 & 0x00ff};
// The suite_id for non-KEM pieces of HPKE is defined as concat("HPKE",
// I2OSP(kem_id, 2), I2OSP(kdf_id, 2), I2OSP(aead_id, 2)).
static int hpke_build_suite_id(uint8_t out[HPKE_SUITE_ID_LEN], uint16_t kdf_id,
uint16_t aead_id) {
CBB cbb;
int ret = CBB_init_fixed(&cbb, out, HPKE_SUITE_ID_LEN) &&
add_label_string(&cbb, "HPKE") &&
CBB_add_u16(&cbb, EVP_HPKE_DHKEM_X25519_HKDF_SHA256) &&
CBB_add_u16(&cbb, kdf_id) &&
CBB_add_u16(&cbb, aead_id);
CBB_cleanup(&cbb);
return ret;
}
static int hpke_labeled_extract(const EVP_MD *hkdf_md, uint8_t *out_key,
size_t *out_len, const uint8_t *salt,
size_t salt_len, const uint8_t *suite_id,
@ -108,81 +100,62 @@ static int hpke_extract_and_expand(const EVP_MD *hkdf_md, uint8_t *out_key,
const uint8_t kem_context[KEM_CONTEXT_LEN]) {
uint8_t prk[EVP_MAX_MD_SIZE];
size_t prk_len;
static const char kEaePrkLabel[] = "eae_prk";
if (!hpke_labeled_extract(hkdf_md, prk, &prk_len, NULL, 0, kX25519SuiteID,
sizeof(kX25519SuiteID), kEaePrkLabel, dh,
sizeof(kX25519SuiteID), "eae_prk", dh,
X25519_PUBLIC_VALUE_LEN)) {
return 0;
}
static const char kPRKExpandLabel[] = "shared_secret";
if (!hpke_labeled_expand(hkdf_md, out_key, out_len, prk, prk_len,
kX25519SuiteID, sizeof(kX25519SuiteID),
kPRKExpandLabel, kem_context, KEM_CONTEXT_LEN)) {
"shared_secret", kem_context, KEM_CONTEXT_LEN)) {
return 0;
}
return 1;
}
uint16_t EVP_HPKE_CTX_get_aead_id(const EVP_HPKE_CTX *hpke) {
return hpke->aead_id;
}
uint16_t EVP_HPKE_CTX_get_kdf_id(const EVP_HPKE_CTX *hpke) {
return hpke->kdf_id;
}
// This is strlen("HPKE") + 3 * sizeof(uint16_t).
#define HPKE_SUITE_ID_LEN 10
const EVP_AEAD *EVP_HPKE_get_aead(uint16_t aead_id) {
switch (aead_id) {
case EVP_HPKE_AEAD_AES_128_GCM:
return EVP_aead_aes_128_gcm();
case EVP_HPKE_AEAD_AES_256_GCM:
return EVP_aead_aes_256_gcm();
case EVP_HPKE_AEAD_CHACHA20POLY1305:
return EVP_aead_chacha20_poly1305();
}
OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR);
return NULL;
// The suite_id for non-KEM pieces of HPKE is defined as concat("HPKE",
// I2OSP(kem_id, 2), I2OSP(kdf_id, 2), I2OSP(aead_id, 2)).
static int hpke_build_suite_id(const EVP_HPKE_CTX *hpke,
uint8_t out[HPKE_SUITE_ID_LEN]) {
CBB cbb;
int ret = CBB_init_fixed(&cbb, out, HPKE_SUITE_ID_LEN) &&
add_label_string(&cbb, "HPKE") &&
CBB_add_u16(&cbb, EVP_HPKE_DHKEM_X25519_HKDF_SHA256) &&
CBB_add_u16(&cbb, hpke->kdf->id) &&
CBB_add_u16(&cbb, hpke->aead->id);
CBB_cleanup(&cbb);
return ret;
}
const EVP_MD *EVP_HPKE_get_hkdf_md(uint16_t kdf_id) {
switch (kdf_id) {
case EVP_HPKE_HKDF_SHA256:
return EVP_sha256();
}
OPENSSL_PUT_ERROR(EVP, ERR_R_INTERNAL_ERROR);
return NULL;
}
#define HPKE_MODE_BASE 0
static int hpke_key_schedule(EVP_HPKE_CTX *hpke, const uint8_t *shared_secret,
size_t shared_secret_len, const uint8_t *info,
size_t info_len) {
// Attempt to get an EVP_AEAD*.
const EVP_AEAD *aead = EVP_HPKE_get_aead(hpke->aead_id);
if (aead == NULL) {
return 0;
}
uint8_t suite_id[HPKE_SUITE_ID_LEN];
if (!hpke_build_suite_id(suite_id, hpke->kdf_id, hpke->aead_id)) {
if (!hpke_build_suite_id(hpke, suite_id)) {
return 0;
}
// psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
static const char kPskIdHashLabel[] = "psk_id_hash";
// TODO(davidben): Precompute this value and store it with the EVP_HPKE_KDF.
const EVP_MD *hkdf_md = hpke->kdf->hkdf_md_func();
uint8_t psk_id_hash[EVP_MAX_MD_SIZE];
size_t psk_id_hash_len;
if (!hpke_labeled_extract(hpke->hkdf_md, psk_id_hash, &psk_id_hash_len, NULL,
0, suite_id, sizeof(suite_id), kPskIdHashLabel,
NULL, 0)) {
if (!hpke_labeled_extract(hkdf_md, psk_id_hash, &psk_id_hash_len, NULL, 0,
suite_id, sizeof(suite_id), "psk_id_hash", NULL,
0)) {
return 0;
}
// info_hash = LabeledExtract("", "info_hash", info)
static const char kInfoHashLabel[] = "info_hash";
uint8_t info_hash[EVP_MAX_MD_SIZE];
size_t info_hash_len;
if (!hpke_labeled_extract(hpke->hkdf_md, info_hash, &info_hash_len, NULL, 0,
suite_id, sizeof(suite_id), kInfoHashLabel, info,
if (!hpke_labeled_extract(hkdf_md, info_hash, &info_hash_len, NULL, 0,
suite_id, sizeof(suite_id), "info_hash", info,
info_len)) {
return 0;
}
@ -200,45 +173,37 @@ static int hpke_key_schedule(EVP_HPKE_CTX *hpke, const uint8_t *shared_secret,
}
// secret = LabeledExtract(shared_secret, "secret", psk)
static const char kSecretExtractLabel[] = "secret";
uint8_t secret[EVP_MAX_MD_SIZE];
size_t secret_len;
if (!hpke_labeled_extract(hpke->hkdf_md, secret, &secret_len, shared_secret,
if (!hpke_labeled_extract(hkdf_md, secret, &secret_len, shared_secret,
shared_secret_len, suite_id, sizeof(suite_id),
kSecretExtractLabel, NULL, 0)) {
"secret", NULL, 0)) {
return 0;
}
// key = LabeledExpand(secret, "key", key_schedule_context, Nk)
static const char kKeyExpandLabel[] = "key";
const EVP_AEAD *aead = hpke->aead->aead_func();
uint8_t key[EVP_AEAD_MAX_KEY_LENGTH];
const size_t kKeyLen = EVP_AEAD_key_length(aead);
if (!hpke_labeled_expand(hpke->hkdf_md, key, kKeyLen, secret, secret_len,
suite_id, sizeof(suite_id), kKeyExpandLabel, context,
context_len)) {
return 0;
}
// Initialize the HPKE context's AEAD context, storing a copy of |key|.
if (!EVP_AEAD_CTX_init(&hpke->aead_ctx, aead, key, kKeyLen, 0, NULL)) {
if (!hpke_labeled_expand(hkdf_md, key, kKeyLen, secret, secret_len, suite_id,
sizeof(suite_id), "key", context, context_len) ||
!EVP_AEAD_CTX_init(&hpke->aead_ctx, aead, key, kKeyLen,
EVP_AEAD_DEFAULT_TAG_LENGTH, NULL)) {
return 0;
}
// base_nonce = LabeledExpand(secret, "base_nonce", key_schedule_context, Nn)
static const char kNonceExpandLabel[] = "base_nonce";
if (!hpke_labeled_expand(hpke->hkdf_md, hpke->base_nonce,
if (!hpke_labeled_expand(hkdf_md, hpke->base_nonce,
EVP_AEAD_nonce_length(aead), secret, secret_len,
suite_id, sizeof(suite_id), kNonceExpandLabel,
context, context_len)) {
suite_id, sizeof(suite_id), "base_nonce", context,
context_len)) {
return 0;
}
// exporter_secret = LabeledExpand(secret, "exp", key_schedule_context, Nh)
static const char kExporterSecretExpandLabel[] = "exp";
if (!hpke_labeled_expand(hpke->hkdf_md, hpke->exporter_secret,
EVP_MD_size(hpke->hkdf_md), secret, secret_len,
suite_id, sizeof(suite_id),
kExporterSecretExpandLabel, context, context_len)) {
if (!hpke_labeled_expand(hkdf_md, hpke->exporter_secret, EVP_MD_size(hkdf_md),
secret, secret_len, suite_id, sizeof(suite_id),
"exp", context, context_len)) {
return 0;
}
@ -290,6 +255,33 @@ static int hpke_decap(const EVP_HPKE_CTX *hpke,
return 1;
}
const EVP_HPKE_KDF *EVP_hpke_hkdf_sha256(void) {
static const EVP_HPKE_KDF kKDF = {EVP_HPKE_HKDF_SHA256, &EVP_sha256};
return &kKDF;
}
uint16_t EVP_HPKE_KDF_id(const EVP_HPKE_KDF *kdf) { return kdf->id; }
const EVP_HPKE_AEAD *EVP_hpke_aes_128_gcm(void) {
static const EVP_HPKE_AEAD kAEAD = {EVP_HPKE_AES_128_GCM,
&EVP_aead_aes_128_gcm};
return &kAEAD;
}
const EVP_HPKE_AEAD *EVP_hpke_aes_256_gcm(void) {
static const EVP_HPKE_AEAD kAEAD = {EVP_HPKE_AES_256_GCM,
&EVP_aead_aes_256_gcm};
return &kAEAD;
}
const EVP_HPKE_AEAD *EVP_hpke_chacha20_poly1305(void) {
static const EVP_HPKE_AEAD kAEAD = {EVP_HPKE_CHACHA20_POLY1305,
&EVP_aead_chacha20_poly1305};
return &kAEAD;
}
uint16_t EVP_HPKE_AEAD_id(const EVP_HPKE_AEAD *aead) { return aead->id; }
void EVP_HPKE_CTX_init(EVP_HPKE_CTX *ctx) {
OPENSSL_memset(ctx, 0, sizeof(EVP_HPKE_CTX));
EVP_AEAD_CTX_zero(&ctx->aead_ctx);
@ -300,23 +292,25 @@ void EVP_HPKE_CTX_cleanup(EVP_HPKE_CTX *ctx) {
}
int EVP_HPKE_CTX_setup_base_s_x25519(EVP_HPKE_CTX *hpke, uint8_t *out_enc,
size_t out_enc_len, uint16_t kdf_id,
uint16_t aead_id,
size_t out_enc_len,
const EVP_HPKE_KDF *kdf,
const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_value,
size_t peer_public_value_len,
const uint8_t *info, size_t info_len) {
uint8_t seed[X25519_PRIVATE_KEY_LEN];
RAND_bytes(seed, sizeof(seed));
return EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
hpke, out_enc, out_enc_len, kdf_id, aead_id, peer_public_value,
hpke, out_enc, out_enc_len, kdf, aead, peer_public_value,
peer_public_value_len, info, info_len, seed, sizeof(seed));
}
int EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
EVP_HPKE_CTX *hpke, uint8_t *out_enc, size_t out_enc_len, uint16_t kdf_id,
uint16_t aead_id, const uint8_t *peer_public_value,
size_t peer_public_value_len, const uint8_t *info, size_t info_len,
const uint8_t *seed, size_t seed_len) {
EVP_HPKE_CTX *hpke, uint8_t *out_enc, size_t out_enc_len,
const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_value, size_t peer_public_value_len,
const uint8_t *info, size_t info_len, const uint8_t *seed,
size_t seed_len) {
if (out_enc_len != X25519_PUBLIC_VALUE_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_BUFFER_SIZE);
return 0;
@ -331,12 +325,8 @@ int EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
}
hpke->is_sender = 1;
hpke->kdf_id = kdf_id;
hpke->aead_id = aead_id;
hpke->hkdf_md = EVP_HPKE_get_hkdf_md(kdf_id);
if (hpke->hkdf_md == NULL) {
return 0;
}
hpke->kdf = kdf;
hpke->aead = aead;
X25519_public_from_private(out_enc, seed);
uint8_t shared_secret[SHA256_DIGEST_LENGTH];
if (!hpke_encap(hpke, shared_secret, peer_public_value, seed, out_enc) ||
@ -347,13 +337,11 @@ int EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
return 1;
}
int EVP_HPKE_CTX_setup_base_r_x25519(EVP_HPKE_CTX *hpke, uint16_t kdf_id,
uint16_t aead_id, const uint8_t *enc,
size_t enc_len, const uint8_t *public_key,
size_t public_key_len,
const uint8_t *private_key,
size_t private_key_len,
const uint8_t *info, size_t info_len) {
int EVP_HPKE_CTX_setup_base_r_x25519(
EVP_HPKE_CTX *hpke, const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *enc, size_t enc_len, const uint8_t *public_key,
size_t public_key_len, const uint8_t *private_key, size_t private_key_len,
const uint8_t *info, size_t info_len) {
if (enc_len != X25519_PUBLIC_VALUE_LEN) {
OPENSSL_PUT_ERROR(EVP, EVP_R_INVALID_PEER_KEY);
return 0;
@ -365,12 +353,8 @@ int EVP_HPKE_CTX_setup_base_r_x25519(EVP_HPKE_CTX *hpke, uint16_t kdf_id,
}
hpke->is_sender = 0;
hpke->kdf_id = kdf_id;
hpke->aead_id = aead_id;
hpke->hkdf_md = EVP_HPKE_get_hkdf_md(kdf_id);
if (hpke->hkdf_md == NULL) {
return 0;
}
hpke->kdf = kdf;
hpke->aead = aead;
uint8_t shared_secret[SHA256_DIGEST_LENGTH];
if (!hpke_decap(hpke, shared_secret, enc, public_key, private_key) ||
!hpke_key_schedule(hpke, shared_secret, sizeof(shared_secret), info,
@ -398,11 +382,6 @@ static void hpke_nonce(const EVP_HPKE_CTX *hpke, uint8_t *out_nonce,
}
}
size_t EVP_HPKE_CTX_max_overhead(const EVP_HPKE_CTX *hpke) {
assert(hpke->is_sender);
return EVP_AEAD_max_overhead(hpke->aead_ctx.aead);
}
int EVP_HPKE_CTX_open(EVP_HPKE_CTX *hpke, uint8_t *out, size_t *out_len,
size_t max_out_len, const uint8_t *in, size_t in_len,
const uint8_t *ad, size_t ad_len) {
@ -455,15 +434,27 @@ int EVP_HPKE_CTX_export(const EVP_HPKE_CTX *hpke, uint8_t *out,
size_t secret_len, const uint8_t *context,
size_t context_len) {
uint8_t suite_id[HPKE_SUITE_ID_LEN];
if (!hpke_build_suite_id(suite_id, hpke->kdf_id, hpke->aead_id)) {
if (!hpke_build_suite_id(hpke, suite_id)) {
return 0;
}
static const char kExportExpandLabel[] = "sec";
if (!hpke_labeled_expand(hpke->hkdf_md, out, secret_len,
hpke->exporter_secret, EVP_MD_size(hpke->hkdf_md),
suite_id, sizeof(suite_id), kExportExpandLabel,
context, context_len)) {
const EVP_MD *hkdf_md = hpke->kdf->hkdf_md_func();
if (!hpke_labeled_expand(hkdf_md, out, secret_len, hpke->exporter_secret,
EVP_MD_size(hkdf_md), suite_id, sizeof(suite_id),
"sec", context, context_len)) {
return 0;
}
return 1;
}
size_t EVP_HPKE_CTX_max_overhead(const EVP_HPKE_CTX *hpke) {
assert(hpke->is_sender);
return EVP_AEAD_max_overhead(EVP_AEAD_CTX_aead(&hpke->aead_ctx));
}
const EVP_HPKE_AEAD *EVP_HPKE_CTX_aead(const EVP_HPKE_CTX *hpke) {
return hpke->aead;
}
const EVP_HPKE_KDF *EVP_HPKE_CTX_kdf(const EVP_HPKE_CTX *hpke) {
return hpke->kdf;
}

@ -35,6 +35,16 @@
namespace bssl {
namespace {
const decltype(&EVP_hpke_aes_128_gcm) kAllAEADs[] = {
&EVP_hpke_aes_128_gcm,
&EVP_hpke_aes_256_gcm,
&EVP_hpke_chacha20_poly1305,
};
const decltype(&EVP_hpke_hkdf_sha256) kAllKDFs[] = {
&EVP_hpke_hkdf_sha256,
};
// HPKETestVector corresponds to one array member in the published
// test-vectors.json.
class HPKETestVector {
@ -45,24 +55,26 @@ class HPKETestVector {
bool ReadFromFileTest(FileTest *t);
void Verify() const {
ScopedEVP_HPKE_CTX sender_ctx;
ScopedEVP_HPKE_CTX receiver_ctx;
ASSERT_GT(secret_key_e_.size(), 0u);
const EVP_HPKE_AEAD *aead = GetAEAD();
ASSERT_TRUE(aead);
const EVP_HPKE_KDF *kdf = GetKDF();
ASSERT_TRUE(kdf);
// Set up the sender.
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
ASSERT_TRUE(EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
sender_ctx.get(), enc, sizeof(enc), kdf_id_, aead_id_,
public_key_r_.data(), public_key_r_.size(), info_.data(), info_.size(),
secret_key_e_.data(), secret_key_e_.size()));
sender_ctx.get(), enc, sizeof(enc), kdf, aead, public_key_r_.data(),
public_key_r_.size(), info_.data(), info_.size(), secret_key_e_.data(),
secret_key_e_.size()));
EXPECT_EQ(Bytes(enc), Bytes(public_key_e_));
// Set up the receiver.
ScopedEVP_HPKE_CTX receiver_ctx;
ASSERT_TRUE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), kdf_id_, aead_id_, enc, sizeof(enc),
public_key_r_.data(), public_key_r_.size(), secret_key_r_.data(),
secret_key_r_.size(), info_.data(), info_.size()));
receiver_ctx.get(), kdf, aead, enc, sizeof(enc), public_key_r_.data(),
public_key_r_.size(), secret_key_r_.data(), secret_key_r_.size(),
info_.data(), info_.size()));
VerifyEncryptions(sender_ctx.get(), receiver_ctx.get());
VerifyExports(sender_ctx.get());
@ -70,6 +82,24 @@ class HPKETestVector {
}
private:
const EVP_HPKE_AEAD *GetAEAD() const {
for (const auto aead : kAllAEADs) {
if (EVP_HPKE_AEAD_id(aead()) == aead_id_) {
return aead();
}
}
return nullptr;
}
const EVP_HPKE_KDF *GetKDF() const {
for (const auto kdf : kAllKDFs) {
if (EVP_HPKE_KDF_id(kdf()) == kdf_id_) {
return kdf();
}
}
return nullptr;
}
void VerifyEncryptions(EVP_HPKE_CTX *sender_ctx,
EVP_HPKE_CTX *receiver_ctx) const {
for (const Encryption &task : encryptions_) {
@ -212,10 +242,6 @@ TEST(HPKETest, VerifyTestVectors) {
// generates new keys for each context. Test this codepath by checking we can
// decrypt our own messages.
TEST(HPKETest, RoundTrip) {
uint16_t kdf_ids[] = {EVP_HPKE_HKDF_SHA256};
uint16_t aead_ids[] = {EVP_HPKE_AEAD_AES_128_GCM, EVP_HPKE_AEAD_AES_256_GCM,
EVP_HPKE_AEAD_CHACHA20POLY1305};
const uint8_t info_a[] = {1, 1, 2, 3, 5, 8};
const uint8_t info_b[] = {42, 42, 42};
const uint8_t ad_a[] = {1, 2, 4, 8, 16};
@ -228,10 +254,10 @@ TEST(HPKETest, RoundTrip) {
uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(public_key_r, secret_key_r);
for (uint16_t kdf_id : kdf_ids) {
SCOPED_TRACE(kdf_id);
for (uint16_t aead_id : aead_ids) {
SCOPED_TRACE(aead_id);
for (const auto kdf : kAllKDFs) {
SCOPED_TRACE(EVP_HPKE_KDF_id(kdf()));
for (const auto aead : kAllAEADs) {
SCOPED_TRACE(EVP_HPKE_AEAD_id(aead()));
for (const Span<const uint8_t> &info : info_values) {
SCOPED_TRACE(Bytes(info));
for (const Span<const uint8_t> &ad : ad_values) {
@ -240,15 +266,15 @@ TEST(HPKETest, RoundTrip) {
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
ASSERT_TRUE(EVP_HPKE_CTX_setup_base_s_x25519(
sender_ctx.get(), enc, sizeof(enc), kdf_id, aead_id, public_key_r,
sender_ctx.get(), enc, sizeof(enc), kdf(), aead(), public_key_r,
sizeof(public_key_r), info.data(), info.size()));
// Set up the receiver.
ScopedEVP_HPKE_CTX receiver_ctx;
ASSERT_TRUE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), kdf_id, aead_id, enc, sizeof(enc),
public_key_r, sizeof(public_key_r), secret_key_r,
sizeof(secret_key_r), info.data(), info.size()));
receiver_ctx.get(), kdf(), aead(), enc, sizeof(enc), public_key_r,
sizeof(public_key_r), secret_key_r, sizeof(secret_key_r),
info.data(), info.size()));
const char kCleartextPayload[] = "foobar";
@ -295,25 +321,21 @@ TEST(HPKETest, X25519EncapSmallOrderPoint) {
uint8_t public_key_r[X25519_PUBLIC_VALUE_LEN];
X25519_keypair(public_key_r, secret_key_r);
uint16_t kdf_ids[] = {EVP_HPKE_HKDF_SHA256};
uint16_t aead_ids[] = {EVP_HPKE_AEAD_AES_128_GCM, EVP_HPKE_AEAD_AES_256_GCM,
EVP_HPKE_AEAD_CHACHA20POLY1305};
for (uint16_t kdf_id : kdf_ids) {
SCOPED_TRACE(kdf_id);
for (uint16_t aead_id : aead_ids) {
SCOPED_TRACE(aead_id);
for (const auto kdf : kAllKDFs) {
SCOPED_TRACE(EVP_HPKE_KDF_id(kdf()));
for (const auto aead : kAllAEADs) {
SCOPED_TRACE(EVP_HPKE_AEAD_id(aead()));
// Set up the sender, passing in kSmallOrderPoint as |peer_public_value|.
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_s_x25519(
sender_ctx.get(), enc, sizeof(enc), kdf_id, aead_id, kSmallOrderPoint,
sender_ctx.get(), enc, sizeof(enc), kdf(), aead(), kSmallOrderPoint,
sizeof(kSmallOrderPoint), nullptr, 0));
// Set up the receiver, passing in kSmallOrderPoint as |enc|.
ScopedEVP_HPKE_CTX receiver_ctx;
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), kdf_id, aead_id, kSmallOrderPoint,
receiver_ctx.get(), kdf(), aead(), kSmallOrderPoint,
sizeof(kSmallOrderPoint), public_key_r, sizeof(public_key_r),
secret_key_r, sizeof(secret_key_r), nullptr, 0));
}
@ -333,7 +355,7 @@ TEST(HPKETest, ReceiverInvalidSeal) {
// Set up the receiver.
ScopedEVP_HPKE_CTX receiver_ctx;
ASSERT_TRUE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM,
receiver_ctx.get(), EVP_hpke_hkdf_sha256(), EVP_hpke_aes_128_gcm(),
kMockEnc, sizeof(kMockEnc), public_key_r, sizeof(public_key_r),
secret_key_r, sizeof(secret_key_r), nullptr, 0));
@ -360,9 +382,8 @@ TEST(HPKETest, SenderInvalidOpen) {
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
ASSERT_TRUE(EVP_HPKE_CTX_setup_base_s_x25519(
sender_ctx.get(), enc, sizeof(enc), EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AEAD_AES_128_GCM, public_key_r, sizeof(public_key_r), nullptr,
0));
sender_ctx.get(), enc, sizeof(enc), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), public_key_r, sizeof(public_key_r), nullptr, 0));
// Call Open() on the sender.
uint8_t cleartext[128];
@ -380,9 +401,8 @@ TEST(HPKETest, SetupSenderWrongLengthEnc) {
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t bogus_enc[X25519_PUBLIC_VALUE_LEN + 5];
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_s_x25519(
sender_ctx.get(), bogus_enc, sizeof(bogus_enc), EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AEAD_AES_128_GCM, public_key_r, sizeof(public_key_r), nullptr,
0));
sender_ctx.get(), bogus_enc, sizeof(bogus_enc), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), public_key_r, sizeof(public_key_r), nullptr, 0));
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
EXPECT_EQ(EVP_R_INVALID_BUFFER_SIZE, ERR_GET_REASON(err));
@ -398,7 +418,7 @@ TEST(HPKETest, SetupReceiverWrongLengthEnc) {
ScopedEVP_HPKE_CTX receiver_ctx;
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM,
receiver_ctx.get(), EVP_hpke_hkdf_sha256(), EVP_hpke_aes_128_gcm(),
bogus_enc, sizeof(bogus_enc), public_key, sizeof(public_key), private_key,
sizeof(private_key), nullptr, 0));
uint32_t err = ERR_get_error();
@ -412,8 +432,8 @@ TEST(HPKETest, SetupSenderWrongLengthPeerPublicValue) {
ScopedEVP_HPKE_CTX sender_ctx;
uint8_t enc[X25519_PUBLIC_VALUE_LEN];
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_s_x25519(
sender_ctx.get(), enc, sizeof(enc), EVP_HPKE_HKDF_SHA256,
EVP_HPKE_AEAD_AES_128_GCM, bogus_public_key_r, sizeof(bogus_public_key_r),
sender_ctx.get(), enc, sizeof(enc), EVP_hpke_hkdf_sha256(),
EVP_hpke_aes_128_gcm(), bogus_public_key_r, sizeof(bogus_public_key_r),
nullptr, 0));
uint32_t err = ERR_get_error();
EXPECT_EQ(ERR_LIB_EVP, ERR_GET_LIB(err));
@ -437,7 +457,7 @@ TEST(HPKETest, SetupReceiverWrongLengthKeys) {
{
// Test base mode with |bogus_public_key|.
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM,
receiver_ctx.get(), EVP_hpke_hkdf_sha256(), EVP_hpke_aes_128_gcm(),
enc, sizeof(enc), bogus_public_key, sizeof(bogus_public_key),
private_key, sizeof(private_key), nullptr, 0));
uint32_t err = ERR_get_error();
@ -448,7 +468,7 @@ TEST(HPKETest, SetupReceiverWrongLengthKeys) {
{
// Test base mode with |bogus_private_key|.
ASSERT_FALSE(EVP_HPKE_CTX_setup_base_r_x25519(
receiver_ctx.get(), EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM,
receiver_ctx.get(), EVP_hpke_hkdf_sha256(), EVP_hpke_aes_128_gcm(),
enc, sizeof(enc), public_key, sizeof(public_key), bogus_private_key,
sizeof(bogus_private_key), nullptr, 0));
uint32_t err = ERR_get_error();

@ -32,35 +32,44 @@ extern "C" {
//
// See https://tools.ietf.org/html/draft-irtf-cfrg-hpke-08.
// EVP_HPKE_DHKEM_* are KEM identifiers.
#define EVP_HPKE_DHKEM_X25519_HKDF_SHA256 0x0020
// EVP_HPKE_AEAD_* are AEAD identifiers.
#define EVP_HPKE_AEAD_AES_128_GCM 0x0001
#define EVP_HPKE_AEAD_AES_256_GCM 0x0002
#define EVP_HPKE_AEAD_CHACHA20POLY1305 0x0003
// Parameters.
//
// An HPKE context is parameterized by KEM, KDF, and AEAD algorithms.
typedef struct evp_hpke_kdf_st EVP_HPKE_KDF;
typedef struct evp_hpke_aead_st EVP_HPKE_AEAD;
// The following constants are KEM identifiers.
#define EVP_HPKE_DHKEM_X25519_HKDF_SHA256 0x0020
// EVP_HPKE_HKDF_* are HKDF identifiers.
// The following constants are KDF identifiers.
#define EVP_HPKE_HKDF_SHA256 0x0001
// EVP_HPKE_MAX_OVERHEAD contains the largest value that
// |EVP_HPKE_CTX_max_overhead| would ever return for any context.
#define EVP_HPKE_MAX_OVERHEAD EVP_AEAD_MAX_OVERHEAD
// The following functions are KDF algorithms which may be used with HPKE.
OPENSSL_EXPORT const EVP_HPKE_KDF *EVP_hpke_hkdf_sha256(void);
// EVP_HPKE_KDF_id returns the HPKE KDF identifier for |kdf|.
OPENSSL_EXPORT uint16_t EVP_HPKE_KDF_id(const EVP_HPKE_KDF *kdf);
// The following constants are AEAD identifiers.
#define EVP_HPKE_AES_128_GCM 0x0001
#define EVP_HPKE_AES_256_GCM 0x0002
#define EVP_HPKE_CHACHA20_POLY1305 0x0003
// The following functions are AEAD algorithms which may be used with HPKE.
OPENSSL_EXPORT const EVP_HPKE_AEAD *EVP_hpke_aes_128_gcm(void);
OPENSSL_EXPORT const EVP_HPKE_AEAD *EVP_hpke_aes_256_gcm(void);
OPENSSL_EXPORT const EVP_HPKE_AEAD *EVP_hpke_chacha20_poly1305(void);
// EVP_HPKE_AEAD_id returns the HPKE AEAD identifier for |aead|.
OPENSSL_EXPORT uint16_t EVP_HPKE_AEAD_id(const EVP_HPKE_AEAD *aead);
// Encryption contexts.
// An |EVP_HPKE_CTX| is an HPKE encryption context.
typedef struct evp_hpke_ctx_st {
const EVP_MD *hkdf_md;
EVP_AEAD_CTX aead_ctx;
uint16_t kdf_id;
uint16_t aead_id;
uint8_t base_nonce[EVP_AEAD_MAX_NONCE_LENGTH];
uint8_t exporter_secret[EVP_MAX_MD_SIZE];
uint64_t seq;
int is_sender;
} EVP_HPKE_CTX;
typedef struct evp_hpke_ctx_st EVP_HPKE_CTX;
// EVP_HPKE_CTX_init initializes an already-allocated |EVP_HPKE_CTX|. The caller
// should then use one of the |EVP_HPKE_CTX_setup_*| functions.
@ -74,12 +83,6 @@ OPENSSL_EXPORT void EVP_HPKE_CTX_cleanup(EVP_HPKE_CTX *ctx);
// Setting up HPKE contexts.
//
// In each of the following functions, |hpke| must have been initialized with
// |EVP_HPKE_CTX_init|. |kdf_id| selects the KDF for non-KEM HPKE operations and
// must be one of the |EVP_HPKE_HKDF_*| constants. |aead_id| selects the AEAD
// for the "open" and "seal" operations and must be one of the |EVP_HPKE_AEAD_*|
// constants.
// EVP_HPKE_CTX_setup_base_s_x25519 sets up |hpke| as a sender context that can
// encrypt for the private key corresponding to |peer_public_value| (the
@ -90,18 +93,19 @@ OPENSSL_EXPORT void EVP_HPKE_CTX_cleanup(EVP_HPKE_CTX *ctx);
// key, to |out_enc|. It will fail if the buffer's size in |out_enc_len| is not
// exactly |X25519_PUBLIC_VALUE_LEN|.
OPENSSL_EXPORT int EVP_HPKE_CTX_setup_base_s_x25519(
EVP_HPKE_CTX *hpke, uint8_t *out_enc, size_t out_enc_len, uint16_t kdf_id,
uint16_t aead_id, const uint8_t *peer_public_value,
size_t peer_public_value_len, const uint8_t *info, size_t info_len);
EVP_HPKE_CTX *hpke, uint8_t *out_enc, size_t out_enc_len,
const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_value, size_t peer_public_value_len,
const uint8_t *info, size_t info_len);
// EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing behaves like
// |EVP_HPKE_CTX_setup_base_s_x25519|, but takes a seed value to behave
// deterministically. This seed is the sender's ephemeral X25519 key.
OPENSSL_EXPORT int EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
EVP_HPKE_CTX *hpke, uint8_t *out_enc, size_t out_enc_len, uint16_t kdf_id,
uint16_t aead_id, const uint8_t *peer_public_value,
size_t peer_public_value_len, const uint8_t *info, size_t info_len,
const uint8_t *seed, size_t seed_len);
EVP_HPKE_CTX *hpke, uint8_t *out_enc, size_t out_enc_len,
const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *peer_public_value, size_t peer_public_value_len,
const uint8_t *info, size_t info_len, const uint8_t *seed, size_t seed_len);
// EVP_HPKE_CTX_setup_base_r_x25519 sets up |hpke| as a recipient context that
// can decrypt messages. It returns one on success, and zero otherwise.
@ -110,10 +114,10 @@ OPENSSL_EXPORT int EVP_HPKE_CTX_setup_base_s_x25519_with_seed_for_testing(
// |enc| is the encapsulated shared secret from the sender. If |enc| is invalid,
// this function will fail.
OPENSSL_EXPORT int EVP_HPKE_CTX_setup_base_r_x25519(
EVP_HPKE_CTX *hpke, uint16_t kdf_id, uint16_t aead_id, const uint8_t *enc,
size_t enc_len, const uint8_t *public_key, size_t public_key_len,
const uint8_t *private_key, size_t private_key_len, const uint8_t *info,
size_t info_len);
EVP_HPKE_CTX *hpke, const EVP_HPKE_KDF *kdf, const EVP_HPKE_AEAD *aead,
const uint8_t *enc, size_t enc_len, const uint8_t *public_key,
size_t public_key_len, const uint8_t *private_key, size_t private_key_len,
const uint8_t *info, size_t info_len);
// Using an HPKE context.
@ -166,28 +170,38 @@ OPENSSL_EXPORT int EVP_HPKE_CTX_export(const EVP_HPKE_CTX *hpke, uint8_t *out,
const uint8_t *context,
size_t context_len);
// EVP_HPKE_MAX_OVERHEAD contains the largest value that
// |EVP_HPKE_CTX_max_overhead| would ever return for any context.
#define EVP_HPKE_MAX_OVERHEAD EVP_AEAD_MAX_OVERHEAD
// EVP_HPKE_CTX_max_overhead returns the maximum number of additional bytes
// added by sealing data with |EVP_HPKE_CTX_seal|. The |hpke| context must be
// set up as a sender.
OPENSSL_EXPORT size_t EVP_HPKE_CTX_max_overhead(const EVP_HPKE_CTX *hpke);
// EVP_HPKE_CTX_get_aead_id returns |hpke|'s configured AEAD. The returned value
// is one of the |EVP_HPKE_AEAD_*| constants, or zero if the context has not
// been set up.
OPENSSL_EXPORT uint16_t EVP_HPKE_CTX_get_aead_id(const EVP_HPKE_CTX *hpke);
// EVP_HPKE_CTX_aead returns |hpke|'s configured AEAD, or NULL if the context
// has not been set up.
OPENSSL_EXPORT const EVP_HPKE_AEAD *EVP_HPKE_CTX_aead(const EVP_HPKE_CTX *hpke);
// EVP_HPKE_CTX_get_aead_id returns |hpke|'s configured KDF. The returned value
// is one of the |EVP_HPKE_HKDF_*| constants, or zero if the context has not
// been set up.
OPENSSL_EXPORT uint16_t EVP_HPKE_CTX_get_kdf_id(const EVP_HPKE_CTX *hpke);
// EVP_HPKE_CTX_kdf returns |hpke|'s configured KDF, or NULL if the context
// has not been set up.
OPENSSL_EXPORT const EVP_HPKE_KDF *EVP_HPKE_CTX_kdf(const EVP_HPKE_CTX *hpke);
// EVP_HPKE_get_aead returns the AEAD corresponding to |aead_id|, or NULL if
// |aead_id| is not a known AEAD identifier.
OPENSSL_EXPORT const EVP_AEAD *EVP_HPKE_get_aead(uint16_t aead_id);
// EVP_HPKE_get_hkdf_md returns the hash function associated with |kdf_id|, or
// NULL if |kdf_id| is not a known KDF identifier that uses HKDF.
OPENSSL_EXPORT const EVP_MD *EVP_HPKE_get_hkdf_md(uint16_t kdf_id);
// Private structures.
//
// The following structures are exported so their types are stack-allocatable,
// but accessing or modifying their fields is forbidden.
struct evp_hpke_ctx_st {
const EVP_HPKE_AEAD *aead;
const EVP_HPKE_KDF *kdf;
EVP_AEAD_CTX aead_ctx;
uint8_t base_nonce[EVP_AEAD_MAX_NONCE_LENGTH];
uint8_t exporter_secret[EVP_MAX_MD_SIZE];
uint64_t seq;
int is_sender;
};
#if defined(__cplusplus)

@ -12,13 +12,17 @@
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <openssl/ssl.h>
#include <assert.h>
#include <openssl/bytestring.h>
#include <openssl/curve25519.h>
#include <openssl/err.h>
#include <openssl/hkdf.h>
#include <openssl/ssl.h>
#include "internal.h"
#include "../crypto/hpke/internal.h"
#if defined(OPENSSL_MSAN)
@ -29,6 +33,22 @@
BSSL_NAMESPACE_BEGIN
static const decltype(&EVP_hpke_aes_128_gcm) kSupportedAEADs[] = {
&EVP_hpke_aes_128_gcm,
&EVP_hpke_aes_256_gcm,
&EVP_hpke_chacha20_poly1305,
};
static const EVP_HPKE_AEAD *get_ech_aead(uint16_t aead_id) {
for (const auto aead_func : kSupportedAEADs) {
const EVP_HPKE_AEAD *aead = aead_func();
if (aead_id == EVP_HPKE_AEAD_id(aead)) {
return aead;
}
}
return nullptr;
}
// ssl_client_hello_write_without_extensions serializes |client_hello| into
// |out|, omitting the length-prefixed extensions. It serializes individual
// fields, starting with |client_hello->version|, and ignores the
@ -316,18 +336,18 @@ bool ssl_client_hello_decrypt(
}
bool ECHServerConfig::Init(Span<const uint8_t> raw,
bool ECHServerConfig::Init(Span<const uint8_t> ech_config,
Span<const uint8_t> private_key,
bool is_retry_config) {
assert(!initialized_);
is_retry_config_ = is_retry_config;
if (!raw_.CopyFrom(raw)) {
if (!ech_config_.CopyFrom(ech_config)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
return false;
}
// Read from |raw_| so we can save Spans with the same lifetime as |this|.
CBS reader(raw_);
// Read from |ech_config_| so we can save Spans with the same lifetime as |this|.
CBS reader(ech_config_);
uint16_t version;
if (!CBS_get_u16(&reader, &version)) {
@ -386,12 +406,9 @@ bool ECHServerConfig::Init(Span<const uint8_t> raw,
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;
}
// This parser fails when it encounters any bytes it does not understand. If
// the config lists any unsupported cipher suites, that is a parse error.
if (kdf_id != EVP_HPKE_HKDF_SHA256 ||
(aead_id != EVP_HPKE_AEAD_AES_128_GCM &&
aead_id != EVP_HPKE_AEAD_AES_256_GCM &&
aead_id != EVP_HPKE_AEAD_CHACHA20POLY1305)) {
// The server promises to support every option in the ECHConfig, so reject
// any unsupported cipher suites.
if (kdf_id != EVP_HPKE_HKDF_SHA256 || get_ech_aead(aead_id) == nullptr) {
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG);
return false;
}
@ -414,10 +431,14 @@ bool ECHServerConfig::Init(Span<const uint8_t> raw,
return true;
}
bool ECHServerConfig::SupportsCipherSuite(uint16_t kdf_id,
uint16_t aead_id) const {
bool ECHServerConfig::SetupContext(EVP_HPKE_CTX *ctx, uint16_t kdf_id,
uint16_t aead_id,
Span<const uint8_t> enc) const {
assert(initialized_);
// Check the cipher suite is supported by this ECHServerConfig.
CBS cbs(cipher_suites_);
bool cipher_ok = false;
while (CBS_len(&cbs) != 0) {
uint16_t supported_kdf_id, supported_aead_id;
if (!CBS_get_u16(&cbs, &supported_kdf_id) ||
@ -425,10 +446,30 @@ bool ECHServerConfig::SupportsCipherSuite(uint16_t kdf_id,
return false;
}
if (kdf_id == supported_kdf_id && aead_id == supported_aead_id) {
return true;
cipher_ok = true;
break;
}
}
return false;
if (!cipher_ok) {
return false;
}
static const uint8_t kInfoLabel[] = "tls ech";
ScopedCBB info_cbb;
if (!CBB_init(info_cbb.get(), sizeof(kInfoLabel) + ech_config_.size()) ||
!CBB_add_bytes(info_cbb.get(), kInfoLabel,
sizeof(kInfoLabel) /* includes trailing NUL */) ||
!CBB_add_bytes(info_cbb.get(), ech_config_.data(), ech_config_.size())) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
return false;
}
assert(kdf_id == EVP_HPKE_HKDF_SHA256);
assert(get_ech_aead(aead_id) != NULL);
return EVP_HPKE_CTX_setup_base_r_x25519(
ctx, EVP_hpke_hkdf_sha256(), get_ech_aead(aead_id), enc.data(),
enc.size(), public_key_.data(), public_key_.size(), private_key_,
sizeof(private_key_), CBB_data(info_cbb.get()), CBB_len(info_cbb.get()));
}
BSSL_NAMESPACE_END

@ -621,35 +621,10 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
if (hs->ech_server_config_list) {
for (const ECHServerConfig &ech_config :
hs->ech_server_config_list->configs) {
// Skip this config if the client-provided config_id does not match or
// if the client indicated an unsupported HPKE ciphersuite.
if (config_id != ech_config.config_id() ||
!ech_config.SupportsCipherSuite(kdf_id, aead_id)) {
continue;
}
static const uint8_t kInfoLabel[] = "tls ech";
ScopedCBB info_cbb;
if (!CBB_init(info_cbb.get(),
sizeof(kInfoLabel) + ech_config.raw().size()) ||
!CBB_add_bytes(info_cbb.get(), kInfoLabel,
sizeof(kInfoLabel) /* includes trailing NUL */) ||
!CBB_add_bytes(info_cbb.get(), ech_config.raw().data(),
ech_config.raw().size())) {
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
return ssl_hs_error;
}
// Set up a fresh HPKE context for each decryption attempt.
hs->ech_hpke_ctx.Reset();
if (CBS_len(&enc) != X25519_PUBLIC_VALUE_LEN ||
!EVP_HPKE_CTX_setup_base_r_x25519(
hs->ech_hpke_ctx.get(), kdf_id, aead_id, CBS_data(&enc),
CBS_len(&enc), ech_config.public_key().data(),
ech_config.public_key().size(), ech_config.private_key().data(),
ech_config.private_key().size(), CBB_data(info_cbb.get()),
CBB_len(info_cbb.get()))) {
if (config_id != ech_config.config_id() ||
!ech_config.SetupContext(hs->ech_hpke_ctx.get(), kdf_id, aead_id,
enc)) {
// Ignore the error and try another ECHConfig.
ERR_clear_error();
continue;

@ -1443,33 +1443,27 @@ class ECHServerConfig {
bool Init(Span<const uint8_t> ech_config, Span<const uint8_t> private_key,
bool is_retry_config);
// SupportsCipherSuite returns true when this ECHConfig supports the HPKE
// ciphersuite composed of |kdf_id| and |aead_id|. This function must only be
// called on an initialized object.
bool SupportsCipherSuite(uint16_t kdf_id, uint16_t aead_id) const;
// SetupContext sets up |ctx| for a new connection, given the specified
// HPKE ciphersuite and encapsulated KEM key. It returns true on success and
// false on error. This function may only be called on an initialized object.
bool SetupContext(EVP_HPKE_CTX *ctx, uint16_t kdf_id, uint16_t aead_id,
Span<const uint8_t> enc) const;
Span<const uint8_t> raw() const {
Span<const uint8_t> ech_config() const {
assert(initialized_);
return raw_;
}
Span<const uint8_t> public_key() const {
assert(initialized_);
return public_key_;
}
Span<const uint8_t> private_key() const {
assert(initialized_);
return MakeConstSpan(private_key_, sizeof(private_key_));
return ech_config_;
}
bool is_retry_config() const {
assert(initialized_);
return is_retry_config_;
}
uint8_t config_id() const {
assert(initialized_);
return config_id_;
}
private:
Array<uint8_t> raw_;
Array<uint8_t> ech_config_;
Span<const uint8_t> public_key_;
Span<const uint8_t> cipher_suites_;

@ -1573,7 +1573,7 @@ TEST(SSLTest, ECHServerConfigListTruncatedPublicKey) {
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
MakeConstSpan(kECHConfigPublicKey, sizeof(kECHConfigPublicKey) - 1),
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM},
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
/*extensions=*/{}));
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
@ -1640,7 +1640,7 @@ TEST(SSLTest, UnsupportedECHConfig) {
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHConfigPublicKey,
std::vector<uint16_t>{0x002 /* HKDF-SHA384 */, EVP_HPKE_AEAD_AES_128_GCM},
std::vector<uint16_t>{0x002 /* HKDF-SHA384 */, EVP_HPKE_AES_128_GCM},
/*extensions=*/{}));
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
config_list.get(), /*is_retry_config=*/1, ech_config.data(),
@ -1660,7 +1660,7 @@ TEST(SSLTest, UnsupportedECHConfig) {
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<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM},
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
/*extensions=*/{}));
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
config_list.get(), /*is_retry_config=*/1, ech_config.data(),
@ -1670,7 +1670,7 @@ TEST(SSLTest, UnsupportedECHConfig) {
static const uint8_t kExtensions[] = {0x00, 0x01, 0x00, 0x00};
ASSERT_TRUE(MakeECHConfig(
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHConfigPublicKey,
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM},
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AES_128_GCM},
kExtensions));
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
config_list.get(), /*is_retry_config=*/1, ech_config.data(),

@ -618,13 +618,10 @@ static bool ext_ech_add_clienthello_grease(SSL_HANDSHAKE *hs, CBB *out) {
return true;
}
constexpr uint16_t kdf_id = EVP_HPKE_HKDF_SHA256;
const uint16_t aead_id = EVP_has_aes_hardware()
? EVP_HPKE_AEAD_AES_128_GCM
: EVP_HPKE_AEAD_CHACHA20POLY1305;
const EVP_AEAD *aead = EVP_HPKE_get_aead(aead_id);
assert(aead != nullptr);
const uint16_t kdf_id = EVP_HPKE_HKDF_SHA256;
const uint16_t aead_id = EVP_has_aes_hardware() ? EVP_HPKE_AES_128_GCM
: EVP_HPKE_CHACHA20_POLY1305;
constexpr size_t kAEADOverhead = 16; // Both AEADs have a 16-byte tag.
uint8_t ech_config_id;
RAND_bytes(&ech_config_id, 1);
@ -671,9 +668,9 @@ static bool ext_ech_add_clienthello_grease(SSL_HANDSHAKE *hs, CBB *out) {
// range of 96 to 192. Note that this estimate does not fully capture
// optional extensions like GREASE, but the rounding gives some leeway.
uint8_t payload[EVP_AEAD_MAX_OVERHEAD + 192];
uint8_t payload[kAEADOverhead + 192];
const size_t payload_len =
EVP_AEAD_max_overhead(aead) + 32 * random_size(96 / 32, 192 / 32);
kAEADOverhead + 32 * random_size(96 / 32, 192 / 32);
assert(payload_len <= sizeof(payload));
RAND_bytes(payload, payload_len);
@ -768,8 +765,8 @@ static bool ext_ech_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
if (!config.is_retry_config()) {
continue;
}
if (!CBB_add_bytes(&retry_configs, config.raw().data(),
config.raw().size())) {
if (!CBB_add_bytes(&retry_configs, config.ech_config().data(),
config.ech_config().size())) {
return false;
}
}

@ -636,10 +636,10 @@ static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
// Check that ClientECH.cipher_suite is unchanged and that
// ClientECH.enc is empty.
if (kdf_id != EVP_HPKE_CTX_get_kdf_id(hs->ech_hpke_ctx.get()) ||
aead_id != EVP_HPKE_CTX_get_aead_id(hs->ech_hpke_ctx.get()) ||
config_id != hs->ech_config_id ||
CBS_len(&enc) > 0) {
if (kdf_id != EVP_HPKE_KDF_id(EVP_HPKE_CTX_kdf(hs->ech_hpke_ctx.get())) ||
aead_id !=
EVP_HPKE_AEAD_id(EVP_HPKE_CTX_aead(hs->ech_hpke_ctx.get())) ||
config_id != hs->ech_config_id || CBS_len(&enc) > 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
return ssl_hs_error;

Loading…
Cancel
Save