Bug 1677548 - land NSS 3eacb92e9adf UPGRADE_NSS_RELEASE, r=jcj
authorKevin Jacobs <kjacobs@mozilla.com>
Thu, 19 Nov 2020 18:28:18 +0000
changeset 558058 39c31ec31cbda6008e612c02bef46650e96ee3c8
parent 558057 044d17174437f47c0a386106b462e430b7bc18ed
child 558059 2956f1a4e43a7aec8a12924e89bc48679fbad7f3
push id37967
push userabutkovits@mozilla.com
push dateFri, 20 Nov 2020 09:45:11 +0000
treeherdermozilla-central@5b8265dc60c8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjcj
bugs1677548, 1654332
milestone85.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1677548 - land NSS 3eacb92e9adf UPGRADE_NSS_RELEASE, r=jcj 2020-11-18 Kevin Jacobs <kjacobs@mozilla.com> * lib/ssl/ssl3con.c, lib/ssl/tls13con.c, lib/ssl/tls13ech.c: Bug 1654332 - Fixup a10493dcfcc9: copy ECHConfig.config_id with socket r=jcj A late review change for ECH was for the server to compute each ECHConfig `config_id` when set to the socket, rather than on each connection. This works, but now we also need to copy that config_id when copying a socket, else the server won't find a matching ECHConfig to use for decryption. [3eacb92e9adf] [tip] 2020-11-17 Kevin Jacobs <kjacobs@mozilla.com> * automation/abi-check/expected-report-libssl3.so.txt, cmd/tstclnt/tstclnt.c, cpputil/tls_parser.h, gtests/ssl_gtest/libssl_internals.c, gtests/ssl_gtest/libssl_internals.h, gtests/ssl_gtest/manifest.mn, gtests/ssl_gtest/ssl_auth_unittest.cc, gtests/ssl_gtest/ssl_custext_unittest.cc, gtests/ssl_gtest/ssl_extension_unittest.cc, gtests/ssl_gtest/ssl_gtest.gyp, gtests/ssl_gtest/ssl_tls13compat_unittest.cc, gtests/ssl_gtest/tls_agent.cc, gtests/ssl_gtest/tls_agent.h, gtests/ssl_gtest/tls_connect.cc, gtests/ssl_gtest/tls_connect.h, gtests/ssl_gtest/tls_ech_unittest.cc, gtests/ssl_gtest/tls_esni_unittest.cc, gtests/ssl_gtest/tls_filter.cc, gtests/ssl_gtest/tls_filter.h, lib/ssl/SSLerrs.h, lib/ssl/manifest.mn, lib/ssl/ssl.gyp, lib/ssl/ssl3con.c, lib/ssl/ssl3ext.c, lib/ssl/ssl3ext.h, lib/ssl/ssl3exthandle.c, lib/ssl/ssl3exthandle.h, lib/ssl/ssl3prot.h, lib/ssl/sslencode.c, lib/ssl/sslencode.h, lib/ssl/sslerr.h, lib/ssl/sslexp.h, lib/ssl/sslimpl.h, lib/ssl/sslinfo.c, lib/ssl/sslsecur.c, lib/ssl/sslsock.c, lib/ssl/sslt.h, lib/ssl/tls13con.c, lib/ssl/tls13con.h, lib/ssl/tls13ech.c, lib/ssl/tls13ech.h, lib/ssl/tls13esni.c, lib/ssl/tls13esni.h, lib/ssl/tls13exthandle.c, lib/ssl/tls13exthandle.h, lib/ssl/tls13hashstate.c, lib/ssl/tls13hashstate.h: Bug 1654332 - Update ESNI to draft-08 (ECH). r=mt This patch adds support for Encrypted Client Hello (draft-ietf-tls- esni-08), replacing the existing ESNI (draft -02) support. There are five new experimental functions to enable this: - SSL_EncodeEchConfig: Generates an encoded (not BASE64) ECHConfig given a set of parameters. - SSL_SetClientEchConfigs: Configures the provided ECHConfig to the given socket. When configured, an ephemeral HPKE keypair will be generated for the CH encryption. - SSL_SetServerEchConfigs: Configures the provided ECHConfig and keypair to the socket. The keypair specified will be used for HPKE operations in order to decrypt encrypted Client Hellos as they are received. - SSL_GetEchRetryConfigs: If ECH is rejected by the server and compatible retry_configs are provided, this API allows the application to extract those retry_configs for use in a new connection. - SSL_EnableTls13GreaseEch: When enabled, non-ECH Client Hellos will have a "GREASE ECH" (i.e. fake) extension appended. GREASE ECH is disabled by default, as there are known compatibility issues that will be addressed in a subsequent draft. The following ESNI experimental functions are deprecated by this update: - SSL_EncodeESNIKeys - SSL_EnableESNI - SSL_SetESNIKeyPair In order to be used, NSS must be compiled with `NSS_ENABLE_DRAFT_HPKE` defined. [a10493dcfcc9] * lib/ssl/ssl3con.c, lib/ssl/sslencode.c, lib/ssl/sslencode.h, lib/ssl/tls13con.c, lib/ssl/tls13con.h: Bug 1654332 - Buffered ClientHello construction. r=mt This patch refactors construction of Client Hello messages. Instead of each component of the message being written separately into `ss->sec.ci.sendBuf`, we now construct the message in its own sslBuffer. Once complete, the entire message is added to the sendBuf via `ssl3_AppendHandshake`. `ssl3_SendServerHello` already uses this approach and it becomes necessary for ECH, where we use the constructed ClientHello to create an inner ClientHello. [d40121ba59ba] 2020-11-13 J.C. Jones <jjones@mozilla.com> * automation/abi-check/expected-report-libnss3.so.txt, automation/abi- check/expected-report-libnssutil3.so.txt, automation/abi-check /previous-nss-release, lib/nss/nss.h, lib/softoken/softkver.h, lib/util/nssutil.h: Set version numbers to 3.60 Beta [5e7b37609f22] Differential Revision: https://phabricator.services.mozilla.com/D97492
build/moz.configure/nss.configure
security/nss/TAG-INFO
security/nss/automation/abi-check/expected-report-libnss3.so.txt
security/nss/automation/abi-check/expected-report-libnssutil3.so.txt
security/nss/automation/abi-check/expected-report-libssl3.so.txt
security/nss/automation/abi-check/previous-nss-release
security/nss/cmd/tstclnt/tstclnt.c
security/nss/coreconf/coreconf.dep
security/nss/cpputil/tls_parser.h
security/nss/gtests/ssl_gtest/libssl_internals.c
security/nss/gtests/ssl_gtest/libssl_internals.h
security/nss/gtests/ssl_gtest/manifest.mn
security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc
security/nss/gtests/ssl_gtest/ssl_custext_unittest.cc
security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
security/nss/gtests/ssl_gtest/ssl_gtest.gyp
security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
security/nss/gtests/ssl_gtest/tls_agent.cc
security/nss/gtests/ssl_gtest/tls_agent.h
security/nss/gtests/ssl_gtest/tls_connect.cc
security/nss/gtests/ssl_gtest/tls_connect.h
security/nss/gtests/ssl_gtest/tls_ech_unittest.cc
security/nss/gtests/ssl_gtest/tls_esni_unittest.cc
security/nss/gtests/ssl_gtest/tls_filter.cc
security/nss/gtests/ssl_gtest/tls_filter.h
security/nss/lib/nss/nss.h
security/nss/lib/softoken/softkver.h
security/nss/lib/ssl/SSLerrs.h
security/nss/lib/ssl/manifest.mn
security/nss/lib/ssl/ssl.gyp
security/nss/lib/ssl/ssl3con.c
security/nss/lib/ssl/ssl3ext.c
security/nss/lib/ssl/ssl3ext.h
security/nss/lib/ssl/ssl3exthandle.c
security/nss/lib/ssl/ssl3exthandle.h
security/nss/lib/ssl/ssl3prot.h
security/nss/lib/ssl/sslencode.c
security/nss/lib/ssl/sslencode.h
security/nss/lib/ssl/sslerr.h
security/nss/lib/ssl/sslexp.h
security/nss/lib/ssl/sslimpl.h
security/nss/lib/ssl/sslinfo.c
security/nss/lib/ssl/sslsecur.c
security/nss/lib/ssl/sslsock.c
security/nss/lib/ssl/sslt.h
security/nss/lib/ssl/tls13con.c
security/nss/lib/ssl/tls13con.h
security/nss/lib/ssl/tls13ech.c
security/nss/lib/ssl/tls13ech.h
security/nss/lib/ssl/tls13esni.c
security/nss/lib/ssl/tls13esni.h
security/nss/lib/ssl/tls13exthandle.c
security/nss/lib/ssl/tls13exthandle.h
security/nss/lib/ssl/tls13hashstate.c
security/nss/lib/ssl/tls13hashstate.h
security/nss/lib/util/nssutil.h
--- a/build/moz.configure/nss.configure
+++ b/build/moz.configure/nss.configure
@@ -4,17 +4,17 @@
 # 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/.
 
 option("--with-system-nss", help="Use system NSS")
 
 imply_option("--with-system-nspr", True, when="--with-system-nss")
 
 nss_pkg = pkg_check_modules(
-    "NSS", "nss >= 3.59", when="--with-system-nss", config=False
+    "NSS", "nss >= 3.60", when="--with-system-nss", config=False
 )
 
 set_config("MOZ_SYSTEM_NSS", True, when="--with-system-nss")
 
 
 @depends(nss_pkg, check_build_environment)
 def nss_config(nss_pkg, build_env):
     cflags = ["-I%s" % os.path.join(build_env.dist, "include", "nss")]
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_59_RTM
\ No newline at end of file
+3eacb92e9adf
\ No newline at end of file
--- a/security/nss/automation/abi-check/expected-report-libnss3.so.txt
+++ b/security/nss/automation/abi-check/expected-report-libnss3.so.txt
@@ -1,8 +0,0 @@
-
-4 Added functions:
-
-  [A] 'function SECStatus CERT_AddCertToListHeadWithData(CERTCertList*, CERTCertificate*, void*)'    {CERT_AddCertToListHeadWithData@@NSS_3.59}
-  [A] 'function SECStatus CERT_AddCertToListTailWithData(CERTCertList*, CERTCertificate*, void*)'    {CERT_AddCertToListTailWithData@@NSS_3.59}
-  [A] 'function PK11SymKey* PK11_PubUnwrapSymKeyWithMechanism(SECKEYPrivateKey*, CK_MECHANISM_TYPE, SECItem*, SECItem*, CK_MECHANISM_TYPE, CK_ATTRIBUTE_TYPE, int)'    {PK11_PubUnwrapSymKeyWithMechanism@@NSS_3.59}
-  [A] 'function SECStatus PK11_PubWrapSymKeyWithMechanism(SECKEYPublicKey*, CK_MECHANISM_TYPE, SECItem*, PK11SymKey*, SECItem*)'    {PK11_PubWrapSymKeyWithMechanism@@NSS_3.59}
-
--- a/security/nss/automation/abi-check/expected-report-libnssutil3.so.txt
+++ b/security/nss/automation/abi-check/expected-report-libnssutil3.so.txt
@@ -1,6 +0,0 @@
-
-2 Added functions:
-
-  [A] 'function PRBool NSS_IsPolicyLocked()'    {NSS_IsPolicyLocked@@NSSUTIL_3.59}
-  [A] 'function void NSS_LockPolicy()'    {NSS_LockPolicy@@NSSUTIL_3.59}
-
--- a/security/nss/automation/abi-check/expected-report-libssl3.so.txt
+++ b/security/nss/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
--- a/security/nss/automation/abi-check/previous-nss-release
+++ b/security/nss/automation/abi-check/previous-nss-release
@@ -1,1 +1,1 @@
-NSS_3_58_BRANCH
+NSS_3_59_BRANCH
--- a/security/nss/cmd/tstclnt/tstclnt.c
+++ b/security/nss/cmd/tstclnt/tstclnt.c
@@ -226,17 +226,17 @@ PrintUsageHeader()
     fprintf(stderr,
             "Usage:  %s -h host [-a 1st_hs_name ] [-a 2nd_hs_name ] [-p port]\n"
             "  [-D | -d certdir] [-C] [-b | -R root-module] \n"
             "  [-n nickname] [-Bafosvx] [-c ciphers] [-Y] [-Z] [-E]\n"
             "  [-V [min-version]:[max-version]] [-K] [-T] [-U]\n"
             "  [-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);
 }
 
 static void
 PrintParameterUsage()
 {
     fprintf(stderr, "%-20s Send different SNI name. 1st_hs_name - at first\n"
@@ -311,17 +311,17 @@ PrintParameterUsage()
                     "%-20s ecdsa_secp521r1_sha512,\n"
                     "%-20s rsa_pss_rsae_sha256, rsa_pss_rsae_sha384, rsa_pss_rsae_sha512,\n"
                     "%-20s rsa_pss_pss_sha256, rsa_pss_pss_sha384, rsa_pss_pss_sha512,\n"
                     "%-20s dsa_sha1, dsa_sha256, dsa_sha384, dsa_sha512\n",
             "-J", "", "", "", "", "", "", "");
     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", "");
     fprintf(stderr, "%-20s Export and print keying material after successful handshake.\n"
                     "%-20s The argument is a comma separated list of exporters in the form:\n"
                     "%-20s   LABEL[:OUTPUT-LENGTH[:CONTEXT]]\n"
                     "%-20s where LABEL and CONTEXT can be either a free-form string or\n"
                     "%-20s a hex string if it is preceded by \"0x\"; OUTPUT-LENGTH\n"
@@ -1005,17 +1005,17 @@ PRUint8 *zeroRttData;
 unsigned int zeroRttLen = 0;
 PRBool enableAltServerHello = PR_FALSE;
 PRBool useDTLS = PR_FALSE;
 PRBool actAsServer = PR_FALSE;
 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;
 unsigned int enabledExporterCount = 0;
 
 static int
 writeBytesToServer(PRFileDesc *s, const PRUint8 *buf, int nb)
 {
@@ -1258,16 +1258,40 @@ importPsk(PRFileDesc *s)
     }
 
     SECStatus rv = SSL_AddExternalPsk(s, symKey, (const PRUint8 *)pskLabel.data,
                                       pskLabel.len, ssl_hash_sha256);
     PK11_FreeSymKey(symKey);
     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()
 {
     int headerSeparatorPtrnId = 0;
     int error = 0;
     SECStatus rv;
     PRStatus status;
     PRInt32 filesReady;
@@ -1506,31 +1530,30 @@ run()
         rv = SSL_SignatureSchemePrefSet(s, enabledSigSchemes, enabledSigSchemeCount);
         if (rv < 0) {
             SECU_PrintError(progName, "SSL_SignatureSchemePrefSet failed");
             error = 1;
             goto done;
         }
     }
 
-    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;
         }
     }
 
     if (psk.data) {
         rv = importPsk(s);
         if (rv != SECSuccess) {
@@ -1697,16 +1720,19 @@ run()
             } else if (nb == 0) {
                 /* EOF on stdin, stop polling stdin for read. */
                 pollset[STDIN_FD].in_flags = 0;
                 if (actAsServer)
                     requestToExit = PR_TRUE;
             } else {
                 error = writeBytesToServer(s, buf, nb);
                 if (error) {
+                    if (echConfigs) {
+                        (void)printEchRetryConfigs(s);
+                    }
                     goto done;
                 }
                 pollset[SSOCK_FD].in_flags = PR_POLL_READ;
             }
         }
 
         if (pollset[SSOCK_FD].in_flags) {
             FPRINTF(stderr,
@@ -1876,17 +1902,17 @@ main(int argc, char **argv)
                     default:
                         serverCertAuth.allowOCSPSideChannelData = PR_TRUE;
                         serverCertAuth.allowCRLSideChannelData = PR_TRUE;
                         break;
                 };
                 break;
 
             case 'N':
-                encryptedSNIKeys = PORT_Strdup(optstate->value);
+                echConfigs = PORT_Strdup(optstate->value);
                 break;
 
             case 'P':
                 useDTLS = PR_TRUE;
                 if (!strcmp(optstate->value, "server")) {
                     actAsServer = 1;
                 } else {
                     if (strcmp(optstate->value, "client")) {
@@ -2252,17 +2278,17 @@ done:
 
     PORT_Free((void *)requestFile);
     PORT_Free(hs1SniHostName);
     PORT_Free(hs2SniHostName);
     PORT_Free(nickname);
     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);
 
     if (enabledGroups) {
         PORT_Free(enabledGroups);
     }
     if (NSS_IsInitialized()) {
         SSL_ClearSessionCache();
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/security/nss/cpputil/tls_parser.h
+++ b/security/nss/cpputil/tls_parser.h
@@ -51,16 +51,17 @@ const uint8_t kTlsAlertProtocolVersion =
 const uint8_t kTlsAlertInsufficientSecurity = 71;
 const uint8_t kTlsAlertInternalError = 80;
 const uint8_t kTlsAlertInappropriateFallback = 86;
 const uint8_t kTlsAlertMissingExtension = 109;
 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
     0xfe,
     0xff,  // Version
     0x00,
     0x00,
     0x00,
--- a/security/nss/gtests/ssl_gtest/libssl_internals.c
+++ b/security/nss/gtests/ssl_gtest/libssl_internals.c
@@ -3,18 +3,20 @@
 /* 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/. */
 
 /* This file contains functions for frobbing the internals of libssl */
 #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"
 #include "sslproto.h"
 
 SECStatus SSLInt_RemoveServerCertificates(PRFileDesc *fd) {
   if (!fd) {
     return SECFailure;
@@ -476,8 +478,22 @@ SECStatus SSLInt_HasPendingHandshakeData
     return SECFailure;
   }
 
   ssl_GetSSL3HandshakeLock(ss);
   *pending = ss->ssl3.hs.msg_body.len > 0;
   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;
+}
--- a/security/nss/gtests/ssl_gtest/libssl_internals.h
+++ b/security/nss/gtests/ssl_gtest/libssl_internals.h
@@ -44,10 +44,12 @@ SSLKEAType SSLInt_GetKEAType(SSLNamedGro
 SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending);
 SECStatus SSLInt_SetSocketMaxEarlyDataSize(PRFileDesc *fd, uint32_t size);
 SECStatus SSLInt_TweakChannelInfoForDC(PRFileDesc *fd, PRBool changeAuthKeyBits,
                                        PRBool changeScheme);
 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_
--- a/security/nss/gtests/ssl_gtest/manifest.mn
+++ b/security/nss/gtests/ssl_gtest/manifest.mn
@@ -53,17 +53,17 @@ CPPSRCS = \
       test_io.cc \
       tls_agent.cc \
       tls_connect.cc \
       tls_hkdf_unittest.cc \
       tls_filter.cc \
       tls_protect.cc \
       tls_psk_unittest.cc \
       tls_subcerts_unittest.cc \
-      tls_esni_unittest.cc \
+      tls_ech_unittest.cc \
       $(SSLKEYLOGFILE_FILES) \
       $(NULL)
 
 INCLUDES += -I$(CORE_DEPTH)/gtests/google_test/gtest/include \
             -I$(CORE_DEPTH)/gtests/common \
             -I$(CORE_DEPTH)/cpputil
 
 REQUIRES = nspr nss libdbm gtest cpputil
--- a/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_auth_unittest.cc
@@ -655,16 +655,28 @@ TEST_P(TlsConnectGeneric, ClientAuthRequ
 TEST_P(TlsConnectGeneric, ClientAuthEcdsa) {
   Reset(TlsAgent::kServerEcdsa256);
   client_->SetupClientAuth();
   server_->RequestClientAuth(true);
   Connect();
   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();
   server_->RequestClientAuth(true);
   Connect();
   CheckKeys();
 }
 
--- a/security/nss/gtests/ssl_gtest/ssl_custext_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_custext_unittest.cc
@@ -62,18 +62,18 @@ static const uint16_t kManyExtensions[] 
     ssl_tls13_early_data_xtn,
     ssl_tls13_supported_versions_xtn,
     ssl_tls13_cookie_xtn,
     ssl_tls13_psk_key_exchange_modes_xtn,
     ssl_tls13_ticket_early_data_info_xtn,
     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),
 // plus the deprecated values (see sslt.h), and two extra dummy values.
 PR_STATIC_ASSERT((SSL_MAX_EXTENSIONS + 5) == PR_ARRAY_SIZE(kManyExtensions));
 
 void InstallManyWriters(std::shared_ptr<TlsAgent> agent,
                         SSLExtensionWriter writer, size_t *installed = nullptr,
--- a/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_extension_unittest.cc
@@ -78,73 +78,16 @@ class TlsExtensionTruncator : public Tls
     return CHANGE;
   }
 
  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) {
-    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)
       : TlsConnectTestBase(variant, version) {}
 
   void ClientHelloErrorTest(std::shared_ptr<PacketFilter> filter,
                             uint8_t desc = kTlsAlertDecodeError) {
     client_->SetFilter(filter);
@@ -1150,16 +1093,32 @@ TEST_P(TlsExtensionTest13, HrrThenRemove
 
 TEST_P(TlsExtensionTest13, HrrThenRemoveSupportedGroups) {
   ExpectAlert(server_, kTlsAlertMissingExtension);
   HrrThenRemoveExtensionsTest(ssl_supported_groups_xtn,
                               SSL_ERROR_MISSING_EXTENSION_ALERT,
                               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));
 }
 
 TEST_P(TlsExtensionTest13, OddVersionList) {
   static const uint8_t ext[] = {0x00, 0x01, 0x00};
   ConnectWithBogusVersionList(ext, sizeof(ext));
@@ -1284,16 +1243,17 @@ TEST_P(TlsBogusExtensionTest13, AddBogus
   ConfigureSessionCache(RESUME_BOTH, RESUME_TICKET);
   ExpectResumption(RESUME_TICKET);
   Connect();
   SendReceive();
 }
 
 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.
   static const char* long_name =
       "chickenchickenchickenchickenchickenchickenchickenchicken."
       "chickenchickenchickenchickenchickenchickenchickenchicken."
       "chickenchickenchickenchickenchicken.";
   SECStatus rv = SSL_SetURL(client_->ssl_fd(), long_name);
--- a/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
+++ b/security/nss/gtests/ssl_gtest/ssl_gtest.gyp
@@ -50,17 +50,17 @@
         'ssl_v2_client_hello_unittest.cc',
         'ssl_version_unittest.cc',
         'ssl_versionpolicy_unittest.cc',
         'test_io.cc',
         'tls_agent.cc',
         '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'
       ],
       'dependencies': [
         '<(DEPTH)/exports.gyp:nss_exports',
         '<(DEPTH)/lib/util/util.gyp:nssutil3',
         '<(DEPTH)/gtests/google_test/google_test.gyp:gtest',
--- a/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
+++ b/security/nss/gtests/ssl_gtest/ssl_tls13compat_unittest.cc
@@ -209,16 +209,41 @@ TEST_F(Tls13CompatTest, EnabledHrrZeroRt
   CheckForCCS(true, true);
 
   Handshake();
   ExpectEarlyDataAccepted(false);
   CheckConnected();
   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)
       : TlsHandshakeFilter(
             a, {kTlsHandshakeClientHello, kTlsHandshakeServerHello}) {}
 
  protected:
   virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
--- a/security/nss/gtests/ssl_gtest/tls_agent.cc
+++ b/security/nss/gtests/ssl_gtest/tls_agent.cc
@@ -69,16 +69,17 @@ TlsAgent::TlsAgent(const std::string& nm
       adapter_(new DummyPrSocket(role_str(), var)),
       ssl_fd_(nullptr),
       state_(STATE_INIT),
       timer_handle_(nullptr),
       falsestart_enabled_(false),
       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),
       auth_certificate_hook_called_(false),
       expected_received_alert_(kTlsAlertCloseNotify),
       expected_received_alert_level_(kTlsAlertWarning),
       expected_sent_alert_(kTlsAlertCloseNotify),
       expected_sent_alert_level_(kTlsAlertWarning),
@@ -682,17 +683,19 @@ void TlsAgent::EnableFalseStart() {
   EXPECT_TRUE(EnsureTlsSetup());
 
   falsestart_enabled_ = true;
   EXPECT_EQ(SECSuccess, SSL_SetCanFalseStartCallback(
                             ssl_fd(), CanFalseStartCallback, this));
   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; }
 
 void TlsAgent::EnableAlpn(const uint8_t* val, size_t len) {
   EXPECT_TRUE(EnsureTlsSetup());
   EXPECT_EQ(SECSuccess, SSL_SetNextProtoNego(ssl_fd(), val, len));
 }
 
@@ -815,17 +818,16 @@ void TlsAgent::WaitForErrorCode(int32_t 
 }
 
 void TlsAgent::CheckPreliminaryInfo() {
   SSLPreliminaryChannelInfo preinfo;
   EXPECT_EQ(SECSuccess,
             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
   // version can negotiate a version.
   if (!expected_version_) {
     expected_version_ = preinfo.protocolVersion;
   }
   EXPECT_EQ(expected_version_, preinfo.protocolVersion);
@@ -927,16 +929,17 @@ void TlsAgent::Connected() {
   CheckCallbacks();
 
   SECStatus rv = SSL_GetChannelInfo(ssl_fd(), &info_, sizeof(info_));
   EXPECT_EQ(SECSuccess, rv);
   EXPECT_EQ(sizeof(info_), info_.length);
 
   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
   // that the final values are correct.
   UpdatePreliminaryChannelInfo();
   EXPECT_EQ(expected_version_, info_.protocolVersion);
   EXPECT_EQ(expected_cipher_suite_, info_.cipherSuite);
 
--- a/security/nss/gtests/ssl_gtest/tls_agent.h
+++ b/security/nss/gtests/ssl_gtest/tls_agent.h
@@ -153,17 +153,19 @@ class TlsAgent : public PollTarget {
   void SetVersionRange(uint16_t minver, uint16_t maxver);
   void GetVersionRange(uint16_t* minver, uint16_t* maxver);
   void CheckPreliminaryInfo();
   void ResetPreliminaryInfo();
   void SetExpectedVersion(uint16_t version);
   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);
   void EnableAlpn(const uint8_t* val, size_t len);
   void CheckAlpn(SSLNextProtoState expected_state,
                  const std::string& expected = "") const;
   void EnableSrtp();
   void CheckSrtp() const;
@@ -181,16 +183,17 @@ class TlsAgent : public PollTarget {
   void AddPsk(const ScopedPK11SymKey& psk, std::string label, SSLHashType hash,
               uint16_t zeroRttSuite = TLS_NULL_WITH_NULL_NULL);
   void RemovePsk(std::string label);
   void ReadBytes(size_t max = 16384U);
   void ResetSentBytes(size_t bytes = 0);  // Hack to test drops.
   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);
   void DisableECDHEServerKeyReuse();
   bool GetPeerChainLength(size_t* count);
   void CheckCipherSuite(uint16_t cipher_suite);
   void SetResumptionTokenCallback();
   bool MaybeSetResumptionToken();
@@ -421,16 +424,17 @@ class TlsAgent : public PollTarget {
   std::shared_ptr<DummyPrSocket> adapter_;
   ScopedPRFileDesc ssl_fd_;
   State state_;
   std::shared_ptr<Poller::Timer> timer_handle_;
   bool falsestart_enabled_;
   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_;
   bool auth_certificate_hook_called_;
   uint8_t expected_received_alert_;
   uint8_t expected_received_alert_level_;
   uint8_t expected_sent_alert_;
   uint8_t expected_sent_alert_level_;
--- a/security/nss/gtests/ssl_gtest/tls_connect.cc
+++ b/security/nss/gtests/ssl_gtest/tls_connect.cc
@@ -243,16 +243,101 @@ void TlsConnectTestBase::Init() {
 void TlsConnectTestBase::ResetAntiReplay(PRTime window) {
   SSLAntiReplayContext* p_anti_replay = nullptr;
   EXPECT_EQ(SECSuccess,
             SSL_CreateAntiReplayContext(now_, window, 1, 3, &p_anti_replay));
   EXPECT_NE(nullptr, p_anti_replay);
   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();
   std::string client_name = client_->name();
   Reset(server_name, client_name);
 }
 
 void TlsConnectTestBase::Reset(const std::string& server_name,
--- a/security/nss/gtests/ssl_gtest/tls_connect.h
+++ b/security/nss/gtests/ssl_gtest/tls_connect.h
@@ -141,16 +141,29 @@ class TlsConnectTestBase : public ::test
   void AdvanceTime(PRTime time_shift);
 
   void ResetAntiReplay(PRTime window);
   void RolloverAntiReplay();
 
   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_;
   std::shared_ptr<TlsAgent> server_;
   std::unique_ptr<TlsAgent> client_model_;
   std::unique_ptr<TlsAgent> server_model_;
   uint16_t version_;
   SessionResumptionMode expected_resumption_mode_;
new file mode 100644
--- /dev/null
+++ b/security/nss/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
deleted file mode 100644
--- a/security/nss/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
--- a/security/nss/gtests/ssl_gtest/tls_filter.cc
+++ b/security/nss/gtests/ssl_gtest/tls_filter.cc
@@ -12,16 +12,17 @@ extern "C" {
 #include "libssl_internals.h"
 }
 
 #include <cassert>
 #include <iostream>
 #include "gtest_utils.h"
 #include "tls_agent.h"
 #include "tls_filter.h"
+#include "tls_parser.h"
 #include "tls_protect.h"
 
 namespace nss_test {
 
 void TlsVersioned::WriteStream(std::ostream& stream) const {
   stream << (is_dtls() ? "DTLS " : "TLS ");
   switch (version()) {
     case 0:
@@ -1026,16 +1027,79 @@ PacketFilter::Action TlsExtensionReplace
   if (extension_type != extension_) {
     return KEEP;
   }
 
   *output = data_;
   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_) {
     return DROP;
   }
   return KEEP;
 }
 
--- a/security/nss/gtests/ssl_gtest/tls_filter.h
+++ b/security/nss/gtests/ssl_gtest/tls_filter.h
@@ -6,16 +6,17 @@
 
 #ifndef tls_filter_h_
 #define tls_filter_h_
 
 #include <functional>
 #include <memory>
 #include <set>
 #include <vector>
+#include "pk11pub.h"
 #include "sslt.h"
 #include "sslproto.h"
 #include "test_io.h"
 #include "tls_agent.h"
 #include "tls_parser.h"
 #include "tls_protect.h"
 
 extern "C" {
@@ -520,16 +521,47 @@ class TlsExtensionReplacer : public TlsE
                                        const DataBuffer& input,
                                        DataBuffer* output) override;
 
  private:
   const uint16_t extension_;
   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)
       : TlsExtensionFilter(a), extension_(extension) {}
   PacketFilter::Action FilterExtension(uint16_t extension_type,
                                        const DataBuffer&, DataBuffer*) override;
 
  private:
--- a/security/nss/lib/nss/nss.h
+++ b/security/nss/lib/nss/nss.h
@@ -17,22 +17,22 @@
 
 /*
  * NSS's major version, minor version, patch level, build number, and whether
  * this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define NSS_VERSION "3.59" _NSS_CUSTOMIZED
+#define NSS_VERSION "3.60" _NSS_CUSTOMIZED " Beta"
 #define NSS_VMAJOR 3
-#define NSS_VMINOR 59
+#define NSS_VMINOR 60
 #define NSS_VPATCH 0
 #define NSS_VBUILD 0
-#define NSS_BETA PR_FALSE
+#define NSS_BETA PR_TRUE
 
 #ifndef RC_INVOKED
 
 #include "seccomon.h"
 
 typedef struct NSSInitParametersStr NSSInitParameters;
 
 /*
--- a/security/nss/lib/softoken/softkver.h
+++ b/security/nss/lib/softoken/softkver.h
@@ -12,16 +12,16 @@
 
 /*
  * Softoken's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define SOFTOKEN_VERSION "3.59" SOFTOKEN_ECC_STRING
+#define SOFTOKEN_VERSION "3.60" SOFTOKEN_ECC_STRING " Beta"
 #define SOFTOKEN_VMAJOR 3
-#define SOFTOKEN_VMINOR 59
+#define SOFTOKEN_VMINOR 60
 #define SOFTOKEN_VPATCH 0
 #define SOFTOKEN_VBUILD 0
-#define SOFTOKEN_BETA PR_FALSE
+#define SOFTOKEN_BETA PR_TRUE
 
 #endif /* _SOFTKVER_H_ */
--- a/security/nss/lib/ssl/SSLerrs.h
+++ b/security/nss/lib/ssl/SSLerrs.h
@@ -580,8 +580,23 @@ ER3(SSL_ERROR_DC_BAD_SIGNATURE, (SSL_ERR
 ER3(SSL_ERROR_DC_INVALID_KEY_USAGE, (SSL_ERROR_BASE + 184),
     "SSL received a delegated credential from a certificate with invalid key usage.")
 
 ER3(SSL_ERROR_DC_EXPIRED, (SSL_ERROR_BASE + 185),
     "SSL received a delegated credential that expired.")
 
 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.")
--- a/security/nss/lib/ssl/manifest.mn
+++ b/security/nss/lib/ssl/manifest.mn
@@ -55,17 +55,17 @@ CSRCS = \
         tls13exthandle.c \
         tls13hashstate.c \
         tls13hkdf.c \
         tls13psk.c \
         tls13replay.c \
         sslcert.c \
         sslgrp.c \
         sslprimitive.c \
-        tls13esni.c \
+        tls13ech.c \
         tls13subcerts.c \
         $(NULL)
 
 LIBRARY_NAME = ssl
 LIBRARY_VERSION = 3
 MAPFILE = $(OBJDIR)/$(LIBRARY_NAME).def
 
 # This part of the code, including all sub-dirs, can be optimized for size
--- a/security/nss/lib/ssl/ssl.gyp
+++ b/security/nss/lib/ssl/ssl.gyp
@@ -39,17 +39,17 @@
         'sslreveal.c',
         'sslsecur.c',
         'sslsnce.c',
         'sslsock.c',
         'sslspec.c',
         'ssltrace.c',
         'sslver.c',
         'tls13con.c',
-        'tls13esni.c',
+        'tls13ech.c',
         'tls13exthandle.c',
         'tls13hashstate.c',
         'tls13hkdf.c',
         'tls13psk.c',
         'tls13replay.c',
         'tls13subcerts.c',
       ],
       'conditions': [
--- a/security/nss/lib/ssl/ssl3con.c
+++ b/security/nss/lib/ssl/ssl3con.c
@@ -16,16 +16,18 @@
 #include "secitem.h"
 #include "sechash.h"
 
 #include "sslimpl.h"
 #include "sslproto.h"
 #include "sslerr.h"
 #include "ssl3ext.h"
 #include "ssl3exthandle.h"
+#include "tls13ech.h"
+#include "tls13exthandle.h"
 #include "tls13psk.h"
 #include "tls13subcerts.h"
 #include "prtime.h"
 #include "prinrval.h"
 #include "prerror.h"
 #include "pratom.h"
 #include "prthread.h"
 #include "nss.h"
@@ -57,17 +59,16 @@ static SECStatus ssl3_HandleClientHelloP
                                              unsigned int len);
 static SECStatus ssl3_HandleServerHelloPart2(sslSocket *ss,
                                              const SECItem *sidBytes,
                                              int *retErrCode);
 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);
 PRBool ssl_IsRsaeSignatureScheme(SSLSignatureScheme scheme);
 PRBool ssl_IsRsaPkcs1SignatureScheme(SSLSignatureScheme scheme);
 PRBool ssl_IsDsaSignatureScheme(SSLSignatureScheme scheme);
 
 const PRUint8 ssl_hello_retry_random[] = {
@@ -1030,37 +1031,16 @@ ssl3_config_match(const ssl3CipherSuiteC
         if (ss->xtnData.selectedPsk->hash != cipher_def->prf_hash) {
             return PR_FALSE;
         }
     }
 
     return ssl3_CipherSuiteAllowedForVersionRange(suite->cipher_suite, vrange);
 }
 
-/* Return the number of cipher suites that are usable. */
-/* called from ssl3_SendClientHello */
-static unsigned int
-count_cipher_suites(sslSocket *ss, PRUint8 policy)
-{
-    unsigned int i, count = 0;
-
-    if (SSL_ALL_VERSIONS_DISABLED(&ss->vrange)) {
-        return 0;
-    }
-    for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
-        if (ssl3_config_match(&ss->cipherSuites[i], policy, &ss->vrange, ss)) {
-            count++;
-        }
-    }
-    if (count == 0) {
-        PORT_SetError(SSL_ERROR_SSL_DISABLED);
-    }
-    return count;
-}
-
 /* For TLS 1.3, when resuming, check for a ciphersuite that is both compatible
  * with the identified ciphersuite and enabled. */
 static PRBool
 tls13_ResumptionCompatible(sslSocket *ss, ssl3CipherSuite suite)
 {
     SSLVersionRange vrange = { SSL_LIBRARY_VERSION_TLS_1_3,
                                SSL_LIBRARY_VERSION_TLS_1_3 };
     SSLHashType hash = tls13_GetHashForCipherSuite(suite);
@@ -1183,17 +1163,17 @@ ssl_ClientReadVersion(sslSocket *ss, PRU
     if (v >= SSL_LIBRARY_VERSION_TLS_1_3) {
         SSL3_SendAlert(ss, alert_fatal, illegal_parameter);
         return SECFailure;
     }
     *version = v;
     return SECSuccess;
 }
 
-static SECStatus
+SECStatus
 ssl3_GetNewRandom(SSL3Random random)
 {
     SECStatus rv;
 
     rv = PK11_GenerateRandom(random, SSL3_RANDOM_LENGTH);
     if (rv != SECSuccess) {
         ssl_MapLowLevelError(SSL_ERROR_GENERATE_RANDOM_FAILURE);
     }
@@ -3125,16 +3105,19 @@ ssl3_HandleAlert(sslSocket *ss, sslBuffe
             error = SSL_ERROR_UNRECOGNIZED_NAME_ALERT;
             break;
         case bad_certificate_status_response:
             error = SSL_ERROR_BAD_CERT_STATUS_RESPONSE_ALERT;
             break;
         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;
     }
     if ((ss->version >= SSL_LIBRARY_VERSION_TLS_1_3) &&
         (ss->ssl3.hs.ws != wait_server_hello)) {
         /* TLS 1.3 requires all but "end of data" alerts to be
          * treated as fatal. */
@@ -3753,16 +3736,37 @@ ssl3_DeriveConnectionKeys(sslSocket *ss,
     return SECSuccess;
 
 loser:
     PK11_FreeSymKey(derivedKeyHandle);
     ssl_MapLowLevelError(SSL_ERROR_SESSION_KEY_GEN_FAILURE);
     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(),
  * and ssl3_HandleServerHello. */
 SECStatus
 ssl3_InitHandshakeHashes(sslSocket *ss)
 {
     SSL_TRC(30, ("%d: SSL3[%d]: start handshake hashes", SSL_GETPID(), ss->fd));
@@ -3797,16 +3801,28 @@ ssl3_InitHandshakeHashes(sslSocket *ss)
                 return SECFailure;
             }
             ss->ssl3.hs.hashType = handshake_hash_single;
             if (PK11_DigestBegin(ss->ssl3.hs.sha) != SECSuccess) {
                 ssl_MapLowLevelError(SSL_ERROR_DIGEST_FAILURE);
                 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. */
             ss->ssl3.hs.md5 = PK11_CreateDigestContext(SEC_OID_MD5);
             if (ss->ssl3.hs.md5 == NULL) {
                 ssl_MapLowLevelError(SSL_ERROR_MD5_DIGEST_FAILURE);
                 return SECFailure;
             }
@@ -3833,41 +3849,85 @@ ssl3_InitHandshakeHashes(sslSocket *ss)
     if (ss->ssl3.hs.hashType != handshake_hash_record &&
         ss->ssl3.hs.messages.len > 0) {
         if (ssl3_UpdateHandshakeHashes(ss, ss->ssl3.hs.messages.buf,
                                        ss->ssl3.hs.messages.len) != SECSuccess) {
             return SECFailure;
         }
         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;
 }
 
 void
 ssl3_RestartHandshakeHashes(sslSocket *ss)
 {
     SSL_TRC(30, ("%d: SSL3[%d]: reset handshake hashes",
                  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;
     }
     if (ss->ssl3.hs.sha) {
         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
  */
 /* Called from  ssl3_InitHandshakeHashes()
 **      ssl3_AppendHandshake()
 **      ssl3_HandleV2ClientHello()
 **      ssl3_HandleHandshakeMessage()
 ** Caller must hold the ssl3Handshake lock.
@@ -3888,17 +3948,24 @@ ssl3_UpdateHandshakeHashes(sslSocket *ss
      *
      * With TLS 1.2, we always append all handshake messages,
      * and never update the hash, because the hash function we must use for
      * certificate_verify might be different from the hash function we use
      * when signing other handshake hashes. */
 
     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));
 
     if (ss->ssl3.hs.hashType == handshake_hash_single) {
         PORT_Assert(ss->version >= SSL_LIBRARY_VERSION_TLS_1_3);
         rv = PK11_DigestOp(ss->ssl3.hs.sha, b, l);
         if (rv != SECSuccess) {
@@ -4898,16 +4965,212 @@ ssl_SetClientHelloSpecVersion(sslSocket 
          * The final version is set when a version is negotiated.
          */
         spec->recordVersion = PR_MIN(SSL_LIBRARY_VERSION_TLS_1_0,
                                      ss->vrange.max);
     }
     ssl_ReleaseSpecWriteLock(ss);
 }
 
+SECStatus
+ssl3_InsertChHeaderSize(const sslSocket *ss, sslBuffer *preamble, const sslBuffer *extensions)
+{
+    SECStatus rv;
+    unsigned int msgLen = preamble->len;
+    msgLen += extensions->len ? (2 + extensions->len) : 0;
+    unsigned int headerLen = IS_DTLS(ss) ? 12 : 4;
+
+    /* Record the message length. */
+    rv = sslBuffer_InsertNumber(preamble, 1, msgLen - headerLen, 3);
+    if (rv != SECSuccess) {
+        return SECFailure; /* code set */
+    }
+    if (IS_DTLS(ss)) {
+        /* Record the (unfragmented) fragment length. */
+        unsigned int offset = 1 /* ch */ + 3 /* len */ +
+                              2 /* seq */ + 3 /* fragment offset */;
+        rv = sslBuffer_InsertNumber(preamble, offset, msgLen - headerLen, 3);
+        if (rv != SECSuccess) {
+            return SECFailure; /* code set */
+        }
+    }
+
+    return SECSuccess;
+}
+
+static SECStatus
+ssl3_AppendCipherSuites(sslSocket *ss, PRBool fallbackSCSV, sslBuffer *buf)
+{
+    SECStatus rv;
+    unsigned int offset;
+    unsigned int i;
+    unsigned int saveLen;
+
+    rv = sslBuffer_Skip(buf, 2, &offset);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    if (ss->ssl3.hs.sendingSCSV) {
+        /* Add the actual SCSV */
+        rv = sslBuffer_AppendNumber(buf, TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
+                                    sizeof(ssl3CipherSuite));
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+    }
+    if (fallbackSCSV) {
+        rv = sslBuffer_AppendNumber(buf, TLS_FALLBACK_SCSV,
+                                    sizeof(ssl3CipherSuite));
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+    }
+
+    saveLen = SSL_BUFFER_LEN(buf);
+    /* CipherSuites are appended to Hello message here */
+    for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
+        ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
+        if (ssl3_config_match(suite, ss->ssl3.policy, &ss->vrange, ss)) {
+            rv = sslBuffer_AppendNumber(buf, suite->cipher_suite,
+                                        sizeof(ssl3CipherSuite));
+            if (rv != SECSuccess) {
+                return SECFailure;
+            }
+        }
+    }
+    if (SSL_ALL_VERSIONS_DISABLED(&ss->vrange) ||
+        (SSL_BUFFER_LEN(buf) - saveLen) == 0) {
+        PORT_SetError(SSL_ERROR_SSL_DISABLED);
+        return SECFailure;
+    }
+
+    return sslBuffer_InsertLength(buf, offset, 2);
+}
+
+SECStatus
+ssl3_CreateClientHelloPreamble(sslSocket *ss, const sslSessionID *sid,
+                               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 && !isEchInner &&
+                          (!realSid || version < sid->version);
+
+    rv = sslBuffer_AppendNumber(&constructed, ssl_hs_client_hello, 1);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = sslBuffer_Skip(&constructed, 3, NULL);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    if (IS_DTLS(ss)) {
+        /* Note that we make an unfragmented message here. We fragment in the
+         * transmission code, if necessary */
+        rv = sslBuffer_AppendNumber(&constructed, ss->ssl3.hs.sendMessageSeq, 2);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+        ss->ssl3.hs.sendMessageSeq++;
+
+        /* 0 is the fragment offset, because it's not fragmented yet */
+        rv = sslBuffer_AppendNumber(&constructed, 0, 3);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+
+        /* Fragment length -- set to the packet length because not fragmented */
+        rv = sslBuffer_Skip(&constructed, 3, NULL);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+    }
+
+    if (ss->firstHsDone) {
+        /* The client hello version must stay unchanged to work around
+         * the Windows SChannel bug described in ssl3_SendClientHello. */
+        PORT_Assert(version == ss->clientHelloVersion);
+    }
+
+    ss->clientHelloVersion = PR_MIN(version, SSL_LIBRARY_VERSION_TLS_1_2);
+    if (IS_DTLS(ss)) {
+        PRUint16 dtlsVersion = dtls_TLSVersionToDTLSVersion(ss->clientHelloVersion);
+        rv = sslBuffer_AppendNumber(&constructed, dtlsVersion, 2);
+    } else {
+        rv = sslBuffer_AppendNumber(&constructed, ss->clientHelloVersion, 2);
+    }
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = sslBuffer_Append(&constructed, client_random, SSL3_RANDOM_LENGTH);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    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)) {
+        /* We're faking session resumption, so rather than create new
+         * randomness, just mix up the client random a little. */
+        PRUint8 buf[SSL3_SESSIONID_BYTES];
+        ssl_MakeFakeSid(ss, buf);
+        rv = sslBuffer_AppendVariable(&constructed, buf, SSL3_SESSIONID_BYTES, 1);
+    } else {
+        rv = sslBuffer_AppendNumber(&constructed, 0, 1);
+    }
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    if (IS_DTLS(ss)) {
+        /* This cookieLen applies to the cookie that appears in the DTLS
+        * ClientHello, which isn't used in DTLS 1.3. */
+        rv = sslBuffer_AppendVariable(&constructed, ss->ssl3.hs.cookie.data,
+                                      ss->ssl3.hs.helloRetry ? 0 : ss->ssl3.hs.cookie.len,
+                                      1);
+        if (rv != SECSuccess) {
+            goto loser;
+        }
+    }
+
+    rv = ssl3_AppendCipherSuites(ss, fallbackSCSV, &constructed);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    /* Compression methods: count is always 1, null compression. */
+    rv = sslBuffer_AppendNumber(&constructed, 1, 1);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+    rv = sslBuffer_AppendNumber(&constructed, ssl_compression_null, 1);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    rv = ssl3_InsertChHeaderSize(ss, &constructed, extensions);
+    if (rv != SECSuccess) {
+        goto loser;
+    }
+
+    *preamble = constructed;
+    return SECSuccess;
+loser:
+    sslBuffer_Clear(&constructed);
+    return SECFailure;
+}
+
 /* Called from ssl3_HandleHelloRequest(),
  *             ssl3_RedoHandshake()
  *             ssl_BeginClientHandshake (when resuming ssl3 session)
  *             dtls_HandleHelloVerifyRequest(with resending=PR_TRUE)
  *
  * The |type| argument indicates what is going on here:
  * - client_hello_initial is set for the very first ClientHello
  * - client_hello_retry indicates that this is a second attempt after receiving
@@ -4915,51 +5178,43 @@ ssl_SetClientHelloSpecVersion(sslSocket 
  * - client_hello_retransmit is used in DTLS when resending
  * - client_hello_renegotiation is used to renegotiate (in TLS <1.3)
  */
 SECStatus
 ssl3_SendClientHello(sslSocket *ss, sslClientHelloType type)
 {
     sslSessionID *sid;
     SECStatus rv;
-    unsigned int i;
-    unsigned int length;
-    unsigned int num_suites;
-    unsigned int actual_count = 0;
     PRBool isTLS = PR_FALSE;
-    PRBool requestingResume = PR_FALSE, fallbackSCSV = PR_FALSE;
+    PRBool requestingResume = PR_FALSE;
     PRBool unlockNeeded = PR_FALSE;
     sslBuffer extensionBuf = SSL_BUFFER_EMPTY;
     PRUint16 version = ss->vrange.max;
     PRInt32 flags;
-    unsigned int cookieLen = ss->ssl3.hs.cookie.len;
+    sslBuffer chBuf = SSL_BUFFER_EMPTY;
 
     SSL_TRC(3, ("%d: SSL3[%d]: send %s ClientHello handshake", SSL_GETPID(),
                 ss->fd, ssl_ClientHelloTypeName(type)));
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(ss->opt.noLocks || ssl_HaveXmitBufLock(ss));
 
     /* shouldn't get here if SSL3 is disabled, but ... */
     if (SSL_ALL_VERSIONS_DISABLED(&ss->vrange)) {
         PR_NOT_REACHED("No versions of SSL 3.0 or later are enabled");
         PORT_SetError(SSL_ERROR_SSL_DISABLED);
         return SECFailure;
     }
 
     /* If we are responding to a HelloRetryRequest, don't reinitialize. We need
      * to maintain the handshake hashes. */
-    if (ss->ssl3.hs.helloRetry) {
-        PORT_Assert(type == client_hello_retry);
-        /* This cookieLen applies to the cookie that appears in the DTLS
-           ClientHello, which isn't used in DTLS 1.3. */
-        cookieLen = 0;
-    } else {
+    if (!ss->ssl3.hs.helloRetry) {
         ssl3_RestartHandshakeHashes(ss);
     }
+    PORT_Assert(!ss->ssl3.hs.helloRetry || type == client_hello_retry);
 
     if (type == client_hello_initial) {
         ssl_SetClientHelloSpecVersion(ss, ss->ssl3.cwSpec);
     }
     /* These must be reset every handshake. */
     ssl3_ResetExtensionData(&ss->xtnData, ss);
     ss->ssl3.hs.sendingSCSV = PR_FALSE;
     ss->ssl3.hs.preliminaryInfo = 0;
@@ -5201,181 +5456,87 @@ ssl3_SendClientHello(sslSocket *ss, sslC
             goto loser;
         }
     }
 
     if (IS_DTLS(ss)) {
         ssl3_DisableNonDTLSSuites(ss);
     }
 
-    /* how many suites are permitted by policy and user preference? */
-    num_suites = count_cipher_suites(ss, ss->ssl3.policy);
-    if (!num_suites) {
-        goto loser; /* count_cipher_suites has set error code. */
-    }
-
-    fallbackSCSV = ss->opt.enableFallbackSCSV && (!requestingResume ||
-                                                  version < sid->version);
-    /* make room for SCSV */
-    if (ss->ssl3.hs.sendingSCSV) {
-        ++num_suites;
-    }
-    if (fallbackSCSV) {
-        ++num_suites;
-    }
-
-    length = sizeof(SSL3ProtocolVersion) + SSL3_RANDOM_LENGTH +
-             1 + /* session id */
-             2 + num_suites * sizeof(ssl3CipherSuite) +
-             1 + 1 /* compression methods */;
-    if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3) {
-        length += sid->u.ssl3.sessionIDLength;
-    } else if (ss->opt.enableTls13CompatMode && !IS_DTLS(ss)) {
-        length += SSL3_SESSIONID_BYTES;
-    }
-    if (IS_DTLS(ss)) {
-        length += 1 + cookieLen;
-    }
-
-    if (extensionBuf.len) {
-        rv = ssl_InsertPaddingExtension(ss, length, &extensionBuf);
-        if (rv != SECSuccess) {
-            goto loser; /* err set by ssl_InsertPaddingExtension */
-        }
-        length += 2 + extensionBuf.len;
-    }
-
-    rv = ssl3_AppendHandshakeHeader(ss, ssl_hs_client_hello, length);
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-
-    if (ss->firstHsDone) {
-        /* The client hello version must stay unchanged to work around
-         * the Windows SChannel bug described above. */
-        PORT_Assert(version == ss->clientHelloVersion);
-    }
-    ss->clientHelloVersion = PR_MIN(version, SSL_LIBRARY_VERSION_TLS_1_2);
-    if (IS_DTLS(ss)) {
-        PRUint16 dtlsVersion;
-
-        dtlsVersion = dtls_TLSVersionToDTLSVersion(ss->clientHelloVersion);
-        rv = ssl3_AppendHandshakeNumber(ss, dtlsVersion, 2);
-    } else {
-        rv = ssl3_AppendHandshakeNumber(ss, ss->clientHelloVersion, 2);
-    }
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-
-    rv = ssl3_AppendHandshake(ss, ss->ssl3.hs.client_random,
-                              SSL3_RANDOM_LENGTH);
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-
-    if (sid->version < SSL_LIBRARY_VERSION_TLS_1_3) {
-        rv = ssl3_AppendHandshakeVariable(
-            ss, sid->u.ssl3.sessionID, sid->u.ssl3.sessionIDLength, 1);
-    } else if (ss->opt.enableTls13CompatMode && !IS_DTLS(ss)) {
-        /* We're faking session resumption, so rather than create new
-         * randomness, just mix up the client random a little. */
-        PRUint8 buf[SSL3_SESSIONID_BYTES];
-        ssl_MakeFakeSid(ss, buf);
-        rv = ssl3_AppendHandshakeVariable(ss, buf, SSL3_SESSIONID_BYTES, 1);
-    } else {
-        rv = ssl3_AppendHandshakeNumber(ss, 0, 1);
-    }
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-
-    if (IS_DTLS(ss)) {
-        rv = ssl3_AppendHandshakeVariable(
-            ss, ss->ssl3.hs.cookie.data, cookieLen, 1);
-        if (rv != SECSuccess) {
-            goto loser; /* err set by ssl3_AppendHandshake* */
-        }
-    }
-
-    rv = ssl3_AppendHandshakeNumber(ss, num_suites * sizeof(ssl3CipherSuite), 2);
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-
-    if (ss->ssl3.hs.sendingSCSV) {
-        /* Add the actual SCSV */
-        rv = ssl3_AppendHandshakeNumber(ss, TLS_EMPTY_RENEGOTIATION_INFO_SCSV,
-                                        sizeof(ssl3CipherSuite));
-        if (rv != SECSuccess) {
-            goto loser; /* err set by ssl3_AppendHandshake* */
-        }
-        actual_count++;
-    }
-    if (fallbackSCSV) {
-        rv = ssl3_AppendHandshakeNumber(ss, TLS_FALLBACK_SCSV,
-                                        sizeof(ssl3CipherSuite));
-        if (rv != SECSuccess) {
-            goto loser; /* err set by ssl3_AppendHandshake* */
-        }
-        actual_count++;
-    }
-    /* CipherSuites are appended to Hello message here */
-    for (i = 0; i < ssl_V3_SUITES_IMPLEMENTED; i++) {
-        ssl3CipherSuiteCfg *suite = &ss->cipherSuites[i];
-        if (ssl3_config_match(suite, ss->ssl3.policy, &ss->vrange, ss)) {
-            actual_count++;
-            if (actual_count > num_suites) {
-                /* set error card removal/insertion error */
-                PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+    rv = ssl3_CreateClientHelloPreamble(ss, sid, requestingResume, version,
+                                        PR_FALSE, &extensionBuf, &chBuf);
+    if (rv != SECSuccess) {
+        goto loser; /* err set by ssl3_CreateClientHelloPreamble. */
+    }
+
+    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. */
+            }
+
+            /* 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 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 = ssl3_AppendHandshakeNumber(ss, suite->cipher_suite,
-                                            sizeof(ssl3CipherSuite));
+        }
+        rv = ssl3_AppendHandshake(ss, chBuf.buf, chBuf.len);
+    } else {
+        rv = tls13_ConstructClientHelloWithEch(ss, sid, !requestingResume, &chBuf, &extensionBuf);
+        if (rv != SECSuccess) {
+            goto loser; /* code set */
+        }
+        rv = ssl3_UpdateOuterHandshakeHashes(ss, chBuf.buf, chBuf.len);
+        if (rv != SECSuccess) {
+            goto loser; /* code set */
+        }
+
+        if (IS_DTLS(ss)) {
+            rv = dtls_StageHandshakeMessage(ss);
             if (rv != SECSuccess) {
-                goto loser; /* err set by ssl3_AppendHandshake* */
-            }
-        }
-    }
-
-    /* if cards were removed or inserted between count_cipher_suites and
-     * generating our list, detect the error here rather than send it off to
-     * the server.. */
-    if (actual_count != num_suites) {
-        /* Card removal/insertion error */
-        PORT_SetError(SSL_ERROR_TOKEN_INSERTION_REMOVAL);
+                goto loser;
+            }
+        }
+        /* 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);
+    }
+    if (rv != SECSuccess) {
         goto loser;
     }
 
-    /* Compression methods: count is always 1, null compression. */
-    rv = ssl3_AppendHandshakeNumber(ss, 1, 1);
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-    rv = ssl3_AppendHandshakeNumber(ss, ssl_compression_null, 1);
-    if (rv != SECSuccess) {
-        goto loser; /* err set by ssl3_AppendHandshake* */
-    }
-
-    if (extensionBuf.len) {
-        /* If we are sending a PSK binder, replace the dummy value. */
-        if (ssl3_ExtensionAdvertised(ss, ssl_tls13_pre_shared_key_xtn)) {
-            PORT_Assert(ss->psk ||
-                        (ss->statelessResume && ss->xtnData.sentSessionTicketInClientHello));
-            PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.psks));
-            rv = tls13_WriteExtensionsWithBinder(ss, &extensionBuf);
-        } else {
-            rv = ssl3_AppendBufferToHandshakeVariable(ss, &extensionBuf, 2);
-        }
-        if (rv != SECSuccess) {
-            goto loser; /* err set by AppendHandshake. */
-        }
-    }
-
-    sslBuffer_Clear(&extensionBuf);
     if (unlockNeeded) {
         /* Note: goto loser can't be used past this point. */
         PR_RWLock_Unlock(sid->u.ssl3.lock);
     }
 
     if (ss->xtnData.sentSessionTicketInClientHello) {
         SSL_AtomicIncrementLong(&ssl3stats.sch_sid_stateless_resumes);
     }
@@ -5396,22 +5557,25 @@ ssl3_SendClientHello(sslSocket *ss, sslC
     if (version >= SSL_LIBRARY_VERSION_TLS_1_3) {
         rv = tls13_MaybeDo0RTTHandshake(ss);
         if (rv != SECSuccess) {
             return SECFailure; /* error code set already. */
         }
     }
 
     ss->ssl3.hs.ws = wait_server_hello;
+    sslBuffer_Clear(&chBuf);
+    sslBuffer_Clear(&extensionBuf);
     return SECSuccess;
 
 loser:
     if (unlockNeeded) {
         PR_RWLock_Unlock(sid->u.ssl3.lock);
     }
+    sslBuffer_Clear(&chBuf);
     sslBuffer_Clear(&extensionBuf);
     return SECFailure;
 }
 
 /* Called from ssl3_HandlePostHelloHandshakeMessage() when it has deciphered a
  * complete ssl3 Hello Request.
  * Caller must hold Handshake and RecvBuf locks.
  */
@@ -6669,16 +6833,17 @@ ssl_CheckServerRandom(sslSocket *ss)
                                        : ss->vrange.max;
 
     if (checkVersion >= SSL_LIBRARY_VERSION_TLS_1_2 &&
         checkVersion > ss->version) {
         /* Both sections use the same sentinel region. */
         PRUint8 *downgrade_sentinel =
             ss->ssl3.hs.server_random +
             SSL3_RANDOM_LENGTH - sizeof(tls12_downgrade_random);
+
         if (!PORT_Memcmp(downgrade_sentinel,
                          tls12_downgrade_random,
                          sizeof(tls12_downgrade_random)) ||
             !PORT_Memcmp(downgrade_sentinel,
                          tls1_downgrade_random,
                          sizeof(tls1_downgrade_random))) {
             return SECFailure;
         }
@@ -6810,27 +6975,25 @@ ssl3_HandleServerHello(sslSocket *ss, PR
         desc = unexpected_message;
         errCode = SSL_ERROR_RX_UNEXPECTED_HELLO_RETRY_REQUEST;
         goto alert_loser;
     }
 
     /* 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;
             errCode = SSL_ERROR_RX_MALFORMED_SERVER_HELLO;
             goto alert_loser;
         }
         if (ss->ssl3.hs.zeroRttState == ssl_0rtt_sent) {
             /* SSL3_SendAlert() will uncache the SID. */
@@ -6896,16 +7059,21 @@ ssl3_HandleServerHello(sslSocket *ss, PR
     if (isHelloRetry) {
         rv = tls13_HandleHelloRetryRequest(ss, savedMsg, savedLength);
         if (rv != SECSuccess) {
             goto loser;
         }
         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) {
         goto alert_loser;
     }
 
     rv = ssl_HashHandshakeMessage(ss, ssl_hs_server_hello,
                                   savedMsg, savedLength);
@@ -7625,16 +7793,22 @@ done:
 SECStatus
 ssl3_CompleteHandleCertificateRequest(sslSocket *ss,
                                       const SSLSignatureScheme *signatureSchemes,
                                       unsigned int signatureSchemeCount,
                                       CERTDistNames *ca_list)
 {
     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);
         PORT_Assert(ss->ssl3.clientPrivateKey == NULL);
         PORT_Assert(ss->ssl3.clientCertificate == NULL);
         PORT_Assert(ss->ssl3.clientCertChain == NULL);
         /* XXX Should pass cert_types and algorithms in this call!! */
         rv = (SECStatus)(*ss->getClientAuthData)(ss->getClientAuthDataArg,
@@ -7707,59 +7881,63 @@ ssl3_CheckFalseStart(sslSocket *ss)
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss));
     PORT_Assert(!ss->ssl3.hs.authCertificatePending);
     PORT_Assert(!ss->ssl3.hs.canFalseStart);
 
     if (!ss->canFalseStartCallback) {
         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);
-        }
-
-        if (!maybeFalseStart) {
+        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 (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;
 }
 
 PRBool
 ssl3_WaitingForServerSecondRound(sslSocket *ss)
 {
     PRBool result;
@@ -8337,22 +8515,19 @@ ssl3_ServerCallSNICallback(sslSocket *ss
                 if (rv != SECSuccess) {
                     errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
                     desc = internal_error;
                     ret = SSL_SNI_SEND_ALERT;
                     break;
                 }
                 /* 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);
                 errCode = SSL_ERROR_INTERNAL_ERROR_ALERT;
                 desc = internal_error;
                 ret = SSL_SNI_SEND_ALERT;
                 break;
             }
@@ -8440,19 +8615,29 @@ ssl3_SelectServerCert(sslSocket *ss)
 
     PORT_SetError(SSL_ERROR_NO_CYPHER_OVERLAP);
     return SECFailure;
 }
 
 static SECStatus
 ssl_GenerateServerRandom(sslSocket *ss)
 {
-    SECStatus rv = ssl3_GetNewRandom(ss->ssl3.hs.server_random);
-    if (rv != SECSuccess) {
-        return SECFailure;
+    SECStatus rv;
+    PRUint8 *downgradeSentinel;
+
+    rv = ssl3_GetNewRandom(ss->ssl3.hs.server_random);
+    if (rv != SECSuccess) {
+        return SECFailure;
+    }
+
+    if (ss->ssl3.hs.echAccepted) {
+        rv = tls13_WriteServerEchSignal(ss);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
     }
 
     if (ss->version == ss->vrange.max) {
         return SECSuccess;
     }
 #ifdef DTLS_1_3_DRAFT_VERSION
     if (IS_DTLS(ss)) {
         return SECSuccess;
@@ -8472,20 +8657,19 @@ ssl_GenerateServerRandom(sslSocket *ss)
      *   44 4F 57 4E 47 52 44 01
      *
      * If negotiating TLS 1.1 or below, TLS 1.3 servers MUST, and TLS 1.2
      * servers SHOULD, set the last 8 bytes of their ServerHello.Random value to
      * the bytes:
      *
      *   44 4F 57 4E 47 52 44 00
      */
-    PRUint8 *downgradeSentinel =
+    downgradeSentinel =
         ss->ssl3.hs.server_random +
         SSL3_RANDOM_LENGTH - sizeof(tls12_downgrade_random);
-
     if (ss->vrange.max >= SSL_LIBRARY_VERSION_TLS_1_2) {
         switch (ss->version) {
             case SSL_LIBRARY_VERSION_TLS_1_2:
                 /* vrange.max > 1.2, since we didn't early exit above. */
                 PORT_Memcpy(downgradeSentinel,
                             tls12_downgrade_random, sizeof(tls12_downgrade_random));
                 break;
             case SSL_LIBRARY_VERSION_TLS_1_1:
@@ -8497,36 +8681,156 @@ ssl_GenerateServerRandom(sslSocket *ss)
                 /* Do not change random. */
                 break;
         }
     }
 
     return SECSuccess;
 }
 
+SECStatus
+ssl3_HandleClientHelloPreamble(sslSocket *ss, PRUint8 **b, PRUint32 *length, SECItem *sidBytes,
+                               SECItem *cookieBytes, SECItem *suites, SECItem *comps)
+{
+    SECStatus rv;
+    PRUint32 tmp;
+    rv = ssl3_ConsumeHandshakeNumber(ss, &tmp, 2, b, length);
+    if (rv != SECSuccess) {
+        return SECFailure; /* malformed, alert already sent */
+    }
+
+    /* Translate the version. */
+    if (IS_DTLS(ss)) {
+        ss->clientHelloVersion = dtls_DTLSVersionToTLSVersion((SSL3ProtocolVersion)tmp);
+    } else {
+        ss->clientHelloVersion = (SSL3ProtocolVersion)tmp;
+    }
+
+    /* Grab the client random data. */
+    rv = ssl3_ConsumeHandshake(
+        ss, ss->ssl3.hs.client_random, SSL3_RANDOM_LENGTH, b, length);
+    if (rv != SECSuccess) {
+        return SECFailure; /* malformed */
+    }
+
+    /* Grab the client's SID, if present. */
+    rv = ssl3_ConsumeHandshakeVariable(ss, sidBytes, 1, b, length);
+    if (rv != SECSuccess) {
+        return SECFailure; /* malformed */
+    }
+
+    /* Grab the client's cookie, if present. It is checked after version negotiation. */
+    if (IS_DTLS(ss)) {
+        rv = ssl3_ConsumeHandshakeVariable(ss, cookieBytes, 1, b, length);
+        if (rv != SECSuccess) {
+            return SECFailure; /* malformed */
+        }
+    }
+
+    /* Grab the list of cipher suites. */
+    rv = ssl3_ConsumeHandshakeVariable(ss, suites, 2, b, length);
+    if (rv != SECSuccess) {
+        return SECFailure; /* malformed */
+    }
+
+    /* Grab the list of compression methods. */
+    rv = ssl3_ConsumeHandshakeVariable(ss, comps, 1, b, length);
+    if (rv != SECSuccess) {
+        return SECFailure; /* malformed */
+    }
+    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.
  */
 static SECStatus
 ssl3_HandleClientHello(sslSocket *ss, PRUint8 *b, PRUint32 length)
 {
     sslSessionID *sid = NULL;
-    PRUint32 tmp;
     unsigned int i;
     SECStatus rv;
+    PRUint32 extensionLength;
     int errCode = SSL_ERROR_RX_MALFORMED_CLIENT_HELLO;
     SSL3AlertDescription desc = illegal_parameter;
     SSL3AlertLevel level = alert_fatal;
-    SSL3ProtocolVersion version;
     TLSExtension *versionExtension;
     SECItem sidBytes = { siBuffer, NULL, 0 };
     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;
 
     SSL_TRC(3, ("%d: SSL3[%d]: handle client_hello handshake",
                 SSL_GETPID(), ss->fd));
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveRecvBufLock(ss));
@@ -8569,71 +8873,30 @@ ssl3_HandleClientHello(sslSocket *ss, PR
      */
     ssl3_ResetExtensionData(&ss->xtnData, ss);
     ss->statelessResume = PR_FALSE;
 
     if (IS_DTLS(ss)) {
         dtls_RehandshakeCleanup(ss);
     }
 
-    rv = ssl3_ConsumeHandshakeNumber(ss, &tmp, 2, &b, &length);
-    if (rv != SECSuccess)
-        goto loser; /* malformed, alert already sent */
-
-    /* Translate the version. */
-    if (IS_DTLS(ss)) {
-        ss->clientHelloVersion = version =
-            dtls_DTLSVersionToTLSVersion((SSL3ProtocolVersion)tmp);
-    } else {
-        ss->clientHelloVersion = version = (SSL3ProtocolVersion)tmp;
-    }
-
-    /* Grab the client random data. */
-    rv = ssl3_ConsumeHandshake(
-        ss, ss->ssl3.hs.client_random, SSL3_RANDOM_LENGTH, &b, &length);
-    if (rv != SECSuccess) {
-        goto loser; /* malformed */
-    }
-
-    /* Grab the client's SID, if present. */
-    rv = ssl3_ConsumeHandshakeVariable(ss, &sidBytes, 1, &b, &length);
-    if (rv != SECSuccess) {
-        goto loser; /* malformed */
-    }
-
-    /* Grab the client's cookie, if present. It is checked after version negotiation. */
-    if (IS_DTLS(ss)) {
-        rv = ssl3_ConsumeHandshakeVariable(ss, &cookieBytes, 1, &b, &length);
-        if (rv != SECSuccess) {
-            goto loser; /* malformed */
-        }
-    }
-
-    /* Grab the list of cipher suites. */
-    rv = ssl3_ConsumeHandshakeVariable(ss, &suites, 2, &b, &length);
-    if (rv != SECSuccess) {
-        goto loser; /* malformed */
-    }
-
-    /* Grab the list of compression methods. */
-    rv = ssl3_ConsumeHandshakeVariable(ss, &comps, 1, &b, &length);
+    rv = ssl3_HandleClientHelloPreamble(ss, &b, &length, &sidBytes,
+                                        &cookieBytes, &suites, &comps);
     if (rv != SECSuccess) {
         goto loser; /* malformed */
     }
 
     /* Handle TLS hello extensions for SSL3 & TLS. We do not know if
      * we are restarting a previous session until extensions have been
      * parsed, since we might have received a SessionTicket extension.
      * Note: we allow extensions even when negotiating SSL3 for the sake
      * of interoperability (and backwards compatibility).
      */
-
     if (length) {
         /* Get length of hello extensions */
-        PRUint32 extensionLength;
         rv = ssl3_ConsumeHandshakeNumber(ss, &extensionLength, 2, &b, &length);
         if (rv != SECSuccess) {
             goto loser; /* alert already sent */
         }
         if (extensionLength != length) {
             errCode = SSL_ERROR_RX_MALFORMED_CLIENT_HELLO;
             desc = decode_error;
             goto alert_loser;
@@ -8652,22 +8915,22 @@ ssl3_HandleClientHello(sslSocket *ss, PR
             errCode = PORT_GetError();
             desc = (errCode == SSL_ERROR_UNSUPPORTED_VERSION) ? protocol_version : illegal_parameter;
             goto alert_loser;
         }
     } else {
         /* The PR_MIN here ensures that we never negotiate 1.3 if the
          * peer didn't offer "supported_versions". */
         rv = ssl3_NegotiateVersion(ss,
-                                   PR_MIN(version,
+                                   PR_MIN(ss->clientHelloVersion,
                                           SSL_LIBRARY_VERSION_TLS_1_2),
                                    PR_TRUE);
         if (rv != SECSuccess) {
-            desc = (version > SSL_LIBRARY_VERSION_3_0) ? protocol_version
-                                                       : handshake_failure;
+            desc = (ss->clientHelloVersion > SSL_LIBRARY_VERSION_3_0) ? protocol_version
+                                                                      : handshake_failure;
             errCode = SSL_ERROR_UNSUPPORTED_VERSION;
             goto alert_loser;
         }
     }
     ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_version;
 
     /* Update the write spec to match the selected version. */
     if (!ss->firstHsDone) {
@@ -8679,74 +8942,33 @@ ssl3_HandleClientHello(sslSocket *ss, PR
     isTLS13 = ss->version >= SSL_LIBRARY_VERSION_TLS_1_3;
     if (isTLS13) {
         if (ss->firstHsDone) {
             desc = unexpected_message;
             errCode = SSL_ERROR_RENEGOTIATION_NOT_ALLOWED;
             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;
-        }
-
-        /* We never send cookies in DTLS 1.2. */
-        if (IS_DTLS(ss) && cookieBytes.len != 0) {
-            goto loser;
-        }
+        rv = tls13_MaybeHandleEch(ss, savedMsg, savedLen, &sidBytes,
+                                  &comps, &cookieBytes, &suites, &echInner);
+        if (rv != SECSuccess) {
+            errCode = PORT_GetError();
+            goto loser; /* code set, alert sent. */
+        }
+    }
+
+    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. */
     rv = ssl3_HandleParsedExtensions(ss, ssl_hs_client_hello);
     ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
     if (rv != SECSuccess) {
         if (PORT_GetError() == SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM) {
             errCode = SSL_ERROR_UNSUPPORTED_SIGNATURE_ALGORITHM;
@@ -8872,31 +9094,36 @@ ssl3_HandleClientHello(sslSocket *ss, PR
     }
 
     if (IS_DTLS(ss)) {
         ssl3_DisableNonDTLSSuites(ss);
         dtls_ReceivedFirstMessageInFlight(ss);
     }
 
     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);
     }
     if (rv != SECSuccess) {
         errCode = PORT_GetError();
         goto loser;
     }
     return SECSuccess;
 
 alert_loser:
     (void)SSL3_SendAlert(ss, level, desc);
 /* FALLTHRU */
 loser:
+    SECITEM_FreeItem(echInner, PR_TRUE);
     PORT_SetError(errCode);
     return SECFailure;
 }
 
 /* unwrap helper function to handle the case where the wrapKey doesn't wind
  * up in the correct token for the master secret */
 PK11SymKey *
 ssl_unwrapSymKey(PK11SymKey *wrapKey,
@@ -9333,16 +9560,18 @@ ssl3_HandleV2ClientHello(sslSocket *ss, 
     rv = ssl3_NegotiateVersion(ss, version, PR_TRUE);
     if (rv != SECSuccess) {
         /* send back which ever alert client will understand. */
         desc = (version > SSL_LIBRARY_VERSION_3_0) ? protocol_version
                                                    : handshake_failure;
         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);
         ssl_SetSpecVersions(ss, ss->ssl3.cwSpec);
         ssl_ReleaseSpecWriteLock(ss);
     }
 
     /* if we get a non-zero SID, just ignore it. */
@@ -12046,16 +12275,31 @@ SECStatus
 ssl_HashHandshakeMessage(sslSocket *ss, SSLHandshakeType ct,
                          const PRUint8 *b, PRUint32 length)
 {
     return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq,
                                        b, length, ssl3_UpdateHandshakeHashes);
 }
 
 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)
 {
     return ssl_HashHandshakeMessageInt(ss, ct, ss->ssl3.hs.recvMessageSeq,
                                        b, length, ssl3_UpdatePostHandshakeHashes);
 }
 
 /* Called from ssl3_HandleHandshake() when it has gathered a complete ssl3
@@ -13177,16 +13421,17 @@ ssl3_InitState(sslSocket *ss)
     }
 
     ss->ssl3.hs.sendingSCSV = PR_FALSE;
     ss->ssl3.hs.preliminaryInfo = 0;
     ss->ssl3.hs.ws = (ss->sec.isServer) ? wait_client_hello : idle_handshake;
 
     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;
         ss->ssl3.hs.rtTimer->timeout = DTLS_RETRANSMIT_INITIAL_MS;
         ss->ssl3.hs.rtRetries = 0;
         ss->ssl3.hs.recvdHighWater = -1;
         PR_INIT_CLIST(&ss->ssl3.hs.lastMessageFlight);
         dtls_SetMTU(ss, 0); /* Set the MTU to the highest plateau */
@@ -13195,16 +13440,18 @@ ssl3_InitState(sslSocket *ss)
     ss->ssl3.hs.currentSecret = NULL;
     ss->ssl3.hs.resumptionMasterSecret = NULL;
     ss->ssl3.hs.dheSecret = NULL;
     ss->ssl3.hs.clientEarlyTrafficSecret = NULL;
     ss->ssl3.hs.clientHsTrafficSecret = NULL;
     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;
     ss->ssl3.hs.messages.space = 0;
 
     ss->ssl3.hs.receivedNewSessionTicket = PR_FALSE;
     PORT_Memset(&ss->ssl3.hs.newSessionTicket, 0,
                 sizeof(ss->ssl3.hs.newSessionTicket));
@@ -13527,22 +13774,28 @@ ssl3_DestroySSL3Info(sslSocket *ss)
 
     /* clean up handshake */
     if (ss->ssl3.hs.md5) {
         PK11_DestroyContext(ss->ssl3.hs.md5, PR_TRUE);
     }
     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);
 
     SECITEM_FreeItem(&ss->ssl3.hs.newSessionTicket.ticket, PR_FALSE);
     SECITEM_FreeItem(&ss->ssl3.hs.srvVirtName, PR_FALSE);
     SECITEM_FreeItem(&ss->ssl3.hs.fakeSid, PR_FALSE);
 
@@ -13551,16 +13804,17 @@ ssl3_DestroySSL3Info(sslSocket *ss)
         dtls_FreeHandshakeMessages(&ss->ssl3.hs.lastMessageFlight);
         if (ss->ssl3.hs.recvdFragments.buf) {
             PORT_Free(ss->ssl3.hs.recvdFragments.buf);
         }
     }
 
     /* Destroy remote extensions */
     ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.remoteExtensions);
+    ssl3_DestroyRemoteExtensions(&ss->ssl3.hs.echOuterExtensions);
     ssl3_DestroyExtensionData(&ss->xtnData);
 
     /* Destroy cipher specs */
     ssl_DestroyCipherSpecs(&ss->ssl3.hs.cipherSpecs);
 
     /* Destroy TLS 1.3 keys */
     if (ss->ssl3.hs.currentSecret)
         PK11_FreeSymKey(ss->ssl3.hs.currentSecret);
@@ -13581,18 +13835,23 @@ ssl3_DestroySSL3Info(sslSocket *ss)
     if (ss->ssl3.hs.earlyExporterSecret)
         PK11_FreeSymKey(ss->ssl3.hs.earlyExporterSecret);
     if (ss->ssl3.hs.exporterSecret)
         PK11_FreeSymKey(ss->ssl3.hs.exporterSecret);
 
     ss->ssl3.hs.zeroRttState = ssl_0rtt_none;
     /* Destroy TLS 1.3 buffered early data. */
     tls13_DestroyEarlyData(&ss->ssl3.hs.bufferedEarlyData);
-    /* Destroy TLS 1.3 PSKs */
+
+    /* 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 */
 }
 
 /*
  * parse the policy value for a single algorithm in a cipher_suite,
  *   return TRUE if we disallow by the cipher suite by policy
  *   (we don't have to parse any more algorithm policies on this cipher suite),
  *  otherwise return FALSE.
  *   1. If we don't have the required policy, disable by default, disallow by
--- a/security/nss/lib/ssl/ssl3ext.c
+++ b/security/nss/lib/ssl/ssl3ext.c
@@ -48,17 +48,16 @@ static const ssl3ExtensionHandler client
     { ssl_extended_master_secret_xtn, &ssl3_HandleExtendedMasterSecretXtn },
     { ssl_signed_cert_timestamp_xtn, &ssl3_ServerHandleSignedCertTimestampXtn },
     { ssl_delegated_credentials_xtn, &tls13_ServerHandleDelegatedCredentialsXtn },
     { ssl_tls13_key_share_xtn, &tls13_ServerHandleKeyShareXtn },
     { ssl_tls13_pre_shared_key_xtn, &tls13_ServerHandlePreSharedKeyXtn },
     { 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 }
 };
 
 /* These two tables are used by the client, to handle server hello
  * extensions. */
 static const ssl3ExtensionHandler serverHelloHandlersTLS[] = {
@@ -69,16 +68,17 @@ static const ssl3ExtensionHandler server
     { ssl_app_layer_protocol_xtn, &ssl3_ClientHandleAppProtoXtn },
     { ssl_use_srtp_xtn, &ssl3_ClientHandleUseSRTPXtn },
     { ssl_cert_status_xtn, &ssl3_ClientHandleStatusRequestXtn },
     { ssl_extended_master_secret_xtn, &ssl3_HandleExtendedMasterSecretXtn },
     { ssl_signed_cert_timestamp_xtn, &ssl3_ClientHandleSignedCertTimestampXtn },
     { 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 }
 };
 
 static const ssl3ExtensionHandler helloRetryRequestHandlers[] = {
     { ssl_tls13_key_share_xtn, tls13_ClientHandleKeyShareXtnHrr },
     { ssl_tls13_cookie_xtn, tls13_ClientHandleHrrCookie },
     { 0, NULL }
@@ -138,17 +138,16 @@ static const sslExtensionBuilder clientH
       /* Some servers (e.g. WebSphere Application Server 7.0 and Tomcat) will
        * time out or terminate the connection if the last extension in the
        * client hello is empty. They are not intolerant of TLS 1.2, so list
        * signature_algorithms at the end. See bug 1243641. */
       { ssl_tls13_supported_versions_xtn, &tls13_ClientSendSupportedVersionsXtn },
       { 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. */
       { ssl_tls13_pre_shared_key_xtn, &tls13_ClientSendPreSharedKeyXtn },
       { 0, NULL }
     };
 
 static const sslExtensionBuilder clientHelloSendersSSL3[] = {
@@ -188,17 +187,18 @@ static const struct {
     { ssl_tls13_key_share_xtn, ssl_ext_native_only },
     { ssl_tls13_pre_shared_key_xtn, ssl_ext_native_only },
     { ssl_tls13_early_data_xtn, ssl_ext_native_only },
     { ssl_tls13_supported_versions_xtn, ssl_ext_native_only },
     { ssl_tls13_cookie_xtn, ssl_ext_native_only },
     { 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
 ssl_GetExtensionSupport(PRUint16 type)
 {
     unsigned int i;
     for (i = 0; i < PR_ARRAY_SIZE(ssl_supported_extensions); ++i) {
         if (type == ssl_supported_extensions[i].type) {
@@ -861,69 +861,105 @@ ssl_CalculatePaddingExtLen(const sslSock
      * terminate the connection if the last ClientHello extension is empty. */
     if (extensionLen < 5) {
         extensionLen = 5;
     }
 
     return extensionLen - 4;
 }
 
+/* Manually insert an extension, retaining the position of the PSK
+ * extension, if present. */
+SECStatus
+ssl3_EmplaceExtension(sslSocket *ss, sslBuffer *buf, PRUint16 exType,
+                      const PRUint8 *data, unsigned int len, PRBool advertise)
+{
+    SECStatus rv;
+    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 + len);
+        if (rv != SECSuccess) {
+            return SECFailure;
+        }
+        PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + len,
+                     buf->buf + ss->xtnData.lastXtnOffset,
+                     tailLen);
+        buf->len = ss->xtnData.lastXtnOffset;
+    } else {
+        tailLen = 0;
+    }
+
+    rv = sslBuffer_AppendNumber(buf, exType, 2);
+    if (rv != SECSuccess) {
+        return SECFailure; /* Code already set. */
+    }
+    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(const sslSocket *ss, unsigned int prefixLen,
+ssl_InsertPaddingExtension(sslSocket *ss, unsigned int prefixLen,
                            sslBuffer *buf)
 {
     static unsigned char padding[252] = { 0 };
     unsigned int paddingLen;
-    unsigned int tailLen;
-    SECStatus rv;
+    /* 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;
     }
 
-    /* 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);
-        if (rv != SECSuccess) {
-            return SECFailure;
-        }
-        PORT_Memmove(buf->buf + ss->xtnData.lastXtnOffset + 4 + paddingLen,
-                     buf->buf + ss->xtnData.lastXtnOffset,
-                     tailLen);
-        buf->len = ss->xtnData.lastXtnOffset;
-    } else {
-        tailLen = 0;
+    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);
     }
-
-    rv = sslBuffer_AppendNumber(buf, ssl_padding_xtn, 2);
-    if (rv != SECSuccess) {
-        return SECFailure; /* Code already set. */
-    }
-    rv = sslBuffer_AppendVariable(buf, padding, paddingLen, 2);
-    if (rv != SECSuccess) {
-        return SECFailure; /* Code already set. */
-    }
-
-    buf->len += tailLen;
-
-    return SECSuccess;
 }
 
 void
 ssl3_DestroyRemoteExtensions(PRCList *list)
 {
     PRCList *cur_p;
 
     while (!PR_CLIST_IS_EMPTY(list)) {
@@ -977,19 +1013,24 @@ ssl3_DestroyExtensionData(TLSExtensionDa
     tls13_DestroyKeyShares(&xtnData->remoteKeyShares);
     SECITEM_FreeItem(&xtnData->certReqContext, PR_FALSE);
     SECITEM_FreeItem(&xtnData->applicationToken, PR_FALSE);
     if (xtnData->certReqAuthorities.arena) {
         PORT_FreeArena(xtnData->certReqAuthorities.arena, PR_FALSE);
         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
  * the starting state. */
 void
 ssl3_ResetExtensionData(TLSExtensionData *xtnData, const sslSocket *ss)
 {
     ssl3_DestroyExtensionData(xtnData);
--- a/security/nss/lib/ssl/ssl3ext.h
+++ b/security/nss/lib/ssl/ssl3ext.h
@@ -4,20 +4,19 @@
  *
  * 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 __ssl3ext_h_
 #define __ssl3ext_h_
 
+#include "pk11hpke.h"
 #include "sslencode.h"
 
-#define TLS13_ESNI_NONCE_SIZE 16
-
 typedef enum {
     sni_nametype_hostname
 } SNINameType;
 typedef struct TLSExtensionDataStr TLSExtensionData;
 
 /* Registerable callback function that either appends extension to buffer
  * or returns length of data that it would have appended.
  */
@@ -93,56 +92,57 @@ struct TLSExtensionDataStr {
     /* In a client: if the server supports Next Protocol Negotiation, then
      * this is the protocol that was negotiated.
      */
     SECItem nextProto;
     SSLNextProtoState nextProtoState;
 
     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. */
     SECItem pskBinder;                     /* The binder for the first PSK. */
     unsigned int pskBindersLen;            /* The length of the binders. */
     PRUint32 ticketAge;                    /* Used to accept early data. */
     SECItem cookie;                        /* HRR Cookie. */
     const sslNamedGroupDef *selectedGroup; /* For HRR. */
     /* The application token contains a value that was passed to the client via
      * a session ticket, or the cookie in a HelloRetryRequest. */
     SECItem applicationToken;
 
     /* 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
      * |tls13_ReadDelegatedCredential|.
      */
     sslDelegatedCredential *peerDelegCred;
     /* Whether the peer requested a delegated credential. */
     PRBool peerRequestedDelegCred;
     /* Whether the host is committed to using a delegated credential. Set by
      * |tls13_MaybeSetDelegatedCredential|.
      */
     PRBool sendingDelegCredToPeer;
 
     /* 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 {
     PRCList link;  /* The linked list link */
     PRUint16 type; /* Extension type */
     SECItem data;  /* Pointers into the handshake data. */
 } TLSExtension;
 
@@ -160,32 +160,35 @@ SECStatus ssl3_HandleExtensions(sslSocke
                                 SSLHandshakeType handshakeMessage);
 SECStatus ssl3_ParseExtensions(sslSocket *ss,
                                PRUint8 **b, PRUint32 *length);
 SECStatus ssl3_HandleParsedExtensions(sslSocket *ss,
                                       SSLHandshakeType handshakeMessage);
 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);
 
 PRBool ssl3_ExtensionNegotiated(const sslSocket *ss, PRUint16 ex_type);
 PRBool ssl3_ExtensionAdvertised(const sslSocket *ss, PRUint16 ex_type);
 
 SECStatus ssl3_RegisterExtensionSender(const sslSocket *ss,
                                        TLSExtensionData *xtnData,
                                        PRUint16 ex_type,
                                        sslExtensionBuilderFunc cb);
 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. */
 void ssl3_ExtSendAlert(const sslSocket *ss, SSL3AlertLevel level,
                        SSL3AlertDescription desc);
 void ssl3_ExtDecodeError(const sslSocket *ss);
 SECStatus ssl3_ExtConsumeHandshake(const sslSocket *ss, void *v, PRUint32 bytes,
                                    PRUint8 **b, PRUint32 *length);
--- a/security/nss/lib/ssl/ssl3exthandle.c
+++ b/security/nss/lib/ssl/ssl3exthandle.c
@@ -10,17 +10,17 @@
 #include "sslproto.h"
 #include "sslimpl.h"
 #include "pk11pub.h"
 #include "blapit.h"
 #include "prinit.h"
 #include "selfencrypt.h"
 #include "ssl3ext.h"
 #include "ssl3exthandle.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
 #include "tls13exthandle.h" /* For tls13_ServerSendStatusRequestXtn. */
 
 PRBool
 ssl_ShouldSendSNIExtension(const sslSocket *ss, const char *url)
 {
     PRNetAddr netAddr;
 
     /* must have a hostname */
@@ -37,23 +37,21 @@ ssl_ShouldSendSNIExtension(const sslSock
 }
 
 /* Format an SNI extension, using the name from the socket's URL,
  * unless that name is a dotted decimal string.
  * Used by client and server.
  */
 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) {
         return SECFailure;
     }
     /* Name Type (sni_host_name) */
     rv = sslBuffer_AppendNumber(buf, 0, 1);
     if (rv != SECSuccess) {
@@ -71,27 +69,25 @@ ssl3_ClientFormatServerNameXtn(const ssl
 SECStatus
 ssl3_ClientSendServerNameXtn(const sslSocket *ss, TLSExtensionData *xtnData,
                              sslBuffer *buf, PRBool *added)
 {
     SECStatus rv;
 
     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;
     }
 
     *added = PR_TRUE;
     return SECSuccess;
 }
 
@@ -102,23 +98,16 @@ ssl3_HandleServerNameXtn(const sslSocket
     SECItem *names = NULL;
     PRUint32 listLenBytes = 0;
     SECStatus rv;
 
     if (!ss->sec.isServer) {
         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) {
         return SECSuccess;
     }
 
     /* length of server_name_list */
     rv = ssl3_ExtConsumeHandshakeNumber(ss, &listLenBytes, 2, &data->data, &data->len);
@@ -318,21 +307,21 @@ ssl3_SelectAppProtocol(const sslSocket *
     rv = ssl3_ValidateAppProtocol(data->data, data->len);
     if (rv != SECSuccess) {
         ssl3_ExtSendAlert(ss, alert_fatal, decode_error);
         PORT_SetError(SSL_ERROR_NEXT_PROTOCOL_DATA_INVALID);
         return rv;
     }
 
     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,
                                result.data, &result.len, sizeof(resultBuffer));
     if (rv != SECSuccess) {
         /* Expect callback to call PORT_SetError() */
         ssl3_ExtSendAlert(ss, alert_fatal, internal_error);
         return SECFailure;
--- a/security/nss/lib/ssl/ssl3exthandle.h
+++ b/security/nss/lib/ssl/ssl3exthandle.h
@@ -88,17 +88,17 @@ SECStatus ssl3_SendExtendedMasterSecretX
                                            sslBuffer *buf, PRBool *added);
 SECStatus ssl3_HandleExtendedMasterSecretXtn(const sslSocket *ss,
                                              TLSExtensionData *xtnData,
                                              SECItem *data);
 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,
                                        sslBuffer *buf, PRBool *added);
 SECStatus ssl3_HandleServerNameXtn(const sslSocket *ss,
                                    TLSExtensionData *xtnData,
                                    SECItem *data);
 SECStatus ssl_HandleSupportedGroupsXtn(const sslSocket *ss,
--- a/security/nss/lib/ssl/ssl3prot.h
+++ b/security/nss/lib/ssl/ssl3prot.h
@@ -71,16 +71,17 @@ typedef enum {
     missing_extension = 109,
     unsupported_extension = 110,
     certificate_unobtainable = 111,
     unrecognized_name = 112,
     bad_certificate_status_response = 113,
     bad_certificate_hash_value = 114,
     certificate_required = 116,
     no_application_protocol = 120,
+    ech_required = 121,
 
     /* invalid alert */
     no_alert = 256
 } SSL3AlertDescription;
 
 typedef PRUint8 SSL3Random[SSL3_RANDOM_LENGTH];
 
 typedef struct {
--- a/security/nss/lib/ssl/sslencode.c
+++ b/security/nss/lib/ssl/sslencode.c
@@ -160,16 +160,33 @@ sslBuffer_InsertLength(sslBuffer *b, uns
         PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
         return SECFailure;
     }
 
     ssl_EncodeUintX(SSL_BUFFER_BASE(b) + at, len, size);
     return SECSuccess;
 }
 
+SECStatus
+sslBuffer_InsertNumber(sslBuffer *b, unsigned int at,
+                       PRUint64 v, unsigned int size)
+{
+    PORT_Assert(b->len >= at + size);
+    PORT_Assert(b->space >= at + size);
+
+    PORT_Assert(size <= 4 && size > 0);
+    if (v >= (1ULL << (8 * size))) {
+        PORT_SetError(SEC_ERROR_LIBRARY_FAILURE);
+        return SECFailure;
+    }
+
+    ssl_EncodeUintX(SSL_BUFFER_BASE(b) + at, v, size);
+    return SECSuccess;
+}
+
 void
 sslBuffer_Clear(sslBuffer *b)
 {
     if (!b->fixed) {
         if (b->buf) {
             PORT_Free(b->buf);
             b->buf = NULL;
         }
@@ -242,18 +259,18 @@ sslRead_ReadNumber(sslReader *reader, un
 /**************************************************************************
  * Append Handshake functions.
  * All these functions set appropriate error codes.
  * Most rely on ssl3_AppendHandshake to set the error code.
  **************************************************************************/
 #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;
     SECStatus rv;
 
     PORT_Assert(ss->opt.noLocks || ssl_HaveSSL3HandshakeLock(ss)); /* protects sendBuf. */
 
     if (!bytes)
@@ -262,17 +279,18 @@ ssl3_AppendHandshake(sslSocket *ss, cons
         rv = sslBuffer_Grow(&ss->sec.ci.sendBuf, PR_MAX(MIN_SEND_BUF_LENGTH,
                                                         PR_MIN(MAX_SEND_BUF_LENGTH, ss->sec.ci.sendBuf.len + bytes)));
         if (rv != SECSuccess)
             return SECFailure; /* sslBuffer_Grow sets a memory error code. */
         room = ss->sec.ci.sendBuf.space - ss->sec.ci.sendBuf.len;
     }
 
     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 */
     }
 
     while (bytes > room) {
         if (room > 0)
             PORT_Memcpy(ss->sec.ci.sendBuf.buf + ss->sec.ci.sendBuf.len, src,
@@ -288,16 +306,28 @@ ssl3_AppendHandshake(sslSocket *ss, cons
         PORT_Assert(ss->sec.ci.sendBuf.len == 0);
     }
     PORT_Memcpy(ss->sec.ci.sendBuf.buf + ss->sec.ci.sendBuf.len, src, bytes);
     ss->sec.ci.sendBuf.len += bytes;
     return SECSuccess;
 }
 
 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)];
     SSL_TRC(60, ("%d: number:", SSL_GETPID()));
     ssl_EncodeUintX(b, num, lenSize);
     return ssl3_AppendHandshake(ss, b, lenSize);
 }
 
--- a/security/nss/lib/ssl/sslencode.h
+++ b/security/nss/lib/ssl/sslencode.h
@@ -22,16 +22,20 @@ typedef struct sslBufferStr {
 #define SSL_BUFFER_EMPTY     \
     {                        \
         NULL, 0, 0, PR_FALSE \
     }
 #define SSL_BUFFER_FIXED(b, maxlen) \
     {                               \
         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)
 #define SSL_BUFFER_NEXT(b) ((b)->buf + (b)->len)
 #define SSL_BUFFER_SPACE(b) ((b)->space - (b)->len)
 
 SECStatus sslBuffer_Grow(sslBuffer *b, unsigned int newLen);
 SECStatus sslBuffer_Append(sslBuffer *b, const void *data, unsigned int len);
@@ -40,20 +44,24 @@ SECStatus sslBuffer_AppendVariable(sslBu
                                    unsigned int len, unsigned int size);
 SECStatus sslBuffer_AppendBuffer(sslBuffer *b, const sslBuffer *append);
 SECStatus sslBuffer_AppendBufferVariable(sslBuffer *b, const sslBuffer *append,
                                          unsigned int size);
 SECStatus sslBuffer_Skip(sslBuffer *b, unsigned int size,
                          unsigned int *savedOffset);
 SECStatus sslBuffer_InsertLength(sslBuffer *b, unsigned int at,
                                  unsigned int size);
+SECStatus sslBuffer_InsertNumber(sslBuffer *b, unsigned int at,
+                                 PRUint64 v, unsigned int size);
 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,
                                      unsigned int lenSize);
 SECStatus ssl3_AppendHandshakeVariable(sslSocket *ss, const PRUint8 *src,
                                        unsigned int bytes, unsigned int lenSize);
 SECStatus ssl3_AppendBufferToHandshake(sslSocket *ss, sslBuffer *buf);
 SECStatus ssl3_AppendBufferToHandshakeVariable(sslSocket *ss, sslBuffer *buf,
--- a/security/nss/lib/ssl/sslerr.h
+++ b/security/nss/lib/ssl/sslerr.h
@@ -271,15 +271,28 @@ typedef enum {
     SSL_ERROR_MISSING_POST_HANDSHAKE_AUTH_EXTENSION = (SSL_ERROR_BASE + 180),
     SSL_ERROR_RX_CERTIFICATE_REQUIRED_ALERT = (SSL_ERROR_BASE + 181),
     SSL_ERROR_DC_CERT_VERIFY_ALG_MISMATCH = (SSL_ERROR_BASE + 182),
     SSL_ERROR_DC_BAD_SIGNATURE = (SSL_ERROR_BASE + 183),
     SSL_ERROR_DC_INVALID_KEY_USAGE = (SSL_ERROR_BASE + 184),
     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 */
 
 #endif /* __SSL_ERR_H_ */
--- a/security/nss/lib/ssl/sslexp.h
+++ b/security/nss/lib/ssl/sslexp.h
@@ -497,73 +497,96 @@ typedef SECStatus(PR_CALLBACK *SSLResump
 /* TLS 1.3 allows a server to set a limit on the number of bytes of early data
  * that can be received. This allows that limit to be set. This function has no
  * effect on a client. */
 #define SSL_SetMaxEarlyDataSize(fd, size)                    \
     SSL_EXPERIMENTAL_API("SSL_SetMaxEarlyDataSize",          \
                          (PRFileDesc * _fd, PRUint32 _size), \
                          (fd, size))
 
-/* Set the ESNI key pair on a socket (server side)
- *
- * fd -- the socket
- * 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",                              \
-                         (PRFileDesc * _fd,                                 \
-                          SECKEYPrivateKey * _privKey,                      \
-                          const PRUint8 *_record, unsigned int _recordLen), \
-                         (fd, privKey,                                      \
-                          record, recordLen))
+/* 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))
 
-/* Set the ESNI keys on a client
+/* 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
- * ensikeys/esniKeysLen -- the ESNI key structure (not base64)
- * dummyESNI -- the dummy ESNI to use (if any)
+ * pubKey -- the server's SECKEYPublicKey for HPKE/ECH.
+ * privateKey -- the server's SECKEYPrivateKey for HPKE/ECH.
+ * record/recordLen -- the encoded DNS record (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_SetServerEchConfigs(fd, pubKey,                                 \
+                                privKey, record, recordLen)                 \
+    SSL_EXPERIMENTAL_API("SSL_SetServerEchConfigs",                         \
+                         (PRFileDesc * _fd,                                 \
+                          const SECKEYPublicKey *_pubKey,                   \
+                          const SECKEYPrivateKey *_privKey,                 \
+                          const PRUint8 *_record, unsigned int _recordLen), \
+                         (fd, pubKey, privKey,                              \
+                          record, recordLen))
+
+/* Set ECHConfig(s) on a client. The first supported ECHConfig will be used.
+ *
+ * fd -- the socket
+ * echConfigs/echConfigsLen -- the ECHConfigs structure (not base64)
+ */
+#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.
  *
  * SSLSecretCallback is called with the current epoch and the corresponding
  * secret; this matches the epoch used in DTLS 1.3, even if the socket is
  * operating in stream mode:
  *
@@ -996,12 +1019,15 @@ typedef struct SSLMaskingContextStr {
                          (PRFileDesc * _fd, const PRUint8 *_identity, \
                           unsigned int _identityLen),                 \
                          (fd, identity, identityLen))
 
 /* Deprecated experimental APIs */
 #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
 
 #endif /* __sslexp_h_ */
--- a/security/nss/lib/ssl/sslimpl.h
+++ b/security/nss/lib/ssl/sslimpl.h
@@ -32,16 +32,18 @@
 #include "prclist.h"
 #include "private/pprthred.h"
 
 #include "sslt.h" /* for some formerly private types, now public */
 
 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;
 typedef struct TLS13KeyShareEntryStr TLS13KeyShareEntry;
 
 #include "sslencode.h"
 #include "sslexp.h"
 #include "ssl3ext.h"
@@ -279,16 +281,17 @@ typedef struct sslOptionsStr {
     unsigned int enableTls13CompatMode : 1;
     unsigned int enableDtlsShortHeader : 1;
     unsigned int enableHelloDowngradeCheck : 1;
     unsigned int enableV2CompatibleHello : 1;
     unsigned int enablePostHandshakeAuth : 1;
     unsigned int enableDelegatedCredentials : 1;
     unsigned int enableDtls13VersionCompat : 1;
     unsigned int suppressEndOfEarlyData : 1;
+    unsigned int enableTls13GreaseEch : 1;
 } sslOptions;
 
 typedef enum { sslHandshakingUndetermined = 0,
                sslHandshakingAsClient,
                sslHandshakingAsServer
 } sslHandshakingType;
 
 #define SSL_LOCK_RANK_SPEC 255
@@ -606,27 +609,36 @@ typedef struct {
 
 /*
 ** This is the "hs" member of the "ssl3" struct.
 ** This entire struct is protected by ssl3HandshakeLock
 */
 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;
     ssl3CipherSuite cipher_suite;
     const ssl3CipherSuiteDef *suite_def;
     sslBuffer msg_body; /* protected by recvBufLock */
                         /* partial handshake message from record layer */
     unsigned int header_bytes;
@@ -657,17 +669,18 @@ typedef struct SSL3HandshakeStateStr {
      * or ssl3_AlwaysFail */
     sslRestartTarget restartTarget;
 
     PRBool canFalseStart; /* Can/did we False Start */
     /* Which preliminaryinfo values have been set. */
     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
                                     * number */
     PRCList lastMessageFlight; /* The last message flight we
                                     * sent */
     PRUint16 maxMessageSent;   /* The largest message we sent */
     PRUint16 recvMessageSeq;   /* The receiving message sequence
@@ -712,35 +725,42 @@ typedef struct SSL3HandshakeStateStr {
                                            * before the handshake started. */
     PRBool rejectCcs;                     /* Excessive ChangeCipherSpecs are rejected. */
     PRBool clientCertRequested;           /* True if CertificateRequest received. */
     PRBool endOfFlight;                   /* Processed a full flight (DTLS 1.3). */
     ssl3KEADef kea_def_mutable;           /* Used to hold the writable kea_def
                                            * 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.
      * Only after it receives a message from the client's second flight does it
      * set the value to something resembling an RTT estimate. */
     PRTime rttEstimate;
 
     /* The following lists contain DTLSHandshakeRecordEntry */
     PRCList dtlsSentHandshake; /* Used to map records to handshake fragments. */
     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)
 
 /*
 ** This is the "ssl3" struct, as in "ss->ssl3".
 ** note:
 ** usually,   crSpec == cwSpec and prSpec == pwSpec.
 ** Sometimes, crSpec == pwSpec and prSpec == cwSpec.
 ** But there are never more than 2 actual specs.
@@ -1096,19 +1116,20 @@ struct sslSocketStr {
      */
     /* True when the current session is a stateless resume. */
     PRBool statelessResume;
     TLSExtensionData xtnData;
 
     /* 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;
 
     /* An out-of-band PSK. */
     sslPsk *psk;
 };
 
@@ -1257,16 +1278,20 @@ extern SECStatus ssl3_UpdatePostHandshak
                                                 unsigned int l);
 SECStatus
 ssl_HashHandshakeMessageInt(sslSocket *ss, SSLHandshakeType type,
                             PRUint32 dtlsSeq,
                             const PRUint8 *b, PRUint32 length,
                             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);
 
 /* Returns PR_TRUE if we are still waiting for the server to complete its
  * response to our client second round. Once we've received the Finished from
  * the server then there is no need to check false start.
  */
 extern PRBool ssl3_WaitingForServerSecondRound(sslSocket *ss);
@@ -1433,16 +1458,21 @@ extern SECStatus ssl3_DecodeError(sslSoc
 extern SECStatus ssl3_AuthCertificateComplete(sslSocket *ss, PRErrorCode error);
 
 /*
  * for dealing with SSL 3.0 clients sending SSL 2.0 format hellos
  */
 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);
 
 /*
  * input into the SSL3 machinery from the actualy network reading code
  */
 SECStatus ssl3_HandleRecord(sslSocket *ss, SSL3Ciphertext *cipher);
 SECStatus ssl3_HandleNonApplicationData(sslSocket *ss, SSLContentType rType,
                                         DTLSEpoch epoch,
@@ -1674,16 +1704,17 @@ extern SECStatus ssl_InitSessionCacheLoc
 extern SECStatus ssl_FreeSessionCacheLocks(void);
 
 CK_MECHANISM_TYPE ssl3_Alg2Mech(SSLCipherAlgorithm calg);
 SECStatus ssl3_NegotiateCipherSuiteInner(sslSocket *ss, const SECItem *suites,
                                          PRUint16 version, PRUint16 *suitep);
 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,
                                          PRUint8 *b, PRUint32 length);
 void ssl3_SendAlertForCertError(sslSocket *ss, PRErrorCode errCode);
 SECStatus ssl3_HandleNoCertificate(sslSocket *ss);
 SECStatus ssl3_SendEmptyCertificate(sslSocket *ss);
 void ssl3_CleanupPeerCerts(sslSocket *ss);
@@ -1720,16 +1751,17 @@ SECStatus ssl3_ComputeHandshakeHashes(ss
                                       SSL3Hashes *hashes,
                                       PRUint32 sender);
 SECStatus ssl_CreateECDHEphemeralKeyPair(const sslSocket *ss,
                                          const sslNamedGroupDef *ecGroup,
                                          sslEphemeralKeyPair **keyPair);
 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,
                                 void *pwArg);
 SECStatus ssl3_FillInCachedSID(sslSocket *ss, sslSessionID *sid,
                                PK11SymKey *secret);
 const ssl3CipherSuiteDef *ssl_LookupCipherSuiteDef(ssl3CipherSuite suite);
 const ssl3CipherSuiteCfg *ssl_LookupCipherSuiteCfg(ssl3CipherSuite suite,
@@ -1909,16 +1941,18 @@ SECStatus SSLExp_CreateVariantMaskingCon
                                              SSLMaskingContext **ctx);
 
 SECStatus SSLExp_CreateMask(SSLMaskingContext *ctx, const PRUint8 *sample,
                             unsigned int sampleLen, PRUint8 *mask,
                             unsigned int len);
 
 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)
 #define SSL_GETPID getpid
 #elif defined(WIN32)
 extern int __cdecl _getpid(void);
 #define SSL_GETPID _getpid
 #else
--- a/security/nss/lib/ssl/sslinfo.c
+++ b/security/nss/lib/ssl/sslinfo.c
@@ -84,16 +84,17 @@ SSL_GetChannelInfo(PRFileDesc *fd, SSLCh
         if (inf.resumed) {
             inf.pskType = ssl_psk_resume;
         } else if (inf.authType == ssl_auth_psk) {
             inf.pskType = ssl_psk_external;
         } else {
             inf.pskType = ssl_psk_none;
         }
         inf.peerDelegCred = tls13_IsVerifyingWithDelegatedCredential(ss);
+        inf.echAccepted = ss->ssl3.hs.echAccepted;
 
         if (sid) {
             unsigned int sidLen;
 
             inf.creationTime = sid->creationTime / PR_USEC_PER_SEC;
             inf.lastAccessTime = sid->lastAccessTime / PR_USEC_PER_SEC;
             inf.expirationTime = sid->expirationTime / PR_USEC_PER_SEC;
             inf.extendedMasterSecretUsed =
@@ -166,16 +167,19 @@ SSL_GetPreliminaryChannelInfo(PRFileDesc
     } else {
         inf.maxEarlyDataSize = 0;
     }
     inf.zeroRttCipherSuite = ss->ssl3.hs.zeroRttSuite;
 
     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;
 }
 
 /* name */
 #define CS_(x) x, #x
 #define CS(x) CS_(TLS_##x)
--- a/security/nss/lib/ssl/sslsecur.c
+++ b/security/nss/lib/ssl/sslsecur.c
@@ -168,19 +168,28 @@ SSL_ResetHandshake(PRFileDesc *s, PRBool
     ssl_ResetSecurityInfo(&ss->sec, PR_TRUE);
     status = ssl_CreateSecurityInfo(ss);
     ssl_ReleaseXmitBufLock(ss);
 
     ssl_ReleaseSSL3HandshakeLock(ss);
     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));
 
 loser:
     SSL_UNLOCK_WRITER(ss);
     SSL_UNLOCK_READER(ss);
 
     return status;
--- a/security/nss/lib/ssl/sslsock.c
+++ b/security/nss/lib/ssl/sslsock.c
@@ -14,17 +14,17 @@
 #include "sslexp.h"
 #include "sslimpl.h"
 #include "sslproto.h"
 #include "nspr.h"
 #include "private/pprio.h"
 #include "nss.h"
 #include "pk11pqg.h"
 #include "pk11pub.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
 #include "tls13psk.h"
 #include "tls13subcerts.h"
 
 static const sslSocketOps ssl_default_ops = { /* No SSL. */
                                               ssl_DefConnect,
                                               NULL,
                                               ssl_DefBind,
                                               ssl_DefListen,
@@ -87,17 +87,18 @@ static sslOptions ssl_defaults = {
     .requireDHENamedGroups = PR_FALSE,
     .enable0RttData = PR_FALSE,
     .enableTls13CompatMode = PR_FALSE,
     .enableDtls13VersionCompat = PR_FALSE,
     .enableDtlsShortHeader = PR_FALSE,
     .enableHelloDowngradeCheck = PR_FALSE,
     .enableV2CompatibleHello = PR_FALSE,
     .enablePostHandshakeAuth = PR_FALSE,
-    .suppressEndOfEarlyData = PR_FALSE
+    .suppressEndOfEarlyData = PR_FALSE,
+    .enableTls13GreaseEch = PR_FALSE
 };
 
 /*
  * default range of enabled SSL/TLS protocols
  */
 static SSLVersionRange versions_defaults_stream = {
     SSL_LIBRARY_VERSION_TLS_1_0,
     SSL_LIBRARY_VERSION_TLS_1_3
@@ -366,22 +367,28 @@ ssl_DupSocket(sslSocket *os)
         ss->nextProtoArg = os->nextProtoArg;
         PORT_Memcpy((void *)ss->namedGroupPreferences,
                     os->namedGroupPreferences,
                     sizeof(ss->namedGroupPreferences));
         ss->additionalShares = os->additionalShares;
         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. */
             if (!ss->antiReplay) {
                 goto loser;
             }
         }
         if (os->psk) {
@@ -473,23 +480,23 @@ ssl_DestroySocketContents(sslSocket *ss)
     ssl_FreeEphemeralKeyPairs(ss);
     SECITEM_FreeItem(&ss->opt.nextProtoNego, PR_FALSE);
     ssl3_FreeSniNameArray(&ss->xtnData);
 
     ssl_ClearPRCList(&ss->ssl3.hs.dtlsSentHandshake, NULL);
     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);
 }
 
 /*
  * free an sslSocket struct, and all the stuff that hangs off of it
  */
 void
 ssl_FreeSocket(sslSocket *ss)
 {
@@ -2373,16 +2380,17 @@ SSL_GetSRTPCipher(PRFileDesc *fd, PRUint
     return SECSuccess;
 }
 
 PRFileDesc *
 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);
         return NULL;
     }
     sm = ssl_FindSocket(model);
     if (sm == NULL) {
         SSL_DBG(("%d: SSL[%d]: bad model socket in ssl_ReconfigFD",
@@ -2442,17 +2450,16 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
     while (!PR_CLIST_IS_EMPTY(&ss->extensionHooks)) {
         cursor = PR_LIST_TAIL(&ss->extensionHooks);
         PR_REMOVE_LINK(cursor);
         PORT_Free(cursor);
     }
     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,
                                        hook->handler, hook->handlerArg);
         if (rv != SECSuccess) {
             return NULL;
         }
     }
@@ -2468,22 +2475,29 @@ SSL_ReconfigFD(PRFileDesc *model, PRFile
             CERT_FreeDistNames(ss->ssl3.ca_list);
         }
         ss->ssl3.ca_list = CERT_DupDistNames(sm->ssl3.ca_list);
         if (!ss->ssl3.ca_list) {
             return NULL;
         }
     }
 
-    /* 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;
         }
     }
 
     /* Copy anti-replay context. */
     if (ss->antiReplay) {
         tls13_ReleaseAntiReplayContext(ss->antiReplay);
         ss->antiReplay = NULL;
@@ -4156,16 +4170,17 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
     ss->rTimeout = PR_INTERVAL_NO_TIMEOUT;
     ss->wTimeout = PR_INTERVAL_NO_TIMEOUT;
     ss->cTimeout = PR_INTERVAL_NO_TIMEOUT;
     ss->url = NULL;
 
     PR_INIT_CLIST(&ss->serverCerts);
     PR_INIT_CLIST(&ss->ephemeralKeyPairs);
     PR_INIT_CLIST(&ss->extensionHooks);
+    PR_INIT_CLIST(&ss->echConfigs);
 
     ss->dbHandle = CERT_GetDefaultCertDB();
 
     /* Provide default implementation of hooks */
     ss->authCertificate = SSL_AuthCertificate;
     ss->authCertificateArg = (void *)ss->dbHandle;
     ss->sniSocketConfig = NULL;
     ss->sniSocketConfigArg = NULL;
@@ -4189,17 +4204,18 @@ ssl_NewSocket(PRBool makeLocks, SSLProto
     PR_INIT_CLIST(&ss->ssl3.hs.cipherSpecs);
     PR_INIT_CLIST(&ss->ssl3.hs.bufferedEarlyData);
     ssl3_InitExtensionData(&ss->xtnData, ss);
     PR_INIT_CLIST(&ss->ssl3.hs.dtlsSentHandshake);
     PR_INIT_CLIST(&ss->ssl3.hs.dtlsRcvdHandshake);
     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;
 
     if (makeLocks) {
         rv = ssl_MakeLocks(ss);
         if (rv != SECSuccess)
             goto loser;
     }
@@ -4272,44 +4288,47 @@ struct {
     EXP(CreateAntiReplayContext),
     EXP(CreateMask),
     EXP(CreateMaskingContext),
     EXP(CreateVariantMaskingContext),
     EXP(DelegateCredential),
     EXP(DestroyAead),
     EXP(DestroyMaskingContext),
     EXP(DestroyResumptionTokenInfo),
-    EXP(EnableESNI),
-    EXP(EncodeESNIKeys),
+    EXP(EnableTls13GreaseEch),
+    EXP(EncodeEchConfig),
     EXP(GetCurrentEpoch),
+    EXP(GetEchRetryConfigs),
     EXP(GetExtensionSupport),
     EXP(GetResumptionTokenInfo),
     EXP(HelloRetryRequestCallback),
     EXP(InstallExtensionHooks),
     EXP(HkdfExtract),
     EXP(HkdfExpandLabel),
     EXP(HkdfExpandLabelWithMech),
     EXP(HkdfVariantExpandLabel),
     EXP(HkdfVariantExpandLabelWithMech),
     EXP(KeyUpdate),
     EXP(MakeAead),
     EXP(MakeVariantAead),
     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 }
 };
 #undef EXP
 #undef PUB
 
 void *
@@ -4337,16 +4356,27 @@ ssl_ClearPRCList(PRCList *list, void (*f
         if (f) {
             f(cursor);
         }
         PORT_Free(cursor);
     }
 }
 
 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);
     if (!ss) {
         return SECFailure;
     }
     ss->opt.enableDtls13VersionCompat = enabled;
     return SECSuccess;
--- a/security/nss/lib/ssl/sslt.h
+++ b/security/nss/lib/ssl/sslt.h
@@ -363,30 +363,36 @@ typedef struct SSLChannelInfoStr {
      * authentication.
      */
     PRBool peerDelegCred;
 
     /* The following fields were added in NSS 3.54. */
     /* 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;
 
 /* Preliminary channel info */
 #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
      * the |len| argument and the length of the struct used by NSS.
      * Callers must ensure the application uses a version of NSS that
      * isn't older than the version used at compile time. */
     PRUint32 length;
     /* A bitfield over SSLPreliminaryValueSet that describes which
@@ -424,16 +430,22 @@ typedef struct SSLPreliminaryChannelInfo
      * the CertificateVerify message. If Delegated Credentials are being used,
      * this is the DC-contained SPKI, else the EE-cert SPKI. These fields are
      * valid only after the Certificate message is handled. This can be determined
      * by checking the valuesSet field against |ssl_preinfo_peer_auth|. */
     PRBool peerDelegCred;
     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;
 
 typedef struct SSLCipherSuiteInfoStr {
     /* On return, SSL_GetCipherSuitelInfo sets |length| to the smaller of
      * the |len| argument and the length of the struct used by NSS.
      * Callers must ensure the application uses a version of NSS that
@@ -528,17 +540,19 @@ typedef enum {
     ssl_tls13_ticket_early_data_info_xtn = 46, /* Deprecated. */
     ssl_tls13_certificate_authorities_xtn = 47,
     ssl_tls13_post_handshake_auth_xtn = 49,
     ssl_signature_algorithms_cert_xtn = 50,
     ssl_tls13_key_share_xtn = 51,
     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. */
 #define ssl_elliptic_curves_xtn ssl_supported_groups_xtn
 
 /* SSL_MAX_EXTENSIONS includes the maximum number of extensions that are
  * supported for any single message type.  That is, a ClientHello; ServerHello
  * and TLS 1.3 NewSessionTicket and HelloRetryRequest extensions have fewer. */
--- a/security/nss/lib/ssl/tls13con.c
+++ b/security/nss/lib/ssl/tls13con.c
@@ -16,17 +16,17 @@
 #include "secmod.h"
 #include "sslimpl.h"
 #include "sslproto.h"
 #include "sslerr.h"
 #include "ssl3exthandle.h"
 #include "tls13hkdf.h"
 #include "tls13con.h"
 #include "tls13err.h"
-#include "tls13esni.h"
+#include "tls13ech.h"
 #include "tls13exthandle.h"
 #include "tls13hashstate.h"
 #include "tls13subcerts.h"
 #include "tls13psk.h"
 
 static SECStatus tls13_SetCipherSpec(sslSocket *ss, PRUint16 epoch,
                                      SSLSecretDirection install,
                                      PRBool deleteSecret);
@@ -68,17 +68,17 @@ tls13_DeriveSecret(sslSocket *ss, PK11Sy
                    const SSL3Hashes *hashes,
                    PK11SymKey **dest,
                    SSLHashType hash);
 static SECStatus tls13_SendEndOfEarlyData(sslSocket *ss);
 static SECStatus tls13_HandleEndOfEarlyData(sslSocket *ss, const PRUint8 *b,
                                             PRUint32 length);
 static SECStatus tls13_MaybeHandleSuppressedEndOfEarlyData(sslSocket *ss);
 static SECStatus tls13_SendFinished(sslSocket *ss, PK11SymKey *baseKey);
-static SECStatus tls13_ComputePskBinderHash(sslSocket *ss, unsigned int prefix,
+static SECStatus tls13_ComputePskBinderHash(sslSocket *ss, PRUint8 *b, size_t length,
                                             SSL3Hashes *hashes, SSLHashType type);
 static SECStatus tls13_VerifyFinished(sslSocket *ss, SSLHandshakeType message,
                                       PK11SymKey *secret,
                                       PRUint8 *b, PRUint32 length,
                                       const SSL3Hashes *hashes);
 static SECStatus tls13_ClientHandleFinished(sslSocket *ss,
                                             PRUint8 *b, PRUint32 length);
 static SECStatus tls13_ServerHandleFinished(sslSocket *ss,
@@ -441,20 +441,17 @@ tls13_SetupClientHello(sslSocket *ss, ss
     NewSessionTicket *session_ticket = NULL;
     sslSessionID *sid = ss->sec.ci.sid;
     unsigned int numShares = 0;
     SECStatus rv;
 
     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;
     }
 
     /* Everything below here is only run on the first CH. */
     if (chType != client_hello_initial) {
         return SECSuccess;
     }
@@ -1750,16 +1747,21 @@ tls13_MaybeSendHelloRetry(sslSocket *ss,
         return SECSuccess;
     }
 
     rv = tls13_SendHelloRetryRequest(ss, requestedGroup, token, tokenLen);
     if (rv != SECSuccess) {
         return SECFailure; /* Code already set. */
     }
 
+    /* We may have 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;
 }
 
 static SECStatus
 tls13_NegotiateAuthentication(sslSocket *ss)
 {
     if (ss->statelessResume) {
@@ -1809,16 +1811,17 @@ tls13_HandleClientHelloPart2(sslSocket *
                              unsigned int len)
 {
     SECStatus rv;
     SSL3Statistics *ssl3stats = SSL_GetStatistics();
     const sslNamedGroupDef *requestedGroup = NULL;
     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,
      * reject the connection with protocol_version alert. */
     if (ss->clientHelloVersion <= SSL_LIBRARY_VERSION_3_0) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, protocol_version);
         goto loser;
     }
@@ -1860,21 +1863,29 @@ tls13_HandleClientHelloPart2(sslSocket *
             goto loser;
         }
         PRINT_BUF(50, (ss, "Client sent cookie",
                        ss->xtnData.cookie.data, ss->xtnData.cookie.len));
 
         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. */
     rv = ssl_HashHandshakeMessage(ss, ssl_hs_client_hello, msg, len);
     if (rv != SECSuccess) {
         FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
         goto loser;
     }
@@ -2022,16 +2033,17 @@ tls13_HandleClientHelloPart2(sslSocket *
     }
 
     /* Now that we have the binder key, check the binder. */
     if (ss->xtnData.selectedPsk) {
         SSL3Hashes hashes;
         PORT_Assert(ss->ssl3.hs.messages.len > ss->xtnData.pskBindersLen);
         rv = tls13_ComputePskBinderHash(
             ss,
+            ss->ssl3.hs.messages.buf,
             ss->ssl3.hs.messages.len - ss->xtnData.pskBindersLen,
             &hashes, tls13_GetHash(ss));
         if (rv != SECSuccess) {
             FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
             goto loser;
         }
 
         PORT_Assert(ss->xtnData.selectedPsk->hash == tls13_GetHash(ss));
@@ -2420,40 +2432,64 @@ loser:
 
 /* [draft-ietf-tls-tls13; S 4.4.1] says:
  *
  *     Transcript-Hash(ClientHello1, HelloRetryRequest, ... MN) =
  *      Hash(message_hash ||        // Handshake type
  *           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,
                            tls13_GetHash(ss));
     if (rv != SECSuccess) {
         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);
-    if (rv != SECSuccess) {
-        return SECFailure;
+    /* 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
 ssl_ListCount(PRCList *list)
 {
     unsigned int c = 0;
@@ -3835,39 +3871,46 @@ loser:
     return SECFailure;
 }
 
 SECStatus
 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) {
         /* Backup: if we haven't done any hashing, then hash now.
          * This happens when we are doing 0-RTT on the client. */
         ctx = PK11_CreateDigestContext(ssl3_HashTypeToOID(tls13_GetHash(ss)));
         if (!ctx) {
             ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
             return SECFailure;
         }
 
         if (PK11_DigestBegin(ctx) != SECSuccess) {
             ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
             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;
         }
     } else {
         if (ss->firstHsDone) {
             ctx = PK11_CloneContext(ss->ssl3.hs.shaPostHandshake);
         } else {
             ctx = PK11_CloneContext(ss->ssl3.hs.sha);
@@ -4127,25 +4170,16 @@ tls13_HandleEncryptedExtensions(sslSocke
         ss->xtnData.nextProtoState = SSL_NEXT_PROTO_NO_SUPPORT;
     }
 
     rv = ssl3_ParseExtensions(ss, &b, &length);
     if (rv != SECSuccess) {
         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) {
         return SECFailure; /* Error code set below */
     }
 
     /* We can only get here if we offered 0-RTT. */
     if (ssl3_ExtensionNegotiated(ss, ssl_tls13_early_data_xtn)) {
@@ -4464,79 +4498,115 @@ tls13_HandleCertificateVerify(sslSocket 
     TLS13_SET_HS_STATE(ss, wait_finished);
     return SECSuccess;
 
 loser:
     SECKEY_DestroyPublicKey(pubKey);
     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, unsigned int prefixLength,
+tls13_ComputePskBinderHash(sslSocket *ss, PRUint8 *b, size_t length,
                            SSL3Hashes *hashes, SSLHashType hashType)
 {
     SECStatus rv;
-
+    PK11Context *ctx = NULL;
+    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));
-    PORT_Assert(prefixLength <= ss->ssl3.hs.messages.len);
-
-    PRINT_BUF(10, (NULL, "Handshake hash computed over ClientHello prefix",
-                   ss->ssl3.hs.messages.buf, prefixLength));
-    rv = PK11_HashBuf(ssl3_HashTypeToOID(hashType),
-                      hashes->u.raw, ss->ssl3.hs.messages.buf, prefixLength);
+
+    PRINT_BUF(10, (NULL, "Binder computed over ClientHello",
+                   b, length));
+
+    ctx = PK11_CreateDigestContext(ssl3_HashTypeToOID(hashType));
+    if (!ctx) {
+        goto loser;
+    }
+    rv = PK11_DigestBegin(ctx);
     if (rv != SECSuccess) {
         ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
-        return SECFailure;
-    }
-
-    hashes->len = tls13_GetHashSizeForHash(hashType);
+        goto loser;
+    }
+
+    if (clientResidual && clientResidual->len) {
+        PRINT_BUF(10, (NULL, " with HRR prefix", clientResidual->buf,
+                       clientResidual->len));
+        rv = PK11_DigestOp(ctx, clientResidual->buf, clientResidual->len);
+        if (rv != SECSuccess) {
+            ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+            goto loser;
+        }
+    }
+
+    rv = PK11_DigestOp(ctx, b, length);
+    if (rv != SECSuccess) {
+        ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+        goto loser;
+    }
+    rv = PK11_DigestFinal(ctx, hashes->u.raw, &hashes->len, sizeof(hashes->u.raw));
+    if (rv != SECSuccess) {
+        ssl_MapLowLevelError(SSL_ERROR_SHA_DIGEST_FAILURE);
+        goto loser;
+    }
+
+    PK11_DestroyContext(ctx, PR_TRUE);
     PRINT_BUF(10, (NULL, "PSK Binder hash", hashes->u.raw, hashes->len));
-
     return SECSuccess;
+
+loser:
+    if (ctx) {
+        PK11_DestroyContext(ctx, PR_TRUE);
+    }
+    return SECFailure;
 }
 
 /* Compute and inject the PSK Binder for sending.
  *
  * When sending a ClientHello, we construct all the extensions with a dummy
  * value for the binder.  To construct the binder, we commit the entire message
  * up to the point where the binders start.  Then we calculate the hash using
  * the saved message (in ss->ssl3.hs.messages).  This is written over the dummy
  * binder, after which we write the remainder of the binder extension. */
 SECStatus
-tls13_WriteExtensionsWithBinder(sslSocket *ss, sslBuffer *extensions)
+tls13_WriteExtensionsWithBinder(sslSocket *ss, sslBuffer *extensions, sslBuffer *chBuf)
 {
     SSL3Hashes hashes;
     SECStatus rv;
 
     PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->ssl3.hs.psks));
     sslPsk *psk = (sslPsk *)PR_LIST_HEAD(&ss->ssl3.hs.psks);
     unsigned int size = tls13_GetHashSizeForHash(psk->hash);
     unsigned int prefixLen = extensions->len - size - 3;
     unsigned int finishedLen;
 
     PORT_Assert(extensions->len >= size + 3);
 
-    rv = ssl3_AppendHandshakeNumber(ss, extensions->len, 2);
+    rv = sslBuffer_AppendNumber(chBuf, extensions->len, 2);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     /* Only write the extension up to the point before the binders.  Assume that
      * the pre_shared_key extension is at the end of the buffer.  Don't write
      * the binder, or the lengths that precede it (a 2 octet length for the list
      * of all binders, plus a 1 octet length for the binder length). */
-    rv = ssl3_AppendHandshake(ss, extensions->buf, prefixLen);
+    rv = sslBuffer_Append(chBuf, extensions->buf, prefixLen);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     /* Calculate the binder based on what has been written out. */
-    rv = tls13_ComputePskBinderHash(ss, ss->ssl3.hs.messages.len,
-                                    &hashes, psk->hash);
+    rv = tls13_ComputePskBinderHash(ss, chBuf->buf, chBuf->len, &hashes, psk->hash);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     /* Write the binder into the extensions buffer, over the zeros we reserved
      * previously. This avoids an allocation and means that we don't need a
      * separate write for the extra bits that precede the binder. */
     PORT_Assert(psk->binderKey);
@@ -4545,18 +4615,18 @@ tls13_WriteExtensionsWithBinder(sslSocke
                                extensions->buf + extensions->len - size,
                                &finishedLen, size);
     if (rv != SECSuccess) {
         return SECFailure;
     }
     PORT_Assert(finishedLen == size);
 
     /* Write out the remainder of the extension. */
-    rv = ssl3_AppendHandshake(ss, extensions->buf + prefixLen,
-                              extensions->len - prefixLen);
+    rv = sslBuffer_Append(chBuf, extensions->buf + prefixLen,
+                          extensions->len - prefixLen);
     if (rv != SECSuccess) {
         return SECFailure;
     }
 
     return SECSuccess;
 }
 
 static SECStatus
@@ -4868,31 +4938,48 @@ tls13_ServerHandleFinished(sslSocket *ss
 loser:
     ssl_ReleaseXmitBufLock(ss);
     return SECFailure;
 }
 
 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);
 
     /* The first handshake is now completed. */
     ss->handshake = NULL;
 
     /* Don't need this. */
     PK11_FreeSymKey(ss->ssl3.hs.clientHsTrafficSecret);
     ss->ssl3.hs.clientHsTrafficSecret = NULL;
     PK11_FreeSymKey(ss->ssl3.hs.serverHsTrafficSecret);
     ss->ssl3.hs.serverHsTrafficSecret = NULL;
 
     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;
 }
 
 /* Do the parts of sending the client's second round that require
  * the XmitBuf lock. */
 static SECStatus
@@ -5393,16 +5480,17 @@ tls13_HandleNewSessionTicket(sslSocket *
 
         /* Cache the session. */
         ssl_CacheSessionID(ss);
     }
 
     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))
 #define _M3(a, b, c) (_M1(a) | _M2(b, c))
 
 static const struct {
     PRUint16 ex_value;
     PRUint32 messages;
@@ -5426,17 +5514,18 @@ static const struct {
     { ssl_cert_status_xtn, _M3(client_hello, certificate_request,
                                certificate) },
     { ssl_delegated_credentials_xtn, _M2(client_hello, certificate) },
     { ssl_tls13_cookie_xtn, _M2(client_hello, hello_retry_request) },
     { ssl_tls13_certificate_authorities_xtn, _M1(certificate_request) },
     { 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) }
 };
 
 tls13ExtensionStatus
 tls13_ExtensionStatus(PRUint16 extension, SSLHandshakeType message)
 {
     unsigned int i;
 
@@ -6122,20 +6211,19 @@ tls13_NegotiateVersion(sslSocket *ss, co
     if (rv != SECSuccess) {
         return SECFailure;
     }
     if (data.len || !versions.len || (versions.len & 1)) {
         FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_CLIENT_HELLO, illegal_parameter);
         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;
         }
 
         PRUint16 wire = tls13_EncodeVersion(version, ss->protocolVariant);
         unsigned long offset;
 
--- a/security/nss/lib/ssl/tls13con.h
+++ b/security/nss/lib/ssl/tls13con.h
@@ -14,16 +14,17 @@
 
 typedef enum {
     tls13_extension_allowed,
     tls13_extension_disallowed,
     tls13_extension_unknown
 } tls13ExtensionStatus;
 
 #define TLS13_MAX_FINISHED_SIZE 64
+#define TLS13_COOKIE_SENTINEL 0xff
 
 SECStatus tls13_UnprotectRecord(
     sslSocket *ss, ssl3CipherSpec *spec,
     SSL3Ciphertext *cText, sslBuffer *plaintext,
     SSLContentType *innerType,
     SSL3AlertDescription *alert);
 
 #if defined(WIN32)
@@ -63,17 +64,18 @@ SECStatus tls13_DeriveSecretNullHash(ssl
 void tls13_FatalError(sslSocket *ss, PRErrorCode prError,
                       SSL3AlertDescription desc);
 SECStatus tls13_SetupClientHello(sslSocket *ss, sslClientHelloType chType);
 SECStatus tls13_MaybeDo0RTTHandshake(sslSocket *ss);
 PRInt32 tls13_LimitEarlyData(sslSocket *ss, SSLContentType type, PRInt32 toSend);
 PRBool tls13_AllowPskCipher(const sslSocket *ss,
                             const ssl3CipherSuiteDef *cipher_def);
 PRBool tls13_PskSuiteEnabled(sslSocket *ss);
-SECStatus tls13_WriteExtensionsWithBinder(sslSocket *ss, sslBuffer *extensions);
+SECStatus tls13_WriteExtensionsWithBinder(sslSocket *ss, sslBuffer *extensions,
+                                          sslBuffer *chBuf);
 SECStatus tls13_HandleClientHelloPart2(sslSocket *ss,
                                        const SECItem *suites,
                                        sslSessionID *sid,
                                        const PRUint8 *msg,
                                        unsigned int len);
 SECStatus tls13_HandleServerHelloPart2(sslSocket *ss);
 SECStatus tls13_HandlePostHelloHandshakeMessage(sslSocket *ss, PRUint8 *b,
                                                 PRUint32 length);
new file mode 100644
--- /dev/null
+++ b/security/nss/lib/ssl/tls13ech.c
@@ -0,0 +1,2202 @@
+/* -*- 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;
+        PORT_Memcpy(newConfig->configId, config->configId, sizeof(newConfig->configId));
+        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;
+}
new file mode 100644
--- /dev/null
+++ b/security/nss/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
deleted file mode 100644
--- a/security/nss/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)
-{