mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-07-05 12:00:51 +02:00
Merge pull request #10141 from sebastian-carpenter/tls-ech-downgrade
TLS ECH Compliance Fixes
This commit is contained in:
@@ -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
@@ -8775,6 +8775,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
|
||||
@@ -15762,6 +15766,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
|
||||
@@ -16950,17 +16956,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 */
|
||||
@@ -16973,11 +16996,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");
|
||||
@@ -16988,18 +17009,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;
|
||||
@@ -22228,6 +22250,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;
|
||||
@@ -27846,6 +27875,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;
|
||||
|
||||
@@ -13758,6 +13758,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
|
||||
|
||||
@@ -13797,7 +13799,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;
|
||||
}
|
||||
|
||||
@@ -13882,14 +13883,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,
|
||||
@@ -13900,55 +13901,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;
|
||||
@@ -14159,6 +14148,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;
|
||||
@@ -14179,6 +14169,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;
|
||||
@@ -14227,8 +14218,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);
|
||||
@@ -14267,16 +14259,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);
|
||||
@@ -14293,17 +14305,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) {
|
||||
@@ -14330,10 +14353,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);
|
||||
@@ -14344,27 +14367,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;
|
||||
@@ -14378,7 +14401,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;
|
||||
@@ -14426,22 +14449,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;
|
||||
@@ -16292,6 +16340,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);
|
||||
@@ -5728,34 +5751,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 */
|
||||
@@ -5774,6 +5806,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. */
|
||||
@@ -6004,6 +6044,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
|
||||
@@ -7048,8 +7097,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;
|
||||
@@ -7255,6 +7302,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");
|
||||
@@ -7413,13 +7469,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. */
|
||||
@@ -8001,6 +8067,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) {
|
||||
@@ -12336,6 +12406,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;
|
||||
@@ -13464,15 +13543,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) {
|
||||
@@ -14257,6 +14345,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
@@ -14680,10 +14680,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 */
|
||||
@@ -14715,17 +14723,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,
|
||||
@@ -14908,7 +14940,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;
|
||||
@@ -15203,6 +15235,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);
|
||||
}
|
||||
@@ -15235,6 +15271,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);
|
||||
}
|
||||
@@ -15263,6 +15303,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
|
||||
@@ -15344,15 +15599,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 */
|
||||
|
||||
@@ -15372,7 +15627,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);
|
||||
@@ -15380,8 +15635,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);
|
||||
|
||||
@@ -15407,17 +15665,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);
|
||||
|
||||
@@ -15485,6 +15745,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
|
||||
@@ -15530,6 +15791,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 */
|
||||
@@ -15583,6 +16135,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) && \
|
||||
@@ -38859,10 +39666,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
@@ -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
@@ -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
@@ -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 */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user