From c3a38dced720cdf37f6d220dc1add97ab05087a2 Mon Sep 17 00:00:00 2001 From: sebastian-carpenter Date: Thu, 12 Feb 2026 10:50:45 -0700 Subject: [PATCH] testing + bug fixes for TLS ECH --- .github/workflows/openssl-ech.yml | 312 ++++++++++++ examples/client/client.c | 41 +- examples/server/server.c | 59 ++- src/internal.c | 3 +- src/ssl_ech.c | 29 +- src/tls.c | 85 +++- src/tls13.c | 39 +- tests/api.c | 770 +++++++++++++++++++++++++++--- wolfssl/internal.h | 10 +- wolfssl/ssl.h | 4 +- 10 files changed, 1233 insertions(+), 119 deletions(-) create mode 100644 .github/workflows/openssl-ech.yml 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..879078d699 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,32 @@ 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]; + 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 { + echConfigBase64[echConfigBase64Len] = '\0'; + printf("ECH config (base64): %s\n", echConfigBase64); + } + } +#endif + #ifdef USE_WINDOWS_API if (port == 0) { /* Generate random port for testing */ diff --git a/src/internal.c b/src/internal.c index 410c2d56bd..2678e37ed4 100644 --- a/src/internal.c +++ b/src/internal.c @@ -8634,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..2edbe9c7f3 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,9 @@ int wolfSSL_CTX_GenerateEchConfig(WOLFSSL_CTX* ctx, const char* publicName, else XMEMSET(newConfig, 0, sizeof(WOLFSSL_EchConfig)); - /* set random config id */ + /* set random configId */ + /* TODO: if an equal configId is found should the old config be removed from + * the LL? Prevents growth beyond 255+ items */ if (ret == 0) ret = wc_RNG_GenerateByte(rng, &newConfig->configId); @@ -147,17 +148,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 +248,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 +259,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 +272,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 +298,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 +307,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 +469,7 @@ int wolfSSL_GetEchConfigs(WOLFSSL* ssl, byte* output, word32* outputLen) return BAD_FUNC_ARG; /* if we don't have ech configs */ - if (ssl->options.useEch != 1) { + if (ssl->echConfigs == NULL) { return WOLFSSL_FATAL_ERROR; } diff --git a/src/tls.c b/src/tls.c index 53dfba7853..8fcfce21f0 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2257,9 +2257,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); @@ -2291,7 +2292,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. @@ -2339,24 +2355,52 @@ 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->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(HAVE_ECH) + if (ech != NULL && ech->sniState == ECH_INNER_SNI_ATTEMPT) { + 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 defined(WOLFSSL_TLS13) && defined(HAVE_ECH) - echX = TLSX_Find(ssl->extensions, TLSX_ECH); - if (echX != NULL) - ech = (WOLFSSL_ECH*)(echX->data); - - if (!matched && ech != NULL) { + if (!matched && ech != NULL && ech->sniState == ECH_OUTER_SNI) { workingConfig = ech->echConfig; - while (workingConfig != NULL) { matched = XSTRLEN(workingConfig->publicName) == size && XSTRNCMP(workingConfig->publicName, @@ -2374,6 +2418,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. */ @@ -13922,13 +13967,12 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, word16 len; 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); @@ -13937,7 +13981,8 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, ret = 0; } /* HRR with special confirmation */ - else if (msgType == hello_retry_request && ssl->options.useEch) { + else if (msgType == hello_retry_request && ssl->echConfigs != NULL && + !ssl->options.disableECH) { /* length must be 8 */ if (size != ECH_ACCEPT_CONFIRMATION_SZ) return BAD_FUNC_ARG; @@ -14055,6 +14100,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, if (ret == 0) { i = 0; /* decrement until before the padding */ + /* TODO: verify padding is 0, abort with illegal_parameter */ while (ech->innerClientHello[ech->innerClientHelloLen + HANDSHAKE_HEADER_SZ - i - 1] != ECH_TYPE_INNER) { i++; @@ -14090,6 +14136,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; @@ -15868,7 +15916,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) @@ -16053,7 +16101,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); @@ -17332,7 +17380,8 @@ int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, byte msgType, #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) /* If client used ECH, server HRR must include ECH confirmation */ - if (ret == 0 && msgType == hello_retry_request && ssl->options.useEch == 1) { + 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"); diff --git a/src/tls13.c b/src/tls13.c index f53aeceb21..1f17a1acf7 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4720,7 +4720,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; @@ -4874,7 +4874,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; @@ -4939,7 +4939,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, @@ -4970,7 +4970,7 @@ int SendTls13ClientHello(WOLFSSL* ssl) { #if defined(HAVE_ECH) /* compute the inner hash */ - if (ssl->options.useEch == 1 && !ssl->options.disableECH && + if (ssl->echConfigs != NULL && !ssl->options.disableECH && (ssl->options.echAccepted || args->ech->innerCount == 0)) { ret = EchHashHelloInner(ssl, args->ech); } @@ -5664,7 +5664,7 @@ int DoTls13ServerHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #if defined(HAVE_ECH) /* check for acceptConfirmation, must be done after hashes restart */ - if (ssl->options.useEch == 1) { + if (ssl->echConfigs != NULL && !ssl->options.disableECH) { args->echX = TLSX_Find(ssl->extensions, TLSX_ECH); /* account for hrr extension instead of server random */ if (args->extMsgType == hello_retry_request) { @@ -7165,9 +7165,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 @@ -7244,7 +7254,8 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #if defined(HAVE_ECH) /* hash clientHelloInner to hsHashesEch */ if (echX != NULL && ssl->ctx->echConfigs != NULL && - !ssl->options.disableECH) { + !ssl->options.disableECH && + ((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) { ret = EchHashHelloInner(ssl, (WOLFSSL_ECH*)echX->data); if (ret != 0) goto exit_dch; @@ -7503,7 +7514,8 @@ exit_dch: #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, @@ -13072,16 +13084,21 @@ 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 */ diff --git a/tests/api.c b/tests/api.c index 9ec28044e3..32213f57ec 100644 --- a/tests/api.c +++ b/tests/api.c @@ -13790,6 +13790,7 @@ static int test_wolfSSL_CTX_add_client_CA(void) defined(HAVE_IO_TESTS_DEPENDENCIES) static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) { + EXPECT_DECLS; callback_functions* callbacks = ((func_args*)args)->callbacks; WOLFSSL_CTX* ctx = callbacks->ctx; WOLFSSL* ssl = NULL; @@ -13814,15 +13815,17 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) AssertIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_use_PrivateKey_file(ctx, svrKeyFile, - WOLFSSL_FILETYPE_PEM)); + WOLFSSL_FILETYPE_PEM)); if (callbacks->ctx_ready) callbacks->ctx_ready(ctx); - ssl = wolfSSL_new(ctx); + ExpectNotNull(ssl = wolfSSL_new(ctx)); /* set the sni for the server */ - wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, privateNameLen); + AssertIntEQ(WOLFSSL_SUCCESS, + wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, + privateNameLen)); tcp_accept(&sfd, &cfd, (func_args*)args, port, 0, 0, 0, 0, 1, NULL, NULL); CloseSocket(sfd); @@ -13841,12 +13844,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, @@ -14058,20 +14062,27 @@ static int test_wolfSSL_Tls13_Key_Logging_test(void) #endif /* OPENSSL_EXTRA && HAVE_SECRET_CALLBACK && WOLFSSL_TLS13 */ return EXPECT_RESULT(); } -#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ - defined(HAVE_IO_TESTS_DEPENDENCIES) + +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +#if defined(HAVE_IO_TESTS_DEPENDENCIES) static int test_wolfSSL_Tls13_ECH_params(void) { EXPECT_DECLS; #if !defined(NO_WOLFSSL_CLIENT) - word32 outputLen = 0; - byte testBuf[72]; - WOLFSSL_CTX *ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); - WOLFSSL *ssl = wolfSSL_new(ctx); + byte testBuf[256]; + /* base64 ech configs from cloudflare-ech.com */ + const char* b64Configs = + "AEX+DQBBFAAgACBuAoQI8+liEVYQbXKBDeVgTmF2rfXuKO2knhwrN7jgTgAEAAEAAQASY2xvdWRmbGFyZS1lY2guY29tAAA="; + word32 outputLen = sizeof(testBuf); + word16 tmpLen; + WOLFSSL_CTX* ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method()); + WOLFSSL* ssl = wolfSSL_new(ctx); ExpectNotNull(ctx); ExpectNotNull(ssl); + /* CTX NULL errors */ + /* invalid ctx */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(NULL, "ech-public-name.com", 0, 0, 0)); @@ -14080,57 +14091,142 @@ static int test_wolfSSL_Tls13_ECH_params(void) 0, 0)); /* invalid algorithms */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, - "ech-public-name.com", 1000, 1000, 1000)); + "ech-public-name.com", 1000, 0, 0)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 0, 1000, 0)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 0, 0, 1000)); - /* invalid ctx */ + /* invalid base64 configs: NULL ctx, NULL configs, 0 length */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(NULL, - (char*)testBuf, sizeof(testBuf))); - /* invalid base64 configs */ + b64Configs, (word32)XSTRLEN(b64Configs))); ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, - NULL, sizeof(testBuf))); - /* invalid length */ + NULL, (word32)XSTRLEN(b64Configs))); ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, - (char*)testBuf, 0)); + b64Configs, 0)); - /* invalid ctx */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(NULL, - testBuf, sizeof(testBuf))); - /* invalid configs */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, - NULL, sizeof(testBuf))); - /* invalid length */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, - testBuf, 0)); - - /* invalid ctx */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, NULL, - &outputLen)); - /* invalid output len */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, NULL)); - - /* invalid ssl */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, - (char*)testBuf, sizeof(testBuf))); - /* invalid configs64 */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, NULL, + /* invalid configs: NULL ctx, NULL configs, 0 length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(NULL, testBuf, sizeof(testBuf))); - /* invalid size */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, - (char*)testBuf, 0)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, NULL, + sizeof(testBuf))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigs(ctx, testBuf, 0)); - /* invalid ssl */ + /* SSL NULL errors */ + + /* invalid base64 configs: NULL ssl, NULL configs, 0 length */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(NULL, b64Configs, + (word32)XSTRLEN(b64Configs))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, NULL, + (word32)XSTRLEN(b64Configs))); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, b64Configs, + 0)); + + /* invalid configs: NULL ssl, NULL configs, 0 length */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(NULL, testBuf, sizeof(testBuf))); - /* invalid configs */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, NULL, sizeof(testBuf))); - /* invalid size */ ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigs(ssl, testBuf, 0)); - /* invalid ssl */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(NULL, NULL, &outputLen)); - /* invalid size */ - ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_GetEchConfigs(ssl, NULL, NULL)); + /* stateful errors */ + + /* actually generate configs */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(ctx, + "ech-public-name.com", 0, 0, 0)); + + /* bad get: NULL ctx, NULL output len, short output len */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(NULL, testBuf, + &outputLen)); + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, testBuf, NULL)); + outputLen = 5; + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, testBuf, + &outputLen)); + + /* should be able to retrieve length with NULL buffer... */ + ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, NULL, + &outputLen)); + ExpectIntGE(sizeof(testBuf), outputLen); + + /* and the get should work with this length */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GetEchConfigs(ctx, testBuf, + &outputLen)); + + /* reject config with invalid total length */ + 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)); + + /* base64 tests */ + + /* set base64 configs */ + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, + b64Configs, (word32)XSTRLEN(b64Configs))); + ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, + b64Configs, (word32)XSTRLEN(b64Configs))); + + /* disable and check ctx has no configs as well */ + wolfSSL_CTX_SetEchEnable(ctx, 0); + outputLen = sizeof(testBuf); + ExpectIntNE(WOLFSSL_SUCCESS, + wolfSSL_CTX_GetEchConfigs(ctx, testBuf, &outputLen)); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); @@ -14139,7 +14235,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; @@ -14166,11 +14263,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, @@ -14188,10 +14284,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, @@ -14221,8 +14317,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); @@ -14239,13 +14335,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; + word16 secondConfigOffset; + word16 secondConfigLen; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + test_ctx.s_cb.method = wolfTLSv1_3_server_method; + test_ctx.c_cb.method = wolfTLSv1_3_client_method; + + /* server generates its own ECH config */ + test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready; + test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + + /* generate a second ECH config for the server */ + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(test_ctx.s_ctx, + echCbTestPrivateName, 0, 0, 0), WOLFSSL_SUCCESS); + ExpectNotNull(test_ctx.s_ctx->echConfigs->next); + + /* capture the second ECH config in the list for the client to use */ + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(test_ctx.s_ctx, combinedConfigs, + &combinedConfigsLen), WOLFSSL_SUCCESS); + + /* ECHConfigList: [2 byte list len] [ECHConfig]... + * ECHConfig: [2 byte version] [2 byte config len] [config data] */ + ExpectIntGE(combinedConfigsLen, OPAQUE16_LEN * 3); + 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) && \ @@ -33498,13 +34134,23 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_SCR_check_enabled), TEST_DECL(test_tls_ext_duplicate), TEST_DECL(test_tls_bad_legacy_version), -#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) && \ - defined(HAVE_IO_TESTS_DEPENDENCIES) +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +#if defined(HAVE_IO_TESTS_DEPENDENCIES) TEST_DECL(test_wolfSSL_Tls13_ECH_params), /* Uses Assert in handshake callback. */ TEST_DECL(test_wolfSSL_Tls13_ECH), TEST_DECL(test_wolfSSL_Tls13_ECH_HRR), + TEST_DECL(test_wolfSSL_SubTls13_ECH), #endif +#if defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) + TEST_DECL(test_wolfSSL_Tls13_ECH_no_private_name), + TEST_DECL(test_wolfSSL_Tls13_ECH_bad_configs), + TEST_DECL(test_wolfSSL_Tls13_ECH_new_config), + TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE), + TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn), +#endif + TEST_DECL(test_wolfSSL_Tls13_ECH_enable_disable), +#endif /* WOLFSSL_TLS13 && HAVE_ECH */ TEST_DECL(test_wolfSSL_X509_TLS_version_test_1), TEST_DECL(test_wolfSSL_X509_TLS_version_test_2), diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 1edc3333a4..90478e0b15 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3100,6 +3100,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; @@ -3122,6 +3129,7 @@ typedef struct WOLFSSL_ECH { Hpke* hpke; HpkeBaseContext* hpkeContext; const byte* aad; + const char* privateName; void* ephemeralKey; WOLFSSL_EchConfig* echConfig; byte* innerClientHello; @@ -3134,6 +3142,7 @@ typedef struct WOLFSSL_ECH { word16 kemId; word16 encLen; EchState state; + EchStateSNI sniState; byte type; byte configId; byte enc[HPKE_Npk_MAX]; @@ -5156,7 +5165,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 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);