From 397e11bfa6175c525d9365e63162888a77008213 Mon Sep 17 00:00:00 2001 From: moparisthebest Date: Fri, 3 Apr 2020 00:32:42 -0400 Subject: [PATCH] Add CURLOPT_CAINFO_PEM --- CMakeLists.txt | 2 ++ acinclude.m4 | 18 +++++++++++++ configure.ac | 1 + include/curl/curl.h | 8 ++++++ lib/cacert.h | 38 ++++++++++++++++++++++++++ lib/setopt.c | 15 +++++++++++ lib/url.c | 20 ++++++++++++++ lib/urldata.h | 3 +++ lib/vtls/openssl.c | 66 +++++++++++++++++++++++++++++++++++++++++++++ lib/vtls/vtls.c | 3 +++ 10 files changed, 174 insertions(+) create mode 100644 lib/cacert.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a410d4955..d5777ca9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -702,6 +702,8 @@ set(CURL_CA_FALLBACK OFF CACHE BOOL "Set ON to use built-in CA store of TLS backend. Defaults to OFF") set(CURL_CA_PATH "auto" CACHE STRING "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.") +set(CURL_CA_BUNDLE_PEM OFF CACHE BOOL + "Set ON to use built-in CACERT_PEM from generated cacert.h. Defaults to OFF") if("${CURL_CA_BUNDLE}" STREQUAL "") message(FATAL_ERROR "Invalid value of CURL_CA_BUNDLE. Use 'none', 'auto' or file path.") diff --git a/acinclude.m4 b/acinclude.m4 index 089449bac..e262ee100 100644 --- a/acinclude.m4 +++ b/acinclude.m4 @@ -2306,6 +2306,24 @@ AC_HELP_STRING([--without-ca-fallback], [Don't use the built in CA store of the fi AC_DEFINE_UNQUOTED(CURL_CA_FALLBACK, 1, [define "1" to use built in CA store of SSL library ]) fi + + AC_MSG_CHECKING([whether to use built in CACERT_PEM from generated cacert.h]) + AC_ARG_WITH(ca-built-in, +AC_HELP_STRING([--with-ca-built-in], [Use the built in CACERT_PEM from generated cacert.h]) +AC_HELP_STRING([--without-ca-built-in], [Don't use the built in CACERT_PEM from generated cacert.h]), + [ + if test "x$with_ca_built_in" != "xyes" -a "x$with_ca_built_in" != "xno"; then + AC_MSG_ERROR([--with-ca-built-in only allows yes or no as parameter]) + fi + ], + [ with_ca_fallback="no"]) + AC_MSG_RESULT([$with_ca_built_in]) + if test "x$with_ca_built_in" = "xyes"; then + if test "x$OPENSSL_ENABLED" != "x1"; then + AC_MSG_ERROR([--with-ca-built-in only works with OpenSSL]) + fi + AC_DEFINE_UNQUOTED(CURL_CA_BUNDLE_PEM, 1, [define "1" to use built in CACERT_PEM from generated cacert.h ]) + fi ]) dnl CURL_CHECK_WIN32_LARGEFILE diff --git a/configure.ac b/configure.ac index 889617ffa..5543f9c40 100755 --- a/configure.ac +++ b/configure.ac @@ -4982,6 +4982,7 @@ AC_MSG_NOTICE([Configured to build curl/libcurl: ca cert bundle: ${ca}${ca_warning} ca cert path: ${capath}${capath_warning} ca fallback: ${with_ca_fallback} + ca built in: ${with_ca_built_in} LDAP: ${curl_ldap_msg} LDAPS: ${curl_ldaps_msg} RTSP: ${curl_rtsp_msg} diff --git a/include/curl/curl.h b/include/curl/curl.h index 17f07b09f..30cc517ba 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -1954,6 +1954,14 @@ typedef enum { /* allow RCPT TO command to fail for some recipients */ CURLOPT(CURLOPT_MAIL_RCPT_ALLLOWFAILS, CURLOPTTYPE_LONG, 290), + /* The CAfile in memory as PEM used to validate the proxy certificate + this option is used only if PROXY_SSL_VERIFYPEER is true */ + CURLOPT(CURLOPT_CAINFO_PEM, CURLOPTTYPE_STRINGPOINT, 291), + + /* The CAfile in memory as PEM used to validate the proxy certificate + this option is used only if PROXY_SSL_VERIFYPEER is true */ + CURLOPT(CURLOPT_PROXY_CAINFO_PEM, CURLOPTTYPE_STRINGPOINT, 292), + CURLOPT_LASTENTRY /* the last unused */ } CURLoption; diff --git a/lib/cacert.h b/lib/cacert.h new file mode 100644 index 000000000..f637fc52c --- /dev/null +++ b/lib/cacert.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2020, Daniel Stenberg, , 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.haxx.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. + * + ***************************************************************************/ + +/* + +regenerate this file like so: + +curl -Oz cacert.pem https://curl.haxx.se/ca/cacert.pem \ +&& xxd -i -C cacert.pem | sed -r 's/(0x..)$/\1, 0x00/' > lib/cacert.h + +unless CACERT_PEM is defined in here, there will be a compile error if +CURL_CA_BUNDLE_PEM is defined, CACERT_PEM must be a proper null-terminated +C string + +unsigned char CACERT_PEM[] = { + 0x62, 0x6f, 0x62, 0x0a, 0x00 +}; +unsigned int CACERT_PEM_LEN = 4; +*/ diff --git a/lib/setopt.c b/lib/setopt.c index 04785a682..a1bc0532d 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -1944,6 +1944,21 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param) result = CURLE_NOT_BUILT_IN; break; #endif + case CURLOPT_CAINFO_PEM: + /* + * Set CA info for SSL connection. Specify entire PEM of the CA certificate + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PEM_ORIG], + va_arg(param, char *)); + break; + case CURLOPT_PROXY_CAINFO_PEM: + /* + * Set CA info for SSL connection proxy. + * Specify entire PEM of the CA certificate + */ + result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PEM_PROXY], + va_arg(param, char *)); + break; case CURLOPT_CRLFILE: /* * Set CRL file info for SSL connection. Specify file name of the CRL diff --git a/lib/url.c b/lib/url.c index 47fc66aed..adde1c6b1 100644 --- a/lib/url.c +++ b/lib/url.c @@ -127,6 +127,10 @@ bool curl_win32_idn_to_ascii(const char *in, char **out); #include "curl_memory.h" #include "memdebug.h" +#if defined(CURL_CA_BUNDLE_PEM) +#include "cacert.h" +#endif + static void conn_free(struct connectdata *conn); static unsigned int get_protocol_family(unsigned int protocol); @@ -507,6 +511,18 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data) set->socks5_gssapi_nec = FALSE; #endif +#if defined(CURL_CA_BUNDLE_PEM) + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PEM_ORIG], + (const char *) CACERT_PEM); + if(result) + return result; + + result = Curl_setstropt(&set->str[STRING_SSL_CAFILE_PEM_PROXY], + (const char *) CACERT_PEM); + if(result) + return result; +#endif + /* Set the default CA cert bundle/path detected/specified at build time. * * If Schannel is the selected SSL backend then these locations are @@ -3555,6 +3571,10 @@ static CURLcode create_conn(struct Curl_easy *data, data->set.proxy_ssl.primary.CApath = data->set.str[STRING_SSL_CAPATH_PROXY]; data->set.ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_ORIG]; data->set.proxy_ssl.primary.CAfile = data->set.str[STRING_SSL_CAFILE_PROXY]; + data->set.ssl.primary.ca_file_pem = + data->set.str[STRING_SSL_CAFILE_PEM_ORIG]; + data->set.proxy_ssl.primary.ca_file_pem = + data->set.str[STRING_SSL_CAFILE_PEM_PROXY]; data->set.ssl.primary.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; data->set.proxy_ssl.primary.random_file = data->set.str[STRING_SSL_RANDOM_FILE]; diff --git a/lib/urldata.h b/lib/urldata.h index 2a36c1147..2af7df7ae 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -230,6 +230,7 @@ struct ssl_primary_config { char *cipher_list; /* list of ciphers to use */ char *cipher_list13; /* list of TLS 1.3 cipher suites to use */ char *pinned_key; + char *ca_file_pem; BIT(verifypeer); /* set TRUE if this is desired */ BIT(verifyhost); /* set TRUE if CN/SAN must match hostname */ BIT(verifystatus); /* set TRUE if certificate status must be checked */ @@ -1529,6 +1530,8 @@ enum dupstring { STRING_SSL_CAPATH_PROXY, /* CA directory name (doesn't work on windows) */ STRING_SSL_CAFILE_ORIG, /* certificate file to verify peer against */ STRING_SSL_CAFILE_PROXY, /* certificate file to verify peer against */ + STRING_SSL_CAFILE_PEM_ORIG, /* PEM certificate to verify peer against */ + STRING_SSL_CAFILE_PEM_PROXY, /* PEM certificate to verify peer against */ STRING_SSL_PINNEDPUBLICKEY_ORIG, /* public key file to verify peer against */ STRING_SSL_PINNEDPUBLICKEY_PROXY, /* public key file to verify proxy */ STRING_SSL_CIPHER_LIST_ORIG, /* list of ciphers to use */ diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 176fa522a..21c721503 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -2385,6 +2385,61 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid) return res; } +static CURLcode load_cacert_from_memory(SSL_CTX *ctx, + const char * const ca_file_pem) +{ + /* these need freed at the end */ + BIO *cbio = NULL; + STACK_OF(X509_INFO) *inf = NULL; + + /* everything else is just a reference */ + int i, count = 0; + X509_STORE *cts = NULL; + X509_INFO *itmp = NULL; + + CURLcode result = CURLE_SSL_CACERT_BADFILE; + + do { + /* casting strlen to int feels wrong, but what to do with this API? */ + cbio = BIO_new_mem_buf(ca_file_pem, (int) strlen(ca_file_pem)); + if(!cbio) + break; + + cts = SSL_CTX_get_cert_store(ctx); + if(!cts) + break; + + inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL); + if(!inf) + break; + + /* add each entry from PEM file to x509_store */ + for(i = 0; i < sk_X509_INFO_num(inf); ++i) { + itmp = sk_X509_INFO_value(inf, i); + if(itmp->x509) { + X509_STORE_add_cert(cts, itmp->x509); + ++count; + } + if(itmp->crl) { + X509_STORE_add_crl(cts, itmp->crl); + ++count; + } + } + + /* if we didn't end up importing anything, treat that as an error */ + if(count > 0) + result = CURLE_OK; + } while(0); + + if(inf) + sk_X509_INFO_pop_free(inf, X509_INFO_free); + + if(cbio) + BIO_free(cbio); + + return result; +} + static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) { CURLcode result = CURLE_OK; @@ -2416,6 +2471,7 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) const char * const ssl_cert_type = SSL_SET_OPTION(cert_type); const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile); const char * const ssl_capath = SSL_CONN_CONFIG(CApath); + const char * const ca_file_pem = SSL_CONN_CONFIG(ca_file_pem); const bool verifypeer = SSL_CONN_CONFIG(verifypeer); const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile); char error_buffer[256]; @@ -2720,6 +2776,16 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) } #endif + if(ca_file_pem && load_cacert_from_memory(backend->ctx, ca_file_pem)) { + if(verifypeer) { + /* Fail if we insist on successfully verifying the server. */ + failf(data, "error setting certificate from memory"); + return CURLE_SSL_CACERT_BADFILE; + } + /* Continue with a warning if no certificate verification is required. */ + infof(data, "error setting certificate file, continuing anyway\n"); + } + #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ if(ssl_cafile) { diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index badd93d08..36ab69fa0 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -93,6 +93,7 @@ Curl_ssl_config_matches(struct ssl_primary_config* data, (data->verifystatus == needle->verifystatus) && Curl_safe_strcasecompare(data->CApath, needle->CApath) && Curl_safe_strcasecompare(data->CAfile, needle->CAfile) && + Curl_safe_strcasecompare(data->ca_file_pem, needle->ca_file_pem) && Curl_safe_strcasecompare(data->clientcert, needle->clientcert) && Curl_safe_strcasecompare(data->random_file, needle->random_file) && Curl_safe_strcasecompare(data->egdsocket, needle->egdsocket) && @@ -117,6 +118,7 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source, CLONE_STRING(CApath); CLONE_STRING(CAfile); + CLONE_STRING(ca_file_pem); CLONE_STRING(clientcert); CLONE_STRING(random_file); CLONE_STRING(egdsocket); @@ -131,6 +133,7 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config* sslc) { Curl_safefree(sslc->CApath); Curl_safefree(sslc->CAfile); + Curl_safefree(sslc->ca_file_pem); Curl_safefree(sslc->clientcert); Curl_safefree(sslc->random_file); Curl_safefree(sslc->egdsocket);