Skip to content

Commit e9c5d72

Browse files
davidbenBoringssl LUCI CQ
authored and
Boringssl LUCI CQ
committed
Add an option to permute ClientHello extension order.
Although not permitted by the TLS specification, systems sometimes ossify TLS extension order, or byte offsets of various fields. To keep the ecosystem healthy, add an API to reorder ClientHello extensions. Since ECH, HelloRetryRequest, and HelloVerifyRequest are sensitive to extension order, I've implemented this by per-connection permutation of the indices in the kExtensions structure. This ensures that all ClientHellos within a connection are consistently ordered. As follow-up work, permuting the other messages would also be nice, though any server messages would need to be incorporated in handshake hints. Change-Id: I18ce39b4df5ee376c654943f07ec26a50e0923a9 Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/48045 Commit-Queue: David Benjamin <[email protected]> Reviewed-by: Adam Langley <[email protected]>
1 parent 5358cb5 commit e9c5d72

11 files changed

+282
-4
lines changed

include/openssl/base.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ extern "C" {
195195
// A consumer may use this symbol in the preprocessor to temporarily build
196196
// against multiple revisions of BoringSSL at the same time. It is not
197197
// recommended to do so for longer than is necessary.
198-
#define BORINGSSL_API_VERSION 15
198+
#define BORINGSSL_API_VERSION 16
199199

200200
#if defined(BORINGSSL_SHARED_LIBRARY)
201201

include/openssl/ssl.h

+8
Original file line numberDiff line numberDiff line change
@@ -4301,6 +4301,14 @@ OPENSSL_EXPORT void SSL_CTX_set_retain_only_sha256_of_client_certs(SSL_CTX *ctx,
43014301
// GREASE. See RFC 8701.
43024302
OPENSSL_EXPORT void SSL_CTX_set_grease_enabled(SSL_CTX *ctx, int enabled);
43034303

4304+
// SSL_CTX_set_permute_extensions configures whether sockets on |ctx| should
4305+
// permute extensions. For now, this is only implemented for the ClientHello.
4306+
OPENSSL_EXPORT void SSL_CTX_set_permute_extensions(SSL_CTX *ctx, int enabled);
4307+
4308+
// SSL_set_permute_extensions configures whether sockets on |ssl| should
4309+
// permute extensions. For now, this is only implemented for the ClientHello.
4310+
OPENSSL_EXPORT void SSL_set_permute_extensions(SSL *ssl, int enabled);
4311+
43044312
// SSL_max_seal_overhead returns the maximum overhead, in bytes, of sealing a
43054313
// record with |ssl|.
43064314
OPENSSL_EXPORT size_t SSL_max_seal_overhead(const SSL *ssl);

ssl/handshake_client.cc

+1
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,7 @@ static enum ssl_hs_wait_t do_start_connect(SSL_HANDSHAKE *hs) {
535535
}
536536

537537
if (!ssl_setup_key_shares(hs, /*override_group_id=*/0) ||
538+
!ssl_setup_extension_permutation(hs) ||
538539
!ssl_encrypt_client_hello(hs, MakeConstSpan(ech_enc, ech_enc_len)) ||
539540
!ssl_add_client_hello(hs)) {
540541
return ssl_hs_error;

ssl/internal.h

+15
Original file line numberDiff line numberDiff line change
@@ -1855,6 +1855,11 @@ struct SSL_HANDSHAKE {
18551855
// peer_key is the peer's ECDH key for a TLS 1.2 client.
18561856
Array<uint8_t> peer_key;
18571857

1858+
// extension_permutation is the permutation to apply to ClientHello
1859+
// extensions. It maps indices into the |kExtensions| table into other
1860+
// indices.
1861+
Array<uint8_t> extension_permutation;
1862+
18581863
// cert_compression_alg_id, for a server, contains the negotiated certificate
18591864
// compression algorithm for this client. It is only valid if
18601865
// |cert_compression_negotiated| is true.
@@ -2100,6 +2105,10 @@ bool tls13_process_new_session_ticket(SSL *ssl, const SSLMessage &msg);
21002105
bssl::UniquePtr<SSL_SESSION> tls13_create_session_with_ticket(SSL *ssl,
21012106
CBS *body);
21022107

2108+
// ssl_setup_extension_permutation computes a ClientHello extension permutation
2109+
// for |hs|, if applicable. It returns true on success and false on error.
2110+
bool ssl_setup_extension_permutation(SSL_HANDSHAKE *hs);
2111+
21032112
// ssl_setup_key_shares computes client key shares and saves them in |hs|. It
21042113
// returns true on success and false on failure. If |override_group_id| is zero,
21052114
// it offers the default groups, including GREASE. If it is non-zero, it offers
@@ -3028,6 +3037,9 @@ struct SSL_CONFIG {
30283037
// QUIC drafts up to and including 32 used a different TLS extension
30293038
// codepoint to convey QUIC's transport parameters.
30303039
bool quic_use_legacy_codepoint : 1;
3040+
3041+
// permute_extensions is whether to permute extensions when sending messages.
3042+
bool permute_extensions : 1;
30313043
};
30323044

30333045
// From RFC 8446, used in determining PSK modes.
@@ -3615,6 +3627,9 @@ struct ssl_ctx_st {
36153627
// grease_enabled is whether GREASE (RFC 8701) is enabled.
36163628
bool grease_enabled : 1;
36173629

3630+
// permute_extensions is whether to permute extensions when sending messages.
3631+
bool permute_extensions : 1;
3632+
36183633
// allow_unknown_alpn_protos is whether the client allows unsolicited ALPN
36193634
// protocols from the peer.
36203635
bool allow_unknown_alpn_protos : 1;

ssl/ssl_lib.cc

+15-1
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,7 @@ ssl_ctx_st::ssl_ctx_st(const SSL_METHOD *ssl_method)
562562
signed_cert_timestamps_enabled(false),
563563
channel_id_enabled(false),
564564
grease_enabled(false),
565+
permute_extensions(false),
565566
allow_unknown_alpn_protos(false),
566567
false_start_allowed_without_alpn(false),
567568
handoff(false),
@@ -684,6 +685,7 @@ SSL *SSL_new(SSL_CTX *ctx) {
684685
ssl->config->custom_verify_callback = ctx->custom_verify_callback;
685686
ssl->config->retain_only_sha256_of_client_certs =
686687
ctx->retain_only_sha256_of_client_certs;
688+
ssl->config->permute_extensions = ctx->permute_extensions;
687689

688690
if (!ssl->config->supported_group_list.CopyFrom(ctx->supported_group_list) ||
689691
!ssl->config->alpn_client_proto_list.CopyFrom(
@@ -730,7 +732,8 @@ SSL_CONFIG::SSL_CONFIG(SSL *ssl_arg)
730732
handoff(false),
731733
shed_handshake_config(false),
732734
jdk11_workaround(false),
733-
quic_use_legacy_codepoint(false) {
735+
quic_use_legacy_codepoint(false),
736+
permute_extensions(false) {
734737
assert(ssl);
735738
}
736739

@@ -2915,6 +2918,17 @@ void SSL_CTX_set_grease_enabled(SSL_CTX *ctx, int enabled) {
29152918
ctx->grease_enabled = !!enabled;
29162919
}
29172920

2921+
void SSL_CTX_set_permute_extensions(SSL_CTX *ctx, int enabled) {
2922+
ctx->permute_extensions = !!enabled;
2923+
}
2924+
2925+
void SSL_set_permute_extensions(SSL *ssl, int enabled) {
2926+
if (!ssl->config) {
2927+
return;
2928+
}
2929+
ssl->config->permute_extensions = !!enabled;
2930+
}
2931+
29182932
int32_t SSL_get_ticket_age_skew(const SSL *ssl) {
29192933
return ssl->s3->ticket_age_skew;
29202934
}

ssl/ssl_test.cc

+122
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
#include <openssl/aead.h>
2828
#include <openssl/base64.h>
29+
#include <openssl/bytestring.h>
2930
#include <openssl/bio.h>
3031
#include <openssl/cipher.h>
3132
#include <openssl/crypto.h>
@@ -7369,5 +7370,126 @@ TEST(SSLTest, CanReleasePrivateKey) {
73697370
}
73707371
}
73717372

7373+
// GetExtensionOrder sets |*out| to the list of extensions a client attached to
7374+
// |ctx| will send in the ClientHello. If |ech_keys| is non-null, the client
7375+
// will offer ECH with the public component. If |decrypt_ech| is true, |*out|
7376+
// will be set to the ClientHelloInner's extensions, rather than
7377+
// ClientHelloOuter.
7378+
static bool GetExtensionOrder(SSL_CTX *client_ctx, std::vector<uint16_t> *out,
7379+
SSL_ECH_KEYS *ech_keys, bool decrypt_ech) {
7380+
struct AppData {
7381+
std::vector<uint16_t> *out;
7382+
bool decrypt_ech;
7383+
bool callback_done = false;
7384+
};
7385+
AppData app_data;
7386+
app_data.out = out;
7387+
app_data.decrypt_ech = decrypt_ech;
7388+
7389+
bssl::UniquePtr<SSL_CTX> server_ctx =
7390+
CreateContextWithTestCertificate(TLS_method());
7391+
if (!server_ctx || //
7392+
!SSL_CTX_set_app_data(server_ctx.get(), &app_data) ||
7393+
(decrypt_ech && !SSL_CTX_set1_ech_keys(server_ctx.get(), ech_keys))) {
7394+
return false;
7395+
}
7396+
7397+
// Configure the server to record the ClientHello extension order. We use a
7398+
// server rather than |GetClientHello| so it can decrypt ClientHelloInner.
7399+
SSL_CTX_set_select_certificate_cb(
7400+
server_ctx.get(),
7401+
[](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
7402+
AppData *app_data_ptr = static_cast<AppData *>(
7403+
SSL_CTX_get_app_data(SSL_get_SSL_CTX(client_hello->ssl)));
7404+
EXPECT_EQ(app_data_ptr->decrypt_ech ? 1 : 0,
7405+
SSL_ech_accepted(client_hello->ssl));
7406+
7407+
app_data_ptr->out->clear();
7408+
CBS extensions;
7409+
CBS_init(&extensions, client_hello->extensions,
7410+
client_hello->extensions_len);
7411+
while (CBS_len(&extensions)) {
7412+
uint16_t type;
7413+
CBS body;
7414+
if (!CBS_get_u16(&extensions, &type) ||
7415+
!CBS_get_u16_length_prefixed(&extensions, &body)) {
7416+
return ssl_select_cert_error;
7417+
}
7418+
app_data_ptr->out->push_back(type);
7419+
}
7420+
7421+
// Don't bother completing the handshake.
7422+
app_data_ptr->callback_done = true;
7423+
return ssl_select_cert_error;
7424+
});
7425+
7426+
bssl::UniquePtr<SSL> client, server;
7427+
if (!CreateClientAndServer(&client, &server, client_ctx, server_ctx.get()) ||
7428+
(ech_keys != nullptr && !InstallECHConfigList(client.get(), ech_keys))) {
7429+
return false;
7430+
}
7431+
7432+
// Run the handshake far enough to process the ClientHello.
7433+
SSL_do_handshake(client.get());
7434+
SSL_do_handshake(server.get());
7435+
return app_data.callback_done;
7436+
}
7437+
7438+
// Test that, when extension permutation is enabled, the ClientHello extension
7439+
// order changes, both with and without ECH, and in both ClientHelloInner and
7440+
// ClientHelloOuter.
7441+
TEST(SSLTest, PermuteExtensions) {
7442+
bssl::UniquePtr<SSL_ECH_KEYS> keys = MakeTestECHKeys();
7443+
ASSERT_TRUE(keys);
7444+
for (bool offer_ech : {false, true}) {
7445+
SCOPED_TRACE(offer_ech);
7446+
SSL_ECH_KEYS *maybe_keys = offer_ech ? keys.get() : nullptr;
7447+
for (bool decrypt_ech : {false, true}) {
7448+
SCOPED_TRACE(decrypt_ech);
7449+
if (!offer_ech && decrypt_ech) {
7450+
continue;
7451+
}
7452+
7453+
// When extension permutation is disabled, the order should be consistent.
7454+
bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
7455+
ASSERT_TRUE(ctx);
7456+
std::vector<uint16_t> order1, order2;
7457+
ASSERT_TRUE(
7458+
GetExtensionOrder(ctx.get(), &order1, maybe_keys, decrypt_ech));
7459+
ASSERT_TRUE(
7460+
GetExtensionOrder(ctx.get(), &order2, maybe_keys, decrypt_ech));
7461+
EXPECT_EQ(order1, order2);
7462+
7463+
ctx.reset(SSL_CTX_new(TLS_method()));
7464+
ASSERT_TRUE(ctx);
7465+
SSL_CTX_set_permute_extensions(ctx.get(), 1);
7466+
7467+
// When extension permutation is enabled, each ClientHello should have a
7468+
// different order.
7469+
//
7470+
// This test is inherently flaky, so we run it multiple times. We send at
7471+
// least five extensions by default from TLS 1.3: supported_versions,
7472+
// key_share, supported_groups, psk_key_exchange_modes, and
7473+
// signature_algorithms. That means the probability of a false negative is
7474+
// at most 1/120. Repeating the test 14 times lowers false negative rate
7475+
// to under 2^-96.
7476+
ASSERT_TRUE(
7477+
GetExtensionOrder(ctx.get(), &order1, maybe_keys, decrypt_ech));
7478+
EXPECT_GE(order1.size(), 5u);
7479+
static const int kNumIterations = 14;
7480+
bool passed = false;
7481+
for (int i = 0; i < kNumIterations; i++) {
7482+
ASSERT_TRUE(
7483+
GetExtensionOrder(ctx.get(), &order2, maybe_keys, decrypt_ech));
7484+
if (order1 != order2) {
7485+
passed = true;
7486+
break;
7487+
}
7488+
}
7489+
EXPECT_TRUE(passed) << "Extensions were not permuted";
7490+
}
7491+
}
7492+
}
7493+
73727494
} // namespace
73737495
BSSL_NAMESPACE_END

ssl/t1_lib.cc

+33-2
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
#include <openssl/hpke.h>
128128
#include <openssl/mem.h>
129129
#include <openssl/nid.h>
130+
#include <openssl/rand.h>
130131

131132
#include "../crypto/internal.h"
132133
#include "internal.h"
@@ -3271,6 +3272,30 @@ static_assert(kNumExtensions <=
32713272
sizeof(((SSL_HANDSHAKE *)NULL)->extensions.received) * 8,
32723273
"too many extensions for received bitset");
32733274

3275+
bool ssl_setup_extension_permutation(SSL_HANDSHAKE *hs) {
3276+
if (!hs->config->permute_extensions) {
3277+
return true;
3278+
}
3279+
3280+
static_assert(kNumExtensions <= UINT8_MAX,
3281+
"extensions_permutation type is too small");
3282+
uint32_t seeds[kNumExtensions - 1];
3283+
Array<uint8_t> permutation;
3284+
if (!RAND_bytes(reinterpret_cast<uint8_t *>(seeds), sizeof(seeds)) ||
3285+
!permutation.Init(kNumExtensions)) {
3286+
return false;
3287+
}
3288+
for (size_t i = 0; i < kNumExtensions; i++) {
3289+
permutation[i] = i;
3290+
}
3291+
for (size_t i = kNumExtensions - 1; i > 0; i--) {
3292+
// Set element |i| to a randomly-selected element 0 <= j <= i.
3293+
std::swap(permutation[i], permutation[seeds[i - 1] % (i + 1)]);
3294+
}
3295+
hs->extension_permutation = std::move(permutation);
3296+
return true;
3297+
}
3298+
32743299
static const struct tls_extension *tls_extension_find(uint32_t *out_index,
32753300
uint16_t value) {
32763301
unsigned i;
@@ -3328,7 +3353,10 @@ static bool ssl_add_clienthello_tlsext_inner(SSL_HANDSHAKE *hs, CBB *out,
33283353
}
33293354
}
33303355

3331-
for (size_t i = 0; i < kNumExtensions; i++) {
3356+
for (size_t unpermuted = 0; unpermuted < kNumExtensions; unpermuted++) {
3357+
size_t i = hs->extension_permutation.empty()
3358+
? unpermuted
3359+
: hs->extension_permutation[unpermuted];
33323360
const size_t len_before = CBB_len(&extensions);
33333361
const size_t len_compressed_before = CBB_len(compressed.get());
33343362
if (!kExtensions[i].add_clienthello(hs, &extensions, compressed.get(),
@@ -3462,7 +3490,10 @@ bool ssl_add_clienthello_tlsext(SSL_HANDSHAKE *hs, CBB *out, CBB *out_encoded,
34623490
}
34633491

34643492
bool last_was_empty = false;
3465-
for (size_t i = 0; i < kNumExtensions; i++) {
3493+
for (size_t unpermuted = 0; unpermuted < kNumExtensions; unpermuted++) {
3494+
size_t i = hs->extension_permutation.empty()
3495+
? unpermuted
3496+
: hs->extension_permutation[unpermuted];
34663497
size_t bytes_written;
34673498
if (omit_ech_len != 0 &&
34683499
kExtensions[i].value == TLSEXT_TYPE_encrypted_client_hello) {

ssl/test/runner/runner.go

+72
Original file line numberDiff line numberDiff line change
@@ -8208,6 +8208,78 @@ func addExtensionTests() {
82088208
base64.StdEncoding.EncodeToString(testSCTList),
82098209
},
82108210
})
8211+
8212+
// Extension permutation should interact correctly with other extensions,
8213+
// HelloVerifyRequest, HelloRetryRequest, and ECH. SSLTest.PermuteExtensions
8214+
// in ssl_test.cc tests that the extensions are actually permuted. This
8215+
// tests the handshake still works.
8216+
//
8217+
// This test also tests that all our extensions interact with each other.
8218+
for _, ech := range []bool{false, true} {
8219+
if ech && ver.version < VersionTLS13 {
8220+
continue
8221+
}
8222+
8223+
test := testCase{
8224+
protocol: protocol,
8225+
name: "AllExtensions-Client-Permute",
8226+
skipQUICALPNConfig: true,
8227+
config: Config{
8228+
MinVersion: ver.version,
8229+
MaxVersion: ver.version,
8230+
NextProtos: []string{"proto"},
8231+
ApplicationSettings: map[string][]byte{"proto": []byte("runner1")},
8232+
Bugs: ProtocolBugs{
8233+
SendServerNameAck: true,
8234+
ExpectServerName: "example.com",
8235+
ExpectGREASE: true,
8236+
},
8237+
},
8238+
resumeSession: true,
8239+
flags: []string{
8240+
"-permute-extensions",
8241+
"-enable-grease",
8242+
"-enable-ocsp-stapling",
8243+
"-enable-signed-cert-timestamps",
8244+
"-advertise-alpn", "\x05proto",
8245+
"-expect-alpn", "proto",
8246+
"-host-name", "example.com",
8247+
},
8248+
}
8249+
8250+
if ech {
8251+
test.name += "-ECH"
8252+
echConfig := generateServerECHConfig(&ECHConfig{ConfigID: 42})
8253+
test.config.ServerECHConfigs = []ServerECHConfig{echConfig}
8254+
test.flags = append(test.flags,
8255+
"-ech-config-list", base64.StdEncoding.EncodeToString(CreateECHConfigList(echConfig.ECHConfig.Raw)),
8256+
"-expect-ech-accept",
8257+
)
8258+
test.expectations.echAccepted = true
8259+
}
8260+
8261+
if ver.version >= VersionTLS13 {
8262+
// Trigger a HelloRetryRequest to test both ClientHellos. Note
8263+
// our DTLS tests always enable HelloVerifyRequest.
8264+
test.name += "-HelloRetryRequest"
8265+
8266+
// ALPS is only available on TLS 1.3.
8267+
test.config.ApplicationSettings = map[string][]byte{"proto": []byte("runner")}
8268+
test.flags = append(test.flags,
8269+
"-application-settings", "proto,shim",
8270+
"-expect-peer-application-settings", "runner")
8271+
test.expectations.peerApplicationSettings = []byte("shim")
8272+
}
8273+
8274+
if protocol == dtls {
8275+
test.config.SRTPProtectionProfiles = []uint16{SRTP_AES128_CM_HMAC_SHA1_80}
8276+
test.flags = append(test.flags, "-srtp-profiles", "SRTP_AES128_CM_SHA1_80")
8277+
test.expectations.srtpProtectionProfile = SRTP_AES128_CM_HMAC_SHA1_80
8278+
}
8279+
8280+
test.name += "-" + suffix
8281+
testCases = append(testCases, test)
8282+
}
82118283
}
82128284
}
82138285

0 commit comments

Comments
 (0)