Source code

Revision control

Other Tools

/* -*- 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 "tls13hashstate.h"
#include "tls13hkdf.h"
extern SECStatus
ssl3_UpdateHandshakeHashesInt(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);
extern SECStatus
tls13_DeriveSecret(sslSocket *ss, PK11SymKey *key,
const char *label,
unsigned int labelLen,
const SSL3Hashes *hashes,
PK11SymKey **dest,
SSLHashType hash);
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);
}
}
void
tls13_DestroyEchXtnState(sslEchXtnState *state)
{
if (!state) {
return;
}
SECITEM_FreeItem(&state->innerCh, PR_FALSE);
SECITEM_FreeItem(&state->senderPubKey, PR_FALSE);
SECITEM_FreeItem(&state->configId, PR_FALSE);
SECITEM_FreeItem(&state->retryConfigs, PR_FALSE);
PORT_ZFree(state, sizeof(*state));
}
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;
newConfig->version = config->version;
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, 8,
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;
}
if (tmpBuf.len == 0) {
PORT_SetError(SSL_ERROR_RX_MALFORMED_ECH_CONFIG);
goto loser;
}
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) {
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 || !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;
}
/* We don't distinguish between "handshake completed
* without retry configs", and "handshake not completed".
* An application should only call this after receiving a
* RETRY_WITH_ECH error code, which implies retry_configs. */
if (!ss->xtnData.ech || !ss->xtnData.ech->retryConfigsValid) {
PORT_SetError(SSL_ERROR_HANDSHAKE_NOT_COMPLETED);
return SECFailure;
}
/* May be empty. */
rv = SECITEM_CopyItem(NULL, &out, &ss->xtnData.ech->retryConfigs);
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.ech && ss->xtnData.ech->retryConfigs.len) {
SECITEM_FreeItem(&ss->xtnData.ech->retryConfigs, 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 };
sslEchConfig *cfg = NULL;
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:
if (!ss->ssl3.hs.echHpkeCtx || !ss->ssl3.hs.echPublicName) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
return SECFailure;
}
/* Nothing else to do. */
return SECSuccess;
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;
}
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;
SECKEY_DestroyPublicKey(pkR);
SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
return SECSuccess;
loser:
PK11_HPKE_DestroyContext(cx, PR_TRUE);
SECKEY_DestroyPublicKey(pkR);
SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
PORT_Assert(PORT_GetError() != 0);
return SECFailure;
}
/*
* enum {
* encrypted_client_hello(0xfe09), (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;
}
if (!ss->ssl3.hs.helloRetry) {
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;
}
} else {
/* one byte for empty configId, two for empty Enc. */
rv = sslBuffer_AppendNumber(chInner, 0, 3);
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_GetMatchingEchConfigs(const sslSocket *ss, HpkeKdfId kdf, HpkeAeadId aead,
const SECItem *configId, const sslEchConfig *cur, sslEchConfig **next)
{
PRINT_BUF(50, (ss, "Server GetMatchingEchConfig with digest:",
configId->data, configId->len));
/* If |cur|, resume the search at that node, else the list head. */
for (PRCList *cur_p = cur ? ((PRCList *)cur)->next : 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;
}
if (echConfig->contents.aeadId == aead &&
echConfig->contents.kdfId == kdf) {
*next = echConfig;
return SECSuccess;
}
}
*next = NULL;
return SECSuccess;
}
/* 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;
}
/*
* struct {
* HpkeKdfId kdfId; // ClientECH.cipher_suite.kdf
* HpkeAeadId aeadId; // ClientECH.cipher_suite.aead
* opaque config_id<0..255>; // ClientECH.config_id
* opaque enc<1..2^16-1>; // ClientECH.enc
* opaque outer_hello<1..2^24-1>;
* } ClientHelloOuterAAD;
*/
static SECStatus
tls13_MakeChOuterAAD(sslSocket *ss, const SECItem *outer, SECItem *outerAAD)
{
SECStatus rv;
sslBuffer aad = SSL_BUFFER_EMPTY;
sslReadBuffer aadXtns = { 0 };
sslReader chReader = SSL_READER(outer->data, outer->len);
PRUint64 tmpn;
sslReadBuffer tmpvar = { 0 };
unsigned int offset;
unsigned int savedOffset;
PORT_Assert(ss->xtnData.ech);
rv = sslBuffer_AppendNumber(&aad, ss->xtnData.ech->kdfId, 2);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_AppendNumber(&aad, ss->xtnData.ech->aeadId, 2);
if (rv != SECSuccess) {
goto loser;
}
if (!ss->ssl3.hs.helloRetry) {
rv = sslBuffer_AppendVariable(&aad, ss->xtnData.ech->configId.data,
ss->xtnData.ech->configId.len, 1);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_AppendVariable(&aad, ss->xtnData.ech->senderPubKey.data,
ss->xtnData.ech->senderPubKey.len, 2);
} else {
/* 1B config_id length, 2B enc length. */
rv = sslBuffer_AppendNumber(&aad, 0, 3);
}
if (rv != SECSuccess) {
goto loser;
}
/* Skip 3 bytes for the CHOuter length. */
rv = sslBuffer_Skip(&aad, 3, &savedOffset);
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);
/* 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_InsertLength(&aad, offset, 2);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_InsertLength(&aad, savedOffset, 3);
if (rv != SECSuccess) {
goto loser;
}
outerAAD->data = aad.buf;
outerAAD->len = aad.len;
return SECSuccess;
loser:
sslBuffer_Clear(&aad);
return SECFailure;
}
SECStatus
tls13_OpenClientHelloInner(sslSocket *ss, const SECItem *outer, const SECItem *outerAAD, sslEchConfig *cfg, SECItem **chInner)
{
SECStatus rv;
HpkeContext *cx = NULL;
SECItem *decryptedChInner = NULL;
SECItem hpkeInfo = { siBuffer, NULL, 0 };
SSL_TRC(50, ("%d: TLS13[%d]: Server opening ECH Inner%s", SSL_GETPID(),
ss->fd, ss->ssl3.hs.helloRetry ? " after HRR" : ""));
if (!ss->ssl3.hs.helloRetry) {
PORT_Assert(!ss->ssl3.hs.echHpkeCtx);
cx = PK11_HPKE_NewContext(cfg->contents.kemId, cfg->contents.kdfId,
cfg->contents.aeadId, NULL, 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.ech->senderPubKey, &hpkeInfo);
if (rv != SECSuccess) {
goto loser; /* code set */
}
} else {
PORT_Assert(ss->ssl3.hs.echHpkeCtx);
cx = ss->ssl3.hs.echHpkeCtx;
}
#ifndef UNSAFE_FUZZER_MODE
rv = PK11_HPKE_Open(cx, outerAAD, &ss->xtnData.ech->innerCh, &decryptedChInner);
if (rv != SECSuccess) {
goto loser; /* code set */
}
#else
rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.ech->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);
return SECSuccess;
loser:
SECITEM_FreeItem(decryptedChInner, PR_TRUE);
SECITEM_FreeItem(&hpkeInfo, PR_FALSE);
if (cx != ss->ssl3.hs.echHpkeCtx) {
/* Don't double-free if it's already global. */
PK11_HPKE_DestroyContext(cx, PR_TRUE);
}
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_ech_is_inner_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 aad = SSL_BUFFER_EMPTY;
unsigned int encodedChLen;
unsigned int preambleLen;
const SECItem *hpkeEnc = NULL;
unsigned int savedOffset;
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_UpdateHandshakeHashesInt(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;
}
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 + 1 + 2 + 2 + encodedChInner.len + 16;
if (!ss->ssl3.hs.helloRetry) {
encodedChLen += 8 + 32; /* configId || enc */
}
rv = ssl_InsertPaddingExtension(ss, chOuter->len + encodedChLen, chOuterXtnsBuf);
if (rv != SECSuccess) {
goto loser;
}
PORT_Assert(!PR_CLIST_IS_EMPTY(&ss->echConfigs));
sslEchConfig *cfg = (sslEchConfig *)PR_LIST_HEAD(&ss->echConfigs);
rv = sslBuffer_AppendNumber(&aad, cfg->contents.kdfId, 2);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_AppendNumber(&aad, cfg->contents.aeadId, 2);
if (rv != SECSuccess) {
goto loser;
}
if (!ss->ssl3.hs.helloRetry) {
rv = sslBuffer_AppendVariable(&aad, cfg->configId, sizeof(cfg->configId), 1);
if (rv != SECSuccess) {
goto loser;
}
hpkeEnc = PK11_HPKE_GetEncapPubKey(ss->ssl3.hs.echHpkeCtx);
if (!hpkeEnc) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
goto loser;
}
rv = sslBuffer_AppendVariable(&aad, hpkeEnc->data, hpkeEnc->len, 2);
} else {
/* 1B config_id length, 2B enc length. */
rv = sslBuffer_AppendNumber(&aad, 0, 3);
}
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_Skip(&aad, 3, &savedOffset);
if (rv != SECSuccess) {
goto loser;
}
/* Skip the handshake header. */
PORT_Assert(chOuter->len > 4);
rv = sslBuffer_Append(&aad, &chOuter->buf[4], chOuter->len - 4);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_AppendBufferVariable(&aad, chOuterXtnsBuf, 2);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_InsertLength(&aad, savedOffset, 3);
if (rv != SECSuccess) {
goto loser;
}
/* Insert the encrypted_client_hello xtn and coalesce. */
rv = tls13_EncryptClientHello(ss, &aad, &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;
}
sslBuffer_Clear(&chInner);
sslBuffer_Clear(&encodedChInner);
sslBuffer_Clear(&chInnerXtns);
sslBuffer_Clear(&pskXtn);
sslBuffer_Clear(&aad);
return SECSuccess;
loser:
sslBuffer_Clear(&chInner);
sslBuffer_Clear(&encodedChInner);
sslBuffer_Clear(&chInnerXtns);
sslBuffer_Clear(&pskXtn);
sslBuffer_Clear(&aad);
PORT_Assert(PORT_GetError() != 0);
return SECFailure;
}
/* Compute the ECH signal using the transcript (up to, excluding) Server Hello.
* We'll append an artificial SH (ServerHelloECHConf). The server sources
* this transcript prefix from ss->ssl3.hs.messages, as it never uses
* ss->ssl3.hs.echInnerMessages. The client uses the inner transcript, echInnerMessages. */
static SECStatus
tls13_ComputeEchSignal(sslSocket *ss, const PRUint8 *sh, unsigned int shLen, PRUint8 *out)
{
SECStatus rv;
PK11SymKey *confirmationKey = NULL;
sslBuffer confMsgs = SSL_BUFFER_EMPTY;
sslBuffer *chSource = ss->sec.isServer ? &ss->ssl3.hs.messages : &ss->ssl3.hs.echInnerMessages;
SSL3Hashes hashes;
SECItem *confirmationBytes;
unsigned int offset = sizeof(SSL3ProtocolVersion) +
SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN;
PORT_Assert(sh && shLen > offset);
PORT_Assert(TLS13_ECH_SIGNAL_LEN <= SSL3_RANDOM_LENGTH);
rv = sslBuffer_AppendBuffer(&confMsgs, chSource);
if (rv != SECSuccess) {
goto loser;
}
/* Re-create the message header. */
rv = sslBuffer_AppendNumber(&confMsgs, ssl_hs_server_hello, 1);
if (rv != SECSuccess) {
goto loser;
}
rv = sslBuffer_AppendNumber(&confMsgs, shLen, 3);
if (rv != SECSuccess) {
goto loser;
}
/* Copy the version and 24B of server_random. */
rv = sslBuffer_Append(&confMsgs, sh, offset);
if (rv != SECSuccess) {
goto loser;
}
/* Zero the signal placeholder. */
rv = sslBuffer_AppendNumber(&confMsgs, 0, TLS13_ECH_SIGNAL_LEN);
if (rv != SECSuccess) {
goto loser;
}
offset += TLS13_ECH_SIGNAL_LEN;
/* Use the remainder of SH. */
rv = sslBuffer_Append(&confMsgs, &sh[offset], shLen - offset);
if (rv != SECSuccess) {
goto loser;
}
rv = tls13_ComputeHash(ss, &hashes, confMsgs.buf, confMsgs.len,
tls13_GetHash(ss));
if (rv != SECSuccess) {
goto loser;
}
/* accept_confirmation =
* Derive-Secret(Handshake Secret,
* "ech accept confirmation",
* ClientHelloInner...ServerHelloECHConf)
*/
rv = tls13_DeriveSecret(ss, ss->ssl3.hs.currentSecret,
kHkdfInfoEchConfirm, strlen(kHkdfInfoEchConfirm),
&hashes, &confirmationKey, tls13_GetHash(ss));
if (rv != SECSuccess) {
return SECFailure;
}
rv = PK11_ExtractKeyValue(confirmationKey);
if (rv != SECSuccess) {
goto loser;
}
confirmationBytes = PK11_GetKeyData(confirmationKey);
if (!confirmationBytes) {
rv = SECFailure;
PORT_SetError(SSL_ERROR_ECH_FAILED);
goto loser;
}
if (confirmationBytes->len < TLS13_ECH_SIGNAL_LEN) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
goto loser;
}
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));
PORT_Memcpy(out, confirmationBytes->data, TLS13_ECH_SIGNAL_LEN);
PK11_FreeSymKey(confirmationKey);
sslBuffer_Clear(&confMsgs);
sslBuffer_Clear(&ss->ssl3.hs.messages);
sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages);
return SECSuccess;
loser:
PK11_FreeSymKey(confirmationKey);
sslBuffer_Clear(&confMsgs);
sslBuffer_Clear(&ss->ssl3.hs.messages);
sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages);
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;
/* 1B aead determinant (don't send), 8B config_id, 32B enc, payload */
const int kNonPayloadLen = 41;
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;
}
/* In draft-09, CH2 sends exactly the same GREASE ECH extension. */
if (ss->ssl3.hs.helloRetry) {
return ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn,
ss->ssl3.hs.greaseEchBuf.buf,
ss->ssl3.hs.greaseEchBuf.len, PR_TRUE);
}
/* 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, kNonPayloadLen + payloadLen,