diff options
-rw-r--r-- | gtests/nss_bogo_shim/nss_bogo_shim.cc | 7 | ||||
-rw-r--r-- | gtests/ssl_gtest/ssl_extension_unittest.cc | 55 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_ech_unittest.cc | 22 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_filter.cc | 6 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_filter.h | 13 | ||||
-rw-r--r-- | gtests/ssl_gtest/tls_grease_unittest.cc | 10 | ||||
-rw-r--r-- | lib/ssl/ssl.h | 7 | ||||
-rw-r--r-- | lib/ssl/ssl3con.c | 13 | ||||
-rw-r--r-- | lib/ssl/ssl3ext.c | 59 | ||||
-rw-r--r-- | lib/ssl/ssl3ext.h | 2 | ||||
-rw-r--r-- | lib/ssl/sslimpl.h | 4 | ||||
-rw-r--r-- | lib/ssl/sslsecur.c | 2 | ||||
-rw-r--r-- | lib/ssl/sslsock.c | 5 |
13 files changed, 204 insertions, 1 deletions
diff --git a/gtests/nss_bogo_shim/nss_bogo_shim.cc b/gtests/nss_bogo_shim/nss_bogo_shim.cc index 890ec7149..52a3e9a94 100644 --- a/gtests/nss_bogo_shim/nss_bogo_shim.cc +++ b/gtests/nss_bogo_shim/nss_bogo_shim.cc @@ -372,6 +372,12 @@ class TestAgent { if (rv != SECSuccess) return false; } + if (cfg_.get<bool>("permute-extensions")) { + rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_CH_EXTENSION_PERMUTATION, + PR_TRUE); + if (rv != SECSuccess) return false; + } + } else { // GREASE - BoGo expects servers to enable GREASE by default rv = SSL_OptionSet(ssl_fd_.get(), SSL_ENABLE_GREASE, PR_TRUE); @@ -907,6 +913,7 @@ std::unique_ptr<const Config> ReadConfig(int argc, char** argv) { cfg->AddEntry<bool>("enable-ech-grease", false); cfg->AddEntry<bool>("enable-early-data", false); cfg->AddEntry<bool>("enable-grease", false); + cfg->AddEntry<bool>("permute-extensions", false); cfg->AddEntry<bool>("on-resume-expect-reject-early-data", false); cfg->AddEntry<bool>("on-resume-expect-accept-early-data", false); cfg->AddEntry<bool>("expect-ticket-supports-early-data", false); diff --git a/gtests/ssl_gtest/ssl_extension_unittest.cc b/gtests/ssl_gtest/ssl_extension_unittest.cc index 08eb3cb02..59a6ea679 100644 --- a/gtests/ssl_gtest/ssl_extension_unittest.cc +++ b/gtests/ssl_gtest/ssl_extension_unittest.cc @@ -1357,6 +1357,61 @@ TEST_F(TlsConnectDatagram13, Dtls13RejectLegacyCookie) { client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT); } +TEST_P(TlsConnectGeneric, ClientHelloExtensionPermutation) { + EnsureTlsSetup(); + PR_ASSERT(SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_CH_EXTENSION_PERMUTATION, + PR_TRUE) == SECSuccess); + Connect(); +} + +/* This test checks that the ClientHello extension order is actually permuted + * if ss->opt.chXtnPermutation is set. It is asserted that at least one out of + * 10 extension orders differs from the others. + * + * This is a probabilistic test: The default TLS 1.3 ClientHello contains 8 + * extensions, leading to a 1/8! probability for any extension order and the + * same probability for two drawn extension orders to coincide. + * Since all sequences are compared against each other this leads to a false + * positive rate of (1/8!)^(n^2-n). + * To achieve a spurious failure rate << 1/2^64, we compare n=10 drawn orders. + * + * This test assures that randomisation is happening but does not check quality + * of the used Fisher-Yates shuffle. */ +TEST_F(TlsConnectStreamTls13, + ClientHelloExtensionPermutationProbabilisticTest) { + std::vector<std::vector<uint16_t>> orders; + + /* Capture the extension order of 10 ClientHello messages. */ + for (size_t i = 0; i < 10; i++) { + client_->StartConnect(); + /* Enable ClientHello extension permutation. */ + PR_ASSERT(SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_CH_EXTENSION_PERMUTATION, + PR_TRUE) == SECSuccess); + /* Capture extension order filter. */ + auto filter = MakeTlsFilter<TlsExtensionOrderCapture>( + client_, kTlsHandshakeClientHello); + /* Send ClientHello. */ + client_->Handshake(); + /* Remember extension order. */ + orders.push_back(filter->order); + /* Reset client / server state. */ + Reset(); + } + + /* Check for extension order inequality. */ + size_t inequal = 0; + for (auto& outerOrders : orders) { + for (auto& innerOrders : orders) { + if (outerOrders != innerOrders) { + inequal++; + } + } + } + PR_ASSERT(inequal >= 1); +} + INSTANTIATE_TEST_SUITE_P( ExtensionStream, TlsExtensionTestGeneric, ::testing::Combine(TlsConnectTestBase::kTlsVariantsStream, diff --git a/gtests/ssl_gtest/tls_ech_unittest.cc b/gtests/ssl_gtest/tls_ech_unittest.cc index ab61beec5..1e70a6ee5 100644 --- a/gtests/ssl_gtest/tls_ech_unittest.cc +++ b/gtests/ssl_gtest/tls_ech_unittest.cc @@ -2884,6 +2884,28 @@ TEST_F(TlsConnectStreamTls13Ech, EchPublicNameNotLdh) { ValidatePublicNames(kNotLdh, SECFailure); } +TEST_F(TlsConnectStreamTls13, EchClientHelloExtensionPermutation) { + EnsureTlsSetup(); + PR_ASSERT(SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_CH_EXTENSION_PERMUTATION, + PR_TRUE) == SECSuccess); + SetupEch(client_, server_); + + client_->ExpectEch(); + server_->ExpectEch(); + Connect(); +} + +TEST_F(TlsConnectStreamTls13, EchGreaseClientHelloExtensionPermutation) { + EnsureTlsSetup(); + PR_ASSERT(SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_CH_EXTENSION_PERMUTATION, + PR_TRUE) == SECSuccess); + PR_ASSERT(SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE) == + SECSuccess); + Connect(); +} + INSTANTIATE_TEST_SUITE_P(EchAgentTest, TlsAgentEchTest, ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll, TlsConnectTestBase::kTlsV13)); diff --git a/gtests/ssl_gtest/tls_filter.cc b/gtests/ssl_gtest/tls_filter.cc index 00fececee..2851619b0 100644 --- a/gtests/ssl_gtest/tls_filter.cc +++ b/gtests/ssl_gtest/tls_filter.cc @@ -1015,6 +1015,12 @@ PacketFilter::Action TlsExtensionFilter::FilterExtensions( return KEEP; } +PacketFilter::Action TlsExtensionOrderCapture::FilterExtension( + uint16_t extension_type, const DataBuffer& input, DataBuffer* output) { + order.push_back(extension_type); + return KEEP; +} + PacketFilter::Action TlsExtensionCapture::FilterExtension( uint16_t extension_type, const DataBuffer& input, DataBuffer* output) { if (extension_type == extension_ && (last_ || !captured_)) { diff --git a/gtests/ssl_gtest/tls_filter.h b/gtests/ssl_gtest/tls_filter.h index d7136c0fd..fc88d0b18 100644 --- a/gtests/ssl_gtest/tls_filter.h +++ b/gtests/ssl_gtest/tls_filter.h @@ -488,6 +488,19 @@ class TlsExtensionFilter : public TlsHandshakeFilter { DataBuffer* output); }; +class TlsExtensionOrderCapture : public TlsExtensionFilter { + public: + TlsExtensionOrderCapture(const std::shared_ptr<TlsAgent>& a, uint8_t message) + : TlsExtensionFilter(a, {message}){}; + + std::vector<uint16_t> order; + + protected: + PacketFilter::Action FilterExtension(uint16_t extension_type, + const DataBuffer& input, + DataBuffer* output) override; +}; + class TlsExtensionCapture : public TlsExtensionFilter { public: TlsExtensionCapture(const std::shared_ptr<TlsAgent>& a, uint16_t ext, diff --git a/gtests/ssl_gtest/tls_grease_unittest.cc b/gtests/ssl_gtest/tls_grease_unittest.cc index a3d5bbf45..8580ee1fc 100644 --- a/gtests/ssl_gtest/tls_grease_unittest.cc +++ b/gtests/ssl_gtest/tls_grease_unittest.cc @@ -473,6 +473,16 @@ TEST_P(GreaseTestStreamTls13, GreasedClientCertificateExtensions) { client_->CheckErrorCode(SSL_ERROR_DECRYPT_ERROR_ALERT); } +TEST_F(TlsConnectStreamTls13, GreaseClientHelloExtensionPermutation) { + EnsureTlsSetup(); + PR_ASSERT(SSL_OptionSet(client_->ssl_fd(), + SSL_ENABLE_CH_EXTENSION_PERMUTATION, + PR_TRUE) == SECSuccess); + PR_ASSERT(SSL_OptionSet(client_->ssl_fd(), SSL_ENABLE_GREASE, PR_TRUE) == + SECSuccess); + Connect(); +} + const uint8_t kTlsGreaseExtensionMessages[] = {kTlsHandshakeEncryptedExtensions, kTlsHandshakeCertificate}; diff --git a/lib/ssl/ssl.h b/lib/ssl/ssl.h index d2e92b170..fc817a781 100644 --- a/lib/ssl/ssl.h +++ b/lib/ssl/ssl.h @@ -373,6 +373,13 @@ SSL_IMPORT PRFileDesc *DTLS_ImportFD(PRFileDesc *model, PRFileDesc *fd); */ #define SSL_ENABLE_GREASE 42 +/* Enables TLS ClientHello Extension Permutation. + * + * On a TLS ClientHello all extensions but the Psk extension + * (which MUST be last) will be sent in randomly shuffeld order. + */ +#define SSL_ENABLE_CH_EXTENSION_PERMUTATION 43 + #ifdef SSL_DEPRECATED_FUNCTION /* Old deprecated function names */ SSL_IMPORT SECStatus SSL_Enable(PRFileDesc *fd, int option, PRIntn on); diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c index 982d8587b..867c13ee7 100644 --- a/lib/ssl/ssl3con.c +++ b/lib/ssl/ssl3con.c @@ -5532,6 +5532,16 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type) } } + /* Setup TLS ClientHello Extension Permutation? */ + if (type == client_hello_initial && + ss->vrange.max > SSL_LIBRARY_VERSION_3_0 && + ss->opt.enableChXtnPermutation) { + rv = tls_ClientHelloExtensionPermutationSetup(ss); + if (rv != SECSuccess) { + goto loser; + } + } + if (isTLS || (ss->firstHsDone && ss->peerRequestedProtection)) { rv = ssl_ConstructExtensions(ss, &extensionBuf, ssl_hs_client_hello); if (rv != SECSuccess) { @@ -14134,6 +14144,9 @@ ssl3_DestroySSL3Info(sslSocket *ss) /* TLS 1.3 GREASE (client) state. */ tls13_ClientGreaseDestroy(ss); + + /* TLS ClientHello Extension Permutation state. */ + tls_ClientHelloExtensionPermutationDestroy(ss); } /* check if the current cipher spec is FIPS. We only need to diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c index 04731115f..c4f9c92d4 100644 --- a/lib/ssl/ssl3ext.c +++ b/lib/ssl/ssl3ext.c @@ -760,7 +760,12 @@ ssl_ConstructExtensions(sslSocket *ss, sslBuffer *buf, SSLHandshakeType message) switch (message) { case ssl_hs_client_hello: if (ss->vrange.max > SSL_LIBRARY_VERSION_3_0) { - sender = clientHelloSendersTLS; + /* Use TLS ClientHello Extension Permutation? */ + if (ss->opt.enableChXtnPermutation) { + sender = ss->ssl3.hs.chExtensionPermutation; + } else { + sender = clientHelloSendersTLS; + } } else { sender = clientHelloSendersSSL3; } @@ -1119,3 +1124,55 @@ ssl3_ExtConsumeHandshakeVariable(const sslSocket *ss, SECItem *i, { return ssl3_ConsumeHandshakeVariable((sslSocket *)ss, i, bytes, b, length); } + +SECStatus +tls_ClientHelloExtensionPermutationSetup(sslSocket *ss) +{ + const size_t buildersLen = PR_ARRAY_SIZE(clientHelloSendersTLS); + const size_t buildersSize = (sizeof(sslExtensionBuilder) * buildersLen); + /* Psk Extension and then NULL entry MUST be last. */ + const size_t permutationLen = buildersLen - 2; + + sslExtensionBuilder *builders = PORT_Alloc(buildersSize); + if (!builders) { + return SECFailure; + } + + /* Get a working copy of default builders. */ + PORT_Memcpy(builders, clientHelloSendersTLS, buildersSize); + + /* Get permutation randoms. */ + uint8_t random[permutationLen]; + if (PK11_GenerateRandom(random, permutationLen) != SECSuccess) { + PORT_Free(builders); + return SECFailure; + } + + /* Fisher-Yates Shuffle */ + for (size_t i = permutationLen - 1; i > 0; i--) { + size_t idx = random[i - 1] % (i + 1); + sslExtensionBuilder tmp = builders[i]; + builders[i] = builders[idx]; + builders[idx] = tmp; + } + + /* Make sure that Psk extension is penultimate (before NULL entry). */ + PR_ASSERT(builders[buildersLen - 2].ex_type == ssl_tls13_pre_shared_key_xtn); + + if (ss->ssl3.hs.chExtensionPermutation) { + PORT_Free(builders); + return SECFailure; + } + ss->ssl3.hs.chExtensionPermutation = builders; + + return SECSuccess; +} + +void +tls_ClientHelloExtensionPermutationDestroy(sslSocket *ss) +{ + if (ss->ssl3.hs.chExtensionPermutation) { + PORT_Free(ss->ssl3.hs.chExtensionPermutation); + ss->ssl3.hs.chExtensionPermutation = NULL; + } +} diff --git a/lib/ssl/ssl3ext.h b/lib/ssl/ssl3ext.h index 73daffb08..d582e2b2c 100644 --- a/lib/ssl/ssl3ext.h +++ b/lib/ssl/ssl3ext.h @@ -206,5 +206,7 @@ SECStatus SSLExp_InstallExtensionHooks( sslCustomExtensionHooks *ssl_FindCustomExtensionHooks(sslSocket *ss, PRUint16 extension); SECStatus ssl_CallCustomExtensionSenders(sslSocket *ss, sslBuffer *buf, SSLHandshakeType message); +SECStatus tls_ClientHelloExtensionPermutationSetup(sslSocket *ss); +void tls_ClientHelloExtensionPermutationDestroy(sslSocket *ss); #endif diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h index 64c657788..4486d0df5 100644 --- a/lib/ssl/sslimpl.h +++ b/lib/ssl/sslimpl.h @@ -291,6 +291,7 @@ typedef struct sslOptionsStr { unsigned int enableTls13BackendEch : 1; unsigned int callExtensionWriterOnEchInner : 1; unsigned int enableGrease : 1; + unsigned int enableChXtnPermutation : 1; } sslOptions; typedef enum { sslHandshakingUndetermined = 0, @@ -784,6 +785,9 @@ typedef struct SSL3HandshakeStateStr { /* TLS 1.3 GREASE state. */ tls13ClientGrease *grease; + + /* ClientHello Extension Permutation state. */ + sslExtensionBuilder *chExtensionPermutation; } SSL3HandshakeState; #define SSL_ASSERT_HASHES_EMPTY(ss) \ diff --git a/lib/ssl/sslsecur.c b/lib/ssl/sslsecur.c index 37affd36a..4a0563703 100644 --- a/lib/ssl/sslsecur.c +++ b/lib/ssl/sslsecur.c @@ -219,6 +219,8 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool asServer) tls13_ClientGreaseDestroy(ss); + tls_ClientHelloExtensionPermutationDestroy(ss); + if (!ss->TCPconnected) ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr)); diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c index 3d1603d92..9a778218a 100644 --- a/lib/ssl/sslsock.c +++ b/lib/ssl/sslsock.c @@ -97,6 +97,7 @@ static sslOptions ssl_defaults = { .enableTls13BackendEch = PR_FALSE, .callExtensionWriterOnEchInner = PR_FALSE, .enableGrease = PR_FALSE, + .enableChXtnPermutation = PR_FALSE }; /* @@ -896,6 +897,10 @@ SSL_OptionSet(PRFileDesc *fd, PRInt32 which, PRIntn val) ss->opt.enableGrease = val; break; + case SSL_ENABLE_CH_EXTENSION_PERMUTATION: + ss->opt.enableChXtnPermutation = val; + break; + default: PORT_SetError(SEC_ERROR_INVALID_ARGS); rv = SECFailure; |