diff --git a/src/internal.c b/src/internal.c index 74f59faff9..45fc6ccbcf 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8587,8 +8587,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); diff --git a/src/tls.c b/src/tls.c index 7d7dcea86c..0f5370bd39 100644 --- a/src/tls.c +++ b/src/tls.c @@ -13265,6 +13265,328 @@ 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 */ +static const byte* EchFindOuterExtension(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 not NULL, copy extensions into the provided buffer */ +static int EchCopyOuterExtensions(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 = EchFindOuterExtension(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 = EchFindOuterExtension(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 + */ +static int TLSX_ExpandEchOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, + void* heap) +{ + int ret = 0; + 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; + + innerCh = ech->innerClientHello + HANDSHAKE_HEADER_SZ; + 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 = EchCopyOuterExtensions(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 + HANDSHAKE_HEADER_SZ, 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 + HANDSHAKE_HEADER_SZ; + 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 */ + innerExtIdx = innerExtIdx - sessionIdLen + ssl->session->sessionIDSz; + c16toa(innerExtLen - echOuterExtLen + (word16)extraSize, + newInnerChRef - OPAQUE16_LEN); + + /* insert expanded extensions from outer ClientHello */ + ret = EchCopyOuterExtensions(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 */ @@ -13516,6 +13838,16 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, } /* subtract the length of the padding from the length */ ech->innerClientHelloLen -= i; + + /* expand EchOuterExtensions if present */ + ret = TLSX_ExpandEchOuterExtensions(ssl, ech, ssl->heap); + if (ret != 0) { + WOLFSSL_MSG_EX("ECH: failed to expand EchOuterExtensions"); + 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; @@ -16778,6 +17110,18 @@ 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->options.useEch == 1) { + 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 5b0f098507..f88d6bccc4 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4196,7 +4196,7 @@ static int WritePSKBinders(WOLFSSL* ssl, byte* output, word32 idx) #endif #if defined(HAVE_ECH) -/* returns status after we hash the ech inner */ +/* returns status after we hash the inner client hello */ static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) { int ret = 0; @@ -4208,52 +4208,44 @@ static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) byte falseHeader[HANDSHAKE_HEADER_SZ]; #endif - if (ssl == NULL || ech == NULL) + if (ssl == NULL || ech == NULL) { return BAD_FUNC_ARG; - realSz = ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt; + } + + if (ssl->options.side == WOLFSSL_CLIENT_END) { + realSz = ech->innerClientHelloLen - ech->paddingLen - ech->hpke->Nt; + } + else { + realSz = ech->innerClientHelloLen; + } + 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 */ + + ssl->hsHashes = ssl->hsHashesEch; + if (ssl->options.echAccepted == 0 && ssl->hsHashes == NULL) { + ret = InitHandshakeHashes(ssl); 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); - } + ssl->hsHashesEch = ssl->hsHashes; + ech->innerCount = 1; + } + } + if (ret == 0) { + 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, sizeof(falseHeader)); if (ret == 0) { - ssl->hsHashes = ssl->hsHashesEchInner; - ret = HashRaw(ssl, falseHeader, HANDSHAKE_HEADER_SZ); - ssl->hsHashes = ssl->hsHashesEch; + ret = HashRaw(ssl, ech->innerClientHello, realSz); } } + else { + /* server-side: innerClientHello contains header + body */ + ret = HashRaw(ssl, ech->innerClientHello, + sizeof(falseHeader) + realSz); + } } - /* 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; } @@ -4682,9 +4674,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); @@ -4748,8 +4747,9 @@ 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)) + (ssl->options.echAccepted || args->ech->innerCount == 0)) { ret = EchHashHelloInner(ssl, args->ech); + } #endif /* compute the outer hash */ if (ret == 0) @@ -4841,43 +4841,77 @@ 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 */ -static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, - const byte* input, int acceptOffset, int helloSz) +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; 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 acceptConfirmation[ECH_ACCEPT_CONFIRMATION_SZ]; + byte messageHashHeader[HANDSHAKE_HEADER_SZ]; + XMEMSET(zeros, 0, sizeof(zeros)); XMEMSET(transcriptEchConf, 0, sizeof(transcriptEchConf)); + XMEMSET(clientHelloInnerHash, 0, sizeof(clientHelloInnerHash)); XMEMSET(expandLabelPrk, 0, sizeof(expandLabelPrk)); - XMEMSET(acceptConfirmation, 0, sizeof(acceptConfirmation)); - /* store so we can restore regardless of the outcome */ + 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) + + if (isHrr) { + /* the transcript hash of ClientHelloInner1 */ + hashSz = GetMsgHash(ssl, clientHelloInnerHash); + if (hashSz <= 0) { + ret = hashSz; + } + + /* 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, sizeof(messageHashHeader)); + } + 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); - /* 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) + if (ret == 0) { ret = GetMsgHash(ssl, transcriptEchConf); - if (ret > 0) - ret = 0; + if (ret > 0) { + ret = 0; + } + } + /* pick the right type and size based on mac_algorithm */ if (ret == 0) { switch (ssl->specs.mac_algorithm) { @@ -4910,6 +4944,7 @@ static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, break; } } + /* extract clientRandomInner with a key of all zeros */ if (ret == 0) { PRIVATE_KEY_UNLOCK(); @@ -4924,47 +4959,116 @@ static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, #endif PRIVATE_KEY_LOCK(); } + /* tls expand with the confirmation label */ if (ret == 0) { PRIVATE_KEY_UNLOCK(); - ret = Tls13HKDFExpandKeyLabel(ssl, acceptConfirmation, + 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; +} + +/* check if the server accepted ech or not, return status */ +static int EchCheckAcceptance(WOLFSSL* ssl, byte* label, word16 labelSz, + const byte* input, int acceptOffset, int helloSz) +{ + int ret = 0; + int isHrr = 0; + HS_Hashes* tmpHashes; + byte acceptConfirmation[ECH_ACCEPT_CONFIRMATION_SZ]; + + XMEMSET(acceptConfirmation, 0, sizeof(acceptConfirmation)); + + tmpHashes = ssl->hsHashes; + ssl->hsHashes = ssl->hsHashesEch; + + 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); + if (ret == 0) { - /* last 8 bytes should match our expand output */ + /* last 8 bytes must match the 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) { + ssl->hsHashes = ssl->hsHashesEch; + ret = HashRaw(ssl, input, helloSz + HANDSHAKE_HEADER_SZ); + } + /* 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; +} + +/* replace the last acceptance field for either ServerHello 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; + HS_Hashes* tmpHashes; + + tmpHashes = ssl->hsHashes; + ssl->hsHashes = ssl->hsHashesEch; + + ret = EchCalcAcceptance(ssl, label, labelSz, output, acceptOffset, + helloSz - HANDSHAKE_HEADER_SZ, msgType == hello_retry_request, + output + acceptOffset); + + /* after HRR, hsHashesEch must contain: + * message_hash(ClientHelloInner1) || HRR (actual, not zeros) */ + if (ret == 0 && msgType == hello_retry_request) { + ssl->hsHashes = ssl->hsHashesEch; + 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; } @@ -6656,7 +6760,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); @@ -6998,18 +7101,11 @@ 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 */ + /* hash clientHelloInner to hsHashesEch */ if (ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) { - tmpHashes = ssl->hsHashes; - ssl->hsHashes = NULL; - ret = InitHandshakeHashes(ssl); + 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 @@ -7277,107 +7373,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,6 +12915,7 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, if (echX != NULL && ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE) { + byte copyRandom = ((WOLFSSL_ECH*)echX->data)->innerCount == 0; /* reset the inOutIdx to the outer start */ *inOutIdx = echInOutIdx; /* call again with the inner hello */ @@ -12931,8 +12927,16 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, } /* 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/wolfssl/internal.h b/wolfssl/internal.h index 59fb268aef..a401b255ea 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 @@ -5854,7 +5856,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;