Skip to content

Commit 71d8df4

Browse files
committed
Search for suitable system CA bundle path
Significantly improves the compatibility of zsync2 and libzsync2 on random Linux distributions, which is really important when embedding it inside cross-platform bundles like AppImages.
1 parent 1f5749c commit 71d8df4

File tree

3 files changed

+89
-8
lines changed

3 files changed

+89
-8
lines changed

src/legacy_http.c

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@ void setup_curl_handle(CURL *handle)
134134
if (verbose!=NULL){
135135
curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
136136
}
137+
138+
const char* ca_bundle = ca_bundle_path();
139+
if (ca_bundle != NULL) {
140+
curl_easy_setopt(handle, CURLOPT_CAINFO, ca_bundle);
141+
}
137142
}
138143

139144

@@ -785,3 +790,77 @@ void range_fetch_end(struct range_fetch *rf) {
785790
free(rf->url);
786791
free(rf);
787792
}
793+
794+
// returns non-zero ("true") on success, 0 ("false") on failure
795+
int file_exists(const char* path) {
796+
struct stat statbuf = {};
797+
798+
if (stat(path, &statbuf) == 0) {
799+
return 1;
800+
}
801+
802+
const int error = errno;
803+
if (error != ENOENT) {
804+
fprintf(stderr, "zsync2: Unknown error while checking whether file %s exists: %s\n", path, strerror(error));
805+
}
806+
807+
return 0;
808+
}
809+
810+
// memory returned by this function must not be freed by the user
811+
const char* ca_bundle_path() {
812+
// in case the user specifies a custom file, we use this one instead
813+
// TODO: consider merging the user-specified file with the system CA bundle into a temp file
814+
const char* path_from_environment = getenv("SSL_CERT_FILE");
815+
816+
if (path_from_environment != NULL) {
817+
return path_from_environment;
818+
}
819+
820+
#if CURL_AT_LEAST_VERSION(7, 84, 0)
821+
{
822+
// (very) recent libcurl versions provide us with a way to query the build-time search path they
823+
// use to find a suitable (distro-provided) CA certificate bundle they can hardcode
824+
// we can use this value to search for a bundle dynamically in the application
825+
CURL* curl = curl_easy_init();
826+
827+
if (curl != NULL) {
828+
CURLcode res;
829+
char* curl_provided_path = NULL;
830+
831+
curl_easy_getinfo(curl, CURLINFO_CAINFO, &curl_provided_path);
832+
833+
// should be safe to delete the handle and use the returned value, since it is allocated statically within libcurl
834+
curl_easy_cleanup(curl);
835+
836+
if (curl_provided_path != NULL) {
837+
if (file_exists(curl_provided_path)) {
838+
return curl_provided_path;
839+
}
840+
}
841+
}
842+
}
843+
#endif
844+
845+
// this list is a compilation of other AppImage projects' lists and the one used in libcurl's build system's autodiscovery
846+
// should cover most Linux distributions
847+
static const char* const possible_ca_bundle_paths[] = {
848+
"/etc/pki/tls/cacert.pem",
849+
"/etc/pki/tls/cert.pem",
850+
"/etc/pki/tls/certs/ca-bundle.crt",
851+
"/etc/ssl/ca-bundle.pem",
852+
"/etc/ssl/cert.pem",
853+
"/etc/ssl/certs/ca-certificates.crt",
854+
"/usr/local/share/certs/ca-root-nss.crt",
855+
"/usr/share/ssl/certs/ca-bundle.crt",
856+
};
857+
858+
for (size_t i = 0; i < sizeof(possible_ca_bundle_paths); ++i) {
859+
const char* path_to_check = possible_ca_bundle_paths[i];
860+
if (file_exists(path_to_check)) {
861+
return path_to_check;
862+
}
863+
}
864+
865+
return NULL;
866+
}

src/legacy_http.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ void range_fetch_addranges(struct range_fetch* rf, off_t* ranges, int nranges);
2323
int get_range_block(struct range_fetch* rf, off_t* offset, unsigned char* data, size_t dlen);
2424
off_t range_fetch_bytes_down(const struct range_fetch* rf);
2525
void range_fetch_end(struct range_fetch* rf);
26+
const char* ca_bundle_path();

src/zsclient.cpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// system includes
55
#include <algorithm>
66
#include <deque>
7-
#include <fstream>
7+
#include <filesystem>
88
#include <fcntl.h>
99
#include <iostream>
1010
#include <set>
@@ -13,6 +13,7 @@
1313
#include <utime.h>
1414

1515
// library includes
16+
#include <curl/curl.h>
1617
#include <cpr/cpr.h>
1718

1819
extern "C" {
@@ -271,17 +272,17 @@ namespace zsync2 {
271272
// request so-called Instance Digest (RFC 3230, RFC 5843)
272273
session.SetHeader(cpr::Header{{"want-digest", "sha-512;q=1, sha-256;q=0.9, sha;q=0.2, md5;q=0.1"}});
273274

274-
// cURL hardcodes the current distro's CA bundle path
275+
// cURL hardcodes the current distro's CA bundle path at build time
275276
// in order to use libzsync2 on other distributions (e.g., when used in an AppImage), the right path
276-
// must be passed to cURL
277-
// we could do this within the library, but it is probably easier to have the caller provide the right
278-
// path, since we can just pass one additional path
279-
// note that in upstream releases of AppImageUpdate and zsync2, we use cURL versions which search for
280-
// a CA bundle in multiple locations
277+
// to the system CA bundle must be passed to cURL
281278
{
282-
char* caBundlePath = getenv("SSL_CERT_FILE");
279+
const auto* caBundlePath = ca_bundle_path();
283280

284281
if (caBundlePath != nullptr) {
282+
// maybe the legacy C code should log this again, but then again, this function should return
283+
// the same result over and over again unless someone deletes a file in the background
284+
issueStatusMessage("Using CA bundle found on system: " + std::string(caBundlePath));
285+
285286
auto sslOptions = cpr::SslOptions{};
286287
sslOptions.SetOption({cpr::ssl::CaInfo{caBundlePath}});
287288
session.SetOption(sslOptions);

0 commit comments

Comments
 (0)