testing + bug fixes for TLS ECH

This commit is contained in:
sebastian-carpenter
2026-02-12 10:50:45 -07:00
parent cb2d693550
commit c3a38dced7
10 changed files with 1233 additions and 119 deletions
+1 -2
View File
@@ -8634,10 +8634,9 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl)
ForceZero(&ssl->serverSecret, sizeof(ssl->serverSecret));
#if defined(HAVE_ECH)
if (ssl->options.useEch == 1) {
if (ssl->echConfigs != NULL) {
FreeEchConfigs(ssl->echConfigs, ssl->heap);
ssl->echConfigs = NULL;
ssl->options.useEch = 0;
}
#endif /* HAVE_ECH */
#endif /* WOLFSSL_TLS13 */
+13 -16
View File
@@ -36,7 +36,6 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName,
int ret = 0;
word16 encLen = DHKEM_X25519_ENC_LEN;
WOLFSSL_EchConfig* newConfig;
WOLFSSL_EchConfig* parentConfig;
#ifdef WOLFSSL_SMALL_STACK
Hpke* hpke = NULL;
WC_RNG* rng;
@@ -67,7 +66,9 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName,
else
XMEMSET(newConfig, 0, sizeof(WOLFSSL_EchConfig));
/* set random config id */
/* set random configId */
/* TODO: if an equal configId is found should the old config be removed from
* the LL? Prevents growth beyond 255+ items */
if (ret == 0)
ret = wc_RNG_GenerateByte(rng, &newConfig->configId);
@@ -147,17 +148,14 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName,
}
}
else {
parentConfig = ctx->echConfigs;
if (parentConfig == NULL) {
/* insert new configs at beginning of LL as preference should be given
* to the most recently generated configs */
if (ctx->echConfigs == NULL) {
ctx->echConfigs = newConfig;
}
else {
while (parentConfig->next != NULL) {
parentConfig = parentConfig->next;
}
parentConfig->next = newConfig;
newConfig->next = ctx->echConfigs;
ctx->echConfigs = newConfig;
}
}
@@ -250,7 +248,7 @@ void wolfSSL_CTX_SetEchEnable(WOLFSSL_CTX* ctx, byte enable)
/* set the ech config from base64 for our client ssl object, base64 is the
* format ech configs are sent using dns records */
int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64,
int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, const char* echConfigs64,
word32 echConfigs64Len)
{
int ret = 0;
@@ -261,7 +259,7 @@ int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64,
return BAD_FUNC_ARG;
/* already have ech configs */
if (ssl->options.useEch == 1) {
if (ssl->echConfigs != NULL) {
return WOLFSSL_FATAL_ERROR;
}
@@ -274,7 +272,7 @@ int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64,
decodedConfigs[decodedLen - 1] = 0;
/* decode the echConfigs */
ret = Base64_Decode((byte*)echConfigs64, echConfigs64Len,
ret = Base64_Decode((const byte*)echConfigs64, echConfigs64Len,
decodedConfigs, &decodedLen);
if (ret != 0) {
@@ -300,7 +298,7 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs,
return BAD_FUNC_ARG;
/* already have ech configs */
if (ssl->options.useEch == 1) {
if (ssl->echConfigs != NULL) {
return WOLFSSL_FATAL_ERROR;
}
@@ -309,7 +307,6 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs,
/* if we found valid configs */
if (ret == 0) {
ssl->options.useEch = 1;
return WOLFSSL_SUCCESS;
}
@@ -472,7 +469,7 @@ int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* output, word32* outputLen)
return BAD_FUNC_ARG;
/* if we don't have ech configs */
if (ssl->options.useEch != 1) {
if (ssl->echConfigs == NULL) {
return WOLFSSL_FATAL_ERROR;
}
+67 -18
View File
@@ -2257,9 +2257,10 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length,
byte type;
byte matched;
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
TLSX* echX = NULL;
WOLFSSL_ECH* ech = NULL;
WOLFSSL_EchConfig* workingConfig;
TLSX* echX;
word16 privateNameLen;
#endif
#endif /* !NO_WOLFSSL_SERVER */
TLSX *extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME);
@@ -2291,7 +2292,22 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length,
}
#ifndef NO_WOLFSSL_SERVER
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
if (!ssl->options.disableECH) {
echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX != NULL) {
ech = (WOLFSSL_ECH*)(echX->data);
}
}
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
if ((!extension || !extension->data) ||
(ech != NULL && ech->sniState == ECH_INNER_SNI &&
ech->privateName == NULL)) {
#else
if (!extension || !extension->data) {
#endif
/* This will keep SNI even though TLSX_UseSNI has not been called.
* Enable it so that the received sni is available to functions
* that use a custom callback when SNI is received.
@@ -2339,24 +2355,52 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length,
if (!cacheOnly && !(sni = TLSX_SNI_Find((SNI*)extension->data, type)))
return 0; /* not using this type of SNI. */
#ifdef WOLFSSL_TLS13
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
if (ech != NULL && ech->sniState == ECH_INNER_SNI){
/* SNI status is carried over from processing the outer hello so it is
* necessary to clear it before processing the inner hello */
ech->sniState = ECH_INNER_SNI_ATTEMPT;
if (sni != NULL){
sni->status = WOLFSSL_SNI_NO_MATCH;
}
}
else if (ech != NULL && ech->sniState == ECH_OUTER_SNI &&
ech->privateName == NULL && sni != NULL){
/* save the private SNI before it is overwritten by the public SNI */
privateNameLen = (word16)XSTRLEN(sni->data.host_name) + 1;
ech->privateName = (char*)XMALLOC(privateNameLen, ssl->heap,
DYNAMIC_TYPE_TMP_BUFFER);
if (ech->privateName == NULL)
return MEMORY_E;
XMEMCPY((char*)ech->privateName, sni->data.host_name,
privateNameLen);
}
#endif
#if defined(WOLFSSL_TLS13)
/* Don't process the second ClientHello SNI extension if there
* was problems with the first.
*/
if (!cacheOnly && sni->status != 0)
if (!cacheOnly && sni->status != WOLFSSL_SNI_NO_MATCH)
return 0;
#endif
matched = cacheOnly || (XSTRLEN(sni->data.host_name) == size &&
XSTRNCMP(sni->data.host_name, (const char*)input + offset, size) == 0);
#if defined(HAVE_ECH)
if (ech != NULL && ech->sniState == ECH_INNER_SNI_ATTEMPT) {
matched = cacheOnly || (XSTRLEN(ech->privateName) == size &&
XSTRNCMP(ech->privateName, (const char*)input + offset, size) == 0);
}
else
#endif
{
matched = cacheOnly || (XSTRLEN(sni->data.host_name) == size &&
XSTRNCMP(sni->data.host_name, (const char*)input + offset,
size) == 0);
}
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX != NULL)
ech = (WOLFSSL_ECH*)(echX->data);
if (!matched && ech != NULL) {
if (!matched && ech != NULL && ech->sniState == ECH_OUTER_SNI) {
workingConfig = ech->echConfig;
while (workingConfig != NULL) {
matched = XSTRLEN(workingConfig->publicName) == size &&
XSTRNCMP(workingConfig->publicName,
@@ -2374,6 +2418,7 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length,
int matchStat;
int r = TLSX_UseSNI(&ssl->extensions, type, input + offset, size,
ssl->heap);
if (r != WOLFSSL_SUCCESS)
return r; /* throws error. */
@@ -13922,13 +13967,12 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
word16 len;
WOLFSSL_MSG("TLSX_ECH_Parse");
if (size == 0)
return BAD_FUNC_ARG;
if (ssl->options.disableECH) {
WOLFSSL_MSG("TLSX_ECH_Parse: ECH disabled. Ignoring.");
return 0;
}
if (size == 0)
return BAD_FUNC_ARG;
/* retry configs */
if (msgType == encrypted_extensions) {
ret = wolfSSL_SetEchConfigs(ssl, readBuf, size);
@@ -13937,7 +13981,8 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
ret = 0;
}
/* HRR with special confirmation */
else if (msgType == hello_retry_request && ssl->options.useEch) {
else if (msgType == hello_retry_request && ssl->echConfigs != NULL &&
!ssl->options.disableECH) {
/* length must be 8 */
if (size != ECH_ACCEPT_CONFIRMATION_SZ)
return BAD_FUNC_ARG;
@@ -14055,6 +14100,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size,
if (ret == 0) {
i = 0;
/* decrement until before the padding */
/* TODO: verify padding is 0, abort with illegal_parameter */
while (ech->innerClientHello[ech->innerClientHelloLen +
HANDSHAKE_HEADER_SZ - i - 1] != ECH_TYPE_INNER) {
i++;
@@ -14090,6 +14136,8 @@ static void TLSX_ECH_Free(WOLFSSL_ECH* ech, void* heap)
XFREE(ech->hpke, heap, DYNAMIC_TYPE_TMP_BUFFER);
if (ech->hpkeContext != NULL)
XFREE(ech->hpkeContext, heap, DYNAMIC_TYPE_TMP_BUFFER);
if (ech->privateName != NULL)
XFREE((char*)ech->privateName, heap, DYNAMIC_TYPE_TMP_BUFFER);
XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER);
(void)heap;
@@ -15868,7 +15916,7 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
}
#endif
#if defined(HAVE_ECH)
if (ssl->options.useEch == 1 && !ssl->options.disableECH
if (ssl->echConfigs != NULL && !ssl->options.disableECH
&& msgType == client_hello) {
ret = TLSX_GetSizeWithEch(ssl, semaphore, msgType, &length);
if (ret != 0)
@@ -16053,7 +16101,7 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
#endif
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
if (ssl->options.useEch == 1 && !ssl->options.disableECH
if (ssl->echConfigs != NULL && !ssl->options.disableECH
&& msgType == client_hello) {
ret = TLSX_WriteWithEch(ssl, output, semaphore,
msgType, &offset);
@@ -17332,7 +17380,8 @@ int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType,
#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->options.useEch == 1) {
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");
+28 -11
View File
@@ -4720,7 +4720,7 @@ int SendTls13ClientHello(WOLFSSL* ssl)
/* find length of outer and inner */
#if defined(HAVE_ECH)
if (ssl->options.useEch == 1 && !ssl->options.disableECH) {
if (ssl->echConfigs != NULL && !ssl->options.disableECH) {
TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX == NULL)
return WOLFSSL_FATAL_ERROR;
@@ -4874,7 +4874,7 @@ int SendTls13ClientHello(WOLFSSL* ssl)
#if defined(HAVE_ECH)
/* write inner then outer */
if (ssl->options.useEch == 1 && !ssl->options.disableECH &&
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
(ssl->options.echAccepted || args->ech->innerCount == 0)) {
/* set the type to inner */
args->ech->type = ECH_TYPE_INNER;
@@ -4939,7 +4939,7 @@ int SendTls13ClientHello(WOLFSSL* ssl)
#if defined(HAVE_ECH)
/* encrypt and pack the ech innerClientHello */
if (ssl->options.useEch == 1 && !ssl->options.disableECH &&
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
(ssl->options.echAccepted || args->ech->innerCount == 0)) {
ret = TLSX_FinalizeEch(args->ech,
args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ,
@@ -4970,7 +4970,7 @@ int SendTls13ClientHello(WOLFSSL* ssl)
{
#if defined(HAVE_ECH)
/* compute the inner hash */
if (ssl->options.useEch == 1 && !ssl->options.disableECH &&
if (ssl->echConfigs != NULL && !ssl->options.disableECH &&
(ssl->options.echAccepted || args->ech->innerCount == 0)) {
ret = EchHashHelloInner(ssl, args->ech);
}
@@ -5664,7 +5664,7 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
#if defined(HAVE_ECH)
/* check for acceptConfirmation, must be done after hashes restart */
if (ssl->options.useEch == 1) {
if (ssl->echConfigs != NULL && !ssl->options.disableECH) {
args->echX = TLSX_Find(ssl->extensions, TLSX_ECH);
/* account for hrr extension instead of server random */
if (args->extMsgType == hello_retry_request) {
@@ -7165,9 +7165,19 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
}
#if defined(HAVE_ECH)
/* jump to the end to clean things up */
if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE)
goto exit_dch;
if (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 {
/* Server has ECH but client did not send ECH. Clear the
* response flag so the empty ECH extension is not written
* in EncryptedExtensions. */
echX->resp = 0;
}
}
#endif
#ifdef HAVE_SNI
@@ -7244,7 +7254,8 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx,
#if defined(HAVE_ECH)
/* hash clientHelloInner to hsHashesEch */
if (echX != NULL && ssl->ctx->echConfigs != NULL &&
!ssl->options.disableECH) {
!ssl->options.disableECH &&
((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) {
ret = EchHashHelloInner(ssl, (WOLFSSL_ECH*)echX->data);
if (ret != 0)
goto exit_dch;
@@ -7503,7 +7514,8 @@ exit_dch:
#if defined(HAVE_ECH)
if (ret == 0 && echX != NULL &&
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE &&
((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) {
/* add the header to the inner hello */
AddTls13HandShakeHeader(((WOLFSSL_ECH*)echX->data)->innerClientHello,
@@ -13072,16 +13084,21 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx,
echX = TLSX_Find(ssl->extensions, TLSX_ECH);
if (echX != NULL &&
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) {
((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE &&
((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) {
byte copyRandom = ((WOLFSSL_ECH*)echX->data)->innerCount == 0;
/* reset the inOutIdx to the outer start */
*inOutIdx = echInOutIdx;
/* call again with the inner hello */
if (ret == 0) {
((WOLFSSL_ECH*)echX->data)->sniState = ECH_INNER_SNI;
ret = DoTls13ClientHello(ssl,
((WOLFSSL_ECH*)echX->data)->innerClientHello,
&echInOutIdx,
((WOLFSSL_ECH*)echX->data)->innerClientHelloLen);
((WOLFSSL_ECH*)echX->data)->sniState = ECH_SNI_DONE;
}
/* if the inner ech parsed successfully we have successfully
* handled the hello and can skip the whole message */