curl · Report #3694390
CVE-2026-7009: OCSP stapling bypass with Apple SecTrust
How it was found
Summary
When curl is built with --with-apple-sectrust (or -DUSE_APPLE_SECTRUST=ON) and OpenSSL, the --cert-status / CURLOPT_SSL_VERIFYSTATUS option is silently bypassed when Apple SecTrust handles certificate chain verification instead of OpenSSL.
The user explicitly requests OCSP stapling enforcement, but the connection succeeds even when the server provides no OCSP staple — violating the documented guarantee that curl "aborts the connection" when no OCSP response is received.
Root Cause
In lib/vtls/openssl.c, Curl_ossl_check_peer_cert(), commit b4630ed8fa (2025-10-31, fixing #19307) introduced a sectrust_verified flag that skips OpenSSL's verifystatus() call when Apple SecTrust verified the certificate chain:
#ifdef USE_APPLE_SECTRUST
if(!verified && conn_config->verifypeer && ssl_config->native_ca_store) {
result = ossl_apple_verify(cf, data, octx, peer, &verified);
if(verified) {
sectrust_verified = TRUE;
}
}
#endif
...
if(conn_config->verifystatus &&
#ifdef USE_APPLE_SECTRUST
!sectrust_verified && /* <-- skips the OCSP check */
#endif
!octx->reused_session) {
result = verifystatus(cf, data, octx); /* never reached */
}
The assumption is "SecTrust already handled OCSP." But SecTrust only receives a stapled OCSP response when ocsp_len > 0 (in ossl_apple_verify). If the server doesn't staple, SSL_get_tlsext_status_ocsp_resp() returns -1, which is clamped to 0, and SecTrustSetOCSPResponse() is never called. SecTrust's default revocation policy (kSecRevocationUseAnyAvailableMethod) is best-effort — kSecRevocationRequirePositiveResponse is explicitly #if 0 in apple.c.
Result: no OCSP enforcement happens at all.
Affected Versions
Introduced in commit b4630ed8fa (2025-10-31). Present in curl 8.17.0, 8.18.0, 8.19.0, and current master.
Steps To Reproduce
Step 1: Build two versions of curl
Tested on curl master at commit bfcc1c6eb4. The bug was introduced in commit b4630ed8fa (2025-10-31), so any version >= 8.17.0 is affected.
Build A — OpenSSL only (control):
cmake -DCURL_USE_OPENSSL=ON -DUSE_APPLE_SECTRUST=OFF
curl --version
curl 8.20.0-DEV (Darwin) libcurl/8.20.0-DEV OpenSSL/3.6.1 zlib/1.2.12 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.68.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns ldap ldaps mqtt mqtts pop3 pop3s rtsp smb smbs smtp smtps telnet tftp ws wss
Features: alt-svc AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz NTLM PSL SSL threadsafe TLS-SRP UnixSockets zstd
Build B — OpenSSL + Apple SecTrust (test):
cmake -DCURL_USE_OPENSSL=ON -DUSE_APPLE_SECTRUST=ON
curl --version
curl 8.20.0-DEV (Darwin) libcurl/8.20.0-DEV OpenSSL/3.6.1 zlib/1.2.12 brotli/1.2.0 zstd/1.5.7 libidn2/2.3.8 libpsl/0.21.5 nghttp2/1.68.0
Release-Date: [unreleased]
Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt mqtts pop3 pop3s rtsp smtp smtps telnet tftp ws wss
Features: alt-svc AppleSecTrust AsynchDNS brotli HSTS HTTP2 HTTPS-proxy IDN IPv6 Largefile libz PSL SSL threadsafe TLS-SRP UnixSockets zstd
Note AppleSecTrust in the Features line of Build B.
Step 2: Generate test certificates
# Generate CA
openssl req -x509 -newkey rsa:2048 -days 30 -nodes \
-keyout ca-key.pem -out ca.pem -subj "/CN=PoC Test CA"
# Generate server cert signed by CA (SAN=localhost)
openssl req -newkey rsa:2048 -nodes \
-keyout server-key.pem -out server.csr -subj "/CN=localhost"
openssl x509 -req -days 30 -in server.csr \
-CA ca.pem -CAkey ca-key.pem -CAcreateserial \
-out server.pem -extfile <(printf "subjectAltName=DNS:localhost")
Step 3: Add CA to macOS Keychain
Add the CA to the macOS login keychain as trusted:
security add-trusted-cert -r trustRoot -k ~/Library/Keychains/login.keychain-db ca.pem
Step 4: Start test server (no OCSP stapling)
openssl s_server does not staple OCSP by default:
openssl s_server -cert server.pem -key server-key.pem -accept 4433 -www
Step 5: Run the three tests
Test 1 — Control (Build A, OpenSSL only):
curl -v --cert-status --cacert ca.pem https://localhost:4433/
Test 2 — Bug (Build B, SecTrust):
curl -v --cert-status --ca-native https://localhost:4433/
Test 3 — Sanity (Build B, SecTrust, no --cert-status):
curl -v --ca-native https://localhost:4433/
Clean up when done:
security delete-certificate -c "PoC Test CA" ~/Library/Keychains/login.keychain-db
Results
| Test | Build | Flags | Result | Expected |
|---|---|---|---|---|
| 1 | OpenSSL only | --cert-status --cacert ca.pem | FAIL (exit 91): "No OCSP response received" | Correct — OCSP enforced |
| 2 | OpenSSL + SecTrust | --cert-status --ca-native | SUCCESS (exit 0) | WRONG — OCSP bypassed |
| 3 | OpenSSL + SecTrust | --ca-native | SUCCESS (exit 0) | Correct — no OCSP requested |
Test 1 verbose output (relevant lines):
* OpenSSL verify result: 0
* SSL certificate verified via OpenSSL.
* No OCSP response received
* closing connection #0
curl: (91) No OCSP response received
Test 2 verbose output (relevant lines):
* OpenSSL verify result: 14
* SSL certificate verified via Apple SecTrust.
* Established connection to localhost (::1 port 4433) from ::1 port 49751
No OCSP check occurs. The connection succeeds despite --cert-status being set and the server providing no OCSP staple.
Impact
Summary:
A user who sets --cert-status / CURLOPT_SSL_VERIFYSTATUS expects curl to abort the connection if the server does not provide a valid OCSP stapled response. This is documented behavior: "If this option is enabled and the server sends an invalid response, or no response at all is received, the verification fails."
When Apple SecTrust handles verification (build with USE_APPLE_SECTRUST, runtime with --ca-native), this guarantee is silently violated. The OCSP stapling check is skipped entirely, and the connection proceeds as if --cert-status was never set.
This is the same vulnerability class as CVE-2024-8096 (OCSP stapling bypass with GnuTLS) and CVE-2024-0853 (OCSP verification bypass with TLS session reuse).