TLS ECH compliance fixes

This commit is contained in:
sebastian-carpenter
2026-03-25 12:04:42 -06:00
parent 5151a695bc
commit 61ba5378fe
9 changed files with 1323 additions and 252 deletions
+52 -20
View File
@@ -8773,6 +8773,10 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl)
FreeEchConfigs(ssl->echConfigs, ssl->heap);
ssl->echConfigs = NULL;
}
if (ssl->echRetryConfigs != NULL) {
FreeEchConfigs(ssl->echRetryConfigs, ssl->heap);
ssl->echRetryConfigs = NULL;
}
#endif /* HAVE_ECH */
#endif /* WOLFSSL_TLS13 */
#ifdef WOLFSSL_HAVE_TLS_UNIQUE
@@ -15744,6 +15748,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
byte* subjectHash = NULL;
int alreadySigner = 0;
char* domainName = NULL;
#if defined(HAVE_CERTIFICATE_STATUS_REQUEST_V2)
int addToPendingCAs = 0;
#endif
@@ -16932,17 +16938,34 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
}
#endif
if (!ssl->options.verifyNone && ssl->buffers.domainName.buffer) {
domainName = (char*)ssl->buffers.domainName.buffer;
#if !defined(NO_WOLFSSL_CLIENT) && defined(HAVE_ECH)
/* RFC 9849 s6.1.7: ECH offered but rejected by the server...
* verify cert is valid for ECHConfig.public_name */
if (ssl->options.side == WOLFSSL_CLIENT_END &&
ssl->echConfigs != NULL &&
!ssl->options.echAccepted) {
TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX != NULL && echX->data != NULL) {
WOLFSSL_ECH* ech = (WOLFSSL_ECH*)echX->data;
if (ech->echConfig != NULL &&
ech->echConfig->publicName != NULL) {
domainName = ech->echConfig->publicName;
}
}
}
#endif
if (!ssl->options.verifyNone && domainName) {
#ifndef WOLFSSL_ALLOW_NO_CN_IN_SAN
/* Per RFC 5280 section 4.2.1.6, "Whenever such identities
* are to be bound into a certificate, the subject
* alternative name extension MUST be used." */
if (args->dCert->altNames) {
if (CheckForAltNames(args->dCert,
(char*)ssl->buffers.domainName.buffer,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(
(const char *)ssl->buffers.domainName.buffer)),
if (CheckForAltNames(
args->dCert,
domainName,
(word32)XSTRLEN((const char *)domainName),
NULL, 0, 0) != 1) {
WOLFSSL_MSG("DomainName match on alt names failed");
/* try to get peer key still */
@@ -16955,11 +16978,9 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
if (MatchDomainName(
args->dCert->subjectCN,
args->dCert->subjectCNLen,
(char*)ssl->buffers.domainName.buffer,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(
(const char *)ssl->buffers.domainName.buffer)
), 0) == 0)
domainName,
(word32)XSTRLEN((const char *)domainName),
0) == 0)
#endif
{
WOLFSSL_MSG("DomainName match failed");
@@ -16970,18 +16991,19 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
#else /* WOLFSSL_ALL_NO_CN_IN_SAN */
/* Old behavior. */
#ifndef WOLFSSL_HOSTNAME_VERIFY_ALT_NAME_ONLY
if (MatchDomainName(args->dCert->subjectCN,
if (MatchDomainName(
args->dCert->subjectCN,
args->dCert->subjectCNLen,
(char*)ssl->buffers.domainName.buffer,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(ssl->buffers.domainName.buffer)), 0) == 0)
domainName,
(word32)XSTRLEN((const char *)domainName),
0) == 0)
#endif
{
if (CheckForAltNames(args->dCert,
(char*)ssl->buffers.domainName.buffer,
(ssl->buffers.domainName.buffer == NULL ? 0 :
(word32)XSTRLEN(ssl->buffers.domainName.buffer)),
NULL, 0, 0) != 1) {
if (CheckForAltNames(
args->dCert,
domainName,
(word32)XSTRLEN((const char *)domainName),
NULL, 0, 0) != 1) {
WOLFSSL_MSG("DomainName match failed");
/* try to get peer key still */
ret = DOMAIN_NAME_MISMATCH;
@@ -22210,6 +22232,13 @@ const char* AlertTypeToString(int type)
return no_application_protocol_str;
}
case ech_required:
{
static const char ech_required_str[] =
"ech_required";
return ech_required_str;
}
default:
WOLFSSL_MSG("Unknown Alert");
return NULL;
@@ -27828,6 +27857,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e)
case SESSION_TICKET_NONCE_OVERFLOW:
return "Session ticket nonce overflow";
case ECH_REQUIRED_E:
return "ECH offered but rejected by server";
}
return "unknown error number";
+152 -102
View File
@@ -311,6 +311,25 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs,
return ret;
}
/* store retry configs received during ECH rejection
* returns 0 on success, error otherwise */
int SetRetryConfigs(WOLFSSL* ssl, const byte* echConfigs, word32 echConfigsLen)
{
int ret;
if (ssl == NULL || echConfigs == NULL || echConfigsLen == 0)
return BAD_FUNC_ARG;
if (ssl->echRetryConfigs != NULL) {
return WOLFSSL_FATAL_ERROR;
}
ret = SetEchConfigsEx(&ssl->echRetryConfigs, ssl->heap, echConfigs,
echConfigsLen);
return ret;
}
/* get the raw ech config from our struct */
int GetEchConfig(WOLFSSL_EchConfig* config, byte* output, word32* outputLen)
{
@@ -434,6 +453,21 @@ int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* output, word32* outputLen)
return GetEchConfigsEx(ssl->echConfigs, output, outputLen);
}
/* wrapper function to get retry configs
* a client should only call this after the 'wolfSSL_connect()' call fails
* returns error if retry configs were not received or were malformed */
int wolfSSL_GetEchRetryConfigs(WOLFSSL* ssl, byte* output, word32* outputLen)
{
if (ssl == NULL || outputLen == NULL)
return BAD_FUNC_ARG;
if (ssl->echRetryConfigs == NULL || !ssl->options.echRetryConfigsAccepted) {
return WOLFSSL_FATAL_ERROR;
}
return GetEchConfigsEx(ssl->echRetryConfigs, output, outputLen);
}
void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable)
{
if (ssl != NULL) {
@@ -446,10 +480,40 @@ void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable)
}
}
/* Walk the ECHConfigExtension list and check for mandatory extensions.
* Returns:
* 0 if all extensions are known/optional,
* error otherwise. */
static int EchConfigCheckExtensions(const byte* exts, word16 extsLen)
{
word16 bytesLeft = extsLen;
word16 extType;
word16 extDataLen;
while (bytesLeft >= 4) {
ato16(exts, &extType);
ato16(exts + 2, &extDataLen);
if (bytesLeft - 4 < extDataLen)
return BUFFER_E;
if (extType & 0x8000)
return UNSUPPORTED_EXTENSION;
exts += 4 + extDataLen;
bytesLeft -= 4 + extDataLen;
}
if (bytesLeft != 0)
return BUFFER_E;
return 0;
}
/* Parse the ECH configs and output to the corresponding outputConfigs
* return 0 on success, error otherwise */
int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap,
const byte* echConfigs, word32 echConfigsLen)
{
int ret = 0;
int unsupportedAlgos = 0;
word32 configIdx;
word32 idx;
int j;
@@ -470,12 +534,13 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap,
/* check that the total length is well formed */
ato16(echConfigs, &totalLength);
if (totalLength != echConfigsLen - 2) {
return WOLFSSL_FATAL_ERROR;
}
if (totalLength != echConfigsLen - 2)
return BUFFER_E;
configIdx = 2;
do {
/* version (2) + length (2) */
if (configIdx + 4 > echConfigsLen) {
ret = BUFFER_E;
break;
@@ -484,91 +549,89 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap,
ato16(echConfig, &version);
ato16(echConfig + 2, &length);
if (configIdx + length + 4 > echConfigsLen) {
if (configIdx + 4 + length > echConfigsLen) {
ret = BUFFER_E;
break;
}
else if (version != TLSX_ECH) {
/* skip this config and try the next one */
configIdx += length + 4;
if (version != TLSX_ECH) {
configIdx += 4 + length;
continue;
}
if (workingConfig == NULL) {
workingConfig =
(WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), heap,
DYNAMIC_TYPE_TMP_BUFFER);
workingConfig = (WOLFSSL_EchConfig*)XMALLOC(
sizeof(WOLFSSL_EchConfig), heap, DYNAMIC_TYPE_TMP_BUFFER);
configList = workingConfig;
}
else {
lastConfig = workingConfig;
workingConfig->next =
(WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), heap,
DYNAMIC_TYPE_TMP_BUFFER);
workingConfig->next = (WOLFSSL_EchConfig*)XMALLOC(
sizeof(WOLFSSL_EchConfig), heap, DYNAMIC_TYPE_TMP_BUFFER);
workingConfig = workingConfig->next;
}
if (workingConfig == NULL) {
ret = MEMORY_E;
break;
}
XMEMSET(workingConfig, 0, sizeof(WOLFSSL_EchConfig));
/* rawLen */
workingConfig->rawLen = length + 4;
/* raw body */
workingConfig->rawLen = 4 + length;
workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen, heap,
DYNAMIC_TYPE_TMP_BUFFER);
DYNAMIC_TYPE_TMP_BUFFER);
if (workingConfig->raw == NULL) {
ret = MEMORY_E;
break;
}
XMEMCPY(workingConfig->raw, echConfig, workingConfig->rawLen);
/* skip over version and length */
/* version and length already checked */
echConfig += 4;
idx = 0;
idx = 5;
if (idx >= length) {
/* configId */
if (idx + 1 > length) {
ret = BUFFER_E;
break;
}
workingConfig->configId = echConfig[idx];
idx += 1;
/* configId, 1 byte */
workingConfig->configId = *echConfig;
echConfig++;
/* kemId, 2 bytes */
ato16(echConfig, &workingConfig->kemId);
echConfig += 2;
/* hpke public_key length, 2 bytes */
ato16(echConfig, &hpkePubkeyLen);
echConfig += 2;
/* hpke public_key
* KEM support will be checked along with the ciphersuites */
if (hpkePubkeyLen != wc_HpkeKemGetEncLen(workingConfig->kemId)) {
/* kemId */
if (idx + 2 > length) {
ret = BUFFER_E;
break;
}
ato16(echConfig + idx, &workingConfig->kemId);
idx += 2;
/* hpke public_key */
if (idx + 2 > length) {
ret = BUFFER_E;
break;
}
ato16(echConfig + idx, &hpkePubkeyLen);
idx += 2;
if (idx + hpkePubkeyLen > length) {
ret = BUFFER_E;
break;
}
/* unsupported KEM: skip pubkey; end of loop will free this config */
if (wc_HpkeKemIsSupported(workingConfig->kemId)) {
if (hpkePubkeyLen != wc_HpkeKemGetEncLen(workingConfig->kemId)) {
ret = BUFFER_E;
break;
}
XMEMCPY(workingConfig->receiverPubkey, echConfig + idx, hpkePubkeyLen);
}
idx += hpkePubkeyLen;
if (idx >= length) {
/* cipher suites */
if (idx + 2 > length) {
ret = BUFFER_E;
break;
}
XMEMCPY(workingConfig->receiverPubkey, echConfig, hpkePubkeyLen);
echConfig += hpkePubkeyLen;
/* cipherSuitesLen */
ato16(echConfig + idx, &cipherSuitesLen);
idx += 2;
if (idx >= length) {
ret = BUFFER_E;
break;
}
ato16(echConfig, &cipherSuitesLen);
if (cipherSuitesLen == 0 || cipherSuitesLen % 4 != 0 ||
cipherSuitesLen >= 1024) {
/* numCipherSuites is a byte so only 256 ciphersuites (each 4 bytes)
@@ -576,108 +639,93 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap,
ret = BUFFER_E;
break;
}
idx += cipherSuitesLen;
if (idx >= length) {
if (idx + cipherSuitesLen > length) {
ret = BUFFER_E;
break;
}
workingConfig->cipherSuites = (EchCipherSuite*)XMALLOC(cipherSuitesLen,
heap, DYNAMIC_TYPE_TMP_BUFFER);
if (workingConfig->cipherSuites == NULL) {
ret = MEMORY_E;
break;
}
echConfig += 2;
workingConfig->numCipherSuites = (byte)(cipherSuitesLen / 4);
/* cipherSuites */
for (j = 0; j < workingConfig->numCipherSuites; j++) {
ato16(echConfig, &workingConfig->cipherSuites[j].kdfId);
ato16(echConfig + 2, &workingConfig->cipherSuites[j].aeadId);
echConfig += 4;
ato16(echConfig + idx, &workingConfig->cipherSuites[j].kdfId);
ato16(echConfig + idx + 2, &workingConfig->cipherSuites[j].aeadId);
idx += 4;
}
/* ignore the maximum name length */
idx++;
if (idx >= length) {
/* ignore maximum name length */
if (idx + 1 > length) {
ret = BUFFER_E;
break;
}
echConfig++;
idx += 1;
/* publicNameLen */
idx++;
if (idx >= length) {
/* publicName */
if (idx + 1 > length) {
ret = BUFFER_E;
break;
}
publicNameLen = *echConfig;
publicNameLen = echConfig[idx];
idx += 1;
if (publicNameLen == 0) {
ret = BUFFER_E;
break;
}
idx += publicNameLen;
if (idx >= length) {
if (idx + publicNameLen > length) {
ret = BUFFER_E;
break;
}
echConfig++;
workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1,
heap, DYNAMIC_TYPE_TMP_BUFFER);
workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1, heap,
DYNAMIC_TYPE_TMP_BUFFER);
if (workingConfig->publicName == NULL) {
ret = MEMORY_E;
break;
}
/* publicName */
XMEMCPY(workingConfig->publicName, echConfig, publicNameLen);
XMEMCPY(workingConfig->publicName, echConfig + idx, publicNameLen);
workingConfig->publicName[publicNameLen] = '\0';
echConfig += publicNameLen;
idx += publicNameLen;
/* TODO: Parse ECHConfigExtension */
/* --> for now just ignore it */
/* extensions */
if (idx + 2 > length) {
ret = BUFFER_E;
break;
}
ato16(echConfig + idx, &extensionsLen);
idx += 2;
if (idx > length) {
ret = BUFFER_E;
break;
}
ato16(echConfig, &extensionsLen);
idx += extensionsLen;
if (idx != length) {
if (idx + extensionsLen != length) {
ret = BUFFER_E;
break;
}
/* KEM or ciphersuite not supported, free this config and then try to
* parse another */
if (EchConfigGetSupportedCipherSuite(workingConfig) < 0) {
ret = EchConfigCheckExtensions(echConfig + idx, extensionsLen);
if (ret < 0)
break;
/* KEM, ciphersuite, or mandatory extension not supported, free this
* config and then try to parse another */
if (ret == WC_NO_ERR_TRACE(UNSUPPORTED_EXTENSION) ||
EchConfigGetSupportedCipherSuite(workingConfig) < 0) {
ret = 0;
unsupportedAlgos = 1;
XFREE(workingConfig->cipherSuites, heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(workingConfig->publicName, heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(workingConfig->raw, heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(workingConfig, heap, DYNAMIC_TYPE_TMP_BUFFER);
workingConfig = lastConfig;
if (workingConfig != NULL) {
if (workingConfig != NULL)
workingConfig->next = NULL;
}
else {
/* if one (or more) of the leading configs are unsupported then
* this case will be hit */
else
configList = NULL;
}
}
configIdx += 4 + length;
} while (configIdx < echConfigsLen);
if (ret == 0 && configIdx != echConfigsLen){
if (ret == 0 && configIdx != echConfigsLen)
ret = BUFFER_E;
}
/* if we found valid configs */
if (ret == 0 && configList != NULL) {
@@ -697,8 +745,11 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap,
XFREE(lastConfig, heap, DYNAMIC_TYPE_TMP_BUFFER);
}
/* syntactically correct but configs are not supported */
if (ret == 0 && unsupportedAlgos)
return UNSUPPORTED_SUITE;
if (ret == 0)
return WOLFSSL_FATAL_ERROR;
return UNSUPPORTED_PROTO_VERSION;
return ret;
}
@@ -717,7 +768,6 @@ int GetEchConfigsEx(WOLFSSL_EchConfig* configs, byte* output, word32* outputLen)
return BAD_FUNC_ARG;
}
/* skip over total length which we fill in later */
if (output != NULL) {
workingOutputLen = *outputLen - totalLen;
+121 -74
View File
@@ -13796,6 +13796,8 @@ static int TLSX_ECH_CheckInnerPadding(WOLFSSL* ssl, WOLFSSL_ECH* ech)
headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ :
HANDSHAKE_HEADER_SZ;
#else
(void)ssl;
headerSz = HANDSHAKE_HEADER_SZ;
#endif
@@ -13835,7 +13837,6 @@ static int TLSX_ECH_CheckInnerPadding(WOLFSSL* ssl, WOLFSSL_ECH* ech)
acc |= innerCh[i];
}
if (acc != 0) {
SendAlert(ssl, alert_fatal, illegal_parameter);
return INVALID_PARAMETER;
}
@@ -13920,14 +13921,14 @@ static const byte* TLSX_ECH_FindOuterExtension(const byte* outerCh,
/* If newinnerCh is NULL, validate ordering and existence of references
* - updates newInnerChLen with total length of selected extensions
* If newinnerCh in not NULL, copy extensions into newInnerCh
* If newinnerCh is not NULL, copy extensions into newInnerCh
*
* outerCh The outer ClientHello buffer.
* outerChLen Outer ClientHello length.
* newInnerCh The inner ClientHello buffer.
* newInnerChLen Inner ClientHello length.
* numOuterRefs Number of references described by OuterExtensions extension.
* numOuterTypes References described by OuterExtensions extension.
* OuterRefTypes References described by OuterExtensions extension.
* returns 0 on success and otherwise failure.
*/
static int TLSX_ECH_CopyOuterExtensions(const byte* outerCh, word32 outerChLen,
@@ -13938,55 +13939,43 @@ static int TLSX_ECH_CopyOuterExtensions(const byte* outerCh, word32 outerChLen,
word16 refType;
word32 outerExtLen;
word32 outerExtOffset = 0;
word16 extsStart;
word16 extsLen;
word16 extsStart = 0;
word16 extsLen = 0;
const byte* outerExtData;
if (newInnerCh == NULL) {
*newInnerChLen = 0;
while (numOuterRefs-- > 0) {
ato16(outerRefTypes, &refType);
if (refType == TLSXT_ECH) {
WOLFSSL_MSG("ECH: ech_outer_extensions references ECH");
ret = INVALID_PARAMETER;
break;
}
outerExtData = TLSX_ECH_FindOuterExtension(outerCh, outerChLen,
refType, &outerExtLen, &outerExtOffset,
&extsStart, &extsLen);
if (outerExtData == NULL) {
WOLFSSL_MSG("ECH: referenced extension not in outer CH");
ret = INVALID_PARAMETER;
break;
}
*newInnerChLen += outerExtLen;
outerRefTypes += OPAQUE16_LEN;
}
}
else {
while (numOuterRefs-- > 0) {
ato16(outerRefTypes, &refType);
outerExtData = TLSX_ECH_FindOuterExtension(outerCh, outerChLen,
refType, &outerExtLen, &outerExtOffset,
&extsStart, &extsLen);
while (numOuterRefs-- > 0) {
ato16(outerRefTypes, &refType);
if (outerExtData == NULL) {
ret = INVALID_PARAMETER;
break;
}
if (refType == TLSXT_ECH) {
WOLFSSL_MSG("ECH: ech_outer_extensions references ECH");
ret = INVALID_PARAMETER;
break;
}
outerExtData = TLSX_ECH_FindOuterExtension(outerCh, outerChLen,
refType, &outerExtLen, &outerExtOffset,
&extsStart, &extsLen);
if (outerExtData == NULL) {
WOLFSSL_MSG("ECH: referenced extension not in outer CH or out "
"of order");
ret = INVALID_PARAMETER;
break;
}
if (newInnerCh == NULL) {
*newInnerChLen += outerExtLen;
}
else {
XMEMCPY(*newInnerCh, outerExtData, outerExtLen);
*newInnerCh += outerExtLen;
outerRefTypes += OPAQUE16_LEN;
}
outerRefTypes += OPAQUE16_LEN;
}
return ret;
@@ -14197,6 +14186,7 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig,
{
int ret = 0;
int i;
int allocatedHpke = 0;
word32 rawConfigLen = 0;
byte* info = NULL;
word32 infoLen = 0;
@@ -14217,6 +14207,7 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig,
}
/* check if hpke already exists, may if HelloRetryRequest */
if (ech->hpke == NULL) {
allocatedHpke = 1;
ech->hpke = (Hpke*)XMALLOC(sizeof(Hpke), heap, DYNAMIC_TYPE_TMP_BUFFER);
if (ech->hpke == NULL)
ret = MEMORY_E;
@@ -14265,8 +14256,9 @@ static int TLSX_ExtractEch(WOLFSSL_ECH* ech, WOLFSSL_EchConfig* echConfig,
ech->outerClientPayload, ech->innerClientHelloLen,
ech->innerClientHello + HANDSHAKE_HEADER_SZ);
}
/* free the hpke and context on failure */
if (ret != 0) {
/* only free hpke/hpkeContext if allocated in this call; otherwise preserve
* them for clientHello2 */
if (ret != 0 && allocatedHpke) {
XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER);
ech->hpke = NULL;
XFREE(ech->hpkeContext, heap, DYNAMIC_TYPE_TMP_BUFFER);
@@ -14305,16 +14297,36 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
/* retry configs */
if (msgType == encrypted_extensions) {
ret = wolfSSL_SetEchConfigs(ssl, readBuf, size);
/* configs must only be sent on ECH rejection (RFC9849, Section 5) */
if (ssl->options.echAccepted) {
SendAlert(ssl, alert_fatal, unsupported_extension);
WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION);
return UNSUPPORTED_EXTENSION;
}
if (ret == WOLFSSL_SUCCESS)
ret = SetRetryConfigs(ssl, readBuf, (word32)size);
if (ret == WC_NO_ERR_TRACE(UNSUPPORTED_SUITE) ||
ret == WC_NO_ERR_TRACE(UNSUPPORTED_PROTO_VERSION)) {
WOLFSSL_MSG("ECH retry configs had 'bad version' or 'bad suite'");
ret = 0;
}
if (ssl->echConfigs == NULL) {
/* on GREASE connection configs must be checked syntactically and
* must not be saved (RFC 9849, Section 6.2.1) */
FreeEchConfigs(ssl->echRetryConfigs, ssl->heap);
ssl->echRetryConfigs = NULL;
}
/* retry configs may only be accepted at the point when ECH_REQUIRED is
* sent */
ssl->options.echRetryConfigsAccepted = 0;
}
/* HRR with special confirmation */
else if (msgType == hello_retry_request && ssl->echConfigs != NULL) {
/* length must be 8 */
if (size != ECH_ACCEPT_CONFIRMATION_SZ)
return BAD_FUNC_ARG;
return BUFFER_ERROR;
/* get extension */
echX = TLSX_Find(ssl->extensions, TLSX_ECH);
@@ -14331,17 +14343,28 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
return BAD_FUNC_ARG;
ech = (WOLFSSL_ECH*)echX->data;
/* if the first ECH was rejected or CH1 did not have ECH then there is
* no need to decrypt this one */
if (!ssl->options.echAccepted && ssl->options.serverState ==
SERVER_HELLO_RETRY_REQUEST_COMPLETE) {
ech->state = ECH_WRITE_RETRY_CONFIGS;
return 0;
}
/* read the ech parameters before the payload */
ech->type = *readBuf_p;
readBuf_p++;
offset += 1;
if (ech->type == ECH_TYPE_INNER) {
if (ssl->options.echProcessingInner && ech->type == ECH_TYPE_INNER) {
ech->state = ECH_PARSED_INTERNAL;
return 0;
}
else if (ech->type != ECH_TYPE_OUTER) {
/* type MUST be INNER or OUTER */
return BAD_FUNC_ARG;
else if ((!ssl->options.echProcessingInner &&
ech->type != ECH_TYPE_OUTER) ||
(ssl->options.echProcessingInner &&
ech->type != ECH_TYPE_INNER)) {
/* MUST process INNER in inner hello and OUTER in outer hello */
return INVALID_PARAMETER;
}
/* Must have kdfId, aeadId, configId, enc len and payload len. */
if (size < offset + 2 + 2 + 1 + 2 + 2) {
@@ -14368,10 +14391,10 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
/* Check encLen isn't more than remaining bytes minus
* payload length. */
if (len > size - offset - 2) {
return BAD_FUNC_ARG;
return BUFFER_ERROR;
}
if (len > HPKE_Npk_MAX) {
return BAD_FUNC_ARG;
return BUFFER_ERROR;
}
/* read enc */
XMEMCPY(ech->enc, readBuf_p, len);
@@ -14382,27 +14405,27 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
/* kdfId */
ato16(readBuf_p, &tmpVal16);
if (tmpVal16 != ech->cipherSuite.kdfId) {
return BAD_FUNC_ARG;
return INVALID_PARAMETER;
}
readBuf_p += 2;
offset += 2;
/* aeadId */
ato16(readBuf_p, &tmpVal16);
if (tmpVal16 != ech->cipherSuite.aeadId) {
return BAD_FUNC_ARG;
return INVALID_PARAMETER;
}
readBuf_p += 2;
offset += 2;
/* configId */
if (*readBuf_p != ech->configId) {
return BAD_FUNC_ARG;
return INVALID_PARAMETER;
}
readBuf_p++;
offset++;
/* on an HRR the enc value MUST be empty */
ato16(readBuf_p, &len);
if (len != 0) {
return BAD_FUNC_ARG;
return INVALID_PARAMETER;
}
readBuf_p += 2;
offset += 2;
@@ -14416,7 +14439,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
offset += 2;
/* Check payload is no bigger than remaining bytes. */
if (ech->innerClientHelloLen > size - offset) {
return BAD_FUNC_ARG;
return BUFFER_ERROR;
}
if (ech->innerClientHelloLen < WC_AES_BLOCK_SIZE) {
return BUFFER_ERROR;
@@ -14464,22 +14487,47 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
echConfig = echConfig->next;
}
}
if (ret == 0) {
/* if we failed to extract/expand */
if (ret != 0) {
WOLFSSL_MSG("ECH rejected");
if (ssl->options.echAccepted == 1) {
/* on SH2 this is fatal */
SendAlert(ssl, alert_fatal, decrypt_error);
WOLFSSL_ERROR_VERBOSE(DECRYPT_ERROR);
ret = DECRYPT_ERROR;
}
else {
/* on SH1 prepare to write retry configs */
XFREE(ech->innerClientHello, ssl->heap,
DYNAMIC_TYPE_TMP_BUFFER);
ech->innerClientHello = NULL;
ech->state = ECH_WRITE_RETRY_CONFIGS;
ret = 0;
}
}
else {
ret = TLSX_ECH_CheckInnerPadding(ssl, ech);
if (ret == 0) {
/* expand EchOuterExtensions if present.
* Also, if it exists, copy sessionID from outer hello */
ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap);
}
if (ret == 0){
WOLFSSL_MSG("ECH accepted");
ssl->options.echAccepted = 1;
}
else {
WOLFSSL_MSG("ECH rejected");
}
}
/* if we failed to extract/expand, set state to retry configs */
if (ret != 0) {
XFREE(ech->innerClientHello, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
ech->innerClientHello = NULL;
ech->state = ECH_WRITE_RETRY_CONFIGS;
}
XFREE(aadCopy, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
return 0;
}
return ret;
@@ -16306,6 +16354,12 @@ static int TLSX_GetSizeWithEch(WOLFSSL* ssl, byte* semaphore, byte msgType,
WC_ALLOC_VAR_EX(serverName, char, WOLFSSL_HOST_NAME_MAX, NULL,
DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E);
r = TLSX_EchChangeSNI(ssl, &echX, serverName, &serverNameX, &extensions);
/* If ECH won't be written exclude it from the size calculation */
if (r == 0 && echX != NULL &&
!ssl->options.echAccepted &&
((WOLFSSL_ECH*)echX->data)->innerCount != 0) {
TURN_ON(semaphore, TLSX_ToSemaphore(echX->type));
}
if (r == 0 && ssl->extensions)
ret = TLSX_GetSize(ssl->extensions, semaphore, msgType, pLength);
if (r == 0 && ret == 0 && ssl->ctx && ssl->ctx->extensions)
@@ -17873,6 +17927,12 @@ int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType,
WOLFSSL_MSG("ECH extension received");
ret = ECH_PARSE(ssl, input + offset, size, msgType);
break;
case TLSXT_ECH_OUTER_EXTENSIONS:
/* RFC 9849 s5.1: ech_outer_extensions MUST only appear in
* the EncodedClientHelloInner */
WOLFSSL_MSG("ech_outer_extensions in plaintext message");
WOLFSSL_ERROR_VERBOSE(INVALID_PARAMETER);
return INVALID_PARAMETER;
#endif
default:
WOLFSSL_MSG("Unknown TLS extension type");
@@ -18012,19 +18072,6 @@ int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType,
if (ret == 0)
ret = TCA_VERIFY_PARSE(ssl, isRequest);
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
/* If client used ECH, server HRR must include ECH confirmation */
if (ret == 0 && msgType == hello_retry_request && ssl->echConfigs != NULL &&
!ssl->options.disableECH) {
TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX == NULL || ((WOLFSSL_ECH*)echX->data)->confBuf == NULL) {
WOLFSSL_MSG("ECH used but HRR missing ECH confirmation");
WOLFSSL_ERROR_VERBOSE(EXT_MISSING);
ret = EXT_MISSING;
}
}
#endif
WOLFSSL_LEAVE("Leaving TLSX_Parse", ret);
return ret;
}
+136 -33
View File
@@ -3863,7 +3863,7 @@ static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech)
tmpHashes = ssl->hsHashes;
ssl->hsHashes = ssl->hsHashesEch;
if (ssl->options.echAccepted == 0 && ssl->hsHashes == NULL) {
if (ssl->hsHashes == NULL) {
ret = InitHandshakeHashes(ssl);
if (ret == 0) {
ssl->hsHashesEch = ssl->hsHashes;
@@ -4921,7 +4921,8 @@ int SendTls13ClientHello(WOLFSSL* ssl)
#if defined(HAVE_ECH)
/* write inner then outer */
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
(ssl->options.echAccepted || args->ech->innerCount == 0)) {
(ssl->options.echAccepted || args->ech->innerCount == 0)) {
byte downgrade;
/* set the type to inner */
args->ech->type = ECH_TYPE_INNER;
/* innerClientHello may already exist from hrr, free if it does */
@@ -4966,11 +4967,15 @@ int SendTls13ClientHello(WOLFSSL* ssl)
/* copy the new client random */
XMEMCPY(ssl->arrays->clientRandom, args->output +
args->clientRandomOffset, RAN_LEN);
/* write the extensions for inner */
/* write the extensions for inner
* ensuring that a version less than TLS1.3 is never offered */
args->length = 0;
ret = TLSX_WriteRequest(ssl, args->ech->innerClientHello + args->idx -
(RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ), client_hello,
&args->length);
downgrade = ssl->options.downgrade;
ssl->options.downgrade = 0;
ret = TLSX_WriteRequest(ssl, args->ech->innerClientHello +
args->idx - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ),
client_hello, &args->length);
ssl->options.downgrade = downgrade;
/* set the type to outer */
args->ech->type = ECH_TYPE_OUTER;
if (ret != 0)
@@ -4991,6 +4996,14 @@ int SendTls13ClientHello(WOLFSSL* ssl)
/* encrypt and pack the ech innerClientHello */
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
(ssl->options.echAccepted || args->ech->innerCount == 0)) {
#if defined(WOLFSSL_TEST_ECH)
if (ssl->echInnerHelloCb != NULL) {
ret = ssl->echInnerHelloCb(args->ech->innerClientHello,
args->ech->innerClientHelloLen - args->ech->hpke->Nt);
if (ret != 0)
return ret;
}
#endif
ret = TLSX_FinalizeEch(args->ech,
args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ,
(word32)(args->sendSz - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ)));
@@ -5154,6 +5167,7 @@ static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz,
ECH_ACCEPT_CONFIRMATION_SZ);
if (ret == 0) {
WOLFSSL_MSG("ECH accepted");
ssl->options.echAccepted = 1;
/* after HRR, hsHashesEch must contain:
@@ -5170,8 +5184,17 @@ static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz,
}
}
else {
if (msgType != hello_retry_request && ssl->options.echAccepted) {
/* the SH has rejected ECH after the HRR has accepted it
* RFC 9849, section 6.1.5 */
WOLFSSL_MSG("ECH rejected, but it was previously accepted...");
ret = INVALID_PARAMETER;
}
else {
WOLFSSL_MSG("ECH rejected");
ret = 0;
}
ssl->options.echAccepted = 0;
ret = 0;
/* ECH rejected, continue with outer transcript */
FreeHandshakeHashes(ssl);
@@ -5727,34 +5750,43 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
#if defined(HAVE_ECH)
/* check for acceptConfirmation */
if (ssl->echConfigs != NULL && !ssl->options.disableECH) {
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
ssl->hsHashesEch != NULL) {
args->echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (args->echX == NULL || args->echX->data == NULL)
return WOLFSSL_FATAL_ERROR;
/* account for hrr extension instead of server random */
if (args->extMsgType == hello_retry_request) {
args->acceptOffset =
(word32)(((WOLFSSL_ECH*)args->echX->data)->confBuf - input);
args->acceptLabel = (byte*)echHrrAcceptConfirmationLabel;
args->acceptLabelSz = ECH_HRR_ACCEPT_CONFIRMATION_LABEL_SZ;
if (args->extMsgType == hello_retry_request &&
((WOLFSSL_ECH*)args->echX->data)->confBuf == NULL) {
/* server rejected ECH, fallback to outer */
Free_HS_Hashes(ssl->hsHashesEch, ssl->heap);
ssl->hsHashesEch = NULL;
}
else {
args->acceptLabel = (byte*)echAcceptConfirmationLabel;
args->acceptLabelSz = ECH_ACCEPT_CONFIRMATION_LABEL_SZ;
}
/* check acceptance */
if (ret == 0) {
ret = EchCheckAcceptance(ssl, args->acceptLabel,
args->acceptLabelSz, input, args->acceptOffset, helloSz,
args->extMsgType);
}
if (ret != 0)
return ret;
/* use the inner random for client random */
if (args->extMsgType != hello_retry_request) {
XMEMCPY(ssl->arrays->clientRandom, ssl->arrays->clientRandomInner,
RAN_LEN);
/* account for hrr extension instead of server random */
if (args->extMsgType == hello_retry_request) {
args->acceptOffset =
(word32)(((WOLFSSL_ECH*)args->echX->data)->confBuf - input);
args->acceptLabel = (byte*)echHrrAcceptConfirmationLabel;
args->acceptLabelSz = ECH_HRR_ACCEPT_CONFIRMATION_LABEL_SZ;
}
else {
args->acceptLabel = (byte*)echAcceptConfirmationLabel;
args->acceptLabelSz = ECH_ACCEPT_CONFIRMATION_LABEL_SZ;
}
/* check acceptance */
if (ret == 0) {
ret = EchCheckAcceptance(ssl, args->acceptLabel,
args->acceptLabelSz, input, args->acceptOffset, helloSz,
args->extMsgType);
}
if (ret != 0)
return ret;
/* use the inner random for client random */
if (args->extMsgType != hello_retry_request) {
XMEMCPY(ssl->arrays->clientRandom,
ssl->arrays->clientRandomInner, RAN_LEN);
}
}
}
#endif /* HAVE_ECH */
@@ -5773,6 +5805,14 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
XMEMSET(ssl->arrays->psk_key, 0, MAX_PSK_KEY_LEN);
}
else {
#if defined(HAVE_ECH)
/* do not resume when outerHandshake will be negotiated */
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
!ssl->options.echAccepted) {
WOLFSSL_MSG("ECH rejected but server negotiated PSK");
return INVALID_PARAMETER;
}
#endif
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
if (ssl->options.certWithExternPsk && psk->resumption) {
/* RFC8773bis mode requires external PSK, not ticket resumption. */
@@ -6003,6 +6043,15 @@ static int DoTls13CertificateRequest(WOLFSSL* ssl, const byte* input,
return ret;
#endif
#if defined(HAVE_ECH)
/* RFC 9849 s6.1.7: ECH was offered but rejected by the server...
* the client MUST respond with an empty Certificate message. */
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
!ssl->options.echAccepted) {
ssl->options.sendVerify = SEND_BLANK_CERT;
}
else
#endif
if ((ssl->buffers.certificate && ssl->buffers.certificate->buffer &&
((ssl->buffers.key && ssl->buffers.key->buffer)
#ifdef HAVE_PK_CALLBACKS
@@ -7047,8 +7096,6 @@ static int EchWriteAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz,
}
/* normal TLS code will calculate transcript of ServerHello */
else {
ssl->options.echAccepted = 1;
ssl->hsHashes = tmpHashes;
FreeHandshakeHashes(ssl);
tmpHashes = ssl->hsHashesEch;
@@ -7254,6 +7301,15 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
if (wantDowngrade) {
#ifndef WOLFSSL_NO_TLS12
byte realMinor;
#endif
#if defined(HAVE_ECH)
if (ssl->options.echProcessingInner) {
WOLFSSL_MSG("ECH: inner client hello does not support version "
"less than TLS v1.3");
ERROR_OUT(INVALID_PARAMETER, exit_dch);
}
#endif
#ifndef WOLFSSL_NO_TLS12
if (!ssl->options.downgrade) {
WOLFSSL_MSG("Client trying to connect with lesser version than "
"TLS v1.3");
@@ -7412,13 +7468,23 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
}
#if defined(HAVE_ECH)
if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
if (!ssl->options.echProcessingInner && echX != NULL &&
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
if (((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) {
/* Client sent real ECH and inner hello was decrypted, jump to
* exit so the caller can re-invoke with the inner hello */
goto exit_dch;
}
else {
/* If ECH was accepted in ClientHello1 then ClientHello2 MUST
* contain an ECH extension */
if (ssl->options.serverState ==
SERVER_HELLO_RETRY_REQUEST_COMPLETE &&
ssl->options.echAccepted) {
WOLFSSL_MSG("Client did not send an EncryptedClientHello "
"extension");
ERROR_OUT(INCOMPLETE_DATA, exit_dch);
}
/* Server has ECH but client did not send ECH. Clear the
* response flag so the empty ECH extension is not written
* in EncryptedExtensions. */
@@ -8000,6 +8066,10 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType)
if (extMsgType == hello_retry_request) {
/* reset the ech state for round 2 */
((WOLFSSL_ECH*)echX->data)->state = ECH_WRITE_NONE;
/* inner hello no longer needed, free it */
XFREE(((WOLFSSL_ECH*)echX->data)->innerClientHello,
ssl->heap, DYNAMIC_TYPE_TMP_BUFFER);
((WOLFSSL_ECH*)echX->data)->innerClientHello = NULL;
}
else {
if (ret == 0) {
@@ -12335,6 +12405,15 @@ static int DoTls13NewSessionTicket(WOLFSSL* ssl, const byte* input,
WOLFSSL_START(WC_FUNC_NEW_SESSION_TICKET_DO);
WOLFSSL_ENTER("DoTls13NewSessionTicket");
#ifdef HAVE_ECH
/* ignore session ticket when ECH is rejected */
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
!ssl->options.echAccepted) {
*inOutIdx += size + ssl->keys.padSz;
return 0;
}
#endif
/* Lifetime hint. */
if ((*inOutIdx - begin) + SESSION_HINT_SZ > size)
return BUFFER_ERROR;
@@ -13463,15 +13542,24 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx,
*inOutIdx = echInOutIdx;
/* call again with the inner hello */
if (ret == 0) {
((WOLFSSL_ECH*)echX->data)->sniState = ECH_INNER_SNI;
if (((WOLFSSL_ECH*)echX->data)->sniState == ECH_OUTER_SNI) {
((WOLFSSL_ECH*)echX->data)->sniState = ECH_INNER_SNI;
}
ssl->options.echProcessingInner = 1;
ret = DoTls13ClientHello(ssl,
((WOLFSSL_ECH*)echX->data)->innerClientHello,
&echInOutIdx,
((WOLFSSL_ECH*)echX->data)->innerClientHelloLen);
ssl->options.echProcessingInner = 0;
((WOLFSSL_ECH*)echX->data)->sniState = ECH_SNI_DONE;
}
if (ret == 0 && ((WOLFSSL_ECH*)echX->data)->state !=
ECH_PARSED_INTERNAL) {
WOLFSSL_MSG("ECH: inner ClientHello missing ECH extension");
ret = INVALID_PARAMETER;
}
/* if the inner ech parsed successfully we have successfully
* handled the hello and can skip the whole message */
if (ret == 0) {
@@ -14256,6 +14344,21 @@ int wolfSSL_connect_TLSv13(WOLFSSL* ssl)
}
#endif /* NO_HANDSHAKE_DONE_CB */
#if defined(HAVE_ECH)
/* RFC 9849 s6.1.6: if we offered ECH but the server rejected it,
* send ech_required alert and abort before returning to the app */
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
!ssl->options.echAccepted) {
if (ssl->echRetryConfigs != NULL) {
ssl->options.echRetryConfigsAccepted = 1;
}
SendAlert(ssl, alert_fatal, ech_required);
ssl->error = ECH_REQUIRED_E;
WOLFSSL_ERROR_VERBOSE(ECH_REQUIRED_E);
return WOLFSSL_FATAL_ERROR;
}
#endif /* HAVE_ECH */
if (!ssl->options.keepResources) {
FreeHandshakeResources(ssl);
}