diff --git a/src/internal.c b/src/internal.c index 30579538e5..a5a72e8102 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8623,8 +8623,6 @@ void wolfSSL_ResourceFree(WOLFSSL* ssl) /* try to free the ech hashes in case we errored out */ ssl->hsHashes = ssl->hsHashesEch; FreeHandshakeHashes(ssl); - ssl->hsHashes = ssl->hsHashesEchInner; - FreeHandshakeHashes(ssl); #endif XFREE(ssl->buffers.domainName.buffer, ssl->heap, DYNAMIC_TYPE_DOMAIN); @@ -8636,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 */ diff --git a/src/ssl_ech.c b/src/ssl_ech.c index d27522c862..c94abceb2a 100644 --- a/src/ssl_ech.c +++ b/src/ssl_ech.c @@ -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; @@ -63,7 +62,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); @@ -139,17 +140,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; } } @@ -242,7 +240,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; @@ -253,7 +251,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; } @@ -266,7 +264,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) { @@ -292,7 +290,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; } @@ -301,7 +299,6 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, /* if we found valid configs */ if (ret == 0) { - ssl->options.useEch = 1; return WOLFSSL_SUCCESS; } @@ -459,7 +456,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; } diff --git a/src/tls.c b/src/tls.c index bcad4e8f3b..beaf87c0b5 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2236,6 +2236,7 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, 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); @@ -2315,24 +2316,56 @@ 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) + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + if (echX != NULL) + ech = (WOLFSSL_ECH*)(echX->data); + + /* SNI status is carried over from processing the outer hello so it is + * necessary to clear it before processing the inner hello */ + if (ech != NULL && ech->processingInner == 1) { + ech->processingInner = 2; + sni->status = 0; + + if (ssl->ctx->sniRecvCb) { + cacheOnly = 1; + } + + if (cacheOnly) { + WOLFSSL_MSG("Forcing SSL object to store SNI parameter"); + } + } +#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) return 0; #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 defined(HAVE_ECH) + if (ech != NULL && ech->processingInner == 2) { + if (ech->privateName != NULL) { + matched = cacheOnly || (XSTRLEN(ech->privateName) == size && + XSTRNCMP(ech->privateName, (const char*)input + offset, + size) == 0); + } + else { + matched = 0; + } + } + else +#endif + { + matched = cacheOnly || (XSTRLEN(sni->data.host_name) == size && + XSTRNCMP(sni->data.host_name, (const char*)input + offset, size) == 0); + } - if (!matched && ech != NULL) { +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + if (!matched && ech != NULL && ech->processingInner == 0) { workingConfig = ech->echConfig; - while (workingConfig != NULL) { matched = XSTRLEN(workingConfig->publicName) == size && XSTRNCMP(workingConfig->publicName, @@ -2348,8 +2381,25 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, if (matched || sni->options & WOLFSSL_SNI_ANSWER_ON_MISMATCH) { int matchStat; - int r = TLSX_UseSNI(&ssl->extensions, type, input + offset, size, + int r; + +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + /* save the private SNI before it is overwritten by the public SNI */ + if (ech != NULL && ech->processingInner == 0 && sni != NULL && + ech->privateName == NULL) { + 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 + + r = TLSX_UseSNI(&ssl->extensions, type, input + offset, size, ssl->heap); + if (r != WOLFSSL_SUCCESS) return r; /* throws error. */ @@ -13407,6 +13457,360 @@ static int TLSX_ECH_GetSize(WOLFSSL_ECH* ech, byte msgType) return (int)size; } +/* Locate the given extension type, use the extOffset to start off after where a + * previous call to this function ended + * + * outerCh The outer ClientHello buffer. + * chLen Outer ClientHello length. + * extType Extension type to look for. + * extLen Out parameter, length of found extension. + * extOffset Offset into outer ClientHello to look for extension from. + * extensionsStart Start of outer ClientHello extensions. + * extensionsLen Length of outer ClientHello extensions. + * returns 0 on success and otherwise failure. + */ +static const byte* TLSX_ECH_FindOuterExtension(const byte* outerCh, + word32 chLen, word16 extType, word16* extLen, word16* extOffset, + word16* extensionsStart, word16* extensionsLen) +{ + word32 idx = *extOffset; + byte sessionIdLen; + word16 cipherSuitesLen; + byte compressionLen; + word16 type; + word16 len; + + if (idx == 0) { + idx = OPAQUE16_LEN + RAN_LEN; + if (idx >= chLen) + return NULL; + + sessionIdLen = outerCh[idx++]; + idx += sessionIdLen; + if (idx + OPAQUE16_LEN > chLen) + return NULL; + + ato16(outerCh + idx, &cipherSuitesLen); + idx += OPAQUE16_LEN + cipherSuitesLen; + if (idx >= chLen) + return NULL; + + compressionLen = outerCh[idx++]; + idx += compressionLen; + if (idx + OPAQUE16_LEN > chLen) + return NULL; + + ato16(outerCh + idx, extensionsLen); + idx += OPAQUE16_LEN; + *extensionsStart = (word16)idx; + + if (idx + *extensionsLen > chLen) + return NULL; + } + + while (idx < chLen && (idx - *extensionsStart) < *extensionsLen) { + if (idx + OPAQUE16_LEN + OPAQUE16_LEN > chLen) + return NULL; + + ato16(outerCh + idx, &type); + idx += OPAQUE16_LEN; + ato16(outerCh + idx, &len); + idx += OPAQUE16_LEN; + + if (idx + len > chLen) + return NULL; + + if (type == extType) { + *extLen = len + OPAQUE16_LEN + OPAQUE16_LEN; + *extOffset = idx + len; + return outerCh + idx - OPAQUE16_LEN - OPAQUE16_LEN; + } + + idx += len; + } + + return NULL; +} + +/* 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 + * + * 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. + * returns 0 on success and otherwise failure. + */ +static int TLSX_ECH_CopyOuterExtensions(const byte* outerCh, word32 outerChLen, + byte** newInnerCh, word32* newInnerChLen, + word16 numOuterRefs, const byte* outerRefTypes) +{ + int ret = 0; + word16 refType; + word16 outerExtLen; + word16 outerExtOffset = 0; + word16 extsStart; + word16 extsLen; + 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); + + if (outerExtData == NULL) { + ret = INVALID_PARAMETER; + break; + } + + XMEMCPY(*newInnerCh, outerExtData, outerExtLen); + *newInnerCh += outerExtLen; + + outerRefTypes += OPAQUE16_LEN; + } + } + + return ret; +} + +/* Expand ech_outer_extensions in the inner ClientHello by copying referenced + * extensions from the outer ClientHello. + * If the sessionID exists in the outer ClientHello then also copy that into the + * expanded inner ClientHello. + * + * ssl SSL/TLS object. + * ech ECH object. + * heap Heap hint. + * returns 0 on success and otherwise failure. + */ +static int TLSX_ECH_ExpandOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, + void* heap) +{ + int ret = 0; + int headerSz; + const byte* innerCh; + word32 innerChLen; + const byte* outerCh; + word32 outerChLen; + word32 idx; + byte sessionIdLen; + word16 cipherSuitesLen; + byte compressionLen; + + word32 innerExtIdx; + word16 innerExtLen; + word32 echOuterExtIdx = 0; + word16 echOuterExtLen = 0; + int foundEchOuter = 0; + word16 numOuterRefs = 0; + const byte* outerRefTypes = NULL; + word32 extraSize = 0; + byte* newInnerCh = NULL; + byte* newInnerChRef; + word32 newInnerChLen; + word32 copyLen; + + WOLFSSL_ENTER("TLSX_ExpandEchOuterExtensions"); + + if (ech == NULL || ech->innerClientHello == NULL || ech->aad == NULL) + return BAD_FUNC_ARG; + +#ifdef WOLFSSL_DTLS13 + headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ; +#else + headerSz = HANDSHAKE_HEADER_SZ; +#endif + + innerCh = ech->innerClientHello + headerSz; + innerChLen = ech->innerClientHelloLen; + outerCh = ech->aad; + outerChLen = ech->aadLen; + + idx = OPAQUE16_LEN + RAN_LEN; + if (idx >= innerChLen) + return BUFFER_ERROR; + + sessionIdLen = innerCh[idx++]; + idx += sessionIdLen; + /* the ECH spec details that innerhello sessionID must initially be empty */ + if (sessionIdLen != 0) + return INVALID_PARAMETER; + if (idx + OPAQUE16_LEN > innerChLen) + return BUFFER_ERROR; + + ato16(innerCh + idx, &cipherSuitesLen); + idx += OPAQUE16_LEN + cipherSuitesLen; + if (idx >= innerChLen) + return BUFFER_ERROR; + + compressionLen = innerCh[idx++]; + idx += compressionLen; + if (idx + OPAQUE16_LEN > innerChLen) + return BUFFER_ERROR; + + ato16(innerCh + idx, &innerExtLen); + idx += OPAQUE16_LEN; + innerExtIdx = idx; + if (idx + innerExtLen > innerChLen) + return BUFFER_ERROR; + + /* validate ech_outer_extensions and calculate extra size */ + while (idx < innerChLen && (idx - innerExtIdx) < innerExtLen) { + word16 type; + word16 len; + byte outerExtListLen; + + if (idx + OPAQUE16_LEN + OPAQUE16_LEN > innerChLen) + return BUFFER_ERROR; + + ato16(innerCh + idx, &type); + idx += OPAQUE16_LEN; + ato16(innerCh + idx, &len); + idx += OPAQUE16_LEN; + + if (idx + len > innerChLen) + return BUFFER_ERROR; + + if (type == TLSXT_ECH_OUTER_EXTENSIONS) { + if (foundEchOuter) { + WOLFSSL_MSG("ECH: duplicate ech_outer_extensions"); + return INVALID_PARAMETER; + } + foundEchOuter = 1; + echOuterExtIdx = idx - OPAQUE16_LEN - OPAQUE16_LEN; + echOuterExtLen = len + OPAQUE16_LEN + OPAQUE16_LEN; + + /* ech_outer_extensions data format: 1-byte length + extension types + * ExtensionType OuterExtensions<2..254>; */ + if (len < 1) + return BUFFER_ERROR; + outerExtListLen = innerCh[idx]; + if (outerExtListLen + 1 != len || outerExtListLen < 2 || + outerExtListLen == 255) + return BUFFER_ERROR; + + outerRefTypes = innerCh + idx + 1; + numOuterRefs = outerExtListLen / OPAQUE16_LEN; + + ret = TLSX_ECH_CopyOuterExtensions(outerCh, outerChLen, NULL, + &extraSize, numOuterRefs, outerRefTypes); + if (ret != 0) + return ret; + } + + idx += len; + } + + newInnerChLen = innerChLen - echOuterExtLen + extraSize - sessionIdLen + + ssl->session->sessionIDSz; + + if (!foundEchOuter && sessionIdLen == ssl->session->sessionIDSz) { + /* no extensions + no sessionID to copy */ + WOLFSSL_MSG("ECH: no EchOuterExtensions extension found"); + return ret; + } + else { + newInnerCh = (byte*)XMALLOC(newInnerChLen + headerSz, heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (newInnerCh == NULL) + return MEMORY_E; + } + + /* note: The first HANDSHAKE_HEADER_SZ bytes are reserved for the header + * but not initialized here. The header will be properly set later by + * AddTls13HandShakeHeader() in DoTls13ClientHello(). */ + + /* copy everything up to EchOuterExtensions */ + newInnerChRef = newInnerCh + headerSz; + copyLen = OPAQUE16_LEN + RAN_LEN; + XMEMCPY(newInnerChRef, innerCh, copyLen); + newInnerChRef += copyLen; + + *newInnerChRef = ssl->session->sessionIDSz; + newInnerChRef += OPAQUE8_LEN; + + copyLen = ssl->session->sessionIDSz; + XMEMCPY(newInnerChRef, ssl->session->sessionID, copyLen); + newInnerChRef += copyLen; + + if (!foundEchOuter) { + WOLFSSL_MSG("ECH: no EchOuterExtensions extension found"); + + copyLen = innerChLen - OPAQUE16_LEN - RAN_LEN - OPAQUE8_LEN - + sessionIdLen; + XMEMCPY(newInnerChRef, innerCh + OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN + + sessionIdLen, copyLen); + } + else { + copyLen = echOuterExtIdx - OPAQUE16_LEN - RAN_LEN - OPAQUE8_LEN - + sessionIdLen; + XMEMCPY(newInnerChRef, innerCh + OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN + + sessionIdLen, copyLen); + newInnerChRef += copyLen; + + /* update extensions length in the new ClientHello */ + c16toa(innerExtLen - echOuterExtLen + (word16)extraSize, + newInnerChRef - OPAQUE16_LEN); + + ret = TLSX_ECH_CopyOuterExtensions(outerCh, outerChLen, &newInnerChRef, + &newInnerChLen, numOuterRefs, outerRefTypes); + if (ret == 0) { + /* copy remaining extensions after ech_outer_extensions */ + copyLen = innerChLen - (echOuterExtIdx + echOuterExtLen); + XMEMCPY(newInnerChRef, innerCh + echOuterExtIdx + echOuterExtLen, + copyLen); + + WOLFSSL_MSG("ECH: expanded ech_outer_extensions successfully"); + } + } + + if (ret == 0) { + XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER); + ech->innerClientHello = newInnerCh; + ech->innerClientHelloLen = (word16)newInnerChLen; + newInnerCh = NULL; + } + + if (newInnerCh != NULL) + XFREE(newInnerCh, heap, DYNAMIC_TYPE_TMP_BUFFER); + + return ret; +} + /* return status after attempting to open the hpke encrypted ech extension, if * successful the inner client hello will be stored in * ech->innerClientHelloLen */ @@ -13532,12 +13936,12 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, byte* aadCopy; byte* readBuf_p = (byte*)readBuf; 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); @@ -13546,7 +13950,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; @@ -13643,21 +14048,26 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, echConfig = echConfig->next; } } - /* if we failed to extract, 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; - } - else { + 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++; } /* subtract the length of the padding from the length */ ech->innerClientHelloLen -= i; + + /* expand EchOuterExtensions if present + * and, if it exists, copy sessionID from outer hello */ + ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap); + } + /* 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; @@ -13677,6 +14087,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; @@ -15456,7 +15868,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) @@ -15642,7 +16054,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); @@ -16920,6 +17332,19 @@ 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; } diff --git a/src/tls13.c b/src/tls13.c index 76a8882080..8abec0756d 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3775,6 +3775,247 @@ int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config) return WOLFSSL_FATAL_ERROR; } + +/* Hash the inner client hello, initializing the hsHashesEch field if needed. + * + * ssl SSL/TLS object. + * ech ECH object. + * returns 0 on success and otherwise failure. + */ +static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) +{ + int ret = 0; + int headerSz; + word32 realSz; + HS_Hashes* tmpHashes; +#ifndef NO_WOLFSSL_CLIENT + byte falseHeader[HRR_MAX_HS_HEADER_SZ]; +#endif + + if (ssl == NULL || ech == NULL) { + return BAD_FUNC_ARG; + } + +#ifdef WOLFSSL_DTLS13 + headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ; +#else + headerSz = HANDSHAKE_HEADER_SZ; +#endif + + realSz = ech->innerClientHelloLen; +#ifndef NO_WOLFSSL_CLIENT + if (ssl->options.side == WOLFSSL_CLIENT_END) { + realSz -= ech->paddingLen + ech->hpke->Nt; + } +#endif + + tmpHashes = ssl->hsHashes; + + ssl->hsHashes = ssl->hsHashesEch; + if (ssl->options.echAccepted == 0 && ssl->hsHashes == NULL) { + ret = InitHandshakeHashes(ssl); + if (ret == 0) { + ssl->hsHashesEch = ssl->hsHashes; + ech->innerCount = 1; + } + } + + if (ret == 0) { +#ifndef NO_WOLFSSL_CLIENT + if (ssl->options.side == WOLFSSL_CLIENT_END) { + /* client-side: innerClientHello contains body only */ + AddTls13HandShakeHeader(falseHeader, realSz, 0, 0, client_hello, + ssl); + ret = HashRaw(ssl, falseHeader, headerSz); + if (ret == 0) { + ret = HashRaw(ssl, ech->innerClientHello, realSz); + } + } +#endif +#ifndef NO_WOLFSSL_SERVER + if (ssl->options.side == WOLFSSL_SERVER_END) { + /* server-side: innerClientHello contains header + body */ + ret = HashRaw(ssl, ech->innerClientHello, headerSz + realSz); + } +#endif + } + + ssl->hsHashes = tmpHashes; + return ret; +} + +/* Calculate the 8 ECH confirmation bytes. + * + * ssl SSL/TLS object. + * label Ascii string describing ECH acceptance or rejection. + * labelSz Length of label excluding NULL character. + * input The buffer to calculate confirmation off of. + * acceptOffset Where the 8 ECH confirmation bytes start. + * helloSz Size of hello message. + * isHrr Whether message is a HelloRetryRequest or not. + * acceptExpanded An 8 byte array to store calculated confirmation to. + * returns 0 on success and otherwise failure. + */ +static int EchCalcAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, + const byte* input, int acceptOffset, int helloSz, byte isHrr, + byte* acceptExpanded) +{ + int ret = 0; + int digestType = 0; + int digestSize = 0; + int hashSz = 0; + int headerSz; + HS_Hashes* tmpHashes; + HS_Hashes* acceptHash = NULL; + byte zeros[WC_MAX_DIGEST_SIZE]; + byte transcriptEchConf[WC_MAX_DIGEST_SIZE]; + byte clientHelloInnerHash[WC_MAX_DIGEST_SIZE]; + byte expandLabelPrk[WC_MAX_DIGEST_SIZE]; + byte messageHashHeader[HRR_MAX_HS_HEADER_SZ]; + + XMEMSET(zeros, 0, sizeof(zeros)); + XMEMSET(transcriptEchConf, 0, sizeof(transcriptEchConf)); + XMEMSET(clientHelloInnerHash, 0, sizeof(clientHelloInnerHash)); + XMEMSET(expandLabelPrk, 0, sizeof(expandLabelPrk)); + + tmpHashes = ssl->hsHashes; + ssl->hsHashes = ssl->hsHashesEch; + +#ifdef WOLFSSL_DTLS13 + headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ; +#else + headerSz = HANDSHAKE_HEADER_SZ; +#endif + + if (isHrr) { + /* the transcript hash of ClientHelloInner1 */ + hashSz = GetMsgHash(ssl, clientHelloInnerHash); + if (hashSz > 0) { + ret = 0; + } + + /* restart ECH transcript hash, similar to RestartHandshakeHash but + * don't add a cookie */ + if (ret == 0) { + ret = InitHandshakeHashes(ssl); + } + if (ret == 0) { + ssl->hsHashesEch = ssl->hsHashes; + AddTls13HandShakeHeader(messageHashHeader, (word32)hashSz, 0, 0, + message_hash, ssl); + ret = HashRaw(ssl, messageHashHeader, headerSz); + } + if (ret == 0) { + ret = HashRaw(ssl, clientHelloInnerHash, (word32)hashSz); + } + } + + /* hash with zeros for confirmation computation */ + if (ret == 0) { + ret = InitHandshakeHashesAndCopy(ssl, ssl->hsHashesEch, &acceptHash); + } + if (ret == 0) { + ssl->hsHashes = acceptHash; + ret = HashRaw(ssl, input, acceptOffset); + } + if (ret == 0) { + ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ); + } + if (ret == 0) { + ret = HashRaw(ssl, input + acceptOffset + ECH_ACCEPT_CONFIRMATION_SZ, + helloSz + headerSz - (acceptOffset + ECH_ACCEPT_CONFIRMATION_SZ)); + } + + /* get the modified transcript hash */ + if (ret == 0) { + ret = GetMsgHash(ssl, transcriptEchConf); + if (ret > 0) { + ret = 0; + } + } + + /* pick the right type and size based on mac_algorithm */ + if (ret == 0) { + switch (ssl->specs.mac_algorithm) { +#ifndef NO_SHA256 + case sha256_mac: + digestType = WC_SHA256; + digestSize = WC_SHA256_DIGEST_SIZE; + break; +#endif /* !NO_SHA256 */ +#ifdef WOLFSSL_SHA384 + case sha384_mac: + digestType = WC_SHA384; + digestSize = WC_SHA384_DIGEST_SIZE; + break; +#endif /* WOLFSSL_SHA384 */ +#ifdef WOLFSSL_TLS13_SHA512 + case sha512_mac: + digestType = WC_SHA512; + digestSize = WC_SHA512_DIGEST_SIZE; + break; +#endif /* WOLFSSL_TLS13_SHA512 */ +#ifdef WOLFSSL_SM3 + case sm3_mac: + digestType = WC_SM3; + digestSize = WC_SM3_DIGEST_SIZE; + break; +#endif /* WOLFSSL_SM3 */ + default: + ret = WOLFSSL_FATAL_ERROR; + break; + } + } + + /* extract clientRandomInner with a key of all zeros */ + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); + #if !defined(HAVE_FIPS) || \ + (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(6,0)) + ret = wc_HKDF_Extract_ex(digestType, zeros, (word32)digestSize, + ssl->arrays->clientRandomInner, RAN_LEN, expandLabelPrk, + ssl->heap, ssl->devId); + #else + ret = wc_HKDF_Extract(digestType, zeros, digestSize, + ssl->arrays->clientRandomInner, RAN_LEN, expandLabelPrk); + #endif + PRIVATE_KEY_LOCK(); + } + + /* tls expand with the confirmation label */ + if (ret == 0) { + PRIVATE_KEY_UNLOCK(); +#ifdef WOLFSSL_DTLS13 + if (ssl->options.dtls) { + ret = Tls13HKDFExpandKeyLabel(ssl, acceptExpanded, + ECH_ACCEPT_CONFIRMATION_SZ, expandLabelPrk, (word32)digestSize, + dtls13ProtocolLabel, DTLS13_PROTOCOL_LABEL_SZ, label, labelSz, + transcriptEchConf, (word32)digestSize, digestType, + WOLFSSL_SERVER_END); + } + else +#endif + { + ret = Tls13HKDFExpandKeyLabel(ssl, acceptExpanded, + ECH_ACCEPT_CONFIRMATION_SZ, expandLabelPrk, (word32)digestSize, + tls13ProtocolLabel, TLS13_PROTOCOL_LABEL_SZ, label, labelSz, + transcriptEchConf, (word32)digestSize, digestType, + WOLFSSL_SERVER_END); + } + PRIVATE_KEY_LOCK(); + } + + if (acceptHash != NULL) { + ssl->hsHashes = acceptHash; + FreeHandshakeHashes(ssl); + } + + ssl->hsHashes = tmpHashes; + return ret; +} + #endif #ifndef NO_WOLFSSL_CLIENT @@ -4195,70 +4436,6 @@ static int WritePSKBinders(WOLFSSL* ssl, byte* output, word32 idx) } #endif -#if defined(HAVE_ECH) -/* returns status after we hash the ech inner */ -static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) -{ - int ret = 0; - word32 realSz; - HS_Hashes* tmpHashes; -#ifdef WOLFSSL_DTLS13 - byte falseHeader[DTLS13_HANDSHAKE_HEADER_SZ]; -#else - byte falseHeader[HANDSHAKE_HEADER_SZ]; -#endif - - if (ssl == NULL || ech == NULL) - return BAD_FUNC_ARG; - realSz = ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt; - tmpHashes = ssl->hsHashes; - ssl->hsHashes = NULL; - /* init the ech hashes */ - ret = InitHandshakeHashes(ssl); - if (ret == 0) { - ssl->hsHashesEch = ssl->hsHashes; - /* do the handshake header then the body */ - AddTls13HandShakeHeader(falseHeader, realSz, 0, 0, client_hello, ssl); - ret = HashRaw(ssl, falseHeader, HANDSHAKE_HEADER_SZ); - /* hash with inner */ - if (ret == 0) { - /* init hsHashesEchInner */ - if (ech->innerCount == 0) { - ssl->hsHashes = ssl->hsHashesEchInner; - ret = InitHandshakeHashes(ssl); - if (ret == 0) { - ssl->hsHashesEchInner = ssl->hsHashes; - ech->innerCount = 1; - } - } - else { - /* switch back to hsHashes so we have hrr -> echInner2 */ - ssl->hsHashes = tmpHashes; - ret = InitHandshakeHashesAndCopy(ssl, ssl->hsHashes, - &ssl->hsHashesEchInner); - } - - if (ret == 0) { - ssl->hsHashes = ssl->hsHashesEchInner; - ret = HashRaw(ssl, falseHeader, HANDSHAKE_HEADER_SZ); - ssl->hsHashes = ssl->hsHashesEch; - } - } - } - /* hash the body */ - if (ret == 0) - ret = HashRaw(ssl, ech->innerClientHello, realSz); - /* hash with inner */ - if (ret == 0) { - ssl->hsHashes = ssl->hsHashesEchInner; - ret = HashRaw(ssl, ech->innerClientHello, realSz); - } - /* swap hsHashes back */ - ssl->hsHashes = tmpHashes; - return ret; -} -#endif - static void GetTls13SessionId(WOLFSSL* ssl, byte* output, word32* idx) { if (ssl->session->sessionIDSz > 0) { @@ -4504,7 +4681,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; @@ -4658,7 +4835,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; @@ -4682,9 +4859,16 @@ int SendTls13ClientHello(WOLFSSL* ssl) XMEMCPY(args->ech->innerClientHello, args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, args->idx - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ)); - /* copy the client random to inner */ - XMEMCPY(ssl->arrays->clientRandomInner, ssl->arrays->clientRandom, - RAN_LEN); + /* copy the client random to inner - only for first CH, not after HRR */ + if (!ssl->options.echAccepted) { + XMEMCPY(ssl->arrays->clientRandomInner, ssl->arrays->clientRandom, + RAN_LEN); + } + else { + /* After HRR, use the same inner random as CH1 */ + XMEMCPY(args->ech->innerClientHello + VERSION_SZ, + ssl->arrays->clientRandomInner, RAN_LEN); + } /* change the outer client random */ ret = wc_RNG_GenerateBlock(ssl->rng, args->output + args->clientRandomOffset, RAN_LEN); @@ -4716,7 +4900,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, @@ -4747,9 +4931,10 @@ int SendTls13ClientHello(WOLFSSL* ssl) { #if defined(HAVE_ECH) /* compute the inner hash */ - if (ssl->options.useEch == 1 && !ssl->options.disableECH && - (ssl->options.echAccepted || args->ech->innerCount == 0)) + if (ssl->echConfigs != NULL && !ssl->options.disableECH && + (ssl->options.echAccepted || args->ech->innerCount == 0)) { ret = EchHashHelloInner(ssl, args->ech); + } #endif /* compute the outer hash */ if (ret == 0) @@ -4841,134 +5026,79 @@ static int Dtls13ClientDoDowngrade(WOLFSSL* ssl) #endif /* WOLFSSL_DTLS13 && !WOLFSSL_NO_CLIENT*/ #if defined(HAVE_ECH) -/* check if the server accepted ech or not, must be run after an hsHashes - * restart */ +/* Calculate ECH acceptance and verify the server accepted ECH. + * + * ssl SSL/TLS object. + * label Ascii string describing ECH acceptance type. + * labelSz Length of label excluding NULL character. + * input The buffer to calculate confirmation off of. + * acceptOffset Where the 8 ECH confirmation bytes start. + * helloSz Size of hello message. + * returns 0 on success and otherwise failure. + */ static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, const byte* input, int acceptOffset, int helloSz) { int ret = 0; - int digestType = 0; - int digestSize = 0; + int isHrr = 0; + int headerSz; HS_Hashes* tmpHashes; - byte zeros[WC_MAX_DIGEST_SIZE]; - byte transcriptEchConf[WC_MAX_DIGEST_SIZE]; - byte expandLabelPrk[WC_MAX_DIGEST_SIZE]; byte acceptConfirmation[ECH_ACCEPT_CONFIRMATION_SZ]; - XMEMSET(zeros, 0, sizeof(zeros)); - XMEMSET(transcriptEchConf, 0, sizeof(transcriptEchConf)); - XMEMSET(expandLabelPrk, 0, sizeof(expandLabelPrk)); + XMEMSET(acceptConfirmation, 0, sizeof(acceptConfirmation)); - /* store so we can restore regardless of the outcome */ + +#ifdef WOLFSSL_DTLS13 + headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ; +#else + headerSz = HANDSHAKE_HEADER_SZ; +#endif + + if (labelSz == ECH_HRR_ACCEPT_CONFIRMATION_LABEL_SZ && + XMEMCMP(label, echHrrAcceptConfirmationLabel, labelSz) == 0) { + isHrr = 1; + } + + ret = EchCalcAcceptance(ssl, label, labelSz, input, acceptOffset, helloSz, + isHrr, acceptConfirmation); + tmpHashes = ssl->hsHashes; - /* swap hsHashes to hsHashesEch */ ssl->hsHashes = ssl->hsHashesEch; - /* hash up to the last 8 bytes */ - ret = HashRaw(ssl, input, acceptOffset); - /* hash 8 zeros */ - if (ret == 0) - ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ); - /* hash the rest of the hello */ - if (ret == 0) { - ret = HashRaw(ssl, input + acceptOffset + ECH_ACCEPT_CONFIRMATION_SZ, - helloSz + HANDSHAKE_HEADER_SZ - - (acceptOffset + ECH_ACCEPT_CONFIRMATION_SZ)); - } - /* get the modified transcript hash */ - if (ret == 0) - ret = GetMsgHash(ssl, transcriptEchConf); - if (ret > 0) - ret = 0; - /* pick the right type and size based on mac_algorithm */ - if (ret == 0) { - switch (ssl->specs.mac_algorithm) { -#ifndef NO_SHA256 - case sha256_mac: - digestType = WC_SHA256; - digestSize = WC_SHA256_DIGEST_SIZE; - break; -#endif /* !NO_SHA256 */ -#ifdef WOLFSSL_SHA384 - case sha384_mac: - digestType = WC_SHA384; - digestSize = WC_SHA384_DIGEST_SIZE; - break; -#endif /* WOLFSSL_SHA384 */ -#ifdef WOLFSSL_TLS13_SHA512 - case sha512_mac: - digestType = WC_SHA512; - digestSize = WC_SHA512_DIGEST_SIZE; - break; -#endif /* WOLFSSL_TLS13_SHA512 */ -#ifdef WOLFSSL_SM3 - case sm3_mac: - digestType = WC_SM3; - digestSize = WC_SM3_DIGEST_SIZE; - break; -#endif /* WOLFSSL_SM3 */ - default: - ret = WOLFSSL_FATAL_ERROR; - break; - } - } - /* extract clientRandomInner with a key of all zeros */ - if (ret == 0) { - PRIVATE_KEY_UNLOCK(); - #if !defined(HAVE_FIPS) || \ - (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(6,0)) - ret = wc_HKDF_Extract_ex(digestType, zeros, (word32)digestSize, - ssl->arrays->clientRandomInner, RAN_LEN, expandLabelPrk, - ssl->heap, ssl->devId); - #else - ret = wc_HKDF_Extract(digestType, zeros, digestSize, - ssl->arrays->clientRandomInner, RAN_LEN, expandLabelPrk); - #endif - PRIVATE_KEY_LOCK(); - } - /* tls expand with the confirmation label */ - if (ret == 0) { - PRIVATE_KEY_UNLOCK(); - ret = Tls13HKDFExpandKeyLabel(ssl, acceptConfirmation, - ECH_ACCEPT_CONFIRMATION_SZ, expandLabelPrk, (word32)digestSize, - tls13ProtocolLabel, TLS13_PROTOCOL_LABEL_SZ, label, labelSz, - transcriptEchConf, (word32)digestSize, digestType, - WOLFSSL_SERVER_END); - PRIVATE_KEY_LOCK(); - } + if (ret == 0) { - /* last 8 bytes should match our expand output */ ret = XMEMCMP(acceptConfirmation, input + acceptOffset, - ECH_ACCEPT_CONFIRMATION_SZ); - /* ech accepted */ + ECH_ACCEPT_CONFIRMATION_SZ); + if (ret == 0) { - /* set echAccepted to 1 */ ssl->options.echAccepted = 1; - /* free hsHashes and go with inner */ - ssl->hsHashes = tmpHashes; - FreeHandshakeHashes(ssl); - ssl->hsHashes = ssl->hsHashesEch; - tmpHashes = ssl->hsHashesEchInner; - ssl->hsHashesEchInner = NULL; + + /* after HRR, hsHashesEch must contain: + * message_hash(ClientHelloInner1) || HRR (actual, not zeros) */ + if (isHrr) { + ret = HashRaw(ssl, input, helloSz + headerSz); + } + /* normal TLS code will calculate transcript of ServerHello */ + else { + ssl->hsHashes = tmpHashes; + FreeHandshakeHashes(ssl); + tmpHashes = ssl->hsHashesEch; + ssl->hsHashesEch = NULL; + } } - /* ech rejected */ else { - /* set echAccepted to 0, needed in case HRR */ ssl->options.echAccepted = 0; - /* free inner since we're continuing with outer */ - ssl->hsHashes = ssl->hsHashesEchInner; + ret = 0; + + /* ECH rejected, continue with outer transcript */ FreeHandshakeHashes(ssl); - ssl->hsHashesEchInner = NULL; + ssl->hsHashesEch = NULL; } - /* continue with outer if we failed to verify ech was accepted */ - ret = 0; } - FreeHandshakeHashes(ssl); - /* set hsHashesEch to NULL to avoid double free */ - ssl->hsHashesEch = NULL; - /* swap to tmp, will be inner if accepted, hsHashes if rejected */ + ssl->hsHashes = tmpHashes; return ret; } -#endif +#endif /* HAVE_ECH */ /* handle processing of TLS 1.3 server_hello (2) and hello_retry_request (6) */ /* Handle the ServerHello message from the server. @@ -5503,7 +5633,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) { @@ -6609,6 +6739,61 @@ static int DoTls13SupportedVersions(WOLFSSL* ssl, const byte* input, word32 i, return 0; } + +#ifdef HAVE_ECH +/* Calculate and write the 8 ECH confirmation bytes. + * Output into confirmation field on HRR and into ServerRandom on ServerHello. + * + * ssl SSL/TLS object. + * label Ascii string describing ECH acceptance or rejection. + * labelSz Length of label excluding NULL character. + * output The buffer to calculate/write confirmation from/to. + * acceptOffset Where the 8 ECH confirmation bytes should be placed. + * helloSz Size of hello message. + * msgType Type of message being written. + * returns 0 on success and otherwise failure. + */ +static int EchWriteAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, + byte* output, int acceptOffset, int helloSz, byte msgType) +{ + int ret = 0; + int headerSz; + HS_Hashes* tmpHashes; + +#ifdef WOLFSSL_DTLS13 + headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ; +#else + headerSz = HANDSHAKE_HEADER_SZ; +#endif + + ret = EchCalcAcceptance(ssl, label, labelSz, output, acceptOffset, + helloSz - headerSz, msgType == hello_retry_request, + output + acceptOffset); + + tmpHashes = ssl->hsHashes; + ssl->hsHashes = ssl->hsHashesEch; + + /* after HRR, hsHashesEch must contain: + * message_hash(ClientHelloInner1) || HRR (actual, not zeros) */ + if (ret == 0 && msgType == hello_retry_request) { + ret = HashRaw(ssl, output, helloSz); + } + /* normal TLS code will calculate transcript of ServerHello */ + else if (ret == 0) { + ssl->options.echAccepted = 1; + + ssl->hsHashes = tmpHashes; + FreeHandshakeHashes(ssl); + tmpHashes = ssl->hsHashesEch; + ssl->hsHashesEch = NULL; + } + + ssl->hsHashes = tmpHashes; + return ret; +} +#endif + /* Handle a ClientHello handshake message. * If the protocol version in the message is not TLS v1.3 or higher, use * DoClientHello() @@ -6657,7 +6842,6 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #endif #if defined(HAVE_ECH) TLSX* echX = NULL; - HS_Hashes* tmpHashes; #endif WOLFSSL_START(WC_FUNC_CLIENT_HELLO_DO); @@ -6945,9 +7129,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 @@ -6999,18 +7193,13 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #endif #if defined(HAVE_ECH) - /* hash clientHelloInner to hsHashesEch independently since it can't include - * the HRR */ - if (ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) { - tmpHashes = ssl->hsHashes; - ssl->hsHashes = NULL; - ret = InitHandshakeHashes(ssl); + /* hash clientHelloInner to hsHashesEch */ + if (echX != NULL && ssl->ctx->echConfigs != NULL && + !ssl->options.disableECH && + ((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) { + ret = EchHashHelloInner(ssl, (WOLFSSL_ECH*)echX->data); if (ret != 0) goto exit_dch; - if ((ret = HashInput(ssl, input + args->begin, (int)helloSz)) != 0) - goto exit_dch; - ssl->hsHashesEch = ssl->hsHashes; - ssl->hsHashes = tmpHashes; } #endif @@ -7266,7 +7455,8 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #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, @@ -7278,107 +7468,6 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, return ret; } -#ifdef HAVE_ECH -/* replace the last acceptance field for either sever hello or hrr with the ech - * acceptance parameter, return status */ -static int EchWriteAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, - byte* output, int acceptOffset, int helloSz, byte msgType) -{ - int ret = 0; - int digestType = 0; - int digestSize = 0; - HS_Hashes* tmpHashes = NULL; - byte zeros[WC_MAX_DIGEST_SIZE]; - byte transcriptEchConf[WC_MAX_DIGEST_SIZE]; - byte expandLabelPrk[WC_MAX_DIGEST_SIZE]; - XMEMSET(zeros, 0, sizeof(zeros)); - XMEMSET(transcriptEchConf, 0, sizeof(transcriptEchConf)); - XMEMSET(expandLabelPrk, 0, sizeof(expandLabelPrk)); - /* store so we can restore regardless of the outcome */ - tmpHashes = ssl->hsHashes; - ssl->hsHashes = ssl->hsHashesEch; - /* hash up to the acceptOffset */ - ret = HashRaw(ssl, output, acceptOffset); - /* hash 8 zeros */ - if (ret == 0) - ret = HashRaw(ssl, zeros, ECH_ACCEPT_CONFIRMATION_SZ); - /* hash the rest of the hello */ - if (ret == 0) { - ret = HashRaw(ssl, output + acceptOffset + ECH_ACCEPT_CONFIRMATION_SZ, - helloSz - (acceptOffset + ECH_ACCEPT_CONFIRMATION_SZ)); - } - /* get the modified transcript hash */ - if (ret == 0) - ret = GetMsgHash(ssl, transcriptEchConf); - if (ret > 0) - ret = 0; - /* pick the right type and size based on mac_algorithm */ - if (ret == 0) { - switch (ssl->specs.mac_algorithm) { -#ifndef NO_SHA256 - case sha256_mac: - digestType = WC_SHA256; - digestSize = WC_SHA256_DIGEST_SIZE; - break; -#endif /* !NO_SHA256 */ -#ifdef WOLFSSL_SHA384 - case sha384_mac: - digestType = WC_SHA384; - digestSize = WC_SHA384_DIGEST_SIZE; - break; -#endif /* WOLFSSL_SHA384 */ -#ifdef WOLFSSL_TLS13_SHA512 - case sha512_mac: - digestType = WC_SHA512; - digestSize = WC_SHA512_DIGEST_SIZE; - break; -#endif /* WOLFSSL_TLS13_SHA512 */ -#ifdef WOLFSSL_SM3 - case sm3_mac: - digestType = WC_SM3; - digestSize = WC_SM3_DIGEST_SIZE; - break; -#endif /* WOLFSSL_SM3 */ - default: - ret = WOLFSSL_FATAL_ERROR; - break; - } - } - /* extract clientRandom with a key of all zeros */ - if (ret == 0) { - PRIVATE_KEY_UNLOCK(); - #if !defined(HAVE_FIPS) || \ - (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(6,0)) - ret = wc_HKDF_Extract_ex(digestType, zeros, (word32)digestSize, - ssl->arrays->clientRandom, RAN_LEN, expandLabelPrk, - ssl->heap, ssl->devId); - #else - ret = wc_HKDF_Extract(digestType, zeros, digestSize, - ssl->arrays->clientRandom, RAN_LEN, expandLabelPrk); - #endif - PRIVATE_KEY_LOCK(); - } - /* tls expand with the confirmation label */ - if (ret == 0) { - PRIVATE_KEY_UNLOCK(); - ret = Tls13HKDFExpandKeyLabel(ssl, output + acceptOffset, - ECH_ACCEPT_CONFIRMATION_SZ, expandLabelPrk, (word32)digestSize, - tls13ProtocolLabel, TLS13_PROTOCOL_LABEL_SZ, label, labelSz, - transcriptEchConf, (word32)digestSize, digestType, - WOLFSSL_SERVER_END); - PRIVATE_KEY_LOCK(); - } - /* mark that ech was accepted */ - if (ret == 0 && msgType != hello_retry_request) - ssl->options.echAccepted = 1; - /* free hsHashesEch, if this is an HRR we will start at client hello 2*/ - FreeHandshakeHashes(ssl); - ssl->hsHashesEch = NULL; - ssl->hsHashes = tmpHashes; - return ret; -} -#endif - /* Send TLS v1.3 ServerHello message to client. * Only a server will send this message. * @@ -12920,20 +13009,34 @@ 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)->processingInner = 1; + ret = DoTls13ClientHello(ssl, ((WOLFSSL_ECH*)echX->data)->innerClientHello, &echInOutIdx, ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen); + + ((WOLFSSL_ECH*)echX->data)->processingInner = 0; } /* if the inner ech parsed successfully we have successfully * handled the hello and can skip the whole message */ - if (ret == 0) + if (ret == 0) { + /* Copy inner client random for ECH acceptance calculation. + * Only on first inner ClientHello (before HRR), not CH2. */ + if (copyRandom) { + XMEMCPY(ssl->arrays->clientRandomInner, + ((WOLFSSL_ECH*)echX->data)->innerClientHello + + HANDSHAKE_HEADER_SZ + VERSION_SZ, RAN_LEN); + } *inOutIdx += size; + } } } #endif /* HAVE_ECH */ diff --git a/tests/api.c b/tests/api.c index 0e09becb7e..329703bc13 100644 --- a/tests/api.c +++ b/tests/api.c @@ -13506,6 +13506,7 @@ static int test_wolfSSL_CTX_add_client_CA(void) defined(HAVE_IO_TESTS_DEPENDENCIES) static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) { + EXPECT_DECLS; callback_functions* callbacks = ((func_args*)args)->callbacks; WOLFSSL_CTX* ctx = callbacks->ctx; WOLFSSL* ssl = NULL; @@ -13530,15 +13531,17 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, - WOLFSSL_FILETYPE_PEM)); + WOLFSSL_FILETYPE_PEM)); if (callbacks->ctx_ready) callbacks->ctx_ready(ctx); - ssl = wolfSSL_new(ctx); + ExpectNotNull(ssl = wolfSSL_new(ctx)); /* set the sni for the server */ - wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, privateNameLen); + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, + privateNameLen)); tcp_accept(&sfd, &cfd, (func_args*)args, port, 0, 0, 0, 0, 1, NULL, NULL); CloseSocket(sfd); @@ -13557,12 +13560,13 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) if (ret != WOLFSSL_SUCCESS) { char buff[WOLFSSL_MAX_ERROR_SZ]; - fprintf(stderr, "error = %d, %s\n", err, wolfSSL_ERR_error_string(err, buff)); + fprintf(stderr, "error = %d, %s\n", err, wolfSSL_ERR_error_string(err, + buff)); } else { if (0 < (idx = wolfSSL_read(ssl, input, sizeof(input)-1))) { input[idx] = 0; - fprintf(stderr, "Client message: %s\n", input); + fprintf(stderr, "Client message: %s\n", input); } AssertIntEQ(privateNameLen, wolfSSL_write(ssl, privateName, @@ -13774,20 +13778,27 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void) #endif /* OPENSSL_EXTRA && HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ return EXPECT_RESULT(); } -#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ - defined(HAVE_IO_TESTS_DEPENDENCIES) + +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +#if defined(HAVE_IO_TESTS_DEPENDENCIES) static int test_wolfSSL_Tls13_ECH_params(void) { EXPECT_DECLS; #if !defined(NO_WOLFSSL_CLIENT) - word32 outputLen = 0; - byte testBuf[72]; - WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - WOLFSSL *ssl = wolfSSL_new(ctx); + byte testBuf[256]; + /* base64 ech configs from cloudflare-ech.com */ + const char* b64Configs = + "AEX+DQBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA="; + word32 outputLen = sizeof(testBuf); + word16 tmpLen; + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + WOLFSSL* ssl = wolfSSL_new(ctx); ExpectNotNull(ctx); ExpectNotNull(ssl); + /* CTX NULL errors */ + /* invalid ctx */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(NULL, "ech-public-name.com", 0, 0, 0)); @@ -13796,57 +13807,130 @@ static int test_wolfSSL_Tls13_ECH_params(void) 0, 0)); /* invalid algorithms */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, - "ech-public-name.com", 1000, 1000, 1000)); + "ech-public-name.com", 1000, 0, 0)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 0, 1000, 0)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 0, 0, 1000)); - /* invalid ctx */ + /* invalid base64 configs: NULL ctx, NULL configs, 0 length */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(NULL, - (char*)testBuf, sizeof(testBuf))); - /* invalid base64 configs */ + b64Configs, (word32)XSTRLEN(b64Configs))); ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, - NULL, sizeof(testBuf))); - /* invalid length */ + NULL, (word32)XSTRLEN(b64Configs))); ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, - (char*)testBuf, 0)); + b64Configs, 0)); - /* invalid ctx */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(NULL, - testBuf, sizeof(testBuf))); - /* invalid configs */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, - NULL, sizeof(testBuf))); - /* invalid length */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, - testBuf, 0)); + /* invalid configs: NULL ctx, NULL configs, 0 length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(NULL, testBuf, + sizeof(testBuf))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, NULL, + sizeof(testBuf))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, 0)); - /* invalid ctx */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, NULL, - &outputLen)); - /* invalid output len */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, NULL)); + /* SSL NULL errors */ - /* invalid ssl */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, - (char*)testBuf, sizeof(testBuf))); - /* invalid configs64 */ + /* invalid base64 configs: NULL ssl, NULL configs, 0 length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, b64Configs, + (word32)XSTRLEN(b64Configs))); ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, NULL, - sizeof(testBuf))); - /* invalid size */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, - (char*)testBuf, 0)); + (word32)XSTRLEN(b64Configs))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, b64Configs, + 0)); - /* invalid ssl */ + /* invalid configs: NULL ssl, NULL configs, 0 length */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(NULL, testBuf, sizeof(testBuf))); - /* invalid configs */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, NULL, sizeof(testBuf))); - /* invalid size */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, 0)); - /* invalid ssl */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(NULL, NULL, &outputLen)); - /* invalid size */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(ssl, NULL, NULL)); + /* stateful errors */ + + /* actually generate configs */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 0, 0, 0)); + + /* bad get: NULL ctx, NULL output len, short output len */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, testBuf, + &outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, testBuf, NULL)); + outputLen = 5; + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, testBuf, + &outputLen)); + + /* should be able to retrieve length with NULL buffer... */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, + &outputLen)); + ExpectIntGE(sizeof(testBuf), outputLen); + + /* and the get should work with this length */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, testBuf, + &outputLen)); + + /* reject config with invalid total length */ + ato16(testBuf, &tmpLen); + testBuf[0] = 0xFF; + testBuf[1] = 0xFF; + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, + outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, + outputLen)); + c16toa(tmpLen, testBuf); + + /* reject config with invalid version */ + testBuf[2] ^= 0x01; + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, + outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, + outputLen)); + testBuf[2] ^= 0x01; + + /* reject config with bad length */ + ato16(testBuf + 4, &tmpLen); + testBuf[4] = 0xFF; + testBuf[5] = 0xFF; + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, + outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, + outputLen)); + c16toa(tmpLen, testBuf + 4); + + /* set valid configs */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, + outputLen)); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, + outputLen)); + + /* NULL ssl, NULL buffer, NULL output len */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(NULL, testBuf, + &outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(ssl, NULL, &outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(ssl, testBuf, NULL)); + + /* reject setting configs when ssl already has them */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, + outputLen)); + + /* unable to get configs from ssl with no configs (because of disable) */ + wolfSSL_SetEchEnable(ssl, 0); + outputLen = sizeof(testBuf); + ExpectIntNE(WOLFSSL_SUCCESS, + wolfSSL_GetEchConfigs(ssl, testBuf, &outputLen)); + + /* base64 tests */ + + /* set base64 configs */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64Configs, (word32)XSTRLEN(b64Configs))); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64Configs, (word32)XSTRLEN(b64Configs))); + + /* disable and check ctx has no configs as well */ + wolfSSL_CTX_SetEchEnable(ctx, 0); + outputLen = sizeof(testBuf); + ExpectIntNE(WOLFSSL_SUCCESS, + wolfSSL_CTX_GetEchConfigs(ctx, testBuf, &outputLen)); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); @@ -13855,7 +13939,8 @@ static int test_wolfSSL_Tls13_ECH_params(void) return EXPECT_RESULT(); } -static int test_wolfSSL_Tls13_ECH_ex(int hrr) +static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, + method_provider clientMeth, int hrr) { EXPECT_DECLS; tcp_ready ready; @@ -13882,11 +13967,10 @@ static int test_wolfSSL_Tls13_ECH_ex(int hrr) XMEMSET(&server_args, 0, sizeof(func_args)); XMEMSET(&server_cbf, 0, sizeof(callback_functions)); XMEMSET(&client_cbf, 0, sizeof(callback_functions)); - server_cbf.method = wolfTLSv1_3_server_method; /* TLS1.3 */ + server_cbf.method = serverMeth; /* create the server context here so we can get the ech config */ - ExpectNotNull(server_cbf.ctx = - wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectNotNull(server_cbf.ctx = wolfSSL_CTX_new(serverMeth())); /* generate ech config */ ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(server_cbf.ctx, @@ -13904,10 +13988,10 @@ static int test_wolfSSL_Tls13_ECH_ex(int hrr) start_thread(server_task_ech, &server_args, &serverThread); wait_tcp_ready(&server_args); - /* run as a TLS1.3 client */ - ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + /* set the client TLS version and run */ + ExpectNotNull(ctx = wolfSSL_CTX_new(clientMeth())); ExpectIntEQ(WOLFSSL_SUCCESS, - wolfSSL_CTX_load_verify_locations(ctx, caCertFile, 0)); + wolfSSL_CTX_load_verify_locations(ctx, caCertFile, 0)); ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_use_certificate_file(ctx, cliCertFile, SSL_FILETYPE_PEM)); ExpectIntEQ(WOLFSSL_SUCCESS, @@ -13937,8 +14021,8 @@ static int test_wolfSSL_Tls13_ECH_ex(int hrr) ExpectIntEQ(wolfSSL_write(ssl, privateName, privateNameLen), privateNameLen); ExpectIntGT((replyLen = wolfSSL_read(ssl, reply, sizeof(reply))), 0); - /* add th null terminator for string compare */ - reply[replyLen] = 0; + /* add the null terminator for string compare */ + reply[replyLen] = '\0'; /* check that the server replied with the private name */ ExpectStrEQ(privateName, reply); wolfSSL_free(ssl); @@ -13955,13 +14039,532 @@ static int test_wolfSSL_Tls13_ECH_ex(int hrr) static int test_wolfSSL_Tls13_ECH(void) { - return test_wolfSSL_Tls13_ECH_ex(0); + return test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, + wolfTLSv1_3_client_method, 0); } static int test_wolfSSL_Tls13_ECH_HRR(void) { - return test_wolfSSL_Tls13_ECH_ex(1); + return test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, + wolfTLSv1_3_client_method, 1); +} + +static int test_wolfSSL_SubTls13_ECH(void) +{ + EXPECT_DECLS; + +#ifndef WOLFSSL_NO_TLS12 + ExpectIntNE(test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, + wolfTLSv1_2_client_method, 0), WOLFSSL_SUCCESS); + ExpectIntNE(test_wolfSSL_ECH_conn_ex(wolfTLSv1_2_server_method, + wolfTLSv1_3_client_method, 0), WOLFSSL_SUCCESS); + ExpectIntNE(test_wolfSSL_ECH_conn_ex(wolfSSLv23_server_method, + wolfTLSv1_2_client_method, 0), WOLFSSL_SUCCESS); +#endif + + return EXPECT_RESULT(); +} +#endif /* HAVE_IO_TESTS_DEPENDENCIES */ + +#ifdef HAVE_SSL_MEMIO_TESTS_DEPENDENCIES + +/* 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* echCbTestPrivateName = "ech-private-name.com"; + +/* the arg is whether the client has ech enabled or not */ +static int test_ech_server_sni_callback(WOLFSSL* ssl, int* ad, void* arg) +{ + const char* name; + + if (!wolfSSL_SNI_GetRequest(ssl, WOLFSSL_SNI_HOST_NAME, (void**)&name)) { + *ad = WOLFSSL_AD_UNRECOGNIZED_NAME; + return fatal_return; + } + + /* reached by *_disable_conn test: expect name to be the public SNI when + * client has ECH enabled, otherwise it should be the private SNI */ + if (arg != NULL && *(int*)arg == 1 && + XSTRCMP(name, echCbTestPublicName) == 0) { + /* server shouldn't recognize the public name, but if SNI verifications + * are relaxed it should hit this */ + return 0; + } + else if (XSTRCMP(name, echCbTestPrivateName) == 0) { + return 0; + } + else { + *ad = WOLFSSL_AD_UNRECOGNIZED_NAME; + return fatal_return; + } +} + +/* Server ctx_ready callback: generate ECH config */ +static int test_ech_server_ctx_ready(WOLFSSL_CTX* ctx) +{ + int ret; + + ret = wolfSSL_CTX_GenerateEchConfig(ctx, echCbTestPublicName, 0, 0, 0); + if (ret != WOLFSSL_SUCCESS) + return TEST_FAIL; + + echCbTestConfigsLen = sizeof(echCbTestConfigs); + ret = wolfSSL_CTX_GetEchConfigs(ctx, echCbTestConfigs, + &echCbTestConfigsLen); + if (ret != WOLFSSL_SUCCESS) + return TEST_FAIL; + + return TEST_SUCCESS; +} + +/* Server ssl_ready callback: set SNI */ +static int test_ech_server_ssl_ready(WOLFSSL* ssl) +{ + int ret; + + ret = wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, echCbTestPrivateName, + (word16)XSTRLEN(echCbTestPrivateName)); + if (ret != WOLFSSL_SUCCESS) + return TEST_FAIL; + + return TEST_SUCCESS; +} + +/* Client ssl_ready callback: set ECH configs and SNI */ +static int test_ech_client_ssl_ready(WOLFSSL* ssl) +{ + int ret; + + ret = wolfSSL_SetEchConfigs(ssl, echCbTestConfigs, echCbTestConfigsLen); + if (ret != WOLFSSL_SUCCESS) + return TEST_FAIL; + + ret = wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, echCbTestPrivateName, + (word16)XSTRLEN(echCbTestPrivateName)); + if (ret != WOLFSSL_SUCCESS) + return TEST_FAIL; + + return TEST_SUCCESS; +} + +/* Test ECH rejection when no private SNI is set */ +static int test_wolfSSL_Tls13_ECH_no_private_name(void) +{ + EXPECT_DECLS; + struct test_ssl_memio_ctx test_ctx; + + /* client sends private SNI, server does not have one set */ + + 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.c_cb.ssl_ready = test_ech_client_ssl_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + + test_ssl_memio_cleanup(&test_ctx); + + /* client does not send private SNI, server has one set */ + + 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); + + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, echCbTestConfigs, + echCbTestConfigsLen), WOLFSSL_SUCCESS); + + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); } + +/* Test ECH rejection when configs don't match */ +/* TODO: private SNI in client ECH configs would fail to match against server's + * public SNI and then (incorrectly?) match against server's private SNI */ +static int test_wolfSSL_Tls13_ECH_bad_configs_ex(int hrr, int sniCb) +{ + EXPECT_DECLS; + struct test_ssl_memio_ctx test_ctx; + WOLFSSL_CTX* tempCtx = NULL; + const char* badPublicName = "ech-bad-public-name.com"; + const char* badPrivateName = "ech-bad-private-name.com"; + byte badPublicConfig[128]; + word32 badPublicConfigLen = sizeof(badPublicConfig); + + /* verify with bad public SNI / config */ + + 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 its own ECH config */ + 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); + + /* generate new ECH config with different publicName for client to use */ + 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, badPublicConfig, + &badPublicConfigLen), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(tempCtx); + tempCtx = NULL; + + /* set bad public config on client */ + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badPublicConfig, + badPublicConfigLen), 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); + } + if (sniCb) { + wolfSSL_CTX_set_servername_callback(test_ctx.s_ctx, + test_ech_server_sni_callback); + } + + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + + test_ssl_memio_cleanup(&test_ctx); + + + /* verify with bad private SNI */ + + 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); + + /* set bad private SNI on client */ + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, echCbTestConfigs, + echCbTestConfigsLen), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + badPrivateName, (word16)XSTRLEN(badPrivateName)), WOLFSSL_SUCCESS); + + if (hrr) { + ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); + } + if (sniCb) { + wolfSSL_CTX_set_servername_callback(test_ctx.s_ctx, + test_ech_server_sni_callback); + } + + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); +} + +static int test_wolfSSL_Tls13_ECH_bad_configs(void) +{ + EXPECT_DECLS; + + ExpectIntEQ(test_wolfSSL_Tls13_ECH_bad_configs_ex(0, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_bad_configs_ex(0, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_bad_configs_ex(1, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_bad_configs_ex(1, 1), WOLFSSL_SUCCESS); + + 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 + * its second config and succeed */ +static int test_wolfSSL_Tls13_ECH_new_config(void) +{ + EXPECT_DECLS; + test_ssl_memio_ctx test_ctx; + byte altConfig[512]; + word32 altConfigLen = sizeof(altConfig); + byte combinedConfigs[512]; + word32 combinedConfigsLen = sizeof(combinedConfigs); + word16 firstConfigLen; + word16 secondConfigOffset; + word16 secondConfigLen; + + 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 its own ECH config */ + 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); + + /* generate a second ECH config for the server */ + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(test_ctx.s_ctx, + echCbTestPrivateName, 0, 0, 0), WOLFSSL_SUCCESS); + ExpectNotNull(test_ctx.s_ctx->echConfigs->next); + + /* capture the second ECH config in the list for the client to use */ + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(test_ctx.s_ctx, combinedConfigs, + &combinedConfigsLen), WOLFSSL_SUCCESS); + + /* ECHConfigList: [2 byte list len] [ECHConfig]... + * ECHConfig: [2 byte version] [2 byte config len] [config data] */ + ExpectIntGE(combinedConfigsLen, OPAQUE16_LEN * 3); + ato16(combinedConfigs + OPAQUE16_LEN + OPAQUE16_LEN, &firstConfigLen); + secondConfigOffset = OPAQUE16_LEN + OPAQUE16_LEN + OPAQUE16_LEN + + firstConfigLen; + ExpectIntGE(combinedConfigsLen, + secondConfigOffset + OPAQUE16_LEN + OPAQUE16_LEN); + ato16(combinedConfigs + secondConfigOffset + OPAQUE16_LEN, + &secondConfigLen); + secondConfigLen += OPAQUE16_LEN + OPAQUE16_LEN; + ExpectIntGE(combinedConfigsLen, secondConfigOffset + secondConfigLen); + + /* build the ECHConfigList */ + c16toa(secondConfigLen, altConfig); + ExpectIntLE(OPAQUE16_LEN + secondConfigLen, (word16)sizeof(altConfig)); + XMEMCPY(altConfig + OPAQUE16_LEN, combinedConfigs + secondConfigOffset, + secondConfigLen); + altConfigLen = OPAQUE16_LEN + secondConfigLen; + + /* Set client configs - server should try both and succeed with second + * Or seek the correct one immediately through the configId */ + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, altConfig, altConfigLen), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)), + WOLFSSL_SUCCESS); + + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); +} + +/* 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 + * 2. client sends GREASE ECH extensions and server has ECH configs, handshake + * succeeds and client receives ECH configs */ +static int test_wolfSSL_Tls13_ECH_GREASE(void) +{ + EXPECT_DECLS; + test_ssl_memio_ctx test_ctx; + byte greaseConfigs[512]; + word32 greaseConfigsLen = sizeof(greaseConfigs); + + /* GREASE when server has no ECH configs */ + + 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); + + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)), + WOLFSSL_SUCCESS); + + /* verify ECH is enabled on the client and server */ + ExpectIntEQ(test_ctx.s_ssl->options.disableECH, 0); + 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); + + /* handshake should succeed - server ignores 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 */ + 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); + + test_ssl_memio_cleanup(&test_ctx); + + /* GREASE when server has ECH configs */ + + 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; + + /* generate ECH configs */ + 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); + + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + echCbTestPrivateName, (word16)XSTRLEN(echCbTestPrivateName)), + WOLFSSL_SUCCESS); + + /* verify ECH is enabled on the client and server */ + ExpectIntEQ(test_ctx.s_ssl->options.disableECH, 0); + 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); + + /* 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 */ + 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); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); +} + +static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, + int enableClient) +{ + 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; + + /* both server and client will be setup to use ECH */ + 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); + + /* this callback will ensure that the correct SNI is being held */ + wolfSSL_CTX_set_servername_callback(test_ctx.s_ctx, + test_ech_server_sni_callback); + ExpectIntEQ(wolfSSL_CTX_set_servername_arg(test_ctx.s_ctx, &enableClient), + WOLFSSL_SUCCESS); + + /* disable ECH on the appropriate side(s) */ + wolfSSL_SetEchEnable(test_ctx.s_ssl, enableServer); + wolfSSL_SetEchEnable(test_ctx.c_ssl, enableClient); + + if (!enableClient) { + /* client ECH disabled: no ECH extension sent, handshake succeeds + * normally but ECH is not accepted */ + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_SUCCESS); + } + else if (!enableServer) { + /* client sends ECH but server can't process it: server has no ECH + * keys so it processes the outer ClientHello, client detects ECH + * rejection and aborts the handshake */ + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_SUCCESS); + } + ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 0); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); +} + +/* setup a server and client with ECH then disable on one, the other, or both. + * Verifies that disabling ECH prevents ECH from being used and that the + * public/private SNI's are verified correctly */ +static int test_wolfSSL_Tls13_ECH_disable_conn(void) +{ + EXPECT_DECLS; + + ExpectIntEQ(test_wolfSSL_Tls13_ECH_disable_conn_ex(0, 1), TEST_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_disable_conn_ex(1, 0), TEST_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_disable_conn_ex(0, 0), TEST_SUCCESS); + + return EXPECT_RESULT(); +} +#endif /* HAVE_SSL_MEMIO_TESTS_DEPENDENCIES */ + +/* verify that ECH can be enabled/disabled without issue */ +static int test_wolfSSL_Tls13_ECH_enable_disable(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + byte echConfigs[128]; + word32 echConfigsLen = sizeof(echConfigs); + + /* NULL ctx, NULL ssl should not crash */ + wolfSSL_SetEchEnable(ssl, 0); + wolfSSL_CTX_SetEchEnable(ctx, 0); + + /* test CTX level enable/disable */ + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx, "public.com", 0, 0, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx, echConfigs, &echConfigsLen), + WOLFSSL_SUCCESS); + + /* disable ECH at CTX level */ + wolfSSL_CTX_SetEchEnable(ctx, 0); + ExpectIntEQ(ctx->disableECH, 1); + ExpectNull(ctx->echConfigs); + + wolfSSL_CTX_SetEchEnable(ctx, 1); + ExpectIntEQ(ctx->disableECH, 0); + + /* test SSL level enable/disable */ + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + ExpectIntEQ(wolfSSL_SetEchConfigs(ssl, echConfigs, echConfigsLen), + WOLFSSL_SUCCESS); + + /* disable ECH at SSL level */ + wolfSSL_SetEchEnable(ssl, 0); + ExpectIntEQ(ssl->options.disableECH, 1); + ExpectNull(ssl->echConfigs); + + wolfSSL_SetEchEnable(ssl, 1); + ExpectIntEQ(ssl->options.disableECH, 0); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* !NO_WOLFSSL_CLIENT */ + + return EXPECT_RESULT(); +} + #endif /* HAVE_ECH && WOLFSSL_TLS13 */ #if defined(HAVE_IO_TESTS_DEPENDENCIES) && \ @@ -32666,13 +33269,23 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_SCR_check_enabled), TEST_DECL(test_tls_ext_duplicate), TEST_DECL(test_tls_bad_legacy_version), -#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ - defined(HAVE_IO_TESTS_DEPENDENCIES) +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +#if defined(HAVE_IO_TESTS_DEPENDENCIES) TEST_DECL(test_wolfSSL_Tls13_ECH_params), /* Uses Assert in handshake callback. */ TEST_DECL(test_wolfSSL_Tls13_ECH), TEST_DECL(test_wolfSSL_Tls13_ECH_HRR), + TEST_DECL(test_wolfSSL_SubTls13_ECH), +#endif +#if defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + TEST_DECL(test_wolfSSL_Tls13_ECH_no_private_name), + TEST_DECL(test_wolfSSL_Tls13_ECH_bad_configs), + TEST_DECL(test_wolfSSL_Tls13_ECH_new_config), + TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE), + TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn), #endif + TEST_DECL(test_wolfSSL_Tls13_ECH_enable_disable), +#endif /* WOLFSSL_TLS13 && HAVE_ECH */ TEST_DECL(test_wolfSSL_X509_TLS_version_test_1), TEST_DECL(test_wolfSSL_X509_TLS_version_test_2), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 6960a29fe3..411600aab0 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2964,6 +2964,8 @@ typedef struct Options Options; #define TLSXT_KEY_QUIC_TP_PARAMS 0x0039 /* RFC 9001, ch. 8.2 */ #define TLSXT_ECH 0xfe0d /* from */ /* draft-ietf-tls-esni-13 */ +#define TLSXT_ECH_OUTER_EXTENSIONS 0xfd00 /* from + draft-ietf-tls-esni-13 */ /* The 0xFF section is experimental/custom/personal use */ #define TLSXT_CKS 0xff92 /* X9.146 */ #define TLSXT_RENEGOTIATION_INFO 0xff01 @@ -3110,6 +3112,7 @@ typedef struct WOLFSSL_ECH { Hpke* hpke; HpkeBaseContext* hpkeContext; const byte* aad; + const char* privateName; void* ephemeralKey; WOLFSSL_EchConfig* echConfig; byte* innerClientHello; @@ -3126,6 +3129,7 @@ typedef struct WOLFSSL_ECH { byte configId; byte enc[HPKE_Npk_MAX]; byte innerCount; + byte processingInner; } WOLFSSL_ECH; WOLFSSL_LOCAL int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config); @@ -5092,7 +5096,6 @@ struct Options { word16 useDtlsCID:1; #endif /* WOLFSSL_DTLS_CID */ #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) - word16 useEch:1; word16 echAccepted:1; byte disableECH:1; /* Did the user disable ech */ #endif @@ -5873,7 +5876,6 @@ struct WOLFSSL { HS_Hashes* hsHashes; #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) HS_Hashes* hsHashesEch; - HS_Hashes* hsHashesEchInner; #endif void* IOCB_ReadCtx; void* IOCB_WriteCtx; diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index ebe06f3e33..b06605e085 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1226,8 +1226,8 @@ WOLFSSL_API int wolfSSL_CTX_GetEchConfigs(WOLFSSL_CTX* ctx, byte* output, WOLFSSL_API void wolfSSL_CTX_SetEchEnable(WOLFSSL_CTX* ctx, byte enable); -WOLFSSL_API int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, - word32 echConfigs64Len); +WOLFSSL_API int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, + const char* echConfigs64, word32 echConfigs64Len); WOLFSSL_API int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, word32 echConfigsLen);