Implement ECH draft 10 and update HPKE to draft 08.

Bug: 275

Change-Id: I8096070386af7d2b5020875ea09bcc0c04ebc8cd
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/47245
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: Steven Valdez <svaldez@google.com>
grpc-202302
Steven Valdez 4 years ago committed by CQ bot account: commit-bot@chromium.org
parent fe049e4d18
commit 94a63a5b6e
  1. 12
      crypto/hpke/hpke.c
  2. 6352
      crypto/hpke/hpke_test_vectors.txt
  3. 2
      crypto/hpke/internal.h
  4. 2
      crypto/hpke/test-vectors.json
  5. 4
      include/openssl/tls1.h
  6. 28
      ssl/encrypted_client_hello.cc
  7. 8
      ssl/handshake_server.cc
  8. 26
      ssl/internal.h
  9. 37
      ssl/ssl_test.cc
  10. 9
      ssl/t1_lib.cc
  11. 6
      ssl/test/runner/common.go
  12. 16
      ssl/test/runner/handshake_client.go
  13. 31
      ssl/test/runner/handshake_messages.go
  14. 2
      ssl/test/runner/hpke/hpke.go
  15. 6
      ssl/test/runner/hpke/kem.go
  16. 2
      ssl/test/runner/hpke/testdata/test-vectors.json
  17. 26
      ssl/test/runner/runner.go
  18. 10
      ssl/tls13_server.cc

