summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gtests/nss_bogo_shim/nss_bogo_shim.cc7
-rw-r--r--gtests/ssl_gtest/ssl_extension_unittest.cc55
-rw-r--r--gtests/ssl_gtest/tls_ech_unittest.cc22
-rw-r--r--gtests/ssl_gtest/tls_filter.cc6
-rw-r--r--gtests/ssl_gtest/tls_filter.h13
-rw-r--r--gtests/ssl_gtest/tls_grease_unittest.cc10
-rw-r--r--lib/ssl/ssl.h7
-rw-r--r--lib/ssl/ssl3con.c13
-rw-r--r--lib/ssl/ssl3ext.c59
-rw-r--r--lib/ssl/ssl3ext.h2
-rw-r--r--lib/ssl/sslimpl.h4
-rw-r--r--lib/ssl/sslsecur.c2
-rw-r--r--lib/ssl/sslsock.c5
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;