Windows: point libcurl at a CA bundle
ci / linux (clang) (push) Canceled after 0s
ci / linux (gcc) (push) Canceled after 0s
ci / sanitizers (push) Canceled after 0s
ci / musl (push) Canceled after 0s
ci / macos (push) Canceled after 0s
ci / linux (clang) (pull_request) Canceled after 0s
ci / linux (gcc) (pull_request) Canceled after 0s
ci / sanitizers (pull_request) Canceled after 0s
ci / musl (pull_request) Canceled after 0s
ci / macos (pull_request) Canceled after 0s

An OpenSSL-based libcurl keeps the CA path it was built with. MSYS2's is
"/mingw64/etc/ssl/certs/ca-bundle.crt", which resolves to nothing once the
binary runs outside an MSYS2 shell, and that build ignores CURL_CA_BUNDLE,
SSL_CERT_FILE and SSL_CERT_DIR. Every HTTPS request then failed with
CURLE_SSL_CACERT_BADFILE (77).

llm_apply_ca_bundle() sets CURLOPT_CAINFO from $CST_CA_BUNDLE, falling back
to ca-bundle.crt beside the executable. If neither exists nothing is set,
so a libcurl linked against Schannel (the Windows certificate store)
behaves exactly as before.
This commit was merged in pull request #2.
This commit is contained in:
XYZ
2026-10-06 17:56:56 +08:00
parent 929f7125b8
commit 9862c29f58
+45 -4
View File
@@ -22,6 +22,45 @@
#define ERRBODY_MAX 4096
#define LLM_MAX_RETRIES 3
/* Point libcurl at a CA bundle on Windows.
*
* An OpenSSL-based libcurl keeps the path it was built with -- MSYS2's is
* "/mingw64/etc/ssl/certs/ca-bundle.crt", which resolves to nothing once the
* binary runs outside an MSYS2 shell, and that build ignores CURL_CA_BUNDLE.
* Every HTTPS request then dies with CURLE_SSL_CACERT_BADFILE (77).
*
* Order: $CST_CA_BUNDLE, then ca-bundle.crt beside the executable. If neither
* exists nothing is set, so a libcurl linked against Schannel (the Windows
* certificate store) behaves exactly as before. */
static void llm_apply_ca_bundle(CURL *curl) {
#if defined(_WIN32)
static char ca[OS_PATH_MAX];
static int checked;
if (!checked) {
checked = 1;
const char *env = getenv("CST_CA_BUNDLE");
if (env && env[0]) {
snprintf(ca, sizeof(ca), "%s", env);
} else {
char dir[OS_PATH_MAX];
if (os_exe_dir(dir, sizeof(dir)) == 0)
os_path_join(ca, sizeof(ca), dir, "ca-bundle.crt");
}
if (ca[0]) {
FILE *f = fopen(ca, "rb");
if (f)
fclose(f);
else
ca[0] = '\0';
}
}
if (ca[0])
curl_easy_setopt(curl, CURLOPT_CAINFO, ca);
#else
(void)curl;
#endif
}
/* --- wire body ----------------------------------------------------------- */
static size_t wire_tool_cap(const cst_llm_request_t *req) {
@@ -621,6 +660,7 @@ int cst_llm_chat_stream(const cst_llm_request_t *req, char *err,
ret = -1;
break;
}
llm_apply_ca_bundle(curl);
sse_reset(&s);
s.curl = curl;
if (rec_dir && rec_dir[0]) {
@@ -765,10 +805,11 @@ int cst_llm_list_models(const char *api_base, const char *api_key,
}
CURL *curl = curl_easy_init();
if (!curl) {
snprintf(err, err_sz, "curl init failed");
return -1;
}
char url[1024];
snprintf(err, err_sz, "curl init failed");
return -1;
}
llm_apply_ca_bundle(curl);
char url[1024];
size_t bl = strlen(api_base);
while (bl && api_base[bl - 1] == '/')
bl--;