@ -28,7 +28,7 @@
#include "internal.h"
// This file implements draft-irtf-cfrg-hpke-07.
// This file implements draft-irtf-cfrg-hpke-08.
#define KEM_CONTEXT_LEN (2 * X25519_PUBLIC_VALUE_LEN)
@ -38,7 +38,7 @@
#define HPKE_MODE_BASE 0
#define HPKE_MODE_PSK 1
static const char kHpkeRfcId[] = "HPKE-07";
static const char kHpkeVersionId[] = "HPKE-v1";
static int add_label_string(CBB *cbb, const char *label) {
return CBB_add_bytes(cbb, (const uint8_t *)label, strlen(label));
@ -70,10 +70,10 @@ static int hpke_labeled_extract(const EVP_MD *hkdf_md, uint8_t *out_key,
size_t salt_len, const uint8_t *suite_id,
size_t suite_id_len, const char *label,
const uint8_t *ikm, size_t ikm_len) {
// labeledIKM = concat("RFCXXXX ", suite_id, label, IKM)
// labeledIKM = concat("HPKE-v1", suite_id, label, IKM)
CBB labeled_ikm;
int ok = CBB_init(&labeled_ikm, 0) &&
add_label_string(&labeled_ikm, kHpkeRfcId) &&
add_label_string(&labeled_ikm, kHpkeVersionId) &&
CBB_add_bytes(&labeled_ikm, suite_id, suite_id_len) &&
add_label_string(&labeled_ikm, label) &&
CBB_add_bytes(&labeled_ikm, ikm, ikm_len) &&
@ -88,11 +88,11 @@ static int hpke_labeled_expand(const EVP_MD *hkdf_md, uint8_t *out_key,
size_t prk_len, const uint8_t *suite_id,
size_t suite_id_len, const char *label,
const uint8_t *info, size_t info_len) {
// labeledInfo = concat(I2OSP(L, 2), "RFCXXXX ", suite_id, label, info)
// labeledInfo = concat(I2OSP(L, 2), "HPKE-v1", suite_id, label, info)
CBB labeled_info;
int ok = CBB_init(&labeled_info, 0) &&
CBB_add_u16(&labeled_info, out_len) &&
add_label_string(&labeled_info, kHpkeRfcId) &&
add_label_string(&labeled_info, kHpkeVersionId) &&
CBB_add_bytes(&labeled_info, suite_id, suite_id_len) &&
add_label_string(&labeled_info, label) &&
CBB_add_bytes(&labeled_info, info, info_len) &&

File diff suppressed because it is too large Load Diff

@ -31,7 +31,7 @@ extern "C" {
// receiver with a public key. Optionally, the sender may authenticate its
// possession of a pre-shared key to the recipient.
//
// See https://tools.ietf.org/html/draft-irtf-cfrg-hpke-07.
// 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

File diff suppressed because one or more lines are too long

@ -253,9 +253,9 @@ extern "C" {
// extension number.
#define TLSEXT_TYPE_application_settings 17513
// ExtensionType values from draft-ietf-tls-esni-09. This is not an IANA defined
// ExtensionType values from draft-ietf-tls-esni-10. This is not an IANA defined
// extension number.
#define TLSEXT_TYPE_encrypted_client_hello 0xfe09
#define TLSEXT_TYPE_encrypted_client_hello 0xfe0a
#define TLSEXT_TYPE_ech_is_inner 0xda09
#define TLSEXT_TYPE_ech_outer_extensions 0xfd00

@ -248,19 +248,18 @@ bool ssl_decode_client_hello_inner(
bool ssl_client_hello_decrypt(
EVP_HPKE_CTX *hpke_ctx, Array<uint8_t> *out_encoded_client_hello_inner,
bool *out_is_decrypt_error, const SSL_CLIENT_HELLO *client_hello_outer,
uint16_t kdf_id, uint16_t aead_id, Span<const uint8_t> config_id,
uint16_t kdf_id, uint16_t aead_id, const uint8_t config_id,
Span<const uint8_t> enc, Span<const uint8_t> payload) {
*out_is_decrypt_error = false;
// Compute the ClientHello portion of the ClientHelloOuterAAD value. See
// draft-ietf-tls-esni-09, section 5.2.
ScopedCBB ch_outer_aad_cbb;
CBB config_id_cbb, enc_cbb, outer_hello_cbb, extensions_cbb;
CBB enc_cbb, outer_hello_cbb, extensions_cbb;
if (!CBB_init(ch_outer_aad_cbb.get(), 0) ||
!CBB_add_u16(ch_outer_aad_cbb.get(), kdf_id) ||
!CBB_add_u16(ch_outer_aad_cbb.get(), aead_id) ||
!CBB_add_u8_length_prefixed(ch_outer_aad_cbb.get(), &config_id_cbb) ||
!CBB_add_bytes(&config_id_cbb, config_id.data(), config_id.size()) ||
!CBB_add_u8(ch_outer_aad_cbb.get(), config_id) ||
!CBB_add_u16_length_prefixed(ch_outer_aad_cbb.get(), &enc_cbb) ||
!CBB_add_bytes(&enc_cbb, enc.data(), enc.size()) ||
!CBB_add_u24_length_prefixed(ch_outer_aad_cbb.get(), &outer_hello_cbb) ||
@ -346,22 +345,25 @@ bool ECHServerConfig::Init(Span<const uint8_t> raw,
}
CBS ech_config_contents, public_name, public_key, cipher_suites, extensions;
uint8_t config_id;
uint16_t kem_id, max_name_len;
if (!CBS_get_u16_length_prefixed(&reader, &ech_config_contents) ||
!CBS_get_u16_length_prefixed(&ech_config_contents, &public_name) ||
CBS_len(&public_name) == 0 ||
!CBS_get_u8(&ech_config_contents, &config_id) ||
!CBS_get_u16(&ech_config_contents, &kem_id) ||
!CBS_get_u16_length_prefixed(&ech_config_contents, &public_key) ||
CBS_len(&public_key) == 0 ||
!CBS_get_u16(&ech_config_contents, &kem_id) ||
!CBS_get_u16_length_prefixed(&ech_config_contents, &cipher_suites) ||
CBS_len(&cipher_suites) == 0 ||
!CBS_get_u16(&ech_config_contents, &max_name_len) ||
!CBS_get_u16_length_prefixed(&ech_config_contents, &public_name) ||
CBS_len(&public_name) == 0 ||
!CBS_get_u16_length_prefixed(&ech_config_contents, &extensions) ||
CBS_len(&ech_config_contents) != 0 || //
CBS_len(&reader) != 0) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;
}
config_id_ = config_id;
// We only support one KEM, and the KEM decides the length of |public_key|.
if (CBS_len(&public_key) != X25519_PUBLIC_VALUE_LEN ||
kem_id != EVP_HPKE_DHKEM_X25519_HKDF_SHA256) {
@ -395,18 +397,6 @@ bool ECHServerConfig::Init(Span<const uint8_t> raw,
}
}
// Precompute the config_id.
uint8_t key[EVP_MAX_KEY_LENGTH];
size_t key_len;
static const uint8_t kInfo[] = "tls ech config id";
if (!HKDF_extract(key, &key_len, EVP_sha256(), raw_.data(), raw_.size(),
nullptr, 0) ||
!HKDF_expand(config_id_sha256_, sizeof(config_id_sha256_), EVP_sha256(),
key, key_len, kInfo, OPENSSL_ARRAY_SIZE(kInfo) - 1)) {
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
return false;
}
if (private_key.size() != X25519_PRIVATE_KEY_LEN) {
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
return false;

@ -599,11 +599,12 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
}
// Parse a ClientECH out of the extension body.
uint8_t config_id;
uint16_t kdf_id, aead_id;
CBS config_id, enc, payload;
CBS enc, payload;
if (!CBS_get_u16(&ech_body, &kdf_id) || //
!CBS_get_u16(&ech_body, &aead_id) ||
!CBS_get_u8_length_prefixed(&ech_body, &config_id) ||
!CBS_get_u8(&ech_body, &config_id) ||
!CBS_get_u16_length_prefixed(&ech_body, &enc) ||
!CBS_get_u16_length_prefixed(&ech_body, &payload) ||
CBS_len(&ech_body) != 0) {
@ -622,7 +623,7 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
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_sha256() ||
if (config_id != ech_config.config_id() ||
!ech_config.SupportsCipherSuite(kdf_id, aead_id)) {
continue;
}
@ -686,6 +687,7 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
return ssl_hs_error;
}
hs->ech_config_id = config_id;
hs->ech_accept = true;
break;
}

@ -1460,14 +1460,13 @@ class ECHServerConfig {
assert(initialized_);
return MakeConstSpan(private_key_, sizeof(private_key_));
}
Span<const uint8_t> config_id_sha256() const {
assert(initialized_);
return MakeConstSpan(config_id_sha256_, sizeof(config_id_sha256_));
}
bool is_retry_config() const {
assert(initialized_);
return is_retry_config_;
}
uint8_t config_id() const {
return config_id_;
}
private:
Array<uint8_t> raw_;
@ -1479,9 +1478,7 @@ class ECHServerConfig {
// X25519 private key.
uint8_t private_key_[X25519_PRIVATE_KEY_LEN];
// config_id_ stores the precomputed result of |ConfigID| for
// |EVP_HPKE_HKDF_SHA256|.
uint8_t config_id_sha256_[8];
uint8_t config_id_;
bool is_retry_config_ : 1;
bool initialized_ : 1;
@ -1504,11 +1501,13 @@ OPENSSL_EXPORT bool ssl_decode_client_hello_inner(
// otherwise, regardless of whether the decrypt was successful. It sets
// |out_encoded_client_hello_inner| to true if the decryption fails, and false
// otherwise.
bool ssl_client_hello_decrypt(
EVP_HPKE_CTX *hpke_ctx, Array<uint8_t> *out_encoded_client_hello_inner,
bool *out_is_decrypt_error, const SSL_CLIENT_HELLO *client_hello_outer,
uint16_t kdf_id, uint16_t aead_id, Span<const uint8_t> config_id,
Span<const uint8_t> enc, Span<const uint8_t> payload);
bool ssl_client_hello_decrypt(EVP_HPKE_CTX *hpke_ctx,
Array<uint8_t> *out_encoded_client_hello_inner,
bool *out_is_decrypt_error,
const SSL_CLIENT_HELLO *client_hello_outer,
uint16_t kdf_id, uint16_t aead_id,
uint8_t config_id, Span<const uint8_t> enc,
Span<const uint8_t> payload);
// tls13_ech_accept_confirmation computes the server's ECH acceptance signal,
// writing it to |out|. It returns true on success, and false on failure.
@ -1982,6 +1981,9 @@ struct SSL_HANDSHAKE {
// record layer.
uint16_t early_data_written = 0;
// ech_config_id is the ECH config sent by the client.
uint8_t ech_config_id = 0;
// session_id is the session ID in the ClientHello.
uint8_t session_id[SSL_MAX_SSL_SESSION_ID_LENGTH] = {0};
uint8_t session_id_len = 0;

@ -1453,22 +1453,24 @@ TEST(SSLTest, AddClientCA) {
// kECHConfig contains a serialized ECHConfig value.
static const uint8_t kECHConfig[] = {
// version
0xfe, 0x09,
0xfe, 0x0a,
// length
0x00, 0x42,
// contents.public_name
0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65,
0x00, 0x43,
// contents.config_id
0x42,
// contents.kem_id
0x00, 0x20,
// contents.public_key
0x00, 0x20, 0xa6, 0x9a, 0x41, 0x48, 0x5d, 0x32, 0x96, 0xa4, 0xe0, 0xc3,
0x6a, 0xee, 0xf6, 0x63, 0x0f, 0x59, 0x32, 0x6f, 0xdc, 0xff, 0x81, 0x29,
0x59, 0xa5, 0x85, 0xd3, 0x9b, 0x3b, 0xde, 0x98, 0x55, 0x5c,
// contents.kem_id
0x00, 0x20,
// contents.cipher_suites
0x00, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x03,
// contents.maximum_name_length
0x00, 0x10,
// contents.public_name
0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65,
// contents.extensions
0x00, 0x00};
@ -1488,8 +1490,8 @@ static const uint8_t kECHConfigPrivateKey[X25519_PRIVATE_KEY_LEN] = {
// MakeECHConfig serializes an ECHConfig and writes it to |*out| with the
// specified parameters. |cipher_suites| is a list of code points which should
// contain pairs of KDF and AEAD IDs.
bool MakeECHConfig(std::vector<uint8_t> *out, uint16_t kem_id,
Span<const uint8_t> public_key,
bool MakeECHConfig(std::vector<uint8_t> *out, uint8_t config_id,
uint16_t kem_id, Span<const uint8_t> public_key,
Span<const uint16_t> cipher_suites,
Span<const uint8_t> extensions) {
bssl::ScopedCBB cbb;
@ -1498,12 +1500,10 @@ bool MakeECHConfig(std::vector<uint8_t> *out, uint16_t kem_id,
if (!CBB_init(cbb.get(), 64) ||
!CBB_add_u16(cbb.get(), TLSEXT_TYPE_encrypted_client_hello) ||
!CBB_add_u16_length_prefixed(cbb.get(), &contents) ||
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, reinterpret_cast<const uint8_t *>(kPublicName),
strlen(kPublicName)) ||
!CBB_add_u8(&contents, config_id) ||
!CBB_add_u16(&contents, kem_id) ||
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, public_key.data(), public_key.size()) ||
!CBB_add_u16(&contents, kem_id) ||
!CBB_add_u16_length_prefixed(&contents, &child)) {
return false;
}
@ -1514,6 +1514,9 @@ bool MakeECHConfig(std::vector<uint8_t> *out, uint16_t kem_id,
}
if (!CBB_add_u16(&contents, strlen(kPublicName)) || // maximum_name_length
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, reinterpret_cast<const uint8_t *>(kPublicName),
strlen(kPublicName)) ||
!CBB_add_u16_length_prefixed(&contents, &child) ||
!CBB_add_bytes(&child, extensions.data(), extensions.size()) ||
!CBB_flush(cbb.get())) {
return false;
@ -1568,7 +1571,7 @@ TEST(SSLTest, ECHServerConfigList) {
TEST(SSLTest, ECHServerConfigListTruncatedPublicKey) {
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(
&ech_config, EVP_HPKE_DHKEM_X25519_HKDF_SHA256,
&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},
/*extensions=*/{}));
@ -1636,7 +1639,7 @@ TEST(SSLTest, UnsupportedECHConfig) {
// Unsupported cipher suites are rejected. (We only support HKDF-SHA256.)
std::vector<uint8_t> ech_config;
ASSERT_TRUE(MakeECHConfig(
&ech_config, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHConfigPublicKey,
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHConfigPublicKey,
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA384, EVP_HPKE_AEAD_AES_128_GCM},
/*extensions=*/{}));
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
@ -1656,7 +1659,7 @@ TEST(SSLTest, UnsupportedECHConfig) {
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, 0x0010 /* DHKEM(P-256, HKDF-SHA256) */, kP256PublicKey,
&ech_config, 0x42, 0x0010 /* DHKEM(P-256, HKDF-SHA256) */, kP256PublicKey,
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM},
/*extensions=*/{}));
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
@ -1666,7 +1669,7 @@ TEST(SSLTest, UnsupportedECHConfig) {
// Unsupported extensions are rejected.
static const uint8_t kExtensions[] = {0x00, 0x01, 0x00, 0x00};
ASSERT_TRUE(MakeECHConfig(
&ech_config, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHConfigPublicKey,
&ech_config, 0x42, EVP_HPKE_DHKEM_X25519_HKDF_SHA256, kECHConfigPublicKey,
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM},
kExtensions));
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(

@ -625,8 +625,8 @@ static bool ext_ech_add_clienthello_grease(SSL_HANDSHAKE *hs, CBB *out) {
const EVP_AEAD *aead = EVP_HPKE_get_aead(aead_id);
assert(aead != nullptr);
uint8_t ech_config_id[8];
RAND_bytes(ech_config_id, sizeof(ech_config_id));
uint8_t ech_config_id;
RAND_bytes(&ech_config_id, 1);
uint8_t ech_enc[X25519_PUBLIC_VALUE_LEN];
uint8_t private_key_unused[X25519_PRIVATE_KEY_LEN];
@ -678,13 +678,12 @@ static bool ext_ech_add_clienthello_grease(SSL_HANDSHAKE *hs, CBB *out) {
RAND_bytes(payload, payload_len);
// Inside the TLS extension contents, write a serialized ClientEncryptedCH.
CBB ech_body, config_id_cbb, enc_cbb, payload_cbb;
CBB ech_body, enc_cbb, payload_cbb;
if (!CBB_add_u16(out, TLSEXT_TYPE_encrypted_client_hello) ||
!CBB_add_u16_length_prefixed(out, &ech_body) ||
!CBB_add_u16(&ech_body, kdf_id) || //
!CBB_add_u16(&ech_body, aead_id) ||
!CBB_add_u8_length_prefixed(&ech_body, &config_id_cbb) ||
!CBB_add_bytes(&config_id_cbb, ech_config_id, sizeof(ech_config_id)) ||
!CBB_add_u8(&ech_body, ech_config_id) ||
!CBB_add_u16_length_prefixed(&ech_body, &enc_cbb) ||
!CBB_add_bytes(&enc_cbb, ech_enc, OPENSSL_ARRAY_SIZE(ech_enc)) ||
!CBB_add_u16_length_prefixed(&ech_body, &payload_cbb) ||

@ -129,7 +129,7 @@ const (
extensionChannelID uint16 = 30032 // not IANA assigned
extensionDelegatedCredentials uint16 = 0x22 // draft-ietf-tls-subcerts-06
extensionDuplicate uint16 = 0xffff // not IANA assigned
extensionEncryptedClientHello uint16 = 0xfe09 // not IANA assigned
extensionEncryptedClientHello uint16 = 0xfe0a // not IANA assigned
extensionECHIsInner uint16 = 0xda09 // not IANA assigned
extensionECHOuterExtensions uint16 = 0xfd00 // not IANA assigned
)
@ -922,6 +922,10 @@ type ProtocolBugs struct {
// incorrectly encrypt the second encrypted_client_hello extension.
CorruptSecondEncryptedClientHello bool
// CorruptSecondEncryptedClientHelloConfigID, if true, causes the client to
// incorrectly set the second ClientHello's ECH config ID.
CorruptSecondEncryptedClientHelloConfigID bool
// AllowTLS12InClientHelloInner, if true, causes the client to include
// TLS 1.2 and earlier in ClientHelloInner.
AllowTLS12InClientHelloInner bool

@ -819,11 +819,7 @@ NextCipherSuite:
}
if innerHello != nil {
hash, err := hpke.GetHKDFHash(hs.echHPKEContext.KDF())
if err != nil {
return nil, err
}
if err := hs.encryptClientHello(hello, innerHello, c.config.ClientECHConfig.configID(hash), echEnc); err != nil {
if err := hs.encryptClientHello(hello, innerHello, c.config.ClientECHConfig.ConfigID, echEnc); err != nil {
return nil, err
}
if c.config.Bugs.CorruptEncryptedClientHello {
@ -858,7 +854,7 @@ NextCipherSuite:
// encryptClientHello encrypts |innerHello| using the specified HPKE context and
// adds the extension to |hello|.
func (hs *clientHandshakeState) encryptClientHello(hello, innerHello *clientHelloMsg, configID, enc []byte) error {
func (hs *clientHandshakeState) encryptClientHello(hello, innerHello *clientHelloMsg, configID uint8, enc []byte) error {
c := hs.c
if c.config.Bugs.MinimalClientHelloOuter {
@ -878,7 +874,7 @@ func (hs *clientHandshakeState) encryptClientHello(hello, innerHello *clientHell
aad := newByteBuilder()
aad.addU16(hs.echHPKEContext.KDF())
aad.addU16(hs.echHPKEContext.AEAD())
aad.addU8LengthPrefixed().addBytes(configID)
aad.addU8(configID)
aad.addU16LengthPrefixed().addBytes(enc)
hello.marshalForOuterAAD(aad.addU24LengthPrefixed())
@ -1521,7 +1517,11 @@ func (hs *clientHandshakeState) applyHelloRetryRequest(helloRetryRequest *helloR
if c.config.Bugs.OmitSecondEncryptedClientHello {
hello.clientECH = nil
} else {
if err := hs.encryptClientHello(hello, innerHello, nil, nil); err != nil {
configID := c.config.ClientECHConfig.ConfigID
if c.config.Bugs.CorruptSecondEncryptedClientHelloConfigID {
configID ^= 1
}
if err := hs.encryptClientHello(hello, innerHello, configID, nil); err != nil {
return err
}
if c.config.Bugs.CorruptSecondEncryptedClientHello {

@ -5,11 +5,8 @@
package runner
import (
"crypto"
"encoding/binary"
"fmt"
"golang.org/x/crypto/hkdf"
)
func writeLen(buf []byte, v, size int) {
@ -258,11 +255,12 @@ type HPKECipherSuite struct {
}
type ECHConfig struct {
PublicName string
PublicKey []byte
ConfigID uint8
KEM uint16
CipherSuites []HPKECipherSuite
PublicKey []byte
MaxNameLen uint16
PublicName string
CipherSuites []HPKECipherSuite
}
func (e *ECHConfig) marshal(bb *byteBuilder) {
@ -270,15 +268,16 @@ func (e *ECHConfig) marshal(bb *byteBuilder) {
// codepoint as a version identifier.
bb.addU16(extensionEncryptedClientHello)
contents := bb.addU16LengthPrefixed()
contents.addU16LengthPrefixed().addBytes([]byte(e.PublicName))
contents.addU16LengthPrefixed().addBytes(e.PublicKey)
contents.addU8(e.ConfigID)
contents.addU16(e.KEM)
contents.addU16LengthPrefixed().addBytes(e.PublicKey)
cipherSuites := contents.addU16LengthPrefixed()
for _, suite := range e.CipherSuites {
cipherSuites.addU16(suite.KDF)
cipherSuites.addU16(suite.AEAD)
}
contents.addU16(e.MaxNameLen)
contents.addU16LengthPrefixed().addBytes([]byte(e.PublicName))
contents.addU16(0) // Empty extensions field
}
@ -297,22 +296,12 @@ func MarshalECHConfigList(configs ...*ECHConfig) []byte {
return bb.finish()
}
func (e *ECHConfig) configID(h crypto.Hash) []byte {
configIDLength := 8
idReader := hkdf.Expand(h.New, hkdf.Extract(h.New, MarshalECHConfig(e), nil), []byte("tls ech config id"))
idBytes := make([]byte, configIDLength)
if n, err := idReader.Read(idBytes); err != nil || n != configIDLength {
panic("failed to compute configID for ECHConfig")
}
return idBytes
}
// The contents of a CH "encrypted_client_hello" extension.
// https://tools.ietf.org/html/draft-ietf-tls-esni-09
type clientECH struct {
hpkeKDF uint16
hpkeAEAD uint16
configID []byte
configID uint8
enc []byte
payload []byte
}
@ -459,7 +448,7 @@ func (m *clientHelloMsg) marshalBody(hello *byteBuilder, typ clientHelloType) {
body := newByteBuilder()
body.addU16(m.clientECH.hpkeKDF)
body.addU16(m.clientECH.hpkeAEAD)
body.addU8LengthPrefixed().addBytes(m.clientECH.configID)
body.addU8(m.clientECH.configID)
body.addU16LengthPrefixed().addBytes(m.clientECH.enc)
body.addU16LengthPrefixed().addBytes(m.clientECH.payload)
@ -937,7 +926,7 @@ func (m *clientHelloMsg) unmarshal(data []byte) bool {
var ech clientECH
if !body.readU16(&ech.hpkeKDF) ||
!body.readU16(&ech.hpkeAEAD) ||
!body.readU8LengthPrefixedBytes(&ech.configID) ||
!body.readU8(&ech.configID) ||
!body.readU16LengthPrefixedBytes(&ech.enc) ||
len(ech.enc) == 0 ||
!body.readU16LengthPrefixedBytes(&ech.payload) ||

@ -14,7 +14,7 @@
// Package hpke implements Hybrid Public Key Encryption (HPKE).
//
// See https://tools.ietf.org/html/draft-irtf-cfrg-hpke-07.
// See https://tools.ietf.org/html/draft-irtf-cfrg-hpke-08.
package hpke
import (

@ -23,7 +23,7 @@ import (
)
const (
rfcLabel string = "HPKE-07"
versionLabel string = "HPKE-v1"
)
func getKDFHash(kdfID uint16) crypto.Hash {
@ -40,7 +40,7 @@ func getKDFHash(kdfID uint16) crypto.Hash {
func labeledExtract(kdfHash crypto.Hash, salt, suiteID, label, ikm []byte) []byte {
var labeledIKM []byte
labeledIKM = append(labeledIKM, rfcLabel...)
labeledIKM = append(labeledIKM, versionLabel...)
labeledIKM = append(labeledIKM, suiteID...)
labeledIKM = append(labeledIKM, label...)
labeledIKM = append(labeledIKM, ikm...)
@ -55,7 +55,7 @@ func labeledExpand(kdfHash crypto.Hash, prk, suiteID, label, info []byte, length
var labeledInfo []byte
labeledInfo = appendBigEndianUint16(labeledInfo, lengthU16)
labeledInfo = append(labeledInfo, rfcLabel...)
labeledInfo = append(labeledInfo, versionLabel...)
labeledInfo = append(labeledInfo, suiteID...)
labeledInfo = append(labeledInfo, label...)
labeledInfo = append(labeledInfo, info...)

File diff suppressed because one or more lines are too long

@ -16554,6 +16554,7 @@ func generateECHConfigWithSecretKey(publicName string, ciphers []HPKECipherSuite
}
}
result := ECHConfig{
ConfigID: 42,
PublicName: publicName,
PublicKey: publicKeyR,
KEM: hpke.X25519WithHKDFSHA256,
@ -17048,6 +17049,30 @@ func addEncryptedClientHelloTests() {
expectedLocalError: "remote error: missing extension",
})
// Test that the server treats a mismatched config ID in the second ClientHello as fatal.
testCases = append(testCases, testCase{
testType: serverTest,
protocol: protocol,
name: prefix + "ECH-Server-DifferentConfigIDSecondClientHello",
config: Config{
ServerName: "secret.example",
ClientECHConfig: publicECHConfig,
// Force a HelloRetryRequest.
DefaultCurves: []CurveID{},
Bugs: ProtocolBugs{
CorruptSecondEncryptedClientHelloConfigID: true,
},
},
flags: []string{
"-ech-server-config", base64.StdEncoding.EncodeToString(MarshalECHConfig(publicECHConfig)),
"-ech-server-key", base64.StdEncoding.EncodeToString(secretKey),
"-ech-is-retry-config", "1",
},
shouldFail: true,
expectedError: ":DECODE_ERROR:",
expectedLocalError: "remote error: illegal parameter",
})
// Test early data works with ECH, in both accept and reject cases.
testCases = append(testCases, testCase{
testType: serverTest,
@ -17145,6 +17170,7 @@ func addEncryptedClientHelloTests() {
})
retryConfigValid := ECHConfig{
ConfigID: 42,
PublicName: "example.com",
// A real X25519 public key obtained from hpke.GenerateKeyPair().
PublicKey: []byte{

@ -621,10 +621,11 @@ static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
// Parse a ClientECH out of the extension body.
uint16_t kdf_id, aead_id;
CBS config_id, enc, payload;
uint8_t config_id;
CBS enc, payload;
if (!CBS_get_u16(&ech_body, &kdf_id) || //
!CBS_get_u16(&ech_body, &aead_id) ||
!CBS_get_u8_length_prefixed(&ech_body, &config_id) ||
!CBS_get_u8(&ech_body, &config_id) ||
!CBS_get_u16_length_prefixed(&ech_body, &enc) ||
!CBS_get_u16_length_prefixed(&ech_body, &payload) ||
CBS_len(&ech_body) != 0) {
@ -634,10 +635,11 @@ static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
}
// Check that ClientECH.cipher_suite is unchanged and that
// ClientECH.config_id and ClientECH.enc are empty.
// 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()) ||
CBS_len(&config_id) > 0 || CBS_len(&enc) > 0) {
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