Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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.

Differential Revision: https://phabricator.services.mozilla.com/D86106

--HG--
rename : gtests/ssl_gtest/tls_esni_unittest.cc => gtests/ssl_gtest/tls_ech_unittest.cc
rename : lib/ssl/tls13esni.c => lib/ssl/tls13ech.c
rename : lib/ssl/tls13esni.h => lib/ssl/tls13ech.h
extra : moz-landing-system : lando
  • Loading branch information
Kevin Jacobs committed Nov 17, 2020
1 parent 85f350f commit 4516d10
Show file tree
Hide file tree
Showing 47 changed files with 5,192 additions and 2,062 deletions.
19 changes: 19 additions & 0 deletions 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
54 changes: 40 additions & 14 deletions cmd/tstclnt/tstclnt.c
Expand Up @@ -231,7 +231,7 @@ PrintUsageHeader()
" [-r N] [-w passwd] [-W pwfile] [-q [-t seconds]]\n"
" [-I groups] [-J signatureschemes]\n"
" [-A requestfile] [-L totalconnections] [-P {client,server}]\n"
" [-N encryptedSniKeys] [-Q] [-z externalPsk]\n"
" [-N echConfigs] [-Q] [-z externalPsk]\n"
"\n",
progName);
}
Expand Down Expand Up @@ -316,7 +316,7 @@ PrintParameterUsage()
fprintf(stderr, "%-20s Enable alternative TLS 1.3 handshake\n", "-X alt-server-hello");
fprintf(stderr, "%-20s Use DTLS\n", "-P {client, server}");
fprintf(stderr, "%-20s Exit after handshake\n", "-Q");
fprintf(stderr, "%-20s Encrypted SNI Keys\n", "-N");
fprintf(stderr, "%-20s Use Encrypted Client Hello with the given Base64-encoded ECHConfigs\n", "-N");
fprintf(stderr, "%-20s Enable post-handshake authentication\n"
"%-20s for TLS 1.3; need to specify -n\n",
"-E", "");
Expand Down Expand Up @@ -1010,7 +1010,7 @@ PRBool stopAfterHandshake = PR_FALSE;
PRBool requestToExit = PR_FALSE;
char *versionString = NULL;
PRBool handshakeComplete = PR_FALSE;
char *encryptedSNIKeys = NULL;
char *echConfigs = NULL;
PRBool enablePostHandshakeAuth = PR_FALSE;
PRBool enableDelegatedCredentials = PR_FALSE;
const secuExporter *enabledExporters = NULL;
Expand Down Expand Up @@ -1263,6 +1263,30 @@ importPsk(PRFileDesc *s)
return rv;
}

static SECStatus
printEchRetryConfigs(PRFileDesc *s)
{
if (PORT_GetError() == SSL_ERROR_ECH_RETRY_WITH_ECH) {
SECItem retries = { siBuffer, NULL, 0 };
SECStatus rv = SSL_GetEchRetryConfigs(s, &retries);
if (rv != SECSuccess) {
SECU_PrintError(progName, "SSL_GetEchRetryConfigs failed");
return SECFailure;
}
char *retriesBase64 = NSSBase64_EncodeItem(NULL, NULL, 0, &retries);
if (!retriesBase64) {
SECU_PrintError(progName, "NSSBase64_EncodeItem on retry_configs failed");
SECITEM_FreeItem(&retries, PR_FALSE);
return SECFailure;
}

fprintf(stderr, "Received ECH retry_configs: \n%s\n", retriesBase64);
PORT_Free(retriesBase64);
SECITEM_FreeItem(&retries, PR_FALSE);
}
return SECSuccess;
}

static int
run()
{
Expand Down Expand Up @@ -1511,21 +1535,20 @@ run()
}
}

if (encryptedSNIKeys) {
SECItem esniKeysBin = { siBuffer, NULL, 0 };
if (echConfigs) {
SECItem echConfigsBin = { siBuffer, NULL, 0 };

if (!NSSBase64_DecodeBuffer(NULL, &esniKeysBin, encryptedSNIKeys,
strlen(encryptedSNIKeys))) {
SECU_PrintError(progName, "ESNIKeys record is invalid base64");
if (!NSSBase64_DecodeBuffer(NULL, &echConfigsBin, echConfigs,
strlen(echConfigs))) {
SECU_PrintError(progName, "ECHConfigs record is invalid base64");
error = 1;
goto done;
}

rv = SSL_EnableESNI(s, esniKeysBin.data, esniKeysBin.len,
"dummy.invalid");
SECITEM_FreeItem(&esniKeysBin, PR_FALSE);
rv = SSL_SetClientEchConfigs(s, echConfigsBin.data, echConfigsBin.len);
SECITEM_FreeItem(&echConfigsBin, PR_FALSE);
if (rv < 0) {
SECU_PrintError(progName, "SSL_EnableESNI failed");
SECU_PrintError(progName, "SSL_SetClientEchConfigs failed");
error = 1;
goto done;
}
Expand Down Expand Up @@ -1702,6 +1725,9 @@ run()
} else {
error = writeBytesToServer(s, buf, nb);
if (error) {
if (echConfigs) {
(void)printEchRetryConfigs(s);
}
goto done;
}
pollset[SSOCK_FD].in_flags = PR_POLL_READ;
Expand Down Expand Up @@ -1881,7 +1907,7 @@ main(int argc, char **argv)
break;

case 'N':
encryptedSNIKeys = PORT_Strdup(optstate->value);
echConfigs = PORT_Strdup(optstate->value);
break;

case 'P':
Expand Down Expand Up @@ -2257,7 +2283,7 @@ main(int argc, char **argv)
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);

Expand Down
1 change: 1 addition & 0 deletions cpputil/tls_parser.h
Expand Up @@ -56,6 +56,7 @@ const uint8_t kTlsAlertUnsupportedExtension = 110;
const uint8_t kTlsAlertUnrecognizedName = 112;
const uint8_t kTlsAlertCertificateRequired = 116;
const uint8_t kTlsAlertNoApplicationProtocol = 120;
const uint8_t kTlsAlertEchRequired = 121;

const uint8_t kTlsFakeChangeCipherSpec[] = {
ssl_ct_change_cipher_spec, // Type
Expand Down
16 changes: 16 additions & 0 deletions gtests/ssl_gtest/libssl_internals.c
Expand Up @@ -8,8 +8,10 @@
#include "libssl_internals.h"

#include "nss.h"
#include "pk11hpke.h"
#include "pk11pub.h"
#include "pk11priv.h"
#include "tls13ech.h"
#include "seccomon.h"
#include "selfencrypt.h"
#include "secmodti.h"
Expand Down Expand Up @@ -481,3 +483,17 @@ SECStatus SSLInt_HasPendingHandshakeData(PRFileDesc *fd, PRBool *pending) {
ssl_ReleaseSSL3HandshakeLock(ss);
return SECSuccess;
}

SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf,
size_t len) {
sslSocket *ss = ssl_FindSocket(fd);
if (!ss) {
return SECFailure;
}

sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
SECITEM_FreeItem(&cfg->raw, PR_FALSE);
SECITEM_AllocItem(NULL, &cfg->raw, len);
memcpy(cfg->raw.data, buf, len);
return SECSuccess;
}
2 changes: 2 additions & 0 deletions gtests/ssl_gtest/libssl_internals.h
Expand Up @@ -49,5 +49,7 @@ SECStatus SSLInt_SetDCAdvertisedSigSchemes(PRFileDesc *fd,
const SSLSignatureScheme *schemes,
uint32_t num_sig_schemes);
SECStatus SSLInt_RemoveServerCertificates(PRFileDesc *fd);
SECStatus SSLInt_SetRawEchConfigForRetry(PRFileDesc *fd, const uint8_t *buf,
size_t len);

#endif // ndef libssl_internals_h_
2 changes: 1 addition & 1 deletion gtests/ssl_gtest/manifest.mn
Expand Up @@ -58,7 +58,7 @@ CPPSRCS = \
tls_protect.cc \
tls_psk_unittest.cc \
tls_subcerts_unittest.cc \
tls_esni_unittest.cc \
tls_ech_unittest.cc \
$(SSLKEYLOGFILE_FILES) \
$(NULL)

Expand Down
12 changes: 12 additions & 0 deletions gtests/ssl_gtest/ssl_auth_unittest.cc
Expand Up @@ -660,6 +660,18 @@ TEST_P(TlsConnectGeneric, ClientAuthEcdsa) {
CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa);
}

#ifdef NSS_ENABLE_DRAFT_HPKE
TEST_P(TlsConnectGeneric, ClientAuthWithEch) {
Reset(TlsAgent::kServerEcdsa256);
EnsureTlsSetup();
SetupEch(client_, server_);
client_->SetupClientAuth();
server_->RequestClientAuth(true);
Connect();
CheckKeys(ssl_kea_ecdh, ssl_auth_ecdsa);
}
#endif

TEST_P(TlsConnectGeneric, ClientAuthBigRsa) {
Reset(TlsAgent::kServerRsa, TlsAgent::kRsa2048);
client_->SetupClientAuth();
Expand Down
2 changes: 1 addition & 1 deletion gtests/ssl_gtest/ssl_custext_unittest.cc
Expand Up @@ -67,8 +67,8 @@ static const uint16_t kManyExtensions[] = {
ssl_tls13_certificate_authorities_xtn,
ssl_next_proto_nego_xtn,
ssl_renegotiation_info_xtn,
ssl_tls13_short_header_xtn,
ssl_record_size_limit_xtn,
ssl_tls13_encrypted_client_hello_xtn,
1,
0xffff};
// The list here includes all extensions we expect to use (SSL_MAX_EXTENSIONS),
Expand Down
74 changes: 17 additions & 57 deletions gtests/ssl_gtest/ssl_extension_unittest.cc
Expand Up @@ -83,63 +83,6 @@ class TlsExtensionTruncator : public TlsExtensionFilter {
size_t length_;
};

class TlsExtensionAppender : public TlsHandshakeFilter {
public:
TlsExtensionAppender(const std::shared_ptr<TlsAgent>& a,
uint8_t handshake_type, uint16_t ext, DataBuffer& data)
: TlsHandshakeFilter(a, {handshake_type}), extension_(ext), data_(data) {}

virtual PacketFilter::Action FilterHandshake(const HandshakeHeader& header,
const DataBuffer& input,
DataBuffer* output) {
TlsParser parser(input);
if (!TlsExtensionFilter::FindExtensions(&parser, header)) {
return KEEP;
}
*output = input;

// Increase the length of the extensions block.
if (!UpdateLength(output, parser.consumed(), 2)) {
return KEEP;
}

// Extensions in Certificate are nested twice. Increase the size of the
// certificate list.
if (header.handshake_type() == kTlsHandshakeCertificate) {
TlsParser p2(input);
if (!p2.SkipVariable(1)) {
ADD_FAILURE();
return KEEP;
}
if (!UpdateLength(output, p2.consumed(), 3)) {
return KEEP;
}
}

size_t offset = output->len();
offset = output->Write(offset, extension_, 2);
WriteVariable(output, offset, data_, 2);

return CHANGE;
}

private:
bool UpdateLength(DataBuffer* output, size_t offset, size_t size) {
uint32_t len;
if (!output->Read(offset, size, &len)) {
ADD_FAILURE();
return false;
}

len += 4 + data_.len();
output->Write(offset, len, size);
return true;
}

const uint16_t extension_;
const DataBuffer data_;
};

class TlsExtensionTestBase : public TlsConnectTestBase {
protected:
TlsExtensionTestBase(SSLProtocolVariant variant, uint16_t version)
Expand Down Expand Up @@ -1155,6 +1098,22 @@ TEST_P(TlsExtensionTest13, HrrThenRemoveSupportedGroups) {
SSL_ERROR_MISSING_SUPPORTED_GROUPS_EXTENSION);
}

#ifdef NSS_ENABLE_DRAFT_HPKE
TEST_P(TlsExtensionTest13, HrrThenRemoveEch) {
if (variant_ == ssl_variant_datagram) {
// ECH not supported in DTLS.
return;
}

EnsureTlsSetup();
SetupEch(client_, server_);
ExpectAlert(server_, kTlsAlertIllegalParameter);
HrrThenRemoveExtensionsTest(ssl_tls13_encrypted_client_hello_xtn,
SSL_ERROR_ILLEGAL_PARAMETER_ALERT,
SSL_ERROR_BAD_2ND_CLIENT_HELLO);
}
#endif

TEST_P(TlsExtensionTest13, EmptyVersionList) {
static const uint8_t ext[] = {0x00, 0x00};
ConnectWithBogusVersionList(ext, sizeof(ext));
Expand Down Expand Up @@ -1289,6 +1248,7 @@ TEST_P(TlsBogusExtensionTest13, AddBogusExtensionNewSessionTicket) {

TEST_P(TlsConnectStream, IncludePadding) {
EnsureTlsSetup();
SSL_EnableTls13GreaseEch(client_->ssl_fd(), PR_FALSE); // Don't GREASE

// This needs to be long enough to push a TLS 1.0 ClientHello over 255, but
// short enough not to push a TLS 1.3 ClientHello over 511.
Expand Down
2 changes: 1 addition & 1 deletion gtests/ssl_gtest/ssl_gtest.gyp
Expand Up @@ -55,7 +55,7 @@
'tls_connect.cc',
'tls_filter.cc',
'tls_hkdf_unittest.cc',
'tls_esni_unittest.cc',
'tls_ech_unittest.cc',
'tls_protect.cc',
'tls_psk_unittest.cc',
'tls_subcerts_unittest.cc'
Expand Down
25 changes: 25 additions & 0 deletions gtests/ssl_gtest/ssl_tls13compat_unittest.cc
Expand Up @@ -214,6 +214,31 @@ TEST_F(Tls13CompatTest, EnabledHrrZeroRtt) {
CheckForCompatHandshake();
}

#ifdef NSS_ENABLE_DRAFT_HPKE
TEST_F(Tls13CompatTest, EnabledAcceptedEch) {
EnsureTlsSetup();
SetupEch(client_, server_);
EnableCompatMode();
InstallFilters();
Connect();
CheckForCompatHandshake();
}

TEST_F(Tls13CompatTest, EnabledRejectedEch) {
EnsureTlsSetup();
// Configure ECH on the client only, and expect CCS.
SetupEch(client_, server_, HpkeDhKemX25519Sha256, false, true, false);
EnableCompatMode();
InstallFilters();
ExpectAlert(client_, kTlsAlertEchRequired);
ConnectExpectFailOneSide(TlsAgent::CLIENT);
client_->CheckErrorCode(SSL_ERROR_ECH_RETRY_WITHOUT_ECH);
CheckForCompatHandshake();
// Reset expectations for the TlsAgent dtor.
server_->ExpectReceiveAlert(kTlsAlertCloseNotify, kTlsAlertWarning);
}
#endif

class TlsSessionIDEchoFilter : public TlsHandshakeFilter {
public:
TlsSessionIDEchoFilter(const std::shared_ptr<TlsAgent>& a)
Expand Down
7 changes: 5 additions & 2 deletions gtests/ssl_gtest/tls_agent.cc
Expand Up @@ -74,6 +74,7 @@ TlsAgent::TlsAgent(const std::string& nm, Role rl, SSLProtocolVariant var)
expected_version_(0),
expected_cipher_suite_(0),
expect_client_auth_(false),
expect_ech_(false),
expect_psk_(ssl_psk_none),
can_falsestart_hook_called_(false),
sni_hook_called_(false),
Expand Down Expand Up @@ -687,7 +688,9 @@ void TlsAgent::EnableFalseStart() {
SetOption(SSL_ENABLE_FALSE_START, PR_TRUE);
}

void TlsAgent::ExpectPsk() { expect_psk_ = ssl_psk_external; }
void TlsAgent::ExpectEch(bool expected) { expect_ech_ = expected; }

void TlsAgent::ExpectPsk(SSLPskType psk) { expect_psk_ = psk; }

void TlsAgent::ExpectResumption() { expect_psk_ = ssl_psk_resume; }

Expand Down Expand Up @@ -820,7 +823,6 @@ void TlsAgent::CheckPreliminaryInfo() {
SSL_GetPreliminaryChannelInfo(ssl_fd(), &preinfo, sizeof(preinfo)));
EXPECT_EQ(sizeof(preinfo), preinfo.length);
EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_version);
EXPECT_TRUE(preinfo.valuesSet & ssl_preinfo_cipher_suite);

// A version of 0 is invalid and indicates no expectation. This value is
// initialized to 0 so that tests that don't explicitly set an expected
Expand Down Expand Up @@ -932,6 +934,7 @@ void TlsAgent::Connected() {

EXPECT_EQ(expect_psk_ == ssl_psk_resume, info_.resumed == PR_TRUE);
EXPECT_EQ(expect_psk_, info_.pskType);
EXPECT_EQ(expect_ech_, info_.echAccepted);

// Preliminary values are exposed through callbacks during the handshake.
// If either expected values were set or the callbacks were called, check
Expand Down

0 comments on commit 4516d10

Please sign in to comment.