curl: support embedding a CA bundle

Add the ability to embed a CA bundle into the curl binary. It is used
when no other runtime or build-time option set one.

This helps curl-for-win macOS and Linux builds to run standalone, and
also helps Windows builds to avoid picking up the CA bundle from an
arbitrary (possibly world-writable) location (though this behaviour is
not currently disablable).

Usage:
- cmake: `-DCURL_CA_EMBED=/path/to/curl-ca-bundle.crt`
- autotools: `--with-ca-embed=/path/to/curl-ca-bundle.crt`
- Makefile.mk: `CURL_CA_EMBED=/path/to/curl-ca-bundle.crt`

Also add new command-line option `--dump-ca-embed` to dump the embedded
CA bundle to standard output.

Closes #14059
This commit is contained in:
Viktor Szakats 2024-06-29 03:30:14 +02:00
parent 87aa4ebd82
commit 8a3740bc8e
No known key found for this signature in database
GPG key ID: B5ABD165E2AEF201
26 changed files with 268 additions and 14 deletions

1
src/.gitignore vendored
View file

@ -10,5 +10,6 @@ curl
curl_config.h
curl_config.h.in
stamp-h2
tool_ca_embed.c
tool_hugehelp.c
tool_version.h.dist

View file

@ -54,6 +54,22 @@ endif()
transform_makefile_inc("Makefile.inc" "${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake")
include(${CMAKE_CURRENT_BINARY_DIR}/Makefile.inc.cmake)
if(CURL_CA_EMBED_SET)
if(PERL_FOUND)
add_definitions("-DCURL_CA_EMBED")
add_custom_command(
OUTPUT tool_ca_embed.c
COMMAND "${PERL_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/mk-file-embed.pl" --var curl_ca_embed < "${CURL_CA_EMBED}" > tool_ca_embed.c
DEPENDS
"${CURL_CA_EMBED}"
"${CMAKE_CURRENT_SOURCE_DIR}/mk-file-embed.pl"
VERBATIM)
list(APPEND CURL_CFILES tool_ca_embed.c)
else()
message(WARNING "Perl not found. Will not embed the CA bundle.")
endif()
endif()
if(WIN32)
list(APPEND CURL_CFILES curl.rc)
endif()

View file

@ -88,7 +88,7 @@ CLEANFILES = tool_hugehelp.c
# embedded text.
NROFF=env LC_ALL=C @NROFF@ @MANOPT@ 2>/dev/null # figured out by the configure script
EXTRA_DIST = mkhelp.pl \
EXTRA_DIST = mk-file-embed.pl mkhelp.pl \
Makefile.mk curl.rc Makefile.inc CMakeLists.txt .checksrc
# Use absolute directory to disable VPATH
@ -135,11 +135,25 @@ $(HUGE):
echo '#include "tool_hugehelp.h"' >> $(HUGE)
endif
# ignore tool_hugehelp.c since it is generated source code and it plays
# by slightly different rules!
CA_EMBED_CSOURCE = tool_ca_embed.c
CURL_CFILES += $(CA_EMBED_CSOURCE)
CLEANFILES += $(CA_EMBED_CSOURCE)
if CURL_CA_EMBED_SET
AM_CPPFLAGS += -DCURL_CA_EMBED
MK_FILE_EMBED = $(top_srcdir)/src/mk-file-embed.pl
$(CA_EMBED_CSOURCE): $(MK_FILE_EMBED)
$(PERL) $(MK_FILE_EMBED) --var curl_ca_embed < $(CURL_CA_EMBED) > $(CA_EMBED_CSOURCE)
else
$(CA_EMBED_CSOURCE):
echo 'extern const void *curl_ca_embed; const void *curl_ca_embed;' > $(CA_EMBED_CSOURCE)
endif
# ignore generated C files since they play by slightly different rules!
checksrc:
$(CHECKSRC)(@PERL@ $(top_srcdir)/scripts/checksrc.pl -D$(srcdir) \
-W$(srcdir)/tool_hugehelp.c $(srcdir)/*.[ch])
-W$(srcdir)/$(HUGE) \
-W$(srcdir)/$(CA_EMBED_CSOURCE) \
$(srcdir)/*.[ch])
if DEBUGBUILD
# for debug builds, we scan the sources on all regular make invokes

View file

@ -45,6 +45,11 @@ TARGETS := curl$(BIN_EXT)
CURL_CFILES += $(notdir $(CURLX_CFILES))
ifneq ($(CURL_CA_EMBED),)
CPPFLAGS += -DCURL_CA_EMBED
CURL_CFILES += tool_ca_embed.c
endif
curl_OBJECTS := $(patsubst %.c,$(OBJ_DIR)/%.o,$(strip $(CURL_CFILES)))
ifdef MAP
CURL_MAP := curl.map
@ -57,8 +62,9 @@ TOCLEAN := $(curl_OBJECTS)
### Rules
ifneq ($(wildcard tool_hugehelp.c.cvs),)
PERL ?= perl
ifneq ($(wildcard tool_hugehelp.c.cvs),)
NROFF ?= groff
TOCLEAN += tool_hugehelp.c
@ -84,6 +90,12 @@ tool_hugehelp.c:
endif
endif
ifneq ($(CURL_CA_EMBED),)
TOCLEAN += tool_ca_embed.c
tool_ca_embed.c: mk-file-embed.pl
$(PERL) mk-file-embed.pl --var curl_ca_embed < $(CURL_CA_EMBED) > $@
endif
$(TARGETS): $(curl_OBJECTS) $(PROOT)/lib/libcurl.a
$(CC) $(LDFLAGS) -o $@ $(curl_OBJECTS) $(LIBS)

56
src/mk-file-embed.pl Executable file
View file

@ -0,0 +1,56 @@
#!/usr/bin/env perl
#***************************************************************************
# _ _ ____ _
# Project ___| | | | _ \| |
# / __| | | | |_) | |
# | (__| |_| | _ <| |___
# \___|\___/|_| \_\_____|
#
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at https://curl.se/docs/copyright.html.
#
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
# copies of the Software, and permit persons to whom the Software is
# furnished to do so, under the terms of the COPYING file.
#
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# KIND, either express or implied.
#
# SPDX-License-Identifier: curl
#
###########################################################################
my $varname = "var";
if($ARGV[0] eq "--var") {
shift;
$varname = shift @ARGV;
}
print <<HEAD
/*
* NEVER EVER edit this manually, fix the mk-file-embed.pl script instead!
*/
extern const unsigned char ${varname}[];
const unsigned char ${varname}[] = {
HEAD
;
while (<STDIN>) {
my $line = $_;
foreach my $n (split //, $line) {
my $ord = ord($n);
printf("%s,", $ord);
if($ord == 10) {
printf("\n");
}
}
}
print <<ENDLINE
0
};
ENDLINE
;

View file

@ -122,6 +122,7 @@ typedef enum {
C_DOH_CERT_STATUS,
C_DOH_INSECURE,
C_DOH_URL,
C_DUMP_CA_EMBED,
C_DUMP_HEADER,
C_ECH,
C_EGD_FILE,
@ -408,6 +409,7 @@ static const struct LongShort aliases[]= {
{"doh-cert-status", ARG_BOOL, ' ', C_DOH_CERT_STATUS},
{"doh-insecure", ARG_BOOL, ' ', C_DOH_INSECURE},
{"doh-url" , ARG_STRG, ' ', C_DOH_URL},
{"dump-ca-embed", ARG_NONE, ' ', C_DUMP_CA_EMBED},
{"dump-header", ARG_FILE, 'D', C_DUMP_HEADER},
{"ech", ARG_STRG, ' ', C_ECH},
{"egd-file", ARG_STRG, ' ', C_EGD_FILE},
@ -2113,6 +2115,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
case C_URL_QUERY: /* --url-query */
err = url_query(nextarg, global, config);
break;
case C_DUMP_CA_EMBED: /* --dump-ca-embed */
err = PARAM_CA_EMBED_REQUESTED;
break;
case C_DUMP_HEADER: /* --dump-header */
err = getstr(&config->headerfile, nextarg, DENY_BLANK);
break;
@ -2984,7 +2989,8 @@ ParameterError parse_args(struct GlobalConfig *global, int argc,
if(result && result != PARAM_HELP_REQUESTED &&
result != PARAM_MANUAL_REQUESTED &&
result != PARAM_VERSION_INFO_REQUESTED &&
result != PARAM_ENGINES_REQUESTED) {
result != PARAM_ENGINES_REQUESTED &&
result != PARAM_CA_EMBED_REQUESTED) {
const char *reason = param2text(result);
if(orig_opt && strcmp(":", orig_opt))

View file

@ -35,6 +35,7 @@ typedef enum {
PARAM_MANUAL_REQUESTED,
PARAM_VERSION_INFO_REQUESTED,
PARAM_ENGINES_REQUESTED,
PARAM_CA_EMBED_REQUESTED,
PARAM_GOT_EXTRA_PARAMETER,
PARAM_BAD_NUMERIC,
PARAM_NEGATIVE_NUMERIC,

View file

@ -244,10 +244,28 @@ void tool_version_info(void)
puts(""); /* newline */
}
if(feature_names[0]) {
printf("Features:");
for(builtin = feature_names; *builtin; ++builtin)
printf(" %s", *builtin);
puts(""); /* newline */
const char **feat_ext;
size_t feat_ext_count = feature_count;
#ifdef CURL_CA_EMBED
++feat_ext_count;
#endif
feat_ext = malloc(sizeof(*feature_names) * (feat_ext_count + 1));
if(feat_ext) {
memcpy((void *)feat_ext, feature_names,
sizeof(*feature_names) * feature_count);
feat_ext_count = feature_count;
#ifdef CURL_CA_EMBED
feat_ext[feat_ext_count++] = "CAcert";
#endif
feat_ext[feat_ext_count] = NULL;
qsort((void *)feat_ext, feat_ext_count, sizeof(*feat_ext),
struplocompare4sort);
printf("Features:");
for(builtin = feat_ext; *builtin; ++builtin)
printf(" %s", *builtin);
puts(""); /* newline */
free((void *)feat_ext);
}
}
if(strcmp(CURL_VERSION, curlinfo->version)) {
printf("WARNING: curl and libcurl versions do not match. "

View file

@ -124,6 +124,7 @@ static struct feature_name_presentp {
static const char *fnames[sizeof(maybe_feature) / sizeof(maybe_feature[0])];
const char * const *feature_names = fnames;
size_t feature_count;
/*
* libcurl_info_init: retrieves runtime information about libcurl,
@ -182,6 +183,7 @@ CURLcode get_libcurl_info(void)
*p->feature_presentp = TRUE;
break;
}
++feature_count;
}
return CURLE_OK;

View file

@ -34,6 +34,7 @@ extern const char * const *built_in_protos;
extern size_t proto_count;
extern const char * const *feature_names;
extern size_t feature_count;
extern const char *proto_file;
extern const char *proto_ftp;

View file

@ -165,6 +165,9 @@ const struct helptxt helptext[] = {
{" --doh-url <URL>",
"Resolve hostnames over DoH",
CURLHELP_DNS},
{" --dump-ca-embed",
"Write the embedded CA bundle to standard output",
CURLHELP_HTTP | CURLHELP_PROXY | CURLHELP_TLS},
{"-D, --dump-header <filename>",
"Write the received headers to <filename>",
CURLHELP_HTTP | CURLHELP_FTP},

View file

@ -94,6 +94,10 @@
#include "memdebug.h" /* keep this as LAST include */
#ifdef CURL_CA_EMBED
extern const unsigned char curl_ca_embed[];
#endif
#ifndef O_BINARY
/* since O_BINARY as used in bitmasks, setting it to zero makes it usable in
source code but yet it does not ruin anything */
@ -1657,6 +1661,37 @@ static CURLcode single_transfer(struct GlobalConfig *global,
break;
}
#ifdef CURL_CA_EMBED
if(!config->cacert && !config->capath) {
struct curl_blob blob;
blob.data = (void *)curl_ca_embed;
blob.len = strlen((const char *)curl_ca_embed);
blob.flags = CURL_BLOB_NOCOPY;
notef(config->global,
"Using embedded CA bundle (%zu bytes)",
blob.len);
result = curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob);
if(result == CURLE_NOT_BUILT_IN) {
warnf(global,
"ignoring embedded CA bundle, not supported by libcurl");
}
}
if(!config->proxy_cacert && !config->proxy_capath) {
struct curl_blob blob;
blob.data = (void *)curl_ca_embed;
blob.len = strlen((const char *)curl_ca_embed);
blob.flags = CURL_BLOB_NOCOPY;
notef(config->global,
"Using embedded CA bundle, for proxies (%zu bytes)",
blob.len);
result = curl_easy_setopt(curl, CURLOPT_PROXY_CAINFO_BLOB, &blob);
if(result == CURLE_NOT_BUILT_IN) {
warnf(global,
"ignoring embedded CA bundle, not supported by libcurl");
}
}
#endif
if(config->crlfile)
my_setopt_str(curl, CURLOPT_CRLFILE, config->crlfile);
if(config->proxy_crlfile)
@ -2842,6 +2877,12 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
/* Check if we were asked to list the SSL engines */
else if(res == PARAM_ENGINES_REQUESTED)
tool_list_engines();
/* Check if we were asked to dump the embedded CA bundle */
else if(res == PARAM_CA_EMBED_REQUESTED) {
#ifdef CURL_CA_EMBED
printf("%s", curl_ca_embed);
#endif
}
else if(res == PARAM_LIBCURL_UNSUPPORTED_PROTOCOL)
result = CURLE_UNSUPPORTED_PROTOCOL;
else if(res == PARAM_READ_ERROR)

View file

@ -262,7 +262,8 @@ int parseconfig(const char *filename, struct GlobalConfig *global)
if(res != PARAM_HELP_REQUESTED &&
res != PARAM_MANUAL_REQUESTED &&
res != PARAM_VERSION_INFO_REQUESTED &&
res != PARAM_ENGINES_REQUESTED) {
res != PARAM_ENGINES_REQUESTED &&
res != PARAM_CA_EMBED_REQUESTED) {
const char *reason = param2text(res);
errorf(operation->global, "%s:%d: '%s' %s",
filename, lineno, option, reason);