Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions src/internal.c
Original file line number Diff line number Diff line change
Expand Up @@ -15689,12 +15689,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) {
WOLFSSL_MSG("Leaf cert doesn't have certificate status.");
return BAD_CERTIFICATE_STATUS_ERROR;
/* 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.");
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;
Expand Down Expand Up @@ -16751,7 +16762,12 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,

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) {
Expand Down
21 changes: 14 additions & 7 deletions src/tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -14876,8 +14876,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:
Expand Down Expand Up @@ -15154,11 +15155,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;

Expand Down Expand Up @@ -16433,6 +16438,7 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
/* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, OID_FILTERS
* TLSX_STATUS_REQUEST
*/
TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST));
}
#endif
#if defined(HAVE_ECH)
Expand Down Expand Up @@ -16624,8 +16630,9 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
/* TODO: TLSX_SIGNED_CERTIFICATE_TIMESTAMP, TLSX_OID_FILTERS
* TLSX_STATUS_REQUEST
*/
TURN_OFF(semaphore, TLSX_ToSemaphore(TLSX_STATUS_REQUEST));
}
#endif
#endif
#endif
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
if (ssl->echConfigs != NULL && !ssl->options.disableECH
Expand Down
10 changes: 10 additions & 0 deletions src/tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -9318,6 +9318,16 @@ static int SetupOcspResp(WOLFSSL* ssl)
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] &&
Expand Down
154 changes: 154 additions & 0 deletions tests/api/test_tls13.c
Original file line number Diff line number Diff line change
Expand Up @@ -5847,3 +5847,157 @@ int test_tls13_ticket_peer_cert_reverify(void)
#endif
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: returns 0 bytes so the server stapling slot
* stays empty. This intentionally exercises the "no staple available"
* path on both peers. */
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();
}
4 changes: 3 additions & 1 deletion tests/api/test_tls13.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ int test_tls13_cert_with_extern_psk_rejects_resumption(void);
int test_tls13_cert_with_extern_psk_sh_missing_key_share(void);
int test_tls13_cert_with_extern_psk_sh_confirms_resumption(void);
int test_tls13_ticket_peer_cert_reverify(void);
int test_tls13_pha_status_request(void);

#define TEST_TLS13_DECLS \
TEST_DECL_GROUP("tls13", test_tls13_apis), \
Expand Down Expand Up @@ -111,6 +112,7 @@ int test_tls13_ticket_peer_cert_reverify(void);
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_rejects_resumption), \
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_missing_key_share), \
TEST_DECL_GROUP("tls13", test_tls13_cert_with_extern_psk_sh_confirms_resumption), \
TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify)
TEST_DECL_GROUP("tls13", test_tls13_ticket_peer_cert_reverify), \
TEST_DECL_GROUP("tls13", test_tls13_pha_status_request)

#endif /* WOLFCRYPT_TEST_TLS13_H */
Loading