diff --git a/.github/scripts/openssl-ech.sh b/.github/scripts/openssl-ech.sh new file mode 100644 index 0000000000..a2d3178515 --- /dev/null +++ b/.github/scripts/openssl-ech.sh @@ -0,0 +1,190 @@ +#!/bin/bash + +set -e + +cleanup() { + cat "$TMP_LOG" + rm -f "$TMP_LOG" +} +trap cleanup EXIT + +usage() { + echo "Usage: $0 [--suite ] [--workspace ]" + exit 1 +} + +MODE="" +SUITE="" + +WORKSPACE=${GITHUB_WORKSPACE:-"."} + +if [ $# -lt 1 ]; then + usage +fi + +case "$1" in + client|server) MODE="$1" ;; + *) usage ;; +esac +shift + +while [ $# -gt 0 ]; do + case "$1" in + --suite) + [ -z "$2" ] && { echo "ERROR: --suite requires a value"; exit 1; } + SUITE="$2" + shift 2 + echo "" + echo "Using suite: $SUITE" + echo "" + ;; + --workspace) + [ -z "$2" ] && { echo "ERROR: --workspace requires a value"; exit 1; } + WORKSPACE="$2" + shift 2 + ;; + *) echo "Unknown argument: $1"; usage ;; + esac +done + +OPENSSL=${OPENSSL:-"openssl"} +WOLFSSL_CLIENT=${WOLFSSL_CLIENT:-"$WORKSPACE/examples/client/client"} +WOLFSSL_SERVER=${WOLFSSL_SERVER:-"$WORKSPACE/examples/server/server"} +CERT_DIR=${CERT_DIR:-"$WORKSPACE/certs"} + +TMP_LOG="$WORKSPACE/tmp_file.log" +PRIV_NAME="ech-private-name.com" +PUB_NAME="ech-public-name.com" +MAX_WAIT=50 + +openssl_server(){ + local ech_file="$WORKSPACE/ech_config.pem" + local ech_config="" + local port="" + + rm -f "$ech_file" + + $OPENSSL ech -public_name "$PUB_NAME" -out "$ech_file" $SUITE &>> "$TMP_LOG" + + # 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" &>> "$TMP_LOG" + + # 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 \ + &>> "$TMP_LOG" <<< "wolfssl!" & + + # wait for server port to be ready and capture it + counter=0 + while [ -z "$port" ]; do + port=$(grep -m1 "ACCEPT" "$TMP_LOG" | sed 's/.*:\([0-9]*\)$/\1/') + sleep 0.1 + counter=$((counter + 1)) + if [ "$counter" -gt "$MAX_WAIT" ]; then + echo "ERROR: server port not found" &>> "$TMP_LOG" + exit 1 + fi + done + echo "parsed port: $port" &>> "$TMP_LOG" + + # test with wolfssl client + $WOLFSSL_CLIENT -v 4 \ + -p "$port" \ + -S "$PRIV_NAME" \ + --ech "$ech_config" \ + &>> "$TMP_LOG" + + rm -f "$ech_file" + + grep -q "ech_success=1" "$TMP_LOG" +} + +openssl_client(){ + local ready_file="$WORKSPACE/wolfssl_tls13_ready$$" + local ech_config="" + local port=0 + + rm -f "$ready_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" \ + $SUITE \ + &>> "$TMP_LOG" & + + # 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 "$MAX_WAIT" ]; then + echo "ERROR: no ready file" &>> "$TMP_LOG" + exit 1 + fi + done + port="$(cat "$ready_file")" + rm -f "$ready_file" + echo "parsed port: $port" &>> "$TMP_LOG" + + # get ECH config from server + counter=0 + while [ -z "$ech_config" ]; do + ech_config=$(grep -m1 "ECH config (base64): " "$TMP_LOG" \ + 2>/dev/null | sed 's/ECH config (base64): //g') + sleep 0.1 + counter=$((counter + 1)) + if [ "$counter" -gt "$MAX_WAIT" ]; then + echo "ERROR: no ECH configs" &>> "$TMP_LOG" + exit 1 + fi + done + echo "parsed ech config: $ech_config" &>> "$TMP_LOG" + + # 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" \ + &>> "$TMP_LOG" + + grep -q "ECH: success: 1" "$TMP_LOG" +} + +rm -f "$TMP_LOG" + +case "$MODE" in + server) + if [ -n "$SUITE" ]; then + SUITE="-suite $SUITE" + fi + openssl_server + ;; + client) + if [ -n "$SUITE" ]; then + SUITE="--ech-suite $SUITE" + fi + openssl_client + ;; + *) + exit 1 + ;; +esac diff --git a/.github/workflows/openssl-ech.yml b/.github/workflows/openssl-ech.yml index 83a73ee55d..e0832738d2 100644 --- a/.github/workflows/openssl-ech.yml +++ b/.github/workflows/openssl-ech.yml @@ -23,7 +23,8 @@ jobs: uses: wolfSSL/actions-build-autotools-project@v1 with: path: wolfssl - configure: --enable-ech CFLAGS='-DUSE_FLAT_TEST_H' + configure: >- + --enable-ech --enable-sha512 --enable-aes CFLAGS='-DUSE_FLAT_TEST_H' install: true - name: tar build-dir @@ -38,6 +39,10 @@ jobs: cp -r "$GITHUB_WORKSPACE/wolfssl/certs" build-dir/certs tar -zcf build-dir.tgz build-dir + # need the ech script to run tests + cp "$GITHUB_WORKSPACE/wolfssl/.github/scripts/openssl-ech.sh" \ + build-dir/openssl-ech.sh + - name: Upload built wolfSSL uses: actions/upload-artifact@v4 with: @@ -117,76 +122,23 @@ jobs: 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 - + 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" & + # default suite (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, HPKE_AES_128_GCM) + bash ./openssl-ech.sh client &>> "$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" + # weird suite (DHKEM_P521_HKDF_SHA512, HKDF_SHA256, HPKE_AES_256_GCM) + bash ./openssl-ech.sh client --suite "18,3,2" &>> "$LOG_FILE" # cleanup - rm -f "$READY_FILE" rm -f "$LOG_FILE" - name: Print debug info on failure @@ -238,69 +190,24 @@ jobs: 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 - + 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" + # default suite (DHKEM_X25519_HKDF_SHA256, HKDF_SHA256, HPKE_AES_128_GCM) + bash ./openssl-ech.sh server &>> "$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" + # weird suite (DHKEM_P521_HKDF_SHA512, HKDF_SHA256, HPKE_AES_256_GCM) + bash ./openssl-ech.sh server --suite "18,3,2" &>> "$LOG_FILE" # cleanup rm -f "$LOG_FILE" - rm -f "$ECH_FILE" - name: Print debug info on failure if: ${{ failure() }} diff --git a/examples/server/server.c b/examples/server/server.c index 07e480eb55..921fa1621c 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -919,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[][69] = { +static const char* server_usage_msg[][70] = { /* English */ { " NOTE: All files relative to wolfSSL home dir\n", /* 0 */ @@ -1120,11 +1120,14 @@ static const char* server_usage_msg[][69] = { "--ech Generate Encrypted Client Hello config with " "public name \n", /* 67 */ + "--ech-suite HPKE suite to use for ech config. " + "Supplied as 3 integers (e.g., 32,1,3)\n", + /* 68 */ #endif "\n" "For simpler wolfSSL TLS server examples, visit\n" "https://github.com/wolfSSL/wolfssl-examples/tree/master/tls\n", - /* 68 */ + /* 69 */ NULL, }, #ifndef NO_MULTIBYTE_PRINT @@ -1502,6 +1505,7 @@ static void Usage(void) #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) printf("%s", msg[++msgId]); /* --ech */ + printf("%s", msg[++msgId]); /* --ech-suite */ #endif printf("%s", msg[++msgId]); /* Examples repo link */ } @@ -1627,6 +1631,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif /* WOLFSSL_SYS_CRYPTO_POLICY */ #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) { "ech", 1, 269 }, + { "ech-suite", 1, 270 }, #endif { 0, 0, 0 } }; @@ -1706,6 +1711,7 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) char* echPublicName = NULL; + char* echSuite = NULL; #endif #ifdef HAVE_TRUSTED_CA @@ -2539,6 +2545,9 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) case 269: echPublicName = myoptarg; break; + case 270: + echSuite = myoptarg; + break; #endif case -1: @@ -3107,9 +3116,27 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) char echConfigBase64[512]; char* echConfigBase64Ptr; word32 echConfigBase64Len = sizeof(echConfigBase64); + word16 kemId = 0; + word16 kdfId = 0; + word16 aeadId = 0; - if (wolfSSL_CTX_GenerateEchConfig(ctx, echPublicName, 0, 0, 0) - != WOLFSSL_SUCCESS) { + /* an optimistic approach to parsing the ciphersuite */ + if (echSuite != NULL) { + const char* s = echSuite; + + kemId = (word16)atoi(s); + for (; *s != '\0' && *s != ','; s++); + if (*s == ',') s++; + + kdfId = (word16)atoi(s); + for (; *s != '\0' && *s != ','; s++); + if (*s == ',') s++; + + aeadId = (word16)atoi(s); + } + + if (wolfSSL_CTX_GenerateEchConfig(ctx, echPublicName, kemId, kdfId, + aeadId) != WOLFSSL_SUCCESS) { err_sys_ex(runWithErrors, "GenerateEchConfig failed"); } if (wolfSSL_CTX_GetEchConfigs(ctx, echConfig, &echConfigLen) diff --git a/tests/api.c b/tests/api.c index ce610df627..99d5a5380d 100644 --- a/tests/api.c +++ b/tests/api.c @@ -14535,6 +14535,30 @@ static int test_wolfSSL_Tls13_ECH_all_algos_ex(void) ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, 1); + if (echCbTestKemID != 0 && echCbTestKdfID != 0 && echCbTestAeadID != 0) { + TLSX* echX = TLSX_Find(test_ctx.c_ssl->extensions, TLSX_ECH); + ExpectNotNull(echX); + if (echX != NULL) { + WOLFSSL_ECH* ech = (WOLFSSL_ECH*)echX->data; + ExpectNotNull(ech); + if (ech != NULL) { + /* verify that the ech extension has the correct algos */ + ExpectIntEQ(ech->kemId, echCbTestKemID); + ExpectIntEQ(ech->cipherSuite.kdfId, echCbTestKdfID); + ExpectIntEQ(ech->cipherSuite.aeadId, echCbTestAeadID); + if (ech->hpke != NULL) { + /* and that hpke was initialized with these algos */ + ExpectIntEQ(ech->hpke->kem, echCbTestKemID); + ExpectIntEQ(ech->hpke->kdf, echCbTestKdfID); + ExpectIntEQ(ech->hpke->aead, echCbTestAeadID); + } + if (ech->echConfig != NULL) { + ExpectIntEQ(ech->echConfig->kemId, echCbTestKemID); + } + } + } + } + test_ssl_memio_cleanup(&test_ctx); return EXPECT_RESULT();