diff --git a/src/internal.c b/src/internal.c index 3765426a327..17b9b226162 100644 --- a/src/internal.c +++ b/src/internal.c @@ -16239,12 +16239,23 @@ static int ProcessPeerCertsChainOCSPStatusCheck(WOLFSSL* ssl) } else return 0; - /* error when leaf cert doesn't have certificate status */ - if (csr->requests < 1 || csr->responses[0].length == 0) { + /* RFC 8446 4.4.2.1: when a server includes the status_request extension + * in its CertificateRequest, the client MAY return an OCSP response with + * its Certificate, but is not required to. Treat a missing or empty + * stapled response as a non-fatal condition by default and fall back to + * standard certificate validation. + * + * RFC 7633: a certificate carrying the TLS Feature extension with the + * status_request feature ("must-staple") asserts that a stapled OCSP + * response is required. The wolfSSL ocspMustStaple flag mirrors that + * policy at the verifier side. Only in those cases should the absence + * of a stapled response be reported as BAD_CERTIFICATE_STATUS_ERROR. */ + if (csr->requests < 1 || csr->responses[0].length == 0) { WOLFSSL_MSG("Leaf cert doesn't have certificate status."); - return BAD_CERTIFICATE_STATUS_ERROR; + if (SSL_CM(ssl)->ocspMustStaple) + return BAD_CERTIFICATE_STATUS_ERROR; + return 0; } - for (i = 0; i < csr->requests; i++) { if (csr->responses[i].length != 0) { ssl->status_request = 1; @@ -16450,7 +16461,12 @@ static int ProcessPeerCertLeafRevocation(WOLFSSL* ssl, ProcPeerCertArgs* args, WOLFSSL_MSG("Checking if ocsp needed"); - if (ssl->options.side == WOLFSSL_CLIENT_END) { + if (ssl->options.side == WOLFSSL_CLIENT_END + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH + || (ssl->options.side == WOLFSSL_SERVER_END + && ssl->options.handShakeDone) + #endif + ) { #ifndef NO_TLS #ifdef HAVE_CERTIFICATE_STATUS_REQUEST if (ssl->status_request) { diff --git a/src/ssl.c b/src/ssl.c index f5a309552c1..de4c2c6f63c 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -852,6 +852,7 @@ void FreeWriteDup(WOLFSSL* ssl) #endif #if defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) Free_HS_Hashes(ssl->dupWrite->postHandshakeHashState, ssl->heap); + Free_HS_Hashes(ssl->dupWrite->postHandshakeSyncedHashState, ssl->heap); { CertReqCtx* ctx = ssl->dupWrite->postHandshakeCertReqCtx; while (ctx != NULL) { @@ -1763,6 +1764,31 @@ static int wolfSSL_write_internal(WOLFSSL* ssl, const void* data, size_t sz) } return WOLFSSL_FATAL_ERROR; } + /* PHA response fully sent: publish the write side's updated + * transcript to the read side for the next PHA round. */ + if (ssl->hsHashes != NULL && ssl->dupWrite != NULL) { + int syncRet; + if (wc_LockMutex(&ssl->dupWrite->dupMutex) != 0) + return BAD_MUTEX_E; + syncRet = InitHandshakeHashesAndCopy(ssl, ssl->hsHashes, + &ssl->dupWrite->postHandshakeSyncedHashState); + if (syncRet != 0) { + /* On failure the copy may have left a partially + * initialized transcript. The read side only checks + * for non-NULL before consuming it, so drop it here to + * avoid hashing onto a corrupt transcript, and surface + * the error to the caller. */ + Free_HS_Hashes( + ssl->dupWrite->postHandshakeSyncedHashState, + ssl->heap); + ssl->dupWrite->postHandshakeSyncedHashState = NULL; + } + wc_UnLockMutex(&ssl->dupWrite->dupMutex); + if (syncRet != 0) { + ssl->error = syncRet; + return WOLFSSL_FATAL_ERROR; + } + } } #endif /* WOLFSSL_POST_HANDSHAKE_AUTH */ #ifdef WOLFSSL_DTLS13 diff --git a/src/tls.c b/src/tls.c index 69c2e0a710b..501b99a9748 100644 --- a/src/tls.c +++ b/src/tls.c @@ -3486,7 +3486,7 @@ word16 TLSX_CSR_GetSize_ex(CertificateStatusRequest* csr, byte isRequest, } #endif #if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_SERVER) - if (!isRequest && IsAtLeastTLSv1_3(csr->ssl->version)) { + if (!isRequest && csr->ssl != NULL && IsAtLeastTLSv1_3(csr->ssl->version)) { if (csr->ssl != NULL && SSL_CM(csr->ssl) != NULL && SSL_CM(csr->ssl)->ocsp_stapling != NULL && SSL_CM(csr->ssl)->ocsp_stapling->statusCb != NULL) { @@ -3626,7 +3626,7 @@ int TLSX_CSR_Write_ex(CertificateStatusRequest* csr, byte* output, } #endif #if defined(WOLFSSL_TLS13) && !defined(NO_WOLFSSL_SERVER) - if (!isRequest && IsAtLeastTLSv1_3(csr->ssl->version)) { + if (!isRequest && csr->ssl != NULL && IsAtLeastTLSv1_3(csr->ssl->version)) { word16 offset = 0; if (csr->ssl != NULL && SSL_CM(csr->ssl) != NULL && SSL_CM(csr->ssl)->ocsp_stapling != NULL && @@ -15404,8 +15404,9 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, break; case TLSX_STATUS_REQUEST: - length += CSR_GET_SIZE( - (CertificateStatusRequest*)extension->data, isRequest); + if (msgType != certificate_request) + length += CSR_GET_SIZE( + (CertificateStatusRequest*)extension->data, isRequest); break; case TLSX_STATUS_REQUEST_V2: @@ -15682,11 +15683,15 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, case TLSX_STATUS_REQUEST: WOLFSSL_MSG("Certificate Status Request extension to write"); - ret = CSR_WRITE((CertificateStatusRequest*)extension->data, - output + offset, isRequest); - if (ret > 0) { - offset += (word16)ret; + if (msgType == certificate_request) { ret = 0; + } else { + ret = CSR_WRITE((CertificateStatusRequest*)extension->data, + output + offset, isRequest); + if (ret > 0) { + offset += (word16)ret; + ret = 0; + } } break; @@ -17122,9 +17127,10 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } #endif - /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, OID_FILTERS - * TLSX_STATUS_REQUEST - */ + /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, OID_FILTERS */ + /* TLSX_STATUS_REQUEST is enabled: the server may request the client + * to staple an OCSP response with its CertificateRequest. */ + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); } #endif #if defined(HAVE_ECH) @@ -17345,11 +17351,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) TLSX_ToSemaphore(TLSX_CERTIFICATE_AUTHORITIES)); } #endif - /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, TLSX_OID_FILTERS - * TLSX_STATUS_REQUEST - */ + /* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, TLSX_OID_FILTERS */ + /* TLSX_STATUS_REQUEST is enabled: the server may request the client + * to staple an OCSP response with its CertificateRequest. */ + TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST)); } - #endif +#endif #endif #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) if (!ssl->options.disableECH && msgType == client_hello) { diff --git a/src/tls13.c b/src/tls13.c index d0e3b5b3419..6ee3f785a5e 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -9550,6 +9550,24 @@ static int SetupOcspResp(WOLFSSL* ssl) OcspRequest* request = NULL; extension = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST); +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* During post-handshake client authentication the client must staple + * its own OCSP response, but the status_request extension it offered in + * the initial ClientHello is no longer present on ssl->extensions. + * Recreate it here (the request extension still lives on + * ctx->extensions). Pass ssl so csr->ssl is set, which the response + * size/write path requires. */ + if (extension == NULL && + ssl->options.side == WOLFSSL_CLIENT_END && + ssl->options.handShakeDone && + TLSX_Find(ssl->ctx->extensions, TLSX_STATUS_REQUEST) != NULL) { + ret = TLSX_UseCertificateStatusRequest(&ssl->extensions, + WOLFSSL_CSR_OCSP, 0, ssl, ssl->heap, ssl->devId); + if (ret != WOLFSSL_SUCCESS) + return ret; + extension = TLSX_Find(ssl->extensions, TLSX_STATUS_REQUEST); + } +#endif if (extension == NULL) return 0; /* peer didn't signal ocsp support */ csr = (CertificateStatusRequest*)extension->data; @@ -9579,12 +9597,33 @@ static int SetupOcspResp(WOLFSSL* ssl) XFREE(cert, ssl->heap, DYNAMIC_TYPE_DCERT); return ret; } - ret = TLSX_CSR_InitRequest(ssl->extensions, cert, ssl->heap); +#ifdef WOLFSSL_POST_HANDSHAKE_AUTH + /* On client PHA the same certificate is re-stapled every round; reuse + * request slot 0 instead of appending, else csr->requests grows until + * MAX_CERT_EXTENSIONS_ERR. */ + if (ssl->options.side == WOLFSSL_CLIENT_END && ssl->options.handShakeDone) { + ret = TLSX_CSR_InitRequest_ex(ssl->extensions, cert, ssl->heap, 0); + } + else +#endif + { + ret = TLSX_CSR_InitRequest(ssl->extensions, cert, ssl->heap); + } FreeDecodedCert(cert); XFREE(cert, ssl->heap, DYNAMIC_TYPE_DCERT); if (ret != 0 ) return ret; + /* Free previous OCSP response buffers to avoid leak on PHA reuse */ + { + int j; + for (j = 0; j < MAX_CERT_EXTENSIONS; j++) { + XFREE(csr->responses[j].buffer, ssl->heap, + DYNAMIC_TYPE_TMP_BUFFER); + csr->responses[j].buffer = NULL; + csr->responses[j].length = 0; + } + } request = &csr->request.ocsp[0]; ret = CreateOcspResponse(ssl, &request, &csr->responses[0]); if (request != &csr->request.ocsp[0] && @@ -9697,10 +9736,16 @@ static int SendTls13Certificate(WOLFSSL* ssl) extSz[extIdx] = OPAQUE16_LEN; #if defined(HAVE_CERTIFICATE_STATUS_REQUEST) && !defined(NO_WOLFSSL_SERVER) - /* We only send CSR on the server side. On client side, the CSR data - * is populated with the server response. We would be sending the server - * its own stapling data. */ - if (ssl->options.side == WOLFSSL_SERVER_END) { + /* Staple our own OCSP response with the Certificate. Normally only the + * server staples; the client's CSR holds the server's response, so + * echoing it back is wrong. The exception is post-handshake auth (PHA), + * where the client sends its own Certificate and staples for it. */ + if (ssl->options.side == WOLFSSL_SERVER_END + #ifdef WOLFSSL_POST_HANDSHAKE_AUTH + || (ssl->options.side == WOLFSSL_CLIENT_END + && ssl->options.handShakeDone) + #endif + ) { ret = SetupOcspResp(ssl); if (ret != 0) return ret; @@ -14053,6 +14098,25 @@ int DoTls13HandShakeMsgType(WOLFSSL* ssl, byte* input, word32* inOutIdx, ssl->error = 0; } #endif +#if defined(HAVE_WRITE_DUP) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) + /* Read side: a fresh PHA CertificateRequest. Resume from the transcript + * the write side published after the previous PHA response, so this CR is + * hashed onto the same base the server has. */ + if (ret == 0 && type == certificate_request && + ssl->options.side == WOLFSSL_CLIENT_END && + ssl->dupSide == READ_DUP_SIDE && + ssl->options.handShakeState == HANDSHAKE_DONE && + ssl->dupWrite != NULL) { + if (wc_LockMutex(&ssl->dupWrite->dupMutex) != 0) + return BAD_MUTEX_E; + if (ssl->dupWrite->postHandshakeSyncedHashState != NULL) { + FreeHandshakeHashes(ssl); + ssl->hsHashes = ssl->dupWrite->postHandshakeSyncedHashState; + ssl->dupWrite->postHandshakeSyncedHashState = NULL; + } + wc_UnLockMutex(&ssl->dupWrite->dupMutex); + } +#endif /* HAVE_WRITE_DUP && WOLFSSL_POST_HANDSHAKE_AUTH */ if (ret == 0 && type != client_hello && type != session_ticket && type != key_update #if defined(WOLFSSL_DTLS13) && defined(WOLFSSL_DTLS_CID) diff --git a/tests/api/test_tls13.c b/tests/api/test_tls13.c index ed5ce88edcb..fcef867515c 100644 --- a/tests/api/test_tls13.c +++ b/tests/api/test_tls13.c @@ -8513,3 +8513,157 @@ int test_tls13_AEAD_limit_KU_aes128_ccm_8_sha256(void) return EXPECT_RESULT(); } +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) +/* Mock OCSP I/O callback that yields no response. It lets stapling be enabled + * without contacting a real responder, but may not even be invoked (the test + * certs need not advertise an OCSP responder URL). Either way no staple is + * produced, which is the "no staple available" path this test exercises. */ +static int test_pha_ocsp_io_cb(void* ioCtx, const char* url, int urlSz, + unsigned char* req, int reqSz, unsigned char** resp) +{ + (void)ioCtx; (void)url; (void)urlSz; (void)req; (void)reqSz; + *resp = NULL; + return 0; +} + +static void test_pha_ocsp_resp_free_cb(void* ioCtx, unsigned char* resp) +{ + (void)ioCtx; (void)resp; +} +#endif + +/* Post-Handshake Authentication combined with OCSP stapling + * (status_request) on the TLS 1.3 CertificateRequest message. + * + * Regression for two related issues: + * 1. The server's PHA CertificateRequest must include the + * status_request extension (with an empty body) when the client + * offered status_request in its ClientHello (RFC 8446 4.2 / 4.3.2). + * Without the fix the extension was suppressed and the size and + * write paths disagreed by 4 bytes, causing the client to send a + * decode_error alert (BUFFER_ERROR / -328). + * 2. The server-side OCSP-status check in ProcessPeerCerts must run + * for the PHA-received client Certificate. The check must also + * tolerate a missing/empty stapled response unless the verifier + * enforces must-staple, per RFC 8446 4.4.2.1 (client OCSP staple + * is MAY) and RFC 7633 (must-staple). Without the fix the server + * either skipped the check entirely or returned + * BAD_CERTIFICATE_STATUS_ERROR (-406) for the no-staple case. + * + * The test exercises a single TLS 1.3 connection: an initial handshake + * without client authentication, followed by a server-initiated PHA + * exchange. The server expects to receive (and verify) the client + * certificate even though no OCSP staple is supplied. + */ +int test_tls13_pha_status_request(void) +{ + EXPECT_DECLS; +#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \ + defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \ + defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \ + !defined(NO_CERTS) && !defined(NO_RSA) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; + WOLFSSL *ssl_c = NULL, *ssl_s = NULL; + WOLFSSL_X509 *peer = NULL; + const char msg[] = "ping"; + char buf[8]; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + + /* --- Client CTX ------------------------------------------------ */ + ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method())); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + /* Must opt in to PHA before wolfSSL_connect to add the + * post_handshake_auth extension to the ClientHello. */ + ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_c), WOLFSSL_SUCCESS); + wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); + + /* --- Server CTX ------------------------------------------------ */ + ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, + WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0), + WOLFSSL_SUCCESS); + /* Trust the client cert issuer as well, otherwise the PHA + * Certificate verification would fail with ASN_SELF_SIGNED_E. */ + ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, + "./certs/client-ca.pem", 0), WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_s), WOLFSSL_SUCCESS); + /* Mock callback: stapling negotiates but the response is empty. */ + ExpectIntEQ(wolfSSL_CTX_SetOCSP_Cb(ctx_s, test_pha_ocsp_io_cb, + test_pha_ocsp_resp_free_cb, NULL), WOLFSSL_SUCCESS); + /* Initial handshake: do not request the client certificate yet - + * the server promotes verification only when triggering PHA. */ + wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_NONE, NULL); + wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); + wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); + + /* --- SSL objects ----------------------------------------------- */ + ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); + wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); + /* Causes status_request in the ClientHello so that the server's + * PHA CertificateRequest re-emits the same extension. */ + ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl_c, WOLFSSL_CSR_OCSP, 0), + WOLFSSL_SUCCESS); + + ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); + wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); + wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); + + /* --- Initial handshake (no client cert requested) -------------- */ + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + ExpectNull(wolfSSL_get_peer_certificate(ssl_s)); + + /* --- Trigger PHA: server now requires the client certificate -- */ + if (EXPECT_SUCCESS()) { + wolfSSL_set_verify(ssl_s, + WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT, + NULL); + ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS); + } + + /* The server's wolfSSL_write below carries the PHA + * CertificateRequest record. The client's wolfSSL_read consumes + * the request, transmits Certificate/CertificateVerify/Finished + * and surfaces the application data to us. */ + ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf) - 1), + (int)sizeof(msg) - 1); + + /* The client's reply lets the server's wolfSSL_read drain the + * incoming PHA Certificate flight before the application data. */ + ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1), + (int)sizeof(msg) - 1); + ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf) - 1), + (int)sizeof(msg) - 1); + + /* PHA succeeded: the server now holds the client certificate. + * Reaching this point also implies the server tolerated the + * empty OCSP staple instead of failing with -406. */ + ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s)); + wolfSSL_X509_free(peer); + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} diff --git a/tests/api/test_tls13.h b/tests/api/test_tls13.h index fdb733d1ee2..52a6f9e454e 100644 --- a/tests/api/test_tls13.h +++ b/tests/api/test_tls13.h @@ -102,6 +102,7 @@ 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); +int test_tls13_pha_status_request(void); #define TEST_TLS13_DECLS \ TEST_DECL_GROUP("tls13", test_tls13_apis), \ @@ -181,6 +182,7 @@ int test_tls13_pqc_hybrid_async_server(void); 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_pqc_hybrid_async_server) + TEST_DECL_GROUP("tls13", test_tls13_pqc_hybrid_async_server), \ + TEST_DECL_GROUP("tls13", test_tls13_pha_status_request) #endif /* WOLFCRYPT_TEST_TLS13_H */ diff --git a/wolfssl/internal.h b/wolfssl/internal.h index d54496e0f9c..49fca98f5e8 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -5950,6 +5950,10 @@ typedef struct BuildMsgArgs { byte postHandshakeSendVerify; /* ssl->options.sendVerify */ byte postHandshakeSigAlgo; /* ssl->options.sigAlgo */ byte postHandshakeHashAlgo; /* ssl->options.hashAlgo */ + /* After the write side sends the PHA response, it stores its updated + * transcript here so the read side can resume from it on the next + * CertificateRequest (keeps client/server transcript in sync). */ + struct HS_Hashes* postHandshakeSyncedHashState; #endif /* WOLFSSL_POST_HANDSHAKE_AUTH */ #endif /* WOLFSSL_TLS13 */