/* -*- 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/. */
/* 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;
}
while (SSL_READER_REMAINING(&extensionReader)) { /* Get the extension's type field */
rv = sslRead_ReadNumber(&extensionReader, 2, &tmpn); if (rv != SECSuccess) { goto loser;
}
for (unsignedint i = 0; i < extensionIndex; i++) { if (extensionTypes[i] == tmpn) {
PORT_SetError(SEC_ERROR_EXTENSION_VALUE_INVALID); goto loser;
}
}
extensionTypes[extensionIndex++] = (PRUint16)tmpn;
/* Clients MUST parse the extension list and check for unsupported * mandatory extensions. If an unsupported mandatory extension is * present, clients MUST ignore the ECHConfig
* [draft-ietf-tls-esni, Section 4.2]. */ if (tmpn & (1 << 15)) {
unsupportedMandatoryXtn = PR_TRUE;
}
/* 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 were compatible AND if NO unsupported mandatory * extensions were found set the outparam. Return success either way if the
* config was well-formed. */ if (hasValidSuite && !unsupportedMandatoryXtn) {
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;
/* Encode an ECHConfigList structure. We only create 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_EncodeEchConfigId(PRUint8 configId, constchar *publicName, unsignedint maxNameLen,
HpkeKemId kemId, const SECKEYPublicKey *pubKey, const HpkeSymmetricSuite *hpkeSuites, unsignedint hpkeSuiteCount,
PRUint8 *out, unsignedint *outlen, unsignedint maxlen)
{
SECStatus rv; unsignedint savedOffset; unsignedint len;
sslBuffer b = SSL_BUFFER_EMPTY;
PRUint8 tmpBuf[66]; // Large enough for an EC public key, currently only X25519. unsignedint tmpLen;
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;
}
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;
}
/* 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);
}
/* 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, unsignedint echConfigsLen)
{
sslSocket *ss;
SECStatus rv;
SECItem data = { siBuffer, CONST_CAST(PRUint8, echConfigs), echConfigsLen };
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 (IS_DTLS(ss)) { 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;
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 (IS_DTLS(ss)) { 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;
}
/* 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;
/* 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);
/* 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;
}
/* * outerAAD - The associated data for the AEAD (the entire client hello with the ECH payload zeroed) * chInner - The plaintext which will be encrypted (the ClientHelloInner plus padding) * echPayload - Output location. A buffer containing all-zeroes of at least chInner->len + TLS13_ECH_AEAD_TAG_LEN bytes. * * echPayload may point into outerAAD to avoid the need to duplicate the ClientHelloOuter buffer.
*/ static SECStatus
tls13_EncryptClientHello(sslSocket *ss, SECItem *aadItem, const sslBuffer *chInner, PRUint8 *echPayload)
{
SECStatus rv;
SECItem chPt = { siBuffer, chInner->buf, chInner->len };
SECItem *chCt = NULL;
PRINT_BUF(50, (ss, "aad for ECH Encrypt", aadItem->data, aadItem->len));
PRINT_BUF(50, (ss, "plaintext for ECH Encrypt", chInner->buf, chInner->len));
#ifdef DEBUG /* When encrypting in-place, the payload is part of the AAD and must be zeroed. */
PRUint8 val = 0; for (int i = 0; i < chCt->len; i++) {
val |= *(echPayload + i);
}
PRINT_BUF(100, (ss, "Empty Placeholder for output of ECH Encryption", echPayload, chCt->len));
PR_ASSERT(val == 0); #endif
/* 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 (echConfig->contents.configId == configId &&
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(sslSocket *ss, sslReader *reader, const SECItem *explicitSid, sslBuffer *writer, sslReadBuffer *extensions)
{
SECStatus rv;
sslReadBuffer tmpReadBuf;
/* padding (optional) */
sslReadBuffer padding;
rv = sslRead_Read(reader, SSL_READER_REMAINING(reader), &padding); if (rv != SECSuccess) { return SECFailure;
}
PRUint8 result = 0; for (int i = 0; i < padding.len; i++) {
result |= padding.buf[i];
} if (result) {
SSL_TRC(50, ("%d: TLS13: Invalid ECH ClientHelloInner padding decoded", SSL_GETPID()));
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, illegal_parameter); return SECFailure;
} return SECSuccess;
}
/* * The ClientHelloOuterAAD is a serialized ClientHello structure, defined in * Section 4.1.2 of [RFC8446], which matches the ClientHelloOuter except the * payload field of the "encrypted_client_hello" is replaced with a byte * string of the same length but whose contents are zeros. This value does * not include the four-byte header from the Handshake structure.
*/ static SECStatus
tls13_ServerMakeChOuterAAD(sslSocket *ss, const PRUint8 *outerCh, unsignedint outerChLen, SECItem *outerAAD)
{
SECStatus rv;
sslBuffer aad = SSL_BUFFER_EMPTY; constunsignedint echPayloadLen = ss->xtnData.ech->innerCh.len; /* Length of incoming payload */ constunsignedint echPayloadOffset = ss->xtnData.ech->payloadStart - outerCh; /* Offset from start of CHO */
#ifndef UNSAFE_FUZZER_MODE
rv = PK11_HPKE_Open(cx, outerAAD, &ss->xtnData.ech->innerCh, &decryptedChInner); if (rv != SECSuccess) {
SSL_TRC(10, ("%d: SSL3[%d]: Failed to decrypt inner CH with this candidate",
SSL_GETPID(), ss->fd)); goto loser; /* code set */
} #else
rv = SECITEM_CopyItem(NULL, decryptedChInner, &ss->xtnData.ech->innerCh); if (rv != SECSuccess) { goto loser;
}
decryptedChInner->len -= TLS13_ECH_AEAD_TAG_LEN; /* Fake tag */ #endif
/* Stash the context, we may need it for HRR. */
ss->ssl3.hs.echHpkeCtx = cx;
*chInner = decryptedChInner;
PRINT_BUF(100, (ss, "Decrypted ECH Inner", decryptedChInner->data, decryptedChInner->len));
SECITEM_FreeItem(&hpkeInfo, PR_FALSE); return SECSuccess;
/* Add ordinary extensions to CHInner. * The value of the extension from CHOuter is in |extensionData|. * * If the value is to be compressed, it is written to |dupXtns|. * Otherwise, a full extension is written to |chInnerXtns|. * * This function is always called twice: * once without compression and once with compression if possible. * * Because we want to allow extensions that did not appear in CHOuter * to be included in CHInner, we also need to track which extensions * have been included. This is what |called| and |nCalled| track.
*/ static SECStatus
tls13_ChInnerAppendExtension(sslSocket *ss, PRUint16 extensionType, const sslReadBuffer *extensionData,
sslBuffer *dupXtns, sslBuffer *chInnerXtns,
PRBool compressing,
PRUint16 *called, unsignedint *nCalled)
{
PRUint8 buf[1024] = { 0 }; const PRUint8 *p; unsignedint len = 0;
PRBool willCompress;
PORT_Assert(extensionType != ssl_tls13_encrypted_client_hello_xtn);
sslCustomExtensionHooks *hook = ss->opt.callExtensionWriterOnEchInner
? ssl_FindCustomExtensionHooks(ss, extensionType)
: NULL; if (hook && hook->writer) { if (*nCalled >= MAX_EXTENSION_WRITERS) {
PORT_SetError(SEC_ERROR_LIBRARY_FAILURE); /* TODO new code? */ return SECFailure;
}
PRBool append = (*hook->writer)(ss->fd, ssl_hs_client_hello,
buf, &len, sizeof(buf), hook->writerArg);
called[(*nCalled)++] = extensionType; if (!append) { /* This extension is not going to appear in CHInner. */ /* TODO: consider removing this extension from ss->xtnData.advertised. * The consequence of not removing it is that we won't complain * if the server accepts ECH and then includes this extension. * The cost is a complete reworking of ss->xtnData.advertised.
*/ return SECSuccess;
} /* It can be compressed if it is the same as the outer value. */
willCompress = (len == extensionData->len &&
NSS_SecureMemcmp(buf, extensionData->buf, len) == 0);
p = buf;
} else { /* Non-custom extensions are duplicated when compressing. */
willCompress = PR_TRUE;
p = extensionData->buf;
len = extensionData->len;
}
/* Duplicated extensions all need to go together. */
sslBuffer *dst = willCompress ? dupXtns : chInnerXtns;
SECStatus rv = sslBuffer_AppendNumber(dst, extensionType, 2); if (rv != SECSuccess) { return SECFailure;
} if (!willCompress || !compressing) {
rv = sslBuffer_AppendVariable(dst, p, len, 2); if (rv != SECSuccess) { return SECFailure;
}
} /* As this function is called twice, we only want to update our state the second time. */ if (compressing) {
ss->xtnData.echAdvertised[ss->xtnData.echNumAdvertised++] = extensionType;
SSL_TRC(50, ("Appending extension=%d to the Client Hello Inner. Compressed?=%d", extensionType, willCompress));
} return SECSuccess;
}
/* Call any custom extension handlers that didn't want to be added to CHOuter. */ static SECStatus
tls13_ChInnerAdditionalExtensionWriters(sslSocket *ss, const PRUint16 *called, unsignedint nCalled, sslBuffer *chInnerXtns)
{ if (!ss->opt.callExtensionWriterOnEchInner) { return SECSuccess;
}
/* Skip if this hook was already called. */
PRBool hookCalled = PR_FALSE; for (unsignedint i = 0; i < nCalled; ++i) { if (called[i] == hook->type) {
hookCalled = PR_TRUE; break;
}
} if (hookCalled) { continue;
}
/* This is a cut-down version of ssl_CallCustomExtensionSenders(). */
PRUint8 buf[1024]; unsignedint len = 0;
PRBool append = (*hook->writer)(ss->fd, ssl_hs_client_hello,
buf, &len, sizeof(buf), hook->writerArg); if (!append) { continue;
}
/* Take the PSK extension CHOuter and fill it with junk. */ static SECStatus
tls13_RandomizePsk(PRUint8 *buf, unsignedint len)
{
sslReader rdr = SSL_READER(buf, len);
/* Read the length of identities. */
PRUint64 outerLen = 0;
SECStatus rv = sslRead_ReadNumber(&rdr, 2, &outerLen); if (rv != SECSuccess) { return SECFailure;
}
PORT_Assert(outerLen < len + 2);
/* Read the length of PskIdentity.identity */
PRUint64 innerLen = 0;
rv = sslRead_ReadNumber(&rdr, 2, &innerLen); if (rv != SECSuccess) { return SECFailure;
} /* identities should contain just one identity. */
PORT_Assert(outerLen == innerLen + 6);
/* Read the length of binders. */
rv = sslRead_ReadNumber(&rdr, 2, &outerLen); if (rv != SECSuccess) { return SECFailure;
}
PORT_Assert(outerLen + rdr.offset == len);
/* Read the length of the binder. */
rv = sslRead_ReadNumber(&rdr, 1, &innerLen); if (rv != SECSuccess) { return SECFailure;
} /* binders should contain just one binder. */
PORT_Assert(outerLen == innerLen + 1);
/* Randomize the binder. */
rv = PK11_GenerateRandom(buf + rdr.offset, innerLen); if (rv != SECSuccess) { return SECFailure;
}
return SECSuccess;
}
/* 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. * * Compressed extensions are moved in both runs. When compressing, they are moved * to a single outer_extensions extension, which lists extensions from CHOuter. * When not compressing, this produces the ClientHello that will be reconstructed * from the compressed ClientHello (that is, what goes into the handshake transcript), * so all the compressed extensions need to appear in the same place that the * outer_extensions extension appears. * * On the first run, if |inOutPskXtn| and OuterXtnsBuf contains a PSK extension, * remove it and return in the outparam.he 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 shouldCompress)
{
SECStatus rv;
PRUint64 extensionType;
sslReadBuffer extensionData;
sslBuffer pskXtn = SSL_BUFFER_EMPTY;
sslBuffer dupXtns = SSL_BUFFER_EMPTY; /* Duplicated extensions, types-only if |compress|. */ unsignedint tmpOffset; unsignedint tmpLen; unsignedint srcXtnBase; /* To truncate CHOuter and remove the PSK extension. */
PRUint16 called[MAX_EXTENSION_WRITERS] = { 0 }; /* For tracking which has been called. */ unsignedint nCalled = 0;
/* 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, 1, 2); if (rv != SECSuccess) { goto loser;
}
rv = sslBuffer_AppendNumber(chInnerXtns, ech_xtn_type_inner, 1); if (rv != SECSuccess) { goto loser;
}
/* Get the extension data. */
rv = sslRead_ReadVariable(&rdr, 2, &extensionData); if (rv != SECSuccess) { goto loser;
}
/* Skip extensions that are TLS < 1.3 only, since CHInner MUST * negotiate TLS 1.3 or above. * If the extension is supported by default (sslSupported) but unknown
* to TLS 1.3 it must be a TLS < 1.3 only extension. */
SSLExtensionSupport sslSupported;
(void)SSLExp_GetExtensionSupport(extensionType, &sslSupported); if (sslSupported != ssl_ext_none &&
tls13_ExtensionStatus(extensionType, ssl_hs_client_hello) == tls13_extension_unknown) { continue;
}
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;
} /* Only update state on second invocation of this function */ if (shouldCompress) {
ss->xtnData.echAdvertised[ss->xtnData.echNumAdvertised++] = extensionType;
} break; case ssl_tls13_supported_versions_xtn: /* Only TLS 1.3 and GREASE on CHInner. */
rv = sslBuffer_AppendNumber(chInnerXtns, extensionType, 2); if (rv != SECSuccess) { goto loser;
} /* Extension length. */
tmpLen = (ss->opt.enableGrease) ? 5 : 3;
rv = sslBuffer_AppendNumber(chInnerXtns, tmpLen, 2); if (rv != SECSuccess) { goto loser;
} /* ProtocolVersion length */
rv = sslBuffer_AppendNumber(chInnerXtns, tmpLen - 1, 1); if (rv != SECSuccess) { goto loser;
} /* ProtocolVersion TLS 1.3 */
rv = sslBuffer_AppendNumber(chInnerXtns, SSL_LIBRARY_VERSION_TLS_1_3, 2); if (rv != SECSuccess) { goto loser;
} /* ProtocolVersion GREASE */ if (ss->opt.enableGrease) {
rv = sslBuffer_AppendNumber(chInnerXtns, ss->ssl3.hs.grease->idx[grease_version], 2); if (rv != SECSuccess) { goto loser;
}
} /* Only update state on second invocation of this function */ if (shouldCompress) {
ss->xtnData.echAdvertised[ss->xtnData.echNumAdvertised++] = extensionType;
} break; case ssl_tls13_pre_shared_key_xtn: if (inOutPskXtn && !shouldCompress) {
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;
} /* This should be the last extension. */
PORT_Assert(srcXtnBase == ss->xtnData.lastXtnOffset);
PORT_Assert(chOuterXtnsBuf->len - srcXtnBase == extensionData.len + 4);
rv = tls13_RandomizePsk(chOuterXtnsBuf->buf + srcXtnBase + 4,
chOuterXtnsBuf->len - srcXtnBase - 4); if (rv != SECSuccess) { goto loser;
}
} elseif (!inOutPskXtn) { /* When GREASEing, only the length is used.
* Order doesn't matter, so just copy the extension. */
rv = sslBuffer_AppendNumber(chInnerXtns, extensionType, 2); if (rv != SECSuccess) { goto loser;
}
rv = sslBuffer_AppendVariable(chInnerXtns, extensionData.buf,
extensionData.len, 2); if (rv != SECSuccess) { goto loser;
}
} /* Only update state on second invocation of this function */ if (shouldCompress) {
ss->xtnData.echAdvertised[ss->xtnData.echNumAdvertised++] = extensionType;
} break; default: { /* This is a regular extension. We can maybe compress these. */
rv = tls13_ChInnerAppendExtension(ss, extensionType,
&extensionData,
&dupXtns, chInnerXtns,
shouldCompress,
called, &nCalled); if (rv != SECSuccess) { goto loser;
} break;
}
}
}
/* Now call custom extension handlers that didn't choose to append anything to
* the outer ClientHello. */
rv = tls13_ChInnerAdditionalExtensionWriters(ss, called, nCalled, chInnerXtns); if (rv != SECSuccess) { goto loser;
}
if (inOutPskXtn) { /* On the first, non-compress run, append the (bad) PSK binder. * On the second compression run, the caller is responsible for
* providing an extension with a valid binder, so append that. */ if (shouldCompress) {
rv = sslBuffer_AppendBuffer(chInnerXtns, inOutPskXtn);
} else {
rv = sslBuffer_AppendBuffer(chInnerXtns, &pskXtn);
*inOutPskXtn = pskXtn;
} if (rv != SECSuccess) { goto loser;
}
}
/* 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 */
}
/* We are using ECH so SNI must have been included */
rv = tls13_PadChInner(&encodedChInner, cfg->contents.maxNameLen, strlen(ss->url)); if (rv != SECSuccess) { goto loser;
}
/* Build the ECH Xtn with placeholder and put it in chOuterXtnsBuf */
sslBuffer echXtn = SSL_BUFFER_EMPTY; const SECItem *hpkeEnc = NULL; if (!ss->ssl3.hs.helloRetry) {
hpkeEnc = PK11_HPKE_GetEncapPubKey(ss->ssl3.hs.echHpkeCtx); if (!hpkeEnc) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); goto loser;
}
}
PRUint16 echXtnPayloadOffset; /* Offset from start of ECH Xtn to ECH Payload */
rv = tls13_BuildEchXtn(cfg, hpkeEnc, encodedChInner.len, &echXtnPayloadOffset, &echXtn); if (rv != SECSuccess) { goto loser;
}
ss->xtnData.echAdvertised[ss->xtnData.echNumAdvertised++] = ssl_tls13_encrypted_client_hello_xtn;
rv = ssl3_EmplaceExtension(ss, chOuterXtnsBuf, ssl_tls13_encrypted_client_hello_xtn,
echXtn.buf, echXtn.len, PR_TRUE); if (rv != SECSuccess) { goto loser;
}
/* Add the padding */
rv = ssl_InsertPaddingExtension(ss, chOuter->len, chOuterXtnsBuf); if (rv != SECSuccess) { goto loser;
}
/* Finish the CHO with the ECH Xtn payload zeroed */
rv = ssl3_InsertChHeaderSize(ss, chOuter, chOuterXtnsBuf); if (rv != SECSuccess) { goto loser;
} unsignedint chOuterXtnsOffset = chOuter->len + 2; /* From Start of CHO to Extensions list */
rv = sslBuffer_AppendBufferVariable(chOuter, chOuterXtnsBuf, 2); if (rv != SECSuccess) { goto loser;
}
/* AAD consists of entire CHO, minus the 4 byte handshake header */
SECItem aadItem = { siBuffer, chOuter->buf + 4, chOuter->len - 4 }; /* ECH Payload begins after CHO Header, after ECH Xtn start, after ECH Xtn header */
PRUint8 *echPayload = chOuter->buf + chOuterXtnsOffset + ss->xtnData.echXtnOffset + 4 + echXtnPayloadOffset; /* Insert the encrypted_client_hello xtn and coalesce. */
rv = tls13_EncryptClientHello(ss, &aadItem, &encodedChInner, echPayload); if (rv != SECSuccess) { goto loser;
}
if (ss->sec.isServer) {
previousTranscript = &(ss->ssl3.hs.messages);
} else {
previousTranscript = &(ss->ssl3.hs.echInnerMessages);
} /* * This segment calculates the hash of the Client Hello * TODO(djackson@mozilla.com) - Replace with existing function? * e.g. tls13_ReinjectHandshakeTranscript * TODO(djackson@mozilla.com) - Replace with streaming version
*/ if (!ss->ssl3.hs.helloRetry || !ss->sec.isServer) { /* * This function can be called in three situations: * - By the server, prior to sending the HRR, when ECH was accepted * - By the client, after receiving the HRR, but before it knows whether ECH was accepted * - By the server, after accepting ECH and receiving CH2 when it needs to reconstruct the HRR * In the first two situations, we need to include the message hash of inner ClientHello1 but don't * want to alter the buffer containing the current transcript. * In the last, the buffer already contains the message hash of inner ClientHello1.
*/
SSL3Hashes hashes;
rv = tls13_ComputeHash(ss, &hashes, previousTranscript->buf, previousTranscript->len, tls13_GetHash(ss)); if (rv != SECSuccess) { goto loser;
}
rv = sslBuffer_AppendNumber(out, ssl_hs_message_hash, 1); if (rv != SECSuccess) { goto loser;
}
rv = sslBuffer_AppendNumber(out, hashes.len, 3); if (rv != SECSuccess) { goto loser;
}
rv = sslBuffer_Append(out, hashes.u.raw, hashes.len); if (rv != SECSuccess) { goto loser;
}
} else {
rv = sslBuffer_AppendBuffer(out, previousTranscript); if (rv != SECSuccess) { goto loser;
}
} /* Ensure the first ClientHello has been hashed. */
PR_ASSERT(out->len == tls13_GetHashSize(ss) + 4);
PRINT_BUF(100, (ss, "ECH Client Hello Message Hash", out->buf, out->len)); /* Message Header */
rv = sslBuffer_AppendNumber(out, ssl_hs_server_hello, 1); if (rv != SECSuccess) { goto loser;
} /* Message Size */
rv = sslBuffer_AppendNumber(out, shLen, 3); if (rv != SECSuccess) { goto loser;
} /* Calculate where the HRR ECH Xtn Signal begins */ unsignedint absEchOffset; if (ss->sec.isServer) { /* We know the ECH HRR Xtn is last */
PORT_Assert(shLen >= TLS13_ECH_SIGNAL_LEN);
absEchOffset = shLen - TLS13_ECH_SIGNAL_LEN;
} else { /* We parsed the offset earlier */ /* The result of pointer comparision is unspecified * (and pointer arithemtic is undefined) if the pointers * do not point to the same array or struct. That means these * asserts cannot be relied on for correctness in compiled code, * but may help the reader understand the requirements.
*/
PORT_Assert(ss->xtnData.ech->hrrConfirmation > sh);
PORT_Assert(ss->xtnData.ech->hrrConfirmation < sh + shLen);
absEchOffset = ss->xtnData.ech->hrrConfirmation - sh;
}
PR_ASSERT(tls13_Debug_CheckXtnBegins(sh + absEchOffset - 4, ssl_tls13_encrypted_client_hello_xtn)); /* The HRR up to the ECH Xtn signal */
rv = sslBuffer_Append(out, sh, absEchOffset); if (rv != SECSuccess) { goto loser;
}
rv = sslBuffer_Append(out, zeroedEchSignal, sizeof(zeroedEchSignal)); if (rv != SECSuccess) { goto loser;
}
PR_ASSERT(absEchOffset + TLS13_ECH_SIGNAL_LEN <= shLen); /* The remainder of the HRR */
rv = sslBuffer_Append(out, sh + absEchOffset + TLS13_ECH_SIGNAL_LEN, shLen - absEchOffset - TLS13_ECH_SIGNAL_LEN); if (rv != SECSuccess) { goto loser;
}
PR_ASSERT(out->len == tls13_GetHashSize(ss) + 4 + shLen + 4); return SECSuccess;
loser:
sslBuffer_Clear(out); return SECFailure;
}
/* Copy the version and 24B of server_random. */
rv = sslBuffer_Append(out, sh, offset); if (rv != SECSuccess) { goto loser;
}
/* Zero the signal placeholder. */
rv = sslBuffer_AppendNumber(out, 0, TLS13_ECH_SIGNAL_LEN); if (rv != SECSuccess) { goto loser;
}
offset += TLS13_ECH_SIGNAL_LEN;
/* Use the remainder of SH. */
rv = sslBuffer_Append(out, &sh[offset], shLen - offset); if (rv != SECSuccess) { goto loser;
}
sslBuffer_Clear(&ss->ssl3.hs.messages);
sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages); return SECSuccess;
loser:
sslBuffer_Clear(&ss->ssl3.hs.messages);
sslBuffer_Clear(&ss->ssl3.hs.echInnerMessages);
sslBuffer_Clear(out); return SECFailure;
}
/* Compute the ECH signal using the transcript (up to, including) * ServerHello. 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. */
SECStatus
tls13_ComputeEchSignal(sslSocket *ss, PRBool isHrr, const PRUint8 *sh, unsignedint shLen, PRUint8 *out)
{
SECStatus rv;
sslBuffer confMsgs = SSL_BUFFER_EMPTY;
SSL3Hashes hashes;
PK11SymKey *echSecret = NULL;
/* Mark ECH as advertised so that we can validate any response.
* We'll use echHpkeCtx to determine if we sent real or GREASE ECH. */
rv = ssl3_EmplaceExtension(ss, buf, ssl_tls13_encrypted_client_hello_xtn,
greaseBuf.buf, greaseBuf.len, PR_TRUE); if (rv != SECSuccess) { goto loser;
}
/* Stash the GREASE ECH extension - in the case of HRR, CH2 must echo it. */
ss->ssl3.hs.greaseEchBuf = greaseBuf;
/* If !echHpkeCtx, we either didn't advertise or sent GREASE ECH. */ if (!ss->ssl3.hs.echHpkeCtx) {
SSL_TRC(50, ("%d: TLS13[%d]: client only sent GREASE ECH",
SSL_GETPID(), ss->fd));
ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; return SECSuccess;
}
PORT_Assert(!IS_DTLS(ss));
if (isHrr) { if (ss->xtnData.ech) {
signal = ss->xtnData.ech->hrrConfirmation;
} else {
SSL_TRC(50, ("%d: TLS13[%d]: client did not receive ECH Xtn from Server HRR",
SSL_GETPID(), ss->fd));
signal = NULL;
ss->ssl3.hs.echAccepted = PR_FALSE;
ss->ssl3.hs.echDecided = PR_TRUE;
}
} else {
signal = &ss->ssl3.hs.server_random[SSL3_RANDOM_LENGTH - TLS13_ECH_SIGNAL_LEN];
}
/* Check ECH Confirmation for HRR ECH Xtn or ServerHello Random */ if (signal) {
rv = tls13_ComputeEchSignal(ss, isHrr, sh, shLen, computed); if (rv != SECSuccess) { return SECFailure;
}
PRINT_BUF(100, (ss, "Server Signal", signal, TLS13_ECH_SIGNAL_LEN));
PRBool new_decision = !NSS_SecureMemcmp(computed, signal, TLS13_ECH_SIGNAL_LEN); /* Server can't change its mind on whether to accept ECH */ if (ss->ssl3.hs.echDecided && new_decision != ss->ssl3.hs.echAccepted) {
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_SERVER_HELLO, illegal_parameter); return SECFailure;
}
ss->ssl3.hs.echAccepted = new_decision;
ss->ssl3.hs.echDecided = PR_TRUE;
}
ss->ssl3.hs.preliminaryInfo |= ssl_preinfo_ech; 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;
} /* Server accepted, but sent an extension which was only advertised in the ClientHelloOuter */ if (ss->ssl3.hs.echInvalidExtension) {
(void)SSL3_SendAlert(ss, alert_fatal, unsupported_extension);
PORT_SetError(SSL_ERROR_RX_UNEXPECTED_EXTENSION); return SECFailure;
}
/* Swap the advertised lists as we've accepted ECH. */
PRUint16 *tempArray = ss->xtnData.advertised;
PRUint16 tempNum = ss->xtnData.numAdvertised;
/* |enc| must not be included in CH2.ClientECH. */ if (ss->ssl3.hs.helloRetry && ss->sec.isServer &&
ss->xtnData.ech->senderPubKey.len) {
ssl3_ExtSendAlert(ss, alert_fatal, illegal_parameter);
PORT_SetError(SSL_ERROR_BAD_2ND_CLIENT_HELLO); return SECFailure;
}
ss->xtnData.negotiated[ss->xtnData.numNegotiated++] = ssl_tls13_encrypted_client_hello_xtn;
/* Only overwrite client_random with client_inner_random if CHInner was
* succesfully used for handshake (NOT if HRR is received). */ if (!isHrr) {
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(3, ("%d: TLS13[%d]: ECH %s accepted by server",
SSL_GETPID(), ss->fd, ss->ssl3.hs.echAccepted ? "is" : "is not")); return SECSuccess;
}
echExtension = ssl3_FindExtension(ss, ssl_tls13_encrypted_client_hello_xtn); if (!echExtension) {
error = SSL_ERROR_MISSING_ECH_EXTENSION;
errDesc = illegal_parameter; goto alert_loser; /* Must have an inner Extension */
}
rv = tls13_ServerHandleInnerEchXtn(ss, &ss->xtnData, &echExtension->data); if (rv != SECSuccess) { goto loser; /* code set, 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) {
SSL_TRC(10, ("%d: SSL3[%d]: copying inner extension of type %d and size %d directly", SSL_GETPID(),
ss->fd, innerExtension->type, innerExtension->data.len));
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;
}
/* Extract ECH info without restoring hash state. If there's * something wrong with the cookie, continue without ECH
* and let HRR code handle the problem. */
rv = tls13_HandleHrrCookie(ss, cookieData.data, cookieData.len,
NULL, NULL, &previouslyOfferedEch, &echData, PR_FALSE); if (rv != SECSuccess) { return SECSuccess;
}
if (!ss->ssl3.hs.echHpkeCtx) { return SECSuccess;
}
}
if (ss->ssl3.hs.echDecided && !ss->ssl3.hs.echAccepted) { /* We don't change our mind */ return SECSuccess;
} /* Regardless of where we return, the outcome is decided */
ss->ssl3.hs.echDecided = PR_TRUE;
/* Cookie data was good, proceed with ECH. */
rv = tls13_GetMatchingEchConfigs(ss, ss->xtnData.ech->kdfId, ss->xtnData.ech->aeadId,
ss->xtnData.ech->configId, candidate, &candidate); if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error); return SECFailure;
}
if (candidate) {
rv = tls13_ServerMakeChOuterAAD(ss, chOuter, chOuterLen, &outerAAD); if (rv != SECSuccess) { return SECFailure;
}
}
while (candidate) {
rv = tls13_OpenClientHelloInner(ss, &outer, &outerAAD, candidate, &decryptedChInner); if (rv != SECSuccess) { /* Get the next matching config */
rv = tls13_GetMatchingEchConfigs(ss, ss->xtnData.ech->kdfId, ss->xtnData.ech->aeadId,
ss->xtnData.ech->configId, candidate, &candidate); if (rv != SECSuccess) {
FATAL_ERROR(ss, SEC_ERROR_LIBRARY_FAILURE, internal_error);
SECITEM_FreeItem(&outerAAD, PR_FALSE); return SECFailure;
} continue;
} break;
}
SECITEM_FreeItem(&outerAAD, PR_FALSE);
if (rv != SECSuccess || !decryptedChInner) { if (ss->ssl3.hs.helloRetry) {
FATAL_ERROR(ss, SSL_ERROR_RX_MALFORMED_ECH_EXTENSION, decrypt_error); return SECFailure;
} else { /* Send retry_configs (if we have any) when we fail to decrypt or
* found no candidates. This does *not* count as negotiating ECH. */ return ssl3_RegisterExtensionSender(ss, &ss->xtnData,
ssl_tls13_encrypted_client_hello_xtn,
tls13_ServerSendEchXtn);
}
}
/* 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);
SECStatus
tls13_WriteServerEchHrrSignal(sslSocket *ss, PRUint8 *sh, unsignedint shLen)
{
SECStatus rv;
PR_ASSERT(shLen >= 4 + TLS13_ECH_SIGNAL_LEN); /* We put the HRR ECH extension last. */
PRUint8 *placeholder_location = sh + shLen - TLS13_ECH_SIGNAL_LEN; /* Defensive check that we are overwriting the contents of the right extension */
PR_ASSERT(tls13_Debug_CheckXtnBegins(placeholder_location - 4, ssl_tls13_encrypted_client_hello_xtn)); /* Calculate signal and overwrite */
rv = tls13_ComputeEchSignal(ss, PR_TRUE, sh, shLen, placeholder_location); if (rv != SECSuccess) { return SECFailure;
} /* Free HRR GREASE/accept_confirmation value, it MUST be restored from
* cookie when handling CH2 after HRR. */
sslBuffer_Clear(&ss->ssl3.hs.greaseEchBuf); return SECSuccess;
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.38 Sekunden
(vorverarbeitet am 2026-04-25)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.