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
+3 -1
View File
@@ -24,7 +24,9 @@ jobs:
with:
path: wolfssl
configure: >-
--enable-ech --enable-sha512 --enable-aes CFLAGS='-DUSE_FLAT_TEST_H'
--enable-ech --enable-sha512 --enable-aes
CFLAGS='-DUSE_FLAT_TEST_H -DWOLFSSL_TEST_ECH'
check: true
install: true
- name: tar build-dir
+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);
}
+836 -17
View File
@@ -14641,10 +14641,18 @@ static int test_wolfSSL_Tls13_ECH_params_b64(void)
#if !defined(NO_WOLFSSL_CLIENT)
/* base64 ech configs from cloudflare-ech.com (these are good configs) */
const char* b64Valid = "AEX+DQBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=";
/* ech configs with bad version */
const char* b64BadVers = "AEX+/gBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=";
/* ech configs with bad/unsupported algorithm */
const char* b64BadAlgo = "AEX+DQBBFP7+ACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA=";
/* ech configs with 0 length algorithm */
const char* b64BadAlgo0 = "ACX+DQAhFAAgAAAABAABAAEAEmNsb3VkZmxhcmUtZWNoLmNvbQAA";
/* ech configs with long algorithm */
const char* b64BadAlgo33 = "AEb+DQBCFAAgACEBbgKECPPpYhFWEG1ygQ3lYE5hdq317ijtpJ4cKze44E4ABAABAAEAEmNsb3VkZmxhcmUtZWNoLmNvbQAA";
/* ech configs with bad/unsupported ciphersuite */
const char* b64BadCiph = "AEX+DQBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAE/v4AAQASY2xvdWRmbGFyZS1lY2guY29tAAA=";
/* ech configs with unrecognized mandatory extension */
const char* b64Mandatory = "AEn+DQBFFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAT6+gAA";
/* ech configs with bad version first */
const char* b64BadVers1 = "AIz+HQBCAQAgACCjR6+Qn9UYkMaWdXZzsby88vXFhPHJ2tWCDHQJLvMkEgAEAAEAAQATZWNoLXB1YmxpYy1uYW1lLmNvbQAA/g0AQgIAIAAgMM6vLrTbOfsfA6fTbJY/Iu0Lj2xeHEPGUJeUwQGAYF4ABAABAAEAE2VjaC1wdWJsaWMtbmFtZS5jb20AAA==";
/* ech configs with bad version second */
@@ -14676,17 +14684,41 @@ static int test_wolfSSL_Tls13_ECH_params_b64(void)
ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl,
b64Valid, 0));
/* bad version */
ExpectIntEQ(UNSUPPORTED_PROTO_VERSION, wolfSSL_CTX_SetEchConfigsBase64(ctx,
b64BadVers, (word32)XSTRLEN(b64BadVers)));
ExpectIntEQ(UNSUPPORTED_PROTO_VERSION, wolfSSL_SetEchConfigsBase64(ssl,
b64BadVers, (word32)XSTRLEN(b64BadVers)));
/* bad algorithm */
ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx,
ExpectIntEQ(UNSUPPORTED_SUITE, wolfSSL_CTX_SetEchConfigsBase64(ctx,
b64BadAlgo, (word32)XSTRLEN(b64BadAlgo)));
ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl,
ExpectIntEQ(UNSUPPORTED_SUITE, wolfSSL_SetEchConfigsBase64(ssl,
b64BadAlgo, (word32)XSTRLEN(b64BadAlgo)));
/* bad algorithm with 0 length key */
ExpectIntEQ(BUFFER_E, wolfSSL_CTX_SetEchConfigsBase64(ctx,
b64BadAlgo0, (word32)XSTRLEN(b64BadAlgo0)));
ExpectIntEQ(BUFFER_E, wolfSSL_SetEchConfigsBase64(ssl,
b64BadAlgo0, (word32)XSTRLEN(b64BadAlgo0)));
/* bad algorithm with long key */
ExpectIntEQ(BUFFER_E, wolfSSL_CTX_SetEchConfigsBase64(ctx,
b64BadAlgo33, (word32)XSTRLEN(b64BadAlgo33)));
ExpectIntEQ(BUFFER_E, wolfSSL_SetEchConfigsBase64(ssl,
b64BadAlgo33, (word32)XSTRLEN(b64BadAlgo33)));
/* bad ciphersuite */
ExpectIntEQ(UNSUPPORTED_SUITE, wolfSSL_CTX_SetEchConfigsBase64(ctx,
b64BadCiph, (word32)XSTRLEN(b64BadCiph)));
ExpectIntEQ(UNSUPPORTED_SUITE, wolfSSL_SetEchConfigsBase64(ssl,
b64BadCiph, (word32)XSTRLEN(b64BadCiph)));
/* unrecognized mandatory extension */
ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx,
b64BadCiph, (word32)XSTRLEN(b64BadCiph)));
b64Mandatory, (word32)XSTRLEN(b64Mandatory)));
ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl,
b64BadCiph, (word32)XSTRLEN(b64BadCiph)));
b64Mandatory, (word32)XSTRLEN(b64Mandatory)));
/* bad version first, should only have config 2 set */
ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx,
@@ -14869,7 +14901,7 @@ static int test_wolfSSL_SubTls13_ECH(void)
/* Static storage for passing ECH config between server and client callbacks */
static byte echCbTestConfigs[512];
static word32 echCbTestConfigsLen;
static const char* echCbTestPublicName = "ech-public-name.com";
static const char* echCbTestPublicName = "example.com";
static const char* echCbTestPrivateName = "ech-private-name.com";
static word16 echCbTestKemID = 0;
static word16 echCbTestKdfID = 0;
@@ -15164,6 +15196,10 @@ static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb)
echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)),
WOLFSSL_SUCCESS);
/* client will send empty cert on rejection, so server should not ask for
* cert */
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
if (hrr) {
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
}
@@ -15196,6 +15232,10 @@ static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb)
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
badPrivateName, (word16)XSTRLEN(badPrivateName)), WOLFSSL_SUCCESS);
/* client will send empty cert on rejection, so server should not ask for
* cert */
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
if (hrr) {
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
}
@@ -15224,6 +15264,221 @@ static int test_wolfSSL_Tls13_ECH_bad_configs(void)
return EXPECT_RESULT();
}
/* Test retry configs are returned after ECH rejection and are usable */
static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
WOLFSSL_CTX* tempCtx = NULL;
byte badConfigs[256];
word32 badConfigsLen = sizeof(badConfigs);
byte retryConfigs[256];
word32 retryConfigsLen = sizeof(retryConfigs);
WOLFSSL_CTX* savedSCtx;
/* --- first attempt: wrong ECH config -> ECH rejected --- */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* throwaway ECH config the server won't recognise */
ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, echCbTestPublicName,
0, 0, 0), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfigs, &badConfigsLen),
WOLFSSL_SUCCESS);
wolfSSL_CTX_free(tempCtx);
tempCtx = NULL;
ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badConfigs,
badConfigsLen), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)),
WOLFSSL_SUCCESS);
if (hrr)
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
/* ECH must fail and retry configs must be present */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
/* capture retry configs */
ExpectIntEQ(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, retryConfigs,
&retryConfigsLen), WOLFSSL_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->error, ECH_REQUIRED_E);
ExpectIntGT(retryConfigsLen, 0);
if (EXPECT_SUCCESS()){
/* keep server CTX so second attempt uses the same ECH keys */
savedSCtx = test_ctx.s_ctx;
test_ctx.s_cb.isSharedCtx = 1;
test_ssl_memio_cleanup(&test_ctx);
/* --- second attempt: same server CTX, retry configs -> accepted --- */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
/* restore server CTX; ctx_ready left NULL to skip ECH key regen */
test_ctx.s_ctx = savedSCtx;
test_ctx.s_cb.isSharedCtx = 1;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, retryConfigs,
retryConfigsLen), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)),
WOLFSSL_SUCCESS);
if (hrr)
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL),
TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1);
wolfSSL_CTX_free(test_ctx.s_ctx);
test_ctx.s_ctx = NULL;
}
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
static int test_wolfSSL_Tls13_ECH_retry_configs(void)
{
EXPECT_DECLS;
ExpectIntEQ(test_wolfSSL_Tls13_ECH_retry_configs_ex(0), TEST_SUCCESS);
ExpectIntEQ(test_wolfSSL_Tls13_ECH_retry_configs_ex(1), TEST_SUCCESS);
return EXPECT_RESULT();
}
/* Test retry configs are cleared when authentication fails */
static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
WOLFSSL_CTX* tempCtx = NULL;
byte badConfigs[256];
word32 badConfigsLen = sizeof(badConfigs);
word32 retryConfigsLen = sizeof(badConfigs);
const char* badPublicName = "ech-public-name.com";
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* generate mismatched ECH configs so retry_configs are sent
* and use a bad public name so auth fails in outer hello */
ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, badPublicName,
0, 0, 0), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfigs, &badConfigsLen),
WOLFSSL_SUCCESS);
wolfSSL_CTX_free(tempCtx);
tempCtx = NULL;
ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badConfigs,
badConfigsLen), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)),
WOLFSSL_SUCCESS);
/* Do not require client cert on server so it does not send
* CertificateRequest */
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL);
/* use badPublicName so ECH public name matches */
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME,
badPublicName, (word16)XSTRLEN(badPublicName)),
WOLFSSL_SUCCESS);
if (hrr)
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
/* auth failure in outer handshake, not ech_required */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH));
/* retry configs must not be accessible */
ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL,
&retryConfigsLen), WOLFSSL_SUCCESS);
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail(void)
{
EXPECT_DECLS;
ExpectIntEQ(test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(0),
TEST_SUCCESS);
ExpectIntEQ(test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(1),
TEST_SUCCESS);
return EXPECT_RESULT();
}
/* Test that bad retry configs (unsupported cipher suite) from the server are
* ignored rather than propagating an error */
static int test_wolfSSL_Tls13_ECH_retry_configs_bad(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
word32 retryConfigsLen = sizeof(echCbTestConfigs);
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* corrupt the server's cipher suite -> ECH decrypt will fail, and retry
* configs will have an unsupported KDF/AEAD pair.
* This will trigger the UNSUPPORTED_SUITE path in TLSX_ECH_Parse */
if (EXPECT_SUCCESS() && test_ctx.s_ctx->echConfigs != NULL &&
test_ctx.s_ctx->echConfigs->cipherSuites != NULL) {
test_ctx.s_ctx->echConfigs->cipherSuites[0].aeadId = 0xFEFE;
}
/* bad retry configs are discarded - failure must be ECH_REQUIRED_E,
* not a retry-config parse error */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
/* no retry configs should be stored since they were all unsupported */
ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL,
&retryConfigsLen), WOLFSSL_SUCCESS);
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
/* Test that client info can be successfully decoded from one of multiple server
* ECH configs
* In this case the server is expected to try it's first config, fail, then try
@@ -15305,15 +15560,15 @@ static int test_wolfSSL_Tls13_ECH_new_config(void)
/* Test GREASE ECH:
* 1. client sends GREASE ECH extension but server has no ECH configs so it
* ignores it, handshake succeeds normally, no ECH configs received
* ignores it, handshake succeeds normally
* 2. client sends GREASE ECH extensions and server has ECH configs, handshake
* succeeds and client receives ECH configs */
* succeeds
* configs should never be received */
static int test_wolfSSL_Tls13_ECH_GREASE(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
byte greaseConfigs[512];
word32 greaseConfigsLen = sizeof(greaseConfigs);
word32 retryConfigsLen = sizeof(test_ctx);
/* GREASE when server has no ECH configs */
@@ -15333,7 +15588,7 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void)
ExpectIntEQ(test_ctx.c_ssl->options.disableECH, 0);
/* verify no ECH configs are set */
ExpectNull(test_ctx.s_ctx->echConfigs);
ExpectNull(test_ctx.c_ctx->echConfigs);
ExpectNull(test_ctx.c_ssl->echConfigs);
/* handshake should succeed - server ignores the GREASE ECH extension */
ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
@@ -15341,8 +15596,11 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void)
/* ECH should NOT be accepted since this was GREASE */
ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 0);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
ExpectIntNE(wolfSSL_GetEchConfigs(test_ctx.c_ssl, greaseConfigs,
&greaseConfigsLen), WOLFSSL_SUCCESS);
/* verify no ECH configs are received */
ExpectNull(test_ctx.c_ssl->echConfigs);
/* retry configs must not be saved */
ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL,
&retryConfigsLen), WOLFSSL_SUCCESS);
test_ssl_memio_cleanup(&test_ctx);
@@ -15368,17 +15626,19 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void)
ExpectIntEQ(test_ctx.c_ssl->options.disableECH, 0);
/* verify ECH configs are set on server */
ExpectNotNull(test_ctx.s_ctx->echConfigs);
ExpectNull(test_ctx.c_ctx->echConfigs);
ExpectNull(test_ctx.c_ssl->echConfigs);
/* handshake should succeed - server responds to the GREASE ECH extension */
ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
/* ECH should NOT be accepted since this was GREASE
* However, configs will be present this time */
/* ECH should NOT be accepted since this was GREASE */
ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 0);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
ExpectIntEQ(wolfSSL_GetEchConfigs(test_ctx.c_ssl, greaseConfigs,
&greaseConfigsLen), WOLFSSL_SUCCESS);
/* verify no ECH configs are received */
ExpectNull(test_ctx.c_ssl->echConfigs);
/* retry configs must not be saved */
ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL,
&retryConfigsLen), WOLFSSL_SUCCESS);
test_ssl_memio_cleanup(&test_ctx);
@@ -15446,6 +15706,7 @@ static int test_wolfSSL_Tls13_ECH_disable_conn(void)
return EXPECT_RESULT();
}
/* Regression test: an inner SNI hostname >= MAX_PUBLIC_NAME_SZ (256) bytes
* must not cause a stack-buffer-overflow in TLSX_EchRestoreSNI. Before the
* fix, the truncated copy omitted the NUL terminator and XSTRLEN read past
@@ -15491,6 +15752,297 @@ static int test_wolfSSL_Tls13_ECH_long_SNI(void)
return EXPECT_RESULT();
}
/* Test the HRR ECH rejection fallback path:
* client offers ECH, HRR is triggered, server sends HRR without ECH extension,
* client falls back to the outer transcript, then aborts with ech_required. */
static int test_wolfSSL_Tls13_ECH_HRR_rejection(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
/* Server generates ECH config with good public name */
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
/* Client sets the correct ECH config and private SNI */
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* Server must not require a client certificate */
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL);
/* Disable ECH on the server SSL object: the server ignores ECH in CH1 and
* sends HRR without an ECH extension (confBuf stays NULL on the client) */
wolfSSL_SetEchEnable(test_ctx.s_ssl, 0);
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME,
echCbTestPublicName, (word16)XSTRLEN(echCbTestPublicName)),
WOLFSSL_SUCCESS);
/* Force HRR so client receives HRR with no ECH extension,
* detects confBuf == NULL and frees hsHashesEch, falling back to the
* outer transcript */
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
/* Handshake must fail: client aborts with ech_required */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
/* hsHashesEch must have been freed by the HRR rejection code path */
ExpectNull(test_ctx.c_ssl->hsHashesEch);
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
/* verify the server aborts if CH2 omits the ECH extension after the server
* accepted ECH in the HRR round */
static int test_wolfSSL_Tls13_ECH_ch2_no_ech(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* withhold key shares from CH1 so the server is forced to send HRR */
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
/* one round: client sends CH1, server processes it and sends HRR */
(void)test_ssl_memio_do_handshake(&test_ctx, 1, NULL);
/* server must have committed to ECH acceptance in the HRR */
ExpectIntEQ(test_ctx.s_ssl->options.serverState,
SERVER_HELLO_RETRY_REQUEST_COMPLETE);
ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 1);
/* disable ECH on the client so CH2 omits the ECH extension entirely */
wolfSSL_SetEchEnable(test_ctx.c_ssl, 0);
/* rest of handshake must fail */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(INCOMPLETE_DATA));
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
/* verify that a decryption failure in CH2 is caught
* this also verifies that HPKE context is correctly reused */
static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
int i;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* Withhold key shares so the server is forced to send HRR */
ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS);
/* One round: client sends CH1, server processes it and sends HRR */
(void)test_ssl_memio_do_handshake(&test_ctx, 1, NULL);
ExpectIntEQ(test_ctx.s_ssl->options.serverState,
SERVER_HELLO_RETRY_REQUEST_COMPLETE);
ExpectIntEQ(test_ctx.s_ssl->options.echAccepted, 1);
if (EXPECT_SUCCESS()) {
/* Client reads HRR and writes CH2 into s_buff */
(void)wolfSSL_connect(test_ctx.c_ssl);
/* Corrupt one byte of the ECH ciphertext in the CH2 record in s_buff.
* ECH outer extension layout after the 0xFE0D type marker:
* extLen(2) + outerType(1) + kdfId(2) + aeadId(2) + configId(1)
* + encLen(2, always 0 in CH2) + payloadLen(2) = 12 bytes, so the
* ciphertext starts 14 bytes past the first 0xFE byte. */
for (i = 0; i < test_ctx.s_len - 1; i++) {
if (test_ctx.s_buff[i] == 0xFE && test_ctx.s_buff[i + 1] == 0x0D) {
if (i + 14 < test_ctx.s_len)
test_ctx.s_buff[i + 14] ^= 0xFF;
break;
}
}
/* Server processes the corrupted CH2.
* hpkeContext is preserved, TLSX_ECH_Parse correctly identifies the CH2
* round and sends decrypt_error. */
(void)wolfSSL_accept(test_ctx.s_ssl);
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(DECRYPT_ERROR));
}
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
/* when ECH is rejected the certificate must match the public name of the chosen
* ech config
* the cert check should pass and the client aborts with ech_required */
static int test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex(const char* publicName,
int validName)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
byte echConfigs[512];
word32 echConfigsLen = sizeof(echConfigs);
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* Generate ECH config with given public_name */
ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(test_ctx.s_ctx, publicName,
0, 0, 0), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(test_ctx.s_ctx, echConfigs,
&echConfigsLen), WOLFSSL_SUCCESS);
/* Client loads ECH configs and sets a private SNI */
ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, echConfigs,
echConfigsLen), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
"ech-private.com", (word16)XSTRLEN("ech-private.com")),
WOLFSSL_SUCCESS);
/* Do not require client cert on server so it does not send
* CertificateRequest */
wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL);
wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL);
/* Disable ECH on the server side so ECH is rejected */
wolfSSL_SetEchEnable(test_ctx.s_ssl, 0);
/* Match the server SNI to the ECH public_name */
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME,
publicName, (word16)XSTRLEN(publicName)), WOLFSSL_SUCCESS);
/* client sends ECH but server can't process it, however it is possible to
* fall back to the outer handshake */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
if (validName) {
/* the server should see the handshake as successful
* the client should abort because the server did not use ECH */
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(WOLFSSL_ERROR_NONE));
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
}
else {
/* the client should abort with cert mismatch
* the server error is then dependent on whether that cert mismatch
* results in an abort */
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH));
}
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
static int test_wolfSSL_Tls13_ECH_rejected_cert_valid(void)
{
EXPECT_DECLS;
/* "example.com" appears in the SAN of certs/server-cert.pem */
ExpectIntEQ(test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex("example.com", 1),
TEST_SUCCESS);
ExpectIntEQ(test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex("badname.com", 0),
TEST_SUCCESS);
return EXPECT_RESULT();
}
/* when ECH is rejected and the server requests a client certificate the client
* must respond with an empty cert */
static int test_wolfSSL_Tls13_ECH_rejected_empty_client_cert(void)
{
EXPECT_DECLS;
test_ssl_memio_ctx test_ctx;
byte echConfigs[512];
word32 echConfigsLen = sizeof(echConfigs);
const char* publicName = "example.com";
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.s_cb.method = wolfTLSv1_3_server_method;
test_ctx.c_cb.method = wolfTLSv1_3_client_method;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* Generate ECH config with public_name matching the server cert SAN */
ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(test_ctx.s_ctx, publicName,
0, 0, 0), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(test_ctx.s_ctx, echConfigs,
&echConfigsLen), WOLFSSL_SUCCESS);
/* Client loads ECH configs and sets a private SNI */
ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, echConfigs,
echConfigsLen), WOLFSSL_SUCCESS);
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME,
"ech-private.com", (word16)XSTRLEN("ech-private.com")),
WOLFSSL_SUCCESS);
wolfSSL_set_verify(test_ctx.s_ssl,
WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL);
/* Disable ECH on the server so ECH is rejected */
wolfSSL_SetEchEnable(test_ctx.s_ssl, 0);
/* Match the Server SNI to the ECH public_name */
ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME,
publicName, (word16)XSTRLEN(publicName)), WOLFSSL_SUCCESS);
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0);
/* Server cert is valid for public_name, cert check passes, ech_required
* is sent on the client side. */
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(ECH_REQUIRED_E));
/* The server received an empty Certificate from the client.
* With FAIL_IF_NO_PEER_CERT set, the server aborts with NO_PEER_CERT. */
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(NO_PEER_CERT));
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
#endif /* HAVE_SSL_MEMIO_TESTS_DEPENDENCIES */
/* verify that ECH can be enabled/disabled without issue */
@@ -15544,6 +16096,261 @@ static int test_wolfSSL_Tls13_ECH_enable_disable(void)
return EXPECT_RESULT();
}
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \
defined(WOLFSSL_TEST_ECH) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \
!defined(WOLFSSL_NO_TLS12)
static int ech_tamper_seek_extension(byte* innerCh, word16* innerExtLen)
{
word16 idx;
byte sessionIdLen;
word16 cipherSuitesLen;
byte compressionLen;
idx = OPAQUE16_LEN + RAN_LEN;
sessionIdLen = innerCh[idx++];
idx += sessionIdLen;
ato16(innerCh + idx, &cipherSuitesLen);
idx += OPAQUE16_LEN + cipherSuitesLen;
compressionLen = innerCh[idx++];
idx += compressionLen;
ato16(innerCh + idx, innerExtLen);
idx += OPAQUE16_LEN;
return idx;
}
static int ech_tamper_find_extension(byte* innerCh, word16* idx_p,
word16 extType)
{
word16 idx;
word16 innerExtIdx;
word16 innerExtLen;
idx = innerExtIdx = ech_tamper_seek_extension(innerCh, &innerExtLen);
while (idx - innerExtIdx < innerExtLen) {
word16 type;
word16 len;
ato16(innerCh + idx, &type);
if (type == extType) {
*idx_p = idx;
return 0;
}
idx += OPAQUE16_LEN;
ato16(innerCh + idx, &len);
idx += OPAQUE16_LEN + len;
}
return BAD_FUNC_ARG;
}
static int ech_tamper_downgrade(byte* innerCh, word32 innerChLen)
{
int ret;
word16 idx;
(void)innerChLen;
ret = ech_tamper_find_extension(innerCh, &idx, TLSXT_SUPPORTED_VERSIONS);
if (ret == 0) {
/* change extension type to something unknown */
innerCh[idx] = 0xFA;
innerCh[idx + 1] = 0xFA;
return 0;
}
else {
return ret;
}
}
static int ech_tamper_padding(byte* innerCh, word32 innerChLen)
{
word16 idx;
word16 innerExtLen;
/* get the unpadded length */
idx = ech_tamper_seek_extension(innerCh, &innerExtLen);
idx += innerExtLen;
/* no padding, but the test would fail if the message is not incorrect...
* so fail the callback */
if (idx == innerChLen) {
return BAD_FUNC_ARG;
}
else {
innerCh[idx] = '\x01';
return 0;
}
}
static int ech_tamper_type(byte* innerCh, word32 innerChLen)
{
int ret;
word16 idx;
(void)innerChLen;
ret = ech_tamper_find_extension(innerCh, &idx, TLSXT_ECH);
if (ret == 0) {
/* change type to outer */
innerCh[idx + 4] = ECH_TYPE_OUTER;
return 0;
}
else {
return ret;
}
}
static int ech_tamper_key_share(byte* innerCh, word32 innerChLen)
{
int ret;
word16 idx;
word16 len;
(void)innerChLen;
ret = ech_tamper_find_extension(innerCh, &idx, TLSXT_KEY_SHARE);
if (ret == 0) {
ato16(innerCh + idx + 8, &len);
if (len == 0) {
return BAD_FUNC_ARG;
}
else {
/* tamper with public key data */
innerCh[idx + 10] ^= 0xFF;
return 0;
}
}
else {
return ret;
}
}
static int ech_tamper_ciphersuite(byte* innerCh, word32 innerChLen)
{
word16 idx;
byte sessionIdLen;
word16 cipherSuitesLen;
(void)innerChLen;
idx = OPAQUE16_LEN + RAN_LEN;
sessionIdLen = innerCh[idx++];
idx += sessionIdLen;
ato16(innerCh + idx, &cipherSuitesLen);
idx += OPAQUE16_LEN;
if (cipherSuitesLen < 2) {
return BAD_FUNC_ARG;
}
else {
/* change all ciphersuites to unknown value */
while (cipherSuitesLen > 0) {
innerCh[idx] = '\xFA';
innerCh[idx + 1] = '\xFA';
idx += OPAQUE16_LEN;
cipherSuitesLen -= OPAQUE16_LEN;
}
return 0;
}
}
static int test_wolfSSL_Tls13_ECH_tamper_ex(struct test_ssl_memio_ctx* test_ctx)
{
EXPECT_DECLS;
test_ssl_memio_cleanup(test_ctx);
XMEMSET(test_ctx, 0, sizeof(struct test_ssl_memio_ctx));
test_ctx->s_cb.method = wolfTLSv1_3_server_method;
test_ctx->c_cb.method = wolfTLSv1_3_client_method;
test_ctx->s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx->s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx->c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(test_ctx), TEST_SUCCESS);
return EXPECT_RESULT();
}
static int test_wolfSSL_Tls13_ECH_tamper_client(void)
{
EXPECT_DECLS;
int err;
struct test_ssl_memio_ctx test_ctx;
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
/* try to downgrade to TLS 1.2 in the inner hello */
test_ctx.s_cb.method = wolfSSLv23_server_method;
test_ctx.c_cb.method = wolfSSLv23_client_method;
test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready;
test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready;
test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
/* change supported_versions extension type to 0xFAFA: this will encourage a
* downgrade to TLS 1.2 */
test_ctx.c_ssl->echInnerHelloCb = ech_tamper_downgrade;
/* the server MUST reject an inner ClientHello that tries to negotiate
* TLS 1.2 or below */
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(INVALID_PARAMETER));
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(FATAL_ERROR));
/* non-zero padding byte */
ExpectIntEQ(test_wolfSSL_Tls13_ECH_tamper_ex(&test_ctx), TEST_SUCCESS);
test_ctx.c_ssl->echInnerHelloCb = ech_tamper_padding;
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
if (EXPECT_SUCCESS()) {
/* padding may have a length of zero which is not an error but the
* callback will treat it as such (thus the BAD_FUNC_ARG) */
err = wolfSSL_get_error(test_ctx.s_ssl, 0);
ExpectTrue(err == WC_NO_ERR_TRACE(INVALID_PARAMETER) ||
err == WC_NO_ERR_TRACE(BAD_FUNC_ARG));
}
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(FATAL_ERROR));
/* bad ECH type */
ExpectIntEQ(test_wolfSSL_Tls13_ECH_tamper_ex(&test_ctx), TEST_SUCCESS);
test_ctx.c_ssl->echInnerHelloCb = ech_tamper_type;
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0),
WC_NO_ERR_TRACE(INVALID_PARAMETER));
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(FATAL_ERROR));
/* corrupted key share */
ExpectIntEQ(test_wolfSSL_Tls13_ECH_tamper_ex(&test_ctx), TEST_SUCCESS);
test_ctx.c_ssl->echInnerHelloCb = ech_tamper_key_share;
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
/* bad ciphersuite */
ExpectIntEQ(test_wolfSSL_Tls13_ECH_tamper_ex(&test_ctx), TEST_SUCCESS);
test_ctx.c_ssl->echInnerHelloCb = ech_tamper_ciphersuite;
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS);
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
#endif /* WOLFSSL_TLS13 && HAVE_ECH && WOLFSSL_TEST_ECH &&
* HAVE_SSL_MEMIO_TESTS_DEPENDENCIES && !WOLFSSL_NO_TLS12 */
#endif /* HAVE_ECH && WOLFSSL_TLS13 */
#if defined(HAVE_IO_TESTS_DEPENDENCIES) && \
@@ -37608,10 +38415,22 @@ TEST_CASE testCases[] = {
TEST_DECL(test_wolfSSL_Tls13_ECH_all_algos),
TEST_DECL(test_wolfSSL_Tls13_ECH_no_private_name),
TEST_DECL(test_wolfSSL_Tls13_ECH_bad_configs),
TEST_DECL(test_wolfSSL_Tls13_ECH_retry_configs),
TEST_DECL(test_wolfSSL_Tls13_ECH_retry_configs_bad),
TEST_DECL(test_wolfSSL_Tls13_ECH_retry_configs_auth_fail),
TEST_DECL(test_wolfSSL_Tls13_ECH_new_config),
TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE),
TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn),
TEST_DECL(test_wolfSSL_Tls13_ECH_long_SNI),
TEST_DECL(test_wolfSSL_Tls13_ECH_HRR_rejection),
TEST_DECL(test_wolfSSL_Tls13_ECH_ch2_no_ech),
TEST_DECL(test_wolfSSL_Tls13_ECH_ch2_decrypt_error),
TEST_DECL(test_wolfSSL_Tls13_ECH_rejected_cert_valid),
TEST_DECL(test_wolfSSL_Tls13_ECH_rejected_empty_client_cert),
#endif
#if defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_TEST_ECH) && \
!defined(WOLFSSL_NO_TLS12)
TEST_DECL(test_wolfSSL_Tls13_ECH_tamper_client),
#endif
TEST_DECL(test_wolfSSL_Tls13_ECH_enable_disable),
#endif /* WOLFSSL_TLS13 && HAVE_ECH */
+4 -2
View File
@@ -240,9 +240,11 @@ enum wolfSSL_ErrorCodes {
SESSION_TICKET_NONCE_OVERFLOW = -517, /* Session ticket nonce overflow */
EMPTY_RECORD_LIMIT_E = -518, /* Too many empty records received */
EMPTY_RECORD_LIMIT_E = -518, /* Too many empty records received */
WOLFSSL_LAST_E = -518
ECH_REQUIRED_E = -519, /* ECH offered but rejected by server */
WOLFSSL_LAST_E = -519
/* codes -1000 to -1999 are reserved for wolfCrypt. */
};
+13 -1
View File
@@ -3179,6 +3179,9 @@ WOLFSSL_LOCAL int GetEchConfigsEx(WOLFSSL_EchConfig* configs,
byte* output, word32* outputLen);
WOLFSSL_LOCAL void FreeEchConfigs(WOLFSSL_EchConfig* configs, void* heap);
WOLFSSL_LOCAL int SetRetryConfigs(WOLFSSL* ssl, const byte* echConfigs,
word32 echConfigsLen);
#endif
struct TLSX {
@@ -5200,7 +5203,9 @@ struct Options {
#endif /* WOLFSSL_DTLS_CID */
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
word16 echAccepted:1;
byte disableECH:1; /* Did the user disable ech */
word16 disableECH:1; /* Did the user disable ech */
word16 echProcessingInner:1; /* Processing the inner hello */
word16 echRetryConfigsAccepted:1;
#endif
#ifdef WOLFSSL_SEND_HRR_COOKIE
word16 cookieGood:1;
@@ -6520,6 +6525,13 @@ struct WOLFSSL {
#endif /* WOLFSSL_QUIC */
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
WOLFSSL_EchConfig* echConfigs;
WOLFSSL_EchConfig* echRetryConfigs;
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && defined(WOLFSSL_TEST_ECH)
/* Test-only hook: called on the client before ECH encryption, after the
* inner ClientHello body is fully constructed. The callback may modify
* innerCh in-place (length stays the same). */
int (*echInnerHelloCb)(byte* innerCh, word32 innerChLen);
#endif
#if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_KEYLOGFILE)
+6 -2
View File
@@ -960,7 +960,7 @@ typedef struct WOLFSSL_ALERT_HISTORY {
/* Valid Alert types from page 16/17
* Add alert string to the function AlertTypeToString in src/ssl.c
* Add alert string to the function AlertTypeToString in src/internal.c
*/
enum AlertDescription {
invalid_alert = -1,
@@ -998,7 +998,8 @@ enum AlertDescription {
bad_certificate_status_response = 113, /**< RFC 6066, section 8 */
unknown_psk_identity = 115, /**< RFC 4279, section 2 */
certificate_required = 116, /**< RFC 8446, section 8.2 */
no_application_protocol = 120
no_application_protocol = 120,
ech_required = 121 /**< RFC 9849, section 5 */
};
#ifdef WOLFSSL_MYSQL_COMPATIBLE
@@ -1251,6 +1252,9 @@ WOLFSSL_API int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs,
WOLFSSL_API int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* echConfigs,
word32* echConfigsLen);
WOLFSSL_API int wolfSSL_GetEchRetryConfigs(WOLFSSL* ssl, byte* echConfigs,
word32* echConfigsLen);
WOLFSSL_API void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable);
#endif /* WOLFSSL_TLS13 && HAVE_ECH */