curl · Report #3735179
CURLOPT_PROXY_CAINFO_BLOB silently activates native CA store on Apple builds
How it was found
Hi all,
CURLOPT_PROXY_CAINFO_BLOB (introduced 7.77.0) never sets proxy_ssl.custom_cablob. On USE_APPLE_SECTRUST / CURL_CA_NATIVE builds this causes curl to silently fall back to the system keychain for proxy TLS verification, nullifying the caller's blob-only trust policy.
Root cause
lib/setopt.c handles the two CA blob options back-to-back. The non-proxy handler sets the guard bit; the proxy handler never has:
/* CURLOPT_CAINFO_BLOB — correct */
if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) {
s->ssl.custom_cablob = TRUE; /* ← set */
return Curl_setblobopt(&s->blobs[BLOB_CAINFO], blob);
}
/* CURLOPT_PROXY_CAINFO_BLOB — bug */
if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB))
return Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob);
/* proxy_ssl.custom_cablob never touched */
Curl_ssl_easy_config_complete() (lib/vtls/vtls.c:351) then sees three zero bits and arms the native store for the proxy config:
#if defined(USE_APPLE_SECTRUST) || defined(CURL_CA_NATIVE)
if(!sslc->custom_capath && !sslc->custom_cafile && !sslc->custom_cablob)
sslc->native_ca_store = TRUE; /* fires unconditionally on proxy blob path */
#endif
With native_ca_store set, ossl_apple_verify() (lib/vtls/openssl.c:4792) fires as a fallback whenever OpenSSL's in-process store (loaded from the blob) rejects the proxy certificate. Apple SecTrust then accepts any certificate signed by a system-trusted CA — including corporate MITM CAs installed via MDM — and the connection succeeds. Verbose output emits SSL certificate verified via Apple SecTrust. with no indication that blob-only trust was overridden.
Proof
Verified against an in-tree USE_APPLE_SECTRUST=ON build of 8.20.0. The test calls into the library directly — no network needed.
#include "curl_config.h"
#include "curl_setup.h"
#include <curl/curl.h>
#include "urldata.h"
#include "vtls/vtls.h"
#include <stdio.h>
#include <string.h>
int main(void)
{
static const char ca[] = "-----BEGIN CERTIFICATE-----\nMIIB\n-----END CERTIFICATE-----\n";
struct curl_blob blob = { (void *)ca, sizeof(ca), CURL_BLOB_COPY };
struct Curl_easy *e;
curl_global_init(CURL_GLOBAL_DEFAULT);
e = (struct Curl_easy *)curl_easy_init();
/* non-proxy: setopt sets custom_cablob, suppressing native store */
curl_easy_setopt(e, CURLOPT_CAINFO_BLOB, &blob);
Curl_ssl_easy_config_complete(e);
printf("ssl.custom_cablob=%d ssl.native_ca_store=%d (expected 1 0)\n",
e->set.ssl.custom_cablob, e->set.ssl.native_ca_store);
/* proxy: setopt never sets custom_cablob; native store fires */
curl_easy_setopt(e, CURLOPT_PROXY_CAINFO_BLOB, &blob);
e->set.proxy_ssl.native_ca_store = 0;
Curl_ssl_easy_config_complete(e);
printf("proxy_ssl.custom_cablob=%d proxy_ssl.native_ca_store=%d (expected 1 0)\n",
e->set.proxy_ssl.custom_cablob, e->set.proxy_ssl.native_ca_store);
curl_easy_cleanup(e);
curl_global_cleanup();
return e->set.proxy_ssl.native_ca_store; /* 1 = bug */
}
Output (exit code 1):
$ proof_cablob
ssl.custom_cablob=1 ssl.native_ca_store=0 (expected 1 0)
proxy_ssl.custom_cablob=0 proxy_ssl.native_ca_store=1 (expected 1 0)
Fix
One line in lib/setopt.c, mirroring the non-proxy case:
- if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB))
- return Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob);
+ if(Curl_ssl_supports(data, SSLSUPP_CAINFO_BLOB)) {
+ s->proxy_ssl.custom_cablob = TRUE;
+ return Curl_setblobopt(&s->blobs[BLOB_CAINFO_PROXY], blob);
+ }
Thanks, AISLE Research Team
Impact
Setting CURLOPT_PROXY_CAINFO_BLOB on Apple builds silently falls back to trusting the system keychain for proxy TLS, allowing a proxy certificate signed by any system-trusted CA — including MDM-installed corporate MITM CAs — to be accepted even though the caller supplied an explicit CA blob expecting blob-only trust