diff --git a/src/internal.c b/src/internal.c index fed9d370dea..abf0dcefdea 100644 --- a/src/internal.c +++ b/src/internal.c @@ -17953,6 +17953,11 @@ static int DoCertificateStatus(WOLFSSL* ssl, byte* input, word32* inOutIdx, || (response->single->status->status != CERT_GOOD)) ret = BAD_CERTIFICATE_STATUS_ERROR; + /* Bundling more than one SingleResponse inside a single + * stapled BasicOCSPResponse is not supported. */ + if (ret == 0 && response->single->next != NULL) + ret = BAD_CERTIFICATE_STATUS_ERROR; + if (ret == 0) { request = (OcspRequest*)TLSX_CSR2_GetRequest( ssl->extensions, status_type, idx); diff --git a/tests/api.c b/tests/api.c index 32b90b9a079..9dd7bd389a4 100644 --- a/tests/api.c +++ b/tests/api.c @@ -35604,6 +35604,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_ocsp_certid_dup), TEST_DECL(test_ocsp_resp_find_status_serial_prefix), TEST_DECL(test_ocsp_tls_cert_cb), + TEST_DECL(test_ocsp_status_request_v2_multi_revoked_single), TEST_DECL(test_ocsp_cert_unknown_crl_fallback), TEST_DECL(test_ocsp_cert_unknown_crl_fallback_nonleaf), TEST_DECL(test_tls13_nonblock_ocsp_low_mfl), diff --git a/tests/api/create_ocsp_test_blobs.py b/tests/api/create_ocsp_test_blobs.py index 6a55de98373..d21c702bcb2 100644 --- a/tests/api/create_ocsp_test_blobs.py +++ b/tests/api/create_ocsp_test_blobs.py @@ -436,6 +436,32 @@ def create_bad_response(rd: dict) -> bytes: 'responder_key': WOLFSSL_OCSP_CERT_PATH + 'intermediate1-ca-key.pem', 'name': 'resp_server1_cert' }, + { + # A single, validly-signed BasicOCSPResponse that bundles two + # SingleResponses: the first (wire order) is a benign CERT_GOOD entry + # for an unrelated serial, the second is the server1 leaf cert marked + # CERT_REVOKED. + # Signed by intermediate1, the legitimate issuer and + # authorized responder for server1. + 'response_status': 0, + 'signature_algorithm': signature_algorithm(), + 'responder_by_name': True, + 'responder_cert': WOLFSSL_OCSP_CERT_PATH + 'intermediate1-ca-cert.pem', + 'responses': [ + { + 'issuer_cert': WOLFSSL_OCSP_CERT_PATH + 'intermediate1-ca-cert.pem', + 'serial': 0x01, + 'status': CERT_GOOD + }, + { + 'issuer_cert': WOLFSSL_OCSP_CERT_PATH + 'intermediate1-ca-cert.pem', + 'serial': 0x05, + 'status': CERT_REVOKED + } + ], + 'responder_key': WOLFSSL_OCSP_CERT_PATH + 'intermediate1-ca-key.pem', + 'name': 'resp_server1_revoked_good_first' + }, { # Ancestor-issued responder; rejected by RFC 6960 4.2.2.2 enforcement 'response_status': 0, diff --git a/tests/api/test_ocsp.c b/tests/api/test_ocsp.c index 9bc74de2238..b457f0d637e 100644 --- a/tests/api/test_ocsp.c +++ b/tests/api/test_ocsp.c @@ -1218,11 +1218,140 @@ int test_ocsp_tls_cert_cb(void) return EXPECT_RESULT(); } +#ifdef HAVE_CERTIFICATE_STATUS_REQUEST_V2 +/* Stapling for a 3-cert chain. The intermediate and root staples are the + * normal single-SingleResponse CERT_GOOD responses, but the leaf (idx 0) + * staple is resp_server1_revoked_good_first: one validly-signed + * BasicOCSPResponse that bundles a benign CERT_GOOD single FIRST and the + * server1 leaf cert's CERT_REVOKED single SECOND. */ +static int test_ocsp_revoked_multi_single_status_cb(WOLFSSL* ssl, void* ioCtx) +{ + byte* leaf_resp = NULL; + byte* int_resp = NULL; + byte* root_resp = NULL; + int ret = WOLFSSL_OCSP_STATUS_CB_ALERT_FATAL; + (void)ioCtx; + leaf_resp = (byte*)XMALLOC(sizeof(resp_server1_revoked_good_first), NULL, 0); + int_resp = (byte*)XMALLOC(sizeof(resp_intermediate1_cert), NULL, 0); + root_resp = (byte*)XMALLOC(sizeof(resp_root_ca_cert), NULL, 0); + if (leaf_resp != NULL && int_resp != NULL && root_resp != NULL) { + XMEMCPY(leaf_resp, resp_server1_revoked_good_first, + sizeof(resp_server1_revoked_good_first)); + XMEMCPY(int_resp, resp_intermediate1_cert, + sizeof(resp_intermediate1_cert)); + XMEMCPY(root_resp, resp_root_ca_cert, sizeof(resp_root_ca_cert)); + if (wolfSSL_set_tlsext_status_ocsp_resp_multi(ssl, leaf_resp, + sizeof(resp_server1_revoked_good_first), 0) == WOLFSSL_SUCCESS) + leaf_resp = NULL; + if (wolfSSL_set_tlsext_status_ocsp_resp_multi(ssl, int_resp, + sizeof(resp_intermediate1_cert), 1) == WOLFSSL_SUCCESS) + int_resp = NULL; + if (wolfSSL_set_tlsext_status_ocsp_resp_multi(ssl, root_resp, + sizeof(resp_root_ca_cert), 2) == WOLFSSL_SUCCESS) + root_resp = NULL; + if (leaf_resp == NULL && int_resp == NULL && root_resp == NULL) + ret = WOLFSSL_OCSP_STATUS_CB_OK; + } + XFREE(leaf_resp, NULL, 0); + XFREE(int_resp, NULL, 0); + XFREE(root_resp, NULL, 0); + return ret; +} + +/* + * status_request_v2 OCSP-multi must reject a stapled leaf response + * whose MATCHING SingleResponse is CERT_REVOKED, even when an unrelated + * CERT_GOOD SingleResponse appears first in the same BasicOCSPResponse. + */ +int test_ocsp_status_request_v2_multi_revoked_single(void) +{ + EXPECT_DECLS; + size_t i; + struct { + method_provider client_meth; + method_provider server_meth; + const char* tls_version; + } params[] = { +#if !defined(WOLFSSL_NO_TLS12) + { wolfTLSv1_2_client_method, wolfTLSv1_2_server_method, "TLSv1_2" }, +#ifdef WOLFSSL_DTLS + { wolfDTLSv1_2_client_method, wolfDTLSv1_2_server_method, "DTLSv1_2" }, +#endif +#endif + }; + + for (i = 0; i < XELEM_CNT(params) && !EXPECT_FAIL(); i++) { + struct test_ssl_memio_ctx test_ctx; + WOLFSSL_ALERT_HISTORY h; + + printf("\nTesting %s\n", params[i].tls_version); + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + test_ctx.c_cb.caPemFile = ""; + /* Server cert/key come only from the per-connection cert callback. */ + test_ctx.s_cb.certPemFile = ""; + test_ctx.s_cb.keyPemFile = ""; + test_ctx.c_cb.method = params[i].client_meth; + test_ctx.s_cb.method = params[i].server_meth; + + /* Full server1 -> intermediate1 -> root chain so the leaf staple is at + * idx 0. */ + test_ocsp_tls_cert_cb_opts.chainLen = 3; + test_ocsp_tls_cert_cb_opts.failStaple = 0; + test_ctx.s_cb.ctx_ready = test_ocsp_tls_cert_cb_ctx_ready; + + ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS); + ExpectIntEQ(wolfSSL_UnloadCertsKeys(test_ctx.s_ssl), WOLFSSL_SUCCESS); + + /* server: enable stapling and supply the multi-single leaf */ + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(test_ctx.s_ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_set_tlsext_status_cb(test_ctx.s_ctx, + test_ocsp_revoked_multi_single_status_cb), WOLFSSL_SUCCESS); + + /* client: verify chain via callback, request status_request_v2 multi. + * Deliberately no ocsp_status_verify_cb: exercise DoCertificateStatus + * in isolation. */ + wolfSSL_set_verify(test_ctx.c_ssl, WOLFSSL_VERIFY_DEFAULT, + test_ocsp_tls_cert_cb_verify_cb); + wolfSSL_SetCertCbCtx(test_ctx.c_ssl, test_ctx.c_ssl); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPStapling(test_ctx.c_ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_CTX_EnableOCSPMustStaple(test_ctx.c_ctx), + WOLFSSL_SUCCESS); + ExpectIntEQ(wolfSSL_UseOCSPStaplingV2(test_ctx.c_ssl, + WOLFSSL_CSR2_OCSP_MULTI, WOLFSSL_CSR2_OCSP_USE_NONCE), + WOLFSSL_SUCCESS); + + /* Revoked leaf must abort the handshake. */ + ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL), + TEST_FAIL); + XMEMSET(&h, 0, sizeof(h)); + ExpectIntEQ(wolfSSL_get_alert_history(test_ctx.s_ssl, &h), + WOLFSSL_SUCCESS); + ExpectIntEQ(h.last_rx.level, alert_fatal); + ExpectIntEQ(h.last_rx.code, bad_certificate_status_response); + + test_ssl_memio_cleanup(&test_ctx); + } + return EXPECT_RESULT(); +} +#else +int test_ocsp_status_request_v2_multi_revoked_single(void) +{ + return TEST_SKIPPED; +} +#endif /* HAVE_CERTIFICATE_STATUS_REQUEST_V2 */ + #else /* feature guards */ int test_ocsp_tls_cert_cb(void) { return TEST_SKIPPED; } +int test_ocsp_status_request_v2_multi_revoked_single(void) +{ + return TEST_SKIPPED; +} #endif /* diff --git a/tests/api/test_ocsp.h b/tests/api/test_ocsp.h index e8e2aa3cf20..21c9210422a 100644 --- a/tests/api/test_ocsp.h +++ b/tests/api/test_ocsp.h @@ -30,6 +30,7 @@ int test_ocsp_basic_verify(void); int test_ocsp_responder_keyhash_binding(void); int test_ocsp_response_parsing(void); int test_ocsp_tls_cert_cb(void); +int test_ocsp_status_request_v2_multi_revoked_single(void); int test_ocsp_cert_unknown_crl_fallback(void); int test_ocsp_cert_unknown_crl_fallback_nonleaf(void); int test_tls13_nonblock_ocsp_low_mfl(void); diff --git a/tests/api/test_ocsp_test_blobs.h b/tests/api/test_ocsp_test_blobs.h index 0444f434830..0b0ecc551ac 100644 --- a/tests/api/test_ocsp_test_blobs.h +++ b/tests/api/test_ocsp_test_blobs.h @@ -2415,4 +2415,64 @@ unsigned char resp_bad[] = { 0x63, 0x29, 0xc6, 0xa8, 0x97, 0x4b, 0x14, 0xff, 0xd2, }; +unsigned char resp_server1_revoked_good_first[] = { + 0x30, 0x82, 0x02, 0x9e, 0x0a, 0x01, 0x00, 0xa0, 0x82, 0x02, 0x97, 0x30, + 0x82, 0x02, 0x93, 0x06, 0x09, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, + 0x01, 0x01, 0x04, 0x82, 0x02, 0x84, 0x30, 0x82, 0x02, 0x80, 0x30, 0x82, + 0x01, 0x6a, 0xa1, 0x81, 0xa4, 0x30, 0x81, 0xa1, 0x31, 0x0b, 0x30, 0x09, + 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x13, 0x30, + 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x57, 0x61, 0x73, 0x68, + 0x69, 0x6e, 0x67, 0x74, 0x6f, 0x6e, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, + 0x55, 0x04, 0x07, 0x0c, 0x07, 0x53, 0x65, 0x61, 0x74, 0x74, 0x6c, 0x65, + 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x07, 0x77, + 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x31, 0x14, 0x30, 0x12, 0x06, 0x03, + 0x55, 0x04, 0x0b, 0x0c, 0x0b, 0x45, 0x6e, 0x67, 0x69, 0x6e, 0x65, 0x65, + 0x72, 0x69, 0x6e, 0x67, 0x31, 0x22, 0x30, 0x20, 0x06, 0x03, 0x55, 0x04, + 0x03, 0x0c, 0x19, 0x77, 0x6f, 0x6c, 0x66, 0x53, 0x53, 0x4c, 0x20, 0x69, + 0x6e, 0x74, 0x65, 0x72, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x74, 0x65, 0x20, + 0x43, 0x41, 0x20, 0x31, 0x31, 0x1f, 0x30, 0x1d, 0x06, 0x09, 0x2a, 0x86, + 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01, 0x16, 0x10, 0x69, 0x6e, 0x66, + 0x6f, 0x40, 0x77, 0x6f, 0x6c, 0x66, 0x73, 0x73, 0x6c, 0x2e, 0x63, 0x6f, + 0x6d, 0x18, 0x0f, 0x32, 0x30, 0x32, 0x36, 0x30, 0x36, 0x32, 0x31, 0x31, + 0x36, 0x34, 0x32, 0x33, 0x36, 0x5a, 0x30, 0x81, 0xaf, 0x30, 0x4d, 0x30, + 0x38, 0x30, 0x07, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, + 0x71, 0x4d, 0x82, 0x23, 0x40, 0x59, 0xc0, 0x96, 0xa1, 0x37, 0x43, 0xfa, + 0x31, 0xdb, 0xba, 0xb1, 0x43, 0x18, 0xda, 0x04, 0x04, 0x14, 0x83, 0xc6, + 0x3a, 0x89, 0x2c, 0x81, 0xf4, 0x02, 0xd7, 0x9d, 0x4c, 0xe2, 0x2a, 0xc0, + 0x71, 0x82, 0x64, 0x44, 0xda, 0x0e, 0x02, 0x01, 0x01, 0x80, 0x00, 0x18, + 0x0f, 0x32, 0x30, 0x32, 0x36, 0x30, 0x36, 0x32, 0x31, 0x31, 0x36, 0x34, + 0x32, 0x33, 0x36, 0x5a, 0x30, 0x5e, 0x30, 0x38, 0x30, 0x07, 0x06, 0x05, + 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x04, 0x14, 0x71, 0x4d, 0x82, 0x23, 0x40, + 0x59, 0xc0, 0x96, 0xa1, 0x37, 0x43, 0xfa, 0x31, 0xdb, 0xba, 0xb1, 0x43, + 0x18, 0xda, 0x04, 0x04, 0x14, 0x83, 0xc6, 0x3a, 0x89, 0x2c, 0x81, 0xf4, + 0x02, 0xd7, 0x9d, 0x4c, 0xe2, 0x2a, 0xc0, 0x71, 0x82, 0x64, 0x44, 0xda, + 0x0e, 0x02, 0x01, 0x05, 0xa1, 0x11, 0x18, 0x0f, 0x32, 0x30, 0x32, 0x36, + 0x30, 0x36, 0x32, 0x31, 0x31, 0x36, 0x34, 0x32, 0x33, 0x36, 0x5a, 0x18, + 0x0f, 0x32, 0x30, 0x32, 0x36, 0x30, 0x36, 0x32, 0x31, 0x31, 0x36, 0x34, + 0x32, 0x33, 0x36, 0x5a, 0x30, 0x0b, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x03, 0x82, 0x01, 0x01, 0x00, 0xc4, 0x95, + 0xdb, 0x71, 0xca, 0x09, 0x4b, 0x37, 0xf1, 0xe8, 0x25, 0xc4, 0x75, 0x6f, + 0x87, 0x22, 0x95, 0x8b, 0x7f, 0x7f, 0x00, 0xf0, 0x75, 0x35, 0x66, 0xea, + 0x56, 0x26, 0xc4, 0x9a, 0xe1, 0x40, 0xe9, 0xd2, 0x7c, 0x8c, 0x83, 0x0f, + 0x33, 0x33, 0xac, 0x1e, 0xcc, 0x8e, 0x0e, 0x99, 0x43, 0xb8, 0xd6, 0x13, + 0xfa, 0xdb, 0xbe, 0xaf, 0x64, 0x6a, 0xbd, 0x92, 0x35, 0xb2, 0x7e, 0xaf, + 0xda, 0x49, 0x89, 0xa2, 0x37, 0xcc, 0x50, 0x88, 0xa8, 0x0b, 0xb4, 0xe1, + 0x6a, 0xb7, 0x94, 0x06, 0x92, 0xb8, 0x6a, 0xd2, 0x55, 0xfe, 0x7e, 0xb4, + 0x5e, 0x12, 0xed, 0x9d, 0xf6, 0x04, 0x49, 0x2c, 0x68, 0xae, 0xc4, 0x21, + 0x3c, 0xd4, 0xe4, 0x97, 0x76, 0x2c, 0xdb, 0x78, 0xfb, 0x22, 0xdd, 0x3b, + 0xed, 0x03, 0x68, 0xaa, 0x7b, 0x9d, 0xd2, 0x66, 0x9f, 0x53, 0xc8, 0x45, + 0xe7, 0x8d, 0xeb, 0x39, 0x05, 0x90, 0xbe, 0x9e, 0xe3, 0x3f, 0xcd, 0x4c, + 0x0a, 0x37, 0x3c, 0x18, 0xbb, 0xef, 0x2e, 0xb6, 0x53, 0xda, 0x65, 0xfb, + 0xd6, 0xd4, 0xfa, 0x04, 0xcb, 0x93, 0x23, 0x60, 0x5e, 0x62, 0x8b, 0xd2, + 0x14, 0xe4, 0x14, 0xd3, 0x3f, 0xdd, 0x8b, 0x65, 0x55, 0x95, 0x27, 0x45, + 0xdb, 0x7c, 0x74, 0x88, 0x33, 0x7b, 0x2d, 0x6e, 0x11, 0x41, 0x19, 0xc6, + 0xe2, 0x79, 0x3d, 0x27, 0xc1, 0x5e, 0xb1, 0xcc, 0xf9, 0x6a, 0x9c, 0xaf, + 0x73, 0x78, 0xc5, 0xeb, 0x1b, 0xfa, 0x6f, 0x92, 0x6b, 0x75, 0xf0, 0x70, + 0xc9, 0x44, 0xd2, 0xa8, 0xbe, 0x2e, 0xee, 0x0e, 0x0b, 0xac, 0x7f, 0x99, + 0x6d, 0x67, 0x1e, 0x2d, 0x7d, 0x19, 0x3a, 0x66, 0x1b, 0xb2, 0x0f, 0xfe, + 0x49, 0x89, 0x24, 0xb2, 0x15, 0xa6, 0xcc, 0xd2, 0xdd, 0xc4, 0x45, 0xfb, + 0xd2, 0xe5, 0xeb, 0xe8, 0xd7, 0x09, 0xf1, 0xc0, 0xb8, 0x0d, 0xc8, 0x34, + 0xc2, 0x6c, +}; + #endif /* OCSP_TEST_BLOBS_H */