diff --git a/src/tls.c b/src/tls.c index 347761b263..5b3812f2b4 100644 --- a/src/tls.c +++ b/src/tls.c @@ -14105,6 +14105,9 @@ static int TLSX_ECH_ExpandOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, sessionIdLen, copyLen); } else { + innerExtIdx = headerSz + innerExtIdx - OPAQUE16_LEN - + sessionIdLen + ssl->session->sessionIDSz; + copyLen = echOuterExtIdx - OPAQUE16_LEN - RAN_LEN - OPAQUE8_LEN - sessionIdLen; XMEMCPY(newInnerChRef, innerCh + OPAQUE16_LEN + RAN_LEN + OPAQUE8_LEN + @@ -14113,7 +14116,7 @@ static int TLSX_ECH_ExpandOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, /* update extensions length in the new ClientHello */ c16toa(innerExtLen - echOuterExtLen + (word16)extraSize, - newInnerChRef - OPAQUE16_LEN); + newInnerCh + innerExtIdx); ret = TLSX_ECH_CopyOuterExtensions(outerCh, outerChLen, &newInnerChRef, &newInnerChLen, numOuterRefs, outerRefTypes); @@ -16326,6 +16329,105 @@ static int TLSX_EchRestoreSNI(WOLFSSL* ssl, char* serverName, return ret; } +/* Returns 1 if the extension may be encoded into ech_outer_extensions, + * 0 otherwise */ +static int TLSX_ECH_IsEncodeable(word16 type) +{ + switch (type) { + case TLSX_SERVER_NAME: + case TLSX_ECH: + case TLSX_APPLICATION_LAYER_PROTOCOL: +#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) + case TLSX_PRE_SHARED_KEY: +#endif +#ifdef WOLFSSL_EARLY_DATA + case TLSX_EARLY_DATA: +#endif + return 0; + default: + return 1; + } +} + +/* find extensions that can be encoded into ech_outer_extensions. + * If output is non-NULL, then write the encoded form. + * + * Layout of OuterExtensions (RFC 9849, S5.1): + * 2-byte extension_type + 2-byte extension_data length + + * 1-byte list length + 2*count bytes of extension types + */ +static int TLSX_ECH_BuildOuterExtensions(WOLFSSL* ssl, const byte* semaphore, + byte msgType, byte* output, word16* pOffset, word16* outCount, + byte* encodeMask) +{ + TLSX* list; + TLSX* extension; + byte* typesStart = NULL; + int listIdx; + word16 count = 0; + byte isRequest = (msgType == client_hello || + msgType == certificate_request); + byte seen[SEMAPHORE_SIZE]; + + /* backup semaphore so it can be aliased by encodeMask */ + XMEMCPY(seen, semaphore, SEMAPHORE_SIZE); + + if (output != NULL && pOffset != NULL) { + typesStart = output + *pOffset + + HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + OPAQUE8_LEN; + } + + for (listIdx = 0; listIdx < 2; listIdx++) { + list = (listIdx == 0) ? ssl->extensions : + (ssl->ctx != NULL ? ssl->ctx->extensions : NULL); + for (extension = list; extension != NULL; extension = extension->next) { + word16 type = (word16)extension->type; + word16 semIdx = TLSX_ToSemaphore(type); + + /* OuterExtensions is <2..254>, so reference at most 127 types */ + if (count >= 127) { + WOLFSSL_MSG("ECH: cannot encode more than 127 extensions"); + break; + } + + if (!isRequest && !extension->resp) + continue; + if (!IS_OFF(seen, semIdx)) + continue; + TURN_ON(seen, semIdx); + if (type == TLSX_ECH || !TLSX_ECH_IsEncodeable(type)) + continue; + + if (typesStart != NULL) + c16toa(type, typesStart + count * OPAQUE16_LEN); + count++; + TURN_ON(encodeMask, semIdx); + } + } + + if (count > 0 && pOffset != NULL) { + word16 listLen = (word16)(OPAQUE16_LEN * count); + word16 blockSz = (word16)(HELLO_EXT_TYPE_SZ + OPAQUE16_LEN + + OPAQUE8_LEN + listLen); + if ((word32)*pOffset + blockSz > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("ECH OuterExtensions overflows extensions length"); + return BUFFER_E; + } + if (output != NULL) { + byte* hdr = output + *pOffset; + c16toa(TLSXT_ECH_OUTER_EXTENSIONS, hdr); + c16toa((word16)(OPAQUE8_LEN + listLen), hdr + OPAQUE16_LEN); + hdr[OPAQUE16_LEN + OPAQUE16_LEN] = (byte)listLen; + } + + /* accumulate offset even if nothing is written */ + *pOffset += blockSz; + } + + *outCount = count; + return 0; +} + /* because the size of ech depends on the size of other extensions we need to * get the size with ech special and process ech last, return status */ static int TLSX_GetSizeWithEch(WOLFSSL* ssl, byte* semaphore, byte msgType, @@ -16335,18 +16437,32 @@ static int TLSX_GetSizeWithEch(WOLFSSL* ssl, byte* semaphore, byte msgType, TLSX* echX = NULL; TLSX* serverNameX = NULL; TLSX** extensions = NULL; + WOLFSSL_ECH* ech = NULL; + word16 count = 0; WC_DECLARE_VAR(serverName, char, WOLFSSL_HOST_NAME_MAX, 0); WC_ALLOC_VAR_EX(serverName, char, WOLFSSL_HOST_NAME_MAX, NULL, DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E); + r = TLSX_EchChangeSNI(ssl, &echX, serverName, &serverNameX, &extensions); + + if (echX != NULL) + ech = (WOLFSSL_ECH*)echX->data; + /* If ECH won't be written exclude it from the size calculation */ - if (r == 0 && echX != NULL && - !ssl->options.echAccepted && - ((WOLFSSL_ECH*)echX->data)->innerCount != 0) { + if (r == 0 !ssl->options.echAccepted && ech != NULL && + ech->innerCount != 0) { TURN_ON(semaphore, TLSX_ToSemaphore(echX->type)); } - if (r == 0 && ssl->extensions) + + /* if encoding, then count encoded form of inner ClientHello. + * `semaphore` is in/out so encodable extensions will later be ignored */ + if (r == 0 && ech != NULL && ech->type == ECH_TYPE_INNER && + ech->writeEncoded) { + ret = TLSX_ECH_BuildOuterExtensions(ssl, semaphore, msgType, + NULL, pLength, &count, semaphore); + } + if (r == 0 && ret == 0 && ssl->extensions) ret = TLSX_GetSize(ssl->extensions, semaphore, msgType, pLength); if (r == 0 && ret == 0 && ssl->ctx && ssl->ctx->extensions) ret = TLSX_GetSize(ssl->ctx->extensions, semaphore, msgType, pLength); @@ -16490,27 +16606,62 @@ static int TLSX_WriteWithEch(WOLFSSL* ssl, byte* output, byte* semaphore, TLSX* echX = NULL; TLSX* serverNameX = NULL; TLSX** extensions = NULL; + WOLFSSL_ECH* ech = NULL; WC_DECLARE_VAR(serverName, char, WOLFSSL_HOST_NAME_MAX, 0); WC_ALLOC_VAR_EX(serverName, char, WOLFSSL_HOST_NAME_MAX, NULL, DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E); r = TLSX_EchChangeSNI(ssl, &echX, serverName, &serverNameX, &extensions); ret = r; - if (ret == 0 && echX != NULL) + if (ret == 0 && echX != NULL) { + ech = (WOLFSSL_ECH*)echX->data; /* turn ech on so it doesn't write, then write it last */ TURN_ON(semaphore, TLSX_ToSemaphore(echX->type)); + } + /* for ECH inner, print the encodable block first, then the non-encodables. + * This allows the same transcript to be produced on either side + * (the transcript is over the expanded form). */ + if (ret == 0 && ech != NULL && ech->type == ECH_TYPE_INNER) { + byte encodeMask[SEMAPHORE_SIZE]; + byte* mask = ech->writeEncoded ? semaphore : encodeMask; + word16 count = 0; + int i; + + XMEMSET(encodeMask, 0, SEMAPHORE_SIZE); + + ret = TLSX_ECH_BuildOuterExtensions(ssl, semaphore, msgType, + ech->writeEncoded ? output : NULL, + ech->writeEncoded ? pOffset : NULL, + &count, mask); + if (ret == 0 && count >= 1 && !ech->writeEncoded) { + /* expanded: print encodable block normally */ + for (i = 0; i < SEMAPHORE_SIZE; i++) { + semaphore[i] |= encodeMask[i]; + encodeMask[i] = (byte)~encodeMask[i]; + } + if (ssl->extensions) { + ret = TLSX_Write(ssl->extensions, output + *pOffset, + encodeMask, msgType, pOffset); + } + if (ret == 0 && ssl->ctx && ssl->ctx->extensions) { + ret = TLSX_Write(ssl->ctx->extensions, output + *pOffset, + encodeMask, msgType, pOffset); + } + } + } + + /* print non-encodable block */ if (ret == 0 && ssl->extensions) { ret = TLSX_Write(ssl->extensions, output + *pOffset, semaphore, msgType, pOffset); } - if (ret == 0 && ssl->ctx && ssl->ctx->extensions) { ret = TLSX_Write(ssl->ctx->extensions, output + *pOffset, semaphore, msgType, pOffset); } - /* only write if have a shot at acceptance */ + /* only write ECH if have a shot at acceptance */ if (ret == 0 && echX != NULL && (ssl->options.echAccepted || ((WOLFSSL_ECH*)echX->data)->innerCount == 0)) { diff --git a/src/tls13.c b/src/tls13.c index 73ed518374..8681feb34e 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3836,6 +3836,7 @@ int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config) } /* Hash the inner client hello, initializing the hsHashesEch field if needed. + * This should receive the client hello without outer_extensions 'encoding' * * ssl SSL/TLS object. * ech ECH object. @@ -3863,11 +3864,6 @@ static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) #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; @@ -3876,7 +3872,6 @@ static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) ret = InitHandshakeHashes(ssl); if (ret == 0) { ssl->hsHashesEch = ssl->hsHashes; - ech->innerCount = 1; } } @@ -4582,6 +4577,7 @@ typedef struct Sch13Args { #if defined(HAVE_ECH) int clientRandomOffset; int preXLength; + word32 expandedInnerLen; WOLFSSL_ECH* ech; #endif } Sch13Args; @@ -4783,24 +4779,49 @@ int SendTls13ClientHello(WOLFSSL* ssl) /* only prepare if we have a chance at acceptance */ if (ssl->options.echAccepted || args->ech->innerCount == 0) { + word32 encodedLen; + byte downgrade; + + /* ensure that a version less than TLS1.3 is never offered */ + downgrade = ssl->options.downgrade; + ssl->options.downgrade = 0; + /* set the type to inner */ args->ech->type = ECH_TYPE_INNER; args->preXLength = (int)args->length; - /* get size for inner */ - ret = TLSX_GetRequestSize(ssl, client_hello, &args->length); + /* get encoded inner size */ + args->ech->writeEncoded = 1; + encodedLen = args->length; + ret = TLSX_GetRequestSize(ssl, client_hello, &encodedLen); + args->ech->writeEncoded = 0; + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + ssl->options.downgrade = downgrade; + return ret; + } + /* innerClientHelloLen and padding are based on the + * encoded (sealed) inner */ + args->ech->paddingLen = 31 - ((encodedLen - 1) % 32); + args->ech->innerClientHelloLen = encodedLen + + args->ech->paddingLen + args->ech->hpke->Nt; + + /* get expanded inner size (used for transcript) */ + ret = TLSX_GetRequestSize(ssl, client_hello, &args->length); /* set the type to outer */ args->ech->type = ECH_TYPE_OUTER; + ssl->options.downgrade = downgrade; if (ret != 0) return ret; - /* set innerClientHelloLen to ClientHelloInner + padding + tag */ - args->ech->paddingLen = 31 - ((args->length - 1) % 32); - args->ech->innerClientHelloLen = args->length + - args->ech->paddingLen + args->ech->hpke->Nt; - if (args->ech->innerClientHelloLen > 0xFFFF) + /* args->expandedInnerLen carries the length for the hash */ + args->expandedInnerLen = args->length; + + if (args->ech->innerClientHelloLen > 0xFFFF || + args->expandedInnerLen > 0xFFFF) return BUFFER_E; + /* set the length back to before we computed ClientHelloInner size */ args->length = (word32)args->preXLength; } @@ -4928,10 +4949,18 @@ int SendTls13ClientHello(WOLFSSL* ssl) args->output[args->idx++] = NO_COMPRESSION; #if defined(HAVE_ECH) - /* write inner then outer */ + /* Build the expanded inner ClientHello */ if (ssl->echConfigs != NULL && !ssl->options.disableECH && (ssl->options.echAccepted || args->ech->innerCount == 0)) { byte downgrade; + + /* calculate maximum buffer size needed */ + word32 encodedBodyLen = args->ech->innerClientHelloLen - + args->ech->hpke->Nt; + word32 innerBufSize = args->expandedInnerLen; + if (encodedBodyLen > innerBufSize) + innerBufSize = encodedBodyLen; + /* set the type to inner */ args->ech->type = ECH_TYPE_INNER; /* innerClientHello may already exist from hrr, free if it does */ @@ -4941,21 +4970,16 @@ int SendTls13ClientHello(WOLFSSL* ssl) } /* allocate the inner */ args->ech->innerClientHello = - (byte*)XMALLOC(args->ech->innerClientHelloLen - args->ech->hpke->Nt, - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + (byte*)XMALLOC(innerBufSize, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (args->ech->innerClientHello == NULL) { args->ech->type = ECH_TYPE_OUTER; return MEMORY_E; } - /* set the padding bytes to 0 */ - XMEMSET(args->ech->innerClientHello + args->ech->innerClientHelloLen - - args->ech->hpke->Nt - args->ech->paddingLen, 0, - args->ech->paddingLen); - /* copy the client hello to the ech innerClientHello, exclude record */ - /* and handshake headers */ + /* copy everything before extensions into the innerClientHello + * ignore record and handshake headers */ XMEMCPY(args->ech->innerClientHello, args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, - args->idx - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ)); + args->preXLength); /* copy the client random to inner - only for first CH, not after HRR */ if (!ssl->options.echAccepted) { XMEMCPY(ssl->arrays->clientRandomInner, ssl->arrays->clientRandom, @@ -4976,17 +5000,46 @@ int SendTls13ClientHello(WOLFSSL* ssl) /* copy the new client random */ XMEMCPY(ssl->arrays->clientRandom, args->output + args->clientRandomOffset, RAN_LEN); - /* write the extensions for inner - * ensuring that a version less than TLS1.3 is never offered */ - args->length = 0; + + /* ensure that a version less than TLS1.3 is never offered */ downgrade = ssl->options.downgrade; ssl->options.downgrade = 0; - ret = TLSX_WriteRequest(ssl, args->ech->innerClientHello + - args->idx - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ), - client_hello, &args->length); - ssl->options.downgrade = downgrade; + + /* write the expanded extensions into the inner buffer */ + args->length = 0; + ret = TLSX_WriteRequest(ssl, + args->ech->innerClientHello + args->preXLength, client_hello, + &args->length); + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + ssl->options.downgrade = downgrade; + return ret; + } + + /* hash expanded form */ + args->ech->innerClientHelloLen = args->expandedInnerLen; + ret = EchHashHelloInner(ssl, args->ech); + args->ech->innerClientHelloLen = encodedBodyLen + args->ech->hpke->Nt; + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + ssl->options.downgrade = downgrade; + return ret; + } + + /* Rewrite inner buffer with the encoded form for sealing */ + XMEMSET(args->ech->innerClientHello + + args->ech->innerClientHelloLen - args->ech->hpke->Nt - + args->ech->paddingLen, 0, args->ech->paddingLen); + args->ech->writeEncoded = 1; + args->length = 0; + ret = TLSX_WriteRequest(ssl, + args->ech->innerClientHello + args->preXLength, client_hello, + &args->length); + args->ech->writeEncoded = 0; + /* set the type to outer */ args->ech->type = ECH_TYPE_OUTER; + ssl->options.downgrade = downgrade; if (ret != 0) return ret; } @@ -5002,9 +5055,9 @@ int SendTls13ClientHello(WOLFSSL* ssl) args->idx += args->length; #if defined(HAVE_ECH) - /* encrypt and pack the ech innerClientHello */ + /* HPKE-seal inner hello and place into outer ECH extension's payload */ if (ssl->echConfigs != NULL && !ssl->options.disableECH && - (ssl->options.echAccepted || args->ech->innerCount == 0)) { + (ssl->options.echAccepted || args->ech->innerCount == 0)) { #if defined(WOLFSSL_TEST_ECH) if (ssl->echInnerHelloCb != NULL) { ret = ssl->echInnerHelloCb(args->ech->innerClientHello, @@ -5019,6 +5072,9 @@ int SendTls13ClientHello(WOLFSSL* ssl) if (ret != 0) return ret; + + /* innerCount gates HRR re-prep and the server's copyRandom logic. */ + args->ech->innerCount = 1; } #endif @@ -5040,16 +5096,10 @@ int SendTls13ClientHello(WOLFSSL* ssl) else #endif /* WOLFSSL_DTLS13 */ { -#if defined(HAVE_ECH) - /* compute the inner hash */ - 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) - ret = HashOutput(ssl, args->output, (int)args->idx, 0); + /* compute the outer hash (the inner hash was fed into + * hsHashesEch earlier, before the inner buffer was overwritten + * with the encoded form for HPKE sealing) */ + ret = HashOutput(ssl, args->output, (int)args->idx, 0); } } if (ret != 0) @@ -7583,6 +7633,7 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, ret = EchHashHelloInner(ssl, (WOLFSSL_ECH*)echX->data); if (ret != 0) goto exit_dch; + ((WOLFSSL_ECH*)echX->data)->innerCount = 1; } #endif diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 256d3d76c2..27becf72a6 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3168,6 +3168,7 @@ typedef struct WOLFSSL_ECH { byte configId; byte enc[HPKE_Npk_MAX]; byte innerCount; + byte writeEncoded; } WOLFSSL_ECH; WOLFSSL_LOCAL int EchConfigGetSupportedCipherSuite(WOLFSSL_EchConfig* config);