From e1ff60887610b10d83dac097f714225f625034f5 Mon Sep 17 00:00:00 2001 From: David Garske Date: Tue, 30 Jun 2026 10:00:00 -0700 Subject: [PATCH] Intel QAT: add async hybrid PQC server key share regression test --- tests/api/test_tls13.c | 62 +++++++++++++++++++++++++++++++++++++++ tests/api/test_tls13.h | 4 ++- tests/utils.c | 14 +++++++++ wolfcrypt/src/async.c | 12 ++++++++ wolfssl/wolfcrypt/async.h | 4 +++ 5 files changed, 95 insertions(+), 1 deletion(-) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index eb9e0d5bbe..a691c90fc8 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -6226,6 +6226,68 @@ int test_tls13_pqc_hybrid_malformed_ecdh(void) return EXPECT_RESULT(); } +/* Regression test for the async hybrid PQC server key share. Drives a full + * TLS 1.3 P-256 + ML-KEM-768 handshake through the software async simulator + * while forcing the server's ECDH keygen to complete synchronously so that + * only the ECDH shared-secret derivation suspends. This "B-first" ordering is + * what Intel QAT exhibits and previously dropped the server's KEM ciphertext, + * failing the handshake with SSL_connect -173. */ +int test_tls13_pqc_hybrid_async_server(void) +{ + EXPECT_DECLS; +#if defined(WOLFSSL_TLS13) && defined(WOLFSSL_ASYNC_CRYPT) && \ + defined(WOLFSSL_ASYNC_CRYPT_SW) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_HAVE_MLKEM) && defined(WOLFSSL_PQC_HYBRIDS) && \ + !defined(WOLFSSL_MLKEM_NO_ENCAPSULATE) && \ + !defined(WOLFSSL_MLKEM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_MLKEM_NO_DECAPSULATE) && \ + !defined(WOLFSSL_NO_ML_KEM_768) && defined(HAVE_ECC) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) && \ + (!defined(NO_ECC256) || defined(HAVE_ALL_CURVES)) && !defined(NO_ECC_SECP) && \ + !defined(WC_ECC_NONBLOCK) && !defined(WOLFSSL_SP_NONBLOCK) + /* Non-blocking math livelocks the hybrid async re-drive; not applicable. */ + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + int group = WOLFSSL_SECP256R1MLKEM768; + int devId = INVALID_DEVID; + + /* Open the software async device so the handshake runs the async path. */ + ExpectIntEQ(wolfAsync_DevOpen(&devId), 0); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLSv1_3_client_method, wolfTLSv1_3_server_method), 0); + + ExpectIntEQ(wolfSSL_SetDevId(ssl_c, devId), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_SetDevId(ssl_s, devId), WOLFSSL_SUCCESS); + + /* Negotiate the P-256 + ML-KEM-768 hybrid group on both ends. */ + ExpectIntEQ(wolfSSL_set_groups(ssl_c, &group, 1), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_set_groups(ssl_s, &group, 1), WOLFSSL_SUCCESS); + + /* Force the server's ECDH keygen to run synchronously so that only the + * ECDH shared-secret derivation suspends (the QAT "B-first" ordering). */ + wolfAsync_SwForceSyncType(ASYNC_SW_ECC_MAKE); + + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 20, NULL), 0); + + /* Server selected and completed the hybrid group end-to-end. */ + ExpectIntEQ(ssl_s->namedGroup, WOLFSSL_SECP256R1MLKEM768); + + /* Restore default simulator ordering for subsequent tests. */ + wolfAsync_SwForceSyncType(ASYNC_SW_NONE); + + wolfSSL_free(ssl_c); + wolfSSL_CTX_free(ctx_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_s); + wolfAsync_DevClose(&devId); +#endif + return EXPECT_RESULT(); +} + /* Test that a TLS 1.3 NewSessionTicket with a ticket shorter than ID_LEN * (32 bytes) does not cause an unsigned integer underflow / OOB read in * SetTicket. Uses a full memio handshake, then injects a crafted diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index a4a52f2927..4ea8110825 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -99,6 +99,7 @@ int test_tls13_AEAD_limit_KU_aes128_gcm_sha256(void); int test_tls13_AEAD_limit_KU_aes256_gcm_sha384(void); int test_tls13_AEAD_limit_KU_aes128_ccm_sha256(void); int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); +int test_tls13_pqc_hybrid_async_server(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -175,6 +176,7 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void); TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_gcm_sha256), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes256_gcm_sha384), \ TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_sha256), \ - TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256) + TEST_DECL_GROUP("tls13", test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256), \ + TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/tests/utils.c b/tests/utils.c index 447746d277..a8566adc86 100644 --- a/tests/utils.c +++ b/tests/utils.c @@ -186,6 +186,13 @@ int test_memio_do_handshake(WOLFSSL *ssl_c, WOLFSSL *ssl_s, if (err == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) { /* retry non-blocking math */ } + #ifdef WOLFSSL_ASYNC_CRYPT + else if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) { + ret = wolfSSL_AsyncPoll(ssl_c, WOLF_POLL_FLAG_CHECK_HW); + if (ret < 0) + return -1; + } + #endif else if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE) { char buff[WOLFSSL_MAX_ERROR_SZ]; @@ -207,6 +214,13 @@ int test_memio_do_handshake(WOLFSSL *ssl_c, WOLFSSL *ssl_s, if (err == WC_NO_ERR_TRACE(MP_WOULDBLOCK)) { /* retry non-blocking math */ } + #ifdef WOLFSSL_ASYNC_CRYPT + else if (err == WC_NO_ERR_TRACE(WC_PENDING_E)) { + ret = wolfSSL_AsyncPoll(ssl_s, WOLF_POLL_FLAG_CHECK_HW); + if (ret < 0) + return -1; + } + #endif else if (err != WOLFSSL_ERROR_WANT_READ && err != WOLFSSL_ERROR_WANT_WRITE) { char buff[WOLFSSL_MAX_ERROR_SZ]; diff --git a/wolfcrypt/src/async.c b/wolfcrypt/src/async.c index 7ae35b3901..2c2a66cf86 100644 --- a/wolfcrypt/src/async.c +++ b/wolfcrypt/src/async.c @@ -67,6 +67,15 @@ static WC_ASYNC_DEV* wolfAsync_GetDev(WOLF_EVENT* event) /* Allow way to have async SW code included, and disabled at run-time */ static int wolfAsyncSwDisabled = 0; /* default off */ +/* Test hook: an op of this type runs synchronously instead of suspending. + * Default ASYNC_SW_NONE means no type is forced (no real op uses NONE). */ +static int wolfAsyncSwForceSyncType = ASYNC_SW_NONE; + +WOLFSSL_TEST_VIS void wolfAsync_SwForceSyncType(int type) +{ + wolfAsyncSwForceSyncType = type; +} + static int wolfAsync_DoSw(WC_ASYNC_DEV* asyncDev) { @@ -312,6 +321,9 @@ int wc_AsyncSwInit(WC_ASYNC_DEV* dev, int type) if (dev) { WC_ASYNC_SW* sw = &dev->sw; if (sw->type == ASYNC_SW_NONE) { + /* Test hook: force this op type to run synchronously. */ + if (type == wolfAsyncSwForceSyncType) + return 0; sw->type = type; return 1; } diff --git a/wolfssl/wolfcrypt/async.h b/wolfssl/wolfcrypt/async.h index c2f9163e2c..e1ac06d9f0 100644 --- a/wolfssl/wolfcrypt/async.h +++ b/wolfssl/wolfcrypt/async.h @@ -427,6 +427,10 @@ WOLFSSL_API int wc_AsyncSleep(word32 ms); #ifdef WOLFSSL_ASYNC_CRYPT_SW WOLFSSL_API int wc_AsyncSwInit(WC_ASYNC_DEV* dev, int type); + /* Test hook: force the given WC_ASYNC_SW_TYPE to complete synchronously + * (do not suspend) so the software simulator can reproduce a specific + * suspend ordering. Pass ASYNC_SW_NONE to disable. */ + WOLFSSL_TEST_VIS void wolfAsync_SwForceSyncType(int type); #endif /* Pthread Helpers */