diff --git a/.github/scripts/openssl-ech.sh b/.github/scripts/openssl-ech.sh index 555cfbbb0c2..ed18a5e6213 100644 --- a/.github/scripts/openssl-ech.sh +++ b/.github/scripts/openssl-ech.sh @@ -70,6 +70,13 @@ if [ "$FORCE_HRR" -ne 0 ] && [ -n "$PQC" ]; then exit 1 fi +# corrupt_ech_config() flips the first public-key byte, this is valid only for +# the default X25519 KEM so consider --suite and --reject mutually exclusive +if [ "$REJECT" -ne 0 ] && [ -n "$SUITE" ]; then + echo "ERROR: --reject only supports the default X25519 suite" + exit 1 +fi + # Pick exactly one test variant. The variant decides which -groups go to # each side and any extra flags needed to drive the desired handshake. # default - both sides use secp256r1 (no HRR) @@ -89,9 +96,6 @@ WOLFSSL_CLIENT=${WOLFSSL_CLIENT:-"$WORKSPACE/examples/client/client"} WOLFSSL_SERVER=${WOLFSSL_SERVER:-"$WORKSPACE/examples/server/server"} CERT_DIR=${CERT_DIR:-"$WORKSPACE/certs"} -# correct ECH config, but it's old, ECH will be rejected -REJECT_ECH_CONFIG="AD7+DQA6rAAgACCATZdDlHed6GlDeiYsu3r7sdWUkLVHZuTa3lbOf+hIbAAEAAEAAQALZXhhbXBsZS5jb20AAA==" - TMP_LOG="$WORKSPACE/tmp_file.log" # Will need to look into validating the name against the cert for the OSSL cli. # This is fine, but should be upgraded to use a second cert in the future. @@ -101,6 +105,27 @@ PRIV_NAME="ech-private-name.com" PUB_NAME="example.com" MAX_WAIT=50 +# -------------------------------------------------------------------------- +# Flip a bit in the HPKE public key of the config the server published. The +# client will offer ECH, but the server can't decrypt so ECH is rejected. +# -------------------------------------------------------------------------- +corrupt_ech_config() { + local config="$1" + local bytes=() + local b + + mapfile -t bytes < <(printf '%s' "$config" | base64 -d | od -An -tx1 -v \ + | tr -s ' ' '\n' | grep -v '^$') + + # list len (2) + version (2) + config len (2) + config_id (1) + + # kem_id (2) + public key len (2), so byte 11 is the first key byte + bytes[11]=$(printf '%02x' $(( 0x${bytes[11]} ^ 0x01 ))) + + for b in "${bytes[@]}"; do + printf "\\x$b" + done | base64 -w 0 +} + # -------------------------------------------------------------------------- # server mode -- OpenSSL is the server, wolfSSL is the client # -------------------------------------------------------------------------- @@ -108,6 +133,7 @@ openssl_server(){ local ech_file="$WORKSPACE/ech_config.pem" local ech_config="" local port="" + local server_pid="" # Per-variant args. # openssl_groups : -groups passed to OpenSSL s_server @@ -140,13 +166,17 @@ openssl_server(){ # parse ECH config from file ech_config=$(sed -n '/BEGIN ECHCONFIG/,/END ECHCONFIG/{/BEGIN ECHCONFIG\|END ECHCONFIG/d;p}' "$ech_file" | tr -d '\n') - # reject overrides the config the client connects with - [ "$REJECT" -ne 0 ] && ech_config="$REJECT_ECH_CONFIG" echo "parsed ech config: $ech_config" &>> "$TMP_LOG" + # reject: corrupt the config so the server can't decrypt the client's ECH + if [ "$REJECT" -ne 0 ]; then + ech_config=$(corrupt_ech_config "$ech_config") + echo "bad ech config : $ech_config" &>> "$TMP_LOG" + fi + # start OpenSSL ECH server with ephemeral port; line-buffer so the # log can be grepped - stdbuf -oL $OPENSSL s_server \ + timeout 30 stdbuf -oL $OPENSSL s_server \ -tls1_3 \ -cert "$CERT_DIR/server-cert.pem" \ -key "$CERT_DIR/server-key.pem" \ @@ -158,6 +188,7 @@ openssl_server(){ -accept 0 \ -naccept 1 \ &>> "$TMP_LOG" <<< "wolfssl!" & + server_pid=$! # wait for server port to be ready and capture it counter=0 @@ -184,11 +215,16 @@ openssl_server(){ $wolfssl_extra \ &>> "$TMP_LOG" || [ "$REJECT" -ne 0 ] + # let s_server finish writing its ech_success= line before grepping; + # on reject it sees a fatal alert, so tolerate a nonzero exit + wait "$server_pid" || [ "$REJECT" -ne 0 ] + if [ "$REJECT" -ne 0 ]; then - grep -q "ECH offered but rejected by server" "$TMP_LOG" && \ - grep -q "ech_success=0" "$TMP_LOG" + grep -q "ech_success=0" "$TMP_LOG" && \ + grep -q "ECH status: rejected" "$TMP_LOG" else - grep -q "ech_success=1" "$TMP_LOG" + grep -q "ech_success=1" "$TMP_LOG" && \ + grep -q "ECH status: accepted" "$TMP_LOG" fi } @@ -199,6 +235,7 @@ openssl_client(){ local ready_file="$WORKSPACE/wolfssl_tls13_ready$$" local ech_config="" local port=0 + local server_pid="" # Per-variant args. # openssl_groups : -groups passed to OpenSSL s_client @@ -231,7 +268,7 @@ openssl_client(){ # start server with ephemeral port + ready file; line-buffer so the # log can be grepped - stdbuf -oL $WOLFSSL_SERVER \ + timeout 30 stdbuf -oL $WOLFSSL_SERVER \ -v 4 \ -R "$ready_file" \ -p "$port" \ @@ -240,6 +277,7 @@ openssl_client(){ $wolfssl_suite \ $wolfssl_extra \ &>> "$TMP_LOG" & + server_pid=$! # wait for server to be ready, then get port counter=0 @@ -267,10 +305,14 @@ openssl_client(){ exit 1 fi done - # reject overrides the config the client connects with - [ "$REJECT" -ne 0 ] && ech_config="$REJECT_ECH_CONFIG" echo "parsed ech config: $ech_config" &>> "$TMP_LOG" + # reject: corrupt the config so the server can't decrypt the client's ECH + if [ "$REJECT" -ne 0 ]; then + ech_config=$(corrupt_ech_config "$ech_config") + echo "bad ech config : $ech_config" &>> "$TMP_LOG" + fi + # test with OpenSSL s_client using ECH # in reject mode the s_client is expected to error out, so tolerate a # nonzero exit @@ -285,10 +327,16 @@ openssl_client(){ $openssl_groups \ &>> "$TMP_LOG" || [ "$REJECT" -ne 0 ] + # let the wolfSSL server finish writing its ECH status line before + # grepping; on reject it errors out, so tolerate a nonzero exit + wait "$server_pid" || [ "$REJECT" -ne 0 ] + if [ "$REJECT" -ne 0 ]; then - grep -q "ECH: Got 1 retry-configs" "$TMP_LOG" + grep -q "ECH: Got 1 retry-configs" "$TMP_LOG" && \ + grep -q "ECH status: rejected" "$TMP_LOG" else - grep -q "ECH: success: 1" "$TMP_LOG" + grep -q "ECH: success: 1" "$TMP_LOG" && \ + grep -q "ECH status: accepted" "$TMP_LOG" fi } diff --git a/examples/client/client.c b/examples/client/client.c index 9e3ff32e30c..f8b80d0c135 100644 --- a/examples/client/client.c +++ b/examples/client/client.c @@ -1869,7 +1869,6 @@ static void showPeerPEM(WOLFSSL* ssl) (void)ssl; } - static void Usage(void) { int msgid = 0; @@ -4418,6 +4417,11 @@ THREAD_RETURN WOLFSSL_THREAD client_test(void* args) timeoutConnect.tv_sec = DEFAULT_TIMEOUT_SEC; timeoutConnect.tv_usec = 0; ret = NonBlockingSSL_Connect(ssl); /* will keep retrying on timeout */ +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + /* print before ret is checked: ECH status is always significant */ + if (echConfigs64 != NULL) + PrintEchStatus(ssl); #endif if (ret != WOLFSSL_SUCCESS) { err = wolfSSL_get_error(ssl, 0); diff --git a/examples/server/server.c b/examples/server/server.c index 91045e94cbb..aeac4673335 100644 --- a/examples/server/server.c +++ b/examples/server/server.c @@ -3804,6 +3804,11 @@ THREAD_RETURN WOLFSSL_THREAD server_test(void* args) #endif #ifdef WOLFSSL_EARLY_DATA EarlyDataStatus(ssl); +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) + /* print before ret is checked: ECH status is always significant */ + if (echPublicName != NULL) + PrintEchStatus(ssl); #endif if (ret != WOLFSSL_SUCCESS) { err = wolfSSL_get_error(ssl, ret); diff --git a/src/tls.c b/src/tls.c index 2dec929827f..7e9d899eb7a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2736,8 +2736,8 @@ int TLSX_UseSNI(TLSX** extensions, byte type, const void* data, word16 size, /* client-side needs this function when ECH is enabled */ #if !defined(NO_WOLFSSL_SERVER) || defined(HAVE_ECH) /** Tells the SNI requested by the client. */ -word16 TLSX_SNI_GetRequest(TLSX* extensions, byte type, void** data, - byte ignoreStatus) +WOLFSSL_TEST_VIS word16 TLSX_SNI_GetRequest(TLSX* extensions, byte type, + void** data, byte ignoreStatus) { TLSX* extension = TLSX_Find(extensions, TLSX_SERVER_NAME); SNI* sni = TLSX_SNI_Find(extension ? (SNI*)extension->data : NULL, type); @@ -15179,18 +15179,18 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, /* get extension */ echX = TLSX_Find(ssl->extensions, TLSX_ECH); - if (echX == NULL) + if (echX == NULL || echX->data == NULL) return BAD_FUNC_ARG; ech = (WOLFSSL_ECH*)echX->data; ech->confBuf = (byte*)readBuf; } - else if (msgType == client_hello && ssl->ctx->echConfigs != NULL) { - /* get extension */ - echX = TLSX_Find(ssl->extensions, TLSX_ECH); - if (echX == NULL) - return BAD_FUNC_ARG; + else if (msgType == client_hello && + (echX = TLSX_Find(ssl->extensions, TLSX_ECH)) != NULL && + echX->data != NULL && + ((WOLFSSL_ECH*)echX->data)->echConfig != NULL) { ech = (WOLFSSL_ECH*)echX->data; + TLSX_SetResponse(ssl, TLSX_ECH); /* if the first ECH was rejected or CH1 did not have ECH then there is * no need to decrypt this one */ @@ -15215,6 +15215,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, /* MUST process INNER in inner hello and OUTER in outer hello */ return INVALID_PARAMETER; } + /* Must have kdfId, aeadId, configId, enc len and payload len. */ if (size < offset + 2 + 2 + 1 + 2 + 2) { return BUFFER_ERROR; @@ -15316,7 +15317,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, return MEMORY_E; } /* try to decrypt with matching configId */ - echConfig = ssl->ctx->echConfigs; + echConfig = ech->echConfig; while (echConfig != NULL) { if (echConfig->configId == ech->configId) { ret = TLSX_ExtractEch(ssl, ech, echConfig, aadCopy, @@ -15328,7 +15329,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, } /* otherwise, try to decrypt with all configs (trial decryption) */ if (echConfig == NULL && ssl->options.enableEchTrialDecrypt) { - echConfig = ssl->ctx->echConfigs; + echConfig = ech->echConfig; while (echConfig != NULL) { if (echConfig->configId != ech->configId) { ret = TLSX_ExtractEch(ssl, ech, echConfig, aadCopy, @@ -15368,7 +15369,7 @@ static int TLSX_ECH_Parse(WOLFSSL* ssl, const byte* readBuf, word16 size, ret = TLSX_ECH_CheckInnerPadding(ssl, ech); if (ret == 0) { /* expand EchOuterExtensions if present. - * Also, if it exists, copy sessionID from outer hello */ + * Also, if it exists, copy sessionID from outer hello */ ret = TLSX_ECH_ExpandOuterExtensions(ssl, ech, ssl->heap); } } @@ -15399,7 +15400,6 @@ static void TLSX_ECH_Free(WOLFSSL_ECH* ech, void* heap) ForceZero(ech->hpkeContext, sizeof(HpkeBaseContext)); XFREE(ech->hpkeContext, heap, DYNAMIC_TYPE_TMP_BUFFER); } - XFREE(ech, heap, DYNAMIC_TYPE_TMP_BUFFER); (void)heap; } @@ -17138,7 +17138,7 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) ret = GREASE_ECH_USE(&(ssl->extensions), ssl->heap, ssl->rng); } - else if (ssl->echConfigs != NULL) { + else { ret = ECH_USE(ssl->echConfigs, &(ssl->extensions), ssl->heap, ssl->rng); } @@ -17146,14 +17146,10 @@ int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isServer) #endif } #if defined(HAVE_ECH) - else if (IsAtLeastTLSv1_3(ssl->version)) { - if (ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) { - ret = SERVER_ECH_USE(&(ssl->extensions), ssl->heap, - ssl->ctx->echConfigs); - - if (ret == 0) - TLSX_SetResponse(ssl, TLSX_ECH); - } + else if (IsAtLeastTLSv1_3(ssl->version) && !ssl->options.disableECH && + ssl->ctx->echConfigs != NULL) { + ret = SERVER_ECH_USE(&(ssl->extensions), ssl->heap, + ssl->ctx->echConfigs); } #endif @@ -19707,8 +19703,7 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, /* Reconcile ECH inner/outer extensions before verifying SNI so the verify * pass sees the authoritative list */ if (ret == 0 && msgType == client_hello && isRequest && - !ssl->options.echProcessingInner && - ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) { + !ssl->options.disableECH && !ssl->options.echProcessingInner) { TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH); WOLFSSL_ECH* ech = NULL; if (echX != NULL) @@ -19736,9 +19731,6 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, ech->state == ECH_WRITE_RETRY_CONFIGS) { ret = TLSX_EchReplaceExtensions(ssl, ssl->options.echAccepted); - if (ret == 0 && ech->state == ECH_WRITE_NONE) { - echX->resp = 0; - } } } } diff --git a/src/tls13.c b/src/tls13.c index 7ca1e3e0458..0228ff2a1b9 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -7933,6 +7933,7 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, #endif #if defined(HAVE_ECH) TLSX* echX = NULL; + WOLFSSL_ECH* ech = NULL; #endif WOLFSSL_START(WC_FUNC_CLIENT_HELLO_DO); @@ -8253,15 +8254,15 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, goto exit_dch; #if defined(HAVE_ECH) - if (ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) { + if (!ssl->options.disableECH) { /* save the start of the buffer so we can use it when parsing ech */ echX = TLSX_Find(ssl->extensions, TLSX_ECH); - if (echX == NULL) - ERROR_OUT(WOLFSSL_FATAL_ERROR, exit_dch); - - ((WOLFSSL_ECH*)echX->data)->aad = input + args->begin; - ((WOLFSSL_ECH*)echX->data)->aadLen = helloSz; + if (echX != NULL && echX->data != NULL) { + ech = (WOLFSSL_ECH*)echX->data; + ech->aad = input + args->begin; + ech->aadLen = helloSz; + } } #endif @@ -8298,9 +8299,11 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, /* ECH accept/reject reconciliation is done at the end of TLSX_Parse. On * acceptance the inner hello was decrypted, so jump to exit and let the * caller re-invoke with the inner hello. */ - if (!ssl->options.echProcessingInner && echX != NULL && - ((WOLFSSL_ECH*)echX->data)->state == ECH_WRITE_NONE && - ((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) { + if (!ssl->options.echProcessingInner && ech != NULL && + ech->state == ECH_WRITE_NONE && ech->innerClientHello != NULL) { + /* need to add handshake header to the inner hello ourselves */ + AddTls13HandShakeHeader(ech->innerClientHello, ech->innerClientHelloLen, + 0, 0, client_hello, ssl); goto exit_dch; } #endif @@ -8378,13 +8381,11 @@ 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 && - ((WOLFSSL_ECH*)echX->data)->innerClientHello != NULL) { - ret = EchHashHelloInner(ssl, (WOLFSSL_ECH*)echX->data); + if (ech != NULL && ech->innerClientHello != NULL) { + ret = EchHashHelloInner(ssl, ech); if (ret != 0) goto exit_dch; - ((WOLFSSL_ECH*)echX->data)->innerCount = 1; + ech->innerCount = 1; } #endif @@ -8683,18 +8684,6 @@ int DoTls13ClientHello(WOLFSSL* ssl, const byte* input, word32* inOutIdx, WOLFSSL_ERROR_VERBOSE(ret); } -#if defined(HAVE_ECH) - if (ret == 0 && echX != NULL && - ((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, - ((WOLFSSL_ECH*)echX->data)->innerClientHelloLen, 0, 0, - client_hello, ssl); - } -#endif - return ret; } @@ -8714,6 +8703,7 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) int sendSz; #if defined(HAVE_ECH) TLSX* echX = NULL; + WOLFSSL_ECH* ech = NULL; byte* acceptLabel = (byte*)echAcceptConfirmationLabel; word32 acceptOffset; word16 acceptLabelSz = ECH_ACCEPT_CONFIRMATION_LABEL_SZ; @@ -8874,19 +8864,20 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) #endif /* WOLFSSL_DTLS13 */ { #if defined(HAVE_ECH) - if (ssl->ctx->echConfigs != NULL && !ssl->options.disableECH) { - echX = TLSX_Find(ssl->extensions, TLSX_ECH); - if (echX == NULL) - return WOLFSSL_FATAL_ERROR; - /* use hrr offset */ - if (extMsgType == hello_retry_request) { - acceptOffset = - (word32)(((WOLFSSL_ECH*)echX->data)->confBuf - output); - acceptLabel = (byte*)echHrrAcceptConfirmationLabel; - acceptLabelSz = ECH_HRR_ACCEPT_CONFIRMATION_LABEL_SZ; - } + if (!ssl->options.disableECH && + (echX = TLSX_Find(ssl->extensions, TLSX_ECH)) != NULL && + echX->data != NULL) { + ech = (WOLFSSL_ECH*)echX->data; + /* replace the last 8 bytes of server random with the accept */ - if (((WOLFSSL_ECH*)echX->data)->state == ECH_PARSED_INTERNAL) { + if (ech->state == ECH_PARSED_INTERNAL) { + /* use hrr offset */ + if (extMsgType == hello_retry_request) { + acceptOffset = (word32)(ech->confBuf - output); + acceptLabel = (byte*)echHrrAcceptConfirmationLabel; + acceptLabelSz = ECH_HRR_ACCEPT_CONFIRMATION_LABEL_SZ; + } + if (ret == 0) { ret = EchWriteAcceptance(ssl, acceptLabel, acceptLabelSz, output + RECORD_HEADER_SZ, @@ -8895,11 +8886,11 @@ int SendTls13ServerHello(WOLFSSL* ssl, byte extMsgType) } if (extMsgType == hello_retry_request) { /* reset the ech state for round 2 */ - ((WOLFSSL_ECH*)echX->data)->state = ECH_WRITE_NONE; + ech->state = ECH_WRITE_NONE; /* inner hello no longer needed, free it */ - XFREE(((WOLFSSL_ECH*)echX->data)->innerClientHello, - ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); - ((WOLFSSL_ECH*)echX->data)->innerClientHello = NULL; + XFREE(ech->innerClientHello, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + ech->innerClientHello = NULL; } else { if (ret == 0) { diff --git a/tests/api.c b/tests/api.c index fad41d325ac..7dbf53029bf 100644 --- a/tests/api.c +++ b/tests/api.c @@ -16419,8 +16419,7 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) char input[1024]; int idx; int ret, err = 0; - const char* privateName = "ech-private-name.com"; - int privateNameLen = (int)XSTRLEN(privateName); + int echPrivateNameLen = (int)XSTRLEN(echPrivateName); ((func_args*)args)->return_code = TEST_FAIL; port = ((func_args*)args)->signal->port; @@ -16444,8 +16443,8 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) /* set the sni for the server */ AssertIntEQ(WOLFSSL_SUCCESS, - wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, privateName, - privateNameLen)); + wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, echPrivateName, + echPrivateNameLen)); tcp_accept(&sfd, &cfd, (func_args*)args, port, 0, 0, 0, 0, 1, NULL, NULL); CloseSocket(sfd); @@ -16467,13 +16466,16 @@ static THREAD_RETURN WOLFSSL_THREAD server_task_ech(void* args) fprintf(stderr, "error = %d, %s\n", err, wolfSSL_ERR_error_string(err, buff)); } + else if (wolfSSL_GetEchStatus(ssl) != WOLFSSL_ECH_STATUS_ACCEPTED) { + fprintf(stderr, "error = Client could not connect with ECH\n"); + } else { if (0 < (idx = wolfSSL_read(ssl, input, sizeof(input)-1))) { input[idx] = 0; fprintf(stderr, "Client message: %s\n", input); - AssertIntEQ(privateNameLen, wolfSSL_write(ssl, privateName, - privateNameLen)); + AssertIntEQ(echPrivateNameLen, wolfSSL_write(ssl, echPrivateName, + echPrivateNameLen)); ((func_args*)args)->return_code = TEST_SUCCESS; } } @@ -16842,9 +16844,8 @@ static int test_wolfSSL_Tls13_Key_Logging_ech_rejected(void) TEST_SUCCESS); /* set inner SNI */ ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - "ech-private-name.com", (word16)XSTRLEN("ech-private-name.com")), - WOLFSSL_SUCCESS); - /* client sends empty cert on rejection, server should not ask for one */ + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); + /* client sends no cert on rejection */ wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); /* clean up keylog file */ @@ -17363,7 +17364,7 @@ static int test_wolfSSL_Tls13_ECH_params_b64(void) ExpectIntNE(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, b64Mandatory, (word32)XSTRLEN(b64Mandatory))); - /* unrecognized mandatory extension */ + /* unrecognized mandatory extension first, should only have config 2 set */ ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_SetEchConfigsBase64(ctx, b64MandatoryFirst, (word32)XSTRLEN(b64MandatoryFirst))); ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_SetEchConfigsBase64(ssl, @@ -17439,10 +17440,8 @@ static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, SOCKET_T sockfd = 0; WOLFSSL_CTX* ctx = NULL; WOLFSSL* ssl = NULL; - const char* publicName = "ech-public-name.com"; - const char* privateName = "ech-private-name.com"; - int privateNameLen = 20; char reply[1024]; + int echPrivateNameLen = (int)XSTRLEN(echPrivateName); int replyLen = 0; byte rawEchConfig[ECH_CONFIG_LEN]; word32 rawEchConfigLen = sizeof(rawEchConfig); @@ -17461,7 +17460,7 @@ static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, /* generate ech config */ ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_CTX_GenerateEchConfig(server_cbf.ctx, - publicName, 0, 0, 0)); + echPublicName, 0, 0, 0)); /* get the config for the client to use */ ExpectIntEQ(WOLFSSL_SUCCESS, @@ -17495,7 +17494,7 @@ static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, /* set the sni for the client */ ExpectIntEQ(WOLFSSL_SUCCESS, wolfSSL_UseSNI(ssl, WOLFSSL_SNI_HOST_NAME, - privateName, privateNameLen)); + echPrivateName, echPrivateNameLen)); /* force hello retry request */ if (hrr) @@ -17505,13 +17504,13 @@ static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, ExpectIntEQ(wolfSSL_set_fd(ssl, sockfd), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_connect(ssl), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(ssl), WOLFSSL_ECH_STATUS_ACCEPTED); - ExpectIntEQ(wolfSSL_write(ssl, privateName, privateNameLen), - privateNameLen); + ExpectIntEQ(wolfSSL_write(ssl, echPrivateName, echPrivateNameLen), + echPrivateNameLen); ExpectIntGT((replyLen = wolfSSL_read(ssl, reply, sizeof(reply))), 0); /* add the null terminator for string compare */ reply[replyLen] = '\0'; /* check that the server replied with the private name */ - ExpectStrEQ(privateName, reply); + ExpectStrEQ(echPrivateName, reply); wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); @@ -17526,14 +17525,14 @@ static int test_wolfSSL_ECH_conn_ex(method_provider serverMeth, static int test_wolfSSL_Tls13_ECH(void) { - return test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, - wolfTLSv1_3_client_method, 0); -} + EXPECT_DECLS; -static int test_wolfSSL_Tls13_ECH_HRR(void) -{ - return test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, - wolfTLSv1_3_client_method, 1); + ExpectIntEQ(test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, + wolfTLSv1_3_client_method, 0), TEST_SUCCESS); + ExpectIntEQ(test_wolfSSL_ECH_conn_ex(wolfTLSv1_3_server_method, + wolfTLSv1_3_client_method, 1), TEST_SUCCESS); + + return EXPECT_RESULT(); } static int test_wolfSSL_SubTls13_ECH(void) @@ -17638,6 +17637,91 @@ static int ech_find_extension(byte* buf, word16* idx_p, word16 extType) return BAD_FUNC_ARG; } +/* swap two extensions in a ClientHello so they appear in the opposite order + * returns 0 on success, error otherwise. */ +static int ech_swap_extensions(byte* buf, word16 typeFirst, word16 typeSecond) +{ + word16 idxA = 0; + word16 idxB = 0; + word16 lenA; + word16 lenB; + word16 midLen; + word16 middle; + word16 swap; + byte* tmp; + int ret; + + if (typeFirst == typeSecond) + return BAD_FUNC_ARG; + + ret = ech_find_extension(buf, &idxA, typeFirst); + if (ret != 0) + return ret; + ret = ech_find_extension(buf, &idxB, typeSecond); + if (ret != 0) + return ret; + + /* idxA must be the earlier extension on the wire */ + if (idxA > idxB) { + swap = idxA; + idxA = idxB; + idxB = swap; + } + + /* a block is type + length + body */ + ato16(buf + idxA + OPAQUE16_LEN, &lenA); + ato16(buf + idxB + OPAQUE16_LEN, &lenB); + /* [A] must end before [B] starts */ + if ((word32)idxA + 2 * OPAQUE16_LEN + lenA > idxB || + lenB > MAX_RECORD_SIZE) + return BAD_FUNC_ARG; + lenA += 2 * OPAQUE16_LEN; + lenB += 2 * OPAQUE16_LEN; + midLen = idxB - (idxA + lenA); + middle = lenA + midLen; + + tmp = (byte*)XMALLOC(middle, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (tmp == NULL) + return MEMORY_E; + + /* stash [A][middle], slide [B] to the beginning, join [middle] then [A] */ + XMEMCPY(tmp, buf + idxA, middle); + XMEMMOVE(buf + idxA, buf + idxB, lenB); + XMEMCPY(buf + idxA + lenB, tmp + lenA, midLen); + XMEMCPY(buf + idxA + lenB + midLen, tmp, lenA); + + XFREE(tmp, NULL, DYNAMIC_TYPE_TMP_BUFFER); + + return 0; +} + +/* the buffer must carry exactly one SNI */ +static int test_ech_assert_wire_sni(byte* buff, const char* publicName) +{ + EXPECT_DECLS; + word16 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + word16 extLen; + word16 listLen; + word16 nameLen; + word16 publicLen = (word16)XSTRLEN(publicName); + + ExpectIntEQ(ech_find_extension(buff, &idx, TLSXT_SERVER_NAME), 0); + + ato16(buff + idx + 2, &extLen); + ato16(buff + idx + 4, &listLen); + ExpectIntEQ(buff[idx + 6], WOLFSSL_SNI_HOST_NAME); + ato16(buff + idx + 7, &nameLen); + + /* the single entry is the public name */ + ExpectIntEQ(nameLen, publicLen); + ExpectIntEQ(XMEMCMP(buff + idx + 9, publicName, publicLen), 0); + /* and it is the only entry: the list and extension hold nothing else */ + ExpectIntEQ(listLen, OPAQUE8_LEN + OPAQUE16_LEN + nameLen); + ExpectIntEQ(extLen, OPAQUE16_LEN + listLen); + + return EXPECT_RESULT(); +} + /* the arg is whether the client has ech enabled or not */ static int test_ech_server_sni_callback(WOLFSSL* ssl, int* ad, void* arg) { @@ -17726,6 +17810,9 @@ static int test_ech_client_ssl_ready(WOLFSSL* ssl) if (ret != WOLFSSL_SUCCESS) return TEST_FAIL; + /* force verify to be on, regardless of the defaults */ + wolfSSL_set_verify(ssl, WOLFSSL_VERIFY_PEER, NULL); + return TEST_SUCCESS; } @@ -17880,6 +17967,7 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) byte retryConfigs[ECH_CONFIG_LEN]; word32 retryConfigsLen = sizeof(retryConfigs); WOLFSSL_CTX* savedSCtx; + void* sniName = NULL; /* --- first attempt: wrong ECH config -> ECH rejected --- */ @@ -17895,8 +17983,7 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) ExpectIntEQ(test_ech_set_bad_echconfigs(test_ctx.s_ctx, test_ctx.c_ssl), TEST_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); if (hrr) ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); @@ -17909,6 +17996,9 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), WC_NO_ERR_TRACE(ECH_REQUIRED_E)); + /* on reject the public name is left on the client */ + wolfSSL_SNI_GetRequest(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, &sniName); + ExpectStrEQ((const char*)sniName, echPublicName); /* capture retry configs */ ExpectIntEQ(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, retryConfigs, @@ -17936,8 +18026,7 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, retryConfigs, retryConfigsLen), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); if (hrr) ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); @@ -17948,6 +18037,10 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_ex(int hrr) WOLFSSL_ECH_STATUS_ACCEPTED); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), WOLFSSL_ECH_STATUS_ACCEPTED); + /* on accept the private name is left on the client */ + sniName = NULL; + wolfSSL_SNI_GetRequest(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, &sniName); + ExpectStrEQ((const char*)sniName, echPrivateName); wolfSSL_CTX_free(test_ctx.s_ctx); test_ctx.s_ctx = NULL; @@ -17969,14 +18062,12 @@ static int test_wolfSSL_Tls13_ECH_retry_configs(void) } /* Test retry configs are cleared when authentication fails */ -static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr) +static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail(void) { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; - WOLFSSL_CTX* tempCtx = NULL; - byte badConfigs[ECH_CONFIG_LEN]; - word32 badConfigsLen = sizeof(badConfigs); - word32 retryConfigsLen = sizeof(badConfigs); + byte retryConfigs[ECH_CONFIG_LEN]; + word32 retryConfigsLen = sizeof(retryConfigs); const char* badPublicName = "ech-public-name.com"; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -17986,24 +18077,28 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr) ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); - /* generate mismatched ECH configs so retry_configs are sent - * and use a bad public name so auth fails in outer hello */ - ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); - ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, badPublicName, - 0, 0, 0), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfigs, &badConfigsLen), - WOLFSSL_SUCCESS); - wolfSSL_CTX_free(tempCtx); - tempCtx = NULL; - - ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badConfigs, - badConfigsLen), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), - WOLFSSL_SUCCESS); + { + WOLFSSL_CTX* tempCtx = NULL; + byte badConfigs[ECH_CONFIG_LEN]; + word32 badConfigsLen = sizeof(badConfigs); + + /* generate mismatched ECH configs so retry_configs are sent + * and use a bad public name so auth fails in outer hello */ + ExpectNotNull(tempCtx = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(tempCtx, badPublicName, + 0, 0, 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(tempCtx, badConfigs, + &badConfigsLen), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(tempCtx); + tempCtx = NULL; + + ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, badConfigs, + badConfigsLen), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); + } - /* Do not require client cert on server so it does not send - * CertificateRequest */ + /* client sends no cert on rejection */ wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); @@ -18012,16 +18107,13 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr) badPublicName, (word16)XSTRLEN(badPublicName)), WOLFSSL_SUCCESS); - if (hrr) - ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); - /* auth failure in outer handshake, not ech_required */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), WC_NO_ERR_TRACE(DOMAIN_NAME_MISMATCH)); /* retry configs must not be accessible */ - ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL, + ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, retryConfigs, &retryConfigsLen), WOLFSSL_SUCCESS); test_ssl_memio_cleanup(&test_ctx); @@ -18029,25 +18121,14 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(int hrr) return EXPECT_RESULT(); } -static int test_wolfSSL_Tls13_ECH_retry_configs_auth_fail(void) -{ - EXPECT_DECLS; - - ExpectIntEQ(test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(0), - TEST_SUCCESS); - ExpectIntEQ(test_wolfSSL_Tls13_ECH_retry_configs_auth_fail_ex(1), - TEST_SUCCESS); - - return EXPECT_RESULT(); -} - /* Test that bad retry configs (unsupported cipher suite) from the server are * ignored rather than propagating an error */ static int test_wolfSSL_Tls13_ECH_retry_configs_bad(void) { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; - word32 retryConfigsLen = sizeof(echCbTestConfigs); + byte retryConfigs[ECH_CONFIG_LEN]; + word32 retryConfigsLen = sizeof(retryConfigs); XMEMSET(&test_ctx, 0, sizeof(test_ctx)); test_ctx.s_cb.method = wolfTLSv1_3_server_method; @@ -18077,7 +18158,7 @@ static int test_wolfSSL_Tls13_ECH_retry_configs_bad(void) WC_NO_ERR_TRACE(ECH_REQUIRED_E)); /* no retry configs should be stored since they were all unsupported */ - ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL, + ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, retryConfigs, &retryConfigsLen), WOLFSSL_SUCCESS); test_ssl_memio_cleanup(&test_ctx); @@ -18153,8 +18234,7 @@ static int test_wolfSSL_Tls13_ECH_new_config(void) ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, altConfig, altConfigLen), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), @@ -18265,7 +18345,8 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; - word32 retryConfigsLen = sizeof(test_ctx); + byte retryConfigs[ECH_CONFIG_LEN]; + word32 retryConfigsLen = sizeof(retryConfigs); /* GREASE when server has no ECH configs */ @@ -18277,8 +18358,7 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); /* verify ECH is enabled on the client and server */ ExpectIntEQ(test_ctx.s_ssl->options.disableECH, 0); @@ -18298,7 +18378,7 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) /* verify no ECH configs are received */ ExpectNull(test_ctx.c_ssl->echConfigs); /* retry configs must not be saved */ - ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL, + ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, retryConfigs, &retryConfigsLen), WOLFSSL_SUCCESS); test_ssl_memio_cleanup(&test_ctx); @@ -18306,6 +18386,7 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) /* GREASE when server has ECH configs */ XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + retryConfigsLen = sizeof(retryConfigs); test_ctx.s_cb.method = wolfTLSv1_3_server_method; test_ctx.c_cb.method = wolfTLSv1_3_client_method; @@ -18317,8 +18398,7 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); /* verify ECH is enabled on the client and server */ ExpectIntEQ(test_ctx.s_ssl->options.disableECH, 0); @@ -18339,7 +18419,7 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) /* verify no ECH configs are received */ ExpectNull(test_ctx.c_ssl->echConfigs); /* retry configs must not be saved */ - ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, NULL, + ExpectIntNE(wolfSSL_GetEchRetryConfigs(test_ctx.c_ssl, retryConfigs, &retryConfigsLen), WOLFSSL_SUCCESS); test_ssl_memio_cleanup(&test_ctx); @@ -18347,41 +18427,13 @@ static int test_wolfSSL_Tls13_ECH_GREASE(void) return EXPECT_RESULT(); } -/* the outer ClientHello in buff must carry exactly one SNI: the public name */ -static int test_ech_assert_wire_sni(byte* buff, const char* publicName) -{ - EXPECT_DECLS; - word16 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; - word16 extLen; - word16 listLen; - word16 nameLen; - word16 publicLen = (word16)XSTRLEN(publicName); - - ExpectIntEQ(ech_find_extension(buff, &idx, TLSXT_SERVER_NAME), 0); - - ato16(buff + idx + 2, &extLen); - ato16(buff + idx + 4, &listLen); - ExpectIntEQ(buff[idx + 6], WOLFSSL_SNI_HOST_NAME); - ato16(buff + idx + 7, &nameLen); - - /* the single entry is the public name */ - ExpectIntEQ(nameLen, publicLen); - ExpectIntEQ(XMEMCMP(buff + idx + 9, publicName, publicLen), 0); - /* and it is the only entry: the list and extension hold nothing else */ - ExpectIntEQ(listLen, OPAQUE8_LEN + OPAQUE16_LEN + nameLen); - ExpectIntEQ(extLen, OPAQUE16_LEN + listLen); - - return EXPECT_RESULT(); -} - -/* The public name must be visible in plaintext - * useCtx installs the inner SNI on the client ctx */ -static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept, int useCtx) +/* Test the client properly inherits an SNI from the ctx, + * the public name must be visible in plaintext */ +static int test_wolfSSL_Tls13_ECH_ctx_sni_ex(int reject) { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; - const char* expectedSni = - accept ? echPrivateName : echPublicName; + const char* expectedSni = reject ? echPublicName : echPrivateName; void* sniName = NULL; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -18395,7 +18447,7 @@ static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept, int useCtx) ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); /* Reject path installs bad configs (with the correct public name) */ - if (!accept) { + if (reject) { /* derive a bad config from the server's real one to reject ECH */ ExpectIntEQ(test_ech_set_bad_echconfigs(test_ctx.s_ctx, test_ctx.c_ssl), TEST_SUCCESS); @@ -18409,25 +18461,21 @@ static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept, int useCtx) WOLFSSL_SNI_ABORT_ON_ABSENCE); } - /* install the private (inner) SNI on the ctx or the per-connection ssl */ - if (useCtx) { - ExpectIntEQ(wolfSSL_CTX_UseSNI(test_ctx.c_ctx, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); - } - else { - ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); - } + /* install the private (inner) SNI on the ctx */ + ExpectIntEQ(wolfSSL_CTX_UseSNI(test_ctx.c_ctx, WOLFSSL_SNI_HOST_NAME, + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); /* force HelloRetryRequest */ ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); - /* On reject, client aborts with ech_required and won't send a cert. */ - if (!accept) { + /* client sends no cert on rejection */ + if (reject) { wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); - wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); } + /* verify mode may be VERIFY_NONE so force it on */ + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); + /* client writes CH1 into s_buff */ ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), @@ -18454,29 +18502,29 @@ static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept, int useCtx) TEST_SUCCESS); /* sanity check: finish the handshake and verify ECH acceptance */ - if (accept) { - ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + if (reject) { + ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), - WOLFSSL_ECH_STATUS_ACCEPTED); + WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), - WOLFSSL_ECH_STATUS_ACCEPTED); + WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), + WC_NO_ERR_TRACE(ECH_REQUIRED_E)); } else { - ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), - WOLFSSL_ECH_STATUS_REJECTED); + WOLFSSL_ECH_STATUS_ACCEPTED); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), - WOLFSSL_ECH_STATUS_REJECTED); + WOLFSSL_ECH_STATUS_ACCEPTED); } - /* verify the correct SNI is authoritative */ - ExpectIntEQ(test_ctx.c_ssl->options.echAccepted, accept); wolfSSL_SNI_GetRequest(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, &sniName); /* an inner SNI installed on the ctx is never copied onto the ssl, so on * accept it does not become the authoritative request on the client ssl */ - if (accept && useCtx) + if (!reject) ExpectNull(sniName); else ExpectStrEQ((const char*)sniName, expectedSni); @@ -18484,7 +18532,7 @@ static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept, int useCtx) wolfSSL_SNI_GetRequest(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME, &sniName); ExpectStrEQ((const char*)sniName, expectedSni); /* verify the ctx always has the private SNI */ - if (useCtx && EXPECT_SUCCESS() && (test_ctx.c_ctx != NULL)) { + if (EXPECT_SUCCESS() && (test_ctx.c_ctx != NULL)) { sniName = NULL; TLSX_SNI_GetRequest(test_ctx.c_ctx->extensions, WOLFSSL_SNI_HOST_NAME, &sniName, 1); @@ -18496,13 +18544,11 @@ static int test_wolfSSL_Tls13_ECH_wire_sni_ex(int accept, int useCtx) return EXPECT_RESULT(); } -static int test_wolfSSL_Tls13_ECH_wire_sni(void) +static int test_wolfSSL_Tls13_ECH_ctx_sni(void) { EXPECT_DECLS; - ExpectIntEQ(test_wolfSSL_Tls13_ECH_wire_sni_ex(0, 0), TEST_SUCCESS); - ExpectIntEQ(test_wolfSSL_Tls13_ECH_wire_sni_ex(1, 0), TEST_SUCCESS); - ExpectIntEQ(test_wolfSSL_Tls13_ECH_wire_sni_ex(0, 1), TEST_SUCCESS); - ExpectIntEQ(test_wolfSSL_Tls13_ECH_wire_sni_ex(1, 1), TEST_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_ctx_sni_ex(0), TEST_SUCCESS); + ExpectIntEQ(test_wolfSSL_Tls13_ECH_ctx_sni_ex(1), TEST_SUCCESS); return EXPECT_RESULT(); } @@ -18511,6 +18557,10 @@ static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; + word16 idx; + word16 extEnd; + word16 extLen; + word16 extType; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); @@ -18519,12 +18569,11 @@ static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, /* 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 */ + /* this callback will verify that the correct SNI is being processed */ 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), @@ -18535,6 +18584,32 @@ static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, wolfSSL_SetEchEnable(test_ctx.c_ssl, enableClient); if (!enableClient) { + /* force HRR: the server must not answer with ECH when it wasn't + * offered */ + ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(test_ctx.s_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ctx.s_ssl->options.serverState, + SERVER_HELLO_RETRY_REQUEST_COMPLETE); + + /* walk the HRR extensions in c_buff */ + idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ + OPAQUE16_LEN + RAN_LEN; + idx += OPAQUE8_LEN + test_ctx.c_buff[idx] + OPAQUE16_LEN + OPAQUE8_LEN; + ato16(test_ctx.c_buff + idx, &extLen); + idx += OPAQUE16_LEN; + extEnd = idx + extLen; + ExpectIntLE(extEnd, test_ctx.c_len); + while (EXPECT_SUCCESS() && idx < extEnd) { + ato16(test_ctx.c_buff + idx, &extType); + ExpectIntNE(extType, TLSXT_ECH); + ato16(test_ctx.c_buff + idx + OPAQUE16_LEN, &extLen); + idx += 2 * OPAQUE16_LEN + extLen; + } + /* 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), @@ -18550,6 +18625,8 @@ static int test_wolfSSL_Tls13_ECH_disable_conn_ex(int enableServer, TEST_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), WOLFSSL_ECH_STATUS_REJECTED); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), + WC_NO_ERR_TRACE(ECH_REQUIRED_E)); } ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), WOLFSSL_ECH_STATUS_NOT_OFFERED); @@ -18582,6 +18659,8 @@ static int test_wolfSSL_Tls13_ECH_HRR_rejection(void) EXPECT_DECLS; test_ssl_memio_ctx test_ctx; + /* this test is somewhat adversarial, a server hosting the public name + * implies an ECH server but this one does not support ECH */ XMEMSET(&test_ctx, 0, sizeof(test_ctx)); test_ctx.s_cb.method = wolfTLSv1_3_server_method; @@ -18589,21 +18668,19 @@ static int test_wolfSSL_Tls13_ECH_HRR_rejection(void) /* Server generates ECH config with good public name */ test_ctx.s_cb.ctx_ready = test_ech_server_ctx_ready; - /* Client sets the correct ECH config and private SNI */ + /* Client sets the correct ECH config and a private SNI */ test_ctx.c_cb.ssl_ready = test_ech_client_ssl_ready; ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); - /* Server must not require a client certificate */ + /* client sends no cert on rejection */ wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); - wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); /* Disable ECH on the server SSL object: the server ignores ECH in CH1 and * sends HRR without an ECH extension (confBuf stays NULL on the client) */ wolfSSL_SetEchEnable(test_ctx.s_ssl, 0); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME, - echPublicName, (word16)XSTRLEN(echPublicName)), - WOLFSSL_SUCCESS); + echPublicName, (word16)XSTRLEN(echPublicName)), WOLFSSL_SUCCESS); /* Force HRR so client receives HRR with no ECH extension, * detects confBuf == NULL and frees hsHashesEch, falling back to the @@ -18614,18 +18691,29 @@ static int test_wolfSSL_Tls13_ECH_HRR_rejection(void) word16 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; /* One round: client sends CH1, server consumes it and writes HRR */ - (void)test_ssl_memio_do_handshake(&test_ctx, 1, NULL); + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ech_assert_wire_sni(test_ctx.s_buff, echPublicName), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_accept(test_ctx.s_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); /* Client reads HRR and writes CH2 into s_buff. * CH2 carries encrypted_client_hello so the connection doesn't * 'stick out' on the wire. */ - (void)wolfSSL_connect(test_ctx.c_ssl); + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), WOLFSSL_ECH_STATUS_REJECTED); /* hsHashesEch must have been freed by the HRR rejection code path */ ExpectNull(test_ctx.c_ssl->hsHashesEch); ExpectIntEQ(ech_find_extension(test_ctx.s_buff, &idx, TLSXT_ECH), 0); + ExpectIntEQ(test_ech_assert_wire_sni(test_ctx.s_buff, echPublicName), + TEST_SUCCESS); } /* Finish handshake: client aborts with ech_required */ @@ -18666,8 +18754,12 @@ static int test_wolfSSL_Tls13_ECH_ch2_no_ech(void) ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); /* one round: client sends CH1, server processes it and sends HRR */ - (void)test_ssl_memio_do_handshake(&test_ctx, 1, NULL); - ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0), WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(wolfSSL_accept(test_ctx.s_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); /* server must have committed to ECH acceptance in the HRR */ ExpectIntEQ(test_ctx.s_ssl->options.serverState, @@ -18713,7 +18805,14 @@ static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void) ExpectIntEQ(wolfSSL_NoKeyShares(test_ctx.c_ssl), WOLFSSL_SUCCESS); /* One round: client sends CH1, server processes it and sends HRR */ - (void)test_ssl_memio_do_handshake(&test_ctx, 1, NULL); + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ech_assert_wire_sni(test_ctx.s_buff, echPublicName), + TEST_SUCCESS); + ExpectIntEQ(wolfSSL_accept(test_ctx.s_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); ExpectIntEQ(test_ctx.s_ssl->options.serverState, SERVER_HELLO_RETRY_REQUEST_COMPLETE); @@ -18724,7 +18823,11 @@ static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void) if (EXPECT_SUCCESS()) { /* Client reads HRR and writes CH2 into s_buff */ - (void)wolfSSL_connect(test_ctx.c_ssl); + ExpectIntEQ(wolfSSL_connect(test_ctx.c_ssl), WOLFSSL_FATAL_ERROR); + ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), + WOLFSSL_ERROR_WANT_READ); + ExpectIntEQ(test_ech_assert_wire_sni(test_ctx.s_buff, echPublicName), + TEST_SUCCESS); /* Corrupt one byte of the ECH ciphertext in the CH2 record in s_buff. * ECH outer extension layout after the 0xFE0D type marker: @@ -18739,7 +18842,7 @@ static int test_wolfSSL_Tls13_ECH_ch2_decrypt_error(void) * hpkeContext is preserved, TLSX_ECH_Parse correctly identifies the CH2 * round and sends decrypt_error. */ if (EXPECT_SUCCESS()) { - (void)wolfSSL_accept(test_ctx.s_ssl); + ExpectIntEQ(wolfSSL_accept(test_ctx.s_ssl), WOLFSSL_FATAL_ERROR); ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0), WC_NO_ERR_TRACE(DECRYPT_ERROR)); } @@ -18758,52 +18861,41 @@ static int test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex(const char* publicName, { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; - byte echConfigs[ECH_CONFIG_LEN]; - word32 echConfigsLen = sizeof(echConfigs); 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 hosts the private name */ + test_ctx.s_cb.ssl_ready = test_ech_server_ssl_ready; ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); - /* Generate ECH config with given public_name */ + /* Server has its own ECH config with the given public_name */ ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(test_ctx.s_ctx, publicName, 0, 0, 0), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(test_ctx.s_ctx, echConfigs, - &echConfigsLen), WOLFSSL_SUCCESS); - /* Client loads ECH configs and sets a private SNI */ - ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, echConfigs, - echConfigsLen), WOLFSSL_SUCCESS); + /* bad config derived from the server's real one to force ECH rejection */ + ExpectIntEQ(test_ech_set_bad_echconfigs(test_ctx.s_ctx, test_ctx.c_ssl), + TEST_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - "ech-private.com", (word16)XSTRLEN("ech-private.com")), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); - /* Do not require client cert on server so it does not send - * CertificateRequest */ + /* client sends no cert on rejection */ wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_NONE, NULL); wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); - /* Disable ECH on the server side so ECH is rejected */ - wolfSSL_SetEchEnable(test_ctx.s_ssl, 0); - - /* Match the server SNI to the ECH public_name */ - ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME, - publicName, (word16)XSTRLEN(publicName)), WOLFSSL_SUCCESS); - - /* client sends ECH but server can't process it, however it is possible to - * fall back to the outer handshake */ + /* client sends ECH but server can't decrypt it, falls back to the outer + * handshake */ ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), - WOLFSSL_ECH_STATUS_NOT_OFFERED); + WOLFSSL_ECH_STATUS_REJECTED); if (validName) { /* the server should see the handshake as successful - * the client should abort because the server did not use ECH */ + * the client should abort because the server rejected ECH */ ExpectIntEQ(wolfSSL_get_error(test_ctx.s_ssl, 0), WC_NO_ERR_TRACE(WOLFSSL_ERROR_NONE)); ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0), @@ -18826,9 +18918,10 @@ static int test_wolfSSL_Tls13_ECH_rejected_cert_valid(void) { EXPECT_DECLS; - /* "example.com" appears in the SAN of certs/server-cert.pem */ - ExpectIntEQ(test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex("example.com", 1), + /* The cert holds echPublicName, the client should accept */ + ExpectIntEQ(test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex(echPublicName, 1), TEST_SUCCESS); + /* The cert holds echPublicName, the client public SNI does not match */ ExpectIntEQ(test_wolfSSL_Tls13_ECH_rejected_cert_valid_ex("badname.com", 0), TEST_SUCCESS); @@ -18841,46 +18934,32 @@ static int test_wolfSSL_Tls13_ECH_rejected_empty_client_cert(void) { EXPECT_DECLS; test_ssl_memio_ctx test_ctx; - byte echConfigs[ECH_CONFIG_LEN]; - word32 echConfigsLen = sizeof(echConfigs); - const char* publicName = "example.com"; 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); - /* Generate ECH config with public_name matching the server cert SAN */ - ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(test_ctx.s_ctx, publicName, - 0, 0, 0), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(test_ctx.s_ctx, echConfigs, - &echConfigsLen), WOLFSSL_SUCCESS); - - /* Client loads ECH configs and sets a private SNI */ - ExpectIntEQ(wolfSSL_SetEchConfigs(test_ctx.c_ssl, echConfigs, - echConfigsLen), WOLFSSL_SUCCESS); + /* bad config derived from the server's real one to force ECH rejection */ + ExpectIntEQ(test_ech_set_bad_echconfigs(test_ctx.s_ctx, test_ctx.c_ssl), + TEST_SUCCESS); ExpectIntEQ(wolfSSL_UseSNI(test_ctx.c_ssl, WOLFSSL_SNI_HOST_NAME, - "ech-private.com", (word16)XSTRLEN("ech-private.com")), - WOLFSSL_SUCCESS); + echPrivateName, (word16)XSTRLEN(echPrivateName)), WOLFSSL_SUCCESS); + /* Server requests client cert, on reject the client returns an empty one */ wolfSSL_set_verify(test_ctx.s_ssl, WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL); wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_PEER, NULL); - /* Disable ECH on the server so ECH is rejected */ - wolfSSL_SetEchEnable(test_ctx.s_ssl, 0); - - /* Match the Server SNI to the ECH public_name */ - ExpectIntEQ(wolfSSL_UseSNI(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME, - publicName, (word16)XSTRLEN(publicName)), WOLFSSL_SUCCESS); - ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), TEST_SUCCESS); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.c_ssl), WOLFSSL_ECH_STATUS_REJECTED); ExpectIntEQ(wolfSSL_GetEchStatus(test_ctx.s_ssl), - WOLFSSL_ECH_STATUS_NOT_OFFERED); + WOLFSSL_ECH_STATUS_REJECTED); /* Server cert is valid for public_name, cert check passes, ech_required * is sent on the client side. */ @@ -18897,9 +18976,25 @@ static int test_wolfSSL_Tls13_ECH_rejected_empty_client_cert(void) return EXPECT_RESULT(); } +/* test vector for TLSX_Parse */ +typedef struct echSniVec { + const char* desc; /* human-readable label */ + const char* sSNI; /* server-side SNI */ + const char* innerName; /* client private SNI */ + const char* outerName; /* client public SNI */ + const char* authoritative; /* value expected from wolfSSL_SNI_GetRequest */ + int outerRet; /* expected return from parsing the outer CH */ + int innerRet; /* expected return from parsing the inner CH */ + int reject; /* setup client's ECH to be rejected */ + byte sniOpt; /* options for the server SNI */ + byte reorder; /* swap SNI/ECH so ECH parses first */ + byte reparseSni; /* re-parse outer SNI (post-HRR) */ +} echSniVec; + /* Install ECH on the server and initialize the ECH AAD, then run TLSX_Parse. * ECH acceptance will be determined during TLSX_Parse */ -static int ech_server_parse_outer_ch(WOLFSSL* ssl, byte* chRecord) +static int ech_server_parse_outer_ch(WOLFSSL* ssl, byte* chRecord, + const echSniVec* v) { byte* chBody = chRecord + RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; TLSX* echX; @@ -18908,6 +19003,13 @@ static int ech_server_parse_outer_ch(WOLFSSL* ssl, byte* chRecord) word16 extOff; int ret; + /* reorder now only if rejection is desired */ + if (v->reorder && v->reject) { + ret = ech_swap_extensions(chBody, TLSXT_SERVER_NAME, TLSXT_ECH); + if (ret != 0) + return ret; + } + ret = TLSX_ServerECH_Use(&ssl->extensions, ssl->heap, ssl->ctx->echConfigs); if (ret != 0) return ret; @@ -18930,7 +19032,7 @@ static int ech_server_parse_outer_ch(WOLFSSL* ssl, byte* chRecord) } /* If ECH is accepted, run the decrypted inner extensions through TLSX_Parse */ -static int ech_server_parse_inner_ch(WOLFSSL* ssl) +static int ech_server_parse_inner_ch(WOLFSSL* ssl, const echSniVec* v) { TLSX* echX = TLSX_Find(ssl->extensions, TLSX_ECH); WOLFSSL_ECH* ech; @@ -18946,6 +19048,12 @@ static int ech_server_parse_inner_ch(WOLFSSL* ssl) return WOLFSSL_FATAL_ERROR; innerBody = ech->innerClientHello + HANDSHAKE_HEADER_SZ; + if (v->reorder) { + ret = ech_swap_extensions(innerBody, TLSXT_SERVER_NAME, TLSXT_ECH); + if (ret != 0) + return ret; + } + ret = ech_seek_extensions(innerBody, &extLen); if (ret < 0) return ret; @@ -18959,18 +19067,25 @@ static int ech_server_parse_inner_ch(WOLFSSL* ssl) return ret; } -/* test vector for TLSX_Parse */ -typedef struct echSniVec { - const char* desc; /* human-readable label */ - const char* sSNI; /* server-side SNI */ - const char* innerName; /* client private SNI */ - const char* outerName; /* client public SNI */ - const char* authoritative; /* value expected from wolfSSL_SNI_GetRequest */ - int outerRet; /* expected return from parsing the outer CH */ - int innerRet; /* expected return from parsing the inner CH */ - int reject; /* setup client's ECH to be rejected */ - byte sniOpt; /* options for the server SNI */ -} echSniVec; +/* Re-parse the outer server_name through TLSX_Parse */ +static int ech_server_reparse_outer_sni(WOLFSSL* ssl, byte* chRecord) +{ + word16 idx = RECORD_HEADER_SZ + HANDSHAKE_HEADER_SZ; + word16 sniLen; + int ret; + + ret = ech_find_extension(chRecord, &idx, TLSXT_SERVER_NAME); + if (ret != 0) + return ret; + + /* idx is at the extension header: type(2) + length(2) + body */ + ato16(chRecord + idx + OPAQUE16_LEN, &sniLen); + + ret = TLSX_Parse(ssl, chRecord + idx, + (word16)(sniLen + OPAQUE16_LEN + OPAQUE16_LEN), client_hello, + (Suites*)WOLFSSL_SUITES(ssl)); + return ret; +} #ifdef WOLFSSL_ALWAYS_KEEP_SNI #define ECH_KEPT(name) (name) @@ -19048,7 +19163,7 @@ static int test_ech_sni_parse_vec(WOLFSSL_CTX* serverCtx, const echSniVec* v) ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, WOLFSSL_FATAL_ERROR), WOLFSSL_ERROR_WANT_READ); - ExpectIntEQ(ech_server_parse_outer_ch(test_ctx.s_ssl, test_ctx.s_buff), + ExpectIntEQ(ech_server_parse_outer_ch(test_ctx.s_ssl, test_ctx.s_buff, v), v->outerRet); /* ECH acceptance must line up */ @@ -19057,19 +19172,23 @@ static int test_ech_sni_parse_vec(WOLFSSL_CTX* serverCtx, const echSniVec* v) /* on accept the inner hello must be parsed to learn the private name */ doInner = (!v->reject && v->outerRet == 0); if (doInner) - ExpectIntEQ(ech_server_parse_inner_ch(test_ctx.s_ssl), v->innerRet); + ExpectIntEQ(ech_server_parse_inner_ch(test_ctx.s_ssl, v), v->innerRet); - /* verify the authoritative SNI has the correct name and state */ - if (v->outerRet == 0 && (!doInner || v->innerRet == 0)) { - wolfSSL_SNI_GetRequest(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME, &sniName); - if (v->authoritative != NULL) - ExpectStrEQ((const char*)sniName, v->authoritative); - else - ExpectNull(sniName); - ExpectIntEQ(wolfSSL_SNI_Status(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME), - ech_sni_expected_status(v)); + /* re-parse the outer SNI as a second (post-HRR) ClientHello would */ + if (v->reparseSni && v->outerRet == 0) { + ExpectIntEQ(ech_server_reparse_outer_sni(test_ctx.s_ssl, + test_ctx.s_buff), 0); } + /* verify the authoritative SNI has the correct name and state */ + wolfSSL_SNI_GetRequest(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME, &sniName); + if (v->authoritative != NULL) + ExpectStrEQ((const char*)sniName, v->authoritative); + else + ExpectNull(sniName); + ExpectIntEQ(wolfSSL_SNI_Status(test_ctx.s_ssl, WOLFSSL_SNI_HOST_NAME), + ech_sni_expected_status(v)); + test_ssl_memio_cleanup(&test_ctx); return EXPECT_RESULT(); @@ -19086,55 +19205,72 @@ static int test_wolfSSL_Tls13_ECH_sni_parse(void) /* --- ECH rejected: outer (public) name governs --- */ { "reject: no sSNI, outer=public", NULL, echPrivateName, echPublicName, - echPublicName, 0, 0, 1, 0 }, + echPublicName, 0, 0, 1, 0, 0, 0 }, /* "reject: sSNI=private, outer=public" is the generic ECH-reject * scenario (server keeps its private SNI, client falls back to the * public name); it is exercised by most ECH handshake tests. */ { "reject: sSNI=public, outer=public", echPublicName, echPrivateName, echPublicName, - echPublicName, 0, 0, 1, 0 }, + echPublicName, 0, 0, 1, 0, 0, 0 }, { "reject: no sSNI, outer=other", NULL, echPrivateName, echOtherName, - ECH_KEPT(echOtherName), 0, 0, 1, 0 }, + ECH_KEPT(echOtherName), 0, 0, 1, 0, 0, 0 }, { "reject: sSNI=private, outer=other", echPrivateName, echPrivateName, echOtherName, - NULL, WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E), 0, 1, 0 }, + NULL, WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E), 0, 1, 0, 0, 0 }, { "reject: sSNI=private+continue, outer=other", echPrivateName, echPrivateName, echOtherName, - NULL, 0, 0, 1, WOLFSSL_SNI_CONTINUE_ON_MISMATCH }, + NULL, 0, 0, 1, WOLFSSL_SNI_CONTINUE_ON_MISMATCH, 0, 0 }, { "reject: sSNI=private+answer, outer=other", echPrivateName, echPrivateName, echOtherName, - echOtherName, 0, 0, 1, WOLFSSL_SNI_ANSWER_ON_MISMATCH }, + echOtherName, 0, 0, 1, WOLFSSL_SNI_ANSWER_ON_MISMATCH, 0, 0 }, /* --- ECH accepted: inner (private) name governs --- */ { "accept: no sSNI, no inner", NULL, NULL, echPublicName, - NULL, 0, 0, 0, 0 }, + NULL, 0, 0, 0, 0, 0, 0 }, { "accept: no sSNI, inner=private", NULL, echPrivateName, echPublicName, - ECH_KEPT(echPrivateName), 0, 0, 0, 0 }, + ECH_KEPT(echPrivateName), 0, 0, 0, 0, 0, 0 }, /* "accept: sSNI=private, inner=private" is the generic ECH-accept * scenario (private inner name matches the server SNI); it is * exercised by most ECH handshake tests. */ + { "accept: sSNI=private, no inner", + echPrivateName, NULL, echPublicName, + NULL, 0, 0, 0, 0, 0, 0 }, { "accept: sSNI=private+abort, no inner", echPrivateName, NULL, echPublicName, NULL, 0, WC_NO_ERR_TRACE(SNI_ABSENT_ERROR), 0, - WOLFSSL_SNI_ABORT_ON_ABSENCE }, + WOLFSSL_SNI_ABORT_ON_ABSENCE, 0, 0 }, { "accept: sSNI=private, inner=other", echPrivateName, echOtherName, echPublicName, - NULL, 0, WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E), 0, 0 }, + NULL, 0, WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E), 0, 0, 0, 0 }, { "accept: sSNI=private+continue, inner=other", echPrivateName, echOtherName, echPublicName, - NULL, 0, 0, 0, WOLFSSL_SNI_CONTINUE_ON_MISMATCH }, + NULL, 0, 0, 0, WOLFSSL_SNI_CONTINUE_ON_MISMATCH, 0, 0 }, { "accept: sSNI=private+answer, inner=other", echPrivateName, echOtherName, echPublicName, - echOtherName, 0, 0, 0, WOLFSSL_SNI_ANSWER_ON_MISMATCH }, + echOtherName, 0, 0, 0, WOLFSSL_SNI_ANSWER_ON_MISMATCH, 0, 0 }, { "accept: sSNI=public, inner=public", echPublicName, echPublicName, echPublicName, - echPublicName, 0, 0, 0, 0 }, + echPublicName, 0, 0, 0, 0, 0, 0 }, { "accept: sSNI=public, inner=private", echPublicName, echPrivateName, echPublicName, - NULL, 0, WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E), 0, 0 }, + NULL, 0, WC_NO_ERR_TRACE(UNKNOWN_SNI_HOST_NAME_E), 0, 0, 0, 0 }, + + /* --- ECH accepted / rejected, reorder and reparse --- */ + { "reorder, reject: ECH before SNI", + echPrivateName, echPrivateName, echPublicName, + echPublicName, 0, 0, 1, 0, 1, 0 }, + { "reorder, accept: ECH before SNI", + echPrivateName, echPrivateName, echPublicName, + echPrivateName, 0, 0, 0, 0, 1, 0 }, + { "reparse, reject: outer SNI re-parsed, no sSNI", + NULL, echPrivateName, echPublicName, + echPublicName, 0, 0, 1, 0, 0, 1 }, + { "reparse, accept: outer SNI re-parsed, no sSNI", + NULL, echPrivateName, echPublicName, + ECH_KEPT(echPrivateName), 0, 0, 0, 0, 0, 1 }, }; /* One server CTX, reused by every vector. test_ssl_memio_setup treats a @@ -19193,6 +19329,7 @@ static int test_wolfSSL_Tls13_ECH_read_ahead(void) 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(wolfSSL_GetEchStatus(test_ctx.c_ssl), WOLFSSL_ECH_STATUS_ACCEPTED); @@ -19222,7 +19359,7 @@ static int test_wolfSSL_Tls13_ECH_enable_disable(void) /* test CTX level enable/disable */ ExpectNotNull(ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); - ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx, "public.com", 0, 0, 0), + ExpectIntEQ(wolfSSL_CTX_GenerateEchConfig(ctx, echPublicName, 0, 0, 0), WOLFSSL_SUCCESS); ExpectIntEQ(wolfSSL_CTX_GetEchConfigs(ctx, echConfigs, &echConfigsLen), WOLFSSL_SUCCESS); @@ -44001,7 +44138,6 @@ TEST_CASE testCases[] = { 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) @@ -44013,7 +44149,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_Tls13_ECH_new_config), TEST_DECL(test_wolfSSL_Tls13_ECH_trial_decrypt), TEST_DECL(test_wolfSSL_Tls13_ECH_GREASE), - TEST_DECL(test_wolfSSL_Tls13_ECH_wire_sni), + TEST_DECL(test_wolfSSL_Tls13_ECH_ctx_sni), TEST_DECL(test_wolfSSL_Tls13_ECH_disable_conn), TEST_DECL(test_wolfSSL_Tls13_ECH_HRR_rejection), TEST_DECL(test_wolfSSL_Tls13_ECH_ch2_no_ech), diff --git a/wolfssl/test.h b/wolfssl/test.h index 6c506a5e265..e3bbefd82e1 100644 --- a/wolfssl/test.h +++ b/wolfssl/test.h @@ -5221,6 +5221,33 @@ static WC_INLINE void EarlyDataStatus(WOLFSSL* ssl) } #endif /* WOLFSSL_EARLY_DATA */ +#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) +static WC_INLINE void PrintEchStatus(WOLFSSL* ssl) +{ + const char* status; + + switch (wolfSSL_GetEchStatus(ssl)) { + case WOLFSSL_ECH_STATUS_NOT_OFFERED: + status = "not offered"; + break; + case WOLFSSL_ECH_STATUS_GREASE: + status = "GREASE"; + break; + case WOLFSSL_ECH_STATUS_REJECTED: + status = "rejected"; + break; + case WOLFSSL_ECH_STATUS_ACCEPTED: + status = "accepted"; + break; + default: + status = "unknown"; + break; + } + + printf("ECH status: %s\n", status); +} +#endif /* WOLFSSL_TLS13 && HAVE_ECH */ + #if defined(HAVE_SESSION_TICKET) || defined (WOLFSSL_DTLS13) static WC_INLINE int process_handshake_messages(WOLFSSL* ssl, int blocking, int* zero_return)