diff --git a/.github/scripts/openssl-ech.sh b/.github/scripts/openssl-ech.sh index d3dde32b6ef..3b233f558d6 100644 --- a/.github/scripts/openssl-ech.sh +++ b/.github/scripts/openssl-ech.sh @@ -11,12 +11,13 @@ cleanup() { trap cleanup EXIT usage() { - echo "Usage: $0 [--suite ] [--workspace ]" + echo "Usage: $0 [--suite ] [--pqc ] [--workspace ]" exit 1 } MODE="" SUITE="" +PQC="" WORKSPACE=${GITHUB_WORKSPACE:-"."} @@ -40,6 +41,11 @@ while [ $# -gt 0 ]; do echo "Using suite: $SUITE" echo "" ;; + --pqc) + [ -z "$2" ] && { echo "ERROR: --pqc requires a value"; exit 1; } + PQC="$2" + shift 2 + ;; --workspace) [ -z "$2" ] && { echo "ERROR: --workspace requires a value"; exit 1; } WORKSPACE="$2" @@ -104,6 +110,7 @@ openssl_server(){ -p "$port" \ -S "$PRIV_NAME" \ --ech "$ech_config" \ + $PQC \ &>> "$TMP_LOG" rm -f "$ech_file" @@ -178,6 +185,9 @@ case "$MODE" in if [ -n "$SUITE" ]; then SUITE="-suite $SUITE" fi + if [ -n "$PQC" ]; then + PQC="--pqc $PQC" + fi openssl_server ;; client) diff --git a/.github/workflows/openssl-ech.yml b/.github/workflows/openssl-ech.yml index e295062c716..efe56927cb5 100644 --- a/.github/workflows/openssl-ech.yml +++ b/.github/workflows/openssl-ech.yml @@ -24,7 +24,8 @@ jobs: with: path: wolfssl configure: >- - --enable-ech --enable-sha512 --enable-aes CFLAGS='-DUSE_FLAT_TEST_H' + --enable-ech --enable-sha512 --enable-aes --enable-mlkem + CFLAGS='-DUSE_FLAT_TEST_H' install: true - name: tar build-dir @@ -140,14 +141,14 @@ jobs: # default suite (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, HPKE_AES_128_GCM) echo -e "\nTesting default suite with OpenSSL server and wolfSSL client\n" &>> "$LOG_FILE" - bash ./openssl-ech.sh server &>> "$LOG_FILE" + bash ./openssl-ech.sh server --pqc SecP384r1MLKEM1024 &>> "$LOG_FILE" echo -e "\nTesting default suite with OpenSSL client and wolfSSL server\n" &>> "$LOG_FILE" bash ./openssl-ech.sh client &>> "$LOG_FILE" # weird suite (DHKEM_P521_HKDF_SHA512, HKDF_SHA256, HPKE_AES_256_GCM) echo -e "\nTesting weird suite with OpenSSL server and wolfSSL client\n" &>> "$LOG_FILE" - bash ./openssl-ech.sh server --suite "18,1,2" &>> "$LOG_FILE" + bash ./openssl-ech.sh server --suite "18,1,2" --pqc SecP384r1MLKEM1024 &>> "$LOG_FILE" echo -e "\nTesting weird suite with OpenSSL client and wolfSSL server\n" &>> "$LOG_FILE" bash ./openssl-ech.sh client --suite "18,1,2" &>> "$LOG_FILE" diff --git a/src/internal.c b/src/internal.c index 70584590f1e..19b3bf70993 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8020,6 +8020,7 @@ int InitSSL(WOLFSSL* ssl, WOLFSSL_CTX* ctx, int writeDup) #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) ssl->options.disableECH = ctx->disableECH; + ssl->options.disableEchEncodeOE = ctx->disableEchEncodeOE; #endif /* default alert state (none) */ diff --git a/src/ssl_ech.c b/src/ssl_ech.c index 1f864bc15b4..2b2825ee733 100644 --- a/src/ssl_ech.c +++ b/src/ssl_ech.c @@ -244,6 +244,14 @@ void wolfSSL_CTX_SetEchEnable(WOLFSSL_CTX* ctx, byte enable) } } +/* set/unset OuterExtensions usage on the client-side + * OuterExtensions can dramatically decrease the size of the inner hello */ +void wolfSSL_CTX_SetEchEncodeOE(WOLFSSL_CTX* ctx, byte enable) +{ + if (ctx != NULL) + ctx->disableEchEncodeOE = !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, const char* echConfigs64, @@ -446,6 +454,14 @@ void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable) } } +/* set/unset OuterExtensions usage on the client-side + * OuterExtensions can dramatically decrease the size of the inner hello */ +void wolfSSL_SetEchEncodeOE(WOLFSSL* ssl, byte enable) +{ + if (ssl != NULL) + ssl->options.disableEchEncodeOE = !enable; +} + int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, const byte* echConfigs, word32 echConfigsLen) { diff --git a/src/tls.c b/src/tls.c index 0f6fd90f78e..b1bbddd5622 100644 --- a/src/tls.c +++ b/src/tls.c @@ -14237,6 +14237,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 + @@ -14245,7 +14248,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); @@ -16287,6 +16290,100 @@ 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_IsEncodable(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 void 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_IsEncodable(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 (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; +} + /* 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, @@ -16296,11 +16393,25 @@ 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 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) { + TLSX_ECH_BuildOuterExtensions(ssl, semaphore, msgType, + NULL, pLength, &count, semaphore); + } if (r == 0 && ssl->extensions) ret = TLSX_GetSize(ssl->extensions, semaphore, msgType, pLength); if (r == 0 && ret == 0 && ssl->ctx && ssl->ctx->extensions) @@ -16438,27 +16549,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); + + TLSX_ECH_BuildOuterExtensions(ssl, semaphore, msgType, + ech->writeEncoded ? output : NULL, + ech->writeEncoded ? pOffset : NULL, + &count, mask); + if (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 824ad08b696..d99e2e03d80 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3823,6 +3823,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. @@ -3850,11 +3851,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; @@ -3863,7 +3859,6 @@ static int EchHashHelloInner(WOLFSSL* ssl, WOLFSSL_ECH* ech) ret = InitHandshakeHashes(ssl); if (ret == 0) { ssl->hsHashesEch = ssl->hsHashes; - ech->innerCount = 1; } } @@ -4569,6 +4564,7 @@ typedef struct Sch13Args { #if defined(HAVE_ECH) int clientRandomOffset; int preXLength; + word32 expandedInnerLen; WOLFSSL_ECH* ech; #endif } Sch13Args; @@ -4770,25 +4766,53 @@ int SendTls13ClientHello(WOLFSSL* ssl) /* only prepare if we have a chance at acceptance */ if (ssl->options.echAccepted || args->ech->innerCount == 0) { + word32 encodedLen; + /* set the type to inner */ args->ech->type = ECH_TYPE_INNER; args->preXLength = (int)args->length; - /* get size for inner */ + /* get expanded inner size (used for transcript) */ ret = TLSX_GetRequestSize(ssl, client_hello, &args->length); + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + return ret; + } + + /* args->expandedInnerLen carries the length for the hash */ + args->expandedInnerLen = args->length; + + if (ssl->options.disableEchEncodeOE) { + /* compression off: the sealed form is just the expanded + * body, no OuterExtensions reference */ + encodedLen = args->expandedInnerLen; + } + else { + /* get encoded inner size */ + args->ech->writeEncoded = 1; + encodedLen = args->preXLength; + ret = TLSX_GetRequestSize(ssl, client_hello, &encodedLen); + args->ech->writeEncoded = 0; + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + return ret; + } + } /* set the type to outer */ args->ech->type = ECH_TYPE_OUTER; - if (ret != 0) - return ret; - /* set innerClientHelloLen to ClientHelloInner + padding + tag */ - args->ech->paddingLen = 31 - ((args->length - 1) % 32); - args->ech->innerClientHelloLen = args->length + + /* 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; - if (args->ech->innerClientHelloLen > 0xFFFF) + + if (args->expandedInnerLen > 0xFFFF || + args->ech->innerClientHelloLen > 0xFFFF) return BUFFER_E; - /* set the length back to before we computed ClientHelloInner size */ + + /* restore the length to pre-ClientHelloInner computations */ args->length = (word32)args->preXLength; } } @@ -4915,9 +4939,16 @@ 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)) { + (ssl->options.echAccepted || args->ech->innerCount == 0)) { + /* calculate maximum buffer size needed */ + word16 encodedBodyLen = (word16)(args->ech->innerClientHelloLen - + args->ech->hpke->Nt); + word16 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 */ @@ -4927,21 +4958,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, @@ -4962,15 +4988,44 @@ int SendTls13ClientHello(WOLFSSL* ssl) /* copy the new client random */ XMEMCPY(ssl->arrays->clientRandom, args->output + args->clientRandomOffset, RAN_LEN); - /* write the extensions for inner */ + /* write the expanded extensions into the inner buffer */ args->length = 0; - ret = TLSX_WriteRequest(ssl, args->ech->innerClientHello + args->idx - - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ), client_hello, + ret = TLSX_WriteRequest(ssl, + args->ech->innerClientHello + args->preXLength, client_hello, &args->length); + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + 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; + return ret; + } + + /* zero padding bytes sealed with the inner hello */ + XMEMSET(args->ech->innerClientHello + + args->ech->innerClientHelloLen - args->ech->hpke->Nt - + args->ech->paddingLen, 0, args->ech->paddingLen); + if (!ssl->options.disableEchEncodeOE) { + /* Rewrite inner buffer with the encoded form for sealing */ + args->ech->writeEncoded = 1; + args->length = 0; + ret = TLSX_WriteRequest(ssl, + args->ech->innerClientHello + args->preXLength, client_hello, + &args->length); + args->ech->writeEncoded = 0; + if (ret != 0) { + args->ech->type = ECH_TYPE_OUTER; + return ret; + } + } /* set the type to outer */ args->ech->type = ECH_TYPE_OUTER; - if (ret != 0) - return ret; } #endif @@ -4984,13 +5039,16 @@ 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)) { ret = TLSX_FinalizeEch(args->ech, args->output + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ, (word32)(args->sendSz - (RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ))); + /* innerCount gates HRR re-prep and the server's copyRandom logic. */ + args->ech->innerCount = 1; + if (ret != 0) return ret; } @@ -5014,16 +5072,8 @@ 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); + ret = HashOutput(ssl, args->output, (int)args->idx, 0); } } if (ret != 0) @@ -7500,6 +7550,7 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, !ssl->options.disableECH && ((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) { ret = EchHashHelloInner(ssl, (WOLFSSL_ECH*)echX->data); + ((WOLFSSL_ECH*)echX->data)->innerCount = 1; if (ret != 0) goto exit_dch; } diff --git a/tests/api.c b/tests/api.c index 4c15bfccc23..8760e665eef 100644 --- a/tests/api.c +++ b/tests/api.c @@ -15266,6 +15266,62 @@ static int test_wolfSSL_Tls13_ECH_long_SNI(void) return EXPECT_RESULT(); } + +/* Client ctx_ready callback: disable OuterExtensions encoding at CTX level */ +static int test_ech_client_ctx_ready_disable_oe(WOLFSSL_CTX* ctx) +{ + wolfSSL_CTX_SetEchEncodeOE(ctx, 0); + if (ctx->disableEchEncodeOE != 1) + return TEST_FAIL; + return TEST_SUCCESS; +} + +/* check that OuterExtensions encoding for ECH can be disabled */ +static int test_wolfSSL_Tls13_ECH_outer_extensions(void) +{ + EXPECT_DECLS; + test_ssl_memio_ctx test_ctx; + + /* test CTX-level disable */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + test_ctx.s_cb.method = wolfTLSv1_3_server_method; + test_ctx.c_cb.method = wolfTLSv1_3_client_method; + + test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready; + test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready; + test_ctx.c_cb.ctx_ready = test_ech_client_ctx_ready_disable_oe; + test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.disableEchEncodeOE, 1); + + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + + test_ssl_memio_cleanup(&test_ctx); + + /* test SSL-level disable */ + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + test_ctx.s_cb.method = wolfTLSv1_3_server_method; + test_ctx.c_cb.method = wolfTLSv1_3_client_method; + + test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready; + test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready; + test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(test_ctx.c_ssl->options.disableEchEncodeOE, 0); + + wolfSSL_SetEchEncodeOE(test_ctx.c_ssl, 0); + ExpectIntEQ(test_ctx.c_ssl->options.disableEchEncodeOE, 1); + + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); + + test_ssl_memio_cleanup(&test_ctx); + + return EXPECT_RESULT(); +} #endif /* HAVE_SSL_MEMIO_TESTS_DEPENDENCIES */ /* verify that ECH can be enabled/disabled without issue */ @@ -36682,6 +36738,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE), TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn), TEST_DECL(test_wolfSSL_Tls13_ECH_long_SNI), + TEST_DECL(test_wolfSSL_Tls13_ECH_outer_extensions), #endif TEST_DECL(test_wolfSSL_Tls13_ECH_enable_disable), #endif /* WOLFSSL_TLS13 && HAVE_ECH */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 9e89e64b0b3..1218ce2ecef 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3152,6 +3152,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); @@ -3983,6 +3984,7 @@ struct WOLFSSL_CTX { #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) byte disableECH:1; + byte disableEchEncodeOE:1; #endif word16 minProto:1; /* sets min to min available */ word16 maxProto:1; /* sets max to max available */ @@ -5182,6 +5184,7 @@ struct Options { #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) word16 echAccepted:1; byte disableECH:1; /* Did the user disable ech */ + word16 disableEchEncodeOE:1; /* OuterExtensions encoding */ #endif #ifdef WOLFSSL_SEND_HRR_COOKIE word16 cookieGood:1; diff --git a/wolfssl/ssl.h b/wolfssl/ssl.h index 236515157b4..25a673ee8db 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1230,6 +1230,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 void wolfSSL_CTX_SetEchEncodeOE(WOLFSSL_CTX* ctx, byte enable); + WOLFSSL_API int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, const char* echConfigs64, word32 echConfigs64Len); @@ -1240,6 +1242,8 @@ WOLFSSL_API int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* echConfigs, word32* echConfigsLen); WOLFSSL_API void wolfSSL_SetEchEnable(WOLFSSL* ssl, byte enable); + +WOLFSSL_API void wolfSSL_SetEchEncodeOE(WOLFSSL* ssl, byte enable); #endif /* WOLFSSL_TLS13 && HAVE_ECH */ #ifdef HAVE_POLY1305