mirror of
https://github.com/wolfSSL/wolfssl.git
synced 2026-08-04 03:54:10 +02:00
Merge pull request #10945 from ejohnstown/ocsp-fail
OCSP: opt-in fail-closed on missing responder
This commit is contained in:
+24
-6
@@ -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
@@ -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
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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) && \
|
||||
|
||||
@@ -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
@@ -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. */
|
||||
};
|
||||
|
||||
@@ -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 ? */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user