Merge pull request #10945 from ejohnstown/ocsp-fail

OCSP: opt-in fail-closed on missing responder
This commit is contained in:
Daniel Pouzzner
2026-07-20 16:42:04 -05:00
committed by GitHub
11 changed files with 169 additions and 23 deletions
+24 -6
View File
@@ -16499,6 +16499,8 @@ static int ProcessPeerCertLeafRevocation(WOLFSSL* ssl, ProcPeerCertArgs* args,
return 1;
}
#endif
if (ret == WC_NO_ERR_TRACE(OCSP_NO_URL))
ret = OcspNoUrlPolicy(SSL_CM(ssl));
doLookup = (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN));
if (ret != 0) {
WOLFSSL_MSG("\tOCSP Lookup not ok");
@@ -17485,6 +17487,8 @@ int ProcessPeerCerts(WOLFSSL* ssl, byte* input, word32* inOutIdx,
goto exit_ppc;
}
#endif
if (ret == WC_NO_ERR_TRACE(OCSP_NO_URL))
ret = OcspNoUrlPolicy(SSL_CM(ssl));
if (ret != 0) {
WOLFSSL_ERROR_VERBOSE(ret);
WOLFSSL_MSG("\tOCSP Lookup not ok");
@@ -26232,9 +26236,12 @@ int CreateOcspResponse(WOLFSSL* ssl, OcspRequest** ocspRequest,
ssl->heap);
/* Suppressing soft-fail responder errors. OCSP_CERT_REVOKED is an
* explicit positive assertion of revocation and must not be ignored. */
* explicit positive assertion of revocation and must not be ignored.
* OCSP_NO_URL just means there is no responder to staple from;
* stapling stays best-effort. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) ||
ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) {
ret = 0;
}
}
@@ -27221,9 +27228,12 @@ int SendCertificateStatus(WOLFSSL* ssl)
/* Suppressing soft-fail responder errors.
* OCSP_CERT_REVOKED is an explicit positive
* assertion of revocation and must not be
* ignored. */
* ignored. OCSP_NO_URL just means there is no
* responder to staple from; stapling stays
* best-effort. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) ||
ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) {
ret = 0;
}
@@ -27252,9 +27262,12 @@ int SendCertificateStatus(WOLFSSL* ssl)
/* Suppressing soft-fail responder errors.
* OCSP_CERT_REVOKED is an explicit positive assertion of
* revocation and must not be ignored. */
* revocation and must not be ignored. OCSP_NO_URL just
* means there is no responder to staple from; stapling
* stays best-effort. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) ||
ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) {
ret = 0;
}
}
@@ -28692,6 +28705,9 @@ const char* wolfSSL_ERR_reason_error_string(unsigned long e)
case OCSP_NEED_URL:
return "OCSP need URL";
case OCSP_NO_URL:
return "Cert has no OCSP responder URL";
case OCSP_CERT_UNKNOWN:
return "OCSP Cert unknown";
@@ -41454,6 +41470,8 @@ static int AddPSKtoPreMasterSecret(WOLFSSL* ssl)
* Run OCSP if the CertManager has it enabled. */
if (ret == 0 && SSL_CM(ssl)->ocspEnabled) {
ret = CheckCertOCSP_ex(SSL_CM(ssl)->ocsp, dCert, ssl);
if (ret == WC_NO_ERR_TRACE(OCSP_NO_URL))
ret = OcspNoUrlPolicy(SSL_CM(ssl));
}
#endif
#ifdef HAVE_CRL
+28 -10
View File
@@ -535,16 +535,12 @@ int CheckOcspRequest(WOLFSSL_OCSP* ocsp, OcspRequest* ocspRequest,
urlSz = ocspRequest->urlSz;
}
else {
/* No AIA URL and no override. ocspCheckAll asks for strict chain
* checking, so fail closed - but only on the client verification
* instance (cm->ocsp); stapling (cm->ocsp_stapling) shares the cm
* flag and must stay best-effort. */
if (ocsp->cm->ocspCheckAll && ocsp == ocsp->cm->ocsp) {
WOLFSSL_MSG("Cert has no OCSP URL and ocspCheckAll is set");
return OCSP_NEED_URL;
}
WOLFSSL_MSG("Cert has no OCSP URL, assuming CERT_GOOD");
return 0;
/* Cert advertises no OCSP responder and no override URL is set, so
* OCSP has no opinion on this cert. Report that distinctly from a
* failed lookup; the caller owns the policy decision. Callers wanting
* the historical soft-fail run this through OcspNoUrlPolicy(). */
WOLFSSL_MSG("Cert has no OCSP URL");
return OCSP_NO_URL;
}
request = (byte*)XMALLOC((size_t)requestSz, ocsp->cm->heap, DYNAMIC_TYPE_OCSP);
@@ -581,6 +577,28 @@ int CheckOcspRequest(WOLFSSL_OCSP* ocsp, OcspRequest* ocspRequest,
return ret;
}
/* Map OCSP_NO_URL - "this cert advertises no OCSP responder" - to a
* verification result.
*
* WOLFSSL_OCSP_CHECKALL selects which certificates get checked, not how hard
* to fail when one cannot be checked, so it deliberately has no say here.
* Refuse the cert only when the user explicitly asked for that with
* WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED.
*
* Returns OCSP_NEED_URL to refuse the cert, or 0 for the historical
* soft-fail. */
int OcspNoUrlPolicy(WOLFSSL_CERT_MANAGER* cm)
{
if (cm != NULL && cm->ocspFailIfNotSupported) {
WOLFSSL_MSG("Cert has no OCSP URL and OCSP is required for every cert");
WOLFSSL_ERROR_VERBOSE(OCSP_NEED_URL);
return OCSP_NEED_URL;
}
WOLFSSL_MSG("Cert has no OCSP URL, assuming CERT_GOOD");
return 0;
}
/* Enforce https://www.rfc-editor.org/rfc/rfc6960#section-4.2.2.2. Both halves
* of CertID (issuerNameHash and issuerKeyHash) must match; name-only matching
* would authorize a same-DN / different-key CA. issuerKeyHash may be NULL when
+12 -2
View File
@@ -2137,7 +2137,8 @@ int wolfSSL_CertManagerLoadCRLFile(WOLFSSL_CERT_MANAGER* cm, const char* file,
* @param [in] cm Certificate manager.
* @param [in] options Options for using OCSP. Valid flags:
* WOLFSSL_OCSP_URL_OVERRIDE, WOLFSSL_OCSP_NO_NONCE,
* WOLFSSL_OCSP_CHECKALL.
* WOLFSSL_OCSP_CHECKALL,
* WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED.
* @return WOLFSSL_SUCCESS on success.
* @return 0 when initializing the OCSP object fails.
* @return BAD_FUNC_ARG when cm is NULL.
@@ -2201,6 +2202,10 @@ int wolfSSL_CertManagerEnableOCSP(WOLFSSL_CERT_MANAGER* cm, int options)
if (options & WOLFSSL_OCSP_CHECKALL) {
cm->ocspCheckAll = 1;
}
/* Fail closed on certs that advertise no OCSP responder if requested. */
if (options & WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED) {
cm->ocspFailIfNotSupported = 1;
}
#ifndef WOLFSSL_USER_IO
/* Set built-in OCSP lookup. */
cm->ocspIOCb = EmbedOcspLookup;
@@ -2446,7 +2451,12 @@ int wolfSSL_CertManagerCheckOCSP(WOLFSSL_CERT_MANAGER* cm,
}
/* Do OCSP checks with decoded certificate. */
else if ((ret = CheckCertOCSP(cm->ocsp, cert)) != 0) {
WOLFSSL_MSG("CheckCertOCSP failed");
/* Apply the caller's policy for a cert that advertises no
* responder rather than reporting a lookup failure. */
if (ret == WC_NO_ERR_TRACE(OCSP_NO_URL))
ret = OcspNoUrlPolicy(cm);
if (ret != 0)
WOLFSSL_MSG("CheckCertOCSP failed");
}
/* Dispose of dynamically allocated memory. */
+22 -4
View File
@@ -3730,9 +3730,11 @@ int ProcessChainOCSPRequest(WOLFSSL* ssl)
request, &csr->responses[i], ssl->heap);
/* Suppressing soft-fail responder errors. OCSP_CERT_REVOKED
* is an explicit positive assertion of revocation and must
* not be ignored. */
* not be ignored. OCSP_NO_URL just means there is no
* responder to staple from; stapling stays best-effort. */
if (ret == WC_NO_ERR_TRACE(OCSP_CERT_UNKNOWN) ||
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL)) {
ret == WC_NO_ERR_TRACE(OCSP_LOOKUP_FAIL) ||
ret == WC_NO_ERR_TRACE(OCSP_NO_URL)) {
ret = 0;
}
i++;
@@ -4007,9 +4009,17 @@ int TLSX_CSR_ForceRequest(WOLFSSL* ssl)
switch (csr->status_type) {
case WOLFSSL_CSR_OCSP:
if (SSL_CM(ssl)->ocspEnabled) {
int ret;
csr->request.ocsp[0].ssl = ssl;
return CheckOcspRequest(SSL_CM(ssl)->ocsp,
ret = CheckOcspRequest(SSL_CM(ssl)->ocsp,
&csr->request.ocsp[0], NULL, NULL);
/* This is the client's fallback leaf lookup on the
* verification instance, so honor the no-responder policy
* just like the non-stapling leaf path. Default stays
* best-effort; FAIL_IF_NOT_SUPPORTED makes it fail closed. */
if (ret == WC_NO_ERR_TRACE(OCSP_NO_URL))
ret = OcspNoUrlPolicy(SSL_CM(ssl));
return ret;
}
else {
WOLFSSL_ERROR_VERBOSE(OCSP_LOOKUP_FAIL);
@@ -4542,9 +4552,17 @@ int TLSX_CSR2_ForceRequest(WOLFSSL* ssl)
case WOLFSSL_CSR2_OCSP_MULTI:
if (SSL_CM(ssl)->ocspEnabled && csr2->requests >= 1) {
int ret;
csr2->request.ocsp[csr2->requests-1].ssl = ssl;
return CheckOcspRequest(SSL_CM(ssl)->ocsp,
ret = CheckOcspRequest(SSL_CM(ssl)->ocsp,
&csr2->request.ocsp[csr2->requests-1], NULL, NULL);
/* This is the client's fallback leaf lookup on the
* verification instance, so honor the no-responder policy
* just like the non-stapling leaf path. Default stays
* best-effort; FAIL_IF_NOT_SUPPORTED makes it fail closed. */
if (ret == WC_NO_ERR_TRACE(OCSP_NO_URL))
ret = OcspNoUrlPolicy(SSL_CM(ssl));
return ret;
}
else {
WOLFSSL_ERROR_VERBOSE(OCSP_LOOKUP_FAIL);
+1
View File
@@ -37859,6 +37859,7 @@ TEST_CASE testCases[] = {
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_ocsp_no_url_policy),
TEST_DECL(test_tls13_nonblock_ocsp_low_mfl),
TEST_DECL(test_ocsp_responder),
TEST_DECL(test_wolfIO_DecodeUrl_crlf_reject),
+68
View File
@@ -1685,6 +1685,74 @@ int test_ocsp_cert_unknown_crl_fallback_nonleaf(void)
}
#endif /* HAVE_OCSP && HAVE_CRL && HAVE_SSL_MEMIO_TESTS_DEPENDENCIES */
#if defined(HAVE_OCSP) && defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES)
/* The default memio chain (certs/server-cert.pem under certs/ca-cert.pem)
* carries no AIA extension, so no cert in it advertises an OCSP responder
* and no override URL is configured. */
static int test_ocsp_no_url_checkall_ctx_ready(WOLFSSL_CTX* ctx)
{
EXPECT_DECLS;
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL);
/* CHECKALL selects scope only, so it must not fail a chain that
* advertises no responder. */
ExpectIntEQ(wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_CHECKALL),
WOLFSSL_SUCCESS);
return EXPECT_RESULT();
}
static int test_ocsp_no_url_fail_closed_ctx_ready(WOLFSSL_CTX* ctx)
{
EXPECT_DECLS;
wolfSSL_CTX_set_verify(ctx, WOLFSSL_VERIFY_PEER, NULL);
ExpectIntEQ(wolfSSL_CTX_EnableOCSP(ctx, WOLFSSL_OCSP_CHECKALL |
WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED),
WOLFSSL_SUCCESS);
return EXPECT_RESULT();
}
int test_ocsp_no_url_policy(void)
{
EXPECT_DECLS;
struct test_ssl_memio_ctx test_ctx;
/* WOLFSSL_OCSP_CHECKALL on its own soft-fails a cert that advertises no
* responder, as it has since the flag was introduced. Regression test:
* failing closed here breaks any chain whose CA publishes no OCSP URI,
* which is common on the public web. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.c_cb.ctx_ready = test_ocsp_no_url_checkall_ctx_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
ExpectIntEQ(test_ssl_memio_do_handshake(&test_ctx, 10, NULL),
TEST_SUCCESS);
test_ssl_memio_cleanup(&test_ctx);
/* Opting in with WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED refuses the same
* chain with OCSP_NEED_URL. */
XMEMSET(&test_ctx, 0, sizeof(test_ctx));
test_ctx.c_cb.ctx_ready = test_ocsp_no_url_fail_closed_ctx_ready;
ExpectIntEQ(test_ssl_memio_setup(&test_ctx), TEST_SUCCESS);
ExpectIntNE(test_ssl_memio_do_handshake(&test_ctx, 10, NULL),
TEST_SUCCESS);
ExpectIntEQ(wolfSSL_get_error(test_ctx.c_ssl, 0),
WC_NO_ERR_TRACE(OCSP_NEED_URL));
test_ssl_memio_cleanup(&test_ctx);
return EXPECT_RESULT();
}
#else
int test_ocsp_no_url_policy(void)
{
return TEST_SKIPPED;
}
#endif /* HAVE_OCSP && HAVE_SSL_MEMIO_TESTS_DEPENDENCIES */
#if defined(HAVE_OCSP) && defined(WOLFSSL_TLS13) && \
defined(WOLFSSL_NONBLOCK_OCSP) && defined(HAVE_MAX_FRAGMENT) && \
defined(HAVE_SSL_MEMIO_TESTS_DEPENDENCIES) && \
+1
View File
@@ -33,6 +33,7 @@ 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_ocsp_no_url_policy(void);
int test_tls13_nonblock_ocsp_low_mfl(void);
int test_ocsp_responder(void);
int test_ocsp_ancestor_responder_rejected(void);
+4 -1
View File
@@ -250,7 +250,10 @@ enum wolfSSL_ErrorCodes {
RPK_UNTRUSTED_E = -521, /* RFC 7250 Raw Public Key not trusted
* out of band */
WOLFSSL_LAST_E = -521
OCSP_NO_URL = -522, /* Cert advertises no OCSP responder
* and no override URL is set */
WOLFSSL_LAST_E = -522
/* codes -1000 to -1999 are reserved for wolfCrypt. */
};
+2
View File
@@ -2668,6 +2668,8 @@ struct WOLFSSL_CERT_MANAGER {
byte crlCheckAll:1; /* always leaf, but all ? */
byte ocspEnabled:1; /* is OCSP on ? */
byte ocspCheckAll:1; /* always leaf, but all ? */
byte ocspFailIfNotSupported:1; /* refuse a cert that advertises
* no OCSP responder ? */
byte ocspSendNonce:1; /* send the OCSP nonce ? */
byte ocspUseOverrideURL:1; /* ignore cert responder, override */
byte ocspStaplingEnabled:1; /* is OCSP Stapling on ? */
+1
View File
@@ -69,6 +69,7 @@ WOLFSSL_LOCAL int CheckCertOCSP_ex(WOLFSSL_OCSP* ocsp, DecodedCert* cert,
WOLFSSL_LOCAL int CheckOcspRequest(WOLFSSL_OCSP* ocsp,
OcspRequest* ocspRequest, WOLFSSL_BUFFER_INFO* responseBuffer,
void* heap);
WOLFSSL_LOCAL int OcspNoUrlPolicy(WOLFSSL_CERT_MANAGER* cm);
WOLFSSL_LOCAL int CheckOcspResponse(WOLFSSL_OCSP *ocsp, byte *response, int responseSz,
WOLFSSL_BUFFER_INFO *responseBuffer, CertStatus *status,
OcspEntry *entry, OcspRequest *ocspRequest,
+6
View File
@@ -2949,7 +2949,13 @@ enum {
enum {
WOLFSSL_OCSP_URL_OVERRIDE = 1,
WOLFSSL_OCSP_NO_NONCE = 2,
/* Check every cert in the chain, not just the leaf. Selects scope only;
* see WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED for failure policy. */
WOLFSSL_OCSP_CHECKALL = 4,
/* Refuse a cert that advertises no OCSP responder, instead of the default
* soft-fail. Independent of WOLFSSL_OCSP_CHECKALL, which decides which
* certs are checked rather than how hard to fail. */
WOLFSSL_OCSP_FAIL_IF_NOT_SUPPORTED = 8,
WOLFSSL_CRL_CHECKALL = 1,
WOLFSSL_CRL_CHECK = 2