mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 14:40:50 +02:00
TLS ECH compliance fixes
This commit is contained in:
+52
-20
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user