summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--automation/abi-check/expected-report-libssl3.so.txt19
-rw-r--r--cmd/tstclnt/tstclnt.c54
-rw-r--r--cpputil/tls_parser.h1
-rw-r--r--gtests/ssl_gtest/libssl_internals.c16
-rw-r--r--gtests/ssl_gtest/libssl_internals.h2
-rw-r--r--gtests/ssl_gtest/manifest.mn2
-rw-r--r--gtests/ssl_gtest/ssl_auth_unittest.cc12
-rw-r--r--gtests/ssl_gtest/ssl_custext_unittest.cc2
-rw-r--r--gtests/ssl_gtest/ssl_extension_unittest.cc74
-rw-r--r--gtests/ssl_gtest/ssl_gtest.gyp2
-rw-r--r--gtests/ssl_gtest/ssl_tls13compat_unittest.cc25
-rw-r--r--gtests/ssl_gtest/tls_agent.cc7
-rw-r--r--gtests/ssl_gtest/tls_agent.h6
-rw-r--r--gtests/ssl_gtest/tls_connect.cc85
-rw-r--r--gtests/ssl_gtest/tls_connect.h13
-rw-r--r--gtests/ssl_gtest/tls_ech_unittest.cc1604
-rw-r--r--gtests/ssl_gtest/tls_esni_unittest.cc494
-rw-r--r--gtests/ssl_gtest/tls_filter.cc64
-rw-r--r--gtests/ssl_gtest/tls_filter.h32
-rw-r--r--lib/ssl/SSLerrs.h15
-rw-r--r--lib/ssl/manifest.mn2
-rw-r--r--lib/ssl/ssl.gyp2
-rw-r--r--lib/ssl/ssl3con.c444
-rw-r--r--lib/ssl/ssl3ext.c99
-rw-r--r--lib/ssl/ssl3ext.h27
-rw-r--r--lib/ssl/ssl3exthandle.c31
-rw-r--r--lib/ssl/ssl3exthandle.h2
-rw-r--r--lib/ssl/ssl3prot.h1
-rw-r--r--lib/ssl/sslencode.c19
-rw-r--r--lib/ssl/sslencode.h6
-rw-r--r--lib/ssl/sslerr.h13
-rw-r--r--lib/ssl/sslexp.h104
-rw-r--r--lib/ssl/sslimpl.h50
-rw-r--r--lib/ssl/sslinfo.c4
-rw-r--r--lib/ssl/sslsecur.c9
-rw-r--r--lib/ssl/sslsock.c72
-rw-r--r--lib/ssl/sslt.h22
-rw-r--r--lib/ssl/tls13con.c119
-rw-r--r--lib/ssl/tls13con.h1
-rw-r--r--lib/ssl/tls13ech.c2201
-rw-r--r--lib/ssl/tls13ech.h82
-rw-r--r--lib/ssl/tls13esni.c846
-rw-r--r--lib/ssl/tls13esni.h51
-rw-r--r--lib/ssl/tls13exthandle.c426
-rw-r--r--lib/ssl/tls13exthandle.h11
-rw-r--r--lib/ssl/tls13hashstate.c75
-rw-r--r--lib/ssl/tls13hashstate.h6
47 files changed, 5192 insertions, 2062 deletions
diff --git a/automation/abi-check/expected-report-libssl3.so.txt b/automation/abi-check/expected-report-libssl3.so.txt
index e69de29bb..5d9c15b2d 100644
--- a/automation/abi-check/expected-report-libssl3.so.txt
+++ b/automation/abi-check/expected-report-libssl3.so.txt
@@ -0,0 +1,19 @@
+
+2 functions with some indirect sub-type change:
+
+ [C] 'function SECStatus SSL_GetChannelInfo(PRFileDesc*, SSLChannelInfo*, PRUintn)' at sslinfo.c:14:1 has some indirect sub-type changes:
+ parameter 2 of type 'SSLChannelInfo*' has sub-type changes:
+ in pointed to type 'typedef SSLChannelInfo' at sslt.h:378:1:
+ underlying type 'struct SSLChannelInfoStr' at sslt.h:299:1 changed:
+ type size hasn't changed
+ 1 data member insertion:
+ 'PRBool SSLChannelInfoStr::echAccepted', at offset 992 (in bits) at sslt.h:374:1
+
+ [C] 'function SECStatus SSL_GetPreliminaryChannelInfo(PRFileDesc*, SSLPreliminaryChannelInfo*, PRUintn)' at sslinfo.c:122:1 has some indirect sub-type changes:
+ parameter 2 of type 'SSLPreliminaryChannelInfo*' has sub-type changes:
+ in pointed to type 'typedef SSLPreliminaryChannelInfo' at sslt.h:446:1:
+ underlying type 'struct SSLPreliminaryChannelInfoStr' at sslt.h:386:1 changed:
+ type size changed from 288 to 384 (in bits)
+ 2 data member insertions:
+ 'PRBool SSLPreliminaryChannelInfoStr::echAccepted', at offset 288 (in bits) at sslt.h:439:1
+ 'const char* SSLPreliminaryChannelInfoStr::echPublicName', at offset 320 (in bits) at sslt.h:442:1
diff --git a/cmd/tstclnt/tstclnt.c b/cmd/tstclnt/tstclnt.c
index c37df118e..5207bc062 100644
--- a/cmd/tstclnt/tstclnt.c
+++ b/cmd/tstclnt/tstclnt.c
@@ -231,7 +231,7 @@ PrintUsageHeader()
" [-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n"
" [-I groups] [-J signatureschemes]\n"
" [-A requestfile] [-L totalconnections] [-P {client,server}]\n"
- " [-N encryptedSniKeys] [-Q] [-z externalPsk]\n"
+ " [-N echConfigs] [-Q] [-z externalPsk]\n"
"\n",
progName);
}
@@ -316,7 +316,7 @@ PrintParameterUsage()
fprintf(stderr, "%-20s Enable alternative TLS 1.3 handshake\n", "-X alt-server-hello");
fprintf(stderr, "%-20s Use DTLS\n", "-P {client, server}");
fprintf(stderr, "%-20s Exit after handshake\n", "-Q");
- fprintf(stderr, "%-20s Encrypted SNI Keys\n", "-N");
+ fprintf(stderr, "%-20s Use Encrypted Client Hello with the given Base64-encoded ECHConfigs\n", "-N");
fprintf(stderr, "%-20s Enable post-handshake authentication\n"
"%-20s for TLS 1.3; need to specify -n\n",
"-E", "");
@@ -1010,7 +1010,7 @@ PRBool stopAfterHandshake = PR_FALSE;
PRBool requestToExit = PR_FALSE;
char *versionString = NULL;
PRBool handshakeComplete = PR_FALSE;
-char *encryptedSNIKeys = NULL;
+char *echConfigs = NULL;
PRBool enablePostHandshakeAuth = PR_FALSE;
PRBool enableDelegatedCredentials = PR_FALSE;
const secuExporter *enabledExporters = NULL;
@@ -1263,6 +1263,30 @@ importPsk(PRFileDesc *s)
return rv;
}
+static SECStatus
+printEchRetryConfigs(PRFileDesc *s)
+{
+ if (PORT_GetError() == SSL_ERROR_ECH_RETRY_WITH_ECH) {
+ SECItem retries = { siBuffer, NULL, 0 };
+ SECStatus rv = SSL_GetEchRetryConfigs(s, &retries);
+ if (rv != SECSuccess) {
+ SECU_PrintError(progName, "SSL_GetEchRetryConfigs failed");
+ return SECFailure;
+ }
+ char *retriesBase64 = NSSBase64_EncodeItem(NULL, NULL, 0, &retries);
+ if (!retriesBase64) {
+ SECU_PrintError(progName, "NSSBase64_EncodeItem on retry_configs failed");
+ SECITEM_FreeItem(&retries, PR_FALSE);
+ return SECFailure;
+ }
+
+ fprintf(stderr, "Received ECH retry_configs: \n%s\n", retriesBase64);
+ PORT_Free(retriesBase64);
+ SECITEM_FreeItem(&retries, PR_FALSE);
+ }
+ return SECSuccess;
+}
+
static int
run()
{
@@ -1511,21 +1535,20 @@ run()
}
}
- if (encryptedSNIKeys) {
- SECItem esniKeysBin = { siBuffer, NULL, 0 };
+ if (echConfigs) {
+ SECItem echConfigsBin = { siBuffer, NULL, 0 };
- if (!NSSBase64_DecodeBuffer(NULL, &esniKeysBin, encryptedSNIKeys,
- strlen(encryptedSNIKeys))) {
- SECU_PrintError(progName, "ESNIKeys record is invalid base64");
+ if (!NSSBase64_DecodeBuffer(NULL, &echConfigsBin, echConfigs,
+ strlen(echConfigs))) {
+ SECU_PrintError(progName, "ECHConfigs record is invalid base64");
error = 1;
goto done;
}
- rv = SSL_EnableESNI(s, esniKeysBin.data, esniKeysBin.len,
- "dummy.invalid");
- SECITEM_FreeItem(&esniKeysBin, PR_FALSE);
+ rv = SSL_SetClientEchConfigs(s, echConfigsBin.data, echConfigsBin.len);
+ SECITEM_FreeItem(&echConfigsBin, PR_FALSE);
if (rv < 0) {
- SECU_PrintError(progName, "SSL_EnableESNI failed");
+ SECU_PrintError(progName, "SSL_SetClientEchConfigs failed");
error = 1;
goto done;
}
@@ -1702,6 +1725,9 @@ run()
} else {
error = writeBytesToServer(s, buf, nb);
if (error) {
+ if (echConfigs) {
+ (void)printEchRetryConfigs(s);
+ }
goto done;
}
pollset[SSOCK_FD].in_flags = PR_POLL_READ;
@@ -1881,7 +1907,7 @@ main(int argc, char **argv)
break;
case 'N':
- encryptedSNIKeys = PORT_Strdup(optstate->value);
+ echConfigs = PORT_Strdup(optstate->value);
break;
case 'P':
@@ -2257,7 +2283,7 @@ done:
PORT_Free(pwdata.data);
PORT_Free(host);
PORT_Free(zeroRttData);
- PORT_Free(encryptedSNIKeys);
+ PORT_Free(echConfigs);
SECITEM_ZfreeItem(&psk, PR_FALSE);
SECITEM_ZfreeItem(&pskLabel, PR_FALSE);
diff --git a/cpputil/tls_parser.h b/cpputil/tls_parser.h
index 6636b3c6a..41d760ed0 100644
--- a/cpputil/tls_parser.h
+++ b/cpputil/tls_parser.h
@@ -56,6 +56,7 @@ const uint8_t kTlsAlertUnsupportedExtension = 110;
const uint8_t kTlsAlertUnrecognizedName = 112;
const uint8_t kTlsAlertCertificateRequired = 116;
const uint8_t kTlsAlertNoApplicationProtocol = 120;
+const uint8_t kTlsAlertEchRequired = 121;
const uint8_t kTlsFakeChangeCipherSpec[] = {
ssl_ct_change_cipher_spec, // Type
diff --git a/gtests/ssl_gtest/libssl_internals.c b/gtests/ssl_gtest/libssl_internals.c
index 854eca07f..01d698e71 100644
--- a/gtests/ssl_gtest/libssl_internals.c
+++ b/gtests/ssl_gtest/libssl_internals.c
@@ -8,8 +8,10 @@
#include "libssl_internals.h"
#include "nss.h"
+#include "pk11hpke.h"
#include "pk11pub.h"
#include "pk11priv.h"
+#include "tls13ech.h"
#include "seccomon.h"
#include "selfencrypt.h"
#include "secmodti.h"
@@ -481,3 +483,17 @@ SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) {
ssl_ReleaseSSL3HandshakeLock(ss);
return SECSuccess;
}
+
+SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf,
+ size_t len) {
+ sslSocket *ss = ssl_FindSocket(fd);
+ if (!ss) {
+ return SECFailure;
+ }
+
+ sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
+ SECITEM_FreeItem(&cfg->raw, PR_FALSE);
+ SECITEM_AllocItem(NULL, &cfg->raw, len);
+ memcpy(cfg->raw.data, buf, len);
+ return SECSuccess;
+}
diff --git a/gtests/ssl_gtest/libssl_internals.h b/gtests/ssl_gtest/libssl_internals.h
index 4b076c2ee..a6a3239d5 100644
--- a/gtests/ssl_gtest/libssl_internals.h
+++ b/gtests/ssl_gtest/libssl_internals.h
@@ -49,5 +49,7 @@ SECStatus SSLInt_SetDCAdvertisedSigSchemes(PRFileDesc *fd,
const SSLSignatureScheme *schemes,
uint32_t num_sig_schemes);
SECStatus SSLInt_RemoveServerCertificates(PRFileDesc *fd);
+SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf,
+ size_t len);
#endif // ndef libssl_internals_h_
diff --git a/gtests/ssl_gtest/manifest.mn b/gtests/ssl_gtest/manifest.mn
index 2cfa7cdd2..af3081e8e 100644
--- a/gtests/ssl_gtest/manifest.mn
+++ b/gtests/ssl_gtest/manifest.mn
@@ -58,7 +58,7 @@ CPPSRCS = \
tls_protect.cc \
tls_psk_unittest.cc \
tls_subcerts_unittest.cc \
- tls_esni_unittest.cc \
+ tls_ech_unittest.cc \
$(SSLKEYLOGFILE_FILES) \
$(NULL)
diff --git a/gtests/ssl_gtest/ssl_auth_unittest.cc b/gtests/ssl_gtest/ssl_auth_unittest.cc
index 5c6eee7b6..2daed68cc 100644
--- a/gtests/ssl_gtest/ssl_auth_unittest.cc
+++ b/gtests/ssl_gtest/ssl_auth_unittest.cc
@@ -660,6 +660,18 @@ TEST_P(TlsConnectGeneric, ClientAuthEcdsa) {
CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa);
}
+#ifdef NSS_ENABLE_DRAFT_HPKE
+TEST_P(TlsConnectGeneric, ClientAuthWithEch) {
+ Reset(TlsAgent::kServerEcdsa256);
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetupClientAuth();
+ server_->RequestClientAuth(true);
+ Connect();
+ CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa);
+}
+#endif
+
TEST_P(TlsConnectGeneric, ClientAuthBigRsa) {
Reset(TlsAgent::kServerRsa, TlsAgent::kRsa2048);
client_->SetupClientAuth();
diff --git a/gtests/ssl_gtest/ssl_custext_unittest.cc b/gtests/ssl_gtest/ssl_custext_unittest.cc
index 68c789a38..bb322430c 100644
--- a/gtests/ssl_gtest/ssl_custext_unittest.cc
+++ b/gtests/ssl_gtest/ssl_custext_unittest.cc
@@ -67,8 +67,8 @@ static const uint16_t kManyExtensions[] = {
ssl_tls13_certificate_authorities_xtn,
ssl_next_proto_nego_xtn,
ssl_renegotiation_info_xtn,
- ssl_tls13_short_header_xtn,
ssl_record_size_limit_xtn,
+ ssl_tls13_encrypted_client_hello_xtn,
1,
0xffff};
// The list here includes all extensions we expect to use (SSL_MAX_EXTENSIONS),
diff --git a/gtests/ssl_gtest/ssl_extension_unittest.cc b/gtests/ssl_gtest/ssl_extension_unittest.cc
index fb995953f..019b6ea1d 100644
--- a/gtests/ssl_gtest/ssl_extension_unittest.cc
+++ b/gtests/ssl_gtest/ssl_extension_unittest.cc
@@ -83,63 +83,6 @@ class TlsExtensionTruncator : public TlsExtensionFilter {
size_t length_;
};
-class TlsExtensionAppender : public TlsHandshakeFilter {
- public:
- TlsExtensionAppender(const std::shared_ptr<TlsAgent>& a,
- uint8_t handshake_type, uint16_t ext, DataBuffer& data)
- : TlsHandshakeFilter(a, {handshake_type}), extension_(ext), data_(data) {}
-
- virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
- const DataBuffer& input,
- DataBuffer* output) {
- TlsParser parser(input);
- if (!TlsExtensionFilter::FindExtensions(&parser, header)) {
- return KEEP;
- }
- *output = input;
-
- // Increase the length of the extensions block.
- if (!UpdateLength(output, parser.consumed(), 2)) {
- return KEEP;
- }
-
- // Extensions in Certificate are nested twice. Increase the size of the
- // certificate list.
- if (header.handshake_type() == kTlsHandshakeCertificate) {
- TlsParser p2(input);
- if (!p2.SkipVariable(1)) {
- ADD_FAILURE();
- return KEEP;
- }
- if (!UpdateLength(output, p2.consumed(), 3)) {
- return KEEP;
- }
- }
-
- size_t offset = output->len();
- offset = output->Write(offset, extension_, 2);
- WriteVariable(output, offset, data_, 2);
-
- return CHANGE;
- }
-
- private:
- bool UpdateLength(DataBuffer* output, size_t offset, size_t size) {
- uint32_t len;
- if (!output->Read(offset, size, &len)) {
- ADD_FAILURE();
- return false;
- }
-
- len += 4 + data_.len();
- output->Write(offset, len, size);
- return true;
- }
-
- const uint16_t extension_;
- const DataBuffer data_;
-};
-
class TlsExtensionTestBase : public TlsConnectTestBase {
protected:
TlsExtensionTestBase(SSLProtocolVariant variant, uint16_t version)
@@ -1155,6 +1098,22 @@ TEST_P(TlsExtensionTest13, HrrThenRemoveSupportedGroups) {
SSL_ERROR_MISSING_SUPPORTED_GROUPS_EXTENSION);
}
+#ifdef NSS_ENABLE_DRAFT_HPKE
+TEST_P(TlsExtensionTest13, HrrThenRemoveEch) {
+ if (variant_ == ssl_variant_datagram) {
+ // ECH not supported in DTLS.
+ return;
+ }
+
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ ExpectAlert(server_, kTlsAlertIllegalParameter);
+ HrrThenRemoveExtensionsTest(ssl_tls13_encrypted_client_hello_xtn,
+ SSL_ERROR_ILLEGAL_PARAMETER_ALERT,
+ SSL_ERROR_BAD_2ND_CLIENT_HELLO);
+}
+#endif
+
TEST_P(TlsExtensionTest13, EmptyVersionList) {
static const uint8_t ext[] = {0x00, 0x00};
ConnectWithBogusVersionList(ext, sizeof(ext));
@@ -1289,6 +1248,7 @@ TEST_P(TlsBogusExtensionTest13, AddBogusExtensionNewSessionTicket) {
TEST_P(TlsConnectStream, IncludePadding) {
EnsureTlsSetup();
+ SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE); // Don't GREASE
// This needs to be long enough to push a TLS 1.0 ClientHello over 255, but
// short enough not to push a TLS 1.3 ClientHello over 511.
diff --git a/gtests/ssl_gtest/ssl_gtest.gyp b/gtests/ssl_gtest/ssl_gtest.gyp
index 5491a0725..f21f5fe80 100644
--- a/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/gtests/ssl_gtest/ssl_gtest.gyp
@@ -55,7 +55,7 @@
'tls_connect.cc',
'tls_filter.cc',
'tls_hkdf_unittest.cc',
- 'tls_esni_unittest.cc',
+ 'tls_ech_unittest.cc',
'tls_protect.cc',
'tls_psk_unittest.cc',
'tls_subcerts_unittest.cc'
diff --git a/gtests/ssl_gtest/ssl_tls13compat_unittest.cc b/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
index 645f84ff0..f65552cb1 100644
--- a/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
+++ b/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
@@ -214,6 +214,31 @@ TEST_F(Tls13CompatTest, EnabledHrrZeroRtt) {
CheckForCompatHandshake();
}
+#ifdef NSS_ENABLE_DRAFT_HPKE
+TEST_F(Tls13CompatTest, EnabledAcceptedEch) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ EnableCompatMode();
+ InstallFilters();
+ Connect();
+ CheckForCompatHandshake();
+}
+
+TEST_F(Tls13CompatTest, EnabledRejectedEch) {
+ EnsureTlsSetup();
+ // Configure ECH on the client only, and expect CCS.
+ SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
+ EnableCompatMode();
+ InstallFilters();
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ ConnectExpectFailOneSide(TlsAgent::CLIENT);
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ CheckForCompatHandshake();
+ // Reset expectations for the TlsAgent dtor.
+ server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
+}
+#endif
+
class TlsSessionIDEchoFilter : public TlsHandshakeFilter {
public:
TlsSessionIDEchoFilter(const std::shared_ptr<TlsAgent>& a)
diff --git a/gtests/ssl_gtest/tls_agent.cc b/gtests/ssl_gtest/tls_agent.cc
index 2eafc5bcb..43a953bec 100644
--- a/gtests/ssl_gtest/tls_agent.cc
+++ b/gtests/ssl_gtest/tls_agent.cc
@@ -74,6 +74,7 @@ TlsAgent::TlsAgent(const std::string& nm, Role rl, SSLProtocolVariant var)
expected_version_(0),
expected_cipher_suite_(0),
expect_client_auth_(false),
+ expect_ech_(false),
expect_psk_(ssl_psk_none),
can_falsestart_hook_called_(false),
sni_hook_called_(false),
@@ -687,7 +688,9 @@ void TlsAgent::EnableFalseStart() {
SetOption(SSL_ENABLE_FALSE_START, PR_TRUE);
}
-void TlsAgent::ExpectPsk() { expect_psk_ = ssl_psk_external; }
+void TlsAgent::ExpectEch(bool expected) { expect_ech_ = expected; }
+
+void TlsAgent::ExpectPsk(SSLPskType psk) { expect_psk_ = psk; }
void TlsAgent::ExpectResumption() { expect_psk_ = ssl_psk_resume; }
@@ -820,7 +823,6 @@ void TlsAgent::CheckPreliminaryInfo() {
SSL_GetPreliminaryChannelInfo(ssl_fd(), &preinfo, sizeof(preinfo)));
EXPECT_EQ(sizeof(preinfo), preinfo.length);
EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_version);
- EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_cipher_suite);
// A version of 0 is invalid and indicates no expectation. This value is
// initialized to 0 so that tests that don't explicitly set an expected
@@ -932,6 +934,7 @@ void TlsAgent::Connected() {
EXPECT_EQ(expect_psk_ == ssl_psk_resume, info_.resumed == PR_TRUE);
EXPECT_EQ(expect_psk_, info_.pskType);
+ EXPECT_EQ(expect_ech_, info_.echAccepted);
// Preliminary values are exposed through callbacks during the handshake.
// If either expected values were set or the callbacks were called, check
diff --git a/gtests/ssl_gtest/tls_agent.h b/gtests/ssl_gtest/tls_agent.h
index f9bb26aee..05470ba3d 100644
--- a/gtests/ssl_gtest/tls_agent.h
+++ b/gtests/ssl_gtest/tls_agent.h
@@ -158,7 +158,9 @@ class TlsAgent : public PollTarget {
void SetServerKeyBits(uint16_t bits);
void ExpectReadWriteError();
void EnableFalseStart();
- void ExpectPsk();
+ void ExpectEch(bool expected = true);
+ bool GetEchExpected() const { return expect_ech_; }
+ void ExpectPsk(SSLPskType psk = ssl_psk_external);
void ExpectResumption();
void SkipVersionChecks();
void SetSignatureSchemes(const SSLSignatureScheme* schemes, size_t count);
@@ -186,6 +188,7 @@ class TlsAgent : public PollTarget {
void EnableExtendedMasterSecret();
void CheckExtendedMasterSecret(bool expected);
void CheckEarlyDataAccepted(bool expected);
+ void CheckEchAccepted(bool expected);
void SetDowngradeCheckVersion(uint16_t version);
void CheckSecretsDestroyed();
void ConfigNamedGroups(const std::vector<SSLNamedGroup>& groups);
@@ -426,6 +429,7 @@ class TlsAgent : public PollTarget {
uint16_t expected_version_;
uint16_t expected_cipher_suite_;
bool expect_client_auth_;
+ bool expect_ech_;
SSLPskType expect_psk_;
bool can_falsestart_hook_called_;
bool sni_hook_called_;
diff --git a/gtests/ssl_gtest/tls_connect.cc b/gtests/ssl_gtest/tls_connect.cc
index 9b7f9b6d8..6456bff56 100644
--- a/gtests/ssl_gtest/tls_connect.cc
+++ b/gtests/ssl_gtest/tls_connect.cc
@@ -248,6 +248,91 @@ void TlsConnectTestBase::ResetAntiReplay(PRTime window) {
anti_replay_.reset(p_anti_replay);
}
+void TlsConnectTestBase::MakeEcKeyParams(SECItem* params, SSLNamedGroup group) {
+ auto groupDef = ssl_LookupNamedGroup(group);
+ ASSERT_NE(nullptr, groupDef);
+
+ auto oidData = SECOID_FindOIDByTag(groupDef->oidTag);
+ ASSERT_NE(nullptr, oidData);
+ ASSERT_NE(nullptr,
+ SECITEM_AllocItem(nullptr, params, (2 + oidData->oid.len)));
+ params->data[0] = SEC_ASN1_OBJECT_ID;
+ params->data[1] = oidData->oid.len;
+ memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);
+}
+
+void TlsConnectTestBase::GenerateEchConfig(
+ HpkeKemId kem_id, const std::vector<uint32_t>& cipher_suites,
+ const std::string& public_name, uint16_t max_name_len, DataBuffer& record,
+ ScopedSECKEYPublicKey& pubKey, ScopedSECKEYPrivateKey& privKey) {
+ bool gen_keys = !pubKey && !privKey;
+ SECKEYECParams ecParams = {siBuffer, NULL, 0};
+ MakeEcKeyParams(&ecParams, ssl_grp_ec_curve25519);
+
+ SECKEYPublicKey* pub = nullptr;
+ SECKEYPrivateKey* priv = nullptr;
+
+ if (gen_keys) {
+ priv = SECKEY_CreateECPrivateKey(&ecParams, &pub, nullptr);
+ } else {
+ priv = privKey.get();
+ pub = pubKey.get();
+ }
+ ASSERT_NE(nullptr, priv);
+ SECITEM_FreeItem(&ecParams, PR_FALSE);
+ PRUint8 encoded[1024];
+ unsigned int encoded_len = 0;
+ SECStatus rv = SSL_EncodeEchConfig(
+ public_name.c_str(), cipher_suites.data(), cipher_suites.size(), kem_id,
+ pub, max_name_len, encoded, &encoded_len, sizeof(encoded));
+ EXPECT_EQ(SECSuccess, rv);
+ EXPECT_GT(encoded_len, 0U);
+
+ if (gen_keys) {
+ pubKey.reset(pub);
+ privKey.reset(priv);
+ }
+ record.Truncate(0);
+ record.Write(0, encoded, encoded_len);
+}
+
+void TlsConnectTestBase::SetupEch(std::shared_ptr<TlsAgent>& client,
+ std::shared_ptr<TlsAgent>& server,
+ HpkeKemId kem_id, bool expect_ech,
+ bool set_client_config,
+ bool set_server_config) {
+ EXPECT_TRUE(set_server_config || set_client_config);
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ static const std::vector<uint32_t> kDefaultSuites = {
+ (static_cast<uint16_t>(HpkeKdfHkdfSha256) << 16) |
+ HpkeAeadChaCha20Poly1305,
+ (static_cast<uint16_t>(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm};
+
+ GenerateEchConfig(kem_id, kDefaultSuites, "public.name", 100, record, pub,
+ priv);
+ ASSERT_NE(0U, record.len());
+ SECStatus rv;
+ if (set_server_config) {
+ rv = SSL_SetServerEchConfigs(server->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len());
+ ASSERT_EQ(SECSuccess, rv);
+ }
+ if (set_client_config) {
+ rv = SSL_SetClientEchConfigs(client->ssl_fd(), record.data(), record.len());
+ ASSERT_EQ(SECSuccess, rv);
+ }
+
+ /* Filter expect_ech, which typically defaults to true. Parameterized tests
+ * running DTLS or TLS < 1.3 should expect only a non-ECH result. */
+ bool expect = expect_ech && variant_ != ssl_variant_datagram &&
+ version_ >= SSL_LIBRARY_VERSION_TLS_1_3 && set_client_config &&
+ set_server_config;
+ client->ExpectEch(expect);
+ server->ExpectEch(expect);
+}
+
void TlsConnectTestBase::Reset() {
// Take a copy of the names because they are about to disappear.
std::string server_name = server_->name();
diff --git a/gtests/ssl_gtest/tls_connect.h b/gtests/ssl_gtest/tls_connect.h
index 3a43d6bca..6acb80977 100644
--- a/gtests/ssl_gtest/tls_connect.h
+++ b/gtests/ssl_gtest/tls_connect.h
@@ -146,6 +146,19 @@ class TlsConnectTestBase : public ::testing::Test {
void SaveAlgorithmPolicy();
void RestoreAlgorithmPolicy();
+ static void MakeEcKeyParams(SECItem* params, SSLNamedGroup group);
+ static void GenerateEchConfig(HpkeKemId kem_id,
+ const std::vector<uint32_t>& cipher_suites,
+ const std::string& public_name,
+ uint16_t max_name_len, DataBuffer& record,
+ ScopedSECKEYPublicKey& pubKey,
+ ScopedSECKEYPrivateKey& privKey);
+ void SetupEch(std::shared_ptr<TlsAgent>& client,
+ std::shared_ptr<TlsAgent>& server,
+ HpkeKemId kem_id = HpkeDhKemX25519Sha256,
+ bool expect_ech = true, bool set_client_config = true,
+ bool set_server_config = true);
+
protected:
SSLProtocolVariant variant_;
std::shared_ptr<TlsAgent> client_;
diff --git a/gtests/ssl_gtest/tls_ech_unittest.cc b/gtests/ssl_gtest/tls_ech_unittest.cc
new file mode 100644
index 000000000..473804323
--- /dev/null
+++ b/gtests/ssl_gtest/tls_ech_unittest.cc
@@ -0,0 +1,1604 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// TODO: Add padding/maxNameLen tests after support is added in bug 1677181.
+
+#include "secerr.h"
+#include "ssl.h"
+
+#include "gtest_utils.h"
+#include "pk11pub.h"
+#include "tls_agent.h"
+#include "tls_connect.h"
+#include "util.h"
+
+namespace nss_test {
+
+class TlsAgentEchTest : public TlsAgentTestClient13 {
+ protected:
+ void InstallEchConfig(const DataBuffer& record, PRErrorCode err = 0) {
+ SECStatus rv =
+ SSL_SetClientEchConfigs(agent_->ssl_fd(), record.data(), record.len());
+ if (err == 0) {
+ ASSERT_EQ(SECSuccess, rv);
+ } else {
+ ASSERT_EQ(SECFailure, rv);
+ ASSERT_EQ(err, PORT_GetError());
+ }
+ }
+};
+
+#ifdef NSS_ENABLE_DRAFT_HPKE
+#include "cpputil.h" // Unused function error if included without HPKE.
+
+static std::string kPublicName("public.name");
+
+static const std::vector<uint32_t> kDefaultSuites = {
+ (static_cast<uint32_t>(HpkeKdfHkdfSha256) << 16) | HpkeAeadChaCha20Poly1305,
+ (static_cast<uint32_t>(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm};
+static const std::vector<uint32_t> kSuiteChaCha = {
+ (static_cast<uint32_t>(HpkeKdfHkdfSha256) << 16) |
+ HpkeAeadChaCha20Poly1305};
+static const std::vector<uint32_t> kSuiteAes = {
+ (static_cast<uint32_t>(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm};
+std::vector<uint32_t> kBogusSuite = {0xfefefefe};
+static const std::vector<uint32_t> kUnknownFirstSuite = {
+ 0xfefefefe,
+ (static_cast<uint32_t>(HpkeKdfHkdfSha256) << 16) | HpkeAeadAes128Gcm};
+
+class TlsConnectStreamTls13Ech : public TlsConnectTestBase {
+ public:
+ TlsConnectStreamTls13Ech()
+ : TlsConnectTestBase(ssl_variant_stream, SSL_LIBRARY_VERSION_TLS_1_3) {}
+
+ void ReplayChWithMalformedInner(const std::string& ch, uint8_t server_alert,
+ uint32_t server_code, uint32_t client_code) {
+ std::vector<uint8_t> ch_vec = hex_string_to_bytes(ch);
+ DataBuffer ch_buf;
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ EnsureTlsSetup();
+ ImportFixedEchKeypair(pub, priv);
+ SetMutualEchConfigs(pub, priv);
+
+ TlsAgentTestBase::MakeRecord(variant_, ssl_ct_handshake,
+ SSL_LIBRARY_VERSION_TLS_1_3, ch_vec.data(),
+ ch_vec.size(), &ch_buf, 0);
+ StartConnect();
+ client_->SendDirect(ch_buf);
+ ExpectAlert(server_, server_alert);
+ server_->Handshake();
+ server_->CheckErrorCode(server_code);
+ client_->ExpectReceiveAlert(server_alert, kTlsAlertFatal);
+ client_->Handshake();
+ client_->CheckErrorCode(client_code);
+ }
+
+ // Setup Client/Server with mismatched AEADs
+ void SetupForEchRetry() {
+ ScopedSECKEYPublicKey server_pub;
+ ScopedSECKEYPrivateKey server_priv;
+ ScopedSECKEYPublicKey client_pub;
+ ScopedSECKEYPrivateKey client_priv;
+ DataBuffer server_rec;
+ DataBuffer client_rec;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha,
+ kPublicName, 100, server_rec,
+ server_pub, server_priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteAes,
+ kPublicName, 100, client_rec,
+ client_pub, client_priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(),
+ client_rec.len()));
+ }
+
+ // Parse a captured SNI extension and validate the contained name.
+ void CheckSniExtension(const DataBuffer& data,
+ const std::string expected_name) {
+ TlsParser parser(data.data(), data.len());
+ uint32_t tmp;
+ ASSERT_TRUE(parser.Read(&tmp, 2));
+ ASSERT_EQ(parser.remaining(), tmp);
+ ASSERT_TRUE(parser.Read(&tmp, 1));
+ ASSERT_EQ(0U, tmp); /* sni_nametype_hostname */
+ DataBuffer name;
+ ASSERT_TRUE(parser.ReadVariable(&name, 2));
+ ASSERT_EQ(0U, parser.remaining());
+ // Manual comparison to silence coverity false-positives.
+ ASSERT_EQ(name.len(), kPublicName.length());
+ ASSERT_EQ(0,
+ memcmp(kPublicName.c_str(), name.data(), kPublicName.length()));
+ }
+
+ void DoEchRetry(const ScopedSECKEYPublicKey& server_pub,
+ const ScopedSECKEYPrivateKey& server_priv,
+ const DataBuffer& server_rec) {
+ StackSECItem retry_configs;
+ ASSERT_EQ(SECSuccess,
+ SSL_GetEchRetryConfigs(client_->ssl_fd(), &retry_configs));
+ ASSERT_NE(0U, retry_configs.len);
+
+ // Reset expectations for the TlsAgent dtor.
+ server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
+ Reset();
+ EnsureTlsSetup();
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+ ASSERT_EQ(SECSuccess,
+ SSL_SetClientEchConfigs(client_->ssl_fd(), retry_configs.data,
+ retry_configs.len));
+ client_->ExpectEch();
+ server_->ExpectEch();
+ Connect();
+ }
+
+ private:
+ // Testing certan invalid CHInner configurations is tricky, particularly
+ // since the CHOuter forms AAD and isn't available in filters. Instead of
+ // generating these inputs on the fly, use a fixed server keypair so that
+ // the input can be generated once (e.g. via a debugger) and replayed in
+ // each invocation of the test.
+ std::string kFixedServerPubkey =
+ "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a"
+ "02010104205a8aa0d2476b28521588e0c704b14db82cdd4970d340d293a957"
+ "6deaee9ec1c7a1230321008756e2580c07c1d2ffcb662f5fadc6d6ff13da85"
+ "abd7adfecf984aaa102c1269";
+
+ void ImportFixedEchKeypair(ScopedSECKEYPublicKey& pub,
+ ScopedSECKEYPrivateKey& priv) {
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ if (!slot) {
+ ADD_FAILURE() << "No slot";
+ return;
+ }
+ std::vector<uint8_t> pkcs8_r = hex_string_to_bytes(kFixedServerPubkey);
+ SECItem pkcs8_r_item = {siBuffer, toUcharPtr(pkcs8_r.data()),
+ static_cast<unsigned int>(pkcs8_r.size())};
+
+ SECKEYPrivateKey* tmp_priv = nullptr;
+ ASSERT_EQ(SECSuccess, PK11_ImportDERPrivateKeyInfoAndReturnKey(
+ slot.get(), &pkcs8_r_item, nullptr, nullptr,
+ false, false, KU_ALL, &tmp_priv, nullptr));
+ priv.reset(tmp_priv);
+ SECKEYPublicKey* tmp_pub = SECKEY_ConvertToPublicKey(tmp_priv);
+ pub.reset(tmp_pub);
+ ASSERT_NE(nullptr, tmp_pub);
+ }
+
+ void SetMutualEchConfigs(ScopedSECKEYPublicKey& pub,
+ ScopedSECKEYPrivateKey& priv) {
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len()));
+ ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(),
+ record.data(), record.len()));
+ }
+};
+
+static void CheckCertVerifyPublicName(TlsAgent* agent) {
+ agent->UpdatePreliminaryChannelInfo();
+ EXPECT_NE(0U, (agent->pre_info().valuesSet & ssl_preinfo_ech));
+ EXPECT_EQ(agent->GetEchExpected(), agent->pre_info().echAccepted);
+
+ // Check that echPublicName is only exposed in the rejection
+ // case, so that the application can use it for CertVerfiy.
+ if (agent->GetEchExpected()) {
+ EXPECT_EQ(nullptr, agent->pre_info().echPublicName);
+ } else {
+ EXPECT_NE(nullptr, agent->pre_info().echPublicName);
+ if (agent->pre_info().echPublicName) {
+ EXPECT_EQ(0,
+ strcmp(kPublicName.c_str(), agent->pre_info().echPublicName));
+ }
+ }
+}
+
+static SECStatus AuthCompleteSuccess(TlsAgent* agent, PRBool, PRBool) {
+ CheckCertVerifyPublicName(agent);
+ return SECSuccess;
+}
+
+static SECStatus AuthCompleteFail(TlsAgent* agent, PRBool, PRBool) {
+ CheckCertVerifyPublicName(agent);
+ return SECFailure;
+}
+
+TEST_P(TlsAgentEchTest, EchConfigsSupportedYesNo) {
+ if (variant_ == ssl_variant_datagram) {
+ return;
+ }
+
+ // ECHConfig 2 cipher_suites are unsupported.
+ const std::string mixed =
+ "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304"
+ "444156E4E04D1BF0FFDA7783B6B457F75600200008000100030001000100640000FE0800"
+ "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0"
+ "4D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000";
+ std::vector<uint8_t> config = hex_string_to_bytes(mixed);
+ DataBuffer record(config.data(), config.size());
+
+ EnsureInit();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ InstallEchConfig(record, 0);
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_TRUE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, EchConfigsSupportedNoYes) {
+ if (variant_ == ssl_variant_datagram) {
+ return;
+ }
+
+ // ECHConfig 1 cipher_suites are unsupported.
+ const std::string mixed =
+ "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304"
+ "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFFFFFF000100640000FE0800"
+ "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0"
+ "4D1BF0FFDA7783B6B457F75600200008000100030001000100640000";
+ std::vector<uint8_t> config = hex_string_to_bytes(mixed);
+ DataBuffer record(config.data(), config.size());
+
+ EnsureInit();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ InstallEchConfig(record, 0);
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_TRUE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, EchConfigsSupportedNoNo) {
+ if (variant_ == ssl_variant_datagram) {
+ return;
+ }
+
+ // ECHConfig 1 and 2 cipher_suites are unsupported.
+ const std::string unsupported =
+ "0086FE08003F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304"
+ "444156E4E04D1BF0FFDA7783B6B457F756002000080001FFFF0001FFFF00640000FE0800"
+ "3F000B7075626C69632E6E616D6500203BB6D46C201B820F1AE4AFD4DEC304444156E4E0"
+ "4D1BF0FFDA7783B6B457F75600200008FFFF0003FFFF000100640000";
+ std::vector<uint8_t> config = hex_string_to_bytes(unsupported);
+ DataBuffer record(config.data(), config.size());
+
+ EnsureInit();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ InstallEchConfig(record, SEC_ERROR_INVALID_ARGS);
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, ShortEchConfig) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ record.Truncate(record.len() - 1);
+ InstallEchConfig(record, SEC_ERROR_BAD_DATA);
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, LongEchConfig) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ record.Write(record.len(), 1, 1); // Append one byte
+ InstallEchConfig(record, SEC_ERROR_BAD_DATA);
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, UnsupportedEchConfigVersion) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ static const uint8_t bad_version[] = {0xff, 0xff};
+ DataBuffer bad_ver_buf(bad_version, sizeof(bad_version));
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ record.Splice(bad_ver_buf, 2, 2);
+ InstallEchConfig(record, SEC_ERROR_INVALID_ARGS);
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, UnsupportedHpkeKem) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ // SSL_EncodeEchConfig encodes without validation.
+ TlsConnectTestBase::GenerateEchConfig(static_cast<HpkeKemId>(0xff),
+ kDefaultSuites, kPublicName, 100,
+ record, pub, priv);
+ InstallEchConfig(record, SEC_ERROR_INVALID_ARGS);
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, EchRejectIgnoreAllUnknownSuites) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kBogusSuite,
+ kPublicName, 100, record, pub, priv);
+ InstallEchConfig(record, SEC_ERROR_INVALID_ARGS);
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_F(TlsConnectStreamTls13, EchAcceptIgnoreSingleUnknownSuite) {
+ EnsureTlsSetup();
+ DataBuffer record;
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256,
+ kUnknownFirstSuite, kPublicName, 100,
+ record, pub, priv);
+ ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(),
+ record.data(), record.len()));
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len()));
+
+ client_->ExpectEch();
+ server_->ExpectEch();
+ Connect();
+}
+
+TEST_P(TlsAgentEchTest, ApiInvalidArgs) {
+ EnsureInit();
+ // SetClient
+ EXPECT_EQ(SECFailure, SSL_SetClientEchConfigs(agent_->ssl_fd(), nullptr, 1));
+
+ EXPECT_EQ(SECFailure,
+ SSL_SetClientEchConfigs(agent_->ssl_fd(),
+ reinterpret_cast<const uint8_t*>(1), 0));
+
+ // SetServer
+ EXPECT_EQ(SECFailure,
+ SSL_SetServerEchConfigs(agent_->ssl_fd(), nullptr,
+ reinterpret_cast<SECKEYPrivateKey*>(1),
+ reinterpret_cast<const uint8_t*>(1), 1));
+ EXPECT_EQ(SECFailure,
+ SSL_SetServerEchConfigs(
+ agent_->ssl_fd(), reinterpret_cast<SECKEYPublicKey*>(1),
+ nullptr, reinterpret_cast<const uint8_t*>(1), 1));
+ EXPECT_EQ(SECFailure,
+ SSL_SetServerEchConfigs(
+ agent_->ssl_fd(), reinterpret_cast<SECKEYPublicKey*>(1),
+ reinterpret_cast<SECKEYPrivateKey*>(1), nullptr, 1));
+ EXPECT_EQ(SECFailure,
+ SSL_SetServerEchConfigs(agent_->ssl_fd(),
+ reinterpret_cast<SECKEYPublicKey*>(1),
+ reinterpret_cast<SECKEYPrivateKey*>(1),
+ reinterpret_cast<const uint8_t*>(1), 0));
+
+ // GetRetries
+ EXPECT_EQ(SECFailure, SSL_GetEchRetryConfigs(agent_->ssl_fd(), nullptr));
+
+ // EncodeEchConfig
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig(nullptr, reinterpret_cast<uint32_t*>(1), 1,
+ static_cast<HpkeKemId>(1),
+ reinterpret_cast<SECKEYPublicKey*>(1), 1,
+ reinterpret_cast<uint8_t*>(1),
+ reinterpret_cast<uint32_t*>(1), 1));
+
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig("name", nullptr, 1, static_cast<HpkeKemId>(1),
+ reinterpret_cast<SECKEYPublicKey*>(1), 1,
+ reinterpret_cast<uint8_t*>(1),
+ reinterpret_cast<uint32_t*>(1), 1));
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig("name", reinterpret_cast<uint32_t*>(1), 0,
+ static_cast<HpkeKemId>(1),
+ reinterpret_cast<SECKEYPublicKey*>(1), 1,
+ reinterpret_cast<uint8_t*>(1),
+ reinterpret_cast<uint32_t*>(1), 1));
+
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig("name", reinterpret_cast<uint32_t*>(1), 1,
+ static_cast<HpkeKemId>(1), nullptr, 1,
+ reinterpret_cast<uint8_t*>(1),
+ reinterpret_cast<uint32_t*>(1), 1));
+
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig(nullptr, reinterpret_cast<uint32_t*>(1), 1,
+ static_cast<HpkeKemId>(1),
+ reinterpret_cast<SECKEYPublicKey*>(1), 0,
+ reinterpret_cast<uint8_t*>(1),
+ reinterpret_cast<uint32_t*>(1), 1));
+
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig("name", reinterpret_cast<uint32_t*>(1), 1,
+ static_cast<HpkeKemId>(1),
+ reinterpret_cast<SECKEYPublicKey*>(1), 1,
+ nullptr, reinterpret_cast<uint32_t*>(1), 1));
+
+ EXPECT_EQ(SECFailure,
+ SSL_EncodeEchConfig("name", reinterpret_cast<uint32_t*>(1), 1,
+ static_cast<HpkeKemId>(1),
+ reinterpret_cast<SECKEYPublicKey*>(1), 1,
+ reinterpret_cast<uint8_t*>(1), nullptr, 1));
+}
+
+TEST_P(TlsAgentEchTest, NoEarlyRetryConfigs) {
+ EnsureInit();
+ StackSECItem retry_configs;
+ EXPECT_EQ(SECFailure,
+ SSL_GetEchRetryConfigs(agent_->ssl_fd(), &retry_configs));
+ EXPECT_EQ(SSL_ERROR_HANDSHAKE_NOT_COMPLETED, PORT_GetError());
+
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ InstallEchConfig(record, 0);
+
+ EXPECT_EQ(SECFailure,
+ SSL_GetEchRetryConfigs(agent_->ssl_fd(), &retry_configs));
+ EXPECT_EQ(SSL_ERROR_HANDSHAKE_NOT_COMPLETED, PORT_GetError());
+}
+
+TEST_P(TlsAgentEchTest, NoSniSoNoEch) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ SSL_SetURL(agent_->ssl_fd(), "");
+ InstallEchConfig(record, 0);
+ SSL_SetURL(agent_->ssl_fd(), "");
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, NoEchConfigSoNoEch) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_P(TlsAgentEchTest, EchConfigDuplicateExtensions) {
+ EnsureInit();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+
+ static const uint8_t duped_xtn[] = {0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00};
+ DataBuffer buf(duped_xtn, sizeof(duped_xtn));
+ record.Truncate(record.len() - 2);
+ record.Append(buf);
+ uint32_t len;
+ ASSERT_TRUE(record.Read(0, 2, &len));
+ len += buf.len() - 2;
+ DataBuffer new_len;
+ ASSERT_TRUE(new_len.Write(0, len, 2));
+ record.Splice(new_len, 0, 2);
+ new_len.Truncate(0);
+
+ ASSERT_TRUE(record.Read(4, 2, &len));
+ len += buf.len() - 2;
+ ASSERT_TRUE(new_len.Write(0, len, 2));
+ record.Splice(new_len, 4, 2);
+
+ InstallEchConfig(record, SEC_ERROR_EXTENSION_VALUE_INVALID);
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(agent_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ agent_, ssl_tls13_encrypted_client_hello_xtn);
+ agent_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
+ ASSERT_FALSE(filter->captured());
+}
+
+// Test an encoded ClientHelloInner containing an extra extensionType
+// in outer_extensions, for which there is no corresponding (uncompressed)
+// extension in ClientHelloOuter.
+TEST_F(TlsConnectStreamTls13Ech, EchOuterExtensionsReferencesMissing) {
+ std::string ch =
+ "01000170030374d616d97efe591bf9bee4496bcc1118145b4dd02f7d1ff979fd0cf61749"
+ "a91e0000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff"
+ "01000100000a00140012001d00170018001901000101010201030104003300260024001d"
+ "00204f346f86351b077492c83564c909d1aaab4f6f3ee2566af0e90a4684c793805d002b"
+ "0003020304000d0018001604030503060302030804080508060401050106010201002d00"
+ "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b"
+ "a5f058dd5c5ab1ca9750ef9d28c70020924764b36fe5d4a985f9857ceb75edb10b5f4b5b"
+ "f9d59290db70743e3c582163006acea5d7785cc506ecf5c859a9cad18f2b1df1a32231fe"
+ "0330471ee0e88ece9047e6491a381bfabed58f7fc542f0ba78eb55030bcfe1d400f67275"
+ "eac8619d1e4237e9d6176dd4eb54f3f25865686756f313a4ba47901c83e5ad5413609d39"
+ "816346b940115fd68e534609";
+ ReplayChWithMalformedInner(ch, kTlsAlertIllegalParameter,
+ SSL_ERROR_RX_MALFORMED_ECH_EXTENSION,
+ SSL_ERROR_ILLEGAL_PARAMETER_ALERT);
+}
+
+// Drop supported_versions from CHInner, make sure we don't negotiate 1.2+ECH.
+TEST_F(TlsConnectStreamTls13Ech, EchVersion12Inner) {
+ std::string ch =
+ "0100017003034dd5bf4c12835e9be21f983953720e3595b3a8eeb4a44467678caceb7727"
+ "3be90000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff"
+ "01000100000a00140012001d00170018001901000101010201030104003300260024001d"
+ "0020af7b976cdf69ffcd494ca5a93ae3ecde692b09be518ee033aad908c45b82c368002b"
+ "0003020304000d0018001604030503060302030804080508060401050106010201002d00"
+ "020101001c0002400100150003000000fe0800ac0001000320a10698ccbd4bd86df91f61"
+ "7e58dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020f5ece4c187b76f7e3d467c7506"
+ "215e73c27c918cd863c0e80d76a7987ec274320063e037492868eff5296a22dc50885e9d"
+ "f6964a5e26546f1bada043f8834988dfea5394b4c45a4d0b3afc52142d33f94161135a63"
+ "ed3c1b63f60d8133fb1cff17e1f9ced6c871984e412ed8ddb0f487c4d09d7aea80488004"
+ "c45a17cd3b5cdca316155fdb";
+ ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion,
+ SSL_ERROR_UNSUPPORTED_VERSION,
+ SSL_ERROR_PROTOCOL_VERSION_ALERT);
+}
+
+// Use CHInner supported_versions to negotiate 1.2.
+TEST_F(TlsConnectStreamTls13Ech, EchVersion12InnerSupportedVersions) {
+ std::string ch =
+ "010001700303845c298db4017d2ed2584284b90e4ecba57a63663560c57aa0b1ac51203d"
+ "c8560000061301130313020100014100000010000e00000b7075626c69632e6e616d65ff"
+ "01000100000a00140012001d00170018001901000101010201030104003300260024001d"
+ "00203356719e88b539645438f645916aeeffe93c38803a59d6997938aa98eefbcf64002b"
+ "0003020304000d0018001604030503060302030804080508060401050106010201002d00"
+ "020101001c00024001fe0800b30001000320a10698ccbd4bd86df91f617e58dd2ca96b8b"
+ "a5f058dd5c5ab1ca9750ef9d28c700208412c945c53624bcace5eda0dc1ad300a1620e86"
+ "5a0f4a27755a3477b115b65b006abf1dfd77ddc1b80c5976732174a5fe7ebcf9ff1a548b"
+ "097daa12a37f3e32a613a0798544ba1d96239431bc807ddd9055ac3fb3e32b2eb42cec30"
+ "e915357418a953027d73020fd739287414205349eeff376dd464750ca70a965141a88800"
+ "6a043fe1d6d882d9a2c2f6f3";
+ ReplayChWithMalformedInner(ch, kTlsAlertProtocolVersion,
+ SSL_ERROR_UNSUPPORTED_VERSION,
+ SSL_ERROR_PROTOCOL_VERSION_ALERT);
+}
+
+// Replay a CH for which the ECH Inner lacks the required
+// empty ECH extension.
+TEST_F(TlsConnectStreamTls13Ech, EchInnerMissingEmptyEch) {
+ std::string ch =
+ "0100017103032bf866cbd6d4abdec8ce23107eaef9af51b644043953e3b70f2f28f1898e"
+ "87880000061301130313020100014200000010000e00000b7075626c69632e6e616d65ff"
+ "01000100000a00140012001d00170018001901000101010201030104003300260024001d"
+ "00208f614d3017575332ca009a42d33bcaf876b4ba6d44b052e8019c31f6f1559e41002b"
+ "0003020304000d0018001604030503060302030804080508060401050106010201002d00"
+ "020101001c000240010015000100fe0800af0001000320a10698ccbd4bd86df91f617e58"
+ "dd2ca96b8ba5f058dd5c5ab1ca9750ef9d28c70020da1d5d9f183a5d5e49892e38eaae5e"
+ "9e3e6c5d404a5fdb672ca37f9cebabd57400660ea1d61917cc1049aab22506078ccecfc4"
+ "16a364a1beaa8915b250bb86ac2c725698c3c641830c4aa4e8b7f50152b5732b29b1ac43"
+ "45c97fc018855fd68e5600d0ef188e905b69997c3711b0ec0114a857177df728c7b84f52"
+ "2923f932838f7f15bb22644fd4";
+ ReplayChWithMalformedInner(ch, kTlsAlertDecodeError,
+ SSL_ERROR_MISSING_ECH_EXTENSION,
+ SSL_ERROR_DECODE_ERROR_ALERT);
+}
+
+// An empty config_id should prompt an alert. We don't support
+// Optional Configuration Identifiers.
+TEST_F(TlsConnectStreamTls13, EchRejectEmptyConfigId) {
+ static const uint8_t junk[16] = {0};
+ DataBuffer junk_buf(junk, sizeof(junk));
+ DataBuffer ech_xtn;
+ ech_xtn.Write(ech_xtn.len(), HpkeKdfHkdfSha256, 2);
+ ech_xtn.Write(ech_xtn.len(), HpkeAeadAes128Gcm, 2);
+ ech_xtn.Write(ech_xtn.len(), 0U, 1); // empty config_id
+ ech_xtn.Write(ech_xtn.len(), junk_buf.len(), 2); // enc
+ ech_xtn.Append(junk_buf);
+ ech_xtn.Write(ech_xtn.len(), junk_buf.len(), 2); // payload
+ ech_xtn.Append(junk_buf);
+
+ EnsureTlsSetup();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ MakeTlsFilter<TlsExtensionAppender>(client_, kTlsHandshakeClientHello,
+ ssl_tls13_encrypted_client_hello_xtn,
+ ech_xtn);
+ ConnectExpectAlert(server_, kTlsAlertDecodeError);
+ client_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+}
+
+TEST_F(TlsConnectStreamTls13Ech, EchAcceptBasic) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+
+ auto c_filter_sni =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+
+ Connect();
+ ASSERT_TRUE(c_filter_sni->captured());
+ CheckSniExtension(c_filter_sni->extension(), kPublicName);
+}
+
+TEST_F(TlsConnectStreamTls13, EchAcceptWithResume) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ Connect();
+ SendReceive(); // Need to read so that we absorb the session ticket.
+ CheckKeys();
+
+ Reset();
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ ExpectResumption(RESUME_TICKET);
+ auto filter =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ StartConnect();
+ Handshake();
+ CheckConnected();
+ // Make sure that the PSK extension is only in CHInner.
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_F(TlsConnectStreamTls13, EchAcceptWithExternalPsk) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ ASSERT_TRUE(!!slot);
+ ScopedPK11SymKey key(
+ PK11_KeyGen(slot.get(), CKM_HKDF_KEY_GEN, nullptr, 16, nullptr));
+ ASSERT_TRUE(!!key);
+ AddPsk(key, std::string("foo"), ssl_hash_sha256);
+
+ // Not permitted in outer.
+ auto filter =
+ MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_pre_shared_key_xtn);
+ StartConnect();
+ Handshake();
+ CheckConnected();
+ SendReceive();
+ CheckKeys(ssl_kea_ecdh, ssl_grp_ec_curve25519, ssl_auth_psk, ssl_sig_none);
+ // Make sure that the PSK extension is only in CHInner.
+ ASSERT_FALSE(filter->captured());
+}
+
+// If an earlier version is negotiated, False Start must be disabled.
+TEST_F(TlsConnectStreamTls13, EchDowngradeNoFalseStart) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
+ MakeTlsFilter<TlsExtensionDropper>(client_,
+ ssl_tls13_encrypted_client_hello_xtn);
+ client_->EnableFalseStart();
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_2);
+
+ StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ client_->Handshake();
+ EXPECT_FALSE(client_->can_falsestart_hook_called());
+
+ // Make sure the write is blocked.
+ client_->ExpectReadWriteError();
+ client_->SendData(10);
+}
+
+SSLHelloRetryRequestAction RetryEchHello(PRBool firstHello,
+ const PRUint8* clientToken,
+ unsigned int clientTokenLen,
+ PRUint8* appToken,
+ unsigned int* appTokenLen,
+ unsigned int appTokenMax, void* arg) {
+ auto* called = reinterpret_cast<size_t*>(arg);
+ ++*called;
+
+ EXPECT_EQ(0U, clientTokenLen);
+ return firstHello ? ssl_hello_retry_request : ssl_hello_retry_accept;
+}
+
+// Generate HRR on CH1 Inner
+TEST_F(TlsConnectStreamTls13, EchAcceptWithHrr) {
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ ConfigureSelfEncrypt();
+ EnsureTlsSetup();
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len()));
+ ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(),
+ record.data(), record.len()));
+ client_->ExpectEch();
+ server_->ExpectEch();
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+
+ // Start the handshake.
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ MakeNewServer();
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len()));
+ client_->ExpectEch();
+ server_->ExpectEch();
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ Handshake();
+ EXPECT_EQ(1U, cb_called);
+ CheckConnected();
+ SendReceive();
+}
+
+// Fail to decrypt CH2. Unlike CH1, this generates an alert.
+TEST_F(TlsConnectStreamTls13, EchFailDecryptCH2) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ EXPECT_EQ(1U, cb_called);
+ // Stop the callback from being called in future handshakes.
+ EXPECT_EQ(SECSuccess,
+ SSL_HelloRetryRequestCallback(server_->ssl_fd(), nullptr, nullptr));
+
+ MakeTlsFilter<TlsExtensionDamager>(client_,
+ ssl_tls13_encrypted_client_hello_xtn, 80);
+ ExpectAlert(server_, kTlsAlertDecryptError);
+ Handshake();
+ client_->CheckErrorCode(SSL_ERROR_DECRYPT_ERROR_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+}
+
+// Change the ECH advertisement between CH1 and CH2. Use GREASE for simplicity.
+TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingYN) {
+ ConfigureSelfEncrypt();
+ EnsureTlsSetup();
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+
+ // Start the handshake, send GREASE ECH.
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_TRUE)); // GREASE
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ MakeNewServer();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ ExpectAlert(server_, kTlsAlertIllegalParameter);
+ Handshake();
+ client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO);
+ EXPECT_EQ(1U, cb_called);
+}
+
+TEST_F(TlsConnectStreamTls13, EchHrrChangeCh2OfferingNY) {
+ ConfigureSelfEncrypt();
+ EnsureTlsSetup();
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ // Start the handshake.
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ MakeNewServer();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_TRUE)); // Send GREASE
+ ExpectAlert(server_, kTlsAlertIllegalParameter);
+ Handshake();
+ client_->CheckErrorCode(SSL_ERROR_ILLEGAL_PARAMETER_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_BAD_2ND_CLIENT_HELLO);
+ EXPECT_EQ(1U, cb_called);
+}
+
+// Configure an external PSK. Generate an HRR off CH1Inner (which contains
+// the PSK extension). Use the same PSK in CH2 and connect.
+TEST_F(TlsConnectStreamTls13, EchAcceptWithHrrAndPsk) {
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ ConfigureSelfEncrypt();
+ EnsureTlsSetup();
+
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len()));
+ ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(),
+ record.data(), record.len()));
+ client_->ExpectEch();
+ server_->ExpectEch();
+
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+
+ static const uint8_t key_buf[16] = {0};
+ SECItem key_item = {siBuffer, const_cast<uint8_t*>(&key_buf[0]),
+ sizeof(key_buf)};
+ const char* label = "foo";
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ ASSERT_TRUE(!!slot);
+ ScopedPK11SymKey key(PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN,
+ PK11_OriginUnwrap, CKA_DERIVE,
+ &key_item, nullptr));
+ ASSERT_TRUE(!!key);
+ AddPsk(key, std::string(label), ssl_hash_sha256);
+
+ // Start the handshake.
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ MakeNewServer();
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), pub.get(), priv.get(),
+ record.data(), record.len()));
+ client_->ExpectEch();
+ server_->ExpectEch();
+ EXPECT_EQ(SECSuccess,
+ SSL_AddExternalPsk0Rtt(server_->ssl_fd(), key.get(),
+ reinterpret_cast<const uint8_t*>(label),
+ strlen(label), ssl_hash_sha256, 0, 1000));
+ server_->ExpectPsk();
+ Handshake();
+ EXPECT_EQ(1U, cb_called);
+ CheckConnected();
+ SendReceive();
+}
+
+// Generate an HRR on CHOuter. Reject ECH on the second CH.
+TEST_F(TlsConnectStreamTls13Ech, EchRejectWithHrr) {
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ ConfigureSelfEncrypt();
+ EnsureTlsSetup();
+ SetupForEchRetry();
+
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+
+ // Start the handshake.
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ MakeNewServer();
+ client_->ExpectEch(false);
+ server_->ExpectEch(false);
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ Handshake();
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ EXPECT_EQ(1U, cb_called);
+}
+
+// Reject ECH on CH1 and (HRR) CH2. PSKs are no longer allowed
+// in CHOuter, but can still make sure the handshake succeeds.
+// (prompting ech_required at the completion).
+TEST_F(TlsConnectStreamTls13, EchRejectWithHrrAndPsk) {
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ ConfigureSelfEncrypt();
+ EnsureTlsSetup();
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, record, pub, priv);
+ ASSERT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(),
+ record.data(), record.len()));
+
+ size_t cb_called = 0;
+ EXPECT_EQ(SECSuccess, SSL_HelloRetryRequestCallback(
+ server_->ssl_fd(), RetryEchHello, &cb_called));
+
+ // Add a PSK to both endpoints.
+ static const uint8_t key_buf[16] = {0};
+ SECItem key_item = {siBuffer, const_cast<uint8_t*>(&key_buf[0]),
+ sizeof(key_buf)};
+ const char* label = "foo";
+ ScopedPK11SlotInfo slot(PK11_GetInternalSlot());
+ ASSERT_TRUE(!!slot);
+ ScopedPK11SymKey key(PK11_ImportSymKey(slot.get(), CKM_HKDF_KEY_GEN,
+ PK11_OriginUnwrap, CKA_DERIVE,
+ &key_item, nullptr));
+ ASSERT_TRUE(!!key);
+ AddPsk(key, std::string(label), ssl_hash_sha256);
+ client_->ExpectPsk(ssl_psk_none);
+
+ // Start the handshake.
+ client_->StartConnect();
+ server_->StartConnect();
+ client_->Handshake();
+ server_->Handshake();
+ MakeNewServer();
+ client_->ExpectEch(false);
+ server_->ExpectEch(false);
+ EXPECT_EQ(SECSuccess,
+ SSL_AddExternalPsk0Rtt(server_->ssl_fd(), key.get(),
+ reinterpret_cast<const uint8_t*>(label),
+ strlen(label), ssl_hash_sha256, 0, 1000));
+ // Don't call ExpectPsk
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ Handshake();
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ EXPECT_EQ(1U, cb_called);
+}
+
+// ECH (both connections), resumption rejected.
+TEST_F(TlsConnectStreamTls13, EchRejectResume) {
+ EnsureTlsSetup();
+ ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
+ SetupEch(client_, server_);
+ Connect();
+ SendReceive();
+
+ Reset();
+ ClearServerCache(); // Invalidate the ticket
+ ConfigureSessionCache(RESUME_BOTH, RESUME_NONE);
+ ExpectResumption(RESUME_NONE);
+ SetupEch(client_, server_);
+ Connect();
+ SendReceive();
+}
+
+// ECH (both connections) + 0-RTT
+TEST_F(TlsConnectStreamTls13, EchZeroRttBoth) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ SetupForZeroRtt();
+ client_->Set0RttEnabled(true);
+ server_->Set0RttEnabled(true);
+ SetupEch(client_, server_);
+ ExpectResumption(RESUME_TICKET);
+ ZeroRttSendReceive(true, true);
+ Handshake();
+ ExpectEarlyDataAccepted(true);
+ CheckConnected();
+ SendReceive();
+}
+
+// ECH (first connection only) + 0-RTT
+TEST_F(TlsConnectStreamTls13, EchZeroRttFirst) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ SetupForZeroRtt();
+ client_->Set0RttEnabled(true);
+ server_->Set0RttEnabled(true);
+ ExpectResumption(RESUME_TICKET);
+ ZeroRttSendReceive(true, true);
+ Handshake();
+ ExpectEarlyDataAccepted(true);
+ CheckConnected();
+ SendReceive();
+}
+
+// ECH (second connection only) + 0-RTT
+TEST_F(TlsConnectStreamTls13, EchZeroRttSecond) {
+ EnsureTlsSetup();
+ SetupForZeroRtt(); // Get a ticket
+ client_->Set0RttEnabled(true);
+ server_->Set0RttEnabled(true);
+ SetupEch(client_, server_);
+ ExpectResumption(RESUME_TICKET);
+ ZeroRttSendReceive(true, true);
+ Handshake();
+ ExpectEarlyDataAccepted(true);
+ CheckConnected();
+ SendReceive();
+}
+
+// ECH (first connection only, reject on second) + 0-RTT
+TEST_F(TlsConnectStreamTls13, EchZeroRttRejectSecond) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ SetupForZeroRtt();
+ client_->Set0RttEnabled(true);
+ server_->Set0RttEnabled(true);
+
+ // Setup ECH only on the client.
+ SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+
+ ExpectResumption(RESUME_NONE);
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ ZeroRttSendReceive(true, false);
+ server_->Handshake();
+ client_->Handshake();
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+
+ ExpectEarlyDataAccepted(false);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ // Reset expectations for the TlsAgent dtor.
+ server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
+}
+
+// Test a critical extension in ECHConfig
+TEST_F(TlsConnectStreamTls13, EchRejectUnknownCriticalExtension) {
+ EnsureTlsSetup();
+ ScopedSECKEYPublicKey pub;
+ ScopedSECKEYPrivateKey priv;
+ DataBuffer record;
+ DataBuffer crit_rec;
+ DataBuffer len_buf;
+ uint64_t tmp;
+
+ static const uint8_t crit_extensions[] = {0x00, 0x04, 0xff, 0xff, 0x00, 0x00};
+ static const uint8_t extensions[] = {0x00, 0x04, 0x7f, 0xff, 0x00, 0x00};
+ DataBuffer crit_exts(crit_extensions, sizeof(crit_extensions));
+ DataBuffer non_crit_exts(extensions, sizeof(extensions));
+
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha,
+ kPublicName, 100, record, pub, priv);
+ record.Truncate(record.len() - 2); // Eat the empty extensions.
+ crit_rec.Assign(record);
+ ASSERT_TRUE(crit_rec.Read(0, 2, &tmp));
+ len_buf.Write(0, tmp + crit_exts.len() - 2, 2); // two bytes of length
+ crit_rec.Splice(len_buf, 0, 2);
+ len_buf.Truncate(0);
+
+ ASSERT_TRUE(crit_rec.Read(4, 2, &tmp));
+ len_buf.Write(0, tmp + crit_exts.len() - 2, 2); // two bytes of length
+ crit_rec.Append(crit_exts);
+ crit_rec.Splice(len_buf, 4, 2);
+ len_buf.Truncate(0);
+
+ ASSERT_TRUE(record.Read(0, 2, &tmp));
+ len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2);
+ record.Append(non_crit_exts);
+ record.Splice(len_buf, 0, 2);
+ ASSERT_TRUE(record.Read(4, 2, &tmp));
+ len_buf.Write(0, tmp + non_crit_exts.len() - 2, 2);
+ record.Splice(len_buf, 4, 2);
+
+ EXPECT_EQ(SECFailure,
+ SSL_SetClientEchConfigs(client_->ssl_fd(), crit_rec.data(),
+ crit_rec.len()));
+ EXPECT_EQ(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION, PORT_GetError());
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_FALSE)); // Don't GREASE
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ client_, ssl_tls13_encrypted_client_hello_xtn);
+ StartConnect();
+ client_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, client_->state());
+ ASSERT_FALSE(filter->captured());
+
+ // Now try a variant with non-critical extensions, it should work.
+ Reset();
+ EnsureTlsSetup();
+ EXPECT_EQ(SECSuccess, SSL_SetClientEchConfigs(client_->ssl_fd(),
+ record.data(), record.len()));
+ filter = MakeTlsFilter<TlsExtensionCapture>(
+ client_, ssl_tls13_encrypted_client_hello_xtn);
+ StartConnect();
+ client_->Handshake();
+ ASSERT_EQ(TlsAgent::STATE_CONNECTING, client_->state());
+ ASSERT_TRUE(filter->captured());
+}
+
+// Secure disable without ECH
+TEST_F(TlsConnectStreamTls13, EchRejectAuthCertSuccessNoRetries) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ ConnectExpectFailOneSide(TlsAgent::CLIENT);
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ // Reset expectations for the TlsAgent dtor.
+ server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
+}
+
+// When authenticating to the public name, the client MUST NOT
+// send a certificate in response to a certificate request.
+TEST_F(TlsConnectStreamTls13, EchRejectSuppressClientCert) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ client_->SetupClientAuth();
+ server_->RequestClientAuth(true);
+ auto cert_capture =
+ MakeTlsFilter<TlsHandshakeRecorder>(client_, kTlsHandshakeCertificate);
+ cert_capture->EnableDecryption();
+
+ StartConnect();
+ client_->ExpectSendAlert(kTlsAlertEchRequired);
+ server_->ExpectSendAlert(kTlsAlertCertificateRequired);
+ ConnectExpectFail();
+
+ static const uint8_t empty_cert[4] = {0};
+ EXPECT_EQ(DataBuffer(empty_cert, sizeof(empty_cert)), cert_capture->buffer());
+}
+
+// Secure disable with incompatible ECHConfig
+TEST_F(TlsConnectStreamTls13, EchRejectAuthCertSuccessIncompatibleRetries) {
+ EnsureTlsSetup();
+ ScopedSECKEYPublicKey server_pub;
+ ScopedSECKEYPrivateKey server_priv;
+ ScopedSECKEYPublicKey client_pub;
+ ScopedSECKEYPrivateKey client_priv;
+ DataBuffer server_rec;
+ DataBuffer client_rec;
+
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha,
+ kPublicName, 100, server_rec,
+ server_pub, server_priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteAes,
+ kPublicName, 100, client_rec,
+ client_pub, client_priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(),
+ client_rec.len()));
+
+ // Change the first ECHConfig version to one we don't understand.
+ server_rec.Write(2, 0xfefe, 2);
+ // Skip the ECHConfigs length, the server sender will re-encode.
+ ASSERT_EQ(SECSuccess, SSLInt_SetRawEchConfigForRetry(server_->ssl_fd(),
+ &server_rec.data()[2],
+ server_rec.len() - 2));
+
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ ConnectExpectFailOneSide(TlsAgent::CLIENT);
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ // Reset expectations for the TlsAgent dtor.
+ server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
+}
+
+// Check that an otherwise-accepted ECH fails expectedly
+// with a bad certificate.
+TEST_F(TlsConnectStreamTls13, EchRejectAuthCertFail) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetAuthCertificateCallback(AuthCompleteFail);
+ ConnectExpectAlert(client_, kTlsAlertBadCertificate);
+ client_->CheckErrorCode(SSL_ERROR_BAD_CERTIFICATE);
+ server_->CheckErrorCode(SSL_ERROR_BAD_CERT_ALERT);
+ EXPECT_EQ(TlsAgent::STATE_ERROR, client_->state());
+}
+
+TEST_F(TlsConnectStreamTls13Ech, EchShortClientEncryptedCH) {
+ EnsureTlsSetup();
+ SetupForEchRetry();
+ auto filter = MakeTlsFilter<TlsExtensionResizer>(
+ client_, ssl_tls13_encrypted_client_hello_xtn, 1);
+ ConnectExpectAlert(server_, kTlsAlertDecodeError);
+ client_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+}
+
+TEST_F(TlsConnectStreamTls13Ech, EchLongClientEncryptedCH) {
+ EnsureTlsSetup();
+ SetupForEchRetry();
+ auto filter = MakeTlsFilter<TlsExtensionResizer>(
+ client_, ssl_tls13_encrypted_client_hello_xtn, 1000);
+ ConnectExpectAlert(server_, kTlsAlertDecodeError);
+ client_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+}
+
+TEST_F(TlsConnectStreamTls13Ech, EchShortServerEncryptedCH) {
+ EnsureTlsSetup();
+ SetupForEchRetry();
+ auto filter = MakeTlsFilter<TlsExtensionResizer>(
+ server_, ssl_tls13_encrypted_client_hello_xtn, 1);
+ filter->EnableDecryption();
+ ConnectExpectAlert(client_, kTlsAlertDecodeError);
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ server_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT);
+}
+
+TEST_F(TlsConnectStreamTls13Ech, EchLongServerEncryptedCH) {
+ EnsureTlsSetup();
+ SetupForEchRetry();
+ auto filter = MakeTlsFilter<TlsExtensionResizer>(
+ server_, ssl_tls13_encrypted_client_hello_xtn, 1000);
+ filter->EnableDecryption();
+ ConnectExpectAlert(client_, kTlsAlertDecodeError);
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ server_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT);
+}
+
+// Check that if authCertificate fails, retry_configs
+// are not available to the application.
+TEST_F(TlsConnectStreamTls13Ech, EchInsecureFallbackNoRetries) {
+ EnsureTlsSetup();
+ StackSECItem retry_configs;
+ SetupForEchRetry();
+
+ // Use the filter to make sure retry_configs are sent.
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ server_, ssl_tls13_encrypted_client_hello_xtn);
+ filter->EnableDecryption();
+
+ client_->SetAuthCertificateCallback(AuthCompleteFail);
+ ConnectExpectAlert(client_, kTlsAlertBadCertificate);
+ client_->CheckErrorCode(SSL_ERROR_BAD_CERTIFICATE);
+ server_->CheckErrorCode(SSL_ERROR_BAD_CERT_ALERT);
+ EXPECT_EQ(TlsAgent::STATE_ERROR, client_->state());
+ EXPECT_EQ(SECFailure,
+ SSL_GetEchRetryConfigs(client_->ssl_fd(), &retry_configs));
+ EXPECT_EQ(SSL_ERROR_HANDSHAKE_NOT_COMPLETED, PORT_GetError());
+ ASSERT_EQ(0U, retry_configs.len);
+ EXPECT_TRUE(filter->captured());
+}
+
+// Test that mismatched ECHConfigContents triggers a retry.
+TEST_F(TlsConnectStreamTls13Ech, EchMismatchHpkeCiphersRetry) {
+ EnsureTlsSetup();
+ ScopedSECKEYPublicKey server_pub;
+ ScopedSECKEYPrivateKey server_priv;
+ ScopedSECKEYPublicKey client_pub;
+ ScopedSECKEYPrivateKey client_priv;
+ DataBuffer server_rec;
+ DataBuffer client_rec;
+
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteChaCha,
+ kPublicName, 100, server_rec,
+ server_pub, server_priv);
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kSuiteAes,
+ kPublicName, 100, client_rec,
+ client_pub, client_priv);
+
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+ ASSERT_EQ(SECSuccess,
+ SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(),
+ client_rec.len()));
+
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ ExpectAlert(client_, kTlsAlertEchRequired);
+ ConnectExpectFailOneSide(TlsAgent::CLIENT);
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ DoEchRetry(server_pub, server_priv, server_rec);
+}
+
+// Test that mismatched ECH server keypair triggers a retry.
+TEST_F(TlsConnectStreamTls13Ech, EchMismatchKeysRetry) {
+ EnsureTlsSetup();
+ ScopedSECKEYPublicKey server_pub;
+ ScopedSECKEYPrivateKey server_priv;
+ ScopedSECKEYPublicKey client_pub;
+ ScopedSECKEYPrivateKey client_priv;
+ DataBuffer server_rec;
+ DataBuffer client_rec;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, server_rec,
+ server_pub, server_priv);
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, client_rec,
+ client_pub, client_priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+ ASSERT_EQ(SECSuccess,
+ SSL_SetClientEchConfigs(client_->ssl_fd(), client_rec.data(),
+ client_rec.len()));
+
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ client_->ExpectSendAlert(kTlsAlertEchRequired);
+ ConnectExpectFailOneSide(TlsAgent::CLIENT);
+ client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ server_->ExpectReceiveAlert(kTlsAlertEchRequired, kTlsAlertFatal);
+ server_->Handshake();
+ DoEchRetry(server_pub, server_priv, server_rec);
+}
+
+// Check that the client validates any server response to GREASE ECH
+TEST_F(TlsConnectStreamTls13, EchValidateGreaseResponse) {
+ EnsureTlsSetup();
+ ScopedSECKEYPublicKey server_pub;
+ ScopedSECKEYPrivateKey server_priv;
+ DataBuffer server_rec;
+ TlsConnectTestBase::GenerateEchConfig(HpkeDhKemX25519Sha256, kDefaultSuites,
+ kPublicName, 100, server_rec,
+ server_pub, server_priv);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+
+ // Damage the length and expect an alert.
+ auto filter = MakeTlsFilter<TlsExtensionDamager>(
+ server_, ssl_tls13_encrypted_client_hello_xtn, 0);
+ filter->EnableDecryption();
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_TRUE)); // GREASE
+ ConnectExpectAlert(client_, kTlsAlertDecodeError);
+ client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ server_->CheckErrorCode(SSL_ERROR_DECODE_ERROR_ALERT);
+
+ // If the retry_config contains an unknown version, it should be ignored.
+ Reset();
+ EnsureTlsSetup();
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+ server_rec.Write(2, 0xfefe, 2);
+ // Skip the ECHConfigs length, the server sender will re-encode.
+ ASSERT_EQ(SECSuccess, SSLInt_SetRawEchConfigForRetry(server_->ssl_fd(),
+ &server_rec.data()[2],
+ server_rec.len() - 2));
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_TRUE)); // GREASE
+ Connect();
+
+ // Lastly, if we DO support the retry_config, GREASE ECH should ignore it.
+ Reset();
+ EnsureTlsSetup();
+ server_rec.Write(2, ssl_tls13_encrypted_client_hello_xtn, 2);
+ ASSERT_EQ(SECSuccess,
+ SSL_SetServerEchConfigs(server_->ssl_fd(), server_pub.get(),
+ server_priv.get(), server_rec.data(),
+ server_rec.len()));
+ EXPECT_EQ(SECSuccess, SSL_EnableTls13GreaseEch(client_->ssl_fd(),
+ PR_TRUE)); // GREASE
+ Connect();
+}
+
+// Test a tampered CHInner (decrypt failure).
+// Expect negotiation on outer, which fails due to the tampered transcript.
+TEST_F(TlsConnectStreamTls13, EchBadCiphertext) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ /* Target the payload:
+ struct {
+ ECHCipherSuite suite; // 4B
+ opaque config_id<0..255>; // 32B
+ opaque enc<1..2^16-1>; // 32B for X25519
+ opaque payload<1..2^16-1>;
+ } ClientEncryptedCH;
+ */
+ MakeTlsFilter<TlsExtensionDamager>(client_,
+ ssl_tls13_encrypted_client_hello_xtn, 80);
+ client_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ ConnectExpectFail();
+}
+
+// Test a tampered CHOuter (decrypt failure on AAD).
+// Expect negotiation on outer, which fails due to the tampered transcript.
+TEST_F(TlsConnectStreamTls13, EchOuterBinding) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetAuthCertificateCallback(AuthCompleteSuccess);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+
+ static const uint8_t supported_vers_13[] = {0x02, 0x03, 0x04};
+ DataBuffer buf(supported_vers_13, sizeof(supported_vers_13));
+ MakeTlsFilter<TlsExtensionReplacer>(client_, ssl_tls13_supported_versions_xtn,
+ buf);
+ client_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ ConnectExpectFail();
+}
+
+// Test a bad (unknown) ECHCipherSuite.
+// Expect negotiation on outer, which fails due to the tampered transcript.
+TEST_F(TlsConnectStreamTls13, EchBadCiphersuite) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ /* Make KDF unknown */
+ MakeTlsFilter<TlsExtensionDamager>(client_,
+ ssl_tls13_encrypted_client_hello_xtn, 0);
+ client_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ ConnectExpectFail();
+
+ Reset();
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ /* Make AEAD unknown */
+ MakeTlsFilter<TlsExtensionDamager>(client_,
+ ssl_tls13_encrypted_client_hello_xtn, 3);
+ client_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ server_->ExpectSendAlert(kTlsAlertBadRecordMac);
+ ConnectExpectFail();
+}
+
+// Connect to a 1.2 server, it should ignore ECH.
+TEST_F(TlsConnectStreamTls13, EchToTls12Server) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_2);
+
+ client_->ExpectEch(false);
+ server_->ExpectEch(false);
+ Connect();
+}
+
+TEST_F(TlsConnectStreamTls13, NoEchFromTls12Client) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_2);
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ client_, ssl_tls13_encrypted_client_hello_xtn);
+ client_->ExpectEch(false);
+ server_->ExpectEch(false);
+ SetExpectedVersion(SSL_LIBRARY_VERSION_TLS_1_2);
+ Connect();
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_F(TlsConnectStreamTls13, EchOuterWith12Max) {
+ EnsureTlsSetup();
+ SetupEch(client_, server_);
+ client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+ server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_3);
+
+ static const uint8_t supported_vers_12[] = {0x02, 0x03, 0x03};
+ DataBuffer buf(supported_vers_12, sizeof(supported_vers_12));
+
+ StartConnect();
+ MakeTlsFilter<TlsExtensionReplacer>(client_, ssl_tls13_supported_versions_xtn,
+ buf);
+
+ // Server should ignore the extension if 1.2 is negotiated.
+ // Here the CHInner is not modified, so if Accepted we'd connect.
+ auto filter = MakeTlsFilter<TlsExtensionCapture>(
+ server_, ssl_tls13_encrypted_client_hello_xtn);
+ client_->ExpectEch(false);
+ server_->ExpectEch(false);
+ ConnectExpectAlert(server_, kTlsAlertDecryptError);
+ client_->CheckErrorCode(SSL_ERROR_DECRYPT_ERROR_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE);
+ ASSERT_FALSE(filter->captured());
+}
+
+TEST_F(TlsConnectStreamTls13, EchOuterExtensionsInCHOuter) {
+ EnsureTlsSetup();
+ uint8_t outer[2] = {0};
+ DataBuffer outer_buf(outer, sizeof(outer));
+ MakeTlsFilter<TlsExtensionAppender>(client_, kTlsHandshakeClientHello,
+ ssl_tls13_outer_extensions_xtn,
+ outer_buf);
+
+ ConnectExpectAlert(server_, kTlsAlertUnsupportedExtension);
+ client_->CheckErrorCode(SSL_ERROR_UNSUPPORTED_EXTENSION_ALERT);
+ server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
+}
+
+INSTANTIATE_TEST_CASE_P(EchAgentTest, TlsAgentEchTest,
+ ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
+ TlsConnectTestBase::kTlsV13));
+#else
+
+TEST_P(TlsAgentEchTest, NoEchWithoutHpke) {
+ EnsureInit();
+ uint8_t non_null[1];
+ SECKEYPublicKey pub;
+ SECKEYPrivateKey priv;
+ ASSERT_EQ(SECFailure, SSL_SetClientEchConfigs(agent_->ssl_fd(), non_null,
+ sizeof(non_null)));
+ ASSERT_EQ(SSL_ERROR_FEATURE_DISABLED, PORT_GetError());
+
+ ASSERT_EQ(SECFailure, SSL_SetServerEchConfigs(agent_->ssl_fd(), &pub, &priv,
+ non_null, sizeof(non_null)));
+ ASSERT_EQ(SSL_ERROR_FEATURE_DISABLED, PORT_GetError());
+}
+
+INSTANTIATE_TEST_CASE_P(EchAgentTest, TlsAgentEchTest,
+ ::testing::Combine(TlsConnectTestBase::kTlsVariantsAll,
+ TlsConnectTestBase::kTlsV13));
+
+#endif // NSS_ENABLE_DRAFT_HPKE
+} // namespace nss_test
diff --git a/gtests/ssl_gtest/tls_esni_unittest.cc b/gtests/ssl_gtest/tls_esni_unittest.cc
deleted file mode 100644
index 0a02d0683..000000000
--- a/gtests/ssl_gtest/tls_esni_unittest.cc
+++ /dev/null
@@ -1,494 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "secerr.h"
-#include "ssl.h"
-
-#include "gtest_utils.h"
-#include "tls_agent.h"
-#include "tls_connect.h"
-
-namespace nss_test {
-
-static const char* kDummySni("dummy.invalid");
-
-std::vector<uint16_t> kDefaultSuites = {TLS_AES_256_GCM_SHA384,
- TLS_AES_128_GCM_SHA256};
-std::vector<uint16_t> kChaChaSuite = {TLS_CHACHA20_POLY1305_SHA256};
-std::vector<uint16_t> kBogusSuites = {0};
-std::vector<uint16_t> kTls12Suites = {
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256};
-
-static void NamedGroup2ECParams(SSLNamedGroup group, SECItem* params) {
- auto groupDef = ssl_LookupNamedGroup(group);
- ASSERT_NE(nullptr, groupDef);
-
- auto oidData = SECOID_FindOIDByTag(groupDef->oidTag);
- ASSERT_NE(nullptr, oidData);
- ASSERT_NE(nullptr,
- SECITEM_AllocItem(nullptr, params, (2 + oidData->oid.len)));
-
- /*
- * params->data needs to contain the ASN encoding of an object ID (OID)
- * representing the named curve. The actual OID is in
- * oidData->oid.data so we simply prepend 0x06 and OID length
- */
- params->data[0] = SEC_ASN1_OBJECT_ID;
- params->data[1] = oidData->oid.len;
- memcpy(params->data + 2, oidData->oid.data, oidData->oid.len);
-}
-
-/* Checksum is a 4-byte array. */
-static void UpdateEsniKeysChecksum(DataBuffer* buf) {
- SECStatus rv;
- PRUint8 sha256[32];
-
- /* Stomp the checksum. */
- PORT_Memset(buf->data() + 2, 0, 4);
-
- rv = PK11_HashBuf(ssl3_HashTypeToOID(ssl_hash_sha256), sha256, buf->data(),
- buf->len());
- ASSERT_EQ(SECSuccess, rv);
- buf->Write(2, sha256, 4);
-}
-
-static void GenerateEsniKey(PRTime now, SSLNamedGroup group,
- std::vector<uint16_t>& cipher_suites,
- DataBuffer* record,
- ScopedSECKEYPublicKey* pubKey = nullptr,
- ScopedSECKEYPrivateKey* privKey = nullptr) {
- SECKEYECParams ecParams = {siBuffer, NULL, 0};
- NamedGroup2ECParams(group, &ecParams);
-
- SECKEYPublicKey* pub = nullptr;
- SECKEYPrivateKey* priv = SECKEY_CreateECPrivateKey(&ecParams, &pub, nullptr);
- ASSERT_NE(nullptr, priv);
- SECITEM_FreeItem(&ecParams, PR_FALSE);
- PRUint8 encoded[1024];
- unsigned int encoded_len = 0;
-
- SECStatus rv = SSL_EncodeESNIKeys(
- &cipher_suites[0], cipher_suites.size(), group, pub, 100,
- (now / PR_USEC_PER_SEC) - 1, (now / PR_USEC_PER_SEC) + 10, encoded,
- &encoded_len, sizeof(encoded));
- ASSERT_EQ(SECSuccess, rv);
- ASSERT_GT(encoded_len, 0U);
-
- if (pubKey) {
- pubKey->reset(pub);
- } else {
- SECKEY_DestroyPublicKey(pub);
- }
- if (privKey) {
- privKey->reset(priv);
- } else {
- SECKEY_DestroyPrivateKey(priv);
- }
- record->Truncate(0);
- record->Write(0, encoded, encoded_len);
-}
-
-static void SetupEsni(PRTime now, const std::shared_ptr<TlsAgent>& client,
- const std::shared_ptr<TlsAgent>& server,
- SSLNamedGroup group = ssl_grp_ec_curve25519) {
- ScopedSECKEYPublicKey pub;
- ScopedSECKEYPrivateKey priv;
- DataBuffer record;
-
- GenerateEsniKey(now, ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub,
- &priv);
- SECStatus rv = SSL_SetESNIKeyPair(server->ssl_fd(), priv.get(), record.data(),
- record.len());
- ASSERT_EQ(SECSuccess, rv);
-
- rv = SSL_EnableESNI(client->ssl_fd(), record.data(), record.len(), kDummySni);
- ASSERT_EQ(SECSuccess, rv);
-}
-
-static void CheckSniExtension(const DataBuffer& data) {
- TlsParser parser(data.data(), data.len());
- uint32_t tmp;
- ASSERT_TRUE(parser.Read(&tmp, 2));
- ASSERT_EQ(parser.remaining(), tmp);
- ASSERT_TRUE(parser.Read(&tmp, 1));
- ASSERT_EQ(0U, tmp); /* sni_nametype_hostname */
- DataBuffer name;
- ASSERT_TRUE(parser.ReadVariable(&name, 2));
- ASSERT_EQ(0U, parser.remaining());
- DataBuffer expected(reinterpret_cast<const uint8_t*>(kDummySni),
- strlen(kDummySni));
- ASSERT_EQ(expected, name);
-}
-
-class TlsAgentEsniTest : public TlsAgentTestClient13 {
- public:
- void SetUp() override { now_ = PR_Now(); }
-
- protected:
- PRTime now() const { return now_; }
-
- void InstallEsni(const DataBuffer& record, PRErrorCode err = 0) {
- SECStatus rv = SSL_EnableESNI(agent_->ssl_fd(), record.data(), record.len(),
- kDummySni);
- if (err == 0) {
- ASSERT_EQ(SECSuccess, rv);
- } else {
- ASSERT_EQ(SECFailure, rv);
- ASSERT_EQ(err, PORT_GetError());
- }
- }
-
- private:
- PRTime now_ = 0;
-};
-
-TEST_P(TlsAgentEsniTest, EsniInstall) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- InstallEsni(record);
-}
-
-// The next set of tests fail at setup time.
-TEST_P(TlsAgentEsniTest, EsniInvalidHash) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(time(0), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- record.data()[2]++;
- InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
-}
-
-TEST_P(TlsAgentEsniTest, EsniInvalidVersion) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- record.Write(0, 0xffff, 2);
- InstallEsni(record, SSL_ERROR_UNSUPPORTED_VERSION);
-}
-
-TEST_P(TlsAgentEsniTest, EsniShort) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- record.Truncate(record.len() - 1);
- UpdateEsniKeysChecksum(&record);
- InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
-}
-
-TEST_P(TlsAgentEsniTest, EsniLong) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- record.Write(record.len(), 1, 1);
- UpdateEsniKeysChecksum(&record);
- InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
-}
-
-TEST_P(TlsAgentEsniTest, EsniExtensionMismatch) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- record.Write(record.len() - 1, 1, 1);
- UpdateEsniKeysChecksum(&record);
- InstallEsni(record, SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
-}
-
-// The following tests fail by ignoring the Esni block.
-TEST_P(TlsAgentEsniTest, EsniUnknownGroup) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- record.Write(8, 0xffff, 2); // Fake group
- UpdateEsniKeysChecksum(&record);
- InstallEsni(record, 0);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
- agent_->Handshake();
- ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
- ASSERT_TRUE(!filter->captured());
-}
-
-TEST_P(TlsAgentEsniTest, EsniUnknownCS) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kBogusSuites, &record);
- InstallEsni(record, 0);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
- agent_->Handshake();
- ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
- ASSERT_TRUE(!filter->captured());
-}
-
-TEST_P(TlsAgentEsniTest, EsniInvalidCS) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kTls12Suites, &record);
- UpdateEsniKeysChecksum(&record);
- InstallEsni(record, 0);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
- agent_->Handshake();
- ASSERT_EQ(TlsAgent::STATE_CONNECTING, agent_->state());
- ASSERT_TRUE(!filter->captured());
-}
-
-TEST_P(TlsAgentEsniTest, EsniNotReady) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now() + 1000, ssl_grp_ec_curve25519, kDefaultSuites, &record);
- InstallEsni(record, 0);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
- agent_->Handshake();
- ASSERT_TRUE(!filter->captured());
-}
-
-TEST_P(TlsAgentEsniTest, EsniExpired) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now() - 1000, ssl_grp_ec_curve25519, kDefaultSuites, &record);
- InstallEsni(record, 0);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
- agent_->Handshake();
- ASSERT_TRUE(!filter->captured());
-}
-
-TEST_P(TlsAgentEsniTest, NoSniSoNoEsni) {
- EnsureInit();
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
- SSL_SetURL(agent_->ssl_fd(), "");
- InstallEsni(record, 0);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(agent_, ssl_tls13_encrypted_sni_xtn);
- agent_->Handshake();
- ASSERT_TRUE(!filter->captured());
-}
-
-static int32_t SniCallback(TlsAgent* agent, const SECItem* srvNameAddr,
- PRUint32 srvNameArrSize) {
- EXPECT_EQ(1U, srvNameArrSize);
- SECItem expected = {
- siBuffer, reinterpret_cast<unsigned char*>(const_cast<char*>("server")),
- 6};
- EXPECT_TRUE(!SECITEM_CompareItem(&expected, &srvNameAddr[0]));
- return SECSuccess;
-}
-
-TEST_P(TlsConnectTls13, ConnectEsni) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- auto cFilterSni =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
- auto cFilterEsni =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_encrypted_sni_xtn);
- client_->SetFilter(std::make_shared<ChainedPacketFilter>(
- ChainedPacketFilterInit({cFilterSni, cFilterEsni})));
- auto sfilter =
- MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
- sfilter->EnableDecryption();
- server_->SetSniCallback(SniCallback);
- Connect();
- CheckSniExtension(cFilterSni->extension());
- ASSERT_TRUE(cFilterEsni->captured());
- // Check that our most preferred suite got chosen.
- uint32_t suite;
- ASSERT_TRUE(cFilterEsni->extension().Read(0, 2, &suite));
- ASSERT_EQ(TLS_AES_128_GCM_SHA256, static_cast<PRUint16>(suite));
- ASSERT_TRUE(!sfilter->captured());
-}
-
-TEST_P(TlsConnectTls13, ConnectEsniHrr) {
- EnsureTlsSetup();
- const std::vector<SSLNamedGroup> groups = {ssl_grp_ec_secp384r1};
- server_->ConfigNamedGroups(groups);
- SetupEsni(now(), client_, server_);
- auto hrr_capture = MakeTlsFilter<TlsHandshakeRecorder>(
- server_, kTlsHandshakeHelloRetryRequest);
- auto filter =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
- auto filter2 =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn, true);
- auto efilter =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_tls13_encrypted_sni_xtn);
- auto efilter2 = MakeTlsFilter<TlsExtensionCapture>(
- client_, ssl_tls13_encrypted_sni_xtn, true);
-
- client_->SetFilter(std::make_shared<ChainedPacketFilter>(
- ChainedPacketFilterInit({filter, filter2, efilter, efilter2})));
- server_->SetSniCallback(SniCallback);
- Connect();
- CheckSniExtension(filter->extension());
- CheckSniExtension(filter2->extension());
- ASSERT_TRUE(efilter->captured());
- ASSERT_TRUE(efilter2->captured());
- ASSERT_NE(efilter->extension(), efilter2->extension());
- EXPECT_NE(0UL, hrr_capture->buffer().len());
-}
-
-TEST_P(TlsConnectTls13, ConnectEsniNoDummy) {
- EnsureTlsSetup();
- ScopedSECKEYPublicKey pub;
- ScopedSECKEYPrivateKey priv;
- DataBuffer record;
-
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub,
- &priv);
- SECStatus rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(),
- record.data(), record.len());
- ASSERT_EQ(SECSuccess, rv);
- rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), "");
- ASSERT_EQ(SECSuccess, rv);
-
- auto cfilter =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
- auto sfilter =
- MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
- server_->SetSniCallback(SniCallback);
- Connect();
- ASSERT_TRUE(!cfilter->captured());
- ASSERT_TRUE(!sfilter->captured());
-}
-
-TEST_P(TlsConnectTls13, ConnectEsniNullDummy) {
- EnsureTlsSetup();
- ScopedSECKEYPublicKey pub;
- ScopedSECKEYPrivateKey priv;
- DataBuffer record;
-
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub,
- &priv);
- SECStatus rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(),
- record.data(), record.len());
- ASSERT_EQ(SECSuccess, rv);
- rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), nullptr);
- ASSERT_EQ(SECSuccess, rv);
-
- auto cfilter =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
- auto sfilter =
- MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
- server_->SetSniCallback(SniCallback);
- Connect();
- ASSERT_TRUE(!cfilter->captured());
- ASSERT_TRUE(!sfilter->captured());
-}
-
-/* Tell the client that it supports AES but the server that it supports ChaCha
- */
-TEST_P(TlsConnectTls13, ConnectEsniCSMismatch) {
- EnsureTlsSetup();
- ScopedSECKEYPublicKey pub;
- ScopedSECKEYPrivateKey priv;
- DataBuffer record;
-
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record, &pub,
- &priv);
- PRUint8 encoded[1024];
- unsigned int encoded_len = 0;
-
- SECStatus rv = SSL_EncodeESNIKeys(
- &kChaChaSuite[0], kChaChaSuite.size(), ssl_grp_ec_curve25519, pub.get(),
- 100, (now() / PR_USEC_PER_SEC) - 1, (now() / PR_USEC_PER_SEC) + 10,
- encoded, &encoded_len, sizeof(encoded));
- ASSERT_EQ(SECSuccess, rv);
- ASSERT_LT(0U, encoded_len);
- rv = SSL_SetESNIKeyPair(server_->ssl_fd(), priv.get(), encoded, encoded_len);
- ASSERT_EQ(SECSuccess, rv);
- rv = SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), "");
- ASSERT_EQ(SECSuccess, rv);
- ConnectExpectAlert(server_, illegal_parameter);
- server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
-}
-
-TEST_P(TlsConnectTls13, ConnectEsniP256) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_, ssl_grp_ec_secp256r1);
- auto cfilter =
- MakeTlsFilter<TlsExtensionCapture>(client_, ssl_server_name_xtn);
- auto sfilter =
- MakeTlsFilter<TlsExtensionCapture>(server_, ssl_server_name_xtn);
- server_->SetSniCallback(SniCallback);
- Connect();
- CheckSniExtension(cfilter->extension());
- ASSERT_TRUE(!sfilter->captured());
-}
-
-TEST_P(TlsConnectTls13, ConnectMismatchedEsniKeys) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- // Now install a new set of keys on the client, so we have a mismatch.
- DataBuffer record;
- GenerateEsniKey(now(), ssl_grp_ec_curve25519, kDefaultSuites, &record);
-
- SECStatus rv =
- SSL_EnableESNI(client_->ssl_fd(), record.data(), record.len(), kDummySni);
- ASSERT_EQ(SECSuccess, rv);
- ConnectExpectAlert(server_, illegal_parameter);
- server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
-}
-
-TEST_P(TlsConnectTls13, ConnectDamagedEsniExtensionCH) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- auto filter = MakeTlsFilter<TlsExtensionDamager>(
- client_, ssl_tls13_encrypted_sni_xtn, 50); // in the ciphertext
- ConnectExpectAlert(server_, illegal_parameter);
- server_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_CLIENT_HELLO);
-}
-
-TEST_P(TlsConnectTls13, ConnectRemoveEsniExtensionEE) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- auto filter =
- MakeTlsFilter<TlsExtensionDropper>(server_, ssl_tls13_encrypted_sni_xtn);
- filter->EnableDecryption();
- ConnectExpectAlert(client_, missing_extension);
- client_->CheckErrorCode(SSL_ERROR_MISSING_ESNI_EXTENSION);
-}
-
-TEST_P(TlsConnectTls13, ConnectShortEsniExtensionEE) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- DataBuffer shortNonce;
- auto filter = MakeTlsFilter<TlsExtensionReplacer>(
- server_, ssl_tls13_encrypted_sni_xtn, shortNonce);
- filter->EnableDecryption();
- ConnectExpectAlert(client_, illegal_parameter);
- client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION);
-}
-
-TEST_P(TlsConnectTls13, ConnectBogusEsniExtensionEE) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- const uint8_t bogusNonceBuf[16] = {0};
- DataBuffer bogusNonce(bogusNonceBuf, sizeof(bogusNonceBuf));
- auto filter = MakeTlsFilter<TlsExtensionReplacer>(
- server_, ssl_tls13_encrypted_sni_xtn, bogusNonce);
- filter->EnableDecryption();
- ConnectExpectAlert(client_, illegal_parameter);
- client_->CheckErrorCode(SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION);
-}
-
-// ESNI is a commitment to doing TLS 1.3 or above.
-// The TLS 1.2 server ignores ESNI and processes the dummy SNI.
-// The client then aborts when it sees the server did TLS 1.2.
-TEST_P(TlsConnectTls13, EsniButTLS12Server) {
- EnsureTlsSetup();
- SetupEsni(now(), client_, server_);
- client_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
- SSL_LIBRARY_VERSION_TLS_1_3);
- server_->SetVersionRange(SSL_LIBRARY_VERSION_TLS_1_2,
- SSL_LIBRARY_VERSION_TLS_1_2);
- ConnectExpectAlert(client_, kTlsAlertProtocolVersion);
- client_->CheckErrorCode(SSL_ERROR_UNSUPPORTED_VERSION);
- server_->CheckErrorCode(SSL_ERROR_PROTOCOL_VERSION_ALERT);
- ASSERT_FALSE(SSLInt_ExtensionNegotiated(server_->ssl_fd(),
- ssl_tls13_encrypted_sni_xtn));
-}
-} // namespace nss_test
diff --git a/gtests/ssl_gtest/tls_filter.cc b/gtests/ssl_gtest/tls_filter.cc
index 074a8bf29..2ba97287b 100644
--- a/gtests/ssl_gtest/tls_filter.cc
+++ b/gtests/ssl_gtest/tls_filter.cc
@@ -17,6 +17,7 @@ extern "C" {
#include "gtest_utils.h"
#include "tls_agent.h"
#include "tls_filter.h"
+#include "tls_parser.h"
#include "tls_protect.h"
namespace nss_test {
@@ -1031,6 +1032,69 @@ PacketFilter::Action TlsExtensionReplacer::FilterExtension(
return CHANGE;
}
+PacketFilter::Action TlsExtensionResizer::FilterExtension(
+ uint16_t extension_type, const DataBuffer& input, DataBuffer* output) {
+ if (extension_type != extension_) {
+ return KEEP;
+ }
+
+ if (input.len() <= length_) {
+ DataBuffer buf(length_ - input.len());
+ output->Append(buf);
+ return CHANGE;
+ }
+
+ output->Assign(input.data(), length_);
+ return CHANGE;
+}
+
+PacketFilter::Action TlsExtensionAppender::FilterHandshake(
+ const HandshakeHeader& header, const DataBuffer& input,
+ DataBuffer* output) {
+ TlsParser parser(input);
+ if (!TlsExtensionFilter::FindExtensions(&parser, header)) {
+ return KEEP;
+ }
+ *output = input;
+
+ // Increase the length of the extensions block.
+ if (!UpdateLength(output, parser.consumed(), 2)) {
+ return KEEP;
+ }
+
+ // Extensions in Certificate are nested twice. Increase the size of the
+ // certificate list.
+ if (header.handshake_type() == kTlsHandshakeCertificate) {
+ TlsParser p2(input);
+ if (!p2.SkipVariable(1)) {
+ ADD_FAILURE();
+ return KEEP;
+ }
+ if (!UpdateLength(output, p2.consumed(), 3)) {
+ return KEEP;
+ }
+ }
+
+ size_t offset = output->len();
+ offset = output->Write(offset, extension_, 2);
+ WriteVariable(output, offset, data_, 2);
+
+ return CHANGE;
+}
+
+bool TlsExtensionAppender::UpdateLength(DataBuffer* output, size_t offset,
+ size_t size) {
+ uint32_t len;
+ if (!output->Read(offset, size, &len)) {
+ ADD_FAILURE();
+ return false;
+ }
+
+ len += 4 + data_.len();
+ output->Write(offset, len, size);
+ return true;
+}
+
PacketFilter::Action TlsExtensionDropper::FilterExtension(
uint16_t extension_type, const DataBuffer& input, DataBuffer* output) {
if (extension_type == extension_) {
diff --git a/gtests/ssl_gtest/tls_filter.h b/gtests/ssl_gtest/tls_filter.h
index 5300075ea..7f0a11e39 100644
--- a/gtests/ssl_gtest/tls_filter.h
+++ b/gtests/ssl_gtest/tls_filter.h
@@ -11,6 +11,7 @@
#include <memory>
#include <set>
#include <vector>
+#include "pk11pub.h"
#include "sslt.h"
#include "sslproto.h"
#include "test_io.h"
@@ -525,6 +526,37 @@ class TlsExtensionReplacer : public TlsExtensionFilter {
const DataBuffer data_;
};
+class TlsExtensionResizer : public TlsExtensionFilter {
+ public:
+ TlsExtensionResizer(const std::shared_ptr<TlsAgent>& a, uint16_t extension,
+ size_t length)
+ : TlsExtensionFilter(a), extension_(extension), length_(length) {}
+ PacketFilter::Action FilterExtension(uint16_t extension_type,
+ const DataBuffer& input,
+ DataBuffer* output) override;
+
+ private:
+ uint16_t extension_;
+ size_t length_;
+};
+
+class TlsExtensionAppender : public TlsHandshakeFilter {
+ public:
+ TlsExtensionAppender(const std::shared_ptr<TlsAgent>& a,
+ uint8_t handshake_type, uint16_t ext, DataBuffer& data)
+ : TlsHandshakeFilter(a, {handshake_type}), extension_(ext), data_(data) {}
+
+ virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
+ const DataBuffer& input,
+ DataBuffer* output);
+
+ private:
+ bool UpdateLength(DataBuffer* output, size_t offset, size_t size);
+
+ const uint16_t extension_;
+ const DataBuffer data_;
+};
+
class TlsExtensionDropper : public TlsExtensionFilter {
public:
TlsExtensionDropper(const std::shared_ptr<TlsAgent>& a, uint16_t extension)
diff --git a/lib/ssl/SSLerrs.h b/lib/ssl/SSLerrs.h
index f9c36b7c1..3c585c7d4 100644
--- a/lib/ssl/SSLerrs.h
+++ b/lib/ssl/SSLerrs.h
@@ -585,3 +585,18 @@ ER3(SSL_ERROR_DC_EXPIRED, (SSL_ERROR_BASE + 185),
ER3(SSL_ERROR_DC_INAPPROPRIATE_VALIDITY_PERIOD, (SSL_ERROR_BASE + 186),
"SSL received a delegated credential with excessive TTL.")
+
+ER3(SSL_ERROR_FEATURE_DISABLED, (SSL_ERROR_BASE + 187),
+ "The requested feature is disabled.")
+
+ER3(SSL_ERROR_ECH_RETRY_WITH_ECH, (SSL_ERROR_BASE + 188),
+ "TLS ECH was rejected, but verification succeeded and compatible retry_configs are available.")
+
+ER3(SSL_ERROR_ECH_RETRY_WITHOUT_ECH, (SSL_ERROR_BASE + 189),
+ "TLS ECH was rejected, but verification succeeded and no compatible retry_configs were found.")
+
+ER3(SSL_ERROR_ECH_FAILED, (SSL_ERROR_BASE + 190),
+ "TLS ECH was rejected and verification failed.")
+
+ER3(SSL_ERROR_ECH_REQUIRED_ALERT, (SSL_ERROR_BASE + 191),
+ "SSL peer reported ECH required.")
diff --git a/lib/ssl/manifest.mn b/lib/ssl/manifest.mn
index 5b3584ba9..d3e38b85b 100644
--- a/lib/ssl/manifest.mn
+++ b/lib/ssl/manifest.mn
@@ -60,7 +60,7 @@ CSRCS = \
sslcert.c \
sslgrp.c \
sslprimitive.c \
- tls13esni.c \
+ tls13ech.c \
tls13subcerts.c \
$(NULL)
diff --git a/lib/ssl/ssl.gyp b/lib/ssl/ssl.gyp
index 5c84a1f03..c3c792805 100644
--- a/lib/ssl/ssl.gyp
+++ b/lib/ssl/ssl.gyp
@@ -44,7 +44,7 @@
'ssltrace.c',
'sslver.c',
'tls13con.c',
- 'tls13esni.c',
+ 'tls13ech.c',
'tls13exthandle.c',
'tls13hashstate.c',
'tls13hkdf.c',
diff --git a/lib/ssl/ssl3con.c b/lib/ssl/ssl3con.c
index dd49343bd..bf5704c7f 100644
--- a/lib/ssl/ssl3con.c
+++ b/lib/ssl/ssl3con.c
@@ -21,6 +21,8 @@
#include "sslerr.h"
#include "ssl3ext.h"
#include "ssl3exthandle.h"
+#include "tls13ech.h"
+#include "tls13exthandle.h"
#include "tls13psk.h"
#include "tls13subcerts.h"
#include "prtime.h"
@@ -62,7 +64,6 @@ static SECStatus ssl3_HandlePostHelloHandshakeMessage(sslSocket *ss,
PRUint8 *b,
PRUint32 length);
static SECStatus ssl3_FlushHandshakeMessages(sslSocket *ss, PRInt32 flags);
-
static CK_MECHANISM_TYPE ssl3_GetHashMechanismByHashType(SSLHashType hashType);
static CK_MECHANISM_TYPE ssl3_GetMgfMechanismByHashType(SSLHashType hash);
PRBool ssl_IsRsaPssSignatureScheme(SSLSignatureScheme scheme);
@@ -1167,7 +1168,7 @@ ssl_ClientReadVersion(sslSocket *ss, PRUint8 **b, unsigned int *len,
return SECSuccess;
}
-static SECStatus
+SECStatus
ssl3_GetNewRandom(SSL3Random random)
{
SECStatus rv;
@@ -3109,6 +3110,9 @@ ssl3_HandleAlert(sslSocket *ss, sslBuffer *buf)
case bad_certificate_hash_value:
error = SSL_ERROR_BAD_CERT_HASH_VALUE_ALERT;
break;
+ case ech_required:
+ error = SSL_ERROR_ECH_REQUIRED_ALERT;
+ break;
default:
error = SSL_ERROR_RX_UNKNOWN_ALERT;
break;
@@ -3737,6 +3741,27 @@ loser:
return SECFailure;
}
+void
+ssl3_CoalesceEchHandshakeHashes(sslSocket *ss)
+{
+ /* |sha| contains the CHOuter transcript, which is the singular
+ * transcript if not doing ECH. If the server responded with 1.2,
+ * contexts are not yet initialized. */
+ if (ss->ssl3.hs.echAccepted) {
+ if (ss->ssl3.hs.sha) {
+ PORT_Assert(ss->ssl3.hs.shaEchInner);
+ PK11_DestroyContext(ss->ssl3.hs.sha, PR_TRUE);
+ ss->ssl3.hs.sha = ss->ssl3.hs.shaEchInner;
+ ss->ssl3.hs.shaEchInner = NULL;
+ }
+ } else {
+ if (ss->ssl3.hs.shaEchInner) {
+ PK11_DestroyContext(ss->ssl3.hs.shaEchInner, PR_TRUE);
+ ss->ssl3.hs.shaEchInner = NULL;
+ }
+ }
+}
+
/* ssl3_InitHandshakeHashes creates handshake hash contexts and hashes in
* buffered messages in ss->ssl3.hs.messages. Called from
* ssl3_NegotiateCipherSuite(), tls13_HandleClientHelloPart2(),
@@ -3781,6 +3806,18 @@ ssl3_InitHandshakeHashes(sslSocket *ss)
return SECFailure;
}
+ /* Alternate transcript hash used in Encrypted Client Hello. */
+ if (!ss->sec.isServer && ss->ssl3.hs.echHpkeCtx) {
+ ss->ssl3.hs.shaEchInner = PK11_CreateDigestContext(hash_oid->offset);
+ if (ss->ssl3.hs.shaEchInner == NULL) {
+ ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ if (PK11_DigestBegin(ss->ssl3.hs.shaEchInner) != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ }
} else {
/* Both ss->ssl3.hs.md5 and ss->ssl3.hs.sha should be NULL or
* created successfully. */
@@ -3817,6 +3854,15 @@ ssl3_InitHandshakeHashes(sslSocket *ss)
}
sslBuffer_Clear(&ss->ssl3.hs.messages);
}
+ if (ss->ssl3.hs.shaEchInner &&
+ ss->ssl3.hs.echInnerMessages.len > 0) {
+ if (PK11_DigestOp(ss->ssl3.hs.shaEchInner, ss->ssl3.hs.echInnerMessages.buf,
+ ss->ssl3.hs.echInnerMessages.len) != SECSuccess) {
+ ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
+ return SECFailure;
+ }
+ sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages);
+ }
return SECSuccess;
}
@@ -3828,6 +3874,7 @@ ssl3_RestartHandshakeHashes(sslSocket *ss)
SSL_GETPID(), ss->fd));
ss->ssl3.hs.hashType = handshake_hash_unknown;
ss->ssl3.hs.messages.len = 0;
+ ss->ssl3.hs.echInnerMessages.len = 0;
if (ss->ssl3.hs.md5) {
PK11_DestroyContext(ss->ssl3.hs.md5, PR_TRUE);
ss->ssl3.hs.md5 = NULL;
@@ -3836,12 +3883,46 @@ ssl3_RestartHandshakeHashes(sslSocket *ss)
PK11_DestroyContext(ss->ssl3.hs.sha, PR_TRUE);
ss->ssl3.hs.sha = NULL;
}
+ if (ss->ssl3.hs.shaEchInner) {
+ PK11_DestroyContext(ss->ssl3.hs.shaEchInner, PR_TRUE);
+ ss->ssl3.hs.shaEchInner = NULL;
+ }
if (ss->ssl3.hs.shaPostHandshake) {
PK11_DestroyContext(ss->ssl3.hs.shaPostHandshake, PR_TRUE);
ss->ssl3.hs.shaPostHandshake = NULL;
}
}
+/* For TLS 1.3 EncryptedClientHello, add the provided buffer to the
+ * given hash context. This is only needed for the initial CH,
+ * after which ssl3_UpdateHandshakeHashes will update both contexts
+ * until ssl3_CoalesceEchHandshakeHashes. */
+SECStatus
+ssl3_UpdateExplicitHandshakeTranscript(sslSocket *ss, const unsigned char *b,
+ unsigned int l, sslBuffer *target)
+{
+ PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
+ PORT_Assert(ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3);
+ if (ss->sec.isServer) {
+ /* Only the client maintains two states at the outset. */
+ PORT_Assert(target != &ss->ssl3.hs.echInnerMessages);
+ }
+ return sslBuffer_Append(target, b, l);
+}
+static SECStatus
+ssl3_UpdateOuterHandshakeHashes(sslSocket *ss, const unsigned char *b,
+ unsigned int l)
+{
+ return ssl3_UpdateExplicitHandshakeTranscript(ss, b, l,
+ &ss->ssl3.hs.messages);
+}
+static SECStatus
+ssl3_UpdateInnerHandshakeHashes(sslSocket *ss, const unsigned char *b,
+ unsigned int l)
+{
+ return ssl3_UpdateExplicitHandshakeTranscript(ss, b, l,
+ &ss->ssl3.hs.echInnerMessages);
+}
/*
* Handshake messages
*/
@@ -3872,7 +3953,14 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss, const unsigned char *b, unsigned int l
if (ss->ssl3.hs.hashType == handshake_hash_unknown ||
ss->ssl3.hs.hashType == handshake_hash_record) {
- return sslBuffer_Append(&ss->ssl3.hs.messages, b, l);
+ rv = sslBuffer_Append(&ss->ssl3.hs.messages, b, l);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ if (!ss->sec.isServer && ss->ssl3.hs.echHpkeCtx) {
+ return ssl3_UpdateInnerHandshakeHashes(ss, b, l);
+ }
+ return SECSuccess;
}
PRINT_BUF(90, (ss, "handshake hash input:", b, l));
@@ -4882,7 +4970,7 @@ ssl_SetClientHelloSpecVersion(sslSocket *ss, ssl3CipherSpec *spec)
ssl_ReleaseSpecWriteLock(ss);
}
-static SECStatus
+SECStatus
ssl3_InsertChHeaderSize(const sslSocket *ss, sslBuffer *preamble, const sslBuffer *extensions)
{
SECStatus rv;
@@ -4958,15 +5046,16 @@ ssl3_AppendCipherSuites(sslSocket *ss, PRBool fallbackSCSV, sslBuffer *buf)
return sslBuffer_InsertLength(buf, offset, 2);
}
-static SECStatus
+SECStatus
ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid,
- PRBool realSid, PRUint16 version,
+ PRBool realSid, PRUint16 version, PRBool isEchInner,
const sslBuffer *extensions, sslBuffer *preamble)
{
SECStatus rv;
sslBuffer constructed = SSL_BUFFER_EMPTY;
+ const PRUint8 *client_random = isEchInner ? ss->ssl3.hs.client_inner_random : ss->ssl3.hs.client_random;
PORT_Assert(sid);
- PRBool fallbackSCSV = ss->opt.enableFallbackSCSV &&
+ PRBool fallbackSCSV = ss->opt.enableFallbackSCSV && !isEchInner &&
(!realSid || version < sid->version);
rv = sslBuffer_AppendNumber(&constructed, ssl_hs_client_hello, 1);
@@ -5018,12 +5107,12 @@ ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid,
goto loser;
}
- rv = sslBuffer_Append(&constructed, ss->ssl3.hs.client_random, SSL3_RANDOM_LENGTH);
+ rv = sslBuffer_Append(&constructed, client_random, SSL3_RANDOM_LENGTH);
if (rv != SECSuccess) {
goto loser;
}
- if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3) {
+ if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3 && !isEchInner) {
rv = sslBuffer_AppendVariable(&constructed, sid->u.ssl3.sessionID,
sid->u.ssl3.sessionIDLength, 1);
} else if (ss->opt.enableTls13CompatMode && !IS_DTLS(ss)) {
@@ -5373,49 +5462,72 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type)
}
rv = ssl3_CreateClientHelloPreamble(ss, sid, requestingResume, version,
- &extensionBuf, &chBuf);
+ PR_FALSE, &extensionBuf, &chBuf);
if (rv != SECSuccess) {
goto loser; /* err set by ssl3_CreateClientHelloPreamble. */
}
- if (extensionBuf.len) {
- rv = ssl_InsertPaddingExtension(ss, chBuf.len, &extensionBuf);
- if (rv != SECSuccess) {
- goto loser; /* err set by ssl_InsertPaddingExtension. */
- }
+ if (!ss->ssl3.hs.echHpkeCtx) {
+ if (extensionBuf.len) {
+ rv = tls13_MaybeGreaseEch(ss, chBuf.len, &extensionBuf);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by tls13_MaybeGreaseEch. */
+ }
+ rv = ssl_InsertPaddingExtension(ss, chBuf.len, &extensionBuf);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl_InsertPaddingExtension. */
+ }
- rv = ssl3_InsertChHeaderSize(ss, &chBuf, &extensionBuf);
- if (rv != SECSuccess) {
- goto loser; /* err set by ssl3_InsertChHeaderSize. */
+ rv = ssl3_InsertChHeaderSize(ss, &chBuf, &extensionBuf);
+ if (rv != SECSuccess) {
+ goto loser; /* err set by ssl3_InsertChHeaderSize. */
+ }
+
+ /* If we are sending a PSK binder, replace the dummy value. */
+ if (ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) {
+ rv = tls13_WriteExtensionsWithBinder(ss, &extensionBuf, &chBuf);
+ } else {
+ rv = sslBuffer_AppendNumber(&chBuf, extensionBuf.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendBuffer(&chBuf, &extensionBuf);
+ }
+ if (rv != SECSuccess) {
+ goto loser; /* err set by sslBuffer_Append*. */
+ }
}
- /* If we are sending a PSK binder, replace the dummy value. */
- if (ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) {
- rv = tls13_WriteExtensionsWithBinder(ss, &extensionBuf, &chBuf);
- } else {
- rv = sslBuffer_AppendNumber(&chBuf, extensionBuf.len, 2);
+ /* If we already have a message in place, we need to enqueue it.
+ * This empties the buffer. This is a convenient place to call
+ * dtls_StageHandshakeMessage to mark the message boundary. */
+ if (IS_DTLS(ss)) {
+ rv = dtls_StageHandshakeMessage(ss);
if (rv != SECSuccess) {
goto loser;
}
- rv = sslBuffer_AppendBuffer(&chBuf, &extensionBuf);
}
+ rv = ssl3_AppendHandshake(ss, chBuf.buf, chBuf.len);
+ } else {
+ rv = tls13_ConstructClientHelloWithEch(ss, sid, !requestingResume, &chBuf, &extensionBuf);
if (rv != SECSuccess) {
- goto loser; /* err set by sslBuffer_Append*. */
+ goto loser; /* code set */
}
- }
- sslBuffer_Clear(&extensionBuf);
-
- /* If we already have a message in place, we need to enqueue it.
- * This empties the buffer. This is a convenient place to call
- * dtls_StageHandshakeMessage to mark the message boundary. */
- if (IS_DTLS(ss)) {
- rv = dtls_StageHandshakeMessage(ss);
+ rv = ssl3_UpdateOuterHandshakeHashes(ss, chBuf.buf, chBuf.len);
if (rv != SECSuccess) {
- return rv;
+ goto loser; /* code set */
+ }
+
+ if (IS_DTLS(ss)) {
+ rv = dtls_StageHandshakeMessage(ss);
+ if (rv != SECSuccess) {
+ return rv;
+ }
}
+ /* By default, all messagess are added to both the inner and
+ * outer transcripts. For CH (or CH2 if HRR), that's problematic. */
+ rv = ssl3_AppendHandshakeSuppressHash(ss, chBuf.buf, chBuf.len);
}
- rv = ssl3_AppendHandshake(ss, chBuf.buf, chBuf.len);
- sslBuffer_Clear(&chBuf);
if (rv != SECSuccess) {
goto loser;
}
@@ -5450,6 +5562,8 @@ ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type)
}
ss->ssl3.hs.ws = wait_server_hello;
+ sslBuffer_Clear(&chBuf);
+ sslBuffer_Clear(&extensionBuf);
return SECSuccess;
loser:
@@ -6866,17 +6980,15 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
/* There are three situations in which the server must pick
* TLS 1.3.
*
- * 1. We offered ESNI.
- * 2. We received HRR
- * 3. We sent early app data.
+ * 1. We received HRR
+ * 2. We sent early app data
+ * 3. ECH was accepted (checked in MaybeHandleEchSignal)
+ *
+ * If we offered ECH and the server negotiated a lower version,
+ * authenticate to the public name for secure disablement.
*
*/
if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
- if (ss->xtnData.esniPrivateKey) {
- desc = protocol_version;
- errCode = SSL_ERROR_UNSUPPORTED_VERSION;
- goto alert_loser;
- }
if (isHelloRetry || ss->ssl3.hs.helloRetry) {
/* SSL3_SendAlert() will uncache the SID. */
desc = illegal_parameter;
@@ -6952,6 +7064,11 @@ ssl3_HandleServerHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
return SECSuccess;
}
+ rv = tls13_MaybeHandleEchSignal(ss);
+ if (rv != SECSuccess) {
+ goto alert_loser;
+ }
+
rv = ssl3_HandleParsedExtensions(ss, ssl_hs_server_hello);
ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
if (rv != SECSuccess) {
@@ -7681,6 +7798,12 @@ ssl3_CompleteHandleCertificateRequest(sslSocket *ss,
{
SECStatus rv;
+ /* Should not send a client cert when (non-GREASE) ECH is rejected. */
+ if (ss->ssl3.hs.echHpkeCtx && !ss->ssl3.hs.echAccepted) {
+ PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn));
+ goto send_no_certificate;
+ }
+
if (ss->getClientAuthData != NULL) {
PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) ==
ssl_preinfo_all);
@@ -7763,49 +7886,53 @@ ssl3_CheckFalseStart(sslSocket *ss)
SSL_TRC(3, ("%d: SSL[%d]: no false start callback so no false start",
SSL_GETPID(), ss->fd));
} else {
- PRBool maybeFalseStart = PR_TRUE;
SECStatus rv;
rv = ssl_CheckServerRandom(ss);
if (rv != SECSuccess) {
SSL_TRC(3, ("%d: SSL[%d]: no false start due to possible downgrade",
SSL_GETPID(), ss->fd));
- maybeFalseStart = PR_FALSE;
+ goto no_false_start;
}
/* An attacker can control the selected ciphersuite so we only wish to
* do False Start in the case that the selected ciphersuite is
* sufficiently strong that the attack can gain no advantage.
* Therefore we always require an 80-bit cipher. */
- if (maybeFalseStart) {
- ssl_GetSpecReadLock(ss);
- maybeFalseStart = ss->ssl3.cwSpec->cipherDef->secret_key_size >= 10;
- ssl_ReleaseSpecReadLock(ss);
+ ssl_GetSpecReadLock(ss);
+ PRBool weakCipher = ss->ssl3.cwSpec->cipherDef->secret_key_size < 10;
+ ssl_ReleaseSpecReadLock(ss);
+ if (weakCipher) {
+ SSL_TRC(3, ("%d: SSL[%d]: no false start due to weak cipher",
+ SSL_GETPID(), ss->fd));
+ goto no_false_start;
}
- if (!maybeFalseStart) {
- SSL_TRC(3, ("%d: SSL[%d]: no false start due to weak cipher",
+ if (ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn)) {
+ SSL_TRC(3, ("%d: SSL[%d]: no false start due to lower version after ECH",
SSL_GETPID(), ss->fd));
+ goto no_false_start;
+ }
+
+ PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) ==
+ ssl_preinfo_all);
+ rv = (ss->canFalseStartCallback)(ss->fd,
+ ss->canFalseStartCallbackData,
+ &ss->ssl3.hs.canFalseStart);
+ if (rv == SECSuccess) {
+ SSL_TRC(3, ("%d: SSL[%d]: false start callback returned %s",
+ SSL_GETPID(), ss->fd,
+ ss->ssl3.hs.canFalseStart ? "TRUE"
+ : "FALSE"));
} else {
- PORT_Assert((ss->ssl3.hs.preliminaryInfo & ssl_preinfo_all) ==
- ssl_preinfo_all);
- rv = (ss->canFalseStartCallback)(ss->fd,
- ss->canFalseStartCallbackData,
- &ss->ssl3.hs.canFalseStart);
- if (rv == SECSuccess) {
- SSL_TRC(3, ("%d: SSL[%d]: false start callback returned %s",
- SSL_GETPID(), ss->fd,
- ss->ssl3.hs.canFalseStart ? "TRUE"
- : "FALSE"));
- } else {
- SSL_TRC(3, ("%d: SSL[%d]: false start callback failed (%s)",
- SSL_GETPID(), ss->fd,
- PR_ErrorToName(PR_GetError())));
- }
- return rv;
+ SSL_TRC(3, ("%d: SSL[%d]: false start callback failed (%s)",
+ SSL_GETPID(), ss->fd,
+ PR_ErrorToName(PR_GetError())));
}
+ return rv;
}
+no_false_start:
ss->ssl3.hs.canFalseStart = PR_FALSE;
return SECSuccess;
}
@@ -8393,12 +8520,9 @@ ssl3_ServerCallSNICallback(sslSocket *ss)
}
/* Need to tell the client that application has picked
* the name from the offered list and reconfigured the socket.
- * Don't do this if we negotiated ESNI.
*/
- if (!ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_sni_xtn)) {
- ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_server_name_xtn,
- ssl_SendEmptyExtension);
- }
+ ssl3_RegisterExtensionSender(ss, &ss->xtnData, ssl_server_name_xtn,
+ ssl_SendEmptyExtension);
} else {
/* Callback returned index outside of the boundary. */
PORT_Assert((unsigned int)ret < ss->xtnData.sniNameArrSize);
@@ -8504,6 +8628,13 @@ ssl_GenerateServerRandom(sslSocket *ss)
return SECFailure;
}
+ if (ss->ssl3.hs.echAccepted) {
+ rv = tls13_WriteServerEchSignal(ss);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ }
+
if (ss->version == ss->vrange.max) {
return SECSuccess;
}
@@ -8608,6 +8739,73 @@ ssl3_HandleClientHelloPreamble(sslSocket *ss, PRUint8 **b, PRUint32 *length, SEC
return SECSuccess;
}
+static SECStatus
+ssl3_ValidatePreambleWithVersion(sslSocket *ss, const SECItem *sidBytes, const SECItem *comps,
+ const SECItem *cookieBytes)
+{
+ SECStatus rv;
+ if (ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) {
+ if (sidBytes->len > 0 && !IS_DTLS(ss)) {
+ SECITEM_FreeItem(&ss->ssl3.hs.fakeSid, PR_FALSE);
+ rv = SECITEM_CopyItem(NULL, &ss->ssl3.hs.fakeSid, sidBytes);
+ if (rv != SECSuccess) {
+ FATAL_ERROR(ss, PORT_GetError(), internal_error);
+ return SECFailure;
+ }
+ }
+
+ /* TLS 1.3 requires that compression include only null. */
+ if (comps->len != 1 || comps->data[0] != ssl_compression_null) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+
+ /* receivedCcs is only valid if we sent an HRR. */
+ if (ss->ssl3.hs.receivedCcs && !ss->ssl3.hs.helloRetry) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER, unexpected_message);
+ return SECFailure;
+ }
+
+ /* A DTLS 1.3-only client MUST set the legacy_cookie field to zero length.
+ * If a DTLS 1.3 ClientHello is received with any other value in this field,
+ * the server MUST abort the handshake with an "illegal_parameter" alert. */
+ if (IS_DTLS(ss) && cookieBytes->len != 0) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+ } else {
+ /* ECH not possible here. */
+ ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech;
+
+ /* HRR and ECH are TLS1.3-only. We ignore the Cookie extension here. */
+ if (ss->ssl3.hs.helloRetry) {
+ FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version);
+ return SECFailure;
+ }
+
+ /* receivedCcs is only valid if we sent an HRR. */
+ if (ss->ssl3.hs.receivedCcs) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER, unexpected_message);
+ return SECFailure;
+ }
+
+ /* TLS versions prior to 1.3 must include null somewhere. */
+ if (comps->len < 1 ||
+ !memchr(comps->data, ssl_compression_null, comps->len)) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+
+ /* We never send cookies in DTLS 1.2. */
+ if (IS_DTLS(ss) && cookieBytes->len != 0) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+ }
+
+ return SECSuccess;
+}
+
/* Called from ssl3_HandleHandshakeMessage() when it has deciphered a complete
* ssl3 Client Hello message.
* Caller must hold Handshake and RecvBuf locks.
@@ -8627,6 +8825,7 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
SECItem cookieBytes = { siBuffer, NULL, 0 };
SECItem suites = { siBuffer, NULL, 0 };
SECItem comps = { siBuffer, NULL, 0 };
+ SECItem *echInner = NULL;
PRBool isTLS13;
const PRUint8 *savedMsg = b;
const PRUint32 savedLen = length;
@@ -8748,64 +8947,23 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
goto alert_loser;
}
- if (sidBytes.len > 0 && !IS_DTLS(ss)) {
- SECITEM_FreeItem(&ss->ssl3.hs.fakeSid, PR_FALSE);
- rv = SECITEM_CopyItem(NULL, &ss->ssl3.hs.fakeSid, &sidBytes);
- if (rv != SECSuccess) {
- desc = internal_error;
- errCode = PORT_GetError();
- goto alert_loser;
- }
- }
-
- /* TLS 1.3 requires that compression include only null. */
- if (comps.len != 1 || comps.data[0] != ssl_compression_null) {
- goto alert_loser;
- }
-
/* If there is a cookie, then this is a second ClientHello (TLS 1.3). */
if (ssl3_FindExtension(ss, ssl_tls13_cookie_xtn)) {
ss->ssl3.hs.helloRetry = PR_TRUE;
}
- /* receivedCcs is only valid if we sent an HRR. */
- if (ss->ssl3.hs.receivedCcs && !ss->ssl3.hs.helloRetry) {
- desc = unexpected_message;
- errCode = SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER;
- goto alert_loser;
- }
-
- /* A DTLS 1.3-only client MUST set the legacy_cookie field to zero length.
- * If a DTLS 1.3 ClientHello is received with any other value in this field,
- * the server MUST abort the handshake with an "illegal_parameter" alert. */
- if (IS_DTLS(ss) && cookieBytes.len != 0) {
- goto alert_loser;
- }
- } else {
- /* HRR is TLS1.3-only. We ignore the Cookie extension here. */
- if (ss->ssl3.hs.helloRetry) {
- desc = protocol_version;
- errCode = SSL_ERROR_UNSUPPORTED_VERSION;
- goto alert_loser;
- }
-
- /* receivedCcs is only valid if we sent an HRR. */
- if (ss->ssl3.hs.receivedCcs) {
- desc = unexpected_message;
- errCode = SSL_ERROR_RX_UNEXPECTED_CHANGE_CIPHER;
- goto alert_loser;
- }
-
- /* TLS versions prior to 1.3 must include null somewhere. */
- if (comps.len < 1 ||
- !memchr(comps.data, ssl_compression_null, comps.len)) {
- goto alert_loser;
+ rv = tls13_MaybeHandleEch(ss, savedMsg, savedLen, &sidBytes,
+ &comps, &cookieBytes, &suites, &echInner);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser; /* code set, alert sent. */
}
+ }
- /* We never send cookies in DTLS 1.2. */
- if (IS_DTLS(ss) && cookieBytes.len != 0) {
- goto loser;
- }
+ rv = ssl3_ValidatePreambleWithVersion(ss, &sidBytes, &comps, &cookieBytes);
+ if (rv != SECSuccess) {
+ errCode = PORT_GetError();
+ goto loser; /* code set, alert sent. */
}
/* Now parse the rest of the extensions. */
@@ -8941,7 +9099,11 @@ ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
}
if (isTLS13) {
- rv = tls13_HandleClientHelloPart2(ss, &suites, sid, savedMsg, savedLen);
+ rv = tls13_HandleClientHelloPart2(ss, &suites, sid,
+ ss->ssl3.hs.echAccepted ? echInner->data : savedMsg,
+ ss->ssl3.hs.echAccepted ? echInner->len : savedLen);
+ SECITEM_FreeItem(echInner, PR_TRUE);
+ echInner = NULL;
} else {
rv = ssl3_HandleClientHelloPart2(ss, &suites, sid,
savedMsg, savedLen);
@@ -8956,6 +9118,7 @@ alert_loser:
(void)SSL3_SendAlert(ss, level, desc);
/* FALLTHRU */
loser:
+ SECITEM_FreeItem(echInner, PR_TRUE);
PORT_SetError(errCode);
return SECFailure;
}
@@ -9402,6 +9565,8 @@ ssl3_HandleV2ClientHello(sslSocket *ss, unsigned char *buffer, unsigned int leng
errCode = SSL_ERROR_UNSUPPORTED_VERSION;
goto alert_loser;
}
+ /* ECH not possible here. */
+ ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech;
ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_version;
if (!ss->firstHsDone) {
ssl_GetSpecWriteLock(ss);
@@ -12115,6 +12280,21 @@ ssl_HashHandshakeMessage(sslSocket *ss, SSLHandshakeType ct,
}
SECStatus
+ssl_HashHandshakeMessageDefault(sslSocket *ss, SSLHandshakeType ct,
+ const PRUint8 *b, PRUint32 length)
+{
+ return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq,
+ b, length, ssl3_UpdateOuterHandshakeHashes);
+}
+SECStatus
+ssl_HashHandshakeMessageEchInner(sslSocket *ss, SSLHandshakeType ct,
+ const PRUint8 *b, PRUint32 length)
+{
+ return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq,
+ b, length, ssl3_UpdateInnerHandshakeHashes);
+}
+
+SECStatus
ssl_HashPostHandshakeMessage(sslSocket *ss, SSLHandshakeType ct,
const PRUint8 *b, PRUint32 length)
{
@@ -13246,6 +13426,7 @@ ssl3_InitState(sslSocket *ss)
ssl3_ResetExtensionData(&ss->xtnData, ss);
PR_INIT_CLIST(&ss->ssl3.hs.remoteExtensions);
+ PR_INIT_CLIST(&ss->ssl3.hs.echOuterExtensions);
if (IS_DTLS(ss)) {
ss->ssl3.hs.sendMessageSeq = 0;
ss->ssl3.hs.recvMessageSeq = 0;
@@ -13264,6 +13445,8 @@ ssl3_InitState(sslSocket *ss)
ss->ssl3.hs.serverHsTrafficSecret = NULL;
ss->ssl3.hs.clientTrafficSecret = NULL;
ss->ssl3.hs.serverTrafficSecret = NULL;
+ ss->ssl3.hs.echHpkeCtx = NULL;
+ ss->ssl3.hs.echAccepted = PR_FALSE;
PORT_Assert(!ss->ssl3.hs.messages.buf && !ss->ssl3.hs.messages.space);
ss->ssl3.hs.messages.buf = NULL;
@@ -13596,12 +13779,18 @@ ssl3_DestroySSL3Info(sslSocket *ss)
if (ss->ssl3.hs.sha) {
PK11_DestroyContext(ss->ssl3.hs.sha, PR_TRUE);
}
+ if (ss->ssl3.hs.shaEchInner) {
+ PK11_DestroyContext(ss->ssl3.hs.shaEchInner, PR_TRUE);
+ }
if (ss->ssl3.hs.shaPostHandshake) {
PK11_DestroyContext(ss->ssl3.hs.shaPostHandshake, PR_TRUE);
}
if (ss->ssl3.hs.messages.buf) {
sslBuffer_Clear(&ss->ssl3.hs.messages);
}
+ if (ss->ssl3.hs.echInnerMessages.buf) {
+ sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages);
+ }
/* free the SSL3Buffer (msg_body) */
PORT_Free(ss->ssl3.hs.msg_body.buf);
@@ -13620,6 +13809,7 @@ ssl3_DestroySSL3Info(sslSocket *ss)
/* Destroy remote extensions */
ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
+ ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions);
ssl3_DestroyExtensionData(&ss->xtnData);
/* Destroy cipher specs */
@@ -13653,6 +13843,10 @@ ssl3_DestroySSL3Info(sslSocket *ss)
/* Destroy TLS 1.3 PSKs. */
tls13_DestroyPskList(&ss->ssl3.hs.psks);
+
+ /* TLS 1.3 ECH state. */
+ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE);
+ PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */
}
/*
diff --git a/lib/ssl/ssl3ext.c b/lib/ssl/ssl3ext.c
index 65a69450d..78c2b901a 100644
--- a/lib/ssl/ssl3ext.c
+++ b/lib/ssl/ssl3ext.c
@@ -53,7 +53,6 @@ static const ssl3ExtensionHandler clientHelloHandlers[] = {
{ ssl_tls13_early_data_xtn, &tls13_ServerHandleEarlyDataXtn },
{ ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ServerHandlePskModesXtn },
{ ssl_tls13_cookie_xtn, &tls13_ServerHandleCookieXtn },
- { ssl_tls13_encrypted_sni_xtn, &tls13_ServerHandleEsniXtn },
{ ssl_tls13_post_handshake_auth_xtn, &tls13_ServerHandlePostHandshakeAuthXtn },
{ ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn },
{ 0, NULL }
@@ -74,6 +73,7 @@ static const ssl3ExtensionHandler serverHelloHandlersTLS[] = {
{ ssl_tls13_key_share_xtn, &tls13_ClientHandleKeyShareXtn },
{ ssl_tls13_pre_shared_key_xtn, &tls13_ClientHandlePreSharedKeyXtn },
{ ssl_tls13_early_data_xtn, &tls13_ClientHandleEarlyDataXtn },
+ { ssl_tls13_encrypted_client_hello_xtn, &tls13_ClientHandleEchXtn },
{ ssl_record_size_limit_xtn, &ssl_HandleRecordSizeLimitXtn },
{ 0, NULL }
};
@@ -143,7 +143,6 @@ static const sslExtensionBuilder clientHelloSendersTLS[] =
{ ssl_signature_algorithms_xtn, &ssl3_SendSigAlgsXtn },
{ ssl_tls13_cookie_xtn, &tls13_ClientSendHrrCookieXtn },
{ ssl_tls13_psk_key_exchange_modes_xtn, &tls13_ClientSendPskModesXtn },
- { ssl_tls13_encrypted_sni_xtn, &tls13_ClientSendEsniXtn },
{ ssl_tls13_post_handshake_auth_xtn, &tls13_ClientSendPostHandshakeAuthXtn },
{ ssl_record_size_limit_xtn, &ssl_SendRecordSizeLimitXtn },
/* The pre_shared_key extension MUST be last. */
@@ -193,7 +192,8 @@ static const struct {
{ ssl_tls13_psk_key_exchange_modes_xtn, ssl_ext_native_only },
{ ssl_tls13_ticket_early_data_info_xtn, ssl_ext_native_only },
{ ssl_tls13_certificate_authorities_xtn, ssl_ext_native },
- { ssl_renegotiation_info_xtn, ssl_ext_native }
+ { ssl_renegotiation_info_xtn, ssl_ext_native },
+ { ssl_tls13_encrypted_client_hello_xtn, ssl_ext_native_only },
};
static SSLExtensionSupport
@@ -866,40 +866,25 @@ ssl_CalculatePaddingExtLen(const sslSocket *ss, unsigned int clientHelloLength)
return extensionLen - 4;
}
-/* ssl3_SendPaddingExtension possibly adds an extension which ensures that a
- * ClientHello record is either < 256 bytes or is >= 512 bytes. This ensures
- * that we don't trigger bugs in F5 products.
- *
- * This takes an existing extension buffer, |buf|, and the length of the
- * remainder of the ClientHello, |prefixLen|. It modifies the extension buffer
- * to insert padding at the right place.
- */
+/* Manually insert an extension, retaining the position of the PSK
+ * extension, if present. */
SECStatus
-ssl_InsertPaddingExtension(const sslSocket *ss, unsigned int prefixLen,
- sslBuffer *buf)
+ssl3_EmplaceExtension(sslSocket *ss, sslBuffer *buf, PRUint16 exType,
+ const PRUint8 *data, unsigned int len, PRBool advertise)
{
- static unsigned char padding[252] = { 0 };
- unsigned int paddingLen;
- unsigned int tailLen;
SECStatus rv;
-
- /* Account for the size of the header, the length field of the extensions
- * block and the size of the existing extensions. */
- paddingLen = ssl_CalculatePaddingExtLen(ss, prefixLen + 2 + buf->len);
- if (!paddingLen) {
- return SECSuccess;
- }
+ unsigned int tailLen;
/* Move the tail if there is one. This only happens if we are sending the
* TLS 1.3 PSK extension, which needs to be at the end. */
if (ss->xtnData.lastXtnOffset) {
PORT_Assert(buf->len > ss->xtnData.lastXtnOffset);
tailLen = buf->len - ss->xtnData.lastXtnOffset;
- rv = sslBuffer_Grow(buf, buf->len + 4 + paddingLen);
+ rv = sslBuffer_Grow(buf, buf->len + 4 + len);
if (rv != SECSuccess) {
return SECFailure;
}
- PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + paddingLen,
+ PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + len,
buf->buf + ss->xtnData.lastXtnOffset,
tailLen);
buf->len = ss->xtnData.lastXtnOffset;
@@ -907,20 +892,71 @@ ssl_InsertPaddingExtension(const sslSocket *ss, unsigned int prefixLen,
tailLen = 0;
}
- rv = sslBuffer_AppendNumber(buf, ssl_padding_xtn, 2);
+ rv = sslBuffer_AppendNumber(buf, exType, 2);
if (rv != SECSuccess) {
return SECFailure; /* Code already set. */
}
- rv = sslBuffer_AppendVariable(buf, padding, paddingLen, 2);
+ rv = sslBuffer_AppendVariable(buf, data, len, 2);
if (rv != SECSuccess) {
return SECFailure; /* Code already set. */
}
+ if (ss->xtnData.lastXtnOffset) {
+ ss->xtnData.lastXtnOffset += 4 + len;
+ }
+
buf->len += tailLen;
+ /* False only to retain behavior with padding_xtn. Maybe
+ * we can just mark that advertised as well? TODO */
+ if (advertise) {
+ ss->xtnData.advertised[ss->xtnData.numAdvertised++] = exType;
+ }
+
return SECSuccess;
}
+/* ssl3_SendPaddingExtension possibly adds an extension which ensures that a
+ * ClientHello record is either < 256 bytes or is >= 512 bytes. This ensures
+ * that we don't trigger bugs in F5 products.
+ *
+ * This takes an existing extension buffer, |buf|, and the length of the
+ * remainder of the ClientHello, |prefixLen|. It modifies the extension buffer
+ * to insert padding at the right place.
+ */
+SECStatus
+ssl_InsertPaddingExtension(sslSocket *ss, unsigned int prefixLen,
+ sslBuffer *buf)
+{
+ static unsigned char padding[252] = { 0 };
+ unsigned int paddingLen;
+ /* Exit early if an application-provided extension hook
+ * already added padding. */
+ if (ssl3_ExtensionAdvertised(ss, ssl_padding_xtn)) {
+ return SECSuccess;
+ }
+
+ /* Account for the size of the header, the length field of the extensions
+ * block and the size of the existing extensions. */
+ paddingLen = ssl_CalculatePaddingExtLen(ss, prefixLen + 2 + buf->len);
+ if (!paddingLen) {
+ return SECSuccess;
+ }
+
+ return ssl3_EmplaceExtension(ss, buf, ssl_padding_xtn, padding, paddingLen, PR_FALSE);
+}
+
+void
+ssl3_MoveRemoteExtensions(PRCList *dst, PRCList *src)
+{
+ PRCList *cur_p;
+ while (!PR_CLIST_IS_EMPTY(src)) {
+ cur_p = PR_LIST_TAIL(src);
+ PR_REMOVE_LINK(cur_p);
+ PR_APPEND_LINK(cur_p, dst);
+ }
+}
+
void
ssl3_DestroyRemoteExtensions(PRCList *list)
{
@@ -982,9 +1018,14 @@ ssl3_DestroyExtensionData(TLSExtensionData *xtnData)
xtnData->certReqAuthorities.arena = NULL;
}
PORT_Free(xtnData->advertised);
- ssl_FreeEphemeralKeyPair(xtnData->esniPrivateKey);
- SECITEM_FreeItem(&xtnData->keyShareExtension, PR_FALSE);
tls13_DestroyDelegatedCredential(xtnData->peerDelegCred);
+
+ /* ECH State */
+ SECITEM_FreeItem(&xtnData->innerCh, PR_FALSE);
+ SECITEM_FreeItem(&xtnData->echSenderPubKey, PR_FALSE);
+ SECITEM_FreeItem(&xtnData->echConfigId, PR_FALSE);
+ SECITEM_FreeItem(&xtnData->echRetryConfigs, PR_FALSE);
+ xtnData->echRetryConfigsValid = PR_FALSE;
}
/* Free everything that has been allocated and then reset back to
diff --git a/lib/ssl/ssl3ext.h b/lib/ssl/ssl3ext.h
index ff2f7c211..45510041e 100644
--- a/lib/ssl/ssl3ext.h
+++ b/lib/ssl/ssl3ext.h
@@ -9,10 +9,9 @@
#ifndef __ssl3ext_h_
#define __ssl3ext_h_
+#include "pk11hpke.h"
#include "sslencode.h"
-#define TLS13_ESNI_NONCE_SIZE 16
-
typedef enum {
sni_nametype_hostname
} SNINameType;
@@ -98,7 +97,8 @@ struct TLSExtensionDataStr {
PRUint16 dtlsSRTPCipherSuite; /* 0 if not selected */
- unsigned int lastXtnOffset; /* Where to insert padding. 0 = end. */
+ unsigned int lastXtnOffset; /* Where to insert any other extensions.
+ * 0 = end, otherwise base of PSK xtn. */
PRCList remoteKeyShares; /* The other side's public keys (TLS 1.3) */
/* The following are used by a TLS 1.3 server. */
@@ -114,14 +114,6 @@ struct TLSExtensionDataStr {
/* The record size limit set by the peer. Our value is kept in ss->opt. */
PRUint16 recordSizeLimit;
- /* ESNI working state */
- SECItem keyShareExtension;
- ssl3CipherSuite esniSuite;
- sslEphemeralKeyPair *esniPrivateKey;
- /* Pointer into |ss->esniKeys->keyShares| */
- TLS13KeyShareEntry *peerEsniShare;
- PRUint8 esniNonce[TLS13_ESNI_NONCE_SIZE];
-
/* Delegated credentials.
*
* The delegated credential sent by the peer. Set by
@@ -138,6 +130,14 @@ struct TLSExtensionDataStr {
/* A non-owning reference to the selected PSKs. MUST NOT be freed directly,
* rather through tls13_DestoryPskList(). */
sslPsk *selectedPsk;
+
+ /* ECH working state. */
+ SECItem innerCh; /* Server: "payload value of ClientECH. */
+ SECItem echSenderPubKey; /* Server: "enc value of ClientECH, required for CHInner decryption. */
+ SECItem echConfigId; /* Server: "config_id" value of ClientECH. */
+ PRUint32 echCipherSuite; /* Server: "cipher_suite" value of ClientECH. */
+ SECItem echRetryConfigs; /* Client: Retry_configs from ServerEncryptedCH. */
+ PRBool echRetryConfigsValid; /* Client: Permits retry_configs to be extracted. */
};
typedef struct TLSExtensionStr {
@@ -165,6 +165,7 @@ SECStatus ssl3_HandleParsedExtensions(sslSocket *ss,
TLSExtension *ssl3_FindExtension(sslSocket *ss,
SSLExtensionType extension_type);
void ssl3_DestroyRemoteExtensions(PRCList *list);
+void ssl3_MoveRemoteExtensions(PRCList *dst, PRCList *src);
void ssl3_InitExtensionData(TLSExtensionData *xtnData, const sslSocket *ss);
void ssl3_DestroyExtensionData(TLSExtensionData *xtnData);
void ssl3_ResetExtensionData(TLSExtensionData *xtnData, const sslSocket *ss);
@@ -180,7 +181,9 @@ SECStatus ssl_ConstructExtensions(sslSocket *ss, sslBuffer *buf,
SSLHandshakeType message);
SECStatus ssl_SendEmptyExtension(const sslSocket *ss, TLSExtensionData *xtnData,
sslBuffer *buf, PRBool *append);
-SECStatus ssl_InsertPaddingExtension(const sslSocket *ss, unsigned int prefixLen,
+SECStatus ssl3_EmplaceExtension(sslSocket *ss, sslBuffer *buf, PRUint16 exType,
+ const PRUint8 *data, unsigned int len, PRBool advertise);
+SECStatus ssl_InsertPaddingExtension(sslSocket *ss, unsigned int prefixLen,
sslBuffer *buf);
/* Thunks to let us operate on const sslSocket* objects. */
diff --git a/lib/ssl/ssl3exthandle.c b/lib/ssl/ssl3exthandle.c
index cb4698253..2f1ab56fe 100644
--- a/lib/ssl/ssl3exthandle.c
+++ b/lib/ssl/ssl3exthandle.c
@@ -15,7 +15,7 @@
#include "selfencrypt.h"
#include "ssl3ext.h"
#include "ssl3exthandle.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
#include "tls13exthandle.h" /* For tls13_ServerSendStatusRequestXtn. */
PRBool
@@ -42,13 +42,11 @@ ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url)
*/
SECStatus
ssl3_ClientFormatServerNameXtn(const sslSocket *ss, const char *url,
- TLSExtensionData *xtnData,
+ unsigned int len, TLSExtensionData *xtnData,
sslBuffer *buf)
{
- unsigned int len;
SECStatus rv;
- len = PORT_Strlen(url);
/* length of server_name_list */
rv = sslBuffer_AppendNumber(buf, len + 3, 2);
if (rv != SECSuccess) {
@@ -76,17 +74,15 @@ ssl3_ClientSendServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData,
const char *url = ss->url;
- /* We only make an ESNI private key if we are going to
- * send ESNI. */
- if (ss->xtnData.esniPrivateKey != NULL) {
- url = ss->esniKeys->dummySni;
- }
-
if (!ssl_ShouldSendSNIExtension(ss, url)) {
return SECSuccess;
}
- rv = ssl3_ClientFormatServerNameXtn(ss, url, xtnData, buf);
+ /* If ECH, write the public name. The real server name
+ * is emplaced while constructing CHInner extensions. */
+ sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
+ const char *sniContents = PR_CLIST_IS_EMPTY(&ss->echConfigs) ? url : cfg->contents.publicName;
+ rv = ssl3_ClientFormatServerNameXtn(ss, sniContents, strlen(sniContents), xtnData, buf);
if (rv != SECSuccess) {
return SECFailure;
}
@@ -107,13 +103,6 @@ ssl3_HandleServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData,
return SECSuccess; /* ignore extension */
}
- if (ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_sni_xtn)) {
- /* If we already have ESNI, make sure we don't overwrite
- * the value. */
- PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
- return SECSuccess;
- }
-
/* Server side - consume client data and register server sender. */
/* do not parse the data if don't have user extension handling function. */
if (!ss->sniSocketConfig) {
@@ -323,11 +312,11 @@ ssl3_SelectAppProtocol(const sslSocket *ss, TLSExtensionData *xtnData,
}
PORT_Assert(ss->nextProtoCallback);
- /* The cipher suite isn't selected yet. Note that extensions
+ /* Neither the cipher suite nor ECH are selected yet Note that extensions
* sometimes affect what cipher suite is selected, e.g., for ECC. */
PORT_Assert((ss->ssl3.hs.preliminaryInfo &
- ssl_preinfo_all & ~ssl_preinfo_cipher_suite) ==
- (ssl_preinfo_all & ~ssl_preinfo_cipher_suite));
+ ssl_preinfo_all & ~ssl_preinfo_cipher_suite & ~ssl_preinfo_ech) ==
+ (ssl_preinfo_all & ~ssl_preinfo_cipher_suite & ~ssl_preinfo_ech));
/* The callback has to make sure that either rv != SECSuccess or that result
* is not set if there is no common protocol. */
rv = ss->nextProtoCallback(ss->nextProtoArg, ss->fd, data->data, data->len,
diff --git a/lib/ssl/ssl3exthandle.h b/lib/ssl/ssl3exthandle.h
index 3e9b418cf..654b90de8 100644
--- a/lib/ssl/ssl3exthandle.h
+++ b/lib/ssl/ssl3exthandle.h
@@ -93,7 +93,7 @@ SECStatus ssl3_ProcessSessionTicketCommon(sslSocket *ss, const SECItem *ticket,
/* out */ SECItem *appToken);
PRBool ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url);
SECStatus ssl3_ClientFormatServerNameXtn(const sslSocket *ss, const char *url,
- TLSExtensionData *xtnData,
+ unsigned int len, TLSExtensionData *xtnData,
sslBuffer *buf);
SECStatus ssl3_ClientSendServerNameXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
diff --git a/lib/ssl/ssl3prot.h b/lib/ssl/ssl3prot.h
index edf459290..b4c5a878a 100644
--- a/lib/ssl/ssl3prot.h
+++ b/lib/ssl/ssl3prot.h
@@ -76,6 +76,7 @@ typedef enum {
bad_certificate_hash_value = 114,
certificate_required = 116,
no_application_protocol = 120,
+ ech_required = 121,
/* invalid alert */
no_alert = 256
diff --git a/lib/ssl/sslencode.c b/lib/ssl/sslencode.c
index e7f1ae7b9..ea6f41e4b 100644
--- a/lib/ssl/sslencode.c
+++ b/lib/ssl/sslencode.c
@@ -264,8 +264,8 @@ sslRead_ReadNumber(sslReader *reader, unsigned int bytes, PRUint64 *num)
#define MAX_SEND_BUF_LENGTH 32000 /* watch for 16-bit integer overflow */
#define MIN_SEND_BUF_LENGTH 4000
-SECStatus
-ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes)
+static SECStatus
+ssl3_AppendHandshakeInternal(sslSocket *ss, const void *void_src, unsigned int bytes, PRBool suppressHash)
{
unsigned char *src = (unsigned char *)void_src;
int room = ss->sec.ci.sendBuf.space - ss->sec.ci.sendBuf.len;
@@ -284,7 +284,8 @@ ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes)
}
PRINT_BUF(60, (ss, "Append to Handshake", (unsigned char *)void_src, bytes));
- if (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
+ // TODO: Move firstHsDone and version check into callers as a suppression.
+ if (!suppressHash && (!ss->firstHsDone || ss->version < SSL_LIBRARY_VERSION_TLS_1_3)) {
rv = ssl3_UpdateHandshakeHashes(ss, src, bytes);
if (rv != SECSuccess)
return SECFailure; /* error code set by ssl3_UpdateHandshakeHashes */
@@ -310,6 +311,18 @@ ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes)
}
SECStatus
+ssl3_AppendHandshakeSuppressHash(sslSocket *ss, const void *void_src, unsigned int bytes)
+{
+ return ssl3_AppendHandshakeInternal(ss, void_src, bytes, PR_TRUE);
+}
+
+SECStatus
+ssl3_AppendHandshake(sslSocket *ss, const void *void_src, unsigned int bytes)
+{
+ return ssl3_AppendHandshakeInternal(ss, void_src, bytes, PR_FALSE);
+}
+
+SECStatus
ssl3_AppendHandshakeNumber(sslSocket *ss, PRUint64 num, unsigned int lenSize)
{
PRUint8 b[sizeof(num)];
diff --git a/lib/ssl/sslencode.h b/lib/ssl/sslencode.h
index 8223169aa..e87bace49 100644
--- a/lib/ssl/sslencode.h
+++ b/lib/ssl/sslencode.h
@@ -27,6 +27,10 @@ typedef struct sslBufferStr {
{ \
b, 0, maxlen, PR_TRUE \
}
+#define SSL_BUFFER_FIXED_LEN(b, len) \
+ { \
+ b, len, 0, PR_TRUE \
+ }
#define SSL_BUFFER(b) SSL_BUFFER_FIXED(b, sizeof(b))
#define SSL_BUFFER_BASE(b) ((b)->buf)
#define SSL_BUFFER_LEN(b) ((b)->len)
@@ -51,6 +55,8 @@ void sslBuffer_Clear(sslBuffer *b);
SECStatus ssl3_AppendHandshake(sslSocket *ss, const void *void_src,
unsigned int bytes);
+SECStatus ssl3_AppendHandshakeSuppressHash(sslSocket *ss, const void *void_src,
+ unsigned int bytes);
SECStatus ssl3_AppendHandshakeHeader(sslSocket *ss,
SSLHandshakeType t, unsigned int length);
SECStatus ssl3_AppendHandshakeNumber(sslSocket *ss, PRUint64 num,
diff --git a/lib/ssl/sslerr.h b/lib/ssl/sslerr.h
index eb8f7c2da..837fca7ba 100644
--- a/lib/ssl/sslerr.h
+++ b/lib/ssl/sslerr.h
@@ -276,8 +276,21 @@ typedef enum {
SSL_ERROR_DC_EXPIRED = (SSL_ERROR_BASE + 185),
SSL_ERROR_DC_INAPPROPRIATE_VALIDITY_PERIOD = (SSL_ERROR_BASE + 186),
SSL_ERROR_FEATURE_DISABLED = (SSL_ERROR_BASE + 187),
+ /* ECH rejected, public name authentication succeeded,
+ * and at least one of the retry_configs is compatible. */
+ SSL_ERROR_ECH_RETRY_WITH_ECH = (SSL_ERROR_BASE + 188),
+ /* ECH rejected, public name authentication succeeded,
+ * but none of the retry_configs are compatible. */
+ SSL_ERROR_ECH_RETRY_WITHOUT_ECH = (SSL_ERROR_BASE + 189),
+ /* ECH rejected and public name authentication failed. */
+ SSL_ERROR_ECH_FAILED = (SSL_ERROR_BASE + 190),
+ SSL_ERROR_ECH_REQUIRED_ALERT = (SSL_ERROR_BASE + 191),
SSL_ERROR_END_OF_LIST /* let the c compiler determine the value of this. */
} SSLErrorCodes;
+
+#define SSL_ERROR_RX_MALFORMED_ECH_CONFIG SSL_ERROR_RX_MALFORMED_ESNI_KEYS
+#define SSL_ERROR_RX_MALFORMED_ECH_EXTENSION SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION
+#define SSL_ERROR_MISSING_ECH_EXTENSION SSL_ERROR_MISSING_ESNI_EXTENSION
#endif /* NO_SECURITY_ERROR_ENUM */
/* clang-format on */
diff --git a/lib/ssl/sslexp.h b/lib/ssl/sslexp.h
index 8a92a39ad..a02f0f351 100644
--- a/lib/ssl/sslexp.h
+++ b/lib/ssl/sslexp.h
@@ -502,63 +502,86 @@ typedef SECStatus(PR_CALLBACK *SSLResumptionTokenCallback)(
(PRFileDesc * _fd, PRUint32 _size), \
(fd, size))
-/* Set the ESNI key pair on a socket (server side)
+/* If |enabled|, a GREASE ECH extension will be sent in every ClientHello,
+ * unless a valid and supported ECHConfig is configured to the socket
+ * (in which case real ECH takes precedence). If |!enabled|, it is not sent.*/
+#define SSL_EnableTls13GreaseEch(fd, enabled) \
+ SSL_EXPERIMENTAL_API("SSL_EnableTls13GreaseEch", \
+ (PRFileDesc * _fd, PRBool _enabled), (fd, enabled))
+
+/* Called by the client after an initial ECH connection fails with
+ * SSL_ERROR_ECH_RETRY_WITH_ECH. Returns compatible ECHConfigs, which
+ * are configured via SetClientEchConfigs for an ECH retry attempt.
+ * These configs MUST NOT be used for more than the single retry
+ * attempt. Subsequent connections MUST use advertised ECHConfigs. */
+#define SSL_GetEchRetryConfigs(fd, out) \
+ SSL_EXPERIMENTAL_API("SSL_GetEchRetryConfigs", \
+ (PRFileDesc * _fd, \
+ SECItem * _out), \
+ (fd, out))
+
+/* Called to remove all ECHConfigs from a socket (fd). */
+#define SSL_RemoveEchConfigs(fd) \
+ SSL_EXPERIMENTAL_API("SSL_RemoveEchConfigs", \
+ (PRFileDesc * _fd), \
+ (fd))
+
+/* Set the ECHConfig and key pair on a socket (server side)
*
* fd -- the socket
+ * pubKey -- the server's SECKEYPublicKey for HPKE/ECH.
+ * privateKey -- the server's SECKEYPrivateKey for HPKE/ECH.
* record/recordLen -- the encoded DNS record (not base64)
- *
- * Important: the suites that are advertised in the record must
- * be configured on, or this call will fail.
*/
-#define SSL_SetESNIKeyPair(fd, \
- privKey, record, recordLen) \
- SSL_EXPERIMENTAL_API("SSL_SetESNIKeyPair", \
+#define SSL_SetServerEchConfigs(fd, pubKey, \
+ privKey, record, recordLen) \
+ SSL_EXPERIMENTAL_API("SSL_SetServerEchConfigs", \
(PRFileDesc * _fd, \
- SECKEYPrivateKey * _privKey, \
+ const SECKEYPublicKey *_pubKey, \
+ const SECKEYPrivateKey *_privKey, \
const PRUint8 *_record, unsigned int _recordLen), \
- (fd, privKey, \
+ (fd, pubKey, privKey, \
record, recordLen))
-/* Set the ESNI keys on a client
+/* Set ECHConfig(s) on a client. The first supported ECHConfig will be used.
*
* fd -- the socket
- * ensikeys/esniKeysLen -- the ESNI key structure (not base64)
- * dummyESNI -- the dummy ESNI to use (if any)
+ * echConfigs/echConfigsLen -- the ECHConfigs structure (not base64)
*/
-#define SSL_EnableESNI(fd, esniKeys, esniKeysLen, dummySNI) \
- SSL_EXPERIMENTAL_API("SSL_EnableESNI", \
- (PRFileDesc * _fd, \
- const PRUint8 *_esniKeys, \
- unsigned int _esniKeysLen, \
- const char *_dummySNI), \
- (fd, esniKeys, esniKeysLen, dummySNI))
+#define SSL_SetClientEchConfigs(fd, echConfigs, echConfigsLen) \
+ SSL_EXPERIMENTAL_API("SSL_SetClientEchConfigs", \
+ (PRFileDesc * _fd, \
+ const PRUint8 *_echConfigs, \
+ unsigned int _echConfigsLen), \
+ (fd, echConfigs, echConfigsLen))
/*
- * Generate an encoded ESNIKeys structure (presumably server side).
+ * Generate an encoded ECHConfig structure (presumably server side).
*
- * cipherSuites -- the cipher suites that can be used
- * cipherSuitesCount -- the number of suites in cipherSuites
+ * publicName -- the public_name value to be placed in SNI.
+ * hpkeSuites -- the HPKE cipher suites that can be used
+ * hpkeSuitesCount -- the number of suites in hpkeSuites
+ * kemId -- the HKPE KEM ID value
* group -- the named group this key corresponds to
* pubKey -- the public key for the key pair
- * pad -- the length to pad to
- * notBefore/notAfter -- validity range in seconds since epoch
+ * pad -- the maximum length to pad to
* out/outlen/maxlen -- where to output the data
*/
-#define SSL_EncodeESNIKeys(cipherSuites, cipherSuiteCount, \
- group, pubKey, pad, notBefore, notAfter, \
- out, outlen, maxlen) \
- SSL_EXPERIMENTAL_API("SSL_EncodeESNIKeys", \
- (PRUint16 * _cipherSuites, \
- unsigned int _cipherSuiteCount, \
- SSLNamedGroup _group, \
- SECKEYPublicKey *_pubKey, \
- PRUint16 _pad, \
- PRUint64 _notBefore, PRUint64 _notAfter, \
- PRUint8 *_out, unsigned int *_outlen, \
- unsigned int _maxlen), \
- (cipherSuites, cipherSuiteCount, \
- group, pubKey, pad, notBefore, notAfter, \
- out, outlen, maxlen))
+#define SSL_EncodeEchConfig(publicName, hpkeSuites, hpkeSuitesCount, \
+ kemId, pubKey, maxNameLen, out, outlen, \
+ maxlen) \
+ SSL_EXPERIMENTAL_API("SSL_EncodeEchConfig", \
+ (const char *_publicName, \
+ const PRUint32 *_hpkeSuites, \
+ unsigned int _hpkeSuitesCount, \
+ HpkeKemId _kemId, \
+ const SECKEYPublicKey *_pubKey, \
+ PRUint16 _maxNameLen, \
+ PRUint8 *_out, unsigned int *_outlen, \
+ unsigned int _maxlen), \
+ (publicName, hpkeSuites, hpkeSuitesCount, \
+ kemId, pubKey, maxNameLen, out, outlen, \
+ maxlen))
/* SSL_SetSecretCallback installs a callback that TLS calls when it installs new
* traffic secrets.
@@ -1001,6 +1024,9 @@ typedef struct SSLMaskingContextStr {
#define SSL_UseAltServerHelloType(fd, enable) SSL_DEPRECATED_EXPERIMENTAL_API
#define SSL_SetupAntiReplay(a, b, c) SSL_DEPRECATED_EXPERIMENTAL_API
#define SSL_InitAntiReplay(a, b, c) SSL_DEPRECATED_EXPERIMENTAL_API
+#define SSL_EnableESNI(a, b, c, d) SSL_DEPRECATED_EXPERIMENTAL_API
+#define SSL_EncodeESNIKeys(a, b, c, d, e, f, g, h, i, j) SSL_DEPRECATED_EXPERIMENTAL_API
+#define SSL_SetESNIKeyPair(a, b, c, d) SSL_DEPRECATED_EXPERIMENTAL_API
SEC_END_PROTOS
diff --git a/lib/ssl/sslimpl.h b/lib/ssl/sslimpl.h
index 35d0c2d6b..a126cb8c3 100644
--- a/lib/ssl/sslimpl.h
+++ b/lib/ssl/sslimpl.h
@@ -37,6 +37,8 @@
typedef struct sslSocketStr sslSocket;
typedef struct sslNamedGroupDefStr sslNamedGroupDef;
typedef struct sslEsniKeysStr sslEsniKeys;
+typedef struct sslEchConfigStr sslEchConfig;
+typedef struct sslEchConfigContentsStr sslEchConfigContents;
typedef struct sslPskStr sslPsk;
typedef struct sslDelegatedCredentialStr sslDelegatedCredential;
typedef struct sslEphemeralKeyPairStr sslEphemeralKeyPair;
@@ -284,6 +286,7 @@ typedef struct sslOptionsStr {
unsigned int enableDelegatedCredentials : 1;
unsigned int enableDtls13VersionCompat : 1;
unsigned int suppressEndOfEarlyData : 1;
+ unsigned int enableTls13GreaseEch : 1;
} sslOptions;
typedef enum { sslHandshakingUndetermined = 0,
@@ -611,17 +614,26 @@ typedef struct {
typedef struct SSL3HandshakeStateStr {
SSL3Random server_random;
SSL3Random client_random;
- SSL3WaitState ws; /* May also contain SSL3WaitState | 0x80 for TLS 1.3 */
+ SSL3Random client_inner_random; /* TLS 1.3 ECH Inner. */
+ SSL3WaitState ws; /* May also contain SSL3WaitState | 0x80 for TLS 1.3 */
/* This group of members is used for handshake running hashes. */
SSL3HandshakeHashType hashType;
- sslBuffer messages; /* Accumulated handshake messages */
+ sslBuffer messages; /* Accumulated handshake messages */
+ sslBuffer echInnerMessages; /* Accumulated ECH Inner handshake messages */
/* PKCS #11 mode:
* SSL 3.0 - TLS 1.1 use both |md5| and |sha|. |md5| is used for MD5 and
* |sha| for SHA-1.
- * TLS 1.2 and later use only |sha|, for SHA-256. */
+ * TLS 1.2 and later use only |sha| variants, for SHA-256.
+ * Under normal (non-1.3 ECH) handshakes, only |sha| and |shaPostHandshake|
+ * are used. When doing 1.3 ECH, |sha| contains the transcript hash
+ * corresponding to the outer Client Hello. To facilitate secure retry and
+ * disablement, |shaEchInner|, tracks, in parallel, the transcript hash
+ * corresponding to the inner Client Hello. Once we process the SH
+ * extensions, coalesce into |sha|. */
PK11Context *md5;
PK11Context *sha;
+ PK11Context *shaEchInner;
PK11Context *shaPostHandshake;
SSLSignatureScheme signatureScheme;
const ssl3KEADef *kea_def;
@@ -662,7 +674,8 @@ typedef struct SSL3HandshakeStateStr {
PRUint32 preliminaryInfo;
/* Parsed extensions */
- PRCList remoteExtensions; /* Parsed incoming extensions */
+ PRCList remoteExtensions; /* Parsed incoming extensions */
+ PRCList echOuterExtensions; /* If ECH, hold CHOuter extensions for decompression. */
/* This group of values is used for DTLS */
PRUint16 sendMessageSeq; /* The sending message sequence
@@ -717,6 +730,7 @@ typedef struct SSL3HandshakeStateStr {
* we use for TLS 1.3 */
PRUint16 ticketNonce; /* A counter we use for tickets. */
SECItem fakeSid; /* ... (server) the SID the client used. */
+ PRCList psks; /* A list of PSKs, resumption and/or external. */
/* rttEstimate is used to guess the round trip time between server and client.
* When the server sends ServerHello it sets this to the current time.
@@ -729,13 +743,19 @@ typedef struct SSL3HandshakeStateStr {
PRCList dtlsRcvdHandshake; /* Handshake records we have received
* used to generate ACKs. */
- PRCList psks; /* A list of PSKs, resumption and/or external. */
+ /* TLS 1.3 ECH state. */
+ PRBool echAccepted; /* Client/Server: True if we've commited to using CHInner. */
+ HpkeContext *echHpkeCtx; /* Client/Server: HPKE context for ECH. */
+ const char *echPublicName; /* Client: If rejected, the ECHConfig.publicName to
+ * use for certificate verification. */
+
} SSL3HandshakeState;
#define SSL_ASSERT_HASHES_EMPTY(ss) \
do { \
PORT_Assert(ss->ssl3.hs.hashType == handshake_hash_unknown); \
PORT_Assert(ss->ssl3.hs.messages.len == 0); \
+ PORT_Assert(ss->ssl3.hs.echInnerMessages.len == 0); \
} while (0)
/*
@@ -1101,9 +1121,10 @@ struct sslSocketStr {
/* Whether we are doing stream or datagram mode */
SSLProtocolVariant protocolVariant;
- /* The information from the ESNI keys record
- * (also the private key for the server). */
- sslEsniKeys *esniKeys;
+ /* TLS 1.3 Encrypted Client Hello. */
+ PRCList echConfigs; /* Client/server: Must not change while hs is in-progress. */
+ SECKEYPublicKey *echPubKey; /* Server: The ECH keypair used in HPKE setup */
+ SECKEYPrivateKey *echPrivKey; /* As above. */
/* Anti-replay for TLS 1.3 0-RTT. */
SSLAntiReplayContext *antiReplay;
@@ -1262,6 +1283,10 @@ ssl_HashHandshakeMessageInt(sslSocket *ss, SSLHandshakeType type,
sslUpdateHandshakeHashes cb);
SECStatus ssl_HashHandshakeMessage(sslSocket *ss, SSLHandshakeType type,
const PRUint8 *b, PRUint32 length);
+SECStatus ssl_HashHandshakeMessageEchInner(sslSocket *ss, SSLHandshakeType type,
+ const PRUint8 *b, PRUint32 length);
+SECStatus ssl_HashHandshakeMessageDefault(sslSocket *ss, SSLHandshakeType type,
+ const PRUint8 *b, PRUint32 length);
SECStatus ssl_HashPostHandshakeMessage(sslSocket *ss, SSLHandshakeType type,
const PRUint8 *b, PRUint32 length);
@@ -1438,6 +1463,11 @@ extern SECStatus ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error);
extern SECStatus ssl3_HandleV2ClientHello(
sslSocket *ss, unsigned char *buffer, unsigned int length, PRUint8 padding);
+SECStatus
+ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid,
+ PRBool realSid, PRUint16 version, PRBool isEchInner,
+ const sslBuffer *extensions, sslBuffer *preamble);
+SECStatus ssl3_InsertChHeaderSize(const sslSocket *ss, sslBuffer *preamble, const sslBuffer *extensions);
SECStatus ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type);
/*
@@ -1679,6 +1709,7 @@ SECStatus ssl3_NegotiateCipherSuiteInner(sslSocket *ss, const SECItem *suites,
SECStatus ssl3_NegotiateCipherSuite(sslSocket *ss, const SECItem *suites,
PRBool initHashes);
SECStatus ssl3_InitHandshakeHashes(sslSocket *ss);
+void ssl3_CoalesceEchHandshakeHashes(sslSocket *ss);
SECStatus ssl3_ServerCallSNICallback(sslSocket *ss);
SECStatus ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags);
SECStatus ssl3_CompleteHandleCertificate(sslSocket *ss,
@@ -1725,6 +1756,7 @@ SECStatus ssl_CreateECDHEphemeralKeyPair(const sslSocket *ss,
SECStatus ssl_CreateStaticECDHEKey(sslSocket *ss,
const sslNamedGroupDef *ecGroup);
SECStatus ssl3_FlushHandshake(sslSocket *ss, PRInt32 flags);
+SECStatus ssl3_GetNewRandom(SSL3Random random);
PK11SymKey *ssl3_GetWrappingKey(sslSocket *ss,
PK11SlotInfo *masterSecretSlot,
CK_MECHANISM_TYPE masterWrapMech,
@@ -1914,6 +1946,8 @@ SECStatus SSLExp_CreateMask(SSLMaskingContext *ctx, const PRUint8 *sample,
SECStatus SSLExp_DestroyMaskingContext(SSLMaskingContext *ctx);
+SECStatus SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled);
+
SEC_END_PROTOS
#if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS)
diff --git a/lib/ssl/sslinfo.c b/lib/ssl/sslinfo.c
index a92ed1604..65aafe94a 100644
--- a/lib/ssl/sslinfo.c
+++ b/lib/ssl/sslinfo.c
@@ -89,6 +89,7 @@ SSL_GetChannelInfo(PRFileDesc *fd, SSLChannelInfo *info, PRUintn len)
inf.pskType = ssl_psk_none;
}
inf.peerDelegCred = tls13_IsVerifyingWithDelegatedCredential(ss);
+ inf.echAccepted = ss->ssl3.hs.echAccepted;
if (sid) {
unsigned int sidLen;
@@ -171,6 +172,9 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc *fd,
inf.peerDelegCred = tls13_IsVerifyingWithDelegatedCredential(ss);
inf.authKeyBits = ss->sec.authKeyBits;
inf.signatureScheme = ss->sec.signatureScheme;
+ inf.echAccepted = ss->ssl3.hs.echAccepted;
+ /* Only expose this if the application should use it for verification. */
+ inf.echPublicName = (inf.echAccepted == PR_FALSE) ? ss->ssl3.hs.echPublicName : NULL;
memcpy(info, &inf, inf.length);
return SECSuccess;
diff --git a/lib/ssl/sslsecur.c b/lib/ssl/sslsecur.c
index ef978c90a..162fc66d0 100644
--- a/lib/ssl/sslsecur.c
+++ b/lib/ssl/sslsecur.c
@@ -173,9 +173,18 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool asServer)
ssl_Release1stHandshakeLock(ss);
ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
+ ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions);
ssl3_ResetExtensionData(&ss->xtnData, ss);
tls13_ResetHandshakePsks(ss, &ss->ssl3.hs.psks);
+ if (ss->ssl3.hs.echHpkeCtx) {
+ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE);
+ ss->ssl3.hs.echHpkeCtx = NULL;
+ PORT_Assert(ss->ssl3.hs.echPublicName);
+ PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */
+ ss->ssl3.hs.echPublicName = NULL;
+ }
+
if (!ss->TCPconnected)
ss->TCPconnected = (PR_SUCCESS == ssl_DefGetpeername(ss, &addr));
diff --git a/lib/ssl/sslsock.c b/lib/ssl/sslsock.c
index 695f39c50..e075e23c8 100644
--- a/lib/ssl/sslsock.c
+++ b/lib/ssl/sslsock.c
@@ -19,7 +19,7 @@
#include "nss.h"
#include "pk11pqg.h"
#include "pk11pub.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
#include "tls13psk.h"
#include "tls13subcerts.h"
@@ -92,7 +92,8 @@ static sslOptions ssl_defaults = {
.enableHelloDowngradeCheck = PR_FALSE,
.enableV2CompatibleHello = PR_FALSE,
.enablePostHandshakeAuth = PR_FALSE,
- .suppressEndOfEarlyData = PR_FALSE
+ .suppressEndOfEarlyData = PR_FALSE,
+ .enableTls13GreaseEch = PR_FALSE
};
/*
@@ -371,12 +372,18 @@ ssl_DupSocket(sslSocket *os)
ss->resumptionTokenCallback = os->resumptionTokenCallback;
ss->resumptionTokenContext = os->resumptionTokenContext;
- if (os->esniKeys) {
- ss->esniKeys = tls13_CopyESNIKeys(os->esniKeys);
- if (!ss->esniKeys) {
+ rv = tls13_CopyEchConfigs(&os->echConfigs, &ss->echConfigs);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (os->echPrivKey && os->echPubKey) {
+ ss->echPrivKey = SECKEY_CopyPrivateKey(os->echPrivKey);
+ ss->echPubKey = SECKEY_CopyPublicKey(os->echPubKey);
+ if (!ss->echPrivKey || !ss->echPubKey) {
goto loser;
}
}
+
if (os->antiReplay) {
ss->antiReplay = tls13_RefAntiReplayContext(os->antiReplay);
PORT_Assert(ss->antiReplay); /* Can't fail. */
@@ -478,13 +485,13 @@ ssl_DestroySocketContents(sslSocket *ss)
ssl_ClearPRCList(&ss->ssl3.hs.dtlsRcvdHandshake, NULL);
tls13_DestroyPskList(&ss->ssl3.hs.psks);
- tls13_DestroyESNIKeys(ss->esniKeys);
tls13_ReleaseAntiReplayContext(ss->antiReplay);
- if (ss->psk) {
- tls13_DestroyPsk(ss->psk);
- ss->psk = NULL;
- }
+ tls13_DestroyPsk(ss->psk);
+
+ tls13_DestroyEchConfigs(&ss->echConfigs);
+ SECKEY_DestroyPrivateKey(ss->echPrivKey);
+ SECKEY_DestroyPublicKey(ss->echPubKey);
}
/*
@@ -2378,6 +2385,7 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
{
sslSocket *sm = NULL, *ss = NULL;
PRCList *cursor;
+ SECStatus rv;
if (model == NULL) {
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
@@ -2447,7 +2455,6 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
for (cursor = PR_NEXT_LINK(&sm->extensionHooks);
cursor != &sm->extensionHooks;
cursor = PR_NEXT_LINK(cursor)) {
- SECStatus rv;
sslCustomExtensionHooks *hook = (sslCustomExtensionHooks *)cursor;
rv = SSL_InstallExtensionHooks(ss->fd, hook->type,
hook->writer, hook->writerArg,
@@ -2473,12 +2480,19 @@ SSL_ReconfigFD(PRFileDesc *model, PRFileDesc *fd)
}
}
- /* Copy ESNI. */
- tls13_DestroyESNIKeys(ss->esniKeys);
- ss->esniKeys = NULL;
- if (sm->esniKeys) {
- ss->esniKeys = tls13_CopyESNIKeys(sm->esniKeys);
- if (!ss->esniKeys) {
+ /* Copy ECH. */
+ tls13_DestroyEchConfigs(&ss->echConfigs);
+ SECKEY_DestroyPrivateKey(ss->echPrivKey);
+ SECKEY_DestroyPublicKey(ss->echPubKey);
+ rv = tls13_CopyEchConfigs(&sm->echConfigs, &ss->echConfigs);
+ if (rv != SECSuccess) {
+ return NULL;
+ }
+ if (sm->echPrivKey && sm->echPubKey) {
+ /* Might be client (no keys). */
+ ss->echPrivKey = SECKEY_CopyPrivateKey(sm->echPrivKey);
+ ss->echPubKey = SECKEY_CopyPublicKey(sm->echPubKey);
+ if (!ss->echPrivKey || !ss->echPubKey) {
return NULL;
}
}
@@ -4161,6 +4175,7 @@ ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
PR_INIT_CLIST(&ss->serverCerts);
PR_INIT_CLIST(&ss->ephemeralKeyPairs);
PR_INIT_CLIST(&ss->extensionHooks);
+ PR_INIT_CLIST(&ss->echConfigs);
ss->dbHandle = CERT_GetDefaultCertDB();
@@ -4194,7 +4209,8 @@ ssl_NewSocket(PRBool makeLocks, SSLProtocolVariant protocolVariant)
PR_INIT_CLIST(&ss->ssl3.hs.psks);
dtls_InitTimers(ss);
- ss->esniKeys = NULL;
+ ss->echPrivKey = NULL;
+ ss->echPubKey = NULL;
ss->antiReplay = NULL;
ss->psk = NULL;
@@ -4277,9 +4293,10 @@ struct {
EXP(DestroyAead),
EXP(DestroyMaskingContext),
EXP(DestroyResumptionTokenInfo),
- EXP(EnableESNI),
- EXP(EncodeESNIKeys),
+ EXP(EnableTls13GreaseEch),
+ EXP(EncodeEchConfig),
EXP(GetCurrentEpoch),
+ EXP(GetEchRetryConfigs),
EXP(GetExtensionSupport),
EXP(GetResumptionTokenInfo),
EXP(HelloRetryRequestCallback),
@@ -4295,16 +4312,18 @@ struct {
EXP(RecordLayerData),
EXP(RecordLayerWriteCallback),
EXP(ReleaseAntiReplayContext),
+ EXP(RemoveEchConfigs),
EXP(RemoveExternalPsk),
EXP(SecretCallback),
EXP(SendCertificateRequest),
EXP(SendSessionTicket),
EXP(SetAntiReplayContext),
+ EXP(SetClientEchConfigs),
EXP(SetDtls13VersionWorkaround),
- EXP(SetESNIKeyPair),
EXP(SetMaxEarlyDataSize),
EXP(SetResumptionTokenCallback),
EXP(SetResumptionToken),
+ EXP(SetServerEchConfigs),
EXP(SetTimeFunc),
#endif
{ "", NULL }
@@ -4342,6 +4361,17 @@ ssl_ClearPRCList(PRCList *list, void (*f)(void *))
}
SECStatus
+SSLExp_EnableTls13GreaseEch(PRFileDesc *fd, PRBool enabled)
+{
+ sslSocket *ss = ssl_FindSocket(fd);
+ if (!ss) {
+ return SECFailure;
+ }
+ ss->opt.enableTls13GreaseEch = enabled;
+ return SECSuccess;
+}
+
+SECStatus
SSLExp_SetDtls13VersionWorkaround(PRFileDesc *fd, PRBool enabled)
{
sslSocket *ss = ssl_FindSocket(fd);
diff --git a/lib/ssl/sslt.h b/lib/ssl/sslt.h
index eaf4133e3..f1713069b 100644
--- a/lib/ssl/sslt.h
+++ b/lib/ssl/sslt.h
@@ -368,6 +368,11 @@ typedef struct SSLChannelInfoStr {
/* Indicates what type of PSK, if any, was used in a handshake. */
SSLPskType pskType;
+ /* The following fields were added in NSS 3.60 */
+ /* This field is PR_TRUE when the connection is established
+ * with TLS 1.3 Encrypted Client Hello. */
+ PRBool echAccepted;
+
/* When adding new fields to this structure, please document the
* NSS version in which they were added. */
} SSLChannelInfo;
@@ -376,12 +381,13 @@ typedef struct SSLChannelInfoStr {
#define ssl_preinfo_version (1U << 0)
#define ssl_preinfo_cipher_suite (1U << 1)
#define ssl_preinfo_0rtt_cipher_suite (1U << 2)
-/* ssl_preinfo_peer_auth covers peerDelegCred, authKeyBits, and scheme. Not
- * included in ssl_preinfo_all as it is client-only. */
+/* ssl_preinfo_peer_auth covers peerDelegCred, authKeyBits,
+ * and scheme. Not included in ssl_preinfo_all as it is client-only. */
#define ssl_preinfo_peer_auth (1U << 3)
+#define ssl_preinfo_ech (1U << 4)
/* ssl_preinfo_all doesn't contain ssl_preinfo_0rtt_cipher_suite because that
* field is only set if 0-RTT is sent (client) or accepted (server). */
-#define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite)
+#define ssl_preinfo_all (ssl_preinfo_version | ssl_preinfo_cipher_suite | ssl_preinfo_ech)
typedef struct SSLPreliminaryChannelInfoStr {
/* On return, SSL_GetPreliminaryChannelInfo sets |length| to the smaller of
@@ -429,6 +435,12 @@ typedef struct SSLPreliminaryChannelInfoStr {
PRUint32 authKeyBits;
SSLSignatureScheme signatureScheme;
+ /* The following fields were added in NSS 3.60. */
+ PRBool echAccepted;
+ /* If the application configured ECH but |!echAccepted|, authCertificate
+ * should use the following hostname extracted from the ECHConfig. */
+ const char* echPublicName;
+
/* When adding new fields to this structure, please document the
* NSS version in which they were added. */
} SSLPreliminaryChannelInfo;
@@ -533,7 +545,9 @@ typedef enum {
ssl_next_proto_nego_xtn = 13172, /* Deprecated. */
ssl_renegotiation_info_xtn = 0xff01,
ssl_tls13_short_header_xtn = 0xff03, /* Deprecated. */
- ssl_tls13_encrypted_sni_xtn = 0xffce,
+ ssl_tls13_outer_extensions_xtn = 0xfd00,
+ ssl_tls13_encrypted_client_hello_xtn = 0xfe08,
+ ssl_tls13_encrypted_sni_xtn = 0xffce, /* Deprecated. */
} SSLExtensionType;
/* This is the old name for the supported_groups extensions. */
diff --git a/lib/ssl/tls13con.c b/lib/ssl/tls13con.c
index 3a6bacb61..b1e3b2ebe 100644
--- a/lib/ssl/tls13con.c
+++ b/lib/ssl/tls13con.c
@@ -21,7 +21,7 @@
#include "tls13hkdf.h"
#include "tls13con.h"
#include "tls13err.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
#include "tls13exthandle.h"
#include "tls13hashstate.h"
#include "tls13subcerts.h"
@@ -446,10 +446,7 @@ tls13_SetupClientHello(sslSocket *ss, sslClientHelloType chType)
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
- /* Do encrypted SNI.
- * Note: this makes a new key even though we don't need one.
- * Maybe remove this in future for efficiency. */
- rv = tls13_ClientSetupESNI(ss);
+ rv = tls13_ClientSetupEch(ss, chType);
if (rv != SECSuccess) {
return SECFailure;
}
@@ -1755,6 +1752,11 @@ tls13_MaybeSendHelloRetry(sslSocket *ss, const sslNamedGroupDef *requestedGroup,
return SECFailure; /* Code already set. */
}
+ /* We received ECH, but have to start over with CH2. */
+ ss->ssl3.hs.echAccepted = PR_FALSE;
+ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE);
+ ss->ssl3.hs.echHpkeCtx = NULL;
+
*hrrSent = PR_TRUE;
return SECSuccess;
}
@@ -1814,6 +1816,7 @@ tls13_HandleClientHelloPart2(sslSocket *ss,
TLS13KeyShareEntry *clientShare = NULL;
ssl3CipherSuite previousCipherSuite = 0;
const sslNamedGroupDef *previousGroup = NULL;
+ PRBool previousEchOffered = PR_FALSE;
PRBool hrr = PR_FALSE;
/* If the legacy_version field is set to 0x300 or smaller,
@@ -1865,11 +1868,19 @@ tls13_HandleClientHelloPart2(sslSocket *ss,
rv = tls13_RecoverHashState(ss, ss->xtnData.cookie.data,
ss->xtnData.cookie.len,
&previousCipherSuite,
- &previousGroup);
+ &previousGroup,
+ &previousEchOffered);
if (rv != SECSuccess) {
FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO, illegal_parameter);
goto loser;
}
+
+ /* CH1/CH2 must either both include ECH, or both exclude it. */
+ if ((ss->xtnData.echConfigId.len > 0) != previousEchOffered) {
+ FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO,
+ illegal_parameter);
+ goto loser;
+ }
}
/* Now merge the ClientHello into the hash state. */
@@ -2426,14 +2437,19 @@ loser:
* 00 00 Hash.length || // Handshake message length
* Hash(ClientHello1) || // Hash of ClientHello1
* HelloRetryRequest ... MN)
+ *
+ * For an ECH handshake, the process occurs for the outer
+ * transcript in |ss->ssl3.hs.messages| and the inner
+ * transcript in |ss->ssl3.hs.echInnerMessages|.
*/
static SECStatus
tls13_ReinjectHandshakeTranscript(sslSocket *ss)
{
SSL3Hashes hashes;
+ SSL3Hashes echInnerHashes;
SECStatus rv;
- // First compute the hash.
+ /* First compute the hash. */
rv = tls13_ComputeHash(ss, &hashes,
ss->ssl3.hs.messages.buf,
ss->ssl3.hs.messages.len,
@@ -2442,16 +2458,35 @@ tls13_ReinjectHandshakeTranscript(sslSocket *ss)
return SECFailure;
}
- // Now re-init the handshake.
+ if (ss->ssl3.hs.echHpkeCtx) {
+ rv = tls13_ComputeHash(ss, &echInnerHashes,
+ ss->ssl3.hs.echInnerMessages.buf,
+ ss->ssl3.hs.echInnerMessages.len,
+ tls13_GetHash(ss));
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ }
+
ssl3_RestartHandshakeHashes(ss);
- // And reinject the message.
- rv = ssl_HashHandshakeMessage(ss, ssl_hs_message_hash,
- hashes.u.raw, hashes.len);
+ /* Reinject the message. The Default context variant updates
+ * the default hash state. Use it for both non-ECH and ECH Outer. */
+ rv = ssl_HashHandshakeMessageDefault(ss, ssl_hs_message_hash,
+ hashes.u.raw, hashes.len);
if (rv != SECSuccess) {
return SECFailure;
}
+ if (ss->ssl3.hs.echHpkeCtx) {
+ rv = ssl_HashHandshakeMessageEchInner(ss, ssl_hs_message_hash,
+ echInnerHashes.u.raw,
+ echInnerHashes.len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ }
+
return SECSuccess;
}
static unsigned int
@@ -3841,6 +3876,8 @@ tls13_ComputeHandshakeHashes(sslSocket *ss, SSL3Hashes *hashes)
{
SECStatus rv;
PK11Context *ctx = NULL;
+ PRBool useEchInner;
+ sslBuffer *transcript;
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
if (ss->ssl3.hs.hashType == handshake_hash_unknown) {
@@ -3857,13 +3894,18 @@ tls13_ComputeHandshakeHashes(sslSocket *ss, SSL3Hashes *hashes)
goto loser;
}
+ /* One might expect this to use ss->ssl3.hs.echAccepted,
+ * but with 0-RTT we don't know that yet. */
+ useEchInner = ss->sec.isServer ? PR_FALSE : !!ss->ssl3.hs.echHpkeCtx;
+ transcript = useEchInner ? &ss->ssl3.hs.echInnerMessages : &ss->ssl3.hs.messages;
+
PRINT_BUF(10, (ss, "Handshake hash computed over saved messages",
- ss->ssl3.hs.messages.buf,
- ss->ssl3.hs.messages.len));
+ transcript->buf,
+ transcript->len));
if (PK11_DigestOp(ctx,
- ss->ssl3.hs.messages.buf,
- ss->ssl3.hs.messages.len) != SECSuccess) {
+ transcript->buf,
+ transcript->len) != SECSuccess) {
ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
goto loser;
}
@@ -4133,15 +4175,6 @@ tls13_HandleEncryptedExtensions(sslSocket *ss, PRUint8 *b, PRUint32 length)
return SECFailure; /* Error code set below */
}
- /* If we sent ESNI, check the nonce. */
- if (ss->xtnData.esniPrivateKey) {
- PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_sni_xtn));
- rv = tls13_ClientCheckEsniXtn(ss);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- }
-
/* Handle the rest of the extensions. */
rv = ssl3_HandleParsedExtensions(ss, ssl_hs_encrypted_extensions);
if (rv != SECSuccess) {
@@ -4470,14 +4503,20 @@ loser:
return SECFailure;
}
+/* Compute the PSK binder hash over:
+ * Client HRR prefix, if present in ss->ssl3.hs.messages or ss->ssl3.hs.echInnerMessages,
+ * |len| bytes of |buf| */
static SECStatus
tls13_ComputePskBinderHash(sslSocket *ss, PRUint8 *b, size_t length,
SSL3Hashes *hashes, SSLHashType hashType)
{
SECStatus rv;
PK11Context *ctx = NULL;
- /* On the server, HRR residual is already buffered. */
- sslBuffer *clientResidual = ss->sec.isServer ? NULL : &ss->ssl3.hs.messages;
+ sslBuffer *clientResidual = NULL;
+ if (!ss->sec.isServer) {
+ /* On the server, HRR residual is already buffered. */
+ clientResidual = ss->ssl3.hs.echHpkeCtx ? &ss->ssl3.hs.echInnerMessages : &ss->ssl3.hs.messages;
+ }
PORT_Assert(ss->ssl3.hs.hashType == handshake_hash_unknown);
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
@@ -4904,6 +4943,8 @@ loser:
static SECStatus
tls13_FinishHandshake(sslSocket *ss)
{
+ /* If |!echHpkeCtx|, any advertised ECH was GREASE ECH. */
+ PRBool offeredEch = !ss->sec.isServer && ss->ssl3.hs.echHpkeCtx;
PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
PORT_Assert(ss->ssl3.hs.restartTarget == NULL);
@@ -4919,6 +4960,21 @@ tls13_FinishHandshake(sslSocket *ss)
TLS13_SET_HS_STATE(ss, idle_handshake);
+ if (offeredEch &&
+ !ssl3_ExtensionNegotiated(ss, ssl_tls13_encrypted_client_hello_xtn)) {
+ SSL3_SendAlert(ss, alert_fatal, ech_required);
+
+ /* "If [one, none] of the values contains a supported version, the client can
+ * regard ECH as securely [replaced, disabled] by the server." */
+ if (ss->xtnData.echRetryConfigs.len) {
+ PORT_SetError(SSL_ERROR_ECH_RETRY_WITH_ECH);
+ ss->xtnData.echRetryConfigsValid = PR_TRUE;
+ } else {
+ PORT_SetError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
+ }
+ return SECFailure;
+ }
+
ssl_FinishHandshake(ss);
return SECSuccess;
@@ -5429,6 +5485,7 @@ tls13_HandleNewSessionTicket(sslSocket *ss, PRUint8 *b, PRUint32 length)
return SECSuccess;
}
+#define _M_NONE 0
#define _M(a) (1 << PR_MIN(a, 31))
#define _M1(a) (_M(ssl_hs_##a))
#define _M2(a, b) (_M1(a) | _M1(b))
@@ -5462,7 +5519,8 @@ static const struct {
{ ssl_tls13_supported_versions_xtn, _M3(client_hello, server_hello,
hello_retry_request) },
{ ssl_record_size_limit_xtn, _M2(client_hello, encrypted_extensions) },
- { ssl_tls13_encrypted_sni_xtn, _M2(client_hello, encrypted_extensions) },
+ { ssl_tls13_encrypted_client_hello_xtn, _M2(client_hello, encrypted_extensions) },
+ { ssl_tls13_outer_extensions_xtn, _M_NONE /* Encoding/decoding only */ },
{ ssl_tls13_post_handshake_auth_xtn, _M1(client_hello) }
};
@@ -6158,10 +6216,9 @@ tls13_NegotiateVersion(sslSocket *ss, const TLSExtension *supportedVersions)
return SECFailure;
}
for (version = ss->vrange.max; version >= ss->vrange.min; --version) {
- if (ss->ssl3.hs.helloRetry && version < SSL_LIBRARY_VERSION_TLS_1_3) {
- /* Prevent negotiating to a lower version in response to a TLS 1.3 HRR.
- * Since we check in descending (local) order, this will only fail if
- * our vrange has changed or the client didn't offer 1.3 in response. */
+ if (version < SSL_LIBRARY_VERSION_TLS_1_3 &&
+ (ss->ssl3.hs.helloRetry || ss->ssl3.hs.echAccepted)) {
+ /* Prevent negotiating to a lower version after 1.3 HRR or ECH */
PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version);
return SECFailure;
diff --git a/lib/ssl/tls13con.h b/lib/ssl/tls13con.h
index 7b8d58648..ae0b4ae33 100644
--- a/lib/ssl/tls13con.h
+++ b/lib/ssl/tls13con.h
@@ -19,6 +19,7 @@ typedef enum {
} tls13ExtensionStatus;
#define TLS13_MAX_FINISHED_SIZE 64
+#define TLS13_COOKIE_SENTINEL 0xff
SECStatus tls13_UnprotectRecord(
sslSocket *ss, ssl3CipherSpec *spec,
diff --git a/lib/ssl/tls13ech.c b/lib/ssl/tls13ech.c
new file mode 100644
index 000000000..e208c92bf
--- /dev/null
+++ b/lib/ssl/tls13ech.c
@@ -0,0 +1,2201 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nss.h"
+#include "pk11func.h"
+#include "pk11hpke.h"
+#include "ssl.h"
+#include "sslproto.h"
+#include "sslimpl.h"
+#include "selfencrypt.h"
+#include "ssl3exthandle.h"
+#include "tls13ech.h"
+#include "tls13exthandle.h"
+#include "tls13hkdf.h"
+
+extern SECStatus
+ssl3_UpdateExplicitHandshakeTranscript(sslSocket *ss, const unsigned char *b,
+ unsigned int l, sslBuffer *transcriptBuf);
+extern SECStatus
+ssl3_HandleClientHelloPreamble(sslSocket *ss, PRUint8 **b, PRUint32 *length, SECItem *sidBytes,
+ SECItem *cookieBytes, SECItem *suites, SECItem *comps);
+
+void
+tls13_DestroyEchConfig(sslEchConfig *config)
+{
+ if (!config) {
+ return;
+ }
+ SECITEM_FreeItem(&config->contents.publicKey, PR_FALSE);
+ SECITEM_FreeItem(&config->contents.suites, PR_FALSE);
+ SECITEM_FreeItem(&config->raw, PR_FALSE);
+ PORT_Free(config->contents.publicName);
+ config->contents.publicName = NULL;
+ PORT_ZFree(config, sizeof(*config));
+}
+
+void
+tls13_DestroyEchConfigs(PRCList *list)
+{
+ PRCList *cur_p;
+ while (!PR_CLIST_IS_EMPTY(list)) {
+ cur_p = PR_LIST_TAIL(list);
+ PR_REMOVE_LINK(cur_p);
+ tls13_DestroyEchConfig((sslEchConfig *)cur_p);
+ }
+}
+
+SECStatus
+tls13_CopyEchConfigs(PRCList *oConfigs, PRCList *configs)
+{
+ SECStatus rv;
+ sslEchConfig *config;
+ sslEchConfig *newConfig = NULL;
+
+ for (PRCList *cur_p = PR_LIST_HEAD(oConfigs);
+ cur_p != oConfigs;
+ cur_p = PR_NEXT_LINK(cur_p)) {
+ config = (sslEchConfig *)PR_LIST_TAIL(oConfigs);
+ newConfig = PORT_ZNew(sslEchConfig);
+ if (!newConfig) {
+ goto loser;
+ }
+
+ rv = SECITEM_CopyItem(NULL, &newConfig->raw, &config->raw);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ newConfig->contents.publicName = PORT_Strdup(config->contents.publicName);
+ if (!newConfig->contents.publicName) {
+ goto loser;
+ }
+ rv = SECITEM_CopyItem(NULL, &newConfig->contents.publicKey,
+ &config->contents.publicKey);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = SECITEM_CopyItem(NULL, &newConfig->contents.suites,
+ &config->contents.suites);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ newConfig->contents.kemId = config->contents.kemId;
+ newConfig->contents.kdfId = config->contents.kdfId;
+ newConfig->contents.aeadId = config->contents.aeadId;
+ newConfig->contents.maxNameLen = config->contents.maxNameLen;
+ PR_APPEND_LINK(&newConfig->link, configs);
+ }
+ return SECSuccess;
+
+loser:
+ tls13_DestroyEchConfig(newConfig);
+ tls13_DestroyEchConfigs(configs);
+ return SECFailure;
+}
+
+static SECStatus
+tls13_DigestEchConfig(const sslEchConfig *cfg, PRUint8 *digest, size_t maxDigestLen)
+{
+ SECStatus rv;
+ PK11SymKey *configKey = NULL;
+ PK11SymKey *derived = NULL;
+ SECItem *derivedItem = NULL;
+ CK_HKDF_PARAMS params = { 0 };
+ SECItem paramsi = { siBuffer, (unsigned char *)&params, sizeof(params) };
+ PK11SlotInfo *slot = PK11_GetInternalSlot();
+
+ if (!slot) {
+ goto loser;
+ }
+
+ configKey = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap,
+ CKA_DERIVE, CONST_CAST(SECItem, &cfg->raw), NULL);
+ if (!configKey) {
+ goto loser;
+ }
+
+ /* We only support SHA256 KDF. */
+ PORT_Assert(cfg->contents.kdfId == HpkeKdfHkdfSha256);
+ params.bExtract = CK_TRUE;
+ params.bExpand = CK_TRUE;
+ params.prfHashMechanism = CKM_SHA256;
+ params.ulSaltType = CKF_HKDF_SALT_NULL;
+ params.pInfo = CONST_CAST(CK_BYTE, hHkdfInfoEchConfigID);
+ params.ulInfoLen = strlen(hHkdfInfoEchConfigID);
+ derived = PK11_DeriveWithFlags(configKey, CKM_HKDF_DATA,
+ &paramsi, CKM_HKDF_DERIVE, CKA_DERIVE, 32,
+ CKF_SIGN | CKF_VERIFY);
+
+ rv = PK11_ExtractKeyValue(derived);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ derivedItem = PK11_GetKeyData(derived);
+ if (!derivedItem) {
+ goto loser;
+ }
+
+ if (derivedItem->len != maxDigestLen) {
+ PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+ goto loser;
+ }
+
+ PORT_Memcpy(digest, derivedItem->data, derivedItem->len);
+ PK11_FreeSymKey(configKey);
+ PK11_FreeSymKey(derived);
+ PK11_FreeSlot(slot);
+ return SECSuccess;
+
+loser:
+ PK11_FreeSymKey(configKey);
+ PK11_FreeSymKey(derived);
+ if (slot) {
+ PK11_FreeSlot(slot);
+ }
+ return SECFailure;
+}
+
+static SECStatus
+tls13_DecodeEchConfigContents(const sslReadBuffer *rawConfig,
+ sslEchConfig **outConfig)
+{
+ SECStatus rv;
+ sslEchConfigContents contents = { 0 };
+ sslEchConfig *decodedConfig;
+ PRUint64 tmpn;
+ PRUint64 tmpn2;
+ sslReadBuffer tmpBuf;
+ PRUint16 *extensionTypes = NULL;
+ unsigned int extensionIndex = 0;
+ sslReader configReader = SSL_READER(rawConfig->buf, rawConfig->len);
+ sslReader suiteReader;
+ sslReader extensionReader;
+ PRBool hasValidSuite = PR_FALSE;
+
+ /* Parse the public_name. */
+ rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ /* Make sure the public name doesn't contain any NULLs.
+ * TODO: Just store the SECItem instead. */
+ for (tmpn = 0; tmpn < tmpBuf.len; tmpn++) {
+ if (tmpBuf.buf[tmpn] == '\0') {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ goto loser;
+ }
+ }
+
+ contents.publicName = PORT_ZAlloc(tmpBuf.len + 1);
+ if (!contents.publicName) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ goto loser;
+ }
+ PORT_Memcpy(contents.publicName, (PRUint8 *)tmpBuf.buf, tmpBuf.len);
+
+ /* Public key. */
+ rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = SECITEM_MakeItem(NULL, &contents.publicKey, (PRUint8 *)tmpBuf.buf, tmpBuf.len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslRead_ReadNumber(&configReader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ contents.kemId = tmpn;
+
+ /* Parse HPKE cipher suites. */
+ rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (tmpBuf.len & 1) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ goto loser;
+ }
+ suiteReader = (sslReader)SSL_READER(tmpBuf.buf, tmpBuf.len);
+ while (SSL_READER_REMAINING(&suiteReader)) {
+ /* kdf_id */
+ rv = sslRead_ReadNumber(&suiteReader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ /* aead_id */
+ rv = sslRead_ReadNumber(&suiteReader, 2, &tmpn2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (!hasValidSuite) {
+ /* Use the first compatible ciphersuite. */
+ rv = PK11_HPKE_ValidateParameters(contents.kemId, tmpn, tmpn2);
+ if (rv == SECSuccess) {
+ hasValidSuite = PR_TRUE;
+ contents.kdfId = tmpn;
+ contents.aeadId = tmpn2;
+ break;
+ }
+ }
+ }
+
+ rv = SECITEM_MakeItem(NULL, &contents.suites, (PRUint8 *)tmpBuf.buf, tmpBuf.len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Read the max name length. */
+ rv = sslRead_ReadNumber(&configReader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ contents.maxNameLen = (PRUint16)tmpn;
+
+ /* Extensions. We don't support any, but must
+ * check for any that are marked critical. */
+ rv = sslRead_ReadVariable(&configReader, 2, &tmpBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ extensionReader = (sslReader)SSL_READER(tmpBuf.buf, tmpBuf.len);
+ extensionTypes = PORT_NewArray(PRUint16, tmpBuf.len / 2 * sizeof(PRUint16));
+ if (!extensionTypes) {
+ goto loser;
+ }
+
+ while (SSL_READER_REMAINING(&extensionReader)) {
+ /* Get the extension's type field */
+ rv = sslRead_ReadNumber(&extensionReader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ for (unsigned int i = 0; i < extensionIndex; i++) {
+ if (extensionTypes[i] == tmpn) {
+ PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID);
+ goto loser;
+ }
+ }
+ extensionTypes[extensionIndex++] = (PRUint16)tmpn;
+
+ /* If it's mandatory, fail. */
+ if (tmpn & (1 << 15)) {
+ PORT_SetError(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION);
+ goto loser;
+ }
+
+ /* Skip. */
+ rv = sslRead_ReadVariable(&extensionReader, 2, &tmpBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+
+ /* Check that we consumed the entire ECHConfig */
+ if (SSL_READER_REMAINING(&configReader)) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
+ goto loser;
+ }
+
+ /* If the ciphersuites weren't compatible, don't
+ * set the outparam. Return success to indicate
+ * the config was well-formed. */
+ if (hasValidSuite) {
+ decodedConfig = PORT_ZNew(sslEchConfig);
+ if (!decodedConfig) {
+ goto loser;
+ }
+ decodedConfig->contents = contents;
+ *outConfig = decodedConfig;
+ } else {
+ PORT_Free(contents.publicName);
+ SECITEM_FreeItem(&contents.publicKey, PR_FALSE);
+ SECITEM_FreeItem(&contents.suites, PR_FALSE);
+ }
+ PORT_Free(extensionTypes);
+ return SECSuccess;
+
+loser:
+ PORT_Free(extensionTypes);
+ PORT_Free(contents.publicName);
+ SECITEM_FreeItem(&contents.publicKey, PR_FALSE);
+ SECITEM_FreeItem(&contents.suites, PR_FALSE);
+ return SECFailure;
+}
+
+/* Decode an ECHConfigs struct and store each ECHConfig
+ * into |configs|. */
+SECStatus
+tls13_DecodeEchConfigs(const SECItem *data, PRCList *configs)
+{
+ SECStatus rv;
+ sslEchConfig *decodedConfig = NULL;
+ sslReader rdr = SSL_READER(data->data, data->len);
+ sslReadBuffer tmp;
+ sslReadBuffer singleConfig;
+ PRUint64 version;
+ PRUint64 length;
+ PORT_Assert(PR_CLIST_IS_EMPTY(configs));
+
+ rv = sslRead_ReadVariable(&rdr, 2, &tmp);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ if (SSL_READER_REMAINING(&rdr)) {
+ PORT_SetError(SEC_ERROR_BAD_DATA);
+ return SECFailure;
+ }
+
+ sslReader configsReader = SSL_READER(tmp.buf, tmp.len);
+
+ if (!SSL_READER_REMAINING(&configsReader)) {
+ PORT_SetError(SEC_ERROR_BAD_DATA);
+ return SECFailure;
+ }
+
+ /* Handle each ECHConfig. */
+ while (SSL_READER_REMAINING(&configsReader)) {
+ singleConfig.buf = SSL_READER_CURRENT(&configsReader);
+ /* Version */
+ rv = sslRead_ReadNumber(&configsReader, 2, &version);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ /* Length */
+ rv = sslRead_ReadNumber(&configsReader, 2, &length);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ singleConfig.len = 4 + length;
+
+ rv = sslRead_Read(&configsReader, length, &tmp);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (version == TLS13_ECH_VERSION) {
+ rv = tls13_DecodeEchConfigContents(&tmp, &decodedConfig);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+
+ if (decodedConfig) {
+ decodedConfig->version = version;
+ rv = SECITEM_MakeItem(NULL, &decodedConfig->raw, singleConfig.buf,
+ singleConfig.len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = tls13_DigestEchConfig(decodedConfig, decodedConfig->configId,
+ sizeof(decodedConfig->configId));
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ PR_APPEND_LINK(&decodedConfig->link, configs);
+ decodedConfig = NULL;
+ }
+ }
+ }
+ return SECSuccess;
+
+loser:
+ tls13_DestroyEchConfigs(configs);
+ return SECFailure;
+}
+
+/* Encode an ECHConfigs structure. We only allow one config, and as the
+ * primary use for this function is to generate test inputs, we don't
+ * validate against what HPKE and libssl can actually support. */
+SECStatus
+SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites,
+ unsigned int hpkeSuiteCount, HpkeKemId kemId,
+ const SECKEYPublicKey *pubKey, PRUint16 maxNameLen,
+ PRUint8 *out, unsigned int *outlen, unsigned int maxlen)
+{
+ SECStatus rv;
+ unsigned int savedOffset;
+ unsigned int len;
+ sslBuffer b = SSL_BUFFER_EMPTY;
+ PRUint8 tmpBuf[66]; // Large enough for an EC public key, currently only X25519.
+ unsigned int tmpLen;
+
+ if (!publicName || PORT_Strlen(publicName) == 0 || !hpkeSuites ||
+ hpkeSuiteCount == 0 || !pubKey || maxNameLen == 0 || !out || !outlen) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ rv = sslBuffer_Skip(&b, 2, NULL);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_AppendNumber(&b, TLS13_ECH_VERSION, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_Skip(&b, 2, &savedOffset);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ len = PORT_Strlen(publicName);
+ rv = sslBuffer_AppendVariable(&b, (const PRUint8 *)publicName, len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = PK11_HPKE_Serialize(pubKey, tmpBuf, &tmpLen, sizeof(tmpBuf));
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(&b, tmpBuf, tmpLen, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_AppendNumber(&b, kemId, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_AppendNumber(&b, hpkeSuiteCount * 4, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ for (unsigned int i = 0; i < hpkeSuiteCount; i++) {
+ rv = sslBuffer_AppendNumber(&b, hpkeSuites[i], 4);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+
+ rv = sslBuffer_AppendNumber(&b, maxNameLen, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* extensions */
+ rv = sslBuffer_AppendNumber(&b, 0, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Write the length now that we know it. */
+ rv = sslBuffer_InsertLength(&b, 0, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_InsertLength(&b, savedOffset, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (SSL_BUFFER_LEN(&b) > maxlen) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ goto loser;
+ }
+ PORT_Memcpy(out, SSL_BUFFER_BASE(&b), SSL_BUFFER_LEN(&b));
+ *outlen = SSL_BUFFER_LEN(&b);
+ sslBuffer_Clear(&b);
+ return SECSuccess;
+
+loser:
+ sslBuffer_Clear(&b);
+ return SECFailure;
+}
+
+SECStatus
+SSLExp_GetEchRetryConfigs(PRFileDesc *fd, SECItem *retryConfigs)
+{
+ SECStatus rv;
+ sslSocket *ss;
+ SECItem out = { siBuffer, NULL, 0 };
+
+ if (!fd || !retryConfigs) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in %s",
+ SSL_GETPID(), fd, __FUNCTION__));
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+ if (!ss->xtnData.echRetryConfigsValid) {
+ PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
+ return SECFailure;
+ }
+ /* May be empty. */
+ rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.echRetryConfigs);
+ if (rv == SECFailure) {
+ return SECFailure;
+ }
+ *retryConfigs = out;
+ return SECSuccess;
+}
+
+SECStatus
+SSLExp_RemoveEchConfigs(PRFileDesc *fd)
+{
+ sslSocket *ss;
+
+ if (!fd) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in %s",
+ SSL_GETPID(), fd, __FUNCTION__));
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ if (!PR_CLIST_IS_EMPTY(&ss->echConfigs)) {
+ tls13_DestroyEchConfigs(&ss->echConfigs);
+ }
+
+ /* Also remove any retry_configs and handshake context. */
+ if (ss->xtnData.echRetryConfigs.len) {
+ SECITEM_FreeItem(&ss->xtnData.echRetryConfigs, PR_FALSE);
+ }
+
+ if (ss->ssl3.hs.echHpkeCtx) {
+ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE);
+ ss->ssl3.hs.echHpkeCtx = NULL;
+ }
+ PORT_Free(CONST_CAST(char, ss->ssl3.hs.echPublicName));
+ ss->ssl3.hs.echPublicName = NULL;
+
+ return SECSuccess;
+}
+
+/* Import one or more ECHConfigs for the given keypair. The AEAD/KDF
+ * may differ , but only X25519 is supported for the KEM.*/
+SECStatus
+SSLExp_SetServerEchConfigs(PRFileDesc *fd,
+ const SECKEYPublicKey *pubKey, const SECKEYPrivateKey *privKey,
+ const PRUint8 *echConfigs, unsigned int echConfigsLen)
+{
+#ifndef NSS_ENABLE_DRAFT_HPKE
+ PORT_SetError(SSL_ERROR_FEATURE_DISABLED);
+ return SECFailure;
+#else
+ sslSocket *ss;
+ SECStatus rv;
+ SECItem data = { siBuffer, CONST_CAST(PRUint8, echConfigs), echConfigsLen };
+
+ if (!fd || !pubKey || !privKey || !echConfigs || echConfigsLen == 0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in %s",
+ SSL_GETPID(), fd, __FUNCTION__));
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ /* Overwrite if we're already configured. */
+ rv = SSLExp_RemoveEchConfigs(fd);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ rv = tls13_DecodeEchConfigs(&data, &ss->echConfigs);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (PR_CLIST_IS_EMPTY(&ss->echConfigs)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ goto loser;
+ }
+
+ ss->echPubKey = SECKEY_CopyPublicKey(pubKey);
+ if (!ss->echPubKey) {
+ goto loser;
+ }
+ ss->echPrivKey = SECKEY_CopyPrivateKey(privKey);
+ if (!ss->echPrivKey) {
+ goto loser;
+ }
+ return SECSuccess;
+
+loser:
+ tls13_DestroyEchConfigs(&ss->echConfigs);
+ SECKEY_DestroyPrivateKey(ss->echPrivKey);
+ SECKEY_DestroyPublicKey(ss->echPubKey);
+ ss->echPubKey = NULL;
+ ss->echPrivKey = NULL;
+ return SECFailure;
+#endif
+}
+
+/* Client enable. For now, we'll use the first
+ * compatible config (server preference). */
+SECStatus
+SSLExp_SetClientEchConfigs(PRFileDesc *fd,
+ const PRUint8 *echConfigs,
+ unsigned int echConfigsLen)
+{
+#ifndef NSS_ENABLE_DRAFT_HPKE
+ PORT_SetError(SSL_ERROR_FEATURE_DISABLED);
+ return SECFailure;
+#else
+ SECStatus rv;
+ sslSocket *ss;
+ SECItem data = { siBuffer, CONST_CAST(PRUint8, echConfigs), echConfigsLen };
+
+ if (!fd || !echConfigs || echConfigsLen == 0) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ ss = ssl_FindSocket(fd);
+ if (!ss) {
+ SSL_DBG(("%d: SSL[%d]: bad socket in %s",
+ SSL_GETPID(), fd, __FUNCTION__));
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ /* Overwrite if we're already configured. */
+ rv = SSLExp_RemoveEchConfigs(fd);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ rv = tls13_DecodeEchConfigs(&data, &ss->echConfigs);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ if (PR_CLIST_IS_EMPTY(&ss->echConfigs)) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+#endif
+}
+
+/* Set up ECH. This generates an ephemeral sender
+ * keypair and the HPKE context */
+SECStatus
+tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type)
+{
+ SECStatus rv;
+ HpkeContext *cx = NULL;
+ SECKEYPublicKey *pkR = NULL;
+ SECItem hpkeInfo = { siBuffer, NULL, 0 };
+ PK11SymKey *hrrPsk = NULL;
+ sslEchConfig *cfg = NULL;
+ const SECItem kEchHrrInfoItem = { siBuffer,
+ (unsigned char *)kHpkeInfoEchHrr,
+ strlen(kHpkeInfoEchHrr) };
+ const SECItem kEchHrrPskLabelItem = { siBuffer,
+ (unsigned char *)kHpkeLabelHrrPsk,
+ strlen(kHpkeLabelHrrPsk) };
+
+ if (PR_CLIST_IS_EMPTY(&ss->echConfigs) ||
+ !ssl_ShouldSendSNIExtension(ss, ss->url) ||
+ IS_DTLS(ss)) {
+ return SECSuccess;
+ }
+
+ /* Maybe apply our own priority if >1. For now, we only support
+ * one version and one KEM. Each ECHConfig can specify multiple
+ * KDF/AEADs, so just use the first. */
+ cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
+
+ /* Skip ECH if the public name matches the private name. */
+ if (0 == PORT_Strcmp(cfg->contents.publicName, ss->url)) {
+ return SECSuccess;
+ }
+
+ SSL_TRC(50, ("%d: TLS13[%d]: Setup client ECH",
+ SSL_GETPID(), ss->fd));
+
+ switch (type) {
+ case client_hello_initial:
+ PORT_Assert(!ss->ssl3.hs.echHpkeCtx && !ss->ssl3.hs.echPublicName);
+ cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId,
+ cfg->contents.aeadId, NULL, NULL);
+ break;
+ case client_hello_retry:
+ PORT_Assert(ss->ssl3.hs.echHpkeCtx && ss->ssl3.hs.echPublicName);
+ rv = PK11_HPKE_ExportSecret(ss->ssl3.hs.echHpkeCtx,
+ &kEchHrrInfoItem, 32, &hrrPsk);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ PK11_HPKE_DestroyContext(ss->ssl3.hs.echHpkeCtx, PR_TRUE);
+ PORT_Free((void *)ss->ssl3.hs.echPublicName); /* CONST */
+ ss->ssl3.hs.echHpkeCtx = NULL;
+ ss->ssl3.hs.echPublicName = NULL;
+ cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId,
+ cfg->contents.aeadId, hrrPsk, &kEchHrrPskLabelItem);
+ break;
+ default:
+ PORT_Assert(0);
+ goto loser;
+ }
+ if (!cx) {
+ goto loser;
+ }
+
+ rv = PK11_HPKE_Deserialize(cx, cfg->contents.publicKey.data, cfg->contents.publicKey.len, &pkR);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (!SECITEM_AllocItem(NULL, &hpkeInfo, strlen(kHpkeInfoEch) + 1 + cfg->raw.len)) {
+ goto loser;
+ }
+ PORT_Memcpy(&hpkeInfo.data[0], kHpkeInfoEch, strlen(kHpkeInfoEch));
+ PORT_Memset(&hpkeInfo.data[strlen(kHpkeInfoEch)], 0, 1);
+ PORT_Memcpy(&hpkeInfo.data[strlen(kHpkeInfoEch) + 1], cfg->raw.data, cfg->raw.len);
+
+ /* Setup with an ephemeral sender keypair. */
+ rv = PK11_HPKE_SetupS(cx, NULL, NULL, pkR, &hpkeInfo);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (!ss->ssl3.hs.helloRetry) {
+ rv = ssl3_GetNewRandom(ss->ssl3.hs.client_inner_random);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+ }
+
+ /* If ECH is rejected, the application will use SSLChannelInfo
+ * to fetch this field and perform cert chain verification. */
+ ss->ssl3.hs.echPublicName = PORT_Strdup(cfg->contents.publicName);
+ if (!ss->ssl3.hs.echPublicName) {
+ goto loser;
+ }
+
+ ss->ssl3.hs.echHpkeCtx = cx;
+ PK11_FreeSymKey(hrrPsk);
+ SECKEY_DestroyPublicKey(pkR);
+ SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
+ return SECSuccess;
+
+loser:
+ PK11_HPKE_DestroyContext(cx, PR_TRUE);
+ PK11_FreeSymKey(hrrPsk);
+ SECKEY_DestroyPublicKey(pkR);
+ SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
+ return SECFailure;
+}
+
+/*
+ * enum {
+ * encrypted_client_hello(0xfe08), (65535)
+ * } ExtensionType;
+ *
+ * struct {
+ * HpkeKdfId kdf_id;
+ * HpkeAeadId aead_id;
+ * } ECHCipherSuite;
+ * struct {
+ * ECHCipherSuite cipher_suite;
+ * opaque config_id<0..255>;
+ * opaque enc<1..2^16-1>;
+ * opaque payload<1..2^16-1>;
+ * } ClientECH;
+ *
+ * Takes as input the constructed ClientHelloInner and
+ * returns a constructed encrypted_client_hello extension
+ * (replacing the contents of |chInner|).
+ */
+static SECStatus
+tls13_EncryptClientHello(sslSocket *ss, sslBuffer *outerAAD, sslBuffer *chInner)
+{
+ SECStatus rv;
+ SECItem chPt = { siBuffer, chInner->buf, chInner->len };
+ SECItem *chCt = NULL;
+ SECItem aadItem = { siBuffer, outerAAD ? outerAAD->buf : NULL, outerAAD ? outerAAD->len : 0 };
+ const SECItem *hpkeEnc = NULL;
+ const sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
+ PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->echConfigs));
+
+ SSL_TRC(50, ("%d: TLS13[%d]: Encrypting Client Hello Inner",
+ SSL_GETPID(), ss->fd));
+
+ hpkeEnc = PK11_HPKE_GetEncapPubKey(ss->ssl3.hs.echHpkeCtx);
+ if (!hpkeEnc) {
+ FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
+ goto loser;
+ }
+
+#ifndef UNSAFE_FUZZER_MODE
+ rv = PK11_HPKE_Seal(ss->ssl3.hs.echHpkeCtx, &aadItem, &chPt, &chCt);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+#else
+ /* Fake a tag. */
+ SECITEM_AllocItem(NULL, chCt, chPt.len + 16);
+ if (!chCt) {
+ goto loser;
+ }
+ PORT_Memcpy(chCt->data, chPt.data, chPt.len);
+#endif
+
+ /* Format the encrypted_client_hello extension. */
+ sslBuffer_Clear(chInner);
+ rv = sslBuffer_AppendNumber(chInner, cfg->contents.kdfId, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(chInner, cfg->contents.aeadId, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(chInner, cfg->configId, sizeof(cfg->configId), 1);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(chInner, hpkeEnc->data, hpkeEnc->len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(chInner, chCt->data, chCt->len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ SECITEM_FreeItem(chCt, PR_TRUE);
+ return SECSuccess;
+
+loser:
+ SECITEM_FreeItem(chCt, PR_TRUE);
+ return SECFailure;
+}
+
+SECStatus
+tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead,
+ const SECItem *configId, sslEchConfig **cfg)
+{
+ sslEchConfig *candidate;
+ PRINT_BUF(50, (ss, "Server GetMatchingEchConfig with digest:",
+ configId->data, configId->len));
+
+ for (PRCList *cur_p = PR_LIST_HEAD(&ss->echConfigs);
+ cur_p != &ss->echConfigs;
+ cur_p = PR_NEXT_LINK(cur_p)) {
+ sslEchConfig *echConfig = (sslEchConfig *)cur_p;
+ if (configId->len != sizeof(echConfig->configId) ||
+ PORT_Memcmp(echConfig->configId, configId->data, sizeof(echConfig->configId))) {
+ continue;
+ }
+ candidate = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
+ if (candidate->contents.aeadId != aead ||
+ candidate->contents.kdfId != kdf) {
+ continue;
+ }
+ *cfg = candidate;
+ return SECSuccess;
+ }
+
+ SSL_TRC(50, ("%d: TLS13[%d]: Server found no matching ECHConfig",
+ SSL_GETPID(), ss->fd));
+
+ *cfg = NULL;
+ return SECSuccess;
+}
+
+/* This is unfortunate in that it requires a second decryption of the cookie.
+ * This is largely copied from tls13hashstate.c as HRR handling is still in flux.
+ * TODO: Consolidate this code no later than -09. */
+/* struct {
+ * uint8 indicator = 0xff; // To disambiguate from tickets.
+ * uint16 cipherSuite; // Selected cipher suite.
+ * uint16 keyShare; // Requested key share group (0=none)
+ * opaque applicationToken<0..65535>; // Application token
+ * opaque echHrrPsk<0..255>; // Encrypted ClientHello HRR PSK
+ * opaque echConfigId<0..255>; // ECH config ID selected in CH1, to decrypt the CH2 ECH payload.
+ * opaque ch_hash[rest_of_buffer]; // H(ClientHello)
+ * } CookieInner;
+ */
+SECStatus
+tls13_GetEchInfoFromCookie(sslSocket *ss, const TLSExtension *hrrCookie, PK11SymKey **echHrrPsk, SECItem *echConfigId)
+{
+ SECStatus rv;
+ PK11SymKey *hrrKey = NULL;
+ PRUint64 tmpn;
+ sslReadBuffer tmpReader = { 0 };
+ PK11SlotInfo *slot = NULL;
+ unsigned char plaintext[1024];
+ unsigned int plaintextLen = 0;
+ SECItem hrrPskItem = { siBuffer, NULL, 0 };
+ SECItem hrrCookieData = { siBuffer, NULL, 0 };
+ SECItem saveHrrCookieData = hrrCookieData;
+ SECItem previousEchConfigId = { siBuffer, NULL, 0 };
+
+ /* Copy the extension data so as to not consume it in the handler.
+ * The extension handler walks the pointer, so save a copy to free. */
+ rv = SECITEM_CopyItem(NULL, &hrrCookieData, &hrrCookie->data);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ saveHrrCookieData = hrrCookieData;
+
+ rv = tls13_ServerHandleCookieXtn(ss, &ss->xtnData, &hrrCookieData);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = ssl_SelfEncryptUnprotect(ss, ss->xtnData.cookie.data, ss->xtnData.cookie.len,
+ plaintext, &plaintextLen, sizeof(plaintext));
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ sslReader reader = SSL_READER(plaintext, plaintextLen);
+
+ /* Should start with 0xff. */
+ rv = sslRead_ReadNumber(&reader, 1, &tmpn);
+ if ((rv != SECSuccess) || (tmpn != 0xff)) {
+ rv = SECFailure;
+ goto loser;
+ }
+ rv = sslRead_ReadNumber(&reader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ /* The named group, if any. */
+ rv = sslRead_ReadNumber(&reader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ /* Application token. */
+ rv = sslRead_ReadNumber(&reader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslRead_Read(&reader, tmpn, &tmpReader);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* ECH Config ID */
+ rv = sslRead_ReadVariable(&reader, 1, &tmpReader);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = SECITEM_MakeItem(NULL, &previousEchConfigId,
+ tmpReader.buf, tmpReader.len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* ECH HRR key. */
+ rv = sslRead_ReadVariable(&reader, 1, &tmpReader);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (tmpReader.len) {
+ slot = PK11_GetInternalSlot();
+ if (!slot) {
+ rv = SECFailure;
+ goto loser;
+ }
+ hrrPskItem.len = tmpReader.len;
+ hrrPskItem.data = CONST_CAST(PRUint8, tmpReader.buf);
+ hrrKey = PK11_ImportSymKey(slot, CKM_HKDF_KEY_GEN, PK11_OriginUnwrap,
+ CKA_DERIVE, &hrrPskItem, NULL);
+ PK11_FreeSlot(slot);
+ if (!hrrKey) {
+ rv = SECFailure;
+ goto loser;
+ }
+ }
+ *echConfigId = previousEchConfigId;
+ *echHrrPsk = hrrKey;
+ SECITEM_FreeItem(&saveHrrCookieData, PR_FALSE);
+ return SECSuccess;
+
+loser:
+ SECITEM_FreeItem(&previousEchConfigId, PR_FALSE);
+ SECITEM_FreeItem(&saveHrrCookieData, PR_FALSE);
+ return SECFailure;
+}
+
+/* Given a CH with extensions, copy from the start up to the extensions
+ * into |writer| and return the extensions themselves in |extensions|.
+ * If |explicitSid|, place this value into |writer| as the SID. Else,
+ * the sid is copied from |reader| to |writer|. */
+static SECStatus
+tls13_CopyChPreamble(sslReader *reader, const SECItem *explicitSid, sslBuffer *writer, sslReadBuffer *extensions)
+{
+ SECStatus rv;
+ sslReadBuffer tmpReadBuf;
+
+ /* Locate the extensions. */
+ rv = sslRead_Read(reader, 2 + SSL3_RANDOM_LENGTH, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ rv = sslBuffer_Append(writer, tmpReadBuf.buf, tmpReadBuf.len);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ /* legacy_session_id */
+ rv = sslRead_ReadVariable(reader, 1, &tmpReadBuf);
+ if (explicitSid) {
+ /* Encoded SID should be empty when copying from CHOuter. */
+ if (tmpReadBuf.len > 0) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+ return SECFailure;
+ }
+ rv = sslBuffer_AppendVariable(writer, explicitSid->data, explicitSid->len, 1);
+ } else {
+ rv = sslBuffer_AppendVariable(writer, tmpReadBuf.buf, tmpReadBuf.len, 1);
+ }
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ /* cipher suites */
+ rv = sslRead_ReadVariable(reader, 2, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ rv = sslBuffer_AppendVariable(writer, tmpReadBuf.buf, tmpReadBuf.len, 2);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ /* compression */
+ rv = sslRead_ReadVariable(reader, 1, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ rv = sslBuffer_AppendVariable(writer, tmpReadBuf.buf, tmpReadBuf.len, 1);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ /* extensions */
+ rv = sslRead_ReadVariable(reader, 2, extensions);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ if (SSL_READER_REMAINING(reader) != 0) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+static SECStatus
+tls13_MakeChOuterAAD(const SECItem *outer, sslBuffer *outerAAD)
+{
+ SECStatus rv;
+ sslBuffer aad = SSL_BUFFER_EMPTY;
+ sslReadBuffer aadXtns;
+ sslReader chReader = SSL_READER(outer->data, outer->len);
+ PRUint64 tmpn;
+ sslReadBuffer tmpvar;
+ unsigned int offset;
+ unsigned int preambleLen;
+
+ rv = sslBuffer_Skip(&aad, 4, NULL);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* aad := preamble, aadXtn := extensions */
+ rv = tls13_CopyChPreamble(&chReader, NULL, &aad, &aadXtns);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ sslReader xtnsReader = SSL_READER(aadXtns.buf, aadXtns.len);
+ preambleLen = SSL_BUFFER_LEN(&aad);
+
+ /* Save room for extensions length. */
+ rv = sslBuffer_Skip(&aad, 2, &offset);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Append each extension, minus encrypted_client_hello_xtn. */
+ while (SSL_READER_REMAINING(&xtnsReader)) {
+ rv = sslRead_ReadNumber(&xtnsReader, 2, &tmpn);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslRead_ReadVariable(&xtnsReader, 2, &tmpvar);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (tmpn != ssl_tls13_encrypted_client_hello_xtn) {
+ rv = sslBuffer_AppendNumber(&aad, tmpn, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(&aad, tmpvar.buf, tmpvar.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ }
+
+ rv = sslBuffer_InsertNumber(&aad, offset, SSL_BUFFER_LEN(&aad) - preambleLen - 2, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Give it a message header. */
+ rv = sslBuffer_InsertNumber(&aad, 0, ssl_hs_client_hello, 1);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_InsertLength(&aad, 1, 3);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ *outerAAD = aad;
+ return SECSuccess;
+
+loser:
+ sslBuffer_Clear(&aad);
+ return SECFailure;
+}
+
+SECStatus
+tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, sslEchConfig *cfg, PK11SymKey *echHrrPsk, SECItem **chInner)
+{
+ SECStatus rv;
+ sslBuffer outerAAD = SSL_BUFFER_EMPTY;
+ HpkeContext *cx = NULL;
+ SECItem *decryptedChInner = NULL;
+ SECItem hpkeInfo = { siBuffer, NULL, 0 };
+ SECItem outerAADItem = { siBuffer, NULL, 0 };
+ const SECItem kEchHrrPskLabelItem = { siBuffer,
+ (unsigned char *)kHpkeLabelHrrPsk,
+ strlen(kHpkeLabelHrrPsk) };
+ SSL_TRC(50, ("%d: TLS13[%d]: Server opening ECH Inner%s", SSL_GETPID(),
+ ss->fd, ss->ssl3.hs.helloRetry ? " after HRR" : ""));
+
+ cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId,
+ cfg->contents.aeadId, echHrrPsk,
+ echHrrPsk ? &kEchHrrPskLabelItem : NULL);
+ if (!cx) {
+ goto loser;
+ }
+
+ if (!SECITEM_AllocItem(NULL, &hpkeInfo, strlen(kHpkeInfoEch) + 1 + cfg->raw.len)) {
+ goto loser;
+ }
+ PORT_Memcpy(&hpkeInfo.data[0], kHpkeInfoEch, strlen(kHpkeInfoEch));
+ PORT_Memset(&hpkeInfo.data[strlen(kHpkeInfoEch)], 0, 1);
+ PORT_Memcpy(&hpkeInfo.data[strlen(kHpkeInfoEch) + 1], cfg->raw.data, cfg->raw.len);
+
+ rv = PK11_HPKE_SetupR(cx, ss->echPubKey, ss->echPrivKey,
+ &ss->xtnData.echSenderPubKey, &hpkeInfo);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+
+ rv = tls13_MakeChOuterAAD(outer, &outerAAD);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+
+ outerAADItem.data = outerAAD.buf;
+ outerAADItem.len = outerAAD.len;
+
+#ifndef UNSAFE_FUZZER_MODE
+ rv = PK11_HPKE_Open(cx, &outerAADItem, &ss->xtnData.innerCh, &decryptedChInner);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+#else
+ rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.innerCh);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ decryptedChInner->len -= 16; /* Fake tag */
+#endif
+
+ /* Stash the context, we may need it for HRR. */
+ ss->ssl3.hs.echHpkeCtx = cx;
+ *chInner = decryptedChInner;
+ SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
+ sslBuffer_Clear(&outerAAD);
+ return SECSuccess;
+
+loser:
+ SECITEM_FreeItem(decryptedChInner, PR_TRUE);
+ PK11_HPKE_DestroyContext(cx, PR_TRUE);
+ SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
+ sslBuffer_Clear(&outerAAD);
+ return SECFailure;
+}
+
+/* Given a buffer of extensions prepared for CHOuter, translate those extensions to a
+ * buffer suitable for CHInner. This is intended to be called twice: once without
+ * compression for the transcript hash and binders, and once with compression for
+ * encoding the actual CHInner value. On the first run, if |inOutPskXtn| and
+ * chOuterXtnsBuf contains a PSK extension, remove it and return in the outparam.
+ * The caller will compute the binder value based on the uncompressed output. Next,
+ * if |compress|, consolidate duplicated extensions (that would otherwise be copied)
+ * into a single outer_extensions extension. If |inOutPskXtn|, the extension contains
+ * a binder, it is appended after the deduplicated outer_extensions. In the case of
+ * GREASE ECH, one call is made to estimate size (wiith compression, null inOutPskXtn).
+ */
+SECStatus
+tls13_ConstructInnerExtensionsFromOuter(sslSocket *ss, sslBuffer *chOuterXtnsBuf,
+ sslBuffer *chInnerXtns, sslBuffer *inOutPskXtn,
+ PRBool compress)
+{
+ SECStatus rv;
+ PRUint64 extensionType;
+ sslReadBuffer extensionData;
+ sslBuffer pskXtn = SSL_BUFFER_EMPTY;
+ sslBuffer dupXtns = SSL_BUFFER_EMPTY; /* Dupcliated extensions, types-only if |compress|. */
+ unsigned int tmpOffset;
+ unsigned int tmpLen;
+ unsigned int srcXtnBase; /* To truncate CHOuter and remove the PSK extension. */
+ SSL_TRC(50, ("%d: TLS13[%d]: Constructing ECH inner extensions %s compression",
+ SSL_GETPID(), compress ? "with" : "without"));
+
+ /* When offering the "encrypted_client_hello" extension in its
+ * ClientHelloOuter, the client MUST also offer an empty
+ * "encrypted_client_hello" extension in its ClientHelloInner. */
+ rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_encrypted_client_hello_xtn, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(chInnerXtns, 0, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ sslReader rdr = SSL_READER(chOuterXtnsBuf->buf, chOuterXtnsBuf->len);
+ while (SSL_READER_REMAINING(&rdr)) {
+ srcXtnBase = rdr.offset;
+ rv = sslRead_ReadNumber(&rdr, 2, &extensionType);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Get the extension data. */
+ rv = sslRead_ReadVariable(&rdr, 2, &extensionData);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ switch (extensionType) {
+ case ssl_server_name_xtn:
+ /* Write the real (private) SNI value. */
+ rv = sslBuffer_AppendNumber(chInnerXtns, extensionType, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_Skip(chInnerXtns, 2, &tmpOffset);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ tmpLen = SSL_BUFFER_LEN(chInnerXtns);
+ rv = ssl3_ClientFormatServerNameXtn(ss, ss->url,
+ strlen(ss->url),
+ NULL, chInnerXtns);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ tmpLen = SSL_BUFFER_LEN(chInnerXtns) - tmpLen;
+ rv = sslBuffer_InsertNumber(chInnerXtns, tmpOffset, tmpLen, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ break;
+ case ssl_tls13_supported_versions_xtn:
+ /* Only TLS 1.3 on CHInner. */
+ rv = sslBuffer_AppendNumber(chInnerXtns, extensionType, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(chInnerXtns, 3, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(chInnerXtns, 2, 1);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(chInnerXtns, SSL_LIBRARY_VERSION_TLS_1_3, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ break;
+ case ssl_tls13_pre_shared_key_xtn:
+ /* If GREASEing, the estimated internal length
+ * will be short. However, the presence of a PSK extension in
+ * CHOuter is already a distinguisher. */
+ if (inOutPskXtn) {
+ rv = sslBuffer_AppendNumber(&pskXtn, extensionType, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(&pskXtn, extensionData.buf,
+ extensionData.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ /* In terms of CHOuter, the PSK extension no longer exists.
+ * 0 lastXtnOffset means insert padding at the end. */
+ SSL_BUFFER_LEN(chOuterXtnsBuf) = srcXtnBase;
+ ss->xtnData.lastXtnOffset = 0;
+ }
+ break;
+ default:
+ PORT_Assert(extensionType != ssl_tls13_encrypted_client_hello_xtn);
+ rv = sslBuffer_AppendNumber(&dupXtns, extensionType, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (!compress) {
+ rv = sslBuffer_AppendVariable(&dupXtns, extensionData.buf,
+ extensionData.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ }
+ break;
+ }
+ }
+
+ /* Append duplicated extensions, compressing or not. */
+ if (SSL_BUFFER_LEN(&dupXtns) && compress) {
+ rv = sslBuffer_AppendNumber(chInnerXtns, ssl_tls13_outer_extensions_xtn, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(chInnerXtns, dupXtns.len + 1, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendBufferVariable(chInnerXtns, &dupXtns, 1);
+ } else if (SSL_BUFFER_LEN(&dupXtns)) {
+ /* Each duplicated extension has its own length. */
+ rv = sslBuffer_AppendBuffer(chInnerXtns, &dupXtns);
+ }
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* On the compression run, append the completed PSK extension (if
+ * provided). Else an incomplete (no binder) extension; the caller
+ * will compute the binder and call again. */
+ if (compress && inOutPskXtn) {
+ rv = sslBuffer_AppendBuffer(chInnerXtns, inOutPskXtn);
+ } else if (pskXtn.len) {
+ rv = sslBuffer_AppendBuffer(chInnerXtns, &pskXtn);
+ if (inOutPskXtn) {
+ *inOutPskXtn = pskXtn;
+ }
+ }
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ sslBuffer_Clear(&dupXtns);
+ return SECSuccess;
+
+loser:
+ sslBuffer_Clear(&pskXtn);
+ sslBuffer_Clear(&dupXtns);
+ return SECFailure;
+}
+
+static SECStatus
+tls13_EncodeClientHelloInner(sslSocket *ss, sslBuffer *chInner, sslBuffer *chInnerXtns, sslBuffer *out)
+{
+ PORT_Assert(ss && chInner && chInnerXtns && out);
+ SECStatus rv;
+ sslReadBuffer tmpReadBuf;
+ sslReader chReader = SSL_READER(chInner->buf, chInner->len);
+
+ rv = sslRead_Read(&chReader, 4, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslRead_Read(&chReader, 2 + SSL3_RANDOM_LENGTH, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_Append(out, tmpReadBuf.buf, tmpReadBuf.len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Skip the legacy_session_id */
+ rv = sslRead_ReadVariable(&chReader, 1, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendNumber(out, 0, 1);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* cipher suites */
+ rv = sslRead_ReadVariable(&chReader, 2, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(out, tmpReadBuf.buf, tmpReadBuf.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* compression methods */
+ rv = sslRead_ReadVariable(&chReader, 1, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(out, tmpReadBuf.buf, tmpReadBuf.len, 1);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Append the extensions. */
+ rv = sslBuffer_AppendBufferVariable(out, chInnerXtns, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ return SECSuccess;
+
+loser:
+ sslBuffer_Clear(out);
+ return SECFailure;
+}
+
+SECStatus
+tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid, PRBool freshSid,
+ sslBuffer *chOuter, sslBuffer *chOuterXtnsBuf)
+{
+ SECStatus rv;
+ sslBuffer chInner = SSL_BUFFER_EMPTY;
+ sslBuffer encodedChInner = SSL_BUFFER_EMPTY;
+ sslBuffer chInnerXtns = SSL_BUFFER_EMPTY;
+ sslBuffer pskXtn = SSL_BUFFER_EMPTY;
+ sslBuffer outerAAD = SSL_BUFFER_EMPTY;
+ unsigned int encodedChLen;
+ unsigned int preambleLen;
+ SSL_TRC(50, ("%d: TLS13[%d]: Constructing ECH inner", SSL_GETPID()));
+
+ /* Create the full (uncompressed) inner extensions and steal any PSK extension.
+ * NB: Neither chOuterXtnsBuf nor chInnerXtns are length-prefixed. */
+ rv = tls13_ConstructInnerExtensionsFromOuter(ss, chOuterXtnsBuf, &chInnerXtns,
+ &pskXtn, PR_FALSE);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+
+ rv = ssl3_CreateClientHelloPreamble(ss, sid, PR_FALSE, SSL_LIBRARY_VERSION_TLS_1_3,
+ PR_TRUE, &chInnerXtns, &chInner);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+ preambleLen = SSL_BUFFER_LEN(&chInner);
+
+ /* Write handshake header length. tls13_EncryptClientHello will
+ * remove this upon encoding, but the transcript needs it. This assumes
+ * the 4B stream-variant header. */
+ PORT_Assert(!IS_DTLS(ss));
+ rv = sslBuffer_InsertNumber(&chInner, 1,
+ chInner.len + 2 + chInnerXtns.len - 4, 3);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (pskXtn.len) {
+ PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn));
+ PORT_Assert(ss->xtnData.lastXtnOffset == 0); /* stolen from outer */
+ rv = tls13_WriteExtensionsWithBinder(ss, &chInnerXtns, &chInner);
+ /* Update the stolen PSK extension with the binder value. */
+ PORT_Memcpy(pskXtn.buf, &chInnerXtns.buf[chInnerXtns.len - pskXtn.len], pskXtn.len);
+ } else {
+ rv = sslBuffer_AppendBufferVariable(&chInner, &chInnerXtns, 2);
+ }
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = ssl3_UpdateExplicitHandshakeTranscript(ss, chInner.buf, chInner.len,
+ &ss->ssl3.hs.echInnerMessages);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+
+ /* Un-append the extensions, then append compressed via Encoded. */
+ SSL_BUFFER_LEN(&chInner) = preambleLen;
+ sslBuffer_Clear(&chInnerXtns);
+ rv = tls13_ConstructInnerExtensionsFromOuter(ss, chOuterXtnsBuf,
+ &chInnerXtns, &pskXtn, PR_TRUE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* TODO: Pad CHInner */
+ rv = tls13_EncodeClientHelloInner(ss, &chInner, &chInnerXtns, &encodedChInner);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Pad the outer prior to appending ECH (for the AAD).
+ * Encoded extension size is (echCipherSuite + enc + configId + payload + tag).
+ * Post-encryption, we'll assert that this was correct. */
+ encodedChLen = 4 + 33 + 34 + 2 + encodedChInner.len + 16;
+ rv = ssl_InsertPaddingExtension(ss, chOuter->len + encodedChLen, chOuterXtnsBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Make the ClientHelloOuterAAD value, which is complete
+ * chOuter minus encrypted_client_hello xtn. */
+ rv = sslBuffer_Append(&outerAAD, chOuter->buf, chOuter->len);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendBufferVariable(&outerAAD, chOuterXtnsBuf, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_InsertLength(&outerAAD, 1, 3);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Insert the encrypted_client_hello xtn and coalesce. */
+ rv = tls13_EncryptClientHello(ss, &outerAAD, &encodedChInner);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ PORT_Assert(encodedChLen == encodedChInner.len);
+
+ rv = ssl3_EmplaceExtension(ss, chOuterXtnsBuf, ssl_tls13_encrypted_client_hello_xtn,
+ encodedChInner.buf, encodedChInner.len, PR_TRUE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = ssl3_InsertChHeaderSize(ss, chOuter, chOuterXtnsBuf);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_AppendBufferVariable(chOuter, chOuterXtnsBuf, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+loser:
+ sslBuffer_Clear(&chInner);
+ sslBuffer_Clear(&encodedChInner);
+ sslBuffer_Clear(&chInnerXtns);
+ sslBuffer_Clear(&pskXtn);
+ sslBuffer_Clear(&outerAAD);
+ return rv;
+}
+
+static SECStatus
+tls13_ComputeEchSignal(sslSocket *ss, PRUint8 *out)
+{
+ SECStatus rv;
+ PRUint8 derived[64];
+ SECItem randItem = { siBuffer,
+ ss->sec.isServer ? ss->ssl3.hs.client_random : ss->ssl3.hs.client_inner_random,
+ SSL3_RANDOM_LENGTH };
+ SSLHashType hashAlg = tls13_GetHash(ss);
+ PK11SymKey *extracted = NULL;
+ PK11SymKey *randKey = NULL;
+ PK11SlotInfo *slot = PK11_GetInternalSlot();
+ if (!slot) {
+ goto loser;
+ }
+
+ randKey = PK11_ImportDataKey(slot, CKM_HKDF_DATA, PK11_OriginUnwrap,
+ CKA_DERIVE, &randItem, NULL);
+ if (!randKey) {
+ goto loser;
+ }
+
+ rv = tls13_HkdfExtract(NULL, randKey, hashAlg, &extracted);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = tls13_HkdfExpandLabelRaw(extracted, hashAlg, ss->ssl3.hs.server_random, 24,
+ kHkdfInfoEchConfirm, strlen(kHkdfInfoEchConfirm),
+ ss->protocolVariant, derived, TLS13_ECH_SIGNAL_LEN);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ PORT_Memcpy(out, derived, TLS13_ECH_SIGNAL_LEN);
+ SSL_TRC(50, ("%d: TLS13[%d]: %s computed ECH signal", SSL_GETPID(), ss->fd, SSL_ROLE(ss)));
+ PRINT_BUF(50, (ss, "", out, TLS13_ECH_SIGNAL_LEN));
+ PK11_FreeSymKey(extracted);
+ PK11_FreeSymKey(randKey);
+ PK11_FreeSlot(slot);
+ return SECSuccess;
+
+loser:
+ PK11_FreeSymKey(extracted);
+ PK11_FreeSymKey(randKey);
+ if (slot) {
+ PK11_FreeSlot(slot);
+ }
+ return SECFailure;
+}
+
+/* Called just prior to padding the CH. Use the size of the CH to estimate
+ * the size of a corresponding ECH extension, then add it to the buffer. */
+SECStatus
+tls13_MaybeGreaseEch(sslSocket *ss, unsigned int preambleLen, sslBuffer *buf)
+{
+ SECStatus rv;
+ sslBuffer chInnerXtns = SSL_BUFFER_EMPTY;
+ sslBuffer greaseBuf = SSL_BUFFER_EMPTY;
+ unsigned int payloadLen;
+ HpkeAeadId aead;
+ PK11SlotInfo *slot = NULL;
+ PK11SymKey *hmacPrk = NULL;
+ PK11SymKey *derivedData = NULL;
+ SECItem *rawData;
+ CK_HKDF_PARAMS params;
+ SECItem paramsi;
+
+ if (!ss->opt.enableTls13GreaseEch || ss->ssl3.hs.echHpkeCtx) {
+ return SECSuccess;
+ }
+
+ if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3 ||
+ IS_DTLS(ss)) {
+ return SECSuccess;
+ }
+
+ /* Compress the extensions for payload length. */
+ rv = tls13_ConstructInnerExtensionsFromOuter(ss, buf, &chInnerXtns,
+ NULL, PR_TRUE);
+ if (rv != SECSuccess) {
+ goto loser; /* Code set */
+ }
+ payloadLen = preambleLen + 2 /* Xtns len */ + chInnerXtns.len - 4 /* msg header */;
+ payloadLen += 16; /* Aead tag */
+
+ /* HMAC-Expand to get something that will pass for ciphertext. */
+ slot = PK11_GetBestSlot(CKM_HKDF_DERIVE, NULL);
+ if (!slot) {
+ goto loser;
+ }
+
+ hmacPrk = PK11_KeyGen(slot, CKM_HKDF_DATA, NULL, SHA256_LENGTH, NULL);
+ if (!hmacPrk) {
+ goto loser;
+ }
+
+ params.bExtract = CK_FALSE;
+ params.bExpand = CK_TRUE;
+ params.prfHashMechanism = CKM_SHA256;
+ params.pInfo = NULL;
+ params.ulInfoLen = 0;
+ paramsi.data = (unsigned char *)&params;
+ paramsi.len = sizeof(params);
+ derivedData = PK11_DeriveWithFlags(hmacPrk, CKM_HKDF_DATA,
+ &paramsi, CKM_HKDF_DATA,
+ CKA_DERIVE, 65 + payloadLen,
+ CKF_VERIFY);
+ if (!derivedData) {
+ goto loser;
+ }
+
+ rv = PK11_ExtractKeyValue(derivedData);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* 1B aead determinant (don't send), 32B config_id, 32B enc, payload */
+ rawData = PK11_GetKeyData(derivedData);
+ if (!rawData) {
+ goto loser;
+ }
+ PORT_Assert(rawData->len == 65 + payloadLen);
+
+ /* struct {
+ HpkeKdfId kdf_id;
+ HpkeAeadId aead_id;
+ opaque config_id<0..255>;
+ opaque enc<1..2^16-1>;
+ opaque payload<1..2^16-1>;
+ } ClientECH; */
+
+ /* Only support SHA256. */
+ rv = sslBuffer_AppendNumber(&greaseBuf, HpkeKdfHkdfSha256, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* HpkeAeadAes128Gcm = 1, HpkeAeadChaCha20Poly1305 = 3, */
+ aead = (rawData->data[0] & 1) ? HpkeAeadAes128Gcm : HpkeAeadChaCha20Poly1305;
+ rv = sslBuffer_AppendNumber(&greaseBuf, aead, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[1], 32, 1);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* enc len is fixed 32B for X25519. */
+ rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[33], 32, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ rv = sslBuffer_AppendVariable(&greaseBuf, &rawData->data[65], payloadLen, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* Mark ECH as advertised so that we can validate any response.
+ * We'll use echHpkeCtx to determine if we sent real or GREASE ECH.
+ * TODO: Maybe a broader need to similarly track GREASED extensions? */
+ rv = ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn,
+ greaseBuf.buf, greaseBuf.len, PR_TRUE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ sslBuffer_Clear(&greaseBuf);
+ sslBuffer_Clear(&chInnerXtns);
+ PK11_FreeSymKey(hmacPrk);
+ PK11_FreeSymKey(derivedData);
+ PK11_FreeSlot(slot);
+ return SECSuccess;
+
+loser:
+ sslBuffer_Clear(&greaseBuf);
+ sslBuffer_Clear(&chInnerXtns);
+ PK11_FreeSymKey(hmacPrk);
+ PK11_FreeSymKey(derivedData);
+ if (slot) {
+ PK11_FreeSlot(slot);
+ }
+ return SECFailure;
+}
+
+SECStatus
+tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem *sidBytes,
+ SECItem *comps, SECItem *cookieBytes, SECItem *suites, SECItem **echInner)
+{
+ SECStatus rv;
+ int error;
+ SSL3AlertDescription desc;
+ SECItem *tmpEchInner = NULL;
+ PRUint8 *b;
+ PRUint32 length;
+ TLSExtension *echExtension;
+ TLSExtension *versionExtension;
+ PORT_Assert(!ss->ssl3.hs.echAccepted);
+ SECItem tmpSid = { siBuffer, NULL, 0 };
+ SECItem tmpCookie = { siBuffer, NULL, 0 };
+ SECItem tmpSuites = { siBuffer, NULL, 0 };
+ SECItem tmpComps = { siBuffer, NULL, 0 };
+
+ echExtension = ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn);
+ if (echExtension) {
+ rv = tls13_ServerHandleEchXtn(ss, &ss->xtnData, &echExtension->data);
+ if (rv != SECSuccess) {
+ goto loser; /* code set, alert sent. */
+ }
+ rv = tls13_MaybeAcceptEch(ss, sidBytes, msg, msgLen, &tmpEchInner);
+ if (rv != SECSuccess) {
+ goto loser; /* code set, alert sent. */
+ }
+ }
+ ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech;
+
+ if (ss->ssl3.hs.echAccepted) {
+ PORT_Assert(tmpEchInner);
+ PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.remoteExtensions));
+
+ /* Start over on ECHInner */
+ b = tmpEchInner->data;
+ length = tmpEchInner->len;
+ rv = ssl3_HandleClientHelloPreamble(ss, &b, &length, &tmpSid,
+ &tmpCookie, &tmpSuites, &tmpComps);
+ if (rv != SECSuccess) {
+ goto loser; /* code set, alert sent. */
+ }
+
+ /* Since in Outer we explicitly call the ECH handler, do the same on Inner.
+ * Extensions are already parsed in tls13_MaybeAcceptEch. */
+ echExtension = ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn);
+ if (!echExtension) {
+ FATAL_ERROR(ss, SSL_ERROR_MISSING_ECH_EXTENSION, decode_error);
+ goto loser;
+ }
+ rv = tls13_ServerHandleEchXtn(ss, &ss->xtnData, &echExtension->data);
+ if (rv != SECSuccess) {
+ goto loser; /* code set, alert sent. */
+ }
+
+ versionExtension = ssl3_FindExtension(ss, ssl_tls13_supported_versions_xtn);
+ if (!versionExtension) {
+ FATAL_ERROR(ss, SSL_ERROR_UNSUPPORTED_VERSION, protocol_version);
+ goto loser;
+ }
+ rv = tls13_NegotiateVersion(ss, versionExtension);
+ if (rv != SECSuccess) {
+ /* Could be malformed or not allowed in ECH. */
+ error = PORT_GetError();
+ desc = (error == SSL_ERROR_UNSUPPORTED_VERSION) ? protocol_version : illegal_parameter;
+ FATAL_ERROR(ss, error, desc);
+ goto loser;
+ }
+
+ *comps = tmpComps;
+ *cookieBytes = tmpCookie;
+ *sidBytes = tmpSid;
+ *suites = tmpSuites;
+ *echInner = tmpEchInner;
+ }
+ return SECSuccess;
+
+loser:
+ SECITEM_FreeItem(tmpEchInner, PR_TRUE);
+ return SECFailure;
+}
+
+SECStatus
+tls13_MaybeHandleEchSignal(sslSocket *ss)
+{
+ SECStatus rv;
+ PRUint8 computed[TLS13_ECH_SIGNAL_LEN];
+ const PRUint8 *signal = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN];
+ PORT_Assert(!ss->sec.isServer);
+
+ /* If !echHpkeCtx, we either didn't advertise or sent GREASE ECH. */
+ if (ss->ssl3.hs.echHpkeCtx) {
+ PORT_Assert(ssl3_ExtensionAdvertised(ss, ssl_tls13_encrypted_client_hello_xtn));
+ rv = tls13_ComputeEchSignal(ss, computed);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ ss->ssl3.hs.echAccepted = !PORT_Memcmp(computed, signal, TLS13_ECH_SIGNAL_LEN);
+ if (ss->ssl3.hs.echAccepted) {
+ if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+ ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn;
+ PORT_Memcpy(ss->ssl3.hs.client_random, ss->ssl3.hs.client_inner_random, SSL3_RANDOM_LENGTH);
+ }
+ /* If rejected, leave echHpkeCtx and echPublicName for rejection paths. */
+ ssl3_CoalesceEchHandshakeHashes(ss);
+ SSL_TRC(50, ("%d: TLS13[%d]: ECH %s accepted by server",
+ SSL_GETPID(), ss->fd, ss->ssl3.hs.echAccepted ? "is" : "is not"));
+ }
+
+ ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech;
+ return SECSuccess;
+}
+
+static SECStatus
+tls13_UnencodeChInner(sslSocket *ss, const SECItem *sidBytes, SECItem **echInner)
+{
+ SECStatus rv;
+ sslReadBuffer outerExtensionsList;
+ sslReadBuffer tmpReadBuf;
+ sslBuffer unencodedChInner = SSL_BUFFER_EMPTY;
+ PRCList *outerCursor;
+ PRCList *innerCursor;
+ PRBool outerFound;
+ PRUint32 xtnsOffset;
+ PRUint64 tmp;
+ PRUint8 *tmpB;
+ PRUint32 tmpLength;
+ sslReader chReader = SSL_READER((*echInner)->data, (*echInner)->len);
+ PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.echOuterExtensions));
+ PORT_Assert(PR_CLIST_IS_EMPTY(&ss->ssl3.hs.remoteExtensions));
+
+ /* unencodedChInner := preamble, tmpReadBuf := encoded extensions. */
+ rv = tls13_CopyChPreamble(&chReader, sidBytes, &unencodedChInner, &tmpReadBuf);
+ if (rv != SECSuccess) {
+ goto loser; /* code set */
+ }
+
+ /* Parse inner extensions into ss->ssl3.hs.remoteExtensions. */
+ tmpB = CONST_CAST(PRUint8, tmpReadBuf.buf);
+ rv = ssl3_ParseExtensions(ss, &tmpB, &tmpReadBuf.len);
+ if (rv != SECSuccess) {
+ goto loser; /* malformed, alert sent. */
+ }
+
+ /* Exit early if there are no outer_extensions to decompress. */
+ if (!ssl3_FindExtension(ss, ssl_tls13_outer_extensions_xtn)) {
+ rv = sslBuffer_AppendVariable(&unencodedChInner, tmpReadBuf.buf, tmpReadBuf.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ sslBuffer_Clear(&unencodedChInner);
+ return SECSuccess;
+ }
+
+ /* Save room for uncompressed length. */
+ rv = sslBuffer_Skip(&unencodedChInner, 2, &xtnsOffset);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* For each inner extension: If not outer_extensions, copy it to the output.
+ * Else if outer_extensions, iterate the compressed extension list and append
+ * each full extension as contained in CHOuter. Compressed extensions must be
+ * contiguous, so decompress at the point at which outer_extensions appears. */
+ for (innerCursor = PR_NEXT_LINK(&ss->ssl3.hs.remoteExtensions);
+ innerCursor != &ss->ssl3.hs.remoteExtensions;
+ innerCursor = PR_NEXT_LINK(innerCursor)) {
+ TLSExtension *innerExtension = (TLSExtension *)innerCursor;
+ if (innerExtension->type != ssl_tls13_outer_extensions_xtn) {
+ rv = sslBuffer_AppendNumber(&unencodedChInner,
+ innerExtension->type, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(&unencodedChInner,
+ innerExtension->data.data,
+ innerExtension->data.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ continue;
+ }
+
+ /* Decompress */
+ sslReader extensionRdr = SSL_READER(innerExtension->data.data,
+ innerExtension->data.len);
+ rv = sslRead_ReadVariable(&extensionRdr, 1, &outerExtensionsList);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (SSL_READER_REMAINING(&extensionRdr) || (outerExtensionsList.len % 2) != 0) {
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+ goto loser;
+ }
+
+ sslReader compressedTypes = SSL_READER(outerExtensionsList.buf, outerExtensionsList.len);
+ while (SSL_READER_REMAINING(&compressedTypes)) {
+ outerFound = PR_FALSE;
+ rv = sslRead_ReadNumber(&compressedTypes, 2, &tmp);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (tmp == ssl_tls13_encrypted_client_hello_xtn ||
+ tmp == ssl_tls13_outer_extensions_xtn) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, illegal_parameter);
+ goto loser;
+ }
+ for (outerCursor = PR_NEXT_LINK(&ss->ssl3.hs.echOuterExtensions);
+ outerCursor != &ss->ssl3.hs.echOuterExtensions;
+ outerCursor = PR_NEXT_LINK(outerCursor)) {
+ if (((TLSExtension *)outerCursor)->type == tmp) {
+ outerFound = PR_TRUE;
+ rv = sslBuffer_AppendNumber(&unencodedChInner,
+ ((TLSExtension *)outerCursor)->type, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ rv = sslBuffer_AppendVariable(&unencodedChInner,
+ ((TLSExtension *)outerCursor)->data.data,
+ ((TLSExtension *)outerCursor)->data.len, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ break;
+ }
+ }
+ if (!outerFound) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, illegal_parameter);
+ goto loser;
+ }
+ }
+ }
+ ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions);
+ ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
+
+ /* Correct the message and extensions sizes. */
+ rv = sslBuffer_InsertNumber(&unencodedChInner, xtnsOffset,
+ unencodedChInner.len - xtnsOffset - 2, 2);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ tmpB = &unencodedChInner.buf[xtnsOffset];
+ tmpLength = unencodedChInner.len - xtnsOffset;
+ rv = ssl3_ConsumeHandshakeNumber64(ss, &tmp, 2, &tmpB, &tmpLength);
+ if (rv != SECSuccess || tmpLength != tmp) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, internal_error);
+ goto loser;
+ }
+
+ rv = ssl3_ParseExtensions(ss, &tmpB, &tmpLength);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ SECITEM_FreeItem(*echInner, PR_FALSE);
+ (*echInner)->data = unencodedChInner.buf;
+ (*echInner)->len = unencodedChInner.len;
+ return SECSuccess;
+
+loser:
+ sslBuffer_Clear(&unencodedChInner);
+ return SECFailure;
+}
+
+SECStatus
+tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOuter,
+ unsigned int chOuterLen, SECItem **chInner)
+{
+ SECStatus rv;
+ SECItem outer = { siBuffer, CONST_CAST(PRUint8, chOuter), chOuterLen };
+ SECItem *decryptedChInner = NULL;
+ PK11SymKey *echHrrPsk = NULL;
+ SECItem hrrCh1ConfigId = { siBuffer, NULL, 0 };
+ HpkeKdfId kdf;
+ HpkeAeadId aead;
+ sslEchConfig *candidate = NULL; /* non-owning */
+ TLSExtension *hrrXtn;
+ SECItem *configId = ss->ssl3.hs.helloRetry ? &hrrCh1ConfigId : &ss->xtnData.echConfigId;
+ if (!ss->xtnData.innerCh.len) {
+ return SECSuccess;
+ }
+
+ PORT_Assert(ss->xtnData.echSenderPubKey.data);
+ PORT_Assert(ss->xtnData.echConfigId.data);
+ PORT_Assert(ss->xtnData.echCipherSuite);
+
+ if (ss->ssl3.hs.helloRetry) {
+ hrrXtn = ssl3_FindExtension(ss, ssl_tls13_cookie_xtn);
+ if (!hrrXtn) {
+ /* If the client doesn't echo cookie, we can't decrypt. */
+ return SECSuccess;
+ }
+
+ rv = tls13_GetEchInfoFromCookie(ss, hrrXtn, &echHrrPsk, &hrrCh1ConfigId);
+ if (rv != SECSuccess) {
+ /* If we failed due to an issue with the cookie, continue without
+ * ECH and let the HRR code handle the problem. */
+ goto exit_success;
+ }
+
+ /* No CH1 config_id means ECH wasn't advertised in CH1.
+ * No CH1 HRR PSK means that ECH was not accepted in CH1, and the
+ * HRR was generated off CH1Outer. */
+ if (hrrCh1ConfigId.len == 0) {
+ FATAL_ERROR(ss, SSL_ERROR_BAD_2ND_CLIENT_HELLO,
+ illegal_parameter);
+ goto loser;
+ }
+ if (!echHrrPsk) {
+ goto exit_success;
+ }
+ }
+ kdf = (HpkeKdfId)(ss->xtnData.echCipherSuite & 0xFFFF);
+ aead = (HpkeAeadId)(((ss->xtnData.echCipherSuite) >> 16) & 0xFFFF);
+ rv = tls13_GetMatchingEchConfig(ss, kdf, aead, configId, &candidate);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+ if (!candidate || candidate->contents.kdfId != kdf ||
+ candidate->contents.aeadId != aead) {
+ /* Send retry_configs if we have any.
+ * This does *not* count as negotiating ECH. */
+ rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData,
+ ssl_tls13_encrypted_client_hello_xtn,
+ tls13_ServerSendEchXtn);
+ goto exit_success;
+ }
+
+ rv = tls13_OpenClientHelloInner(ss, &outer, candidate, echHrrPsk, &decryptedChInner);
+ if (rv != SECSuccess) {
+ if (ss->ssl3.hs.helloRetry) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, decrypt_error);
+ goto loser;
+ } else {
+ rv = ssl3_RegisterExtensionSender(ss, &ss->xtnData,
+ ssl_tls13_encrypted_client_hello_xtn,
+ tls13_ServerSendEchXtn);
+ goto exit_success;
+ }
+ }
+ SSL_TRC(20, ("%d: TLS13[%d]: Successfully opened ECH inner CH",
+ SSL_GETPID(), ss->fd));
+ ss->ssl3.hs.echAccepted = PR_TRUE;
+
+ /* Stash the CHOuter extensions. They're not yet handled (only parsed). If
+ * the CHInner contains outer_extensions_xtn, we'll need to reference them. */
+ ssl3_MoveRemoteExtensions(&ss->ssl3.hs.echOuterExtensions, &ss->ssl3.hs.remoteExtensions);
+
+ rv = tls13_UnencodeChInner(ss, sidBytes, &decryptedChInner);
+ if (rv != SECSuccess) {
+ SECITEM_FreeItem(decryptedChInner, PR_TRUE);
+ goto loser; /* code set */
+ }
+ *chInner = decryptedChInner;
+
+exit_success:
+ PK11_FreeSymKey(echHrrPsk);
+ SECITEM_FreeItem(&hrrCh1ConfigId, PR_FALSE);
+ return SECSuccess;
+
+loser:
+ PK11_FreeSymKey(echHrrPsk);
+ SECITEM_FreeItem(&hrrCh1ConfigId, PR_FALSE);
+ return SECFailure;
+}
+
+SECStatus
+tls13_WriteServerEchSignal(sslSocket *ss)
+{
+ SECStatus rv;
+ PRUint8 signal[TLS13_ECH_SIGNAL_LEN];
+ rv = tls13_ComputeEchSignal(ss, signal);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ PRUint8 *dest = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN];
+ PORT_Memcpy(dest, signal, TLS13_ECH_SIGNAL_LEN);
+ return SECSuccess;
+}
diff --git a/lib/ssl/tls13ech.h b/lib/ssl/tls13ech.h
new file mode 100644
index 000000000..5c53b3f8a
--- /dev/null
+++ b/lib/ssl/tls13ech.h
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is PRIVATE to SSL.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __tls13ech_h_
+#define __tls13ech_h_
+
+#include "pk11hpke.h"
+
+/* draft-08, shared-mode only.
+ * Notes on the implementation status:
+ * - Padding (https://tools.ietf.org/html/draft-ietf-tls-esni-08#section-6.2),
+ * is not implemented (see bug 1677181).
+ * - When multiple ECHConfigs are provided by the server, the first compatible
+ * config is selected by the client. Ciphersuite choices are limited and only
+ * the AEAD may vary (AES-128-GCM or ChaCha20Poly1305).
+ * - Some of the buffering (construction/compression/decompression) could likely
+ * be optimized, but the spec is still evolving so that work is deferred.
+ */
+#define TLS13_ECH_VERSION 0xfe08
+#define TLS13_ECH_SIGNAL_LEN 8
+
+static const char kHpkeInfoEch[] = "tls ech";
+static const char kHpkeInfoEchHrr[] = "tls ech hrr key";
+static const char kHpkeLabelHrrPsk[] = "hrr key";
+static const char hHkdfInfoEchConfigID[] = "tls ech config id";
+static const char kHkdfInfoEchConfirm[] = "ech accept confirmation";
+
+struct sslEchConfigContentsStr {
+ char *publicName;
+ SECItem publicKey; /* NULL on server. Use the keypair in sslEchConfig instead. */
+ HpkeKemId kemId;
+ HpkeKdfId kdfId;
+ HpkeAeadId aeadId;
+ SECItem suites; /* One or more HpkeCipherSuites. The selected s
+ * suite is placed in kdfId and aeadId. */
+ PRUint16 maxNameLen;
+ /* No supported extensions. */
+};
+
+struct sslEchConfigStr {
+ PRCList link;
+ SECItem raw;
+ PRUint8 configId[32];
+ PRUint16 version;
+ sslEchConfigContents contents;
+};
+
+SECStatus SSLExp_EncodeEchConfig(const char *publicName, const PRUint32 *hpkeSuites,
+ unsigned int hpkeSuiteCount, HpkeKemId kemId,
+ const SECKEYPublicKey *pubKey, PRUint16 maxNameLen,
+ PRUint8 *out, unsigned int *outlen, unsigned int maxlen);
+SECStatus SSLExp_GetEchRetryConfigs(PRFileDesc *fd, SECItem *retryConfigs);
+SECStatus SSLExp_SetClientEchConfigs(PRFileDesc *fd, const PRUint8 *echConfigs,
+ unsigned int echConfigsLen);
+SECStatus SSLExp_SetServerEchConfigs(PRFileDesc *fd,
+ const SECKEYPublicKey *pubKey, const SECKEYPrivateKey *privKey,
+ const PRUint8 *echConfigs, unsigned int numEchConfigs);
+SECStatus SSLExp_RemoveEchConfigs(PRFileDesc *fd);
+
+SECStatus tls13_ClientSetupEch(sslSocket *ss, sslClientHelloType type);
+SECStatus tls13_ConstructClientHelloWithEch(sslSocket *ss, const sslSessionID *sid,
+ PRBool freshSid, sslBuffer *chOuterBuf,
+ sslBuffer *chInnerXtnsBuf);
+SECStatus tls13_CopyEchConfigs(PRCList *oconfigs, PRCList *configs);
+SECStatus tls13_DecodeEchConfigs(const SECItem *data, PRCList *configs);
+void tls13_DestroyEchConfigs(PRCList *list);
+SECStatus tls13_GetMatchingEchConfig(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead,
+ const SECItem *configId, sslEchConfig **cfg);
+SECStatus tls13_MaybeHandleEch(sslSocket *ss, const PRUint8 *msg, PRUint32 msgLen, SECItem *sidBytes,
+ SECItem *comps, SECItem *cookieBytes, SECItem *suites, SECItem **echInner);
+SECStatus tls13_MaybeHandleEchSignal(sslSocket *ss);
+SECStatus tls13_MaybeAcceptEch(sslSocket *ss, const SECItem *sidBytes, const PRUint8 *chOuter,
+ unsigned int chOuterLen, SECItem **chInner);
+SECStatus tls13_MaybeGreaseEch(sslSocket *ss, unsigned int prefixLen, sslBuffer *buf);
+SECStatus tls13_WriteServerEchSignal(sslSocket *ss);
+
+#endif
diff --git a/lib/ssl/tls13esni.c b/lib/ssl/tls13esni.c
deleted file mode 100644
index 7182f22af..000000000
--- a/lib/ssl/tls13esni.c
+++ /dev/null
@@ -1,846 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#define TLS13_ESNI_VERSION 0xff01
-
-/*
- * struct {
- * uint16 version;
- * uint8 checksum[4];
- * KeyShareEntry keys<4..2^16-1>;
- * CipherSuite cipher_suites<2..2^16-2>;
- * uint16 padded_length;
- * uint64 not_before;
- * uint64 not_after;
- * Extension extensions<0..2^16-1>;
- * } ESNIKeys;
- */
-#include "nss.h"
-#include "pk11func.h"
-#include "ssl.h"
-#include "sslproto.h"
-#include "sslimpl.h"
-#include "ssl3exthandle.h"
-#include "tls13esni.h"
-#include "tls13exthandle.h"
-#include "tls13hkdf.h"
-
-const char kHkdfPurposeEsniKey[] = "esni key";
-const char kHkdfPurposeEsniIv[] = "esni iv";
-
-void
-tls13_DestroyESNIKeys(sslEsniKeys *keys)
-{
- if (!keys) {
- return;
- }
- SECITEM_FreeItem(&keys->data, PR_FALSE);
- PORT_Free((void *)keys->dummySni);
- tls13_DestroyKeyShares(&keys->keyShares);
- ssl_FreeEphemeralKeyPair(keys->privKey);
- SECITEM_FreeItem(&keys->suites, PR_FALSE);
- PORT_ZFree(keys, sizeof(sslEsniKeys));
-}
-
-sslEsniKeys *
-tls13_CopyESNIKeys(sslEsniKeys *okeys)
-{
- sslEsniKeys *nkeys;
- SECStatus rv;
-
- PORT_Assert(okeys);
-
- nkeys = PORT_ZNew(sslEsniKeys);
- if (!nkeys) {
- return NULL;
- }
- PR_INIT_CLIST(&nkeys->keyShares);
- rv = SECITEM_CopyItem(NULL, &nkeys->data, &okeys->data);
- if (rv != SECSuccess) {
- goto loser;
- }
- if (okeys->dummySni) {
- nkeys->dummySni = PORT_Strdup(okeys->dummySni);
- if (!nkeys->dummySni) {
- goto loser;
- }
- }
- for (PRCList *cur_p = PR_LIST_HEAD(&okeys->keyShares);
- cur_p != &okeys->keyShares;
- cur_p = PR_NEXT_LINK(cur_p)) {
- TLS13KeyShareEntry *copy = tls13_CopyKeyShareEntry(
- (TLS13KeyShareEntry *)cur_p);
- if (!copy) {
- goto loser;
- }
- PR_APPEND_LINK(&copy->link, &nkeys->keyShares);
- }
- if (okeys->privKey) {
- nkeys->privKey = ssl_CopyEphemeralKeyPair(okeys->privKey);
- if (!nkeys->privKey) {
- goto loser;
- }
- }
- rv = SECITEM_CopyItem(NULL, &nkeys->suites, &okeys->suites);
- if (rv != SECSuccess) {
- goto loser;
- }
- nkeys->paddedLength = okeys->paddedLength;
- nkeys->notBefore = okeys->notBefore;
- nkeys->notAfter = okeys->notAfter;
- return nkeys;
-
-loser:
- tls13_DestroyESNIKeys(nkeys);
- return NULL;
-}
-
-/* Checksum is a 4-byte array. */
-static SECStatus
-tls13_ComputeESNIKeysChecksum(const PRUint8 *buf, unsigned int len,
- PRUint8 *checksum)
-{
- SECItem copy;
- SECStatus rv;
- PRUint8 sha256[32];
-
- rv = SECITEM_MakeItem(NULL, &copy, buf, len);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- /* Stomp the checksum. */
- PORT_Memset(copy.data + 2, 0, 4);
-
- rv = PK11_HashBuf(ssl3_HashTypeToOID(ssl_hash_sha256),
- sha256,
- copy.data, copy.len);
- SECITEM_FreeItem(&copy, PR_FALSE);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- PORT_Memcpy(checksum, sha256, 4);
- return SECSuccess;
-}
-
-static SECStatus
-tls13_DecodeESNIKeys(SECItem *data, sslEsniKeys **keysp)
-{
- SECStatus rv;
- sslReadBuffer tmp;
- PRUint64 tmpn;
- sslEsniKeys *keys;
- PRUint8 checksum[4];
- sslReader rdr = SSL_READER(data->data, data->len);
-
- rv = sslRead_ReadNumber(&rdr, 2, &tmpn);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- if (tmpn != TLS13_ESNI_VERSION) {
- PORT_SetError(SSL_ERROR_UNSUPPORTED_VERSION);
- return SECFailure;
- }
- keys = PORT_ZNew(sslEsniKeys);
- if (!keys) {
- return SECFailure;
- }
- PR_INIT_CLIST(&keys->keyShares);
-
- /* Make a copy. */
- rv = SECITEM_CopyItem(NULL, &keys->data, data);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- rv = tls13_ComputeESNIKeysChecksum(data->data, data->len, checksum);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Read and check checksum. */
- rv = sslRead_Read(&rdr, 4, &tmp);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- if (0 != NSS_SecureMemcmp(tmp.buf, checksum, 4)) {
- goto loser;
- }
-
- /* Parse the key shares. */
- rv = sslRead_ReadVariable(&rdr, 2, &tmp);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- sslReader rdr2 = SSL_READER(tmp.buf, tmp.len);
- while (SSL_READER_REMAINING(&rdr2)) {
- TLS13KeyShareEntry *ks = NULL;
-
- rv = tls13_DecodeKeyShareEntry(&rdr2, &ks);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- if (ks) {
- PR_APPEND_LINK(&ks->link, &keys->keyShares);
- }
- }
-
- /* Parse cipher suites. */
- rv = sslRead_ReadVariable(&rdr, 2, &tmp);
- if (rv != SECSuccess) {
- goto loser;
- }
- /* This can't be odd. */
- if (tmp.len & 1) {
- goto loser;
- }
- rv = SECITEM_MakeItem(NULL, &keys->suites, (PRUint8 *)tmp.buf, tmp.len);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Padded Length */
- rv = sslRead_ReadNumber(&rdr, 2, &tmpn);
- if (rv != SECSuccess) {
- goto loser;
- }
- keys->paddedLength = (PRUint16)tmpn;
-
- /* Not Before */
- rv = sslRead_ReadNumber(&rdr, 8, &keys->notBefore);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Not After */
- rv = sslRead_ReadNumber(&rdr, 8, &keys->notAfter);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Extensions, which we ignore. */
- rv = sslRead_ReadVariable(&rdr, 2, &tmp);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Check that this is empty. */
- if (SSL_READER_REMAINING(&rdr) > 0) {
- goto loser;
- }
-
- *keysp = keys;
- return SECSuccess;
-
-loser:
- tls13_DestroyESNIKeys(keys);
- PORT_SetError(SSL_ERROR_RX_MALFORMED_ESNI_KEYS);
-
- return SECFailure;
-}
-
-/* Encode an ESNI keys structure. We only allow one key
- * share. */
-SECStatus
-SSLExp_EncodeESNIKeys(PRUint16 *cipherSuites, unsigned int cipherSuiteCount,
- SSLNamedGroup group, SECKEYPublicKey *pubKey,
- PRUint16 pad, PRUint64 notBefore, PRUint64 notAfter,
- PRUint8 *out, unsigned int *outlen, unsigned int maxlen)
-{
- unsigned int savedOffset;
- SECStatus rv;
- sslBuffer b = SSL_BUFFER_EMPTY;
-
- rv = sslBuffer_AppendNumber(&b, TLS13_ESNI_VERSION, 2);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- rv = sslBuffer_Skip(&b, 4, &savedOffset);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Length of vector. */
- rv = sslBuffer_AppendNumber(
- &b, tls13_SizeOfKeyShareEntry(pubKey), 2);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Our one key share. */
- rv = tls13_EncodeKeyShareEntry(&b, group, pubKey);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Cipher suites. */
- rv = sslBuffer_AppendNumber(&b, cipherSuiteCount * 2, 2);
- if (rv != SECSuccess) {
- goto loser;
- }
- for (unsigned int i = 0; i < cipherSuiteCount; i++) {
- rv = sslBuffer_AppendNumber(&b, cipherSuites[i], 2);
- if (rv != SECSuccess) {
- goto loser;
- }
- }
-
- /* Padding Length. Fixed for now. */
- rv = sslBuffer_AppendNumber(&b, pad, 2);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Start time. */
- rv = sslBuffer_AppendNumber(&b, notBefore, 8);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* End time. */
- rv = sslBuffer_AppendNumber(&b, notAfter, 8);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* No extensions. */
- rv = sslBuffer_AppendNumber(&b, 0, 2);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- rv = tls13_ComputeESNIKeysChecksum(SSL_BUFFER_BASE(&b),
- SSL_BUFFER_LEN(&b),
- SSL_BUFFER_BASE(&b) + 2);
- if (rv != SECSuccess) {
- PORT_Assert(PR_FALSE);
- goto loser;
- }
-
- if (SSL_BUFFER_LEN(&b) > maxlen) {
- PORT_SetError(SEC_ERROR_INVALID_ARGS);
- goto loser;
- }
- PORT_Memcpy(out, SSL_BUFFER_BASE(&b), SSL_BUFFER_LEN(&b));
- *outlen = SSL_BUFFER_LEN(&b);
-
- sslBuffer_Clear(&b);
- return SECSuccess;
-loser:
- sslBuffer_Clear(&b);
- return SECFailure;
-}
-
-SECStatus
-SSLExp_SetESNIKeyPair(PRFileDesc *fd,
- SECKEYPrivateKey *privKey,
- const PRUint8 *record, unsigned int recordLen)
-{
- sslSocket *ss;
- SECStatus rv;
- sslEsniKeys *keys = NULL;
- SECKEYPublicKey *pubKey = NULL;
- SECItem data = { siBuffer, CONST_CAST(PRUint8, record), recordLen };
- PLArenaPool *arena = NULL;
-
- ss = ssl_FindSocket(fd);
- if (!ss) {
- SSL_DBG(("%d: SSL[%d]: bad socket in %s",
- SSL_GETPID(), fd, __FUNCTION__));
- return SECFailure;
- }
-
- rv = tls13_DecodeESNIKeys(&data, &keys);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- /* Check the cipher suites. */
- (void)ssl3_config_match_init(ss);
- /* Make sure the cipher suite is OK. */
- SSLVersionRange vrange = { SSL_LIBRARY_VERSION_TLS_1_3,
- SSL_LIBRARY_VERSION_TLS_1_3 };
-
- sslReader csrdr = SSL_READER(keys->suites.data,
- keys->suites.len);
- while (SSL_READER_REMAINING(&csrdr)) {
- PRUint64 asuite;
-
- rv = sslRead_ReadNumber(&csrdr, 2, &asuite);
- if (rv != SECSuccess) {
- goto loser;
- }
- const ssl3CipherSuiteCfg *suiteCfg =
- ssl_LookupCipherSuiteCfg(asuite, ss->cipherSuites);
- if (!ssl3_config_match(suiteCfg, ss->ssl3.policy, &vrange, ss)) {
- /* Illegal suite. */
- PORT_SetError(SEC_ERROR_INVALID_ARGS);
- goto loser;
- }
- }
-
- if (PR_CLIST_IS_EMPTY(&keys->keyShares)) {
- PORT_SetError(SEC_ERROR_INVALID_ARGS);
- goto loser;
- }
- if (PR_PREV_LINK(&keys->keyShares) != PR_NEXT_LINK(&keys->keyShares)) {
- PORT_SetError(SEC_ERROR_INVALID_ARGS);
- goto loser;
- }
- TLS13KeyShareEntry *entry = (TLS13KeyShareEntry *)PR_LIST_HEAD(
- &keys->keyShares);
- if (entry->group->keaType != ssl_kea_ecdh) {
- PORT_SetError(SEC_ERROR_INVALID_ARGS);
- goto loser;
- }
- arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE);
- if (!arena) {
- goto loser;
- }
- pubKey = PORT_ArenaZNew(arena, SECKEYPublicKey);
- if (!pubKey) {
- goto loser;
- }
- pubKey->arena = arena;
- arena = NULL; /* From here, this will be destroyed with the pubkey. */
- /* Dummy PKCS11 values because this key isn't on a slot. */
- pubKey->pkcs11Slot = NULL;
- pubKey->pkcs11ID = CK_INVALID_HANDLE;
- rv = ssl_ImportECDHKeyShare(pubKey,
- entry->key_exchange.data,
- entry->key_exchange.len,
- entry->group);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- privKey = SECKEY_CopyPrivateKey(privKey);
- if (!privKey) {
- goto loser;
- }
- keys->privKey = ssl_NewEphemeralKeyPair(entry->group, privKey, pubKey);
- if (!keys->privKey) {
- goto loser;
- }
- pubKey = NULL;
- ss->esniKeys = keys;
- return SECSuccess;
-
-loser:
- PORT_FreeArena(arena, PR_FALSE);
- SECKEY_DestroyPublicKey(pubKey);
- tls13_DestroyESNIKeys(keys);
- return SECFailure;
-}
-
-SECStatus
-SSLExp_EnableESNI(PRFileDesc *fd,
- const PRUint8 *esniKeys,
- unsigned int esniKeysLen,
- const char *dummySNI)
-{
- sslSocket *ss;
- sslEsniKeys *keys = NULL;
- SECItem data = { siBuffer, CONST_CAST(PRUint8, esniKeys), esniKeysLen };
- SECStatus rv;
-
- ss = ssl_FindSocket(fd);
- if (!ss) {
- SSL_DBG(("%d: SSL[%d]: bad socket in %s",
- SSL_GETPID(), fd, __FUNCTION__));
- return SECFailure;
- }
-
- rv = tls13_DecodeESNIKeys(&data, &keys);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- if (dummySNI) {
- keys->dummySni = PORT_Strdup(dummySNI);
- if (!keys->dummySni) {
- tls13_DestroyESNIKeys(keys);
- return SECFailure;
- }
- }
-
- /* Delete in case it was set before. */
- tls13_DestroyESNIKeys(ss->esniKeys);
- ss->esniKeys = keys;
-
- return SECSuccess;
-}
-
-/*
- * struct {
- * opaque record_digest<0..2^16-1>;
- * KeyShareEntry esni_key_share;
- * Random client_hello_random;
- * } ESNIContents;
- */
-SECStatus
-tls13_ComputeESNIKeys(const sslSocket *ss,
- TLS13KeyShareEntry *entry,
- sslKeyPair *keyPair,
- const ssl3CipherSuiteDef *suite,
- const PRUint8 *esniKeysHash,
- const PRUint8 *keyShareBuf,
- unsigned int keyShareBufLen,
- const PRUint8 *clientRandom,
- ssl3KeyMaterial *keyMat)
-{
- PK11SymKey *Z = NULL;
- PK11SymKey *Zx = NULL;
- SECStatus ret = SECFailure;
- PRUint8 esniContentsBuf[256]; /* Just big enough. */
- sslBuffer esniContents = SSL_BUFFER(esniContentsBuf);
- PRUint8 hash[64];
- const ssl3BulkCipherDef *cipherDef = ssl_GetBulkCipherDef(suite);
- size_t keySize = cipherDef->key_size;
- size_t ivSize = cipherDef->iv_size +
- cipherDef->explicit_nonce_size; /* This isn't always going to
- * work, but it does for
- * AES-GCM */
- unsigned int hashSize = tls13_GetHashSizeForHash(suite->prf_hash);
- SECStatus rv;
-
- rv = tls13_HandleKeyShare(CONST_CAST(sslSocket, ss), entry, keyPair,
- suite->prf_hash, &Z);
- if (rv != SECSuccess) {
- goto loser;
- }
- rv = tls13_HkdfExtract(NULL, Z, suite->prf_hash, &Zx);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Encode ESNIContents. */
- rv = sslBuffer_AppendVariable(&esniContents,
- esniKeysHash, hashSize, 2);
- if (rv != SECSuccess) {
- goto loser;
- }
- rv = sslBuffer_Append(&esniContents, keyShareBuf, keyShareBufLen);
- if (rv != SECSuccess) {
- goto loser;
- }
- rv = sslBuffer_Append(&esniContents, clientRandom, SSL3_RANDOM_LENGTH);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- PORT_Assert(hashSize <= sizeof(hash));
- rv = PK11_HashBuf(ssl3_HashTypeToOID(suite->prf_hash),
- hash,
- SSL_BUFFER_BASE(&esniContents),
- SSL_BUFFER_LEN(&esniContents));
- ;
- if (rv != SECSuccess) {
- goto loser;
- }
-
- rv = tls13_HkdfExpandLabel(Zx, suite->prf_hash,
- hash, hashSize,
- kHkdfPurposeEsniKey, strlen(kHkdfPurposeEsniKey),
- ssl3_Alg2Mech(cipherDef->calg),
- keySize, ss->protocolVariant,
- &keyMat->key);
- if (rv != SECSuccess) {
- goto loser;
- }
- rv = tls13_HkdfExpandLabelRaw(Zx, suite->prf_hash,
- hash, hashSize,
- kHkdfPurposeEsniIv, strlen(kHkdfPurposeEsniIv),
- ss->protocolVariant, keyMat->iv, ivSize);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- ret = SECSuccess;
-
-loser:
- PK11_FreeSymKey(Z);
- PK11_FreeSymKey(Zx);
- return ret;
-}
-
-/* Set up ESNI. This generates a private key as a side effect. */
-SECStatus
-tls13_ClientSetupESNI(sslSocket *ss)
-{
- ssl3CipherSuite suite;
- sslEphemeralKeyPair *keyPair;
- size_t i;
- PRCList *cur;
- SECStatus rv;
- TLS13KeyShareEntry *share = NULL;
- const sslNamedGroupDef *group = NULL;
- PRTime now = ssl_Time(ss) / PR_USEC_PER_SEC;
-
- PORT_Assert(!ss->xtnData.esniPrivateKey);
-
- if (!ss->esniKeys) {
- return SECSuccess;
- }
-
- if ((ss->esniKeys->notBefore > now) || (ss->esniKeys->notAfter < now)) {
- return SECSuccess;
- }
-
- /* If we're not sending SNI, don't send ESNI. */
- if (!ssl_ShouldSendSNIExtension(ss, ss->url)) {
- return SECSuccess;
- }
-
- /* Pick the group. */
- for (i = 0; i < SSL_NAMED_GROUP_COUNT; ++i) {
- for (cur = PR_NEXT_LINK(&ss->esniKeys->keyShares);
- cur != &ss->esniKeys->keyShares;
- cur = PR_NEXT_LINK(cur)) {
- if (!ss->namedGroupPreferences[i]) {
- continue;
- }
- share = (TLS13KeyShareEntry *)cur;
- if (share->group->name == ss->namedGroupPreferences[i]->name) {
- group = ss->namedGroupPreferences[i];
- break;
- }
- }
- }
-
- if (!group) {
- /* No compatible group. */
- return SECSuccess;
- }
-
- rv = ssl3_NegotiateCipherSuiteInner(ss, &ss->esniKeys->suites,
- SSL_LIBRARY_VERSION_TLS_1_3, &suite);
- if (rv != SECSuccess) {
- return SECSuccess;
- }
-
- rv = tls13_CreateKeyShare(ss, group, &keyPair);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- ss->xtnData.esniPrivateKey = keyPair;
- ss->xtnData.esniSuite = suite;
- ss->xtnData.peerEsniShare = share;
-
- return SECSuccess;
-}
-
-/*
- * struct {
- * CipherSuite suite;
- * KeyShareEntry key_share;
- * opaque record_digest<0..2^16-1>;
- * opaque encrypted_sni<0..2^16-1>;
- * } ClientEncryptedSNI;
- *
- * struct {
- * ServerNameList sni;
- * opaque zeros[ESNIKeys.padded_length - length(sni)];
- * } PaddedServerNameList;
- *
- * struct {
- * uint8 nonce[16];
- * PaddedServerNameList realSNI;
- * } ClientESNIInner;
- */
-SECStatus
-tls13_FormatEsniAADInput(sslBuffer *aadInput,
- PRUint8 *keyShare, unsigned int keyShareLen)
-{
- SECStatus rv;
-
- /* Key share. */
- PORT_Assert(keyShareLen > 0);
- rv = sslBuffer_Append(aadInput, keyShare, keyShareLen);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- return SECSuccess;
-}
-
-static SECStatus
-tls13_ServerGetEsniAEAD(const sslSocket *ss, PRUint64 suite,
- const ssl3CipherSuiteDef **suiteDefp)
-{
- SECStatus rv;
- const ssl3CipherSuiteDef *suiteDef;
-
- /* Check against the suite list for ESNI */
- PRBool csMatch = PR_FALSE;
- sslReader csrdr = SSL_READER(ss->esniKeys->suites.data,
- ss->esniKeys->suites.len);
- while (SSL_READER_REMAINING(&csrdr)) {
- PRUint64 asuite;
-
- rv = sslRead_ReadNumber(&csrdr, 2, &asuite);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- if (asuite == suite) {
- csMatch = PR_TRUE;
- break;
- }
- }
- if (!csMatch) {
- return SECFailure;
- }
-
- suiteDef = ssl_LookupCipherSuiteDef(suite);
- PORT_Assert(suiteDef);
- if (!suiteDef) {
- return SECFailure;
- }
-
- *suiteDefp = suiteDef;
- return SECSuccess;
-}
-
-SECStatus
-tls13_ServerDecryptEsniXtn(const sslSocket *ss, const PRUint8 *in, unsigned int inLen,
- PRUint8 *out, unsigned int *outLen, unsigned int maxLen)
-{
- sslReader rdr = SSL_READER(in, inLen);
- PRUint64 suite;
- const ssl3CipherSuiteDef *suiteDef = NULL;
- TLSExtension *keyShareExtension;
- TLS13KeyShareEntry *entry = NULL;
- ssl3KeyMaterial keyMat = { NULL };
-
- sslBuffer aadInput = SSL_BUFFER_EMPTY;
- const PRUint8 *keyShareBuf;
- sslReadBuffer buf;
- unsigned int keyShareBufLen;
- PRUint8 hash[64];
- SECStatus rv;
-
- /* Read the cipher suite. */
- rv = sslRead_ReadNumber(&rdr, 2, &suite);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Find the AEAD */
- rv = tls13_ServerGetEsniAEAD(ss, suite, &suiteDef);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Note where the KeyShare starts. */
- keyShareBuf = SSL_READER_CURRENT(&rdr);
- rv = tls13_DecodeKeyShareEntry(&rdr, &entry);
- if (rv != SECSuccess) {
- goto loser;
- }
- keyShareBufLen = SSL_READER_CURRENT(&rdr) - keyShareBuf;
- if (!entry || entry->group->name != ss->esniKeys->privKey->group->name) {
- goto loser;
- }
-
- /* The hash of the ESNIKeys structure. */
- rv = sslRead_ReadVariable(&rdr, 2, &buf);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Check that the hash matches. */
- unsigned int hashLen = tls13_GetHashSizeForHash(suiteDef->prf_hash);
- PORT_Assert(hashLen <= sizeof(hash));
- rv = PK11_HashBuf(ssl3_HashTypeToOID(suiteDef->prf_hash),
- hash,
- ss->esniKeys->data.data, ss->esniKeys->data.len);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- if (buf.len != hashLen) {
- /* This is malformed. */
- goto loser;
- }
- if (0 != NSS_SecureMemcmp(hash, buf.buf, hashLen)) {
- goto loser;
- }
-
- rv = tls13_ComputeESNIKeys(ss, entry,
- ss->esniKeys->privKey->keys,
- suiteDef,
- hash, keyShareBuf, keyShareBufLen,
- ((sslSocket *)ss)->ssl3.hs.client_random,
- &keyMat);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Read the ciphertext. */
- rv = sslRead_ReadVariable(&rdr, 2, &buf);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Check that this is empty. */
- if (SSL_READER_REMAINING(&rdr) > 0) {
- goto loser;
- }
-
- /* Find the key share extension. */
- keyShareExtension = ssl3_FindExtension(CONST_CAST(sslSocket, ss),
- ssl_tls13_key_share_xtn);
- if (!keyShareExtension) {
- goto loser;
- }
- rv = tls13_FormatEsniAADInput(&aadInput,
- keyShareExtension->data.data,
- keyShareExtension->data.len);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- const ssl3BulkCipherDef *cipher_def = ssl_GetBulkCipherDef(suiteDef);
- unsigned char *aad = SSL_BUFFER_BASE(&aadInput);
- int aadLen = SSL_BUFFER_LEN(&aadInput);
- int ivLen = cipher_def->iv_size + cipher_def->explicit_nonce_size;
- SSLCipherAlgorithm calg = cipher_def->calg;
- unsigned char zero[sizeof(sslSequenceNumber)] = { 0 };
- SECItem null_params = { siBuffer, NULL, 0 };
- PK11Context *ctxt = PK11_CreateContextBySymKey(ssl3_Alg2Mech(calg),
- CKA_NSS_MESSAGE | CKA_DECRYPT,
- keyMat.key, &null_params);
- if (!ctxt) {
- sslBuffer_Clear(&aadInput);
- goto loser;
- }
-
- rv = tls13_AEAD(ctxt, PR_TRUE /* Decrypt */, CKG_NO_GENERATE, 0,
- keyMat.iv, NULL, ivLen, zero, sizeof(zero), aad, aadLen,
- out, outLen, maxLen, cipher_def->tag_size, buf.buf, buf.len);
- PK11_DestroyContext(ctxt, PR_TRUE);
- sslBuffer_Clear(&aadInput);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- ssl_DestroyKeyMaterial(&keyMat);
- tls13_DestroyKeyShareEntry(entry);
- return SECSuccess;
-
-loser:
- FATAL_ERROR(CONST_CAST(sslSocket, ss), SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter);
- ssl_DestroyKeyMaterial(&keyMat); /* Safe because zeroed. */
- if (entry) {
- tls13_DestroyKeyShareEntry(entry);
- }
- return SECFailure;
-}
diff --git a/lib/ssl/tls13esni.h b/lib/ssl/tls13esni.h
deleted file mode 100644
index 0fb12d25e..000000000
--- a/lib/ssl/tls13esni.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This file is PRIVATE to SSL.
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef __tls13esni_h_
-#define __tls13esni_h_
-
-struct sslEsniKeysStr {
- SECItem data; /* The encoded record. */
- sslEphemeralKeyPair *privKey;
- const char *dummySni;
- PRCList keyShares; /* List of TLS13KeyShareEntry */
- SECItem suites;
- PRUint16 paddedLength;
- PRUint64 notBefore;
- PRUint64 notAfter;
-};
-
-SECStatus SSLExp_SetESNIKeyPair(PRFileDesc *fd,
- SECKEYPrivateKey *privKey,
- const PRUint8 *record, unsigned int recordLen);
-
-SECStatus SSLExp_EnableESNI(PRFileDesc *fd, const PRUint8 *esniKeys,
- unsigned int esniKeysLen, const char *dummySNI);
-SECStatus SSLExp_EncodeESNIKeys(PRUint16 *cipherSuites, unsigned int cipherSuiteCount,
- SSLNamedGroup group, SECKEYPublicKey *pubKey,
- PRUint16 pad, PRUint64 notBefore, PRUint64 notAfter,
- PRUint8 *out, unsigned int *outlen, unsigned int maxlen);
-sslEsniKeys *tls13_CopyESNIKeys(sslEsniKeys *okeys);
-void tls13_DestroyESNIKeys(sslEsniKeys *keys);
-SECStatus tls13_ClientSetupESNI(sslSocket *ss);
-SECStatus tls13_ComputeESNIKeys(const sslSocket *ss,
- TLS13KeyShareEntry *entry,
- sslKeyPair *keyPair,
- const ssl3CipherSuiteDef *suite,
- const PRUint8 *esniKeysHash,
- const PRUint8 *keyShareBuf,
- unsigned int keyShareBufLen,
- const PRUint8 *clientRandom,
- ssl3KeyMaterial *keyMat);
-SECStatus tls13_FormatEsniAADInput(sslBuffer *aadInput,
- PRUint8 *keyShare, unsigned int keyShareLen);
-
-SECStatus tls13_ServerDecryptEsniXtn(const sslSocket *ss, const PRUint8 *in, unsigned int inLen,
- PRUint8 *out, unsigned int *outLen, unsigned int maxLen);
-
-#endif
diff --git a/lib/ssl/tls13exthandle.c b/lib/ssl/tls13exthandle.c
index 5c3930ac2..54a4abb09 100644
--- a/lib/ssl/tls13exthandle.c
+++ b/lib/ssl/tls13exthandle.c
@@ -12,7 +12,7 @@
#include "pk11pub.h"
#include "ssl3ext.h"
#include "ssl3exthandle.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
#include "tls13exthandle.h"
#include "tls13psk.h"
#include "tls13subcerts.h"
@@ -126,7 +126,6 @@ tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData,
{
SECStatus rv;
PRCList *cursor;
- unsigned int extStart;
unsigned int lengthOffset;
if (ss->vrange.max < SSL_LIBRARY_VERSION_TLS_1_3) {
@@ -138,8 +137,6 @@ tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData,
SSL_TRC(3, ("%d: TLS13[%d]: send client key share xtn",
SSL_GETPID(), ss->fd));
- extStart = SSL_BUFFER_LEN(buf);
-
/* Save the offset to the length. */
rv = sslBuffer_Skip(buf, 2, &lengthOffset);
if (rv != SECSuccess) {
@@ -162,13 +159,6 @@ tls13_ClientSendKeyShareXtn(const sslSocket *ss, TLSExtensionData *xtnData,
return SECFailure;
}
- rv = SECITEM_MakeItem(NULL, &xtnData->keyShareExtension,
- SSL_BUFFER_BASE(buf) + extStart,
- SSL_BUFFER_LEN(buf) - extStart);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
*added = PR_TRUE;
return SECSuccess;
}
@@ -849,6 +839,7 @@ tls13_ClientSendSupportedVersionsXtn(const sslSocket *ss, TLSExtensionData *xtnD
return SECFailure;
}
+ PORT_Assert(!ss->ssl3.hs.echHpkeCtx || ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_3);
for (version = ss->vrange.max; version >= ss->vrange.min; --version) {
PRUint16 wire = tls13_EncodeVersion(version,
ss->protocolVariant);
@@ -1213,289 +1204,30 @@ tls13_ServerSendHrrCookieXtn(const sslSocket *ss, TLSExtensionData *xtnData,
}
SECStatus
-tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
- sslBuffer *buf, PRBool *added)
-{
- SECStatus rv;
- PRUint8 sniBuf[1024];
- PRUint8 hash[64];
- sslBuffer sni = SSL_BUFFER(sniBuf);
- const ssl3CipherSuiteDef *suiteDef;
- ssl3KeyMaterial keyMat;
- PRUint8 outBuf[1024];
- unsigned int outLen;
- unsigned int sniStart;
- unsigned int sniLen;
- sslBuffer aadInput = SSL_BUFFER_EMPTY;
- unsigned int keyShareBufStart;
- unsigned int keyShareBufLen;
-
- PORT_Memset(&keyMat, 0, sizeof(keyMat));
-
- if (!ss->xtnData.esniPrivateKey) {
- return SECSuccess;
- }
-
- /* nonce */
- rv = PK11_GenerateRandom(
- (unsigned char *)xtnData->esniNonce, sizeof(xtnData->esniNonce));
- if (rv != SECSuccess) {
- return SECFailure;
- }
- rv = sslBuffer_Append(&sni, xtnData->esniNonce, sizeof(xtnData->esniNonce));
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- /* sni */
- sniStart = SSL_BUFFER_LEN(&sni);
- rv = ssl3_ClientFormatServerNameXtn(ss, ss->url, xtnData, &sni);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- sniLen = SSL_BUFFER_LEN(&sni) - sniStart;
- /* Padding. */
- if (ss->esniKeys->paddedLength > sniLen) {
- unsigned int paddingRequired = ss->esniKeys->paddedLength - sniLen;
- while (paddingRequired--) {
- rv = sslBuffer_AppendNumber(&sni, 0, 1);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- }
- }
-
- suiteDef = ssl_LookupCipherSuiteDef(xtnData->esniSuite);
- PORT_Assert(suiteDef);
- if (!suiteDef) {
- PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
- return SECFailure;
- }
-
- /* Format the first part of the extension so we have the
- * encoded KeyShareEntry. */
- rv = sslBuffer_AppendNumber(buf, xtnData->esniSuite, 2);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- keyShareBufStart = SSL_BUFFER_LEN(buf);
- rv = tls13_EncodeKeyShareEntry(buf,
- xtnData->esniPrivateKey->group->name,
- xtnData->esniPrivateKey->keys->pubKey);
- if (rv != SECSuccess) {
- return SECFailure;
- }
- keyShareBufLen = SSL_BUFFER_LEN(buf) - keyShareBufStart;
-
- if (tls13_GetHashSizeForHash(suiteDef->prf_hash) > sizeof(hash)) {
- PORT_Assert(PR_FALSE);
- PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
- return SECFailure;
- }
-
- rv = PK11_HashBuf(ssl3_HashTypeToOID(suiteDef->prf_hash),
- hash,
- ss->esniKeys->data.data,
- ss->esniKeys->data.len);
- if (rv != SECSuccess) {
- PORT_Assert(PR_FALSE);
- return SECFailure;
- }
-
- rv = sslBuffer_AppendVariable(buf, hash,
- tls13_GetHashSizeForHash(suiteDef->prf_hash), 2);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- /* Compute the ESNI keys. */
- rv = tls13_ComputeESNIKeys(ss, xtnData->peerEsniShare,
- xtnData->esniPrivateKey->keys,
- suiteDef,
- hash,
- SSL_BUFFER_BASE(buf) + keyShareBufStart,
- keyShareBufLen,
- CONST_CAST(PRUint8, ss->ssl3.hs.client_random),
- &keyMat);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- rv = tls13_FormatEsniAADInput(&aadInput,
- xtnData->keyShareExtension.data,
- xtnData->keyShareExtension.len);
- if (rv != SECSuccess) {
- ssl_DestroyKeyMaterial(&keyMat);
- return SECFailure;
- }
-
- /* Now encrypt. */
- unsigned char *aad = SSL_BUFFER_BASE(&aadInput);
- int aadLen = SSL_BUFFER_LEN(&aadInput);
- const ssl3BulkCipherDef *cipher_def = ssl_GetBulkCipherDef(suiteDef);
- int ivLen = cipher_def->iv_size + cipher_def->explicit_nonce_size;
- unsigned char zero[sizeof(sslSequenceNumber)] = { 0 };
- SSLCipherAlgorithm calg = cipher_def->calg;
- SECItem null_params = { siBuffer, NULL, 0 };
- PK11Context *ctxt = PK11_CreateContextBySymKey(ssl3_Alg2Mech(calg),
- CKA_NSS_MESSAGE | CKA_ENCRYPT,
- keyMat.key, &null_params);
- if (!ctxt) {
- ssl_DestroyKeyMaterial(&keyMat);
- sslBuffer_Clear(&aadInput);
- return SECFailure;
- }
-
- /* This function is a single shot, with fresh/unique keys, no need to
- * generate the IV internally */
- rv = tls13_AEAD(ctxt, PR_FALSE /* Encrypt */, CKG_NO_GENERATE, 0,
- keyMat.iv, NULL, ivLen, zero, sizeof(zero), aad, aadLen,
- outBuf, &outLen, sizeof(outBuf), cipher_def->tag_size,
- SSL_BUFFER_BASE(&sni), SSL_BUFFER_LEN(&sni));
- ssl_DestroyKeyMaterial(&keyMat);
- sslBuffer_Clear(&aadInput);
- PK11_DestroyContext(ctxt, PR_TRUE);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- /* Encode the rest. */
- rv = sslBuffer_AppendVariable(buf, outBuf, outLen, 2);
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- *added = PR_TRUE;
- return SECSuccess;
-}
-
-static SECStatus
-tls13_ServerSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
- sslBuffer *buf, PRBool *added)
-{
- SECStatus rv;
-
- rv = sslBuffer_Append(buf, xtnData->esniNonce, sizeof(xtnData->esniNonce));
- if (rv != SECSuccess) {
- return SECFailure;
- }
-
- *added = PR_TRUE;
- return SECSuccess;
-}
-
-SECStatus
-tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
- SECItem *data)
+tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ SECItem *data)
{
- sslReadBuffer buf;
- PRUint8 *plainText = NULL;
- unsigned int ptLen;
SECStatus rv;
+ PRCList parsedConfigs;
+ PR_INIT_CLIST(&parsedConfigs);
- /* If we are doing < TLS 1.3, then ignore this. */
- if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3) {
- return SECSuccess;
- }
-
- if (!ss->esniKeys) {
- /* Apparently we used to be configured for ESNI, but
- * no longer. This violates the spec, or the client is
- * broken. */
- return SECFailure;
- }
-
- plainText = PORT_ZAlloc(data->len);
- if (!plainText) {
- return SECFailure;
- }
- rv = tls13_ServerDecryptEsniXtn(ss, data->data, data->len,
- plainText, &ptLen, data->len);
- if (rv) {
- goto loser;
- }
-
- /* Read out the interior extension. */
- sslReader sniRdr = SSL_READER(plainText, ptLen);
-
- rv = sslRead_Read(&sniRdr, sizeof(xtnData->esniNonce), &buf);
- if (rv != SECSuccess) {
- goto loser;
- }
- PORT_Memcpy(xtnData->esniNonce, buf.buf, sizeof(xtnData->esniNonce));
-
- /* We need to capture the whole block with the length. */
- SECItem sniItem = { siBuffer, (unsigned char *)SSL_READER_CURRENT(&sniRdr), 0 };
- rv = sslRead_ReadVariable(&sniRdr, 2, &buf);
- if (rv != SECSuccess) {
- goto loser;
- }
- sniItem.len = buf.len + 2;
-
- /* Check the padding. Note we don't need to do this in constant time
- * because it's inside the AEAD boundary. */
- /* TODO(ekr@rtfm.com): check that the padding is the right length. */
- PRUint64 tmp;
- while (SSL_READER_REMAINING(&sniRdr)) {
- rv = sslRead_ReadNumber(&sniRdr, 1, &tmp);
- if (rv != SECSuccess) {
- goto loser;
- }
- if (tmp != 0) {
- goto loser;
- }
- }
-
- rv = ssl3_HandleServerNameXtn(ss, xtnData, &sniItem);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- rv = ssl3_RegisterExtensionSender(ss, xtnData,
- ssl_tls13_encrypted_sni_xtn,
- tls13_ServerSendEsniXtn);
- if (rv != SECSuccess) {
- goto loser;
- }
-
- /* Keep track of negotiated extensions. */
- xtnData->negotiated[xtnData->numNegotiated++] =
- ssl_tls13_encrypted_sni_xtn;
-
- PORT_ZFree(plainText, data->len);
- return SECSuccess;
-loser:
- PORT_ZFree(plainText, data->len);
- return SECFailure;
-}
-
-/* Function to check the extension. We don't install a handler here
- * because we need to check for the presence of the extension as
- * well and it's easier to do it in one place. */
-SECStatus
-tls13_ClientCheckEsniXtn(sslSocket *ss)
-{
- TLSExtension *esniExtension =
- ssl3_FindExtension(ss, ssl_tls13_encrypted_sni_xtn);
- if (!esniExtension) {
- FATAL_ERROR(ss, SSL_ERROR_MISSING_ESNI_EXTENSION, missing_extension);
+ /* Parse the list to determine 1) That the configs are valid
+ * and properly encoded, and 2) If any are compatible. */
+ rv = tls13_DecodeEchConfigs(data, &parsedConfigs);
+ if (rv == SECFailure) {
+ ssl3_ExtSendAlert(ss, alert_fatal, decode_error);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
return SECFailure;
}
-
- if (esniExtension->data.len != sizeof(ss->xtnData.esniNonce)) {
- FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter);
- return SECFailure;
+ /* Don't mark ECH negotiated on retry. Save the the raw
+ * configs so the application can retry. If we sent GREASE
+ * ECH (no echHpkeCtx), don't apply returned retry_configs. */
+ if (ss->ssl3.hs.echHpkeCtx && !PR_CLIST_IS_EMPTY(&parsedConfigs)) {
+ rv = SECITEM_CopyItem(NULL, &xtnData->echRetryConfigs, data);
}
+ tls13_DestroyEchConfigs(&parsedConfigs);
- if (0 != NSS_SecureMemcmp(esniExtension->data.data,
- ss->xtnData.esniNonce,
- sizeof(ss->xtnData.esniNonce))) {
- FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ESNI_EXTENSION, illegal_parameter);
- return SECFailure;
- }
-
- return SECSuccess;
+ return rv;
}
/* Indicates support for the delegated credentials extension. This should be
@@ -1691,3 +1423,123 @@ tls13_ServerHandleDelegatedCredentialsXtn(const sslSocket *ss,
ss, xtnData, ssl_delegated_credentials_xtn,
tls13_ServerSendDelegatedCredentialsXtn);
}
+
+/* Adds the ECH extension containing server retry_configs */
+SECStatus
+tls13_ServerSendEchXtn(const sslSocket *ss,
+ TLSExtensionData *xtnData,
+ sslBuffer *buf, PRBool *added)
+{
+ SECStatus rv;
+ PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
+ if (PR_CLIST_IS_EMPTY(&ss->echConfigs)) {
+ return SECSuccess;
+ }
+
+ const sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
+ rv = sslBuffer_AppendVariable(buf, cfg->raw.data, cfg->raw.len, 2);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ *added = PR_TRUE;
+ return SECSuccess;
+}
+
+SECStatus
+tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ SECItem *data)
+{
+ SECStatus rv;
+ HpkeKdfId kdf;
+ HpkeAeadId aead;
+ PRUint32 tmp;
+ SECItem configId;
+ SECItem senderPubKey;
+ SECItem encryptedCh;
+
+ /* Ignore it if not doing 1.3+. If we have no ECHConfigs,
+ * proceed to save the config_id for HRR validation. */
+ if (ss->version < SSL_LIBRARY_VERSION_TLS_1_3 ||
+ IS_DTLS(ss)) {
+ return SECSuccess;
+ }
+
+ /* On CHInner, the extension must be empty. */
+ if (ss->ssl3.hs.echAccepted && data->len > 0) {
+ ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+ return SECFailure;
+ } else if (ss->ssl3.hs.echAccepted) {
+ xtnData->negotiated[xtnData->numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn;
+ return SECSuccess;
+ }
+
+ /* Parse the KDF and AEAD. */
+ rv = ssl3_ExtConsumeHandshakeNumber(ss, &tmp, 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto alert_loser;
+ }
+ kdf = (HpkeKdfId)tmp;
+ rv = ssl3_ExtConsumeHandshakeNumber(ss, &tmp, 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto alert_loser;
+ }
+ aead = (HpkeAeadId)tmp;
+
+ /* config_id */
+ rv = ssl3_ExtConsumeHandshakeVariable(ss, &configId, 1,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto alert_loser;
+ }
+
+ /* enc */
+ rv = ssl3_ExtConsumeHandshakeVariable(ss, &senderPubKey, 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto alert_loser;
+ }
+
+ /* payload */
+ rv = ssl3_ExtConsumeHandshakeVariable(ss, &encryptedCh, 2,
+ &data->data, &data->len);
+ if (rv != SECSuccess) {
+ goto alert_loser;
+ }
+
+ if (data->len) {
+ goto alert_loser;
+ }
+
+ /* All fields required. */
+ if (!configId.len || !senderPubKey.len || !encryptedCh.len) {
+ goto alert_loser;
+ }
+
+ rv = SECITEM_CopyItem(NULL, &xtnData->echSenderPubKey, &senderPubKey);
+ if (rv == SECFailure) {
+ return SECFailure;
+ }
+
+ rv = SECITEM_CopyItem(NULL, &xtnData->innerCh, &encryptedCh);
+ if (rv == SECFailure) {
+ return SECFailure;
+ }
+
+ rv = SECITEM_CopyItem(NULL, &xtnData->echConfigId, &configId);
+ if (rv == SECFailure) {
+ return SECFailure;
+ }
+ xtnData->echCipherSuite = (aead & 0xFFFF) << 16 | (kdf & 0xFFFF);
+
+ /* Not negotiated until tls13_MaybeAcceptEch. */
+ return SECSuccess;
+
+alert_loser:
+ ssl3_ExtSendAlert(ss, alert_fatal, decode_error);
+ PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_EXTENSION);
+ return SECFailure;
+}
diff --git a/lib/ssl/tls13exthandle.h b/lib/ssl/tls13exthandle.h
index abc7f9deb..556737210 100644
--- a/lib/ssl/tls13exthandle.h
+++ b/lib/ssl/tls13exthandle.h
@@ -88,11 +88,12 @@ SECStatus tls13_DecodeKeyShareEntry(sslReader *rdr, TLS13KeyShareEntry **ksp);
PRUint32 tls13_SizeOfKeyShareEntry(const SECKEYPublicKey *pubKey);
SECStatus tls13_EncodeKeyShareEntry(sslBuffer *buf, SSLNamedGroup group,
SECKEYPublicKey *pubKey);
-SECStatus tls13_ClientSendEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
- sslBuffer *buf, PRBool *added);
-SECStatus tls13_ServerHandleEsniXtn(const sslSocket *ss, TLSExtensionData *xtnData,
- SECItem *data);
-SECStatus tls13_ClientCheckEsniXtn(sslSocket *ss);
+SECStatus tls13_ServerHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ SECItem *data);
+SECStatus tls13_ServerSendEchXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ sslBuffer *buf, PRBool *added);
+SECStatus tls13_ClientHandleEchXtn(const sslSocket *ss, TLSExtensionData *xtnData,
+ SECItem *data);
SECStatus tls13_ClientSendPostHandshakeAuthXtn(const sslSocket *ss,
TLSExtensionData *xtnData,
sslBuffer *buf, PRBool *added);
diff --git a/lib/ssl/tls13hashstate.c b/lib/ssl/tls13hashstate.c
index 53d3738f0..011b4838e 100644
--- a/lib/ssl/tls13hashstate.c
+++ b/lib/ssl/tls13hashstate.c
@@ -12,6 +12,7 @@
#include "sslimpl.h"
#include "selfencrypt.h"
#include "tls13con.h"
+#include "tls13ech.h"
#include "tls13err.h"
#include "tls13hashstate.h"
@@ -24,8 +25,13 @@
* uint16 cipherSuite; // Selected cipher suite.
* uint16 keyShare; // Requested key share group (0=none)
* opaque applicationToken<0..65535>; // Application token
+ * echConfigId<0..255>; // Encrypted Client Hello config_id
+ * echHrrPsk<0..255>; // Encrypted Client Hello HRR PSK
* opaque ch_hash[rest_of_buffer]; // H(ClientHello)
* } CookieInner;
+ *
+ * An empty echConfigId means that ECH was not offered in the first ClientHello.
+ * An empty echHrrPsk means that ECH was not accepted in CH1.
*/
SECStatus
tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup,
@@ -37,6 +43,10 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup,
PRUint8 cookie[1024];
sslBuffer cookieBuf = SSL_BUFFER(cookie);
static const PRUint8 indicator = 0xff;
+ SECItem hrrNonceInfoItem = { siBuffer, (unsigned char *)kHpkeInfoEchHrr,
+ strlen(kHpkeInfoEchHrr) };
+ PK11SymKey *echHrrPsk = NULL;
+ SECItem *rawEchPsk = NULL;
/* Encode header. */
rv = sslBuffer_Append(&cookieBuf, &indicator, 1);
@@ -59,6 +69,48 @@ tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGroup,
return SECFailure;
}
+ /* Received ECH config_id, regardless of acceptance or possession
+ * of a matching ECHConfig. If rejecting ECH, this is essentially a boolean
+ * indicating that ECH was offered in CH1. If accepting ECH, this config_id
+ * will be used for the ECH decryption in CH2. */
+ if (ss->xtnData.echConfigId.len) {
+ rv = sslBuffer_AppendVariable(&cookieBuf, ss->xtnData.echConfigId.data,
+ ss->xtnData.echConfigId.len, 1);
+ } else {
+ PORT_Assert(!ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn));
+ rv = sslBuffer_AppendNumber(&cookieBuf, 0, 1);
+ }
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
+ /* Extract and encode the ech-hrr-key, if ECH was accepted
+ * (i.e. an Open() succeeded. */
+ if (ss->ssl3.hs.echAccepted) {
+ rv = PK11_HPKE_ExportSecret(ss->ssl3.hs.echHpkeCtx, &hrrNonceInfoItem, 32, &echHrrPsk);
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+ rv = PK11_ExtractKeyValue(echHrrPsk);
+ if (rv != SECSuccess) {
+ PK11_FreeSymKey(echHrrPsk);
+ return SECFailure;
+ }
+ rawEchPsk = PK11_GetKeyData(echHrrPsk);
+ if (!rawEchPsk) {
+ PK11_FreeSymKey(echHrrPsk);
+ return SECFailure;
+ }
+ rv = sslBuffer_AppendVariable(&cookieBuf, rawEchPsk->data, rawEchPsk->len, 1);
+ PK11_FreeSymKey(echHrrPsk);
+ } else {
+ /* Zero length ech_hrr_key. */
+ rv = sslBuffer_AppendNumber(&cookieBuf, 0, 1);
+ }
+ if (rv != SECSuccess) {
+ return SECFailure;
+ }
+
/* Compute and encode hashes. */
rv = tls13_ComputeHandshakeHashes(ss, &hashes);
if (rv != SECSuccess) {
@@ -84,12 +136,15 @@ SECStatus
tls13_RecoverHashState(sslSocket *ss,
unsigned char *cookie, unsigned int cookieLen,
ssl3CipherSuite *previousCipherSuite,
- const sslNamedGroupDef **previousGroup)
+ const sslNamedGroupDef **previousGroup,
+ PRBool *previousEchOffered)
{
SECStatus rv;
unsigned char plaintext[1024];
unsigned int plaintextLen = 0;
sslBuffer messageBuf = SSL_BUFFER_EMPTY;
+ sslReadBuffer echPskBuf;
+ sslReadBuffer echConfigIdBuf;
PRUint64 sentinel;
PRUint64 cipherSuite;
PRUint64 group;
@@ -104,9 +159,9 @@ tls13_RecoverHashState(sslSocket *ss,
sslReader reader = SSL_READER(plaintext, plaintextLen);
- /* Should start with 0xff. */
+ /* Should start with the sentinel value. */
rv = sslRead_ReadNumber(&reader, 1, &sentinel);
- if ((rv != SECSuccess) || (sentinel != 0xff)) {
+ if ((rv != SECSuccess) || (sentinel != TLS13_COOKIE_SENTINEL)) {
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
return SECFailure;
}
@@ -147,6 +202,19 @@ tls13_RecoverHashState(sslSocket *ss,
PORT_Assert(appTokenReader.len == appTokenLen);
PORT_Memcpy(ss->xtnData.applicationToken.data, appTokenReader.buf, appTokenLen);
+ /* ECH Config ID, which may be empty. */
+ rv = sslRead_ReadVariable(&reader, 1, &echConfigIdBuf);
+ if (rv != SECSuccess) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+ /* ECH HRR PSK, if present, is already used by tls13_GetEchInfoFromCookie */
+ rv = sslRead_ReadVariable(&reader, 1, &echPskBuf);
+ if (rv != SECSuccess) {
+ FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
+ return SECFailure;
+ }
+
/* The remainder is the hash. */
unsigned int hashLen = SSL_READER_REMAINING(&reader);
if (hashLen != tls13_GetHashSize(ss)) {
@@ -183,5 +251,6 @@ tls13_RecoverHashState(sslSocket *ss,
*previousCipherSuite = cipherSuite;
*previousGroup = selectedGroup;
+ *previousEchOffered = echConfigIdBuf.len > 0;
return SECSuccess;
}
diff --git a/lib/ssl/tls13hashstate.h b/lib/ssl/tls13hashstate.h
index e9a4aa84f..8126bd0db 100644
--- a/lib/ssl/tls13hashstate.h
+++ b/lib/ssl/tls13hashstate.h
@@ -18,8 +18,8 @@ SECStatus tls13_MakeHrrCookie(sslSocket *ss, const sslNamedGroupDef *selectedGro
PRUint8 *buf, unsigned int *len, unsigned int maxlen);
SECStatus tls13_GetHrrCookieLength(sslSocket *ss, unsigned int *length);
SECStatus tls13_RecoverHashState(sslSocket *ss,
- unsigned char *cookie,
- unsigned int cookieLen,
+ unsigned char *cookie, unsigned int cookieLen,
ssl3CipherSuite *previousCipherSuite,
- const sslNamedGroupDef **previousGroup);
+ const sslNamedGroupDef **previousGroup,
+ PRBool *previousEchOffered);
#endif