Skip to content

Commit 6da3008

Browse files
committed
Post handsake authentication with client end OCSP
1 parent bf6c870 commit 6da3008

5 files changed

Lines changed: 203 additions & 14 deletions

File tree

src/internal.c

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15692,12 +15692,23 @@ static int ProcessPeerCertsChainOCSPStatusCheck(WOLFSSL* ssl)
1569215692
} else
1569315693
return 0;
1569415694

15695-
/* error when leaf cert doesn't have certificate status */
15696-
if (csr->requests < 1 || csr->responses[0].length == 0) {
15697-
WOLFSSL_MSG("Leaf cert doesn't have certificate status.");
15698-
return BAD_CERTIFICATE_STATUS_ERROR;
15695+
/* RFC 8446 §4.4.2.1: when a server includes the status_request extension
15696+
* in its CertificateRequest, the client MAY return an OCSP response with
15697+
* its Certificate, but is not required to. Treat a missing or empty
15698+
* stapled response as a non-fatal condition by default and fall back to
15699+
* standard certificate validation.
15700+
*
15701+
* RFC 7633: a certificate carrying the TLS Feature extension with the
15702+
* status_request feature ("must-staple") asserts that a stapled OCSP
15703+
* response is required. The wolfSSL ocspMustStaple flag mirrors that
15704+
* policy at the verifier side. Only in those cases should the absence
15705+
* of a stapled response be reported as BAD_CERTIFICATE_STATUS_ERROR. */
15706+
if (csr->requests < 1 || csr->responses[0].length == 0) {
15707+
WOLFSSL_MSG("Leaf cert doesn't have certificate status.");
15708+
if (SSL_CM(ssl)->ocspMustStaple)
15709+
return BAD_CERTIFICATE_STATUS_ERROR;
15710+
return 0;
1569915711
}
15700-
1570115712
for (i = 0; i < csr->requests; i++) {
1570215713
if (csr->responses[i].length != 0) {
1570315714
ssl->status_request = 1;
@@ -16754,7 +16765,12 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
1675416765

1675516766
WOLFSSL_MSG("Checking if ocsp needed");
1675616767

16757-
if (ssl->options.side == WOLFSSL_CLIENT_END) {
16768+
if (ssl->options.side == WOLFSSL_CLIENT_END
16769+
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
16770+
|| (ssl->options.side == WOLFSSL_SERVER_END
16771+
&& ssl->options.handShakeDone)
16772+
#endif
16773+
) {
1675816774
#ifndef NO_TLS
1675916775
#ifdef HAVE_CERTIFICATE_STATUS_REQUEST
1676016776
if (ssl->status_request) {

src/tls.c

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14882,8 +14882,9 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1488214882
break;
1488314883

1488414884
case TLSX_STATUS_REQUEST:
14885-
length += CSR_GET_SIZE(
14886-
(CertificateStatusRequest*)extension->data, isRequest);
14885+
if (msgType != certificate_request)
14886+
length += CSR_GET_SIZE(
14887+
(CertificateStatusRequest*)extension->data, isRequest);
1488714888
break;
1488814889

1488914890
case TLSX_STATUS_REQUEST_V2:
@@ -15160,11 +15161,15 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1516015161

1516115162
case TLSX_STATUS_REQUEST:
1516215163
WOLFSSL_MSG("Certificate Status Request extension to write");
15163-
ret = CSR_WRITE((CertificateStatusRequest*)extension->data,
15164-
output + offset, isRequest);
15165-
if (ret > 0) {
15166-
offset += (word16)ret;
15164+
if (msgType == certificate_request) {
1516715165
ret = 0;
15166+
} else {
15167+
ret = CSR_WRITE((CertificateStatusRequest*)extension->data,
15168+
output + offset, isRequest);
15169+
if (ret > 0) {
15170+
offset += (word16)ret;
15171+
ret = 0;
15172+
}
1516815173
}
1516915174
break;
1517015175

@@ -16555,6 +16560,7 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1655516560
/* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, OID_FILTERS
1655616561
* TLSX_STATUS_REQUEST
1655716562
*/
16563+
TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST));
1655816564
}
1655916565
#endif
1656016566
#if defined(HAVE_ECH)
@@ -16781,8 +16787,9 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1678116787
/* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, TLSX_OID_FILTERS
1678216788
* TLSX_STATUS_REQUEST
1678316789
*/
16790+
TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST));
1678416791
}
16785-
#endif
16792+
#endif
1678616793
#endif
1678716794
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
1678816795
if (ssl->echConfigs != NULL && !ssl->options.disableECH

src/tls13.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9371,6 +9371,16 @@ static int SetupOcspResp(WOLFSSL* ssl)
93719371
if (ret != 0 )
93729372
return ret;
93739373

9374+
/* Free previous OCSP response buffers to avoid leak on PHA reuse */
9375+
{
9376+
int j;
9377+
for (j = 0; j < MAX_CERT_EXTENSIONS; j++) {
9378+
XFREE(csr->responses[j].buffer, ssl->heap,
9379+
DYNAMIC_TYPE_TMP_BUFFER);
9380+
csr->responses[j].buffer = NULL;
9381+
csr->responses[j].length = 0;
9382+
}
9383+
}
93749384
request = &csr->request.ocsp[0];
93759385
ret = CreateOcspResponse(ssl, &request, &csr->responses[0]);
93769386
if (request != &csr->request.ocsp[0] &&

tests/api/test_tls13.c

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5873,3 +5873,157 @@ int test_tls13_clear_preserves_psk_dhe(void)
58735873
#endif
58745874
return EXPECT_RESULT();
58755875
}
5876+
5877+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
5878+
defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
5879+
defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \
5880+
!defined(NO_CERTS) && !defined(NO_RSA) && \
5881+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
5882+
/* Mock OCSP I/O callback: returns 0 bytes so the server stapling slot
5883+
* stays empty. This intentionally exercises the "no staple available"
5884+
* path on both peers. */
5885+
static int test_pha_ocsp_io_cb(void* ioCtx, const char* url, int urlSz,
5886+
unsigned char* req, int reqSz, unsigned char** resp)
5887+
{
5888+
(void)ioCtx; (void)url; (void)urlSz; (void)req; (void)reqSz;
5889+
*resp = NULL;
5890+
return 0;
5891+
}
5892+
5893+
static void test_pha_ocsp_resp_free_cb(void* ioCtx, unsigned char* resp)
5894+
{
5895+
(void)ioCtx; (void)resp;
5896+
}
5897+
#endif
5898+
5899+
/* Post-Handshake Authentication combined with OCSP stapling
5900+
* (status_request) on the TLS 1.3 CertificateRequest message.
5901+
*
5902+
* Regression for two related issues:
5903+
* 1. The server's PHA CertificateRequest must include the
5904+
* status_request extension (with an empty body) when the client
5905+
* offered status_request in its ClientHello (RFC 8446 4.2 / 4.3.2).
5906+
* Without the fix the extension was suppressed and the size and
5907+
* write paths disagreed by 4 bytes, causing the client to send a
5908+
* decode_error alert (BUFFER_ERROR / -328).
5909+
* 2. The server-side OCSP-status check in ProcessPeerCerts must run
5910+
* for the PHA-received client Certificate. The check must also
5911+
* tolerate a missing/empty stapled response unless the verifier
5912+
* enforces must-staple, per RFC 8446 4.4.2.1 (client OCSP staple
5913+
* is MAY) and RFC 7633 (must-staple). Without the fix the server
5914+
* either skipped the check entirely or returned
5915+
* BAD_CERTIFICATE_STATUS_ERROR (-406) for the no-staple case.
5916+
*
5917+
* The test exercises a single TLS 1.3 connection: an initial handshake
5918+
* without client authentication, followed by a server-initiated PHA
5919+
* exchange. The server expects to receive (and verify) the client
5920+
* certificate even though no OCSP staple is supplied.
5921+
*/
5922+
int test_tls13_pha_status_request(void)
5923+
{
5924+
EXPECT_DECLS;
5925+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
5926+
defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
5927+
defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \
5928+
!defined(NO_CERTS) && !defined(NO_RSA) && \
5929+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
5930+
struct test_memio_ctx test_ctx;
5931+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
5932+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
5933+
WOLFSSL_X509 *peer = NULL;
5934+
const char msg[] = "ping";
5935+
char buf[8];
5936+
5937+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
5938+
5939+
/* --- Client CTX ------------------------------------------------ */
5940+
ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
5941+
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0),
5942+
WOLFSSL_SUCCESS);
5943+
ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile,
5944+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
5945+
ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile,
5946+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
5947+
/* Must opt in to PHA before wolfSSL_connect to add the
5948+
* post_handshake_auth extension to the ClientHello. */
5949+
ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0);
5950+
ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_c), WOLFSSL_SUCCESS);
5951+
wolfSSL_SetIORecv(ctx_c, test_memio_read_cb);
5952+
wolfSSL_SetIOSend(ctx_c, test_memio_write_cb);
5953+
5954+
/* --- Server CTX ------------------------------------------------ */
5955+
ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
5956+
ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile,
5957+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
5958+
ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile,
5959+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
5960+
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0),
5961+
WOLFSSL_SUCCESS);
5962+
/* Trust the client cert issuer as well, otherwise the PHA
5963+
* Certificate verification would fail with ASN_SELF_SIGNED_E. */
5964+
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s,
5965+
"./certs/client-ca.pem", 0), WOLFSSL_SUCCESS);
5966+
ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_s), WOLFSSL_SUCCESS);
5967+
/* Mock callback: stapling negotiates but the response is empty. */
5968+
ExpectIntEQ(wolfSSL_CTX_SetOCSP_Cb(ctx_s, test_pha_ocsp_io_cb,
5969+
test_pha_ocsp_resp_free_cb, NULL), WOLFSSL_SUCCESS);
5970+
/* Initial handshake: do not request the client certificate yet -
5971+
* the server promotes verification only when triggering PHA. */
5972+
wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_NONE, NULL);
5973+
wolfSSL_SetIORecv(ctx_s, test_memio_read_cb);
5974+
wolfSSL_SetIOSend(ctx_s, test_memio_write_cb);
5975+
5976+
/* --- SSL objects ----------------------------------------------- */
5977+
ExpectNotNull(ssl_c = wolfSSL_new(ctx_c));
5978+
wolfSSL_SetIOReadCtx(ssl_c, &test_ctx);
5979+
wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx);
5980+
/* Causes status_request in the ClientHello so that the server's
5981+
* PHA CertificateRequest re-emits the same extension. */
5982+
ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl_c, WOLFSSL_CSR_OCSP, 0),
5983+
WOLFSSL_SUCCESS);
5984+
5985+
ExpectNotNull(ssl_s = wolfSSL_new(ctx_s));
5986+
wolfSSL_SetIOReadCtx(ssl_s, &test_ctx);
5987+
wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx);
5988+
5989+
/* --- Initial handshake (no client cert requested) -------------- */
5990+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
5991+
ExpectNull(wolfSSL_get_peer_certificate(ssl_s));
5992+
5993+
/* --- Trigger PHA: server now requires the client certificate -- */
5994+
if (EXPECT_SUCCESS()) {
5995+
wolfSSL_set_verify(ssl_s,
5996+
WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT,
5997+
NULL);
5998+
ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS);
5999+
}
6000+
6001+
/* The server's wolfSSL_write below carries the PHA
6002+
* CertificateRequest record. The client's wolfSSL_read consumes
6003+
* the request, transmits Certificate/CertificateVerify/Finished
6004+
* and surfaces the application data to us. */
6005+
ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg) - 1),
6006+
(int)sizeof(msg) - 1);
6007+
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf) - 1),
6008+
(int)sizeof(msg) - 1);
6009+
6010+
/* The client's reply lets the server's wolfSSL_read drain the
6011+
* incoming PHA Certificate flight before the application data. */
6012+
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1),
6013+
(int)sizeof(msg) - 1);
6014+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf) - 1),
6015+
(int)sizeof(msg) - 1);
6016+
6017+
/* PHA succeeded: the server now holds the client certificate.
6018+
* Reaching this point also implies the server tolerated the
6019+
* empty OCSP staple instead of failing with -406. */
6020+
ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s));
6021+
wolfSSL_X509_free(peer);
6022+
6023+
wolfSSL_free(ssl_c);
6024+
wolfSSL_free(ssl_s);
6025+
wolfSSL_CTX_free(ctx_c);
6026+
wolfSSL_CTX_free(ctx_s);
6027+
#endif
6028+
return EXPECT_RESULT();
6029+
}

tests/api/test_tls13.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ int test_tls13_cert_with_extern_psk_sh_missing_key_share(void);
6868
int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
6969
int test_tls13_ticket_peer_cert_reverify(void);
7070
int test_tls13_clear_preserves_psk_dhe(void);
71+
int test_tls13_pha_status_request(void);
7172

7273
#define TEST_TLS13_DECLS \
7374
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@@ -113,6 +114,7 @@ int test_tls13_clear_preserves_psk_dhe(void);
113114
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \
114115
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \
115116
TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \
116-
TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe)
117+
TEST_DECL_GROUP("tls13", test_tls13_clear_preserves_psk_dhe), \
118+
TEST_DECL_GROUP("tls13", test_tls13_pha_status_request)
117119

118120
#endif /* WOLFCRYPT_TEST_TLS13_H */

0 commit comments

Comments
 (0)