diff --git a/.github/workflows/openssl-ech.yml b/.github/workflows/openssl-ech.yml new file mode 100644 index 0000000000..83a73ee55d --- /dev/null +++ b/.github/workflows/openssl-ech.yml @@ -0,0 +1,312 @@ +name: OpenSSL ECH Interop Test + +# START OF COMMON SECTION +on: + push: + branches: [ 'master', 'main', 'release/**' ] + pull_request: + branches: [ '*' ] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +# END OF COMMON SECTION + +jobs: + build_wolfssl: + name: Build wolfSSL + if: github.repository_owner == 'wolfssl' + runs-on: ubuntu-24.04 + timeout-minutes: 4 + steps: + - name: Build wolfSSL + uses: wolfSSL/actions-build-autotools-project@v1 + with: + path: wolfssl + configure: --enable-ech CFLAGS='-DUSE_FLAT_TEST_H' + install: true + + - name: tar build-dir + run: | + # need server.h and client.h which are not installed normally + cp "$GITHUB_WORKSPACE/wolfssl/examples/server/server.h" \ + build-dir/share/doc/wolfssl/example/server.h + cp "$GITHUB_WORKSPACE/wolfssl/examples/client/client.h" \ + build-dir/share/doc/wolfssl/example/client.h + + # need certs so 'wolfSSL error: wolf root not found' does not show up + cp -r "$GITHUB_WORKSPACE/wolfssl/certs" build-dir/certs + tar -zcf build-dir.tgz build-dir + + - name: Upload built wolfSSL + uses: actions/upload-artifact@v4 + with: + name: wolf-install-openssl-ech + path: build-dir.tgz + retention-days: 5 + + build_openssl_ech: + name: Build OpenSSL (feature/ech) + if: github.repository_owner == 'wolfssl' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Checkout OpenSSL feature/ech branch + uses: actions/checkout@v4 + with: + repository: openssl/openssl + ref: feature/ech + path: openssl + + - name: Build OpenSSL + working-directory: openssl + run: | + ./Configure --prefix=$GITHUB_WORKSPACE/openssl-install \ + --openssldir=$GITHUB_WORKSPACE/openssl-install/ssl \ + enable-ech no-docs + make -j$(nproc) + make install_sw + + - name: tar openssl-install + run: tar -zcf openssl-install.tgz openssl-install + + - name: Upload built OpenSSL + uses: actions/upload-artifact@v4 + with: + name: openssl-ech-install + path: openssl-install.tgz + retention-days: 5 + + ech_server_interop_test: + name: ECH Server Interop Test + if: github.repository_owner == 'wolfssl' + needs: [build_wolfssl, build_openssl_ech] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Download wolfSSL build + uses: actions/download-artifact@v4 + with: + name: wolf-install-openssl-ech + + - name: Download OpenSSL build + uses: actions/download-artifact@v4 + with: + name: openssl-ech-install + + - name: Extract builds + run: | + tar -xzf build-dir.tgz + tar -xzf openssl-install.tgz + + - name: Build wolfssl server example + run: | + export WOLFSSL_INSTALL_DIR="$GITHUB_WORKSPACE/build-dir" + export WOLFSSL_BIN_DIR="$WOLFSSL_INSTALL_DIR/bin" + export CFLAGS="-Wall -I$WOLFSSL_INSTALL_DIR/include" + export LIBS="-L$WOLFSSL_INSTALL_DIR/lib -lm -lwolfssl" + export LD_LIBRARY_PATH="$WOLFSSL_INSTALL_DIR/lib/:$LD_LIBRARY_PATH" + + gcc -o "$WOLFSSL_BIN_DIR/server" \ + "$WOLFSSL_INSTALL_DIR/share/doc/wolfssl/example/server.c" \ + $CFLAGS $LIBS -I"$WOLFSSL_INSTALL_DIR/share/doc/wolfssl/example" + + - name: ECH interop - wolfSSL server, OpenSSL client + run: | + set -e + + export LD_LIBRARY_PATH="$GITHUB_WORKSPACE/openssl-install/lib64:$GITHUB_WORKSPACE/openssl-install/lib:$GITHUB_WORKSPACE/build-dir/lib:$LD_LIBRARY_PATH" + + OPENSSL=$GITHUB_WORKSPACE/openssl-install/bin/openssl + WOLFSSL_SERVER=$GITHUB_WORKSPACE/build-dir/bin/server + + CERT_DIR="$GITHUB_WORKSPACE/build-dir/certs" + READY_FILE="$GITHUB_WORKSPACE/wolfssl_tls13_ready$$" + LOG_FILE="$GITHUB_WORKSPACE/log_file.log" + PRIV_NAME="ech-private-name.com" + PUB_NAME="ech-public-name.com" + ECH_CONFIG="" + PORT=0 + + rm -f "$READY_FILE" + + # need to cd into build-dir so the certs/ dir is available for server + cd build-dir + + $OPENSSL version | tee "$LOG_FILE" + + # start server with ephemeral port + ready file + # also set server to be line buffered so the log can be grepped + stdbuf -oL $WOLFSSL_SERVER \ + -v 4 \ + -R "$READY_FILE" \ + -p "$PORT" \ + -S "$PRIV_NAME" \ + --ech "$PUB_NAME" \ + &>> "$LOG_FILE" & + + # wait for server to be ready, then get port + counter=0 + while [ ! -s "$READY_FILE" ]; do + sleep 0.1 + counter=$((counter + 1)) + if [ "$counter" -gt 50 ]; then + echo "ERROR: no ready file" &>> "$LOG_FILE" + exit 1 + fi + done + PORT="$(cat "$READY_FILE")" + echo "parsed port: $PORT" &>> "$LOG_FILE" + + # get ECH config from server + counter=0 + while [ -z "$ECH_CONFIG" ]; do + ECH_CONFIG=$(grep -m1 "ECH config (base64): " "$LOG_FILE" \ + 2>/dev/null | sed 's/ECH config (base64): //g') + sleep 0.1 + counter=$((counter + 1)) + if [ "$counter" -gt 50 ]; then + echo "ERROR: no ECH configs" &>> "$LOG_FILE" + exit 1 + fi + done + echo "parsed ech config: $ECH_CONFIG" &>> "$LOG_FILE" + + # Test with OpenSSL s_client using ECH + echo "wolfssl" | $OPENSSL s_client \ + -tls1_3 \ + -connect "localhost:$PORT" \ + -cert "$CERT_DIR/client-cert.pem" \ + -key "$CERT_DIR/client-key.pem" \ + -CAfile "$CERT_DIR/ca-cert.pem" \ + -servername "$PRIV_NAME" \ + -ech_config_list "$ECH_CONFIG" \ + &>> "$LOG_FILE" + + grep "ECH: success: 1" "$LOG_FILE" + + # cleanup + rm -f "$READY_FILE" + rm -f "$LOG_FILE" + + - name: Print debug info on failure + if: ${{ failure() }} + run: | + if [ -s "$GITHUB_WORKSPACE/log_file.log" ]; then + cat "$GITHUB_WORKSPACE/log_file.log" + else + echo "No log file" + fi + + ech_client_interop_test: + name: ECH Client Interop Test + if: github.repository_owner == 'wolfssl' + needs: [build_wolfssl, build_openssl_ech] + runs-on: ubuntu-24.04 + timeout-minutes: 10 + steps: + - name: Download wolfSSL build + uses: actions/download-artifact@v4 + with: + name: wolf-install-openssl-ech + + - name: Download OpenSSL build + uses: actions/download-artifact@v4 + with: + name: openssl-ech-install + + - name: Extract builds + run: | + tar -xzf build-dir.tgz + tar -xzf openssl-install.tgz + + - name: Build wolfssl client example + run: | + export WOLFSSL_INSTALL_DIR="$GITHUB_WORKSPACE/build-dir" + export WOLFSSL_BIN_DIR="$WOLFSSL_INSTALL_DIR/bin" + export CFLAGS="-Wall -I$WOLFSSL_INSTALL_DIR/include" + export LIBS="-L$WOLFSSL_INSTALL_DIR/lib -lm -lwolfssl" + export LD_LIBRARY_PATH="$WOLFSSL_INSTALL_DIR/lib/:$LD_LIBRARY_PATH" + + gcc -o "$WOLFSSL_BIN_DIR/client" \ + "$WOLFSSL_INSTALL_DIR/share/doc/wolfssl/example/client.c" \ + $CFLAGS $LIBS -I"$WOLFSSL_INSTALL_DIR/share/doc/wolfssl/example" + + - name: ECH interop - wolfSSL client, OpenSSL server + run: | + set -e + + export LD_LIBRARY_PATH="$GITHUB_WORKSPACE/openssl-install/lib64:$GITHUB_WORKSPACE/openssl-install/lib:$GITHUB_WORKSPACE/build-dir/lib:$LD_LIBRARY_PATH" + + OPENSSL=$GITHUB_WORKSPACE/openssl-install/bin/openssl + WOLFSSL_CLIENT=$GITHUB_WORKSPACE/build-dir/bin/client + + CERT_DIR="$GITHUB_WORKSPACE/build-dir/certs" + LOG_FILE="$GITHUB_WORKSPACE/log_file.log" + ECH_FILE="$GITHUB_WORKSPACE/ech_config.pem" + PRIV_NAME="ech-private-name.com" + PUB_NAME="ech-public-name.com" + PORT="" + ECH_CONFIG="" + + rm -f "$ECH_FILE" + + # need to cd into build-dir so the certs/ dir is available for client + cd build-dir + + $OPENSSL version | tee "$LOG_FILE" + + $OPENSSL ech -public_name "$PUB_NAME" -out "$ECH_FILE" &>> "$LOG_FILE" + + # parse ECH config from file + ECH_CONFIG=$(sed -n '/BEGIN ECHCONFIG/,/END ECHCONFIG/{/BEGIN ECHCONFIG\|END ECHCONFIG/d;p}' "$ECH_FILE" | tr -d '\n') + echo "parsed ech config: $ECH_CONFIG" &>> "$LOG_FILE" + + # start OpenSSL ECH server with ephemeral port and make sure it is + # line-buffered + stdbuf -oL $OPENSSL s_server \ + -tls1_3 \ + -cert "$CERT_DIR/server-cert.pem" \ + -key "$CERT_DIR/server-key.pem" \ + -cert2 "$CERT_DIR/server-cert.pem" \ + -key2 "$CERT_DIR/server-key.pem" \ + -ech_key "$ECH_FILE" \ + -servername "$PRIV_NAME" \ + -accept 0 \ + -naccept 1 \ + &>> "$LOG_FILE" <<< "wolfssl!" & + + # wait for server port to be ready and capture it + counter=0 + while [ -z "$PORT" ]; do + PORT=$(grep -m1 "ACCEPT" "$LOG_FILE" | sed 's/.*:\([0-9]*\)$/\1/') + sleep 0.1 + counter=$((counter + 1)) + if [ "$counter" -gt 50 ]; then + echo "ERROR: server port not found" &>> "$LOG_FILE" + exit 1 + fi + done + echo "parsed port: $PORT" &>> "$LOG_FILE" + + # test with wolfssl client + $WOLFSSL_CLIENT -v 4 \ + -p "$PORT" \ + -S "$PRIV_NAME" \ + --ech "$ECH_CONFIG" \ + &>> "$LOG_FILE" + + grep "ech_success=1" "$LOG_FILE" + + # cleanup + rm -f "$LOG_FILE" + rm -f "$ECH_FILE" + + - name: Print debug info on failure + if: ${{ failure() }} + run: | + if [ -s "$GITHUB_WORKSPACE/log_file.log" ]; then + cat "$GITHUB_WORKSPACE/log_file.log" + else + echo "No log file" + fi diff --git a/examples/client/client.c b/examples/client/client.c index cbd651de35..5730744c08 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -52,10 +52,14 @@ static const char *wolfsentry_config_path = NULL; #endif #include - -#include #include +#ifdef USE_FLAT_TEST_H + #include "client.h" +#else + #include "examples/client/client.h" +#endif + #if !defined(NO_WOLFSSL_CLIENT) && !defined(NO_TLS) @@ -1171,7 +1175,7 @@ static int ClientWriteRead(WOLFSSL* ssl, const char* msg, int msgSz, /* 4. add the same message into Japanese section */ /* (will be translated later) */ /* 5. add printf() into suitable position of Usage() */ -static const char* client_usage_msg[][79] = { +static const char* client_usage_msg[][80] = { /* English */ { " NOTE: All files relative to wolfSSL home dir\n", /* 0 */ @@ -1425,10 +1429,15 @@ static const char* client_usage_msg[][79] = { #endif #ifdef HAVE_ECC_BRAINPOOL "--bpKs Use Brainpool ECC group for key share\n", /* 77 */ +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + "--ech Use Encrypted Client Hello with base64 encoded " + "ECH configs\n", + /* 78 */ #endif "\n" "For simpler wolfSSL TLS client examples, visit\n" - "https://github.com/wolfSSL/wolfssl-examples/tree/master/tls\n", /* 78 */ + "https://github.com/wolfSSL/wolfssl-examples/tree/master/tls\n", /* 79 */ NULL, }, #ifndef NO_MULTIBYTE_PRINT @@ -1931,6 +1940,9 @@ static void Usage(void) #endif #ifdef HAVE_ECC_BRAINPOOL printf("%s", msg[++msgid]); /* --bpKs */ +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + printf("%s", msg[++msgid]); /* --ech */ #endif printf("%s", msg[++msgid]); /* --files-are-der */ printf("%s", msg[++msgid]); /* Documentation Hint */ @@ -2119,6 +2131,9 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) #endif /* WOLFSSL_SYS_CRYPTO_POLICY */ #ifdef HAVE_ECC_BRAINPOOL { "bpKs", 0, 270 }, +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + { "ech", 1, 271 }, #endif { 0, 0, 0 } }; @@ -2187,6 +2202,9 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) #ifdef HAVE_SNI char* sniHostName = NULL; #endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + char* echConfigs64 = NULL; +#endif #ifdef HAVE_TRUSTED_CA int trustedCaKeyId = 0; #endif @@ -3013,6 +3031,11 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) #endif break; #endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + case 271: + echConfigs64 = myoptarg; + break; +#endif default: Usage(); @@ -3878,6 +3901,16 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) err_sys("unable to get SSL object"); } +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + if (echConfigs64 != NULL) { + if (wolfSSL_SetEchConfigsBase64(ssl, echConfigs64, + (word32)XSTRLEN(echConfigs64)) != WOLFSSL_SUCCESS) { + wolfSSL_CTX_free(ctx); ctx = NULL; + err_sys("SetEchConfigsBase64 failed"); + } + } +#endif + #ifdef WOLFSSL_DUAL_ALG_CERTS if (!wolfSSL_UseCKS(ssl, cks_order, sizeof(cks_order))) { wolfSSL_CTX_free(ctx); ctx = NULL; diff --git a/examples/server/server.c b/examples/server/server.c index 21f5380e73..48198d33c9 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -48,6 +48,10 @@ #include /* wc_ecc_fp_free */ #endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + #include +#endif + #ifdef WOLFSSL_WOLFSENTRY_HOOKS #include #if !defined(NO_FILESYSTEM) && !defined(WOLFSENTRY_NO_JSON) @@ -73,7 +77,11 @@ static const char *wolfsentry_config_path = NULL; #include #include -#include "examples/server/server.h" +#ifdef USE_FLAT_TEST_H + #include "server.h" +#else + #include "examples/server/server.h" +#endif #if !defined(NO_WOLFSSL_SERVER) && !defined(NO_TLS) @@ -911,7 +919,7 @@ static void SetKeyShare(WOLFSSL* ssl, int onlyKeyShare, int useX25519, /* 4. add the same message into Japanese section */ /* (will be translated later) */ /* 5. add printf() into suitable position of Usage() */ -static const char* server_usage_msg[][66] = { +static const char* server_usage_msg[][69] = { /* English */ { " NOTE: All files relative to wolfSSL home dir\n", /* 0 */ @@ -1107,11 +1115,16 @@ static const char* server_usage_msg[][66] = { #endif #ifdef WOLFSSL_SYS_CRYPTO_POLICY "--crypto-policy \n", /* 66 */ +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + "--ech Generate Encrypted Client Hello config with " + "public name \n", + /* 67 */ #endif "\n" "For simpler wolfSSL TLS server examples, visit\n" "https://github.com/wolfSSL/wolfssl-examples/tree/master/tls\n", - /* 67 */ + /* 68 */ NULL, }, #ifndef NO_MULTIBYTE_PRINT @@ -1486,6 +1499,9 @@ static void Usage(void) #endif #ifdef WOLFSSL_DUAL_ALG_CERTS printf("%s", msg[++msgId]); /* --altPrivKey */ +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + printf("%s", msg[++msgId]); /* --ech */ #endif printf("%s", msg[++msgId]); /* Examples repo link */ } @@ -1609,6 +1625,9 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #if defined(WOLFSSL_SYS_CRYPTO_POLICY) { "crypto-policy", 1, 268 }, #endif /* WOLFSSL_SYS_CRYPTO_POLICY */ +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + { "ech", 1, 269 }, +#endif { 0, 0, 0 } }; #endif @@ -1685,6 +1704,9 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #ifdef HAVE_SNI char* sniHostName = NULL; #endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + char* echPublicName = NULL; +#endif #ifdef HAVE_TRUSTED_CA int trustedCaKeyId = 0; @@ -2513,6 +2535,11 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) policy = myoptarg; #endif /* WOLFSSL_SYS_CRYPTO_POLICY */ break; +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + case 269: + echPublicName = myoptarg; + break; +#endif case -1: default: @@ -3073,6 +3100,37 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) err_sys_ex(runWithErrors, "UseSNI failed"); #endif +#ifdef HAVE_ECH + if (echPublicName != NULL) { + byte echConfig[512]; + word32 echConfigLen = sizeof(echConfig); + char echConfigBase64[512]; + char* echConfigBase64Ptr; + word32 echConfigBase64Len = sizeof(echConfigBase64); + + if (wolfSSL_CTX_GenerateEchConfig(ctx, echPublicName, 0, 0, 0) + != WOLFSSL_SUCCESS) { + err_sys_ex(runWithErrors, "GenerateEchConfig failed"); + } + if (wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen) + != WOLFSSL_SUCCESS) { + err_sys_ex(runWithErrors, "GetEchConfigs failed"); + } + if (Base64_Encode_NoNl(echConfig, echConfigLen, (byte*)echConfigBase64, + &echConfigBase64Len) != 0) { + err_sys_ex(runWithErrors, "Base64_Encode_NoNl failed"); + } + else { + echConfigBase64Ptr = echConfigBase64; + printf("ECH config (base64): "); + while (echConfigBase64Len-- > 0) { + printf("%c", *echConfigBase64Ptr++); + } + printf("\n"); + } + } +#endif + #ifdef USE_WINDOWS_API if (port == 0) { /* Generate random port for testing */ diff --git a/src/internal.c b/src/internal.c index 81df695b7a..fd0a1622a7 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 717b709274..6eccc3f871 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; @@ -67,7 +66,7 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, else XMEMSET(newConfig, 0, sizeof(WOLFSSL_EchConfig)); - /* set random config id */ + /* set random configId */ if (ret == 0) ret = wc_RNG_GenerateByte(rng, &newConfig->configId); @@ -147,17 +146,14 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, } } else { - parentConfig = ctx->echConfigs; - - if (parentConfig == NULL) { + /* insert new configs at beginning of LL as preference should be given + * to the most recently generated configs */ + if (ctx->echConfigs == NULL) { ctx->echConfigs = newConfig; } else { - while (parentConfig->next != NULL) { - parentConfig = parentConfig->next; - } - - parentConfig->next = newConfig; + newConfig->next = ctx->echConfigs; + ctx->echConfigs = newConfig; } } @@ -250,7 +246,7 @@ void wolfSSL_CTX_SetEchEnable(WOLFSSL_CTX* ctx, byte enable) /* set the ech config from base64 for our client ssl object, base64 is the * format ech configs are sent using dns records */ -int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, +int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, const char* echConfigs64, word32 echConfigs64Len) { int ret = 0; @@ -261,7 +257,7 @@ int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, return BAD_FUNC_ARG; /* already have ech configs */ - if (ssl->options.useEch == 1) { + if (ssl->echConfigs != NULL) { return WOLFSSL_FATAL_ERROR; } @@ -274,7 +270,7 @@ int wolfSSL_SetEchConfigsBase64(WOLFSSL* ssl, char* echConfigs64, decodedConfigs[decodedLen - 1] = 0; /* decode the echConfigs */ - ret = Base64_Decode((byte*)echConfigs64, echConfigs64Len, + ret = Base64_Decode((const byte*)echConfigs64, echConfigs64Len, decodedConfigs, &decodedLen); if (ret != 0) { @@ -300,7 +296,7 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, return BAD_FUNC_ARG; /* already have ech configs */ - if (ssl->options.useEch == 1) { + if (ssl->echConfigs != NULL) { return WOLFSSL_FATAL_ERROR; } @@ -309,7 +305,6 @@ int wolfSSL_SetEchConfigs(WOLFSSL* ssl, const byte* echConfigs, /* if we found valid configs */ if (ret == 0) { - ssl->options.useEch = 1; return WOLFSSL_SUCCESS; } @@ -472,7 +467,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; } @@ -495,68 +490,61 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, const byte* echConfigs, word32 echConfigsLen) { int ret = 0; - int i; + word32 configIdx; + word32 idx; int j; word16 totalLength; word16 version; word16 length; word16 hpkePubkeyLen; word16 cipherSuitesLen; - word16 publicNameLen; + word16 extensionsLen; + byte publicNameLen; WOLFSSL_EchConfig* configList = NULL; WOLFSSL_EchConfig* workingConfig = NULL; WOLFSSL_EchConfig* lastConfig = NULL; - byte* echConfig = NULL; + const byte* echConfig = NULL; - if (outputConfigs == NULL || echConfigs == NULL || echConfigsLen == 0) + if (outputConfigs == NULL || echConfigs == NULL || echConfigsLen < 2) return BAD_FUNC_ARG; /* check that the total length is well formed */ ato16(echConfigs, &totalLength); - if (totalLength != echConfigsLen - 2) { return WOLFSSL_FATAL_ERROR; } - - /* skip the total length uint16_t */ - i = 2; + configIdx = 2; do { - echConfig = (byte*)echConfigs + i; + if (configIdx + 4 > echConfigsLen) { + ret = BUFFER_E; + break; + } + echConfig = echConfigs + configIdx; ato16(echConfig, &version); ato16(echConfig + 2, &length); - /* if the version does not match */ - if (version != TLSX_ECH) { - /* we hit the end of the configs */ - if ( (word32)i + 2 >= echConfigsLen ) { - break; - } - - /* skip this config, +4 for version and length */ - i += length + 4; - continue; - } - - /* check if the length will overrun the buffer */ - if ((word32)i + length + 4 > echConfigsLen) { + if (configIdx + length + 4 > echConfigsLen) { + ret = BUFFER_E; break; } + else if (version != TLSX_ECH) { + /* skip this config and try the next one */ + configIdx += length + 4; + continue; + } if (workingConfig == NULL) { workingConfig = (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), heap, - DYNAMIC_TYPE_TMP_BUFFER); + DYNAMIC_TYPE_TMP_BUFFER); configList = workingConfig; - if (workingConfig != NULL) { - workingConfig->next = NULL; - } } else { lastConfig = workingConfig; workingConfig->next = - (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), - heap, DYNAMIC_TYPE_TMP_BUFFER); + (WOLFSSL_EchConfig*)XMALLOC(sizeof(WOLFSSL_EchConfig), heap, + DYNAMIC_TYPE_TMP_BUFFER); workingConfig = workingConfig->next; } @@ -571,8 +559,8 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, workingConfig->rawLen = length + 4; /* raw body */ - workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen, - heap, DYNAMIC_TYPE_TMP_BUFFER); + workingConfig->raw = (byte*)XMALLOC(workingConfig->rawLen, heap, + DYNAMIC_TYPE_TMP_BUFFER); if (workingConfig->raw == NULL) { ret = MEMORY_E; break; @@ -583,8 +571,14 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, /* skip over version and length */ echConfig += 4; + idx = 5; + if (idx >= length) { + ret = BUFFER_E; + break; + } + /* configId, 1 byte */ - workingConfig->configId = *(echConfig); + workingConfig->configId = *echConfig; echConfig++; /* kemId, 2 bytes */ ato16(echConfig, &workingConfig->kemId); @@ -592,15 +586,41 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, /* hpke public_key length, 2 bytes */ ato16(echConfig, &hpkePubkeyLen); echConfig += 2; + /* hpke public_key */ - if (hpkePubkeyLen > HPKE_Npk_MAX) { + if (hpkePubkeyLen > HPKE_Npk_MAX || hpkePubkeyLen == 0) { ret = BUFFER_E; break; } + idx += hpkePubkeyLen; + if (idx >= length) { + ret = BUFFER_E; + break; + } + XMEMCPY(workingConfig->receiverPubkey, echConfig, hpkePubkeyLen); echConfig += hpkePubkeyLen; + /* cipherSuitesLen */ + idx += 2; + if (idx >= length) { + ret = BUFFER_E; + break; + } ato16(echConfig, &cipherSuitesLen); + if (cipherSuitesLen == 0 || cipherSuitesLen % 4 != 0 || + cipherSuitesLen >= 1024) { + /* numCipherSuites is a byte so only 256 ciphersuites (each 4 bytes) + * can be accessed */ + ret = BUFFER_E; + break; + } + + idx += cipherSuitesLen; + if (idx >= length) { + ret = BUFFER_E; + break; + } workingConfig->cipherSuites = (EchCipherSuite*)XMALLOC(cipherSuitesLen, heap, DYNAMIC_TYPE_TMP_BUFFER); @@ -610,32 +630,68 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, } echConfig += 2; - workingConfig->numCipherSuites = cipherSuitesLen / 4; + workingConfig->numCipherSuites = (byte)(cipherSuitesLen / 4); /* cipherSuites */ for (j = 0; j < workingConfig->numCipherSuites; j++) { - ato16(echConfig + j * 4, &workingConfig->cipherSuites[j].kdfId); - ato16(echConfig + j * 4 + 2, - &workingConfig->cipherSuites[j].aeadId); + ato16(echConfig, &workingConfig->cipherSuites[j].kdfId); + ato16(echConfig + 2, &workingConfig->cipherSuites[j].aeadId); + echConfig += 4; } - echConfig += cipherSuitesLen; + /* ignore the maximum name length */ + idx++; + if (idx >= length) { + ret = BUFFER_E; + break; + } echConfig++; + /* publicNameLen */ - publicNameLen = *(echConfig); + idx++; + if (idx >= length) { + ret = BUFFER_E; + break; + } + + publicNameLen = *echConfig; + if (publicNameLen == 0) { + ret = BUFFER_E; + break; + } + + idx += publicNameLen; + if (idx >= length) { + ret = BUFFER_E; + break; + } + echConfig++; + workingConfig->publicName = (char*)XMALLOC(publicNameLen + 1, heap, DYNAMIC_TYPE_TMP_BUFFER); if (workingConfig->publicName == NULL) { ret = MEMORY_E; break; } - echConfig++; + /* publicName */ XMEMCPY(workingConfig->publicName, echConfig, publicNameLen); - /* null terminated */ - workingConfig->publicName[publicNameLen] = 0; + workingConfig->publicName[publicNameLen] = '\0'; + echConfig += publicNameLen; + + /* TODO: Parse ECHConfigExtension */ + /* --> for now just ignore it */ + idx += 2; + if (idx > length) { + ret = BUFFER_E; + break; + } + ato16(echConfig, &extensionsLen); - /* add length to go to next config, +4 for version and length */ - i += length + 4; + idx += extensionsLen; + if (idx != length) { + ret = BUFFER_E; + break; + } /* check that we support this config */ for (j = 0; j < HPKE_SUPPORTED_KEM_LEN; j++) { @@ -643,18 +699,31 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, break; } - /* if we don't support the kem or at least one cipher suite */ + /* KEM or ciphersuite not supported, free this config and then try to + * parse another */ if (j >= HPKE_SUPPORTED_KEM_LEN || - EchConfigGetSupportedCipherSuite(workingConfig) < 0) - { - XFREE(workingConfig->cipherSuites, heap, - DYNAMIC_TYPE_TMP_BUFFER); - XFREE(workingConfig->publicName, heap, - DYNAMIC_TYPE_TMP_BUFFER); + EchConfigGetSupportedCipherSuite(workingConfig) < 0) { + XFREE(workingConfig->cipherSuites, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(workingConfig->publicName, heap, DYNAMIC_TYPE_TMP_BUFFER); XFREE(workingConfig->raw, heap, DYNAMIC_TYPE_TMP_BUFFER); + XFREE(workingConfig, heap, DYNAMIC_TYPE_TMP_BUFFER); workingConfig = lastConfig; + + if (workingConfig != NULL) { + workingConfig->next = NULL; + } + else { + /* if one (or more) of the leading configs are unsupported then + * this case will be hit */ + configList = NULL; + } } - } while ((word32)i < echConfigsLen); + + configIdx += 4 + length; + } while (configIdx < echConfigsLen); + if (ret == 0 && configIdx != echConfigsLen){ + ret = BUFFER_E; + } /* if we found valid configs */ if (ret == 0 && configList != NULL) { @@ -664,7 +733,6 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, } workingConfig = configList; - while (workingConfig != NULL) { lastConfig = workingConfig; workingConfig = workingConfig->next; @@ -672,7 +740,6 @@ int SetEchConfigsEx(WOLFSSL_EchConfig** outputConfigs, void* heap, XFREE(lastConfig->cipherSuites, heap, DYNAMIC_TYPE_TMP_BUFFER); XFREE(lastConfig->publicName, heap, DYNAMIC_TYPE_TMP_BUFFER); XFREE(lastConfig->raw, heap, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(lastConfig, heap, DYNAMIC_TYPE_TMP_BUFFER); } diff --git a/src/tls.c b/src/tls.c index 5ddb1ca40c..20a53cc317 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2249,9 +2249,10 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte type; byte matched; #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + TLSX* echX = NULL; WOLFSSL_ECH* ech = NULL; WOLFSSL_EchConfig* workingConfig; - TLSX* echX; + word16 privateNameLen; #endif #endif /* !NO_WOLFSSL_SERVER */ TLSX *extension = TLSX_Find(ssl->extensions, TLSX_SERVER_NAME); @@ -2283,7 +2284,22 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, } #ifndef NO_WOLFSSL_SERVER +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + if (!ssl->options.disableECH) { + echX = TLSX_Find(ssl->extensions, TLSX_ECH); + if (echX != NULL) { + ech = (WOLFSSL_ECH*)(echX->data); + } + } +#endif + +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + if ((!extension || !extension->data) || + (ech != NULL && ech->sniState == ECH_INNER_SNI && + ech->privateName == NULL)) { +#else if (!extension || !extension->data) { +#endif /* This will keep SNI even though TLSX_UseSNI has not been called. * Enable it so that the received sni is available to functions * that use a custom callback when SNI is received. @@ -2331,24 +2347,53 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, if (!cacheOnly && !(sni = TLSX_SNI_Find((SNI*)extension->data, type))) return 0; /* not using this type of SNI. */ -#ifdef WOLFSSL_TLS13 +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + if (ech != NULL && ech->sniState == ECH_INNER_SNI){ + /* SNI status is carried over from processing the outer hello so it is + * necessary to clear it before processing the inner hello */ + ech->sniState = ECH_INNER_SNI_ATTEMPT; + if (sni != NULL){ + sni->status = WOLFSSL_SNI_NO_MATCH; + } + } + else if (ech != NULL && ech->sniState == ECH_OUTER_SNI && + ech->privateName == NULL && sni != NULL){ + /* save the private SNI before it is overwritten by the public SNI */ + privateNameLen = (word16)XSTRLEN(sni->data.host_name) + 1; + ech->privateName = (char*)XMALLOC(privateNameLen, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + if (ech->privateName == NULL) + return MEMORY_E; + XMEMCPY((char*)ech->privateName, sni->data.host_name, + privateNameLen); + } +#endif + +#if defined(WOLFSSL_TLS13) /* Don't process the second ClientHello SNI extension if there * was problems with the first. */ - if (!cacheOnly && sni->status != 0) + if (!cacheOnly && sni != NULL && sni->status != WOLFSSL_SNI_NO_MATCH) return 0; #endif - matched = cacheOnly || (XSTRLEN(sni->data.host_name) == size && - XSTRNCMP(sni->data.host_name, (const char*)input + offset, size) == 0); -#if defined(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->sniState == ECH_INNER_SNI_ATTEMPT && + ech->privateName != NULL) { + matched = cacheOnly || (XSTRLEN(ech->privateName) == size && + XSTRNCMP(ech->privateName, (const char*)input + offset, size) == 0); + } + else +#endif + { + matched = cacheOnly || (XSTRLEN(sni->data.host_name) == size && + XSTRNCMP(sni->data.host_name, (const char*)input + offset, + size) == 0); + } - if (!matched && ech != NULL) { +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + if (!matched && ech != NULL && ech->sniState == ECH_OUTER_SNI) { workingConfig = ech->echConfig; - while (workingConfig != NULL) { matched = XSTRLEN(workingConfig->publicName) == size && XSTRNCMP(workingConfig->publicName, @@ -2366,6 +2411,7 @@ static int TLSX_SNI_Parse(WOLFSSL* ssl, const byte* input, word16 length, int matchStat; int r = TLSX_UseSNI(&ssl->extensions, type, input + offset, size, ssl->heap); + if (r != WOLFSSL_SUCCESS) return r; /* throws error. */ @@ -13421,6 +13467,417 @@ static int TLSX_ECH_GetSize(WOLFSSL_ECH* ech, byte msgType) return (int)size; } +/* rough check that inner hello fields do not exceed length of decrypted + * information. Additionally, this function will check that all padding bytes + * are zero and decrease the innerHelloLen accordingly if so. + * returns 0 on success and otherwise failure */ +static int TLSX_ECH_CheckInnerPadding(WOLFSSL* ssl, WOLFSSL_ECH* ech) +{ + int headerSz; + const byte* innerCh; + word32 innerChLen; + word32 idx; + byte sessionIdLen; + word16 cipherSuitesLen; + byte compressionLen; + word16 extLen; + byte acc = 0; + word32 i; + +#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; + + idx = OPAQUE16_LEN + RAN_LEN; + if (idx >= innerChLen) + return BUFFER_ERROR; + + sessionIdLen = innerCh[idx++]; + /* innerHello sessionID must initially be empty */ + if (sessionIdLen != 0) + return INVALID_PARAMETER; + idx += sessionIdLen; + 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, &extLen); + idx += OPAQUE16_LEN + extLen; + if (idx > innerChLen) + return BUFFER_ERROR; + + /* should now be at the end of the innerHello + * Per ECH spec all padding bytes MUST be 0 */ + for (i = idx; i < innerChLen; i++) { + acc |= innerCh[i]; + } + if (acc != 0) { + SendAlert(ssl, alert_fatal, illegal_parameter); + return INVALID_PARAMETER; + } + + ech->innerClientHelloLen -= i - idx; + return 0; +} + +/* 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 - *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 - *extensionsStart > *extensionsLen) + 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; + + /* don't need to check for buffer overflows here since they are caught by + * TLSX_ECH_CheckInnerPadding */ + idx = OPAQUE16_LEN + RAN_LEN; + + sessionIdLen = innerCh[idx++]; + idx += sessionIdLen; + + ato16(innerCh + idx, &cipherSuitesLen); + idx += OPAQUE16_LEN + cipherSuitesLen; + + compressionLen = innerCh[idx++]; + idx += compressionLen; + + ato16(innerCh + idx, &innerExtLen); + idx += OPAQUE16_LEN; + innerExtIdx = idx; + + /* 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 */ @@ -13539,19 +13996,22 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, byte msgType) { int ret = 0; - int i; TLSX* echX; WOLFSSL_ECH* ech; WOLFSSL_EchConfig* echConfig; byte* aadCopy; byte* readBuf_p = (byte*)readBuf; + word16 tmpVal16; + word16 localEncLen; + 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); @@ -13560,15 +14020,17 @@ 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) { /* length must be 8 */ if (size != ECH_ACCEPT_CONFIRMATION_SZ) return BAD_FUNC_ARG; + /* get extension */ echX = TLSX_Find(ssl->extensions, TLSX_ECH); if (echX == NULL) return BAD_FUNC_ARG; ech = (WOLFSSL_ECH*)echX->data; + ech->confBuf = (byte*)readBuf; } else if (msgType == client_hello && ssl->ctx->echConfigs != NULL) { @@ -13577,6 +14039,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, if (echX == NULL) return BAD_FUNC_ARG; ech = (WOLFSSL_ECH*)echX->data; + /* read the ech parameters before the payload */ ech->type = *readBuf_p; readBuf_p++; @@ -13584,52 +14047,96 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, ech->state = ECH_PARSED_INTERNAL; return 0; } - /* technically the payload would only be 1 byte at this length */ - if (size < 11 + ech->encLen) - return BAD_FUNC_ARG; - /* read kdfId */ - ato16(readBuf_p, &ech->cipherSuite.kdfId); - readBuf_p += 2; - /* read aeadId */ - ato16(readBuf_p, &ech->cipherSuite.aeadId); - readBuf_p += 2; - /* read configId */ - ech->configId = *readBuf_p; - readBuf_p++; + else if (ech->type != ECH_TYPE_OUTER) { + /* type MUST be INNER or OUTER */ + SendAlert(ssl, alert_fatal, illegal_parameter); + return INVALID_PARAMETER; + } + + if (size < 11) + return BUFFER_ERROR; + /* only get enc if we don't already have the hpke context */ if (ech->hpkeContext == NULL) { - /* read encLen */ + /* kdfId */ + ato16(readBuf_p, &ech->cipherSuite.kdfId); + readBuf_p += 2; + /* aeadId */ + ato16(readBuf_p, &ech->cipherSuite.aeadId); + readBuf_p += 2; + /* configId */ + ech->configId = *readBuf_p; + readBuf_p++; + /* encLen */ ato16(readBuf_p, &ech->encLen); readBuf_p += 2; if (ech->encLen > HPKE_Npk_MAX) return BAD_FUNC_ARG; + + if (size < 11 + ech->encLen) + return BUFFER_ERROR; + /* read enc */ XMEMCPY(ech->enc, readBuf_p, ech->encLen); readBuf_p += ech->encLen; + localEncLen = ech->encLen; } else { + /* kdfId, aeadId, and configId must be the same as last time */ + /* kdf */ + ato16(readBuf_p, &tmpVal16); + if (tmpVal16 != ech->cipherSuite.kdfId) { + SendAlert(ssl, alert_fatal, illegal_parameter); + return INVALID_PARAMETER; + } + readBuf_p += 2; + /* aead */ + ato16(readBuf_p, &tmpVal16); + if (tmpVal16 != ech->cipherSuite.aeadId) { + SendAlert(ssl, alert_fatal, illegal_parameter); + return INVALID_PARAMETER; + } + readBuf_p += 2; + /* configId */ + if (*readBuf_p != ech->configId) { + SendAlert(ssl, alert_fatal, illegal_parameter); + return INVALID_PARAMETER; + } + readBuf_p++; + /* on the second client hello the enc value MUST be empty */ + ato16(readBuf_p, &localEncLen); + if (localEncLen != 0) { + SendAlert(ssl, alert_fatal, illegal_parameter); + return INVALID_PARAMETER; + } readBuf_p += 2; } - /* read hello inner len */ + + /* read payload (encrypted CH) len */ ato16(readBuf_p, &ech->innerClientHelloLen); - if (ech->innerClientHelloLen < WC_AES_BLOCK_SIZE) { + if (ech->innerClientHelloLen < WC_AES_BLOCK_SIZE || + 10 + localEncLen + ech->innerClientHelloLen != size) { return BUFFER_ERROR; } ech->innerClientHelloLen -= WC_AES_BLOCK_SIZE; readBuf_p += 2; ech->outerClientPayload = readBuf_p; + /* make a copy of the aad */ aadCopy = (byte*)XMALLOC(ech->aadLen, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (aadCopy == NULL) return MEMORY_E; XMEMCPY(aadCopy, ech->aad, ech->aadLen); + /* set the ech payload of the copy to zeros */ XMEMSET(aadCopy + (readBuf_p - ech->aad), 0, ech->innerClientHelloLen + WC_AES_BLOCK_SIZE); - /* free the old ech in case this is our second client hello */ + + /* free the old ech when this is the second client hello */ if (ech->innerClientHello != NULL) XFREE(ech->innerClientHello, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); + /* allocate the inner payload buffer */ ech->innerClientHello = (byte*)XMALLOC(ech->innerClientHelloLen + HANDSHAKE_HEADER_SZ, @@ -13638,10 +14145,10 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, XFREE(aadCopy, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); return MEMORY_E; } - /* first check if the config id matches */ + + /* try to decrypt with matching configId */ echConfig = ssl->ctx->echConfigs; while (echConfig != NULL) { - /* decrypt with this config */ if (echConfig->configId == ech->configId) { ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen, ssl->heap); @@ -13649,33 +14156,31 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, } echConfig = echConfig->next; } - /* try to decrypt with all configs */ + /* otherwise, try to decrypt with all configs */ if (echConfig == NULL || ret != 0) { echConfig = ssl->ctx->echConfigs; while (echConfig != NULL) { ret = TLSX_ExtractEch(ech, echConfig, aadCopy, ech->aadLen, ssl->heap); - if (ret== 0) + if (ret == 0) break; echConfig = echConfig->next; } } - /* if we failed to extract, set state to retry configs */ + if (ret == 0) { + ret = TLSX_ECH_CheckInnerPadding(ssl, ech); + if (ret == 0) { + /* expand EchOuterExtensions if present. + * Also, 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; } - else { - i = 0; - /* decrement until before the padding */ - 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; - } XFREE(aadCopy, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); return 0; } @@ -13694,6 +14199,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; @@ -15467,7 +15974,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) @@ -15653,7 +16160,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); @@ -16938,6 +17445,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 cadcb26337..b47ad6da6b 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -3784,6 +3784,254 @@ 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)); + +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Add("ECH PRK", expandLabelPrk, sizeof(expandLabelPrk)); +#endif + + 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; + ForceZero(expandLabelPrk, sizeof(expandLabelPrk)); +#ifdef WOLFSSL_CHECK_MEM_ZERO + wc_MemZero_Check(expandLabelPrk, sizeof(expandLabelPrk)); +#endif + return ret; +} #endif #ifndef NO_WOLFSSL_CLIENT @@ -4204,70 +4452,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) { @@ -4513,7 +4697,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; @@ -4667,7 +4851,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; @@ -4691,9 +4875,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); @@ -4725,7 +4916,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, @@ -4756,9 +4947,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) @@ -4850,142 +5042,74 @@ 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) + const byte* input, int acceptOffset, int helloSz, byte msgType) { int ret = 0; - int digestType = 0; - int digestSize = 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)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("ECH PRK", expandLabelPrk, - sizeof(expandLabelPrk)); + +#ifdef WOLFSSL_DTLS13 + headerSz = ssl->options.dtls ? DTLS13_HANDSHAKE_HEADER_SZ : + HANDSHAKE_HEADER_SZ; +#else + headerSz = HANDSHAKE_HEADER_SZ; #endif - /* store so we can restore regardless of the outcome */ + + ret = EchCalcAcceptance(ssl, label, labelSz, input, acceptOffset, helloSz, + msgType == hello_retry_request, 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 */ + /* last 8 bytes must match the expand output */ ret = ConstantCompare(acceptConfirmation, input + acceptOffset, ECH_ACCEPT_CONFIRMATION_SZ); - /* ech accepted */ + 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 (msgType == hello_retry_request) { + 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; - ForceZero(expandLabelPrk, sizeof(expandLabelPrk)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(expandLabelPrk, sizeof(expandLabelPrk)); -#endif 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. @@ -5519,8 +5643,8 @@ 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) { + /* check for acceptConfirmation */ + 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) { @@ -5536,7 +5660,8 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, /* check acceptance */ if (ret == 0) { ret = EchCheckAcceptance(ssl, args->acceptLabel, - args->acceptLabelSz, input, args->acceptOffset, helloSz); + args->acceptLabelSz, input, args->acceptOffset, helloSz, + args->extMsgType); } if (ret != 0) return ret; @@ -6628,6 +6753,60 @@ 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() @@ -6676,7 +6855,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); @@ -6972,9 +7150,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 @@ -7049,18 +7237,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 @@ -7316,7 +7499,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, @@ -7328,115 +7512,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)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Add("ECH PRK", expandLabelPrk, - sizeof(expandLabelPrk)); -#endif - /* 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; - ForceZero(expandLabelPrk, sizeof(expandLabelPrk)); -#ifdef WOLFSSL_CHECK_MEM_ZERO - wc_MemZero_Check(expandLabelPrk, sizeof(expandLabelPrk)); -#endif - return ret; -} -#endif - /* Send TLS v1.3 ServerHello message to client. * Only a server will send this message. * @@ -12994,20 +13069,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)->sniState = ECH_INNER_SNI; + ret = DoTls13ClientHello(ssl, ((WOLFSSL_ECH*)echX->data)->innerClientHello, &echInOutIdx, ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen); + + ((WOLFSSL_ECH*)echX->data)->sniState = ECH_SNI_DONE; } /* if the inner ech parsed successfully we have successfully * handled the hello and can skip the whole message */ - 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 12a26d1e93..463b31162a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -13813,15 +13813,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); + AssertNotNull(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); @@ -13840,12 +13842,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, @@ -14057,20 +14060,24 @@ 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]; + word32 outputLen = sizeof(testBuf); + word16 tmpLen = 0; + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + WOLFSSL* ssl = wolfSSL_new(ctx); ExpectNotNull(ctx); ExpectNotNull(ssl); + /* generation errors */ + /* invalid ctx */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(NULL, "ech-public-name.com", 0, 0, 0)); @@ -14079,57 +14086,216 @@ 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)); - - /* invalid ctx */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(NULL, - (char*)testBuf, sizeof(testBuf))); - /* invalid base64 configs */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, - NULL, sizeof(testBuf))); - /* invalid length */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, - (char*)testBuf, 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)); + "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 */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, NULL, - &outputLen)); - /* invalid output len */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, NULL)); + /* bad function calls */ - /* invalid ssl */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, - (char*)testBuf, sizeof(testBuf))); - /* invalid configs64 */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, NULL, + /* NULL ctx */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(NULL, testBuf, sizeof(testBuf))); - /* invalid size */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, - (char*)testBuf, 0)); - - /* invalid ssl */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(NULL, testBuf, sizeof(testBuf))); - /* invalid configs */ + + /* NULL configs */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, NULL, + sizeof(testBuf))); ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, NULL, sizeof(testBuf))); - /* invalid size */ + + /* 0 length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, 0)); 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 */ + if (EXPECT_SUCCESS()) { + 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)); + if (EXPECT_SUCCESS()) { + c16toa(tmpLen, testBuf); + } + + /* reject config with invalid version */ + if (EXPECT_SUCCESS()) { + testBuf[2] ^= 0x01; + } + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, + outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, + outputLen)); + if (EXPECT_SUCCESS()) { + testBuf[2] ^= 0x01; + } + + /* reject config with bad length */ + if (EXPECT_SUCCESS()) { + 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)); + if (EXPECT_SUCCESS()) { + 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)); + + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); +#endif /* !NO_WOLFSSL_CLIENT */ + + return EXPECT_RESULT(); +} + +static int test_wolfSSL_Tls13_ECH_params_b64(void) +{ + EXPECT_DECLS; +#if !defined(NO_WOLFSSL_CLIENT) + /* base64 ech configs from cloudflare-ech.com (these are good configs) */ + const char* b64Valid = "AEX+DQBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA="; + /* ech configs with bad/unsupported algorithm */ + const char* b64BadAlgo = "AEX+DQBBFP//ACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA="; + /* ech configs with bad/unsupported ciphersuite */ + const char* b64BadCiph = "AEX+DQBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAE//8AAQASY2xvdWRmbGFyZS1lY2guY29tAAA="; + /* ech configs with bad version first */ + const char* b64BadVers1 = "AIz+HQBCAQAgACCjR6+Qn9UYkMaWdXZzsby88vXFhPHJ2tWCDHQJLvMkEgAEAAEAAQATZWNoLXB1YmxpYy1uYW1lLmNvbQAA/g0AQgIAIAAgMM6vLrTbOfsfA6fTbJY/Iu0Lj2xeHEPGUJeUwQGAYF4ABAABAAEAE2VjaC1wdWJsaWMtbmFtZS5jb20AAA=="; + /* ech configs with bad version second */ + const char* b64BadVers2 = "AIz+DQBCAQAgACCjR6+Qn9UYkMaWdXZzsby88vXFhPHJ2tWCDHQJLvMkEgAEAAEAAQATZWNoLXB1YmxpYy1uYW1lLmNvbQAA/h0AQgIAIAAgMM6vLrTbOfsfA6fTbJY/Iu0Lj2xeHEPGUJeUwQGAYF4ABAABAAEAE2VjaC1wdWJsaWMtbmFtZS5jb20AAA=="; + byte testBuf[256]; + word32 outputLen; + + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + WOLFSSL* ssl = wolfSSL_new(ctx); + + ExpectNotNull(ctx); + ExpectNotNull(ssl); + + /* NULL ctx/ssl, short public key */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(NULL, + b64Valid, (word32)XSTRLEN(b64Valid))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, + b64Valid, (word32)XSTRLEN(b64Valid))); + + /* NULL configs */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + NULL, (word32)XSTRLEN(b64Valid))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + NULL, (word32)XSTRLEN(b64Valid))); + + /* 0 length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64Valid, 0)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64Valid, 0)); + + /* bad algorithm */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64BadAlgo, (word32)XSTRLEN(b64BadAlgo))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64BadAlgo, (word32)XSTRLEN(b64BadAlgo))); + + /* bad ciphersuite */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64BadCiph, (word32)XSTRLEN(b64BadCiph))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64BadCiph, (word32)XSTRLEN(b64BadCiph))); + + /* bad version first, should only have config 2 set */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64BadVers1, (word32)XSTRLEN(b64BadVers1))); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64BadVers1, (word32)XSTRLEN(b64BadVers1))); + ExpectIntEQ(2, ctx->echConfigs->configId); + ExpectIntEQ(2, ssl->echConfigs->configId); + + /* clear configs */ + wolfSSL_CTX_SetEchEnable(ctx, 0); + wolfSSL_CTX_SetEchEnable(ctx, 1); + wolfSSL_SetEchEnable(ssl, 0); + wolfSSL_SetEchEnable(ssl, 1); + + /* bad version second, should only have config 1 set */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64BadVers2, (word32)XSTRLEN(b64BadVers2))); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64BadVers2, (word32)XSTRLEN(b64BadVers2))); + ExpectIntEQ(1, ctx->echConfigs->configId); + ExpectIntEQ(1, ssl->echConfigs->configId); + + /* clear configs */ + wolfSSL_CTX_SetEchEnable(ctx, 0); + wolfSSL_CTX_SetEchEnable(ctx, 1); + wolfSSL_SetEchEnable(ssl, 0); + wolfSSL_SetEchEnable(ssl, 1); + + /* base64 tests */ + + /* set base64 configs */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64Valid, (word32)XSTRLEN(b64Valid))); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64Valid, (word32)XSTRLEN(b64Valid))); + + /* disable and check ctx has no configs */ + wolfSSL_CTX_SetEchEnable(ctx, 0); + outputLen = sizeof(testBuf); + ExpectIntNE(WOLFSSL_SUCCESS, + wolfSSL_CTX_GetEchConfigs(ctx, testBuf, &outputLen)); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); @@ -14138,7 +14304,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; @@ -14165,11 +14332,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, @@ -14187,10 +14353,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, @@ -14220,8 +14386,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); @@ -14238,13 +14404,553 @@ 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) { + 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 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); + + 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); + + /* 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); + + /* client does not send 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; + + 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 */ +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* 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 throwaway ECH config for client to use */ + ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, echCbTestPublicName, + 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 = 0; + word16 secondConfigOffset = 0; + word16 secondConfigLen = 0; + + 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); + if (EXPECT_SUCCESS()) { + ato16(combinedConfigs + OPAQUE16_LEN + OPAQUE16_LEN, &firstConfigLen); + secondConfigOffset = OPAQUE16_LEN + OPAQUE16_LEN + OPAQUE16_LEN + + firstConfigLen; + ExpectIntGE(combinedConfigsLen, + secondConfigOffset + OPAQUE16_LEN + OPAQUE16_LEN); + } + if (EXPECT_SUCCESS()) { + ato16(combinedConfigs + secondConfigOffset + OPAQUE16_LEN, + &secondConfigLen); + secondConfigLen += OPAQUE16_LEN + OPAQUE16_LEN; + ExpectIntGE(combinedConfigsLen, secondConfigOffset + secondConfigLen); + } + if (EXPECT_SUCCESS()) { + /* build the ECHConfigList */ + c16toa(secondConfigLen, altConfig); + ExpectIntLE(OPAQUE16_LEN + secondConfigLen, (word16)sizeof(altConfig)); + if (EXPECT_SUCCESS()) { + 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) && \ @@ -33496,13 +34202,24 @@ 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), + TEST_DECL(test_wolfSSL_Tls13_ECH_params_b64), /* 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/wolfcrypt/src/hpke.c b/wolfcrypt/src/hpke.c index ed6b1a06e2..09e6ad843f 100644 --- a/wolfcrypt/src/hpke.c +++ b/wolfcrypt/src/hpke.c @@ -469,6 +469,10 @@ static int wc_HpkeLabeledExtract(Hpke* hpke, byte* suite_id, if (hpke == NULL) { return BAD_FUNC_ARG; } + if (HPKE_VERSION_STR_LEN + suite_id_len + label_len + ikm_len > + MAX_HPKE_LABEL_SZ) { + return BUFFER_E; + } WC_ALLOC_VAR_EX(labeled_ikm, byte, MAX_HPKE_LABEL_SZ, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E); @@ -516,6 +520,10 @@ static int wc_HpkeLabeledExpand(Hpke* hpke, byte* suite_id, word32 suite_id_len, if (hpke == NULL) { return BAD_FUNC_ARG; } + if (2 + HPKE_VERSION_STR_LEN + suite_id_len + label_len + infoSz > + MAX_HPKE_LABEL_SZ) { + return BUFFER_E; + } WC_ALLOC_VAR_EX(labeled_info, byte, MAX_HPKE_LABEL_SZ, hpke->heap, DYNAMIC_TYPE_TMP_BUFFER, return MEMORY_E); diff --git a/wolfssl/internal.h b/wolfssl/internal.h index ee541f68e7..45071c5377 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -2972,8 +2972,8 @@ typedef struct Options Options; #define TLSXT_KEY_SHARE 0x0033 #define TLSXT_CONNECTION_ID 0x0036 #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 0xfe0d /* RFC 9849 */ +#define TLSXT_ECH_OUTER_EXTENSIONS 0xfd00 /* RFC 9849 */ /* The 0xFF section is experimental/custom/personal use */ #define TLSXT_CKS 0xff92 /* X9.146 */ #define TLSXT_RENEGOTIATION_INFO 0xff01 @@ -3096,6 +3096,13 @@ typedef enum { ECH_PARSED_INTERNAL, } EchState; +typedef enum { + ECH_OUTER_SNI, + ECH_INNER_SNI, + ECH_INNER_SNI_ATTEMPT, + ECH_SNI_DONE, +} EchStateSNI; + typedef struct EchCipherSuite { word16 kdfId; word16 aeadId; @@ -3118,6 +3125,7 @@ typedef struct WOLFSSL_ECH { Hpke* hpke; HpkeBaseContext* hpkeContext; const byte* aad; + const char* privateName; void* ephemeralKey; WOLFSSL_EchConfig* echConfig; byte* innerClientHello; @@ -3130,6 +3138,7 @@ typedef struct WOLFSSL_ECH { word16 kemId; word16 encLen; EchState state; + EchStateSNI sniState; byte type; byte configId; byte enc[HPKE_Npk_MAX]; @@ -5149,7 +5158,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 @@ -5932,7 +5940,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 8d56767dc4..e5df24edf6 100644 --- a/wolfssl/ssl.h +++ b/wolfssl/ssl.h @@ -1230,8 +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 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);