Add ECH server (draft-ietf-tls-esni-09).
This CL adds an initial implementation of the ECH server, with pieces of the client in BoGo as necessary for testing. In particular, the server supports ClientHelloInner compression with ech_outer_extensions. When ECH decryption fails, it can send retry_configs back to the client. This server passes the "ech-accept" and "ech-reject" test cases in tls-interop-runner[0] when tested against both the cloudflare-go and nss clients. For reproducibility, I started with the main branch at commit 707604c262d8bcf3e944ed1d5a675077304732ce and updated the endpoint's script to pass the server's ECHConfig and private key to the boringssl tool. Follow-up CLs will update HPKE to the latest draft and catch us up to draft-10. [0]: https://github.com/xvzcf/tls-interop-runner Bug: 275 Change-Id: I49be35af46d1fd5dd9c62252f07d0bae179381ab Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/45285 Reviewed-by: David Benjamin <davidben@google.com> Commit-Queue: David Benjamin <davidben@google.com>
This commit is contained in:
parent
61d5aabc06
commit
00e434d67e
@ -58,6 +58,9 @@ SSL,264,DUPLICATE_KEY_SHARE
|
||||
SSL,296,DUPLICATE_SIGNATURE_ALGORITHM
|
||||
SSL,283,EARLY_DATA_NOT_IN_USE
|
||||
SSL,144,ECC_CERT_NOT_FOR_SIGNING
|
||||
SSL,310,ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH
|
||||
SSL,311,ECH_SERVER_CONFIG_UNSUPPORTED_EXTENSION
|
||||
SSL,313,ECH_SERVER_WOULD_HAVE_NO_RETRY_CONFIGS
|
||||
SSL,282,EMPTY_HELLO_RETRY_REQUEST
|
||||
SSL,145,EMS_STATE_INCONSISTENT
|
||||
SSL,146,ENCRYPTED_LENGTH_TOO_LONG
|
||||
@ -76,6 +79,7 @@ SSL,156,HTTP_REQUEST
|
||||
SSL,157,INAPPROPRIATE_FALLBACK
|
||||
SSL,303,INCONSISTENT_CLIENT_HELLO
|
||||
SSL,259,INVALID_ALPN_PROTOCOL
|
||||
SSL,314,INVALID_CLIENT_HELLO_INNER
|
||||
SSL,158,INVALID_COMMAND
|
||||
SSL,256,INVALID_COMPRESSION_LIST
|
||||
SSL,301,INVALID_DELEGATED_CREDENTIAL
|
||||
@ -226,6 +230,7 @@ SSL,235,UNKNOWN_STATE
|
||||
SSL,236,UNSAFE_LEGACY_RENEGOTIATION_DISABLED
|
||||
SSL,237,UNSUPPORTED_CIPHER
|
||||
SSL,238,UNSUPPORTED_COMPRESSION_ALGORITHM
|
||||
SSL,312,UNSUPPORTED_ECH_SERVER_CONFIG
|
||||
SSL,239,UNSUPPORTED_ELLIPTIC_CURVE
|
||||
SSL,240,UNSUPPORTED_PROTOCOL
|
||||
SSL,252,UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY
|
||||
|
@ -32,9 +32,6 @@
|
||||
|
||||
#define KEM_CONTEXT_LEN (2 * X25519_PUBLIC_VALUE_LEN)
|
||||
|
||||
// HPKE KEM scheme IDs.
|
||||
#define HPKE_DHKEM_X25519_HKDF_SHA256 0x0020
|
||||
|
||||
// This is strlen("HPKE") + 3 * sizeof(uint16_t).
|
||||
#define HPKE_SUITE_ID_LEN 10
|
||||
|
||||
@ -51,8 +48,8 @@ static int add_label_string(CBB *cbb, const char *label) {
|
||||
// that the suite_id used outside of the KEM also includes the kdf_id and
|
||||
// aead_id.
|
||||
static const uint8_t kX25519SuiteID[] = {
|
||||
'K', 'E', 'M', HPKE_DHKEM_X25519_HKDF_SHA256 >> 8,
|
||||
HPKE_DHKEM_X25519_HKDF_SHA256 & 0x00ff};
|
||||
'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)).
|
||||
@ -61,7 +58,7 @@ static int hpke_build_suite_id(uint8_t out[HPKE_SUITE_ID_LEN], uint16_t kdf_id,
|
||||
CBB cbb;
|
||||
int ret = CBB_init_fixed(&cbb, out, HPKE_SUITE_ID_LEN) &&
|
||||
add_label_string(&cbb, "HPKE") &&
|
||||
CBB_add_u16(&cbb, HPKE_DHKEM_X25519_HKDF_SHA256) &&
|
||||
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);
|
||||
@ -126,6 +123,14 @@ static int hpke_extract_and_expand(const EVP_MD *hkdf_md, uint8_t *out_key,
|
||||
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;
|
||||
}
|
||||
|
||||
const EVP_AEAD *EVP_HPKE_get_aead(uint16_t aead_id) {
|
||||
switch (aead_id) {
|
||||
case EVP_HPKE_AEAD_AES_128_GCM:
|
||||
|
@ -33,6 +33,9 @@ extern "C" {
|
||||
//
|
||||
// See https://tools.ietf.org/html/draft-irtf-cfrg-hpke-07.
|
||||
|
||||
// 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
|
||||
@ -224,6 +227,16 @@ OPENSSL_EXPORT int EVP_HPKE_CTX_export(const EVP_HPKE_CTX *hpke, uint8_t *out,
|
||||
// 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_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_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);
|
||||
|
@ -29,3 +29,4 @@ fuzzer(dtls_server ssl)
|
||||
fuzzer(dtls_client ssl)
|
||||
fuzzer(ssl_ctx_api ssl)
|
||||
fuzzer(session ssl)
|
||||
fuzzer(decode_client_hello_inner ssl)
|
||||
|
52
fuzz/decode_client_hello_inner.cc
Normal file
52
fuzz/decode_client_hello_inner.cc
Normal file
@ -0,0 +1,52 @@
|
||||
/* Copyright (c) 2021, Google Inc.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||
|
||||
#include <openssl/bytestring.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/span.h>
|
||||
|
||||
#include "../ssl/internal.h"
|
||||
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
|
||||
static bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
|
||||
static bssl::UniquePtr<SSL> ssl(SSL_new(ctx.get()));
|
||||
|
||||
CBS reader(bssl::MakeConstSpan(buf, len));
|
||||
CBS encoded_client_hello_inner_cbs;
|
||||
|
||||
if (!CBS_get_u24_length_prefixed(&reader, &encoded_client_hello_inner_cbs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bssl::Array<uint8_t> encoded_client_hello_inner;
|
||||
if (!encoded_client_hello_inner.CopyFrom(encoded_client_hello_inner_cbs)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use the remaining bytes in |reader| as the ClientHelloOuter.
|
||||
SSL_CLIENT_HELLO client_hello_outer;
|
||||
if (!bssl::ssl_client_hello_init(ssl.get(), &client_hello_outer, reader)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Recover the ClientHelloInner from the EncodedClientHelloInner and
|
||||
// ClientHelloOuter.
|
||||
uint8_t alert_unused;
|
||||
bssl::Array<uint8_t> client_hello_inner;
|
||||
bssl::ssl_decode_client_hello_inner(
|
||||
ssl.get(), &alert_unused, &client_hello_inner, encoded_client_hello_inner,
|
||||
&client_hello_outer);
|
||||
return 0;
|
||||
}
|
@ -431,6 +431,7 @@ typedef struct spake2_ctx_st SPAKE2_CTX;
|
||||
typedef struct srtp_protection_profile_st SRTP_PROTECTION_PROFILE;
|
||||
typedef struct ssl_cipher_st SSL_CIPHER;
|
||||
typedef struct ssl_ctx_st SSL_CTX;
|
||||
typedef struct ssl_ech_server_config_list_st SSL_ECH_SERVER_CONFIG_LIST;
|
||||
typedef struct ssl_method_st SSL_METHOD;
|
||||
typedef struct ssl_private_key_method_st SSL_PRIVATE_KEY_METHOD;
|
||||
typedef struct ssl_quic_method_st SSL_QUIC_METHOD;
|
||||
|
@ -3575,7 +3575,7 @@ OPENSSL_EXPORT const char *SSL_early_data_reason_string(
|
||||
enum ssl_early_data_reason_t reason);
|
||||
|
||||
|
||||
// Encrypted Client Hello.
|
||||
// Encrypted ClientHello.
|
||||
//
|
||||
// ECH is a mechanism for encrypting the entire ClientHello message in TLS 1.3.
|
||||
// This can prevent observers from seeing cleartext information about the
|
||||
@ -3589,6 +3589,72 @@ OPENSSL_EXPORT const char *SSL_early_data_reason_string(
|
||||
// as part of this connection.
|
||||
OPENSSL_EXPORT void SSL_set_enable_ech_grease(SSL *ssl, int enable);
|
||||
|
||||
// SSL_ECH_SERVER_CONFIG_LIST_new returns a newly-allocated
|
||||
// |SSL_ECH_SERVER_CONFIG_LIST| or NULL on error.
|
||||
OPENSSL_EXPORT SSL_ECH_SERVER_CONFIG_LIST *SSL_ECH_SERVER_CONFIG_LIST_new(void);
|
||||
|
||||
// SSL_ECH_SERVER_CONFIG_LIST_up_ref increments the reference count of |list|.
|
||||
OPENSSL_EXPORT void SSL_ECH_SERVER_CONFIG_LIST_up_ref(
|
||||
SSL_ECH_SERVER_CONFIG_LIST *list);
|
||||
|
||||
// SSL_ECH_SERVER_CONFIG_LIST_free releases memory associated with |list|.
|
||||
OPENSSL_EXPORT void SSL_ECH_SERVER_CONFIG_LIST_free(
|
||||
SSL_ECH_SERVER_CONFIG_LIST *list);
|
||||
|
||||
// SSL_ECH_SERVER_CONFIG_LIST_add appends an ECHConfig in |ech_config| and its
|
||||
// corresponding private key in |private_key| to |list|. 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_server_config_list|.
|
||||
//
|
||||
// This function should be called successively to register each ECHConfig in
|
||||
// decreasing order of preference. This configuration must be completed before
|
||||
// setting |list| on an |SSL_CTX| with |SSL_CTX_set1_ech_server_config_list|.
|
||||
// After that point, |list| is immutable; no more ECHConfig values may be added.
|
||||
OPENSSL_EXPORT int SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
SSL_ECH_SERVER_CONFIG_LIST *list, int is_retry_config,
|
||||
const uint8_t *ech_config, size_t ech_config_len,
|
||||
const uint8_t *private_key, size_t private_key_len);
|
||||
|
||||
// SSL_CTX_set1_ech_server_config_list atomically sets the refcounted |list|
|
||||
// onto |ctx|, releasing the old list. |SSL| objects associated with |ctx|, as
|
||||
// servers, will use |list| to decrypt incoming encrypted ClientHello messages.
|
||||
// It returns one on success, and zero on failure.
|
||||
//
|
||||
// If |list| does not contain any retry configs, this function will fail. Retry
|
||||
// configs are marked as such when they are added to |list| with
|
||||
// |SSL_ECH_SERVER_CONFIG_LIST_add|.
|
||||
//
|
||||
// Once |list| has been passed to this function, it is immutable. Unlike most
|
||||
// |SSL_CTX| configuration functions, this function may be called even if |ctx|
|
||||
// already has associated connections on multiple threads. This may be used to
|
||||
// rotate keys in a long-lived server process.
|
||||
//
|
||||
// The configured ECHConfig values should also be advertised out-of-band via DNS
|
||||
// (see draft-ietf-dnsop-svcb-https). Before advertising an ECHConfig in DNS,
|
||||
// deployments should ensure all instances of the service are configured with
|
||||
// the ECHConfig and corresponding private key.
|
||||
//
|
||||
// Only the most recent fully-deployed ECHConfigs should be advertised in DNS.
|
||||
// |list| may contain a newer set if those ECHConfigs are mid-deployment. It
|
||||
// should also contain older sets, until the DNS change has rolled out and the
|
||||
// old records have expired from caches.
|
||||
//
|
||||
// If there is a mismatch, |SSL| objects associated with |ctx| will complete the
|
||||
// handshake using the cleartext ClientHello and send updated ECHConfig values
|
||||
// to the client. The client will then retry to recover, but with a latency
|
||||
// penalty. This recovery flow depends on the public name in the ECHConfig.
|
||||
// Before advertising an ECHConfig in DNS, deployments must ensure all instances
|
||||
// of the service can present a valid certificate for the public name.
|
||||
//
|
||||
// BoringSSL negotiates ECH before certificate selection callbacks are called,
|
||||
// including |SSL_CTX_set_select_certificate_cb|. If ECH is negotiated, the
|
||||
// reported |SSL_CLIENT_HELLO| structure and |SSL_get_servername| function will
|
||||
// transparently reflect the inner ClientHello. Callers should select parameters
|
||||
// based on these values to correctly handle ECH as well as the recovery flow.
|
||||
OPENSSL_EXPORT int SSL_CTX_set1_ech_server_config_list(
|
||||
SSL_CTX *ctx, SSL_ECH_SERVER_CONFIG_LIST *list);
|
||||
|
||||
|
||||
// Alerts.
|
||||
//
|
||||
@ -4960,6 +5026,10 @@ BSSL_NAMESPACE_BEGIN
|
||||
BORINGSSL_MAKE_DELETER(SSL, SSL_free)
|
||||
BORINGSSL_MAKE_DELETER(SSL_CTX, SSL_CTX_free)
|
||||
BORINGSSL_MAKE_UP_REF(SSL_CTX, SSL_CTX_up_ref)
|
||||
BORINGSSL_MAKE_DELETER(SSL_ECH_SERVER_CONFIG_LIST,
|
||||
SSL_ECH_SERVER_CONFIG_LIST_free)
|
||||
BORINGSSL_MAKE_UP_REF(SSL_ECH_SERVER_CONFIG_LIST,
|
||||
SSL_ECH_SERVER_CONFIG_LIST_up_ref)
|
||||
BORINGSSL_MAKE_DELETER(SSL_SESSION, SSL_SESSION_free)
|
||||
BORINGSSL_MAKE_UP_REF(SSL_SESSION, SSL_SESSION_up_ref)
|
||||
|
||||
@ -5293,6 +5363,11 @@ BSSL_NAMESPACE_END
|
||||
#define SSL_R_NO_APPLICATION_PROTOCOL 307
|
||||
#define SSL_R_NEGOTIATED_ALPS_WITHOUT_ALPN 308
|
||||
#define SSL_R_ALPS_MISMATCH_ON_EARLY_DATA 309
|
||||
#define SSL_R_ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH 310
|
||||
#define SSL_R_ECH_SERVER_CONFIG_UNSUPPORTED_EXTENSION 311
|
||||
#define SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG 312
|
||||
#define SSL_R_ECH_SERVER_WOULD_HAVE_NO_RETRY_CONFIGS 313
|
||||
#define SSL_R_INVALID_CLIENT_HELLO_INNER 314
|
||||
#define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
|
||||
#define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
|
||||
#define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
|
||||
|
@ -257,6 +257,7 @@ extern "C" {
|
||||
// extension number.
|
||||
#define TLSEXT_TYPE_encrypted_client_hello 0xfe09
|
||||
#define TLSEXT_TYPE_ech_is_inner 0xda09
|
||||
#define TLSEXT_TYPE_ech_outer_extensions 0xfd00
|
||||
|
||||
// ExtensionType value from RFC6962
|
||||
#define TLSEXT_TYPE_certificate_timestamp 18
|
||||
|
@ -10,6 +10,7 @@ add_library(
|
||||
d1_srtp.cc
|
||||
dtls_method.cc
|
||||
dtls_record.cc
|
||||
encrypted_client_hello.cc
|
||||
handoff.cc
|
||||
handshake.cc
|
||||
handshake_client.cc
|
||||
|
444
ssl/encrypted_client_hello.cc
Normal file
444
ssl/encrypted_client_hello.cc
Normal file
@ -0,0 +1,444 @@
|
||||
/* Copyright (c) 2021, Google Inc.
|
||||
*
|
||||
* Permission to use, copy, modify, and/or distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
|
||||
|
||||
#include <openssl/bytestring.h>
|
||||
#include <openssl/curve25519.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/hkdf.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "internal.h"
|
||||
|
||||
|
||||
#if defined(OPENSSL_MSAN)
|
||||
#define NO_SANITIZE_MEMORY __attribute__((no_sanitize("memory")))
|
||||
#else
|
||||
#define NO_SANITIZE_MEMORY
|
||||
#endif
|
||||
|
||||
BSSL_NAMESPACE_BEGIN
|
||||
|
||||
// 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
|
||||
// |client_hello->client_hello| field. It returns true on success and false on
|
||||
// failure.
|
||||
static bool ssl_client_hello_write_without_extensions(
|
||||
const SSL_CLIENT_HELLO *client_hello, CBB *out) {
|
||||
CBB cbb;
|
||||
if (!CBB_add_u16(out, client_hello->version) ||
|
||||
!CBB_add_bytes(out, client_hello->random, client_hello->random_len) ||
|
||||
!CBB_add_u8_length_prefixed(out, &cbb) ||
|
||||
!CBB_add_bytes(&cbb, client_hello->session_id,
|
||||
client_hello->session_id_len) ||
|
||||
!CBB_add_u16_length_prefixed(out, &cbb) ||
|
||||
!CBB_add_bytes(&cbb, client_hello->cipher_suites,
|
||||
client_hello->cipher_suites_len) ||
|
||||
!CBB_add_u8_length_prefixed(out, &cbb) ||
|
||||
!CBB_add_bytes(&cbb, client_hello->compression_methods,
|
||||
client_hello->compression_methods_len) ||
|
||||
!CBB_flush(out)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ssl_decode_client_hello_inner(
|
||||
SSL *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
|
||||
Span<const uint8_t> encoded_client_hello_inner,
|
||||
const SSL_CLIENT_HELLO *client_hello_outer) {
|
||||
SSL_CLIENT_HELLO client_hello_inner;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello_inner,
|
||||
encoded_client_hello_inner)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
// TLS 1.3 ClientHellos must have extensions, and EncodedClientHelloInners use
|
||||
// ClientHelloOuter's session_id.
|
||||
if (client_hello_inner.extensions_len == 0 ||
|
||||
client_hello_inner.session_id_len != 0) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
client_hello_inner.session_id = client_hello_outer->session_id;
|
||||
client_hello_inner.session_id_len = client_hello_outer->session_id_len;
|
||||
|
||||
// Begin serializing a message containing the ClientHelloInner in |cbb|.
|
||||
ScopedCBB cbb;
|
||||
CBB body, extensions;
|
||||
if (!ssl->method->init_message(ssl, cbb.get(), &body, SSL3_MT_CLIENT_HELLO) ||
|
||||
!ssl_client_hello_write_without_extensions(&client_hello_inner, &body) ||
|
||||
!CBB_add_u16_length_prefixed(&body, &extensions)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Sort the extensions in ClientHelloOuter, so ech_outer_extensions may be
|
||||
// processed in O(n*log(n)) time, rather than O(n^2).
|
||||
struct Extension {
|
||||
uint16_t extension = 0;
|
||||
Span<const uint8_t> body;
|
||||
bool copied = false;
|
||||
};
|
||||
|
||||
// MSan's libc interceptors do not handle |bsearch|. See b/182583130.
|
||||
auto compare_extension = [](const void *a, const void *b)
|
||||
NO_SANITIZE_MEMORY -> int {
|
||||
const Extension *extension_a = reinterpret_cast<const Extension *>(a);
|
||||
const Extension *extension_b = reinterpret_cast<const Extension *>(b);
|
||||
if (extension_a->extension < extension_b->extension) {
|
||||
return -1;
|
||||
} else if (extension_a->extension > extension_b->extension) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
GrowableArray<Extension> sorted_extensions;
|
||||
CBS unsorted_extensions(MakeConstSpan(client_hello_outer->extensions,
|
||||
client_hello_outer->extensions_len));
|
||||
while (CBS_len(&unsorted_extensions) > 0) {
|
||||
Extension extension;
|
||||
CBS extension_body;
|
||||
if (!CBS_get_u16(&unsorted_extensions, &extension.extension) ||
|
||||
!CBS_get_u16_length_prefixed(&unsorted_extensions, &extension_body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
extension.body = extension_body;
|
||||
if (!sorted_extensions.Push(extension)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
qsort(sorted_extensions.data(), sorted_extensions.size(), sizeof(Extension),
|
||||
compare_extension);
|
||||
|
||||
// Copy extensions from |client_hello_inner|, expanding ech_outer_extensions.
|
||||
CBS inner_extensions(MakeConstSpan(client_hello_inner.extensions,
|
||||
client_hello_inner.extensions_len));
|
||||
while (CBS_len(&inner_extensions) > 0) {
|
||||
uint16_t extension_id;
|
||||
CBS extension_body;
|
||||
if (!CBS_get_u16(&inner_extensions, &extension_id) ||
|
||||
!CBS_get_u16_length_prefixed(&inner_extensions, &extension_body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (extension_id != TLSEXT_TYPE_ech_outer_extensions) {
|
||||
if (!CBB_add_u16(&extensions, extension_id) ||
|
||||
!CBB_add_u16(&extensions, CBS_len(&extension_body)) ||
|
||||
!CBB_add_bytes(&extensions, CBS_data(&extension_body),
|
||||
CBS_len(&extension_body))) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Replace ech_outer_extensions with the corresponding outer extensions.
|
||||
CBS outer_extensions;
|
||||
if (!CBS_get_u8_length_prefixed(&extension_body, &outer_extensions) ||
|
||||
CBS_len(&extension_body) != 0) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
while (CBS_len(&outer_extensions) > 0) {
|
||||
uint16_t extension_needed;
|
||||
if (!CBS_get_u16(&outer_extensions, &extension_needed)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (extension_needed == TLSEXT_TYPE_encrypted_client_hello) {
|
||||
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
// Find the referenced extension.
|
||||
Extension key;
|
||||
key.extension = extension_needed;
|
||||
Extension *result = reinterpret_cast<Extension *>(
|
||||
bsearch(&key, sorted_extensions.data(), sorted_extensions.size(),
|
||||
sizeof(Extension), compare_extension));
|
||||
if (result == nullptr) {
|
||||
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extensions may be referenced at most once, to bound the result size.
|
||||
if (result->copied) {
|
||||
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION);
|
||||
return false;
|
||||
}
|
||||
result->copied = true;
|
||||
|
||||
if (!CBB_add_u16(&extensions, extension_needed) ||
|
||||
!CBB_add_u16(&extensions, result->body.size()) ||
|
||||
!CBB_add_bytes(&extensions, result->body.data(),
|
||||
result->body.size())) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!CBB_flush(&body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
// See https://github.com/tlswg/draft-ietf-tls-esni/pull/411
|
||||
CBS extension;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello_inner,
|
||||
MakeConstSpan(CBB_data(&body), CBB_len(&body))) ||
|
||||
!ssl_client_hello_get_extension(&client_hello_inner, &extension,
|
||||
TLSEXT_TYPE_ech_is_inner) ||
|
||||
CBS_len(&extension) != 0 ||
|
||||
ssl_client_hello_get_extension(&client_hello_inner, &extension,
|
||||
TLSEXT_TYPE_encrypted_client_hello) ||
|
||||
!ssl_client_hello_get_extension(&client_hello_inner, &extension,
|
||||
TLSEXT_TYPE_supported_versions)) {
|
||||
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CLIENT_HELLO_INNER);
|
||||
return false;
|
||||
}
|
||||
// Parse supported_versions and reject TLS versions prior to TLS 1.3. Older
|
||||
// versions are incompatible with ECH.
|
||||
CBS versions;
|
||||
if (!CBS_get_u8_length_prefixed(&extension, &versions) ||
|
||||
CBS_len(&extension) != 0 || //
|
||||
CBS_len(&versions) == 0) {
|
||||
*out_alert = SSL_AD_DECODE_ERROR;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
while (CBS_len(&versions) != 0) {
|
||||
uint16_t version;
|
||||
if (!CBS_get_u16(&versions, &version)) {
|
||||
*out_alert = SSL_AD_DECODE_ERROR;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (version == SSL3_VERSION || version == TLS1_VERSION ||
|
||||
version == TLS1_1_VERSION || version == TLS1_2_VERSION ||
|
||||
version == DTLS1_VERSION || version == DTLS1_2_VERSION) {
|
||||
*out_alert = SSL_AD_ILLEGAL_PARAMETER;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_CLIENT_HELLO_INNER);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ssl->method->finish_message(ssl, cbb.get(), out_client_hello_inner)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
*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;
|
||||
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_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) ||
|
||||
!ssl_client_hello_write_without_extensions(client_hello_outer,
|
||||
&outer_hello_cbb) ||
|
||||
!CBB_add_u16_length_prefixed(&outer_hello_cbb, &extensions_cbb)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
CBS extensions(MakeConstSpan(client_hello_outer->extensions,
|
||||
client_hello_outer->extensions_len));
|
||||
while (CBS_len(&extensions) > 0) {
|
||||
uint16_t extension_id;
|
||||
CBS extension_body;
|
||||
if (!CBS_get_u16(&extensions, &extension_id) ||
|
||||
!CBS_get_u16_length_prefixed(&extensions, &extension_body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (extension_id == TLSEXT_TYPE_encrypted_client_hello) {
|
||||
continue;
|
||||
}
|
||||
if (!CBB_add_u16(&extensions_cbb, extension_id) ||
|
||||
!CBB_add_u16(&extensions_cbb, CBS_len(&extension_body)) ||
|
||||
!CBB_add_bytes(&extensions_cbb, CBS_data(&extension_body),
|
||||
CBS_len(&extension_body))) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!CBB_flush(ch_outer_aad_cbb.get())) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Attempt to decrypt into |out_encoded_client_hello_inner|.
|
||||
if (!out_encoded_client_hello_inner->Init(payload.size())) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
||||
return false;
|
||||
}
|
||||
size_t encoded_client_hello_inner_len;
|
||||
if (!EVP_HPKE_CTX_open(hpke_ctx, out_encoded_client_hello_inner->data(),
|
||||
&encoded_client_hello_inner_len,
|
||||
out_encoded_client_hello_inner->size(), payload.data(),
|
||||
payload.size(), CBB_data(ch_outer_aad_cbb.get()),
|
||||
CBB_len(ch_outer_aad_cbb.get()))) {
|
||||
*out_is_decrypt_error = true;
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
|
||||
return false;
|
||||
}
|
||||
out_encoded_client_hello_inner->Shrink(encoded_client_hello_inner_len);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ECHServerConfig::Init(Span<const uint8_t> raw,
|
||||
Span<const uint8_t> private_key,
|
||||
bool is_retry_config) {
|
||||
assert(!initialized_);
|
||||
is_retry_config_ = is_retry_config;
|
||||
|
||||
if (!raw_.CopyFrom(raw)) {
|
||||
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_);
|
||||
|
||||
uint16_t version;
|
||||
if (!CBS_get_u16(&reader, &version)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
// Parse the ECHConfig, rejecting all unsupported parameters and extensions.
|
||||
// Unlike most server options, ECH's server configuration is serialized and
|
||||
// configured in both the server and DNS. If the caller configures an
|
||||
// unsupported parameter, this is a deployment error. To catch these errors,
|
||||
// we fail early.
|
||||
if (version != TLSEXT_TYPE_encrypted_client_hello) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG);
|
||||
return false;
|
||||
}
|
||||
|
||||
CBS ech_config_contents, public_name, public_key, cipher_suites, extensions;
|
||||
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_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, &extensions) ||
|
||||
CBS_len(&ech_config_contents) != 0 || //
|
||||
CBS_len(&reader) != 0) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
// 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) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG);
|
||||
return false;
|
||||
}
|
||||
public_key_ = public_key;
|
||||
|
||||
// We do not support any ECHConfig extensions, so |extensions| must be empty.
|
||||
if (CBS_len(&extensions) != 0) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_ECH_SERVER_CONFIG_UNSUPPORTED_EXTENSION);
|
||||
return false;
|
||||
}
|
||||
|
||||
cipher_suites_ = cipher_suites;
|
||||
while (CBS_len(&cipher_suites) > 0) {
|
||||
uint16_t kdf_id, aead_id;
|
||||
if (!CBS_get_u16(&cipher_suites, &kdf_id) ||
|
||||
!CBS_get_u16(&cipher_suites, &aead_id)) {
|
||||
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)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
uint8_t expected_public_key[X25519_PUBLIC_VALUE_LEN];
|
||||
X25519_public_from_private(expected_public_key, private_key.data());
|
||||
if (public_key_ != expected_public_key) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_ECH_SERVER_CONFIG_AND_PRIVATE_KEY_MISMATCH);
|
||||
return false;
|
||||
}
|
||||
assert(sizeof(private_key_) == private_key.size());
|
||||
OPENSSL_memcpy(private_key_, private_key.data(), private_key.size());
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ECHServerConfig::SupportsCipherSuite(uint16_t kdf_id,
|
||||
uint16_t aead_id) const {
|
||||
assert(initialized_);
|
||||
CBS cbs(cipher_suites_);
|
||||
while (CBS_len(&cbs) != 0) {
|
||||
uint16_t supported_kdf_id, supported_aead_id;
|
||||
if (!CBS_get_u16(&cbs, &supported_kdf_id) ||
|
||||
!CBS_get_u16(&cbs, &supported_aead_id)) {
|
||||
return false;
|
||||
}
|
||||
if (kdf_id == supported_kdf_id && aead_id == supported_aead_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
BSSL_NAMESPACE_END
|
@ -93,7 +93,7 @@ bool SSL_serialize_handoff(const SSL *ssl, CBB *out,
|
||||
!serialize_features(&seq) ||
|
||||
!CBB_flush(out) ||
|
||||
!ssl->method->get_message(ssl, &msg) ||
|
||||
!ssl_client_hello_init(ssl, out_hello, msg)) {
|
||||
!ssl_client_hello_init(ssl, out_hello, msg.body)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,7 @@ BSSL_NAMESPACE_BEGIN
|
||||
|
||||
SSL_HANDSHAKE::SSL_HANDSHAKE(SSL *ssl_arg)
|
||||
: ssl(ssl_arg),
|
||||
ech_accept(false),
|
||||
ech_present(false),
|
||||
ech_is_inner_present(false),
|
||||
scts_requested(false),
|
||||
@ -164,6 +165,28 @@ void SSL_HANDSHAKE::ResizeSecrets(size_t hash_len) {
|
||||
hash_len_ = hash_len;
|
||||
}
|
||||
|
||||
bool SSL_HANDSHAKE::GetClientHello(SSLMessage *out_msg,
|
||||
SSL_CLIENT_HELLO *out_client_hello) {
|
||||
if (!ech_client_hello_buf.empty()) {
|
||||
// If the backing buffer is non-empty, the ClientHelloInner has been set.
|
||||
out_msg->is_v2_hello = false;
|
||||
out_msg->type = SSL3_MT_CLIENT_HELLO;
|
||||
out_msg->raw = CBS(ech_client_hello_buf);
|
||||
out_msg->body = MakeConstSpan(ech_client_hello_buf).subspan(4);
|
||||
} else if (!ssl->method->get_message(ssl, out_msg)) {
|
||||
// The message has already been read, so this cannot fail.
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ssl_client_hello_init(ssl, out_client_hello, out_msg->body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_PARSE_FAILED);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
UniquePtr<SSL_HANDSHAKE> ssl_handshake_new(SSL *ssl) {
|
||||
UniquePtr<SSL_HANDSHAKE> hs = MakeUnique<SSL_HANDSHAKE>(ssl);
|
||||
if (!hs || !hs->transcript.Init()) {
|
||||
|
@ -154,6 +154,8 @@
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/bytestring.h>
|
||||
#include <openssl/cipher.h>
|
||||
#include <openssl/curve25519.h>
|
||||
#include <openssl/digest.h>
|
||||
#include <openssl/ec.h>
|
||||
#include <openssl/ecdsa.h>
|
||||
#include <openssl/err.h>
|
||||
@ -167,6 +169,7 @@
|
||||
|
||||
#include "internal.h"
|
||||
#include "../crypto/internal.h"
|
||||
#include "../crypto/hpke/internal.h"
|
||||
|
||||
|
||||
BSSL_NAMESPACE_BEGIN
|
||||
@ -563,7 +566,7 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
|
||||
}
|
||||
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg.body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
return ssl_hs_error;
|
||||
@ -581,12 +584,137 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
|
||||
return ssl_hs_handoff;
|
||||
}
|
||||
|
||||
// If the ClientHello contains an encrypted_client_hello extension (and no
|
||||
// ech_is_inner extension), act as a client-facing server and attempt to
|
||||
// decrypt the ClientHelloInner.
|
||||
CBS ech_body;
|
||||
if (ssl_client_hello_get_extension(&client_hello, &ech_body,
|
||||
TLSEXT_TYPE_encrypted_client_hello)) {
|
||||
CBS unused;
|
||||
if (ssl_client_hello_get_extension(&client_hello, &unused,
|
||||
TLSEXT_TYPE_ech_is_inner)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Parse a ClientECH out of the extension body.
|
||||
uint16_t kdf_id, aead_id;
|
||||
CBS config_id, 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_u16_length_prefixed(&ech_body, &enc) ||
|
||||
!CBS_get_u16_length_prefixed(&ech_body, &payload) ||
|
||||
CBS_len(&ech_body) != 0) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
{
|
||||
MutexReadLock lock(&ssl->ctx->lock);
|
||||
hs->ech_server_config_list = UpRef(ssl->ctx->ech_server_config_list);
|
||||
}
|
||||
|
||||
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_sha256() ||
|
||||
!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()))) {
|
||||
// Ignore the error and try another ECHConfig.
|
||||
ERR_clear_error();
|
||||
continue;
|
||||
}
|
||||
Array<uint8_t> encoded_client_hello_inner;
|
||||
bool is_decrypt_error;
|
||||
if (!ssl_client_hello_decrypt(hs->ech_hpke_ctx.get(),
|
||||
&encoded_client_hello_inner,
|
||||
&is_decrypt_error, &client_hello, kdf_id,
|
||||
aead_id, config_id, enc, payload)) {
|
||||
if (is_decrypt_error) {
|
||||
// Ignore the error and try another ECHConfig.
|
||||
ERR_clear_error();
|
||||
continue;
|
||||
}
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Recover the ClientHelloInner from the EncodedClientHelloInner.
|
||||
uint8_t alert = SSL_AD_DECODE_ERROR;
|
||||
bssl::Array<uint8_t> client_hello_inner;
|
||||
if (!ssl_decode_client_hello_inner(ssl, &alert, &client_hello_inner,
|
||||
encoded_client_hello_inner,
|
||||
&client_hello)) {
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
hs->ech_client_hello_buf = std::move(client_hello_inner);
|
||||
|
||||
// Load the ClientHelloInner into |client_hello|.
|
||||
if (!hs->GetClientHello(&msg, &client_hello)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
hs->ech_accept = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we did not set |hs->ech_accept| to true, we will send the current
|
||||
// ECHConfigs as retry_configs in the ServerHello's encrypted extensions.
|
||||
// Proceed with the ClientHelloOuter.
|
||||
}
|
||||
|
||||
uint8_t alert = SSL_AD_DECODE_ERROR;
|
||||
if (!extract_sni(hs, &alert, &client_hello)) {
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
hs->state = state12_read_client_hello_after_ech;
|
||||
return ssl_hs_ok;
|
||||
}
|
||||
|
||||
static enum ssl_hs_wait_t do_read_client_hello_after_ech(SSL_HANDSHAKE *hs) {
|
||||
SSL *const ssl = hs->ssl;
|
||||
|
||||
SSLMessage msg_unused;
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!hs->GetClientHello(&msg_unused, &client_hello)) {
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Run the early callback.
|
||||
if (ssl->ctx->select_certificate_cb != NULL) {
|
||||
switch (ssl->ctx->select_certificate_cb(&client_hello)) {
|
||||
@ -614,6 +742,7 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
|
||||
hs->apply_jdk11_workaround = true;
|
||||
}
|
||||
|
||||
uint8_t alert = SSL_AD_DECODE_ERROR;
|
||||
if (!negotiate_version(hs, &alert, &client_hello)) {
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
|
||||
return ssl_hs_error;
|
||||
@ -657,11 +786,6 @@ static enum ssl_hs_wait_t do_read_client_hello(SSL_HANDSHAKE *hs) {
|
||||
static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) {
|
||||
SSL *const ssl = hs->ssl;
|
||||
|
||||
SSLMessage msg;
|
||||
if (!ssl->method->get_message(ssl, &msg)) {
|
||||
return ssl_hs_read_message;
|
||||
}
|
||||
|
||||
// Call |cert_cb| to update server certificates if required.
|
||||
if (hs->config->cert->cert_cb != NULL) {
|
||||
int rv = hs->config->cert->cert_cb(ssl, hs->config->cert->cert_cb_arg);
|
||||
@ -701,10 +825,16 @@ static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) {
|
||||
return ssl_hs_ok;
|
||||
}
|
||||
|
||||
// It should not be possible to negotiate TLS 1.2 with ECH. The
|
||||
// ClientHelloInner decoding function rejects ClientHellos which offer TLS 1.2
|
||||
// or below.
|
||||
assert(!hs->ech_accept);
|
||||
|
||||
ssl->s3->early_data_reason = ssl_early_data_protocol_version;
|
||||
|
||||
SSLMessage msg_unused;
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
|
||||
if (!hs->GetClientHello(&msg_unused, &client_hello)) {
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
@ -743,7 +873,7 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
|
||||
}
|
||||
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg.body)) {
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
@ -1693,6 +1823,9 @@ enum ssl_hs_wait_t ssl_server_handshake(SSL_HANDSHAKE *hs) {
|
||||
case state12_read_client_hello:
|
||||
ret = do_read_client_hello(hs);
|
||||
break;
|
||||
case state12_read_client_hello_after_ech:
|
||||
ret = do_read_client_hello_after_ech(hs);
|
||||
break;
|
||||
case state12_select_certificate:
|
||||
ret = do_select_certificate(hs);
|
||||
break;
|
||||
@ -1773,6 +1906,8 @@ const char *ssl_server_handshake_state(SSL_HANDSHAKE *hs) {
|
||||
return "TLS server start_accept";
|
||||
case state12_read_client_hello:
|
||||
return "TLS server read_client_hello";
|
||||
case state12_read_client_hello_after_ech:
|
||||
return "TLS server read_client_hello_after_ech";
|
||||
case state12_select_certificate:
|
||||
return "TLS server select_certificate";
|
||||
case state12_tls13:
|
||||
|
136
ssl/internal.h
136
ssl/internal.h
@ -152,6 +152,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include <openssl/aead.h>
|
||||
#include <openssl/curve25519.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/lhash.h>
|
||||
#include <openssl/mem.h>
|
||||
@ -161,6 +162,7 @@
|
||||
|
||||
#include "../crypto/err/internal.h"
|
||||
#include "../crypto/internal.h"
|
||||
#include "../crypto/hpke/internal.h"
|
||||
|
||||
|
||||
#if defined(OPENSSL_WINDOWS)
|
||||
@ -378,6 +380,8 @@ class GrowableArray {
|
||||
return *this;
|
||||
}
|
||||
|
||||
const T *data() const { return array_.data(); }
|
||||
T *data() { return array_.data(); }
|
||||
size_t size() const { return size_; }
|
||||
bool empty() const { return size_ == 0; }
|
||||
|
||||
@ -1423,7 +1427,88 @@ bool tls13_verify_psk_binder(SSL_HANDSHAKE *hs, SSL_SESSION *session,
|
||||
const SSLMessage &msg, CBS *binders);
|
||||
|
||||
|
||||
// Encrypted Client Hello.
|
||||
// Encrypted ClientHello.
|
||||
|
||||
class ECHServerConfig {
|
||||
public:
|
||||
ECHServerConfig() : is_retry_config_(false), initialized_(false) {}
|
||||
ECHServerConfig(ECHServerConfig &&other) = default;
|
||||
~ECHServerConfig() = default;
|
||||
ECHServerConfig &operator=(ECHServerConfig &&) = default;
|
||||
|
||||
// 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<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;
|
||||
|
||||
Span<const uint8_t> raw() 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_));
|
||||
}
|
||||
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_;
|
||||
}
|
||||
|
||||
private:
|
||||
Array<uint8_t> raw_;
|
||||
Span<const uint8_t> public_key_;
|
||||
Span<const uint8_t> cipher_suites_;
|
||||
|
||||
// private_key_ is the key corresponding to |public_key|. For clients, it must
|
||||
// be empty (|private_key_present_ == false|). For servers, it must be a valid
|
||||
// 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];
|
||||
|
||||
bool is_retry_config_ : 1;
|
||||
bool initialized_ : 1;
|
||||
};
|
||||
|
||||
// ssl_decode_client_hello_inner recovers the full ClientHelloInner from the
|
||||
// EncodedClientHelloInner |encoded_client_hello_inner| by replacing its
|
||||
// outer_extensions extension with the referenced extensions from the
|
||||
// ClientHelloOuter |client_hello_outer|. If successful, it writes the recovered
|
||||
// ClientHelloInner to |out_client_hello_inner|. It returns true on success and
|
||||
// false on failure.
|
||||
OPENSSL_EXPORT bool ssl_decode_client_hello_inner(
|
||||
SSL *ssl, uint8_t *out_alert, Array<uint8_t> *out_client_hello_inner,
|
||||
Span<const uint8_t> encoded_client_hello_inner,
|
||||
const SSL_CLIENT_HELLO *client_hello_outer);
|
||||
|
||||
// ssl_client_hello_decrypt attempts to decrypt the given |payload| into
|
||||
// |out_encoded_client_hello_inner|. The decrypted value should be an
|
||||
// EncodedClientHelloInner. It returns false if any fatal errors occur and true
|
||||
// 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);
|
||||
|
||||
// tls13_ech_accept_confirmation computes the server's ECH acceptance signal,
|
||||
// writing it to |out|. It returns true on success, and false on failure.
|
||||
@ -1507,6 +1592,7 @@ enum ssl_grease_index_t {
|
||||
enum tls12_server_hs_state_t {
|
||||
state12_start_accept = 0,
|
||||
state12_read_client_hello,
|
||||
state12_read_client_hello_after_ech,
|
||||
state12_select_certificate,
|
||||
state12_tls13,
|
||||
state12_select_parameters,
|
||||
@ -1602,6 +1688,17 @@ struct SSL_HANDSHAKE {
|
||||
public:
|
||||
void ResizeSecrets(size_t hash_len);
|
||||
|
||||
// GetClientHello, on the server, returns either the normal ClientHello
|
||||
// message or the ClientHelloInner if it has been serialized to
|
||||
// |ech_client_hello_buf|. This function should only be called when the
|
||||
// current message is a ClientHello. It returns true on success and false on
|
||||
// error.
|
||||
//
|
||||
// Note that fields of the returned |out_msg| and |out_client_hello| point
|
||||
// into a handshake-owned buffer, so their lifetimes should not exceed this
|
||||
// SSL_HANDSHAKE.
|
||||
bool GetClientHello(SSLMessage *out_msg, SSL_CLIENT_HELLO *out_client_hello);
|
||||
|
||||
Span<uint8_t> secret() { return MakeSpan(secret_, hash_len_); }
|
||||
Span<uint8_t> early_traffic_secret() {
|
||||
return MakeSpan(early_traffic_secret_, hash_len_);
|
||||
@ -1654,6 +1751,10 @@ struct SSL_HANDSHAKE {
|
||||
// the first ClientHello.
|
||||
Array<uint8_t> ech_grease;
|
||||
|
||||
// ech_client_hello_buf, on the server, contains the bytes of the
|
||||
// reconstructed ClientHelloInner message.
|
||||
Array<uint8_t> ech_client_hello_buf;
|
||||
|
||||
// key_share_bytes is the value of the previously sent KeyShare extension by
|
||||
// the client in TLS 1.3.
|
||||
Array<uint8_t> key_share_bytes;
|
||||
@ -1690,6 +1791,10 @@ struct SSL_HANDSHAKE {
|
||||
// |cert_compression_negotiated| is true.
|
||||
uint16_t cert_compression_alg_id;
|
||||
|
||||
// ech_hpke_ctx, on the server, is the HPKE context used to decrypt the
|
||||
// client's ECH payloads.
|
||||
ScopedEVP_HPKE_CTX ech_hpke_ctx;
|
||||
|
||||
// server_params, in a TLS 1.2 server, stores the ServerKeyExchange
|
||||
// parameters. It has client and server randoms prepended for signing
|
||||
// convenience.
|
||||
@ -1726,12 +1831,21 @@ struct SSL_HANDSHAKE {
|
||||
// the client if |in_early_data| is true.
|
||||
UniquePtr<SSL_SESSION> early_session;
|
||||
|
||||
// ech_server_config_list, for servers, is the list of ECHConfig values that
|
||||
// were valid when the server received the first ClientHello. Its value will
|
||||
// not change when the config list on |SSL_CTX| is updated.
|
||||
UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> ech_server_config_list;
|
||||
|
||||
// new_cipher is the cipher being negotiated in this handshake.
|
||||
const SSL_CIPHER *new_cipher = nullptr;
|
||||
|
||||
// key_block is the record-layer key block for TLS 1.2 and earlier.
|
||||
Array<uint8_t> key_block;
|
||||
|
||||
// ech_accept, on the server, indicates whether the server should overwrite
|
||||
// part of ServerHello.random with the ECH accept_confirmation value.
|
||||
bool ech_accept : 1;
|
||||
|
||||
// ech_present, on the server, indicates whether the ClientHello contained an
|
||||
// encrypted_client_hello extension.
|
||||
bool ech_present : 1;
|
||||
@ -1997,7 +2111,7 @@ bool ssl_log_secret(const SSL *ssl, const char *label,
|
||||
// ClientHello functions.
|
||||
|
||||
bool ssl_client_hello_init(const SSL *ssl, SSL_CLIENT_HELLO *out,
|
||||
const SSLMessage &msg);
|
||||
Span<const uint8_t> body);
|
||||
|
||||
bool ssl_client_hello_get_extension(const SSL_CLIENT_HELLO *client_hello,
|
||||
CBS *out, uint16_t extension_type);
|
||||
@ -3321,6 +3435,11 @@ struct ssl_ctx_st {
|
||||
// The client's Channel ID private key.
|
||||
bssl::UniquePtr<EVP_PKEY> channel_id_private;
|
||||
|
||||
// ech_server_config_list contains the server's list of ECHConfig values and
|
||||
// associated private keys. This list may be swapped out at any time, so all
|
||||
// access must be synchronized through |lock|.
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> ech_server_config_list;
|
||||
|
||||
// keylog_callback, if not NULL, is the key logging callback. See
|
||||
// |SSL_CTX_set_keylog_callback|.
|
||||
void (*keylog_callback)(const SSL *ssl, const char *line) = nullptr;
|
||||
@ -3634,5 +3753,18 @@ struct ssl_session_st {
|
||||
friend void SSL_SESSION_free(SSL_SESSION *);
|
||||
};
|
||||
|
||||
struct ssl_ech_server_config_list_st {
|
||||
ssl_ech_server_config_list_st() = default;
|
||||
ssl_ech_server_config_list_st(const ssl_ech_server_config_list_st &) = delete;
|
||||
ssl_ech_server_config_list_st &operator=(
|
||||
const ssl_ech_server_config_list_st &) = delete;
|
||||
|
||||
bssl::GrowableArray<bssl::ECHServerConfig> configs;
|
||||
CRYPTO_refcount_t references = 1;
|
||||
|
||||
private:
|
||||
~ssl_ech_server_config_list_st() = default;
|
||||
friend void SSL_ECH_SERVER_CONFIG_LIST_free(SSL_ECH_SERVER_CONFIG_LIST *);
|
||||
};
|
||||
|
||||
#endif // OPENSSL_HEADER_SSL_INTERNAL_H
|
||||
|
@ -2186,6 +2186,63 @@ int SSL_CTX_set_tlsext_servername_arg(SSL_CTX *ctx, void *arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
SSL_ECH_SERVER_CONFIG_LIST *SSL_ECH_SERVER_CONFIG_LIST_new() {
|
||||
return New<SSL_ECH_SERVER_CONFIG_LIST>();
|
||||
}
|
||||
|
||||
void SSL_ECH_SERVER_CONFIG_LIST_up_ref(SSL_ECH_SERVER_CONFIG_LIST *configs) {
|
||||
CRYPTO_refcount_inc(&configs->references);
|
||||
}
|
||||
|
||||
void SSL_ECH_SERVER_CONFIG_LIST_free(SSL_ECH_SERVER_CONFIG_LIST *configs) {
|
||||
if (configs == nullptr ||
|
||||
!CRYPTO_refcount_dec_and_test_zero(&configs->references)) {
|
||||
return;
|
||||
}
|
||||
|
||||
configs->~ssl_ech_server_config_list_st();
|
||||
OPENSSL_free(configs);
|
||||
}
|
||||
|
||||
int SSL_ECH_SERVER_CONFIG_LIST_add(SSL_ECH_SERVER_CONFIG_LIST *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) {
|
||||
ECHServerConfig parsed_config;
|
||||
if (!parsed_config.Init(MakeConstSpan(ech_config, ech_config_len),
|
||||
MakeConstSpan(private_key, private_key_len),
|
||||
!!is_retry_config)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
return 0;
|
||||
}
|
||||
if (!configs->configs.Push(std::move(parsed_config))) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int SSL_CTX_set1_ech_server_config_list(SSL_CTX *ctx,
|
||||
SSL_ECH_SERVER_CONFIG_LIST *list) {
|
||||
bool has_retry_config = false;
|
||||
for (const bssl::ECHServerConfig &config : list->configs) {
|
||||
if (config.is_retry_config()) {
|
||||
has_retry_config = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!has_retry_config) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_ECH_SERVER_WOULD_HAVE_NO_RETRY_CONFIGS);
|
||||
return 0;
|
||||
}
|
||||
UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> owned_list = UpRef(list);
|
||||
MutexWriteLock lock(&ctx->lock);
|
||||
ctx->ech_server_config_list.swap(owned_list);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int SSL_select_next_proto(uint8_t **out, uint8_t *out_len, const uint8_t *peer,
|
||||
unsigned peer_len, const uint8_t *supported,
|
||||
unsigned supported_len) {
|
||||
|
225
ssl/ssl_test.cc
225
ssl/ssl_test.cc
@ -29,6 +29,7 @@
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/cipher.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <openssl/curve25519.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/pem.h>
|
||||
@ -1440,6 +1441,230 @@ TEST(SSLTest, AddClientCA) {
|
||||
EXPECT_EQ(0, X509_NAME_cmp(sk_X509_NAME_value(list, 2), name1));
|
||||
}
|
||||
|
||||
// kECHConfig contains a serialized ECHConfig value.
|
||||
static const uint8_t kECHConfig[] = {
|
||||
// version
|
||||
0xfe, 0x09,
|
||||
// length
|
||||
0x00, 0x42,
|
||||
// contents.public_name
|
||||
0x00, 0x0e, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2e, 0x65, 0x78, 0x61,
|
||||
0x6d, 0x70, 0x6c, 0x65,
|
||||
// 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.extensions
|
||||
0x00, 0x00};
|
||||
|
||||
// kECHConfigPublicKey is the public key encoded in |kECHConfig|.
|
||||
static const uint8_t kECHConfigPublicKey[X25519_PUBLIC_VALUE_LEN] = {
|
||||
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};
|
||||
|
||||
// kECHConfigPrivateKey is the X25519 private key corresponding to
|
||||
// |kECHConfigPublicKey|.
|
||||
static const uint8_t kECHConfigPrivateKey[X25519_PRIVATE_KEY_LEN] = {
|
||||
0xbc, 0xb5, 0x51, 0x29, 0x31, 0x10, 0x30, 0xc9, 0xed, 0x26, 0xde,
|
||||
0xd4, 0xb3, 0xdf, 0x3a, 0xce, 0x06, 0x8a, 0xee, 0x17, 0xab, 0xce,
|
||||
0xd7, 0xdb, 0xf3, 0x11, 0xe5, 0xa8, 0xf3, 0xb1, 0x8e, 0x24};
|
||||
|
||||
// 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,
|
||||
Span<const uint16_t> cipher_suites,
|
||||
Span<const uint8_t> extensions) {
|
||||
bssl::ScopedCBB cbb;
|
||||
CBB contents, child;
|
||||
static const char kPublicName[] = "example.com";
|
||||
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_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;
|
||||
}
|
||||
for (uint16_t cipher_suite : cipher_suites) {
|
||||
if (!CBB_add_u16(&child, cipher_suite)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!CBB_add_u16(&contents, strlen(kPublicName)) || // maximum_name_length
|
||||
!CBB_add_u16_length_prefixed(&contents, &child) ||
|
||||
!CBB_add_bytes(&child, extensions.data(), extensions.size()) ||
|
||||
!CBB_flush(cbb.get())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out->assign(CBB_data(cbb.get()), CBB_data(cbb.get()) + CBB_len(cbb.get()));
|
||||
return true;
|
||||
}
|
||||
|
||||
TEST(SSLTest, ECHServerConfigList) {
|
||||
// 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<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
|
||||
ASSERT_TRUE(ctx);
|
||||
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> config_list(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
ASSERT_TRUE(config_list);
|
||||
|
||||
// Adding an ECHConfig with the wrong private key is an error.
|
||||
ASSERT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, kECHConfig, sizeof(kECHConfig),
|
||||
kWrongPrivateKey, sizeof(kWrongPrivateKey)));
|
||||
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.
|
||||
ASSERT_TRUE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, kECHConfig, sizeof(kECHConfig),
|
||||
kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
|
||||
ASSERT_TRUE(
|
||||
SSL_CTX_set1_ech_server_config_list(ctx.get(), config_list.get()));
|
||||
|
||||
// Build a new config list and replace the old one on |ctx|.
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> next_config_list(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
ASSERT_TRUE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
next_config_list.get(), /*is_retry_config=*/1, kECHConfig,
|
||||
sizeof(kECHConfig), kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
ASSERT_TRUE(
|
||||
SSL_CTX_set1_ech_server_config_list(ctx.get(), next_config_list.get()));
|
||||
}
|
||||
|
||||
TEST(SSLTest, ECHServerConfigListTruncatedPublicKey) {
|
||||
std::vector<uint8_t> ech_config;
|
||||
ASSERT_TRUE(MakeECHConfig(
|
||||
&ech_config, 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=*/{}));
|
||||
|
||||
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
|
||||
ASSERT_TRUE(ctx);
|
||||
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> config_list(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
ASSERT_TRUE(config_list);
|
||||
ASSERT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, ech_config.data(),
|
||||
ech_config.size(), kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
|
||||
uint32_t err = ERR_peek_error();
|
||||
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
|
||||
EXPECT_EQ(SSL_R_UNSUPPORTED_ECH_SERVER_CONFIG, ERR_GET_REASON(err));
|
||||
ERR_clear_error();
|
||||
}
|
||||
|
||||
// Test that |SSL_CTX_set1_ech_server_config_list| fails when the config list
|
||||
// has no retry configs.
|
||||
TEST(SSLTest, ECHServerConfigsWithoutRetryConfigs) {
|
||||
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
|
||||
ASSERT_TRUE(ctx);
|
||||
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> config_list(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
ASSERT_TRUE(config_list);
|
||||
|
||||
// Adding an ECHConfig with the matching private key succeeds.
|
||||
ASSERT_TRUE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/0, kECHConfig, sizeof(kECHConfig),
|
||||
kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
|
||||
ASSERT_FALSE(
|
||||
SSL_CTX_set1_ech_server_config_list(ctx.get(), config_list.get()));
|
||||
uint32_t err = ERR_peek_error();
|
||||
EXPECT_EQ(ERR_LIB_SSL, ERR_GET_LIB(err));
|
||||
EXPECT_EQ(SSL_R_ECH_SERVER_WOULD_HAVE_NO_RETRY_CONFIGS, ERR_GET_REASON(err));
|
||||
ERR_clear_error();
|
||||
|
||||
// Add the same ECHConfig to the list, but this time mark it as a retry
|
||||
// config.
|
||||
ASSERT_TRUE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, kECHConfig, sizeof(kECHConfig),
|
||||
kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
ASSERT_TRUE(
|
||||
SSL_CTX_set1_ech_server_config_list(ctx.get(), config_list.get()));
|
||||
}
|
||||
|
||||
// Test that the server APIs reject ECHConfigs with unsupported features.
|
||||
TEST(SSLTest, UnsupportedECHConfig) {
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> config_list(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
ASSERT_TRUE(config_list);
|
||||
|
||||
// Unsupported versions are rejected.
|
||||
static const uint8_t kUnsupportedVersion[] = {0xff, 0xff, 0x00, 0x00};
|
||||
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, kUnsupportedVersion,
|
||||
sizeof(kUnsupportedVersion), kECHConfigPrivateKey,
|
||||
sizeof(kECHConfigPrivateKey)));
|
||||
|
||||
// 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,
|
||||
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA384, EVP_HPKE_AEAD_AES_128_GCM},
|
||||
/*extensions=*/{}));
|
||||
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, ech_config.data(),
|
||||
ech_config.size(), kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
|
||||
// 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, 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(
|
||||
config_list.get(), /*is_retry_config=*/1, ech_config.data(),
|
||||
ech_config.size(), kP256PrivateKey, sizeof(kP256PrivateKey)));
|
||||
|
||||
// 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,
|
||||
std::vector<uint16_t>{EVP_HPKE_HKDF_SHA256, EVP_HPKE_AEAD_AES_128_GCM},
|
||||
kExtensions));
|
||||
EXPECT_FALSE(SSL_ECH_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), /*is_retry_config=*/1, ech_config.data(),
|
||||
ech_config.size(), kECHConfigPrivateKey, sizeof(kECHConfigPrivateKey)));
|
||||
}
|
||||
|
||||
static void AppendSession(SSL_SESSION *session, void *arg) {
|
||||
std::vector<SSL_SESSION*> *out =
|
||||
reinterpret_cast<std::vector<SSL_SESSION*>*>(arg);
|
||||
|
@ -209,11 +209,11 @@ static bool is_post_quantum_group(uint16_t id) {
|
||||
}
|
||||
|
||||
bool ssl_client_hello_init(const SSL *ssl, SSL_CLIENT_HELLO *out,
|
||||
const SSLMessage &msg) {
|
||||
Span<const uint8_t> body) {
|
||||
OPENSSL_memset(out, 0, sizeof(*out));
|
||||
out->ssl = const_cast<SSL *>(ssl);
|
||||
out->client_hello = CBS_data(&msg.body);
|
||||
out->client_hello_len = CBS_len(&msg.body);
|
||||
out->client_hello = body.data();
|
||||
out->client_hello_len = body.size();
|
||||
|
||||
CBS client_hello, random, session_id;
|
||||
CBS_init(&client_hello, out->client_hello, out->client_hello_len);
|
||||
@ -591,7 +591,7 @@ static bool ext_sni_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
|
||||
}
|
||||
|
||||
|
||||
// Encrypted Client Hello (ECH)
|
||||
// Encrypted ClientHello (ECH)
|
||||
//
|
||||
// https://tools.ietf.org/html/draft-ietf-tls-esni-09
|
||||
|
||||
@ -748,6 +748,35 @@ static bool ext_ech_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool ext_ech_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
|
||||
SSL *const ssl = hs->ssl;
|
||||
if (ssl_protocol_version(ssl) < TLS1_3_VERSION || //
|
||||
hs->ech_accept || //
|
||||
hs->ech_server_config_list == nullptr) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write the list of retry configs to |out|. Note
|
||||
// |SSL_CTX_set1_ech_server_config_list| ensures |ech_server_config_list|
|
||||
// contains at least one retry config.
|
||||
CBB body, retry_configs;
|
||||
if (!CBB_add_u16(out, TLSEXT_TYPE_encrypted_client_hello) ||
|
||||
!CBB_add_u16_length_prefixed(out, &body) ||
|
||||
!CBB_add_u16_length_prefixed(&body, &retry_configs)) {
|
||||
return false;
|
||||
}
|
||||
for (const ECHServerConfig &config : hs->ech_server_config_list->configs) {
|
||||
if (!config.is_retry_config()) {
|
||||
continue;
|
||||
}
|
||||
if (!CBB_add_bytes(&retry_configs, config.raw().data(),
|
||||
config.raw().size())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return CBB_flush(out);
|
||||
}
|
||||
|
||||
static bool ext_ech_is_inner_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
|
||||
return true;
|
||||
}
|
||||
@ -3264,7 +3293,7 @@ static const struct tls_extension kExtensions[] = {
|
||||
ext_ech_add_clienthello,
|
||||
ext_ech_parse_serverhello,
|
||||
ext_ech_parse_clienthello,
|
||||
dont_add_serverhello,
|
||||
ext_ech_add_serverhello,
|
||||
},
|
||||
{
|
||||
TLSEXT_TYPE_ech_is_inner,
|
||||
|
@ -16,6 +16,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"boringssl.googlesource.com/boringssl/ssl/test/runner/hpke"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -129,6 +131,7 @@ const (
|
||||
extensionDuplicate uint16 = 0xffff // not IANA assigned
|
||||
extensionEncryptedClientHello uint16 = 0xfe09 // not IANA assigned
|
||||
extensionECHIsInner uint16 = 0xda09 // not IANA assigned
|
||||
extensionECHOuterExtensions uint16 = 0xfd00 // not IANA assigned
|
||||
)
|
||||
|
||||
// TLS signaling cipher suite values
|
||||
@ -266,6 +269,7 @@ type ConnectionState struct {
|
||||
QUICTransportParamsLegacy []byte // the legacy QUIC transport params received from the peer
|
||||
HasApplicationSettings bool // whether ALPS was negotiated
|
||||
PeerApplicationSettings []byte // application settings received from the peer
|
||||
ECHAccepted bool // whether ECH was accepted on this connection
|
||||
}
|
||||
|
||||
// ClientAuthType declares the policy the server will follow for
|
||||
@ -422,6 +426,19 @@ type Config struct {
|
||||
// in the client's handshake to support virtual hosting.
|
||||
ServerName string
|
||||
|
||||
// ClientECHConfig, when non-nil, is the ECHConfig the client will use to
|
||||
// attempt ECH.
|
||||
ClientECHConfig *ECHConfig
|
||||
|
||||
// ECHCipherSuites, for the client, is the list of HPKE cipher suites in
|
||||
// decreasing order of preference. If empty, the default will be used.
|
||||
ECHCipherSuites []HPKECipherSuite
|
||||
|
||||
// ECHOuterExtensions is the list of extensions that the client will
|
||||
// compress with the ech_outer_extensions extension. If empty, no extensions
|
||||
// will be compressed.
|
||||
ECHOuterExtensions []uint16
|
||||
|
||||
// ClientAuth determines the server's policy for
|
||||
// TLS Client Authentication. The default is NoClientCert.
|
||||
ClientAuth ClientAuthType
|
||||
@ -846,23 +863,74 @@ type ProtocolBugs struct {
|
||||
// encrypted_client_hello extension containing a ClientECH structure.
|
||||
ExpectClientECH bool
|
||||
|
||||
// ExpectServerAcceptECH causes the client to expect that the server will
|
||||
// indicate ECH acceptance in the ServerHello.
|
||||
ExpectServerAcceptECH bool
|
||||
// IgnoreECHConfigCipherPreferences, when true, causes the client to ignore
|
||||
// the cipher preferences in the ECHConfig and select the most preferred ECH
|
||||
// cipher suite unconditionally.
|
||||
IgnoreECHConfigCipherPreferences bool
|
||||
|
||||
// ExpectECHRetryConfigs, when non-nil, contains the expected bytes of the
|
||||
// server's retry configs.
|
||||
ExpectECHRetryConfigs []byte
|
||||
|
||||
// SendECHRetryConfigs, if not empty, contains the ECH server's serialized
|
||||
// retry configs.
|
||||
SendECHRetryConfigs []byte
|
||||
|
||||
// SendEncryptedClientHello, when true, causes the client to send a
|
||||
// placeholder encrypted_client_hello extension on the ClientHelloOuter
|
||||
// message.
|
||||
SendPlaceholderEncryptedClientHello bool
|
||||
// SendInvalidECHIsInner, if not empty, causes the client to send the
|
||||
// specified byte string in the ech_is_inner extension.
|
||||
SendInvalidECHIsInner []byte
|
||||
|
||||
// SendECHIsInner, when non-nil, causes the client to send an ech_is_inner
|
||||
// extension on the ClientHelloOuter message. When nil, the extension will
|
||||
// be omitted.
|
||||
SendECHIsInner []byte
|
||||
// OmitECHIsInner, if true, causes the client to omit the ech_is_inner
|
||||
// extension on the ClientHelloInner message.
|
||||
OmitECHIsInner bool
|
||||
|
||||
// OmitSecondECHIsInner, if true, causes the client to omit the ech_is_inner
|
||||
// extension on the second ClientHelloInner message.
|
||||
OmitSecondECHIsInner bool
|
||||
|
||||
// AlwaysSendECHIsInner, if true, causes the client to send the
|
||||
// ech_is_inner extension on all ClientHello messages. The server is then
|
||||
// expected to unconditionally confirm the extension when negotiating
|
||||
// TLS 1.3 or later.
|
||||
AlwaysSendECHIsInner bool
|
||||
|
||||
// TruncateClientECHEnc, if true, causes the client to send a shortened
|
||||
// ClientECH.enc value in its encrypted_client_hello extension.
|
||||
TruncateClientECHEnc bool
|
||||
|
||||
// OfferSessionInClientHelloOuter, if true, causes the client to offer
|
||||
// sessions in ClientHelloOuter.
|
||||
OfferSessionInClientHelloOuter bool
|
||||
|
||||
// FirstExtensionInClientHelloOuter, if non-zero, causes the client to place
|
||||
// the specified extension first in ClientHelloOuter.
|
||||
FirstExtensionInClientHelloOuter uint16
|
||||
|
||||
// OnlyCompressSecondClientHelloInner, if true, causes the client to
|
||||
// only apply outer_extensions to the second ClientHello.
|
||||
OnlyCompressSecondClientHelloInner bool
|
||||
|
||||
// OmitSecondEncryptedClientHello, if true, causes the client to omit the
|
||||
// second encrypted_client_hello extension.
|
||||
OmitSecondEncryptedClientHello bool
|
||||
|
||||
// CorruptEncryptedClientHello, if true, causes the client to incorrectly
|
||||
// encrypt the encrypted_client_hello extension.
|
||||
CorruptEncryptedClientHello bool
|
||||
|
||||
// CorruptSecondEncryptedClientHello, if true, causes the client to
|
||||
// incorrectly encrypt the second encrypted_client_hello extension.
|
||||
CorruptSecondEncryptedClientHello bool
|
||||
|
||||
// AllowTLS12InClientHelloInner, if true, causes the client to include
|
||||
// TLS 1.2 and earlier in ClientHelloInner.
|
||||
AllowTLS12InClientHelloInner bool
|
||||
|
||||
// MinimalClientHelloOuter, if true, causes the client to omit most fields
|
||||
// in ClientHelloOuter. Note this will make handshake attempts with the
|
||||
// ClientHelloOuter fail and should only be used in tests that expect
|
||||
// success.
|
||||
MinimalClientHelloOuter bool
|
||||
|
||||
// SwapNPNAndALPN switches the relative order between NPN and ALPN in
|
||||
// both ClientHello and ServerHello.
|
||||
@ -1875,6 +1943,19 @@ func (c *Config) defaultCurves() map[CurveID]bool {
|
||||
return defaultCurves
|
||||
}
|
||||
|
||||
var defaultECHCipherSuitePreferences = []HPKECipherSuite{
|
||||
{KDF: hpke.HKDFSHA256, AEAD: hpke.AES128GCM},
|
||||
{KDF: hpke.HKDFSHA256, AEAD: hpke.AES256GCM},
|
||||
{KDF: hpke.HKDFSHA256, AEAD: hpke.ChaCha20Poly1305},
|
||||
}
|
||||
|
||||
func (c *Config) echCipherSuitePreferences() []HPKECipherSuite {
|
||||
if c == nil || len(c.ECHCipherSuites) == 0 {
|
||||
return defaultECHCipherSuitePreferences
|
||||
}
|
||||
return c.ECHCipherSuites
|
||||
}
|
||||
|
||||
func wireToVersion(vers uint16, isDTLS bool) (uint16, bool) {
|
||||
if isDTLS {
|
||||
switch vers {
|
||||
@ -1904,16 +1985,21 @@ func (c *Config) isSupportedVersion(wireVers uint16, isDTLS bool) (uint16, bool)
|
||||
return vers, true
|
||||
}
|
||||
|
||||
func (c *Config) supportedVersions(isDTLS bool) []uint16 {
|
||||
func (c *Config) supportedVersions(isDTLS, requireTLS13 bool) []uint16 {
|
||||
versions := allTLSWireVersions
|
||||
if isDTLS {
|
||||
versions = allDTLSWireVersions
|
||||
}
|
||||
var ret []uint16
|
||||
for _, vers := range versions {
|
||||
if _, ok := c.isSupportedVersion(vers, isDTLS); ok {
|
||||
ret = append(ret, vers)
|
||||
for _, wireVers := range versions {
|
||||
vers, ok := c.isSupportedVersion(wireVers, isDTLS)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if requireTLS13 && vers < VersionTLS13 {
|
||||
continue
|
||||
}
|
||||
ret = append(ret, wireVers)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
@ -119,6 +119,9 @@ type Conn struct {
|
||||
// handshake data may be received until the next flight or epoch change.
|
||||
seenHandshakePackEnd bool
|
||||
|
||||
// echAccepted indicates whether ECH was accepted for this connection.
|
||||
echAccepted bool
|
||||
|
||||
tmp [16]byte
|
||||
}
|
||||
|
||||
@ -1860,6 +1863,7 @@ func (c *Conn) ConnectionState() ConnectionState {
|
||||
state.QUICTransportParamsLegacy = c.quicTransportParamsLegacy
|
||||
state.HasApplicationSettings = c.hasApplicationSettings
|
||||
state.PeerApplicationSettings = c.peerApplicationSettings
|
||||
state.ECHAccepted = c.echAccepted
|
||||
}
|
||||
|
||||
return state
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,8 +5,11 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
func writeLen(buf []byte, v, size int) {
|
||||
@ -262,8 +265,7 @@ type ECHConfig struct {
|
||||
MaxNameLen uint16
|
||||
}
|
||||
|
||||
func MarshalECHConfig(e *ECHConfig) []byte {
|
||||
bb := newByteBuilder()
|
||||
func (e *ECHConfig) marshal(bb *byteBuilder) {
|
||||
// ECHConfig's wire format reuses the encrypted_client_hello extension
|
||||
// codepoint as a version identifier.
|
||||
bb.addU16(extensionEncryptedClientHello)
|
||||
@ -278,9 +280,33 @@ func MarshalECHConfig(e *ECHConfig) []byte {
|
||||
}
|
||||
contents.addU16(e.MaxNameLen)
|
||||
contents.addU16(0) // Empty extensions field
|
||||
}
|
||||
|
||||
func MarshalECHConfig(e *ECHConfig) []byte {
|
||||
bb := newByteBuilder()
|
||||
e.marshal(bb)
|
||||
return bb.finish()
|
||||
}
|
||||
|
||||
func MarshalECHConfigList(configs ...*ECHConfig) []byte {
|
||||
bb := newByteBuilder()
|
||||
list := bb.addU16LengthPrefixed()
|
||||
for _, config := range configs {
|
||||
config.marshal(list)
|
||||
}
|
||||
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 {
|
||||
@ -343,6 +369,7 @@ type clientHelloMsg struct {
|
||||
compressedCertAlgs []uint16
|
||||
delegatedCredentials bool
|
||||
alpsProtocols []string
|
||||
outerExtensions []uint16
|
||||
prefixExtensions []uint16
|
||||
}
|
||||
|
||||
@ -358,34 +385,21 @@ func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) marshal() []byte {
|
||||
if m.raw != nil {
|
||||
return m.raw
|
||||
}
|
||||
type clientHelloType int
|
||||
|
||||
if m.isV2ClientHello {
|
||||
v2Msg := newByteBuilder()
|
||||
v2Msg.addU8(1)
|
||||
v2Msg.addU16(m.vers)
|
||||
v2Msg.addU16(uint16(len(m.cipherSuites) * 3))
|
||||
v2Msg.addU16(uint16(len(m.sessionID)))
|
||||
v2Msg.addU16(uint16(len(m.v2Challenge)))
|
||||
for _, spec := range m.cipherSuites {
|
||||
v2Msg.addU24(int(spec))
|
||||
}
|
||||
v2Msg.addBytes(m.sessionID)
|
||||
v2Msg.addBytes(m.v2Challenge)
|
||||
m.raw = v2Msg.finish()
|
||||
return m.raw
|
||||
}
|
||||
const (
|
||||
clientHelloNormal clientHelloType = iota
|
||||
clientHelloOuterAAD
|
||||
clientHelloEncodedInner
|
||||
)
|
||||
|
||||
handshakeMsg := newByteBuilder()
|
||||
handshakeMsg.addU8(typeClientHello)
|
||||
hello := handshakeMsg.addU24LengthPrefixed()
|
||||
func (m *clientHelloMsg) marshalBody(hello *byteBuilder, typ clientHelloType) {
|
||||
hello.addU16(m.vers)
|
||||
hello.addBytes(m.random)
|
||||
sessionID := hello.addU8LengthPrefixed()
|
||||
sessionID.addBytes(m.sessionID)
|
||||
if typ != clientHelloEncodedInner {
|
||||
sessionID.addBytes(m.sessionID)
|
||||
}
|
||||
if m.isDTLS {
|
||||
cookie := hello.addU8LengthPrefixed()
|
||||
cookie.addBytes(m.cookie)
|
||||
@ -441,7 +455,7 @@ func (m *clientHelloMsg) marshal() []byte {
|
||||
body: serverNameList.finish(),
|
||||
})
|
||||
}
|
||||
if m.clientECH != nil {
|
||||
if m.clientECH != nil && typ != clientHelloOuterAAD {
|
||||
body := newByteBuilder()
|
||||
body.addU16(m.clientECH.hpkeKDF)
|
||||
body.addU16(m.clientECH.hpkeAEAD)
|
||||
@ -460,6 +474,17 @@ func (m *clientHelloMsg) marshal() []byte {
|
||||
body: m.echIsInner,
|
||||
})
|
||||
}
|
||||
if m.outerExtensions != nil && typ == clientHelloEncodedInner {
|
||||
body := newByteBuilder()
|
||||
extensionsList := body.addU8LengthPrefixed()
|
||||
for _, extID := range m.outerExtensions {
|
||||
extensionsList.addU16(extID)
|
||||
}
|
||||
extensions = append(extensions, extension{
|
||||
id: extensionECHOuterExtensions,
|
||||
body: body.finish(),
|
||||
})
|
||||
}
|
||||
if m.ocspStapling {
|
||||
certificateStatusRequest := newByteBuilder()
|
||||
// RFC 4366, section 3.6
|
||||
@ -700,6 +725,12 @@ func (m *clientHelloMsg) marshal() []byte {
|
||||
for _, ext := range extensions {
|
||||
extMap[ext.id] = ext.body
|
||||
}
|
||||
// Elide each of the extensions named by |m.outerExtensions|.
|
||||
if m.outerExtensions != nil && typ == clientHelloEncodedInner {
|
||||
for _, extID := range m.outerExtensions {
|
||||
delete(extMap, extID)
|
||||
}
|
||||
}
|
||||
// Write each of the prefix extensions, if we have it.
|
||||
for _, extID := range m.prefixExtensions {
|
||||
if body, ok := extMap[extID]; ok {
|
||||
@ -738,7 +769,43 @@ func (m *clientHelloMsg) marshal() []byte {
|
||||
hello.addU16(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) marshalForOuterAAD(bb *byteBuilder) {
|
||||
m.marshalBody(bb, clientHelloOuterAAD)
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) marshalForEncodedInner() []byte {
|
||||
hello := newByteBuilder()
|
||||
m.marshalBody(hello, clientHelloEncodedInner)
|
||||
return hello.finish()
|
||||
}
|
||||
|
||||
func (m *clientHelloMsg) marshal() []byte {
|
||||
if m.raw != nil {
|
||||
return m.raw
|
||||
}
|
||||
|
||||
if m.isV2ClientHello {
|
||||
v2Msg := newByteBuilder()
|
||||
v2Msg.addU8(1)
|
||||
v2Msg.addU16(m.vers)
|
||||
v2Msg.addU16(uint16(len(m.cipherSuites) * 3))
|
||||
v2Msg.addU16(uint16(len(m.sessionID)))
|
||||
v2Msg.addU16(uint16(len(m.v2Challenge)))
|
||||
for _, spec := range m.cipherSuites {
|
||||
v2Msg.addU24(int(spec))
|
||||
}
|
||||
v2Msg.addBytes(m.sessionID)
|
||||
v2Msg.addBytes(m.v2Challenge)
|
||||
m.raw = v2Msg.finish()
|
||||
return m.raw
|
||||
}
|
||||
|
||||
handshakeMsg := newByteBuilder()
|
||||
handshakeMsg.addU8(typeClientHello)
|
||||
hello := handshakeMsg.addU24LengthPrefixed()
|
||||
m.marshalBody(hello, clientHelloNormal)
|
||||
m.raw = handshakeMsg.finish()
|
||||
// Sanity-check padding.
|
||||
if m.pad != 0 && (len(m.raw)-4)%m.pad != 0 {
|
||||
@ -1529,9 +1596,7 @@ func (m *serverExtensions) marshal(extensions *byteBuilder) {
|
||||
}
|
||||
if len(m.echRetryConfigs) > 0 {
|
||||
extensions.addU16(extensionEncryptedClientHello)
|
||||
body := extensions.addU16LengthPrefixed()
|
||||
echConfigs := body.addU16LengthPrefixed()
|
||||
echConfigs.addBytes(m.echRetryConfigs)
|
||||
extensions.addU16LengthPrefixed().addBytes(m.echRetryConfigs)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1647,21 +1712,23 @@ func (m *serverExtensions) unmarshal(data byteReader, version uint16) bool {
|
||||
m.hasApplicationSettings = true
|
||||
m.applicationSettings = body
|
||||
case extensionEncryptedClientHello:
|
||||
if version < VersionTLS13 {
|
||||
return false
|
||||
}
|
||||
m.echRetryConfigs = body
|
||||
|
||||
// Validate the ECHConfig with a top-level parse.
|
||||
var echConfigs byteReader
|
||||
if !body.readU16LengthPrefixed(&echConfigs) {
|
||||
return false
|
||||
}
|
||||
for len(echConfigs) > 0 {
|
||||
// Validate the ECHConfig with a top-level parse.
|
||||
echConfigReader := echConfigs
|
||||
var version uint16
|
||||
var contents byteReader
|
||||
if !echConfigReader.readU16(&version) ||
|
||||
!echConfigReader.readU16LengthPrefixed(&contents) {
|
||||
if !echConfigs.readU16(&version) ||
|
||||
!echConfigs.readU16LengthPrefixed(&contents) {
|
||||
return false
|
||||
}
|
||||
|
||||
m.echRetryConfigs = contents
|
||||
}
|
||||
if len(body) > 0 {
|
||||
return false
|
||||
|
@ -18,10 +18,12 @@
|
||||
package hpke
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
@ -51,6 +53,20 @@ const (
|
||||
hpkeModePSK uint8 = 1
|
||||
)
|
||||
|
||||
// GetHKDFHash returns the crypto.Hash that corresponds to kdf. If kdf is not
|
||||
// one the supported KDF IDs, returns an error.
|
||||
func GetHKDFHash(kdf uint16) (crypto.Hash, error) {
|
||||
switch kdf {
|
||||
case HKDFSHA256:
|
||||
return crypto.SHA256, nil
|
||||
case HKDFSHA384:
|
||||
return crypto.SHA384, nil
|
||||
case HKDFSHA512:
|
||||
return crypto.SHA512, nil
|
||||
}
|
||||
return 0, fmt.Errorf("unknown KDF: %d", kdf)
|
||||
}
|
||||
|
||||
type GenerateKeyPairFunc func() (public []byte, secret []byte, e error)
|
||||
|
||||
// Context holds the HPKE state for a sender or a receiver.
|
||||
@ -113,6 +129,12 @@ func SetupPSKReceiverX25519(kdfID, aeadID uint16, enc, secretKeyR, info, psk, ps
|
||||
return context, nil
|
||||
}
|
||||
|
||||
func (c *Context) KEM() uint16 { return c.kemID }
|
||||
|
||||
func (c *Context) KDF() uint16 { return c.kdfID }
|
||||
|
||||
func (c *Context) AEAD() uint16 { return c.aeadID }
|
||||
|
||||
func (c *Context) Seal(plaintext, additionalData []byte) []byte {
|
||||
ciphertext := c.aead.Seal(nil, c.computeNonce(), plaintext, additionalData)
|
||||
c.incrementSeq()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -238,6 +238,12 @@ const Flag<std::vector<int>> kIntVectorFlags[] = {
|
||||
{"-verify-prefs", &TestConfig::verify_prefs},
|
||||
{"-expect-peer-verify-pref", &TestConfig::expect_peer_verify_prefs},
|
||||
{"-curves", &TestConfig::curves},
|
||||
{"-ech-is-retry-config", &TestConfig::ech_is_retry_config},
|
||||
};
|
||||
|
||||
const Flag<std::vector<std::string>> kBase64VectorFlags[] = {
|
||||
{"-ech-server-config", &TestConfig::ech_server_configs},
|
||||
{"-ech-server-key", &TestConfig::ech_server_keys},
|
||||
};
|
||||
|
||||
const Flag<std::vector<std::pair<std::string, std::string>>>
|
||||
@ -245,6 +251,23 @@ const Flag<std::vector<std::pair<std::string, std::string>>>
|
||||
{"-application-settings", &TestConfig::application_settings},
|
||||
};
|
||||
|
||||
bool DecodeBase64(std::string *out, const std::string &in) {
|
||||
size_t len;
|
||||
if (!EVP_DecodedLength(&len, in.size())) {
|
||||
fprintf(stderr, "Invalid base64: %s.\n", in.c_str());
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> buf(len);
|
||||
if (!EVP_DecodeBase64(buf.data(), &len, buf.size(),
|
||||
reinterpret_cast<const uint8_t *>(in.data()),
|
||||
in.size())) {
|
||||
fprintf(stderr, "Invalid base64: %s.\n", in.c_str());
|
||||
return false;
|
||||
}
|
||||
out->assign(reinterpret_cast<const char *>(buf.data()), len);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseFlag(char *flag, int argc, char **argv, int *i,
|
||||
bool skip, TestConfig *out_config) {
|
||||
bool *bool_field = FindField(out_config, kBoolFlags, flag);
|
||||
@ -289,21 +312,12 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i,
|
||||
fprintf(stderr, "Missing parameter.\n");
|
||||
return false;
|
||||
}
|
||||
size_t len;
|
||||
if (!EVP_DecodedLength(&len, strlen(argv[*i]))) {
|
||||
fprintf(stderr, "Invalid base64: %s.\n", argv[*i]);
|
||||
return false;
|
||||
}
|
||||
std::unique_ptr<uint8_t[]> decoded(new uint8_t[len]);
|
||||
if (!EVP_DecodeBase64(decoded.get(), &len, len,
|
||||
reinterpret_cast<const uint8_t *>(argv[*i]),
|
||||
strlen(argv[*i]))) {
|
||||
fprintf(stderr, "Invalid base64: %s.\n", argv[*i]);
|
||||
std::string value;
|
||||
if (!DecodeBase64(&value, argv[*i])) {
|
||||
return false;
|
||||
}
|
||||
if (!skip) {
|
||||
base64_field->assign(reinterpret_cast<const char *>(decoded.get()),
|
||||
len);
|
||||
*base64_field = std::move(value);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -337,6 +351,25 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i,
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> *base64_vector_field =
|
||||
FindField(out_config, kBase64VectorFlags, flag);
|
||||
if (base64_vector_field) {
|
||||
*i = *i + 1;
|
||||
if (*i >= argc) {
|
||||
fprintf(stderr, "Missing parameter.\n");
|
||||
return false;
|
||||
}
|
||||
std::string value;
|
||||
if (!DecodeBase64(&value, argv[*i])) {
|
||||
return false;
|
||||
}
|
||||
// Each instance of the flag adds to the list.
|
||||
if (!skip) {
|
||||
base64_vector_field->push_back(std::move(value));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> *string_pair_vector_field =
|
||||
FindField(out_config, kStringPairVectorFlags, flag);
|
||||
if (string_pair_vector_field) {
|
||||
@ -347,8 +380,10 @@ bool ParseFlag(char *flag, int argc, char **argv, int *i,
|
||||
}
|
||||
const char *comma = strchr(argv[*i], ',');
|
||||
if (!comma) {
|
||||
fprintf(stderr,
|
||||
"Parameter should be a pair of comma-separated strings.\n");
|
||||
fprintf(
|
||||
stderr,
|
||||
"Parameter should be a comma-separated triple composed of two base64 "
|
||||
"strings followed by \"true\" or \"false\".\n");
|
||||
return false;
|
||||
}
|
||||
// Each instance of the flag adds to the list.
|
||||
@ -1595,6 +1630,36 @@ bssl::UniquePtr<SSL> TestConfig::NewSSL(
|
||||
if (enable_ech_grease) {
|
||||
SSL_set_enable_ech_grease(ssl.get(), 1);
|
||||
}
|
||||
if (ech_server_configs.size() != ech_server_keys.size() ||
|
||||
ech_server_configs.size() != ech_is_retry_config.size()) {
|
||||
fprintf(stderr,
|
||||
"-ech-server-config, -ech-server-key, and -ech-is-retry-config "
|
||||
"flags must match.\n");
|
||||
return nullptr;
|
||||
}
|
||||
if (!ech_server_configs.empty()) {
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> config_list(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
if (!config_list) {
|
||||
return nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < ech_server_configs.size(); i++) {
|
||||
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_SERVER_CONFIG_LIST_add(
|
||||
config_list.get(), is_retry_config,
|
||||
reinterpret_cast<const uint8_t *>(ech_config.data()),
|
||||
ech_config.size(),
|
||||
reinterpret_cast<const uint8_t *>(ech_private_key.data()),
|
||||
ech_private_key.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (!SSL_CTX_set1_ech_server_config_list(ssl_ctx, config_list.get())) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
if (!send_channel_id.empty()) {
|
||||
SSL_set_tls_channel_id_enabled(ssl.get(), 1);
|
||||
if (!async) {
|
||||
|
@ -40,6 +40,9 @@ struct TestConfig {
|
||||
std::string cert_file;
|
||||
std::string expect_server_name;
|
||||
bool enable_ech_grease = false;
|
||||
std::vector<std::string> ech_server_configs;
|
||||
std::vector<std::string> ech_server_keys;
|
||||
std::vector<int> ech_is_retry_config;
|
||||
std::string expect_certificate_types;
|
||||
bool require_any_client_certificate = false;
|
||||
std::string advertise_npn;
|
||||
|
@ -28,6 +28,7 @@
|
||||
#include <openssl/stack.h>
|
||||
|
||||
#include "../crypto/internal.h"
|
||||
#include "../crypto/hpke/internal.h"
|
||||
#include "internal.h"
|
||||
|
||||
|
||||
@ -186,13 +187,8 @@ static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
|
||||
// the common handshake logic. Resolve the remaining non-PSK parameters.
|
||||
SSL *const ssl = hs->ssl;
|
||||
SSLMessage msg;
|
||||
if (!ssl->method->get_message(ssl, &msg)) {
|
||||
return ssl_hs_read_message;
|
||||
}
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_PARSE_FAILED);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
if (!hs->GetClientHello(&msg, &client_hello)) {
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
@ -347,13 +343,8 @@ static bool quic_ticket_compatible(const SSL_SESSION *session,
|
||||
static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
|
||||
SSL *const ssl = hs->ssl;
|
||||
SSLMessage msg;
|
||||
if (!ssl->method->get_message(ssl, &msg)) {
|
||||
return ssl_hs_read_message;
|
||||
}
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_PARSE_FAILED);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
if (!hs->GetClientHello(&msg, &client_hello)) {
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
@ -527,6 +518,7 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
|
||||
}
|
||||
|
||||
ssl->method->next_message(ssl);
|
||||
hs->ech_client_hello_buf.Reset();
|
||||
hs->tls13_state = state13_send_server_hello;
|
||||
return ssl_hs_ok;
|
||||
}
|
||||
@ -534,7 +526,6 @@ static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
|
||||
static enum ssl_hs_wait_t do_send_hello_retry_request(SSL_HANDSHAKE *hs) {
|
||||
SSL *const ssl = hs->ssl;
|
||||
|
||||
|
||||
ScopedCBB cbb;
|
||||
CBB body, session_id, extensions;
|
||||
uint16_t group_id;
|
||||
@ -576,12 +567,78 @@ static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
|
||||
return ssl_hs_error;
|
||||
}
|
||||
SSL_CLIENT_HELLO client_hello;
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg)) {
|
||||
if (!ssl_client_hello_init(ssl, &client_hello, msg.body)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_PARSE_FAILED);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
if (hs->ech_accept) {
|
||||
// If we previously accepted the ClientHelloInner, check that the second
|
||||
// ClientHello contains an encrypted_client_hello extension.
|
||||
CBS ech_body;
|
||||
if (!ssl_client_hello_get_extension(&client_hello, &ech_body,
|
||||
TLSEXT_TYPE_encrypted_client_hello)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_MISSING_EXTENSION);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_MISSING_EXTENSION);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Parse a ClientECH out of the extension body.
|
||||
uint16_t kdf_id, aead_id;
|
||||
CBS config_id, 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_u16_length_prefixed(&ech_body, &enc) ||
|
||||
!CBS_get_u16_length_prefixed(&ech_body, &payload) ||
|
||||
CBS_len(&ech_body) != 0) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Check that ClientECH.cipher_suite is unchanged and that
|
||||
// ClientECH.config_id and ClientECH.enc are 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) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Decrypt the payload with the HPKE context from the first ClientHello.
|
||||
Array<uint8_t> encoded_client_hello_inner;
|
||||
bool unused;
|
||||
if (!ssl_client_hello_decrypt(
|
||||
hs->ech_hpke_ctx.get(), &encoded_client_hello_inner, &unused,
|
||||
&client_hello, kdf_id, aead_id, config_id, enc, payload)) {
|
||||
// Decryption failure is fatal in the second ClientHello.
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECRYPTION_FAILED);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
|
||||
// Recover the ClientHelloInner from the EncodedClientHelloInner.
|
||||
uint8_t alert = SSL_AD_DECODE_ERROR;
|
||||
bssl::Array<uint8_t> client_hello_inner;
|
||||
if (!ssl_decode_client_hello_inner(ssl, &alert, &client_hello_inner,
|
||||
encoded_client_hello_inner,
|
||||
&client_hello)) {
|
||||
OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
|
||||
ssl_send_alert(ssl, SSL3_AL_FATAL, alert);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
hs->ech_client_hello_buf = std::move(client_hello_inner);
|
||||
|
||||
// Reparse |client_hello| from the buffer owned by |hs|.
|
||||
if (!hs->GetClientHello(&msg, &client_hello)) {
|
||||
OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
|
||||
return ssl_hs_error;
|
||||
}
|
||||
}
|
||||
|
||||
// We perform all our negotiation based on the first ClientHello (for
|
||||
// consistency with what |select_certificate_cb| observed), which is in the
|
||||
// transcript, so we can ignore most of this second one.
|
||||
@ -639,6 +696,7 @@ static enum ssl_hs_wait_t do_read_second_client_hello(SSL_HANDSHAKE *hs) {
|
||||
}
|
||||
|
||||
ssl->method->next_message(ssl);
|
||||
hs->ech_client_hello_buf.Reset();
|
||||
hs->tls13_state = state13_send_server_hello;
|
||||
return ssl_hs_ok;
|
||||
}
|
||||
@ -649,9 +707,8 @@ static enum ssl_hs_wait_t do_send_server_hello(SSL_HANDSHAKE *hs) {
|
||||
Span<uint8_t> random(ssl->s3->server_random);
|
||||
RAND_bytes(random.data(), random.size());
|
||||
|
||||
// If the ClientHello has an ech_is_inner extension, we must be the ECH
|
||||
// backend server. In response to ech_is_inner, we will overwrite part of the
|
||||
// ServerHello.random with the ECH acceptance confirmation.
|
||||
assert(!hs->ech_accept || hs->ech_is_inner_present);
|
||||
|
||||
if (hs->ech_is_inner_present) {
|
||||
// Construct the ServerHelloECHConf message, which is the same as
|
||||
// ServerHello, except the last 8 bytes of its random field are zeroed out.
|
||||
|
@ -60,6 +60,16 @@ static const struct argument kArguments[] = {
|
||||
{
|
||||
"-ocsp-response", kOptionalArgument, "OCSP response file to send",
|
||||
},
|
||||
{
|
||||
"-echconfig-key",
|
||||
kOptionalArgument,
|
||||
"File containing the private key corresponding to the ECHConfig.",
|
||||
},
|
||||
{
|
||||
"-echconfig",
|
||||
kOptionalArgument,
|
||||
"File containing one ECHConfig.",
|
||||
},
|
||||
{
|
||||
"-loop", kBooleanArgument,
|
||||
"The server will continue accepting new sequential connections.",
|
||||
@ -261,6 +271,47 @@ bool Server(const std::vector<std::string> &args) {
|
||||
}
|
||||
}
|
||||
|
||||
if (args_map.count("-echconfig-key") + args_map.count("-echconfig") == 1) {
|
||||
fprintf(stderr,
|
||||
"-echconfig and -echconfig-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"];
|
||||
|
||||
// Load the ECH private key.
|
||||
ScopedFILE echconfig_key_file(fopen(echconfig_key_path.c_str(), "rb"));
|
||||
std::vector<uint8_t> 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());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load the ECHConfig.
|
||||
ScopedFILE echconfig_file(fopen(echconfig_path.c_str(), "rb"));
|
||||
std::vector<uint8_t> echconfig;
|
||||
if (echconfig_file == nullptr ||
|
||||
!ReadAll(&echconfig, echconfig_file.get())) {
|
||||
fprintf(stderr, "Error reading %s\n", echconfig_path.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
bssl::UniquePtr<SSL_ECH_SERVER_CONFIG_LIST> configs(
|
||||
SSL_ECH_SERVER_CONFIG_LIST_new());
|
||||
if (!configs ||
|
||||
!SSL_ECH_SERVER_CONFIG_LIST_add(configs.get(),
|
||||
/*is_retry_config=*/1, echconfig.data(),
|
||||
echconfig.size(), echconfig_key.data(),
|
||||
echconfig_key.size()) ||
|
||||
!SSL_CTX_set1_ech_server_config_list(ctx.get(), configs.get())) {
|
||||
fprintf(stderr, "Error setting server's ECHConfig and private key\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (args_map.count("-cipher") != 0 &&
|
||||
!SSL_CTX_set_strict_cipher_list(ctx.get(), args_map["-cipher"].c_str())) {
|
||||
fprintf(stderr, "Failed setting cipher list\n");
|
||||
|
Loading…
x
Reference in New Issue
Block a user