Skip to content

Commit 665fb81

Browse files
committed
Post handsake authentication with client end OCSP
1 parent 7827872 commit 665fb81

5 files changed

Lines changed: 202 additions & 13 deletions

File tree

src/internal.c

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15860,12 +15860,23 @@ static int ProcessPeerCertsChainOCSPStatusCheck(WOLFSSL* ssl)
1586015860
} else
1586115861
return 0;
1586215862

15863-
/* error when leaf cert doesn't have certificate status */
15864-
if (csr->requests < 1 || csr->responses[0].length == 0) {
15863+
/* RFC 8446 4.4.2.1: when a server includes the status_request extension
15864+
* in its CertificateRequest, the client MAY return an OCSP response with
15865+
* its Certificate, but is not required to. Treat a missing or empty
15866+
* stapled response as a non-fatal condition by default and fall back to
15867+
* standard certificate validation.
15868+
*
15869+
* RFC 7633: a certificate carrying the TLS Feature extension with the
15870+
* status_request feature ("must-staple") asserts that a stapled OCSP
15871+
* response is required. The wolfSSL ocspMustStaple flag mirrors that
15872+
* policy at the verifier side. Only in those cases should the absence
15873+
* of a stapled response be reported as BAD_CERTIFICATE_STATUS_ERROR. */
15874+
if (csr->requests < 1 || csr->responses[0].length == 0) {
1586515875
WOLFSSL_MSG("Leaf cert doesn't have certificate status.");
15866-
return BAD_CERTIFICATE_STATUS_ERROR;
15876+
if (SSL_CM(ssl)->ocspMustStaple)
15877+
return BAD_CERTIFICATE_STATUS_ERROR;
15878+
return 0;
1586715879
}
15868-
1586915880
for (i = 0; i < csr->requests; i++) {
1587015881
if (csr->responses[i].length != 0) {
1587115882
ssl->status_request = 1;
@@ -16922,7 +16933,12 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
1692216933

1692316934
WOLFSSL_MSG("Checking if ocsp needed");
1692416935

16925-
if (ssl->options.side == WOLFSSL_CLIENT_END) {
16936+
if (ssl->options.side == WOLFSSL_CLIENT_END
16937+
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
16938+
|| (ssl->options.side == WOLFSSL_SERVER_END
16939+
&& ssl->options.handShakeDone)
16940+
#endif
16941+
) {
1692616942
#ifndef NO_TLS
1692716943
#ifdef HAVE_CERTIFICATE_STATUS_REQUEST
1692816944
if (ssl->status_request) {

src/tls.c

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

1496214962
case TLSX_STATUS_REQUEST:
14963-
length += CSR_GET_SIZE(
14964-
(CertificateStatusRequest*)extension->data, isRequest);
14963+
if (msgType != certificate_request)
14964+
length += CSR_GET_SIZE(
14965+
(CertificateStatusRequest*)extension->data, isRequest);
1496514966
break;
1496614967

1496714968
case TLSX_STATUS_REQUEST_V2:
@@ -15238,11 +15239,15 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1523815239

1523915240
case TLSX_STATUS_REQUEST:
1524015241
WOLFSSL_MSG("Certificate Status Request extension to write");
15241-
ret = CSR_WRITE((CertificateStatusRequest*)extension->data,
15242-
output + offset, isRequest);
15243-
if (ret > 0) {
15244-
offset += (word16)ret;
15242+
if (msgType == certificate_request) {
1524515243
ret = 0;
15244+
} else {
15245+
ret = CSR_WRITE((CertificateStatusRequest*)extension->data,
15246+
output + offset, isRequest);
15247+
if (ret > 0) {
15248+
offset += (word16)ret;
15249+
ret = 0;
15250+
}
1524615251
}
1524715252
break;
1524815253

@@ -16633,6 +16638,7 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1663316638
/* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, OID_FILTERS
1663416639
* TLSX_STATUS_REQUEST
1663516640
*/
16641+
TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST));
1663616642
}
1663716643
#endif
1663816644
#if defined(HAVE_ECH)
@@ -16859,8 +16865,9 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1685916865
/* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, TLSX_OID_FILTERS
1686016866
* TLSX_STATUS_REQUEST
1686116867
*/
16868+
TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST));
1686216869
}
16863-
#endif
16870+
#endif
1686416871
#endif
1686516872
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
1686616873
if (ssl->echConfigs != NULL && !ssl->options.disableECH

src/tls13.c

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

9406+
/* Free previous OCSP response buffers to avoid leak on PHA reuse */
9407+
{
9408+
int j;
9409+
for (j = 0; j < MAX_CERT_EXTENSIONS; j++) {
9410+
XFREE(csr->responses[j].buffer, ssl->heap,
9411+
DYNAMIC_TYPE_TMP_BUFFER);
9412+
csr->responses[j].buffer = NULL;
9413+
csr->responses[j].length = 0;
9414+
}
9415+
}
94069416
request = &csr->request.ocsp[0];
94079417
ret = CreateOcspResponse(ssl, &request, &csr->responses[0]);
94089418
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
@@ -6189,3 +6189,157 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void)
61896189
#endif
61906190
return EXPECT_RESULT();
61916191
}
6192+
6193+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6194+
defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
6195+
defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \
6196+
!defined(NO_CERTS) && !defined(NO_RSA) && \
6197+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
6198+
/* Mock OCSP I/O callback: returns 0 bytes so the server stapling slot
6199+
* stays empty. This intentionally exercises the "no staple available"
6200+
* path on both peers. */
6201+
static int test_pha_ocsp_io_cb(void* ioCtx, const char* url, int urlSz,
6202+
unsigned char* req, int reqSz, unsigned char** resp)
6203+
{
6204+
(void)ioCtx; (void)url; (void)urlSz; (void)req; (void)reqSz;
6205+
*resp = NULL;
6206+
return 0;
6207+
}
6208+
6209+
static void test_pha_ocsp_resp_free_cb(void* ioCtx, unsigned char* resp)
6210+
{
6211+
(void)ioCtx; (void)resp;
6212+
}
6213+
#endif
6214+
6215+
/* Post-Handshake Authentication combined with OCSP stapling
6216+
* (status_request) on the TLS 1.3 CertificateRequest message.
6217+
*
6218+
* Regression for two related issues:
6219+
* 1. The server's PHA CertificateRequest must include the
6220+
* status_request extension (with an empty body) when the client
6221+
* offered status_request in its ClientHello (RFC 8446 4.2 / 4.3.2).
6222+
* Without the fix the extension was suppressed and the size and
6223+
* write paths disagreed by 4 bytes, causing the client to send a
6224+
* decode_error alert (BUFFER_ERROR / -328).
6225+
* 2. The server-side OCSP-status check in ProcessPeerCerts must run
6226+
* for the PHA-received client Certificate. The check must also
6227+
* tolerate a missing/empty stapled response unless the verifier
6228+
* enforces must-staple, per RFC 8446 4.4.2.1 (client OCSP staple
6229+
* is MAY) and RFC 7633 (must-staple). Without the fix the server
6230+
* either skipped the check entirely or returned
6231+
* BAD_CERTIFICATE_STATUS_ERROR (-406) for the no-staple case.
6232+
*
6233+
* The test exercises a single TLS 1.3 connection: an initial handshake
6234+
* without client authentication, followed by a server-initiated PHA
6235+
* exchange. The server expects to receive (and verify) the client
6236+
* certificate even though no OCSP staple is supplied.
6237+
*/
6238+
int test_tls13_pha_status_request(void)
6239+
{
6240+
EXPECT_DECLS;
6241+
#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && \
6242+
defined(WOLFSSL_TLS13) && defined(WOLFSSL_POST_HANDSHAKE_AUTH) && \
6243+
defined(HAVE_CERTIFICATE_STATUS_REQUEST) && defined(HAVE_OCSP) && \
6244+
!defined(NO_CERTS) && !defined(NO_RSA) && \
6245+
!defined(NO_WOLFSSL_CLIENT) && !defined(NO_WOLFSSL_SERVER)
6246+
struct test_memio_ctx test_ctx;
6247+
WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL;
6248+
WOLFSSL *ssl_c = NULL, *ssl_s = NULL;
6249+
WOLFSSL_X509 *peer = NULL;
6250+
const char msg[] = "ping";
6251+
char buf[8];
6252+
6253+
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
6254+
6255+
/* --- Client CTX ------------------------------------------------ */
6256+
ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfTLSv1_3_client_method()));
6257+
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_c, caCertFile, 0),
6258+
WOLFSSL_SUCCESS);
6259+
ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_c, cliCertFile,
6260+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
6261+
ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_c, cliKeyFile,
6262+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
6263+
/* Must opt in to PHA before wolfSSL_connect to add the
6264+
* post_handshake_auth extension to the ClientHello. */
6265+
ExpectIntEQ(wolfSSL_CTX_allow_post_handshake_auth(ctx_c), 0);
6266+
ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_c), WOLFSSL_SUCCESS);
6267+
wolfSSL_SetIORecv(ctx_c, test_memio_read_cb);
6268+
wolfSSL_SetIOSend(ctx_c, test_memio_write_cb);
6269+
6270+
/* --- Server CTX ------------------------------------------------ */
6271+
ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfTLSv1_3_server_method()));
6272+
ExpectIntEQ(wolfSSL_CTX_use_certificate_file(ctx_s, svrCertFile,
6273+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
6274+
ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile,
6275+
WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS);
6276+
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s, caCertFile, 0),
6277+
WOLFSSL_SUCCESS);
6278+
/* Trust the client cert issuer as well, otherwise the PHA
6279+
* Certificate verification would fail with ASN_SELF_SIGNED_E. */
6280+
ExpectIntEQ(wolfSSL_CTX_load_verify_locations(ctx_s,
6281+
"./certs/client-ca.pem", 0), WOLFSSL_SUCCESS);
6282+
ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(ctx_s), WOLFSSL_SUCCESS);
6283+
/* Mock callback: stapling negotiates but the response is empty. */
6284+
ExpectIntEQ(wolfSSL_CTX_SetOCSP_Cb(ctx_s, test_pha_ocsp_io_cb,
6285+
test_pha_ocsp_resp_free_cb, NULL), WOLFSSL_SUCCESS);
6286+
/* Initial handshake: do not request the client certificate yet -
6287+
* the server promotes verification only when triggering PHA. */
6288+
wolfSSL_CTX_set_verify(ctx_s, WOLFSSL_VERIFY_NONE, NULL);
6289+
wolfSSL_SetIORecv(ctx_s, test_memio_read_cb);
6290+
wolfSSL_SetIOSend(ctx_s, test_memio_write_cb);
6291+
6292+
/* --- SSL objects ----------------------------------------------- */
6293+
ExpectNotNull(ssl_c = wolfSSL_new(ctx_c));
6294+
wolfSSL_SetIOReadCtx(ssl_c, &test_ctx);
6295+
wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx);
6296+
/* Causes status_request in the ClientHello so that the server's
6297+
* PHA CertificateRequest re-emits the same extension. */
6298+
ExpectIntEQ(wolfSSL_UseOCSPStapling(ssl_c, WOLFSSL_CSR_OCSP, 0),
6299+
WOLFSSL_SUCCESS);
6300+
6301+
ExpectNotNull(ssl_s = wolfSSL_new(ctx_s));
6302+
wolfSSL_SetIOReadCtx(ssl_s, &test_ctx);
6303+
wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx);
6304+
6305+
/* --- Initial handshake (no client cert requested) -------------- */
6306+
ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0);
6307+
ExpectNull(wolfSSL_get_peer_certificate(ssl_s));
6308+
6309+
/* --- Trigger PHA: server now requires the client certificate -- */
6310+
if (EXPECT_SUCCESS()) {
6311+
wolfSSL_set_verify(ssl_s,
6312+
WOLFSSL_VERIFY_PEER | WOLFSSL_VERIFY_FAIL_IF_NO_PEER_CERT,
6313+
NULL);
6314+
ExpectIntEQ(wolfSSL_request_certificate(ssl_s), WOLFSSL_SUCCESS);
6315+
}
6316+
6317+
/* The server's wolfSSL_write below carries the PHA
6318+
* CertificateRequest record. The client's wolfSSL_read consumes
6319+
* the request, transmits Certificate/CertificateVerify/Finished
6320+
* and surfaces the application data to us. */
6321+
ExpectIntEQ(wolfSSL_write(ssl_s, msg, (int)sizeof(msg) - 1),
6322+
(int)sizeof(msg) - 1);
6323+
ExpectIntEQ(wolfSSL_read(ssl_c, buf, sizeof(buf) - 1),
6324+
(int)sizeof(msg) - 1);
6325+
6326+
/* The client's reply lets the server's wolfSSL_read drain the
6327+
* incoming PHA Certificate flight before the application data. */
6328+
ExpectIntEQ(wolfSSL_write(ssl_c, msg, (int)sizeof(msg) - 1),
6329+
(int)sizeof(msg) - 1);
6330+
ExpectIntEQ(wolfSSL_read(ssl_s, buf, sizeof(buf) - 1),
6331+
(int)sizeof(msg) - 1);
6332+
6333+
/* PHA succeeded: the server now holds the client certificate.
6334+
* Reaching this point also implies the server tolerated the
6335+
* empty OCSP staple instead of failing with -406. */
6336+
ExpectNotNull(peer = wolfSSL_get_peer_certificate(ssl_s));
6337+
wolfSSL_X509_free(peer);
6338+
6339+
wolfSSL_free(ssl_c);
6340+
wolfSSL_free(ssl_s);
6341+
wolfSSL_CTX_free(ctx_c);
6342+
wolfSSL_CTX_free(ctx_s);
6343+
#endif
6344+
return EXPECT_RESULT();
6345+
}

tests/api/test_tls13.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ int test_tls13_cipher_fuzz_aes256_gcm_sha384(void);
7474
int test_tls13_cipher_fuzz_chacha20_poly1305_sha256(void);
7575
int test_tls13_cipher_fuzz_aes128_ccm_sha256(void);
7676
int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
77+
int test_tls13_pha_status_request(void);
7778

7879
#define TEST_TLS13_DECLS \
7980
TEST_DECL_GROUP("tls13", test_tls13_apis), \
@@ -125,6 +126,7 @@ int test_tls13_cipher_fuzz_aes128_ccm_8_sha256(void);
125126
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes256_gcm_sha384), \
126127
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_chacha20_poly1305_sha256), \
127128
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_sha256), \
128-
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256)
129+
TEST_DECL_GROUP("tls13", test_tls13_cipher_fuzz_aes128_ccm_8_sha256), \
130+
TEST_DECL_GROUP("tls13", test_tls13_pha_status_request)
129131

130132
#endif /* WOLFCRYPT_TEST_TLS13_H */

0 commit comments

Comments
 (0)