Browse Source

SSL: support in-memory CA certs for some backends

- New options CURLOPT_CAINFO_BLOB and CURLOPT_PROXY_CAINFO_BLOB to
  specify in-memory PEM certificates for OpenSSL, Schannel (Windows)
  and Secure Transport (Apple) SSL backends.

Prior to this change PEM certificates could only be imported from a file
and not from memory.

Co-authored-by: moparisthebest@users.noreply.github.com

Ref: https://github.com/curl/curl/pull/4679
Ref: https://github.com/curl/curl/pull/5677
Ref: https://github.com/curl/curl/pull/6109

Closes https://github.com/curl/curl/pull/6662
Gilles Vollant 3 years ago
parent
commit
77fc3859b2

+ 4 - 0
docs/libcurl/curl_easy_setopt.3

@@ -584,8 +584,12 @@ Verify the DOH (DNS-over-HTTPS) SSL certificate's status. See
 \fICURLOPT_DOH_SSL_VERIFYSTATUS(3)\fP
 .IP CURLOPT_CAINFO
 CA cert bundle. See \fICURLOPT_CAINFO(3)\fP
+.IP CURLOPT_CAINFO_BLOB
+CA cert bundle memory buffer. See \fICURLOPT_CAINFO_BLOB(3)\fP
 .IP CURLOPT_PROXY_CAINFO
 Proxy CA cert bundle. See \fICURLOPT_PROXY_CAINFO(3)\fP
+.IP CURLOPT_PROXY_CAINFO_BLOB
+Proxy CA cert bundle memory buffer. See \fICURLOPT_PROXY_CAINFO_BLOB(3)\fP
 .IP CURLOPT_ISSUERCERT
 Issuer certificate. See \fICURLOPT_ISSUERCERT(3)\fP
 .IP CURLOPT_ISSUERCERT_BLOB

+ 1 - 1
docs/libcurl/opts/CURLOPT_CAINFO.3

@@ -79,5 +79,5 @@ option is ignored. Schannel support added in libcurl 7.60.
 Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
 CURLE_OUT_OF_MEMORY if there was insufficient heap space.
 .SH "SEE ALSO"
-.BR CURLOPT_CAPATH "(3), "
+.BR CURLOPT_CAINFO_BLOB "(3), " CURLOPT_CAPATH "(3), "
 .BR CURLOPT_SSL_VERIFYPEER "(3), " CURLOPT_SSL_VERIFYHOST "(3), "

+ 68 - 0
docs/libcurl/opts/CURLOPT_CAINFO_BLOB.3

@@ -0,0 +1,68 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2021, 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.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_CAINFO_BLOB 3 "31 March 2021" "libcurl 7.77.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_CAINFO_BLOB \- Certificate Authority (CA) bundle in PEM format
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_CAINFO_BLOB, struct curl_blob *stblob);
+.SH DESCRIPTION
+Pass a pointer to a curl_blob structure, which contains information (pointer
+and size) about a memory block with binary data of PEM encoded content holding
+one or more certificates to verify the HTTPS server with.
+
+If \fICURLOPT_SSL_VERIFYPEER(3)\fP is zero and you avoid verifying the
+server's certificate, \fICURLOPT_CAINFO_BLOB(3)\fP is not needed.
+
+This option overrides \fICURLOPT_CAINFO(3)\fP.
+.SH DEFAULT
+NULL
+.SH PROTOCOLS
+All TLS based protocols: HTTPS, FTPS, IMAPS, POP3S, SMTPS etc.
+.SH EXAMPLE
+.nf
+char *strpem; /* strpem must point to a PEM string */
+CURL *curl = curl_easy_init();
+if(curl) {
+  struct curl_blob blob;
+  curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
+  blob.data = strpem;
+  blob.len = strlen(strpem);
+  blob.flags = CURL_BLOB_COPY;
+  curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob);
+  ret = curl_easy_perform(curl);
+  curl_easy_cleanup(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.77.0.
+
+This option is supported by the OpenSSL, Secure
+Transport and Schannel backends.
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
+CURLE_OUT_OF_MEMORY if there was insufficient heap space.
+.SH "SEE ALSO"
+.BR CURLOPT_CAINFO "(3), " CURLOPT_CAPATH "(3), "
+.BR CURLOPT_SSL_VERIFYPEER "(3), " CURLOPT_SSL_VERIFYHOST "(3), "

+ 2 - 1
docs/libcurl/opts/CURLOPT_PROXY_CAINFO.3

@@ -77,7 +77,8 @@ https://curl.se/docs/ssl-compared.html
 Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
 CURLE_OUT_OF_MEMORY if there was insufficient heap space.
 .SH "SEE ALSO"
-.BR CURLOPT_PROXY_CAPATH "(3), "
+.BR CURLOPT_PROXY_CAINFO_BLOB "(3), " CURLOPT_PROXY_CAPATH "(3), "
 .BR CURLOPT_PROXY_SSL_VERIFYPEER "(3), " CURLOPT_PROXY_SSL_VERIFYHOST "(3), "
+.BR CURLOPT_CAINFO "(3), " CURLOPT_CAINFO_BLOB "(3), "
 .BR CURLOPT_CAPATH "(3), "
 .BR CURLOPT_SSL_VERIFYPEER "(3), " CURLOPT_SSL_VERIFYHOST "(3), "

+ 75 - 0
docs/libcurl/opts/CURLOPT_PROXY_CAINFO_BLOB.3

@@ -0,0 +1,75 @@
+.\" **************************************************************************
+.\" *                                  _   _ ____  _
+.\" *  Project                     ___| | | |  _ \| |
+.\" *                             / __| | | | |_) | |
+.\" *                            | (__| |_| |  _ <| |___
+.\" *                             \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2021, 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.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_PROXY_CAINFO_BLOB 3 "31 March 2021" "libcurl 7.77.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_PROXY_CAINFO_BLOB \- proxy Certificate Authority (CA) bundle in PEM format
+.SH SYNOPSIS
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_PROXY_CAINFO_BLOB, struct curl_blob *stblob);
+.SH DESCRIPTION
+This option is for connecting to an HTTPS proxy, not an HTTPS server.
+
+Pass a pointer to a curl_blob structure, which contains information (pointer
+and size) about a memory block with binary data of PEM encoded content holding
+one or more certificates to verify the HTTPS proxy with.
+
+If \fICURLOPT_PROXY_SSL_VERIFYPEER(3)\fP is zero and you avoid verifying the
+server's certificate, \fICURLOPT_PROXY_CAINFO_BLOB(3)\fP is not needed.
+
+This option overrides \fICURLOPT_PROXY_CAINFO(3)\fP.
+.SH DEFAULT
+NULL
+.SH PROTOCOLS
+Used with HTTPS proxy
+.SH EXAMPLE
+.nf
+char *strpem; /* strpem must point to a PEM string */
+CURL *curl = curl_easy_init();
+if(curl) {
+  struct curl_blob blob;
+  curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/");
+  /* using an HTTPS proxy */
+  curl_easy_setopt(curl, CURLOPT_PROXY, "https://localhost:443");
+  blob.data = strpem;
+  blob.len = strlen(strpem);
+  blob.flags = CURL_BLOB_COPY;  
+  curl_easy_setopt(curl, CURLOPT_PROXY_CAINFO_BLOB, &blob);
+  ret = curl_easy_perform(curl);
+  curl_easy_cleanup(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.77.0.
+
+This option is supported by the OpenSSL, Secure
+Transport and Schannel backends.
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, CURLE_UNKNOWN_OPTION if not, or
+CURLE_OUT_OF_MEMORY if there was insufficient heap space.
+.SH "SEE ALSO"
+.BR CURLOPT_PROXY_CAINFO "(3), " CURLOPT_PROXY_CAPATH "(3), "
+.BR CURLOPT_PROXY_SSL_VERIFYPEER "(3), " CURLOPT_PROXY_SSL_VERIFYHOST "(3), "
+.BR CURLOPT_CAINFO "(3), " CURLOPT_CAINFO_BLOB "(3), "
+.BR CURLOPT_CAPATH "(3), "
+.BR CURLOPT_SSL_VERIFYPEER "(3), " CURLOPT_SSL_VERIFYHOST "(3), "

+ 2 - 0
docs/libcurl/opts/Makefile.inc

@@ -115,6 +115,7 @@ man_MANS =                                      \
   CURLOPT_AUTOREFERER.3                         \
   CURLOPT_BUFFERSIZE.3                          \
   CURLOPT_CAINFO.3                              \
+  CURLOPT_CAINFO_BLOB.3                         \
   CURLOPT_CAPATH.3                              \
   CURLOPT_CERTINFO.3                            \
   CURLOPT_CHUNK_BGN_FUNCTION.3                  \
@@ -267,6 +268,7 @@ man_MANS =                                      \
   CURLOPT_PROXYUSERNAME.3                       \
   CURLOPT_PROXYUSERPWD.3                        \
   CURLOPT_PROXY_CAINFO.3                        \
+  CURLOPT_PROXY_CAINFO_BLOB.3                   \
   CURLOPT_PROXY_CAPATH.3                        \
   CURLOPT_PROXY_CRLFILE.3                       \
   CURLOPT_PROXY_KEYPASSWD.3                     \

+ 2 - 0
docs/libcurl/symbols-in-versions

@@ -374,6 +374,7 @@ CURLOPT_APPEND                  7.17.0
 CURLOPT_AUTOREFERER             7.1
 CURLOPT_BUFFERSIZE              7.10
 CURLOPT_CAINFO                  7.4.2
+CURLOPT_CAINFO_BLOB             7.77.0
 CURLOPT_CAPATH                  7.9.8
 CURLOPT_CERTINFO                7.19.1
 CURLOPT_CHUNK_BGN_FUNCTION      7.21.0
@@ -543,6 +544,7 @@ CURLOPT_PROXYTYPE               7.10
 CURLOPT_PROXYUSERNAME           7.19.1
 CURLOPT_PROXYUSERPWD            7.1
 CURLOPT_PROXY_CAINFO            7.52.0
+CURLOPT_PROXY_CAINFO_BLOB       7.77.0
 CURLOPT_PROXY_CAPATH            7.52.0
 CURLOPT_PROXY_CRLFILE           7.52.0
 CURLOPT_PROXY_ISSUERCERT        7.71.0

+ 8 - 0
include/curl/curl.h

@@ -2093,6 +2093,14 @@ typedef enum {
   /* Same as CURLOPT_SSL_VERIFYSTATUS but for DOH (DNS-over-HTTPS) servers. */
   CURLOPT(CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOPTTYPE_LONG, 308),
 
+  /* The CA certificates as "blob" used to validate the peer certificate
+     this option is used only if SSL_VERIFYPEER is true */
+  CURLOPT(CURLOPT_CAINFO_BLOB, CURLOPTTYPE_BLOB, 309),
+
+  /* The CA certificates as "blob" used to validate the proxy certificate
+     this option is used only if PROXY_SSL_VERIFYPEER is true */
+  CURLOPT(CURLOPT_PROXY_CAINFO_BLOB, CURLOPTTYPE_BLOB, 310),
+
   CURLOPT_LASTENTRY /* the last unused */
 } CURLoption;
 

+ 4 - 0
lib/doh.c

@@ -313,6 +313,10 @@ static CURLcode dohprobe(struct Curl_easy *data,
       ERROR_CHECK_SETOPT(CURLOPT_CAINFO,
                          data->set.str[STRING_SSL_CAFILE]);
     }
+    if(data->set.blobs[BLOB_CAINFO]) {
+      ERROR_CHECK_SETOPT(CURLOPT_CAINFO_BLOB,
+                         data->set.blobs[BLOB_CAINFO]);
+    }
     if(data->set.str[STRING_SSL_CAPATH]) {
       ERROR_CHECK_SETOPT(CURLOPT_CAPATH,
                          data->set.str[STRING_SSL_CAPATH]);

+ 3 - 1
lib/easyoptions.c

@@ -38,6 +38,7 @@ struct curl_easyoption Curl_easyopts[] = {
   {"AWS_SIGV4", CURLOPT_AWS_SIGV4, CURLOT_STRING, 0},
   {"BUFFERSIZE", CURLOPT_BUFFERSIZE, CURLOT_LONG, 0},
   {"CAINFO", CURLOPT_CAINFO, CURLOT_STRING, 0},
+  {"CAINFO_BLOB", CURLOPT_CAINFO_BLOB, CURLOT_BLOB, 0},
   {"CAPATH", CURLOPT_CAPATH, CURLOT_STRING, 0},
   {"CERTINFO", CURLOPT_CERTINFO, CURLOT_LONG, 0},
   {"CHUNK_BGN_FUNCTION", CURLOPT_CHUNK_BGN_FUNCTION, CURLOT_FUNCTION, 0},
@@ -205,6 +206,7 @@ struct curl_easyoption Curl_easyopts[] = {
   {"PROXYUSERNAME", CURLOPT_PROXYUSERNAME, CURLOT_STRING, 0},
   {"PROXYUSERPWD", CURLOPT_PROXYUSERPWD, CURLOT_STRING, 0},
   {"PROXY_CAINFO", CURLOPT_PROXY_CAINFO, CURLOT_STRING, 0},
+  {"PROXY_CAINFO_BLOB", CURLOPT_PROXY_CAINFO_BLOB, CURLOT_BLOB, 0},
   {"PROXY_CAPATH", CURLOPT_PROXY_CAPATH, CURLOT_STRING, 0},
   {"PROXY_CRLFILE", CURLOPT_PROXY_CRLFILE, CURLOT_STRING, 0},
   {"PROXY_ISSUERCERT", CURLOPT_PROXY_ISSUERCERT, CURLOT_STRING, 0},
@@ -352,6 +354,6 @@ struct curl_easyoption Curl_easyopts[] = {
  */
 int Curl_easyopts_check(void)
 {
-  return ((CURLOPT_LASTENTRY%10000) != (308 + 1));
+  return ((CURLOPT_LASTENTRY%10000) != (310 + 1));
 }
 #endif

+ 27 - 0
lib/setopt.c

@@ -2041,6 +2041,20 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE],
                             va_arg(param, char *));
     break;
+  case CURLOPT_CAINFO_BLOB:
+    /*
+     * Blob that holds CA info for SSL connection.
+     * Specify entire PEM of the CA certificate
+     */
+#ifdef USE_SSL
+    if(Curl_ssl->supports & SSLSUPP_CAINFO_BLOB)
+      result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO],
+                               va_arg(param, struct curl_blob *));
+    else
+#endif
+      return CURLE_NOT_BUILT_IN;
+
+    break;
 #ifndef CURL_DISABLE_PROXY
   case CURLOPT_PROXY_CAINFO:
     /*
@@ -2050,6 +2064,19 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     result = Curl_setstropt(&data->set.str[STRING_SSL_CAFILE_PROXY],
                             va_arg(param, char *));
     break;
+  case CURLOPT_PROXY_CAINFO_BLOB:
+    /*
+     * Blob that holds CA info for SSL connection proxy.
+     * Specify entire PEM of the CA certificate
+     */
+#ifdef USE_SSL
+    if(Curl_ssl->supports & SSLSUPP_CAINFO_BLOB)
+      result = Curl_setblobopt(&data->set.blobs[BLOB_CAINFO_PROXY],
+                               va_arg(param, struct curl_blob *));
+    else
+#endif
+      return CURLE_NOT_BUILT_IN;
+    break;
 #endif
   case CURLOPT_CAPATH:
     /*

+ 3 - 0
lib/url.c

@@ -3738,6 +3738,7 @@ static CURLcode create_conn(struct Curl_easy *data,
   data->set.ssl.primary.pinned_key =
     data->set.str[STRING_SSL_PINNEDPUBLICKEY];
   data->set.ssl.primary.cert_blob = data->set.blobs[BLOB_CERT];
+  data->set.ssl.primary.ca_info_blob = data->set.blobs[BLOB_CAINFO];
   data->set.ssl.primary.curves = data->set.str[STRING_SSL_EC_CURVES];
 
 #ifndef CURL_DISABLE_PROXY
@@ -3753,6 +3754,8 @@ static CURLcode create_conn(struct Curl_easy *data,
   data->set.proxy_ssl.primary.pinned_key =
     data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY];
   data->set.proxy_ssl.primary.cert_blob = data->set.blobs[BLOB_CERT_PROXY];
+  data->set.proxy_ssl.primary.ca_info_blob =
+    data->set.blobs[BLOB_CAINFO_PROXY];
   data->set.proxy_ssl.CRLfile = data->set.str[STRING_SSL_CRLFILE_PROXY];
   data->set.proxy_ssl.issuercert = data->set.str[STRING_SSL_ISSUERCERT_PROXY];
   data->set.proxy_ssl.cert_type = data->set.str[STRING_CERT_TYPE_PROXY];

+ 3 - 0
lib/urldata.h

@@ -253,6 +253,7 @@ struct ssl_primary_config {
   char *cipher_list13;   /* list of TLS 1.3 cipher suites to use */
   char *pinned_key;
   struct curl_blob *cert_blob;
+  struct curl_blob *ca_info_blob;
   char *curves;          /* list of curves to use */
   BIT(verifypeer);       /* set TRUE if this is desired */
   BIT(verifyhost);       /* set TRUE if CN/SAN must match hostname */
@@ -1604,6 +1605,8 @@ enum dupblob {
   BLOB_KEY_PROXY,
   BLOB_SSL_ISSUERCERT,
   BLOB_SSL_ISSUERCERT_PROXY,
+  BLOB_CAINFO,
+  BLOB_CAINFO_PROXY,
   BLOB_LAST
 };
 

+ 81 - 2
lib/vtls/openssl.c

@@ -2510,6 +2510,67 @@ static int ossl_new_session_cb(SSL *ssl, SSL_SESSION *ssl_sessionid)
   return res;
 }
 
+static CURLcode load_cacert_from_memory(SSL_CTX *ctx,
+                                        const struct curl_blob *ca_info_blob)
+{
+  /* 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;
+
+  if(ca_info_blob->len > (size_t)INT_MAX)
+    return CURLE_SSL_CACERT_BADFILE;
+
+  cts = SSL_CTX_get_cert_store(ctx);
+  if(!cts)
+    return CURLE_OUT_OF_MEMORY;
+
+  cbio = BIO_new_mem_buf(ca_info_blob->data, (int)ca_info_blob->len);
+  if(!cbio)
+    return CURLE_OUT_OF_MEMORY;
+
+  inf = PEM_X509_INFO_read_bio(cbio, NULL, NULL, NULL);
+  if(!inf) {
+    BIO_free(cbio);
+    return CURLE_SSL_CACERT_BADFILE;
+  }
+
+  /* add each entry from PEM file to x509_store */
+  for(i = 0; i < (int)sk_X509_INFO_num(inf); ++i) {
+    itmp = sk_X509_INFO_value(inf, i);
+    if(itmp->x509) {
+      if(X509_STORE_add_cert(cts, itmp->x509)) {
+        ++count;
+      }
+      else {
+        /* set count to 0 to return an error */
+        count = 0;
+        break;
+      }
+    }
+    if(itmp->crl) {
+      if(X509_STORE_add_crl(cts, itmp->crl)) {
+        ++count;
+      }
+      else {
+        /* set count to 0 to return an error */
+        count = 0;
+        break;
+      }
+    }
+  }
+
+  sk_X509_INFO_pop_free(inf, X509_INFO_free);
+  BIO_free(cbio);
+
+  /* if we didn't end up importing anything, treat that as an error */
+  return (count > 0 ? CURLE_OK : CURLE_SSL_CACERT_BADFILE);
+}
+
 static CURLcode ossl_connect_step1(struct Curl_easy *data,
                                    struct connectdata *conn, int sockindex)
 {
@@ -2537,8 +2598,11 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
 #endif
   char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
   const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
+  const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
   const char * const ssl_cert_type = SSL_SET_OPTION(cert_type);
-  const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
+  const char * const ssl_cafile =
+    /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+    (ca_info_blob ? NULL : SSL_CONN_CONFIG(CAfile));
   const char * const ssl_capath = SSL_CONN_CONFIG(CApath);
   const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
   const char * const ssl_crlfile = SSL_SET_OPTION(CRLfile);
@@ -2962,6 +3026,19 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
   }
 #endif
 
+  if(ca_info_blob) {
+    result = load_cacert_from_memory(backend->ctx, ca_info_blob);
+    if(result) {
+      if(result == CURLE_OUT_OF_MEMORY ||
+         (verifypeer && !imported_native_ca)) {
+        failf(data, "error importing CA certificate blob");
+        return result;
+      }
+      /* Only warning if no certificate verification is required. */
+      infof(data, "error importing CA certificate blob, continuing anyway\n");
+    }
+  }
+
 #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
   /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
   {
@@ -3018,7 +3095,8 @@ static CURLcode ossl_connect_step1(struct Curl_easy *data,
 #endif
 
 #ifdef CURL_CA_FALLBACK
-  if(verifypeer && !ssl_cafile && !ssl_capath && !imported_native_ca) {
+  if(verifypeer &&
+     !ca_info_blob && !ssl_cafile && !ssl_capath && !imported_native_ca) {
     /* verifying the peer without any CA certificates won't
        work so use openssl's built in default as fallback */
     SSL_CTX_set_default_verify_paths(backend->ctx);
@@ -4425,6 +4503,7 @@ const struct Curl_ssl Curl_ssl_openssl = {
   { CURLSSLBACKEND_OPENSSL, "openssl" }, /* info */
 
   SSLSUPP_CA_PATH |
+  SSLSUPP_CAINFO_BLOB |
   SSLSUPP_CERTINFO |
   SSLSUPP_PINNEDPUBKEY |
   SSLSUPP_SSL_CTX |

+ 5 - 2
lib/vtls/schannel.c

@@ -473,7 +473,7 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
 #endif
 #else
 #ifdef HAS_MANUAL_VERIFY_API
-  if(SSL_CONN_CONFIG(CAfile)) {
+  if(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) {
     if(curlx_verify_windows_version(6, 1, PLATFORM_WINNT,
                                     VERSION_GREATER_THAN_EQUAL)) {
       BACKEND->use_manual_cred_validation = true;
@@ -487,7 +487,7 @@ schannel_connect_step1(struct Curl_easy *data, struct connectdata *conn,
   else
     BACKEND->use_manual_cred_validation = false;
 #else
-  if(SSL_CONN_CONFIG(CAfile)) {
+  if(SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) {
     failf(data, "schannel: CA cert support not built in");
     return CURLE_NOT_BUILT_IN;
   }
@@ -2403,6 +2403,9 @@ const struct Curl_ssl Curl_ssl_schannel = {
   { CURLSSLBACKEND_SCHANNEL, "schannel" }, /* info */
 
   SSLSUPP_CERTINFO |
+#ifdef HAS_MANUAL_VERIFY_API
+  SSLSUPP_CAINFO_BLOB |
+#endif
   SSLSUPP_PINNEDPUBKEY,
 
   sizeof(struct ssl_backend_data),

+ 161 - 110
lib/vtls/schannel_verify.c

@@ -77,21 +77,156 @@ static int is_cr_or_lf(char c)
   return c == '\r' || c == '\n';
 }
 
-static CURLcode add_certs_to_store(HCERTSTORE trust_store,
-                                   const char *ca_file,
-                                   struct Curl_easy *data)
+/* Search the substring needle,needlelen into string haystack,haystacklen
+ * Strings don't need to be terminated by a '\0'.
+ * Similar of OSX/Linux memmem (not available on Visual Studio).
+ * Return position of beginning of first occurence or NULL if not found
+ */
+static const char *c_memmem(const void *haystack, size_t haystacklen,
+                            const void *needle, size_t needlelen)
+{
+  const char *p;
+  char first;
+  const char *str_limit = (const char *)haystack + haystacklen;
+  if(!needlelen || needlelen > haystacklen)
+    return NULL;
+  first = *(const char *)needle;
+  for(p = (const char *)haystack; p <= (str_limit - needlelen); p++)
+    if(((*p) == first) && (memcmp(p, needle, needlelen) == 0))
+      return p;
+
+  return NULL;
+}
+
+static CURLcode add_certs_data_to_store(HCERTSTORE trust_store,
+                                        const char *ca_buffer,
+                                        size_t ca_buffer_size,
+                                        const char *ca_file_text,
+                                        struct Curl_easy *data)
+{
+  const size_t begin_cert_len = strlen(BEGIN_CERT);
+  const size_t end_cert_len = strlen(END_CERT);
+  CURLcode result = CURLE_OK;
+  int num_certs = 0;
+  bool more_certs = 1;
+  const char *current_ca_file_ptr = ca_buffer;
+  const char *ca_buffer_limit = ca_buffer + ca_buffer_size;
+
+  while(more_certs && (current_ca_file_ptr<ca_buffer_limit)) {
+    const char *begin_cert_ptr = c_memmem(current_ca_file_ptr,
+                                          ca_buffer_limit-current_ca_file_ptr,
+                                          BEGIN_CERT,
+                                          begin_cert_len);
+    if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[begin_cert_len])) {
+      more_certs = 0;
+    }
+    else {
+      const char *end_cert_ptr = c_memmem(begin_cert_ptr,
+                                          ca_buffer_limit-begin_cert_ptr,
+                                          END_CERT,
+                                          end_cert_len);
+      if(!end_cert_ptr) {
+        failf(data,
+              "schannel: CA file '%s' is not correctly formatted",
+              ca_file_text);
+        result = CURLE_SSL_CACERT_BADFILE;
+        more_certs = 0;
+      }
+      else {
+        CERT_BLOB cert_blob;
+        CERT_CONTEXT *cert_context = NULL;
+        BOOL add_cert_result = FALSE;
+        DWORD actual_content_type = 0;
+        DWORD cert_size = (DWORD)
+          ((end_cert_ptr + end_cert_len) - begin_cert_ptr);
+
+        cert_blob.pbData = (BYTE *)begin_cert_ptr;
+        cert_blob.cbData = cert_size;
+        if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
+                             &cert_blob,
+                             CERT_QUERY_CONTENT_FLAG_CERT,
+                             CERT_QUERY_FORMAT_FLAG_ALL,
+                             0,
+                             NULL,
+                             &actual_content_type,
+                             NULL,
+                             NULL,
+                             NULL,
+                             (const void **)&cert_context)) {
+          char buffer[STRERROR_LEN];
+          failf(data,
+                "schannel: failed to extract certificate from CA file "
+                "'%s': %s",
+                ca_file_text,
+                Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
+          result = CURLE_SSL_CACERT_BADFILE;
+          more_certs = 0;
+        }
+        else {
+          current_ca_file_ptr = begin_cert_ptr + cert_size;
+
+          /* Sanity check that the cert_context object is the right type */
+          if(CERT_QUERY_CONTENT_CERT != actual_content_type) {
+            failf(data,
+                  "schannel: unexpected content type '%d' when extracting "
+                  "certificate from CA file '%s'",
+                  actual_content_type, ca_file_text);
+            result = CURLE_SSL_CACERT_BADFILE;
+            more_certs = 0;
+          }
+          else {
+            add_cert_result =
+              CertAddCertificateContextToStore(trust_store,
+                                               cert_context,
+                                               CERT_STORE_ADD_ALWAYS,
+                                               NULL);
+            CertFreeCertificateContext(cert_context);
+            if(!add_cert_result) {
+              char buffer[STRERROR_LEN];
+              failf(data,
+                    "schannel: failed to add certificate from CA file '%s' "
+                    "to certificate store: %s",
+                    ca_file_text,
+                    Curl_winapi_strerror(GetLastError(), buffer,
+                                         sizeof(buffer)));
+              result = CURLE_SSL_CACERT_BADFILE;
+              more_certs = 0;
+            }
+            else {
+              num_certs++;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  if(result == CURLE_OK) {
+    if(!num_certs) {
+      infof(data,
+            "schannel: did not add any certificates from CA file '%s'\n",
+            ca_file_text);
+    }
+    else {
+      infof(data,
+            "schannel: added %d certificate(s) from CA file '%s'\n",
+            num_certs, ca_file_text);
+    }
+  }
+  return result;
+}
+
+static CURLcode add_certs_file_to_store(HCERTSTORE trust_store,
+                                        const char *ca_file,
+                                        struct Curl_easy *data)
 {
   CURLcode result;
   HANDLE ca_file_handle = INVALID_HANDLE_VALUE;
   LARGE_INTEGER file_size;
   char *ca_file_buffer = NULL;
-  char *current_ca_file_ptr = NULL;
   TCHAR *ca_file_tstr = NULL;
   size_t ca_file_bufsize = 0;
   DWORD total_bytes_read = 0;
-  bool more_certs = 0;
-  int num_certs = 0;
-  size_t END_CERT_LEN;
 
   ca_file_tstr = curlx_convert_UTF8_to_tchar((char *)ca_file);
   if(!ca_file_tstr) {
@@ -181,106 +316,10 @@ static CURLcode add_certs_to_store(HCERTSTORE trust_store,
   if(result != CURLE_OK) {
     goto cleanup;
   }
-
-  END_CERT_LEN = strlen(END_CERT);
-
-  more_certs = 1;
-  current_ca_file_ptr = ca_file_buffer;
-  while(more_certs && *current_ca_file_ptr != '\0') {
-    char *begin_cert_ptr = strstr(current_ca_file_ptr, BEGIN_CERT);
-    if(!begin_cert_ptr || !is_cr_or_lf(begin_cert_ptr[strlen(BEGIN_CERT)])) {
-      more_certs = 0;
-    }
-    else {
-      char *end_cert_ptr = strstr(begin_cert_ptr, END_CERT);
-      if(!end_cert_ptr) {
-        failf(data,
-              "schannel: CA file '%s' is not correctly formatted",
-              ca_file);
-        result = CURLE_SSL_CACERT_BADFILE;
-        more_certs = 0;
-      }
-      else {
-        CERT_BLOB cert_blob;
-        CERT_CONTEXT *cert_context = NULL;
-        BOOL add_cert_result = FALSE;
-        DWORD actual_content_type = 0;
-        DWORD cert_size = (DWORD)
-          ((end_cert_ptr + END_CERT_LEN) - begin_cert_ptr);
-
-        cert_blob.pbData = (BYTE *)begin_cert_ptr;
-        cert_blob.cbData = cert_size;
-        if(!CryptQueryObject(CERT_QUERY_OBJECT_BLOB,
-                             &cert_blob,
-                             CERT_QUERY_CONTENT_FLAG_CERT,
-                             CERT_QUERY_FORMAT_FLAG_ALL,
-                             0,
-                             NULL,
-                             &actual_content_type,
-                             NULL,
-                             NULL,
-                             NULL,
-                             (const void **)&cert_context)) {
-          char buffer[STRERROR_LEN];
-          failf(data,
-                "schannel: failed to extract certificate from CA file "
-                "'%s': %s",
-                ca_file,
-                Curl_winapi_strerror(GetLastError(), buffer, sizeof(buffer)));
-          result = CURLE_SSL_CACERT_BADFILE;
-          more_certs = 0;
-        }
-        else {
-          current_ca_file_ptr = begin_cert_ptr + cert_size;
-
-          /* Sanity check that the cert_context object is the right type */
-          if(CERT_QUERY_CONTENT_CERT != actual_content_type) {
-            failf(data,
-                  "schannel: unexpected content type '%d' when extracting "
-                  "certificate from CA file '%s'",
-                  actual_content_type, ca_file);
-            result = CURLE_SSL_CACERT_BADFILE;
-            more_certs = 0;
-          }
-          else {
-            add_cert_result =
-              CertAddCertificateContextToStore(trust_store,
-                                               cert_context,
-                                               CERT_STORE_ADD_ALWAYS,
-                                               NULL);
-            CertFreeCertificateContext(cert_context);
-            if(!add_cert_result) {
-              char buffer[STRERROR_LEN];
-              failf(data,
-                    "schannel: failed to add certificate from CA file '%s' "
-                    "to certificate store: %s",
-                    ca_file,
-                    Curl_winapi_strerror(GetLastError(), buffer,
-                                         sizeof(buffer)));
-              result = CURLE_SSL_CACERT_BADFILE;
-              more_certs = 0;
-            }
-            else {
-              num_certs++;
-            }
-          }
-        }
-      }
-    }
-  }
-
-  if(result == CURLE_OK) {
-    if(!num_certs) {
-      infof(data,
-            "schannel: did not add any certificates from CA file '%s'\n",
-            ca_file);
-    }
-    else {
-      infof(data,
-            "schannel: added %d certificate(s) from CA file '%s'\n",
-            num_certs, ca_file);
-    }
-  }
+  result = add_certs_data_to_store(trust_store,
+                                   ca_file_buffer, ca_file_bufsize,
+                                   ca_file,
+                                   data);
 
 cleanup:
   if(ca_file_handle != INVALID_HANDLE_VALUE) {
@@ -550,7 +589,8 @@ CURLcode Curl_verify_certificate(struct Curl_easy *data,
     result = CURLE_PEER_FAILED_VERIFICATION;
   }
 
-  if(result == CURLE_OK && SSL_CONN_CONFIG(CAfile) &&
+  if(result == CURLE_OK &&
+      (SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
       BACKEND->use_manual_cred_validation) {
     /*
      * Create a chain engine that uses the certificates in the CA file as
@@ -576,8 +616,19 @@ CURLcode Curl_verify_certificate(struct Curl_easy *data,
         result = CURLE_SSL_CACERT_BADFILE;
       }
       else {
-        result = add_certs_to_store(trust_store, SSL_CONN_CONFIG(CAfile),
-                                    data);
+        const struct curl_blob *ca_info_blob = SSL_CONN_CONFIG(ca_info_blob);
+        if(ca_info_blob) {
+          result = add_certs_data_to_store(trust_store,
+                                           (const char *)ca_info_blob->data,
+                                           ca_info_blob->len,
+                                           "(memory blob)",
+                                           data);
+        }
+        else {
+          result = add_certs_file_to_store(trust_store,
+                                           SSL_CONN_CONFIG(CAfile),
+                                           data);
+        }
       }
     }
 

+ 55 - 23
lib/vtls/sectransp.c

@@ -1659,8 +1659,10 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
   curl_socket_t sockfd = conn->sock[sockindex];
   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
   struct ssl_backend_data *backend = connssl->backend;
-  const char * const ssl_cafile = SSL_CONN_CONFIG(CAfile);
-  const struct curl_blob *ssl_cablob = NULL;
+  const struct curl_blob *ssl_cablob = SSL_CONN_CONFIG(ca_info_blob);
+  const char * const ssl_cafile =
+    /* CURLOPT_CAINFO_BLOB overrides CURLOPT_CAINFO */
+    (ssl_cablob ? NULL : SSL_CONN_CONFIG(CAfile));
   const bool verifypeer = SSL_CONN_CONFIG(verifypeer);
   char * const ssl_cert = SSL_SET_OPTION(primary.clientcert);
   const struct curl_blob *ssl_cert_blob = SSL_SET_OPTION(primary.cert_blob);
@@ -2007,7 +2009,8 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
     bool is_cert_file = (!is_cert_data) && is_file(ssl_cafile);
 
     if(!(is_cert_file || is_cert_data)) {
-      failf(data, "SSL: can't load CA certificate file %s", ssl_cafile);
+      failf(data, "SSL: can't load CA certificate file %s",
+            ssl_cafile ? ssl_cafile : "(blob memory)");
       return CURLE_SSL_CACERT_BADFILE;
     }
   }
@@ -2084,7 +2087,8 @@ static CURLcode sectransp_connect_step1(struct Curl_easy *data,
     else {
       CURLcode result;
       ssl_sessionid =
-        aprintf("%s:%d:%d:%s:%ld", ssl_cafile,
+        aprintf("%s:%d:%d:%s:%ld",
+                ssl_cafile ? ssl_cafile : "(blob memory)",
                 verifypeer, SSL_CONN_CONFIG(verifyhost), hostname, port);
       ssl_sessionid_len = strlen(ssl_sessionid);
 
@@ -2224,7 +2228,7 @@ static int read_cert(const char *file, unsigned char **out, size_t *outlen)
 }
 
 static int append_cert_to_array(struct Curl_easy *data,
-                                unsigned char *buf, size_t buflen,
+                                const unsigned char *buf, size_t buflen,
                                 CFMutableArrayRef array)
 {
     CFDataRef certdata = CFDataCreate(kCFAllocatorDefault, buf, buflen);
@@ -2262,18 +2266,14 @@ static int append_cert_to_array(struct Curl_easy *data,
     return CURLE_OK;
 }
 
-static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
-                            SSLContextRef ctx)
+static CURLcode verify_cert_buf(struct Curl_easy *data,
+                                const unsigned char *certbuf, size_t buflen,
+                                SSLContextRef ctx)
 {
   int n = 0, rc;
   long res;
-  unsigned char *certbuf, *der;
-  size_t buflen, derlen, offset = 0;
-
-  if(read_cert(cafile, &certbuf, &buflen) < 0) {
-    failf(data, "SSL: failed to read or invalid CA certificate");
-    return CURLE_SSL_CACERT_BADFILE;
-  }
+  unsigned char *der;
+  size_t derlen, offset = 0;
 
   /*
    * Certbuf now contains the contents of the certificate file, which can be
@@ -2287,7 +2287,6 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
   CFMutableArrayRef array = CFArrayCreateMutable(kCFAllocatorDefault, 0,
                                                  &kCFTypeArrayCallBacks);
   if(!array) {
-    free(certbuf);
     failf(data, "SSL: out of memory creating CA certificate array");
     return CURLE_OUT_OF_MEMORY;
   }
@@ -2301,7 +2300,6 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
      */
     res = pem_to_der((const char *)certbuf + offset, &der, &derlen);
     if(res < 0) {
-      free(certbuf);
       CFRelease(array);
       failf(data, "SSL: invalid CA certificate #%d (offset %zu) in bundle",
             n, offset);
@@ -2312,7 +2310,6 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
     if(res == 0 && offset == 0) {
       /* This is not a PEM file, probably a certificate in DER format. */
       rc = append_cert_to_array(data, certbuf, buflen, array);
-      free(certbuf);
       if(rc != CURLE_OK) {
         CFRelease(array);
         return rc;
@@ -2321,14 +2318,12 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
     }
     else if(res == 0) {
       /* No more certificates in the bundle. */
-      free(certbuf);
       break;
     }
 
     rc = append_cert_to_array(data, der, derlen, array);
     free(der);
     if(rc != CURLE_OK) {
-      free(certbuf);
       CFRelease(array);
       return rc;
     }
@@ -2385,6 +2380,38 @@ static CURLcode verify_cert(const char *cafile, struct Curl_easy *data,
   }
 }
 
+static CURLcode verify_cert(struct Curl_easy *data, const char *cafile,
+                            const struct curl_blob *ca_info_blob,
+                            SSLContextRef ctx)
+{
+  int result;
+  unsigned char *certbuf;
+  size_t buflen;
+
+  if(ca_info_blob) {
+    certbuf = (unsigned char *)malloc(ca_info_blob->len + 1);
+    if(!certbuf) {
+      return CURLE_OUT_OF_MEMORY;
+    }
+    buflen = ca_info_blob->len;
+    memcpy(certbuf, ca_info_blob->data, ca_info_blob->len);
+    certbuf[ca_info_blob->len]='\0';
+  }
+  else if(cafile) {
+    if(read_cert(cafile, &certbuf, &buflen) < 0) {
+      failf(data, "SSL: failed to read or invalid CA certificate");
+      return CURLE_SSL_CACERT_BADFILE;
+    }
+  }
+  else
+    return CURLE_SSL_CACERT_BADFILE;
+
+  result = verify_cert_buf(data, certbuf, buflen, ctx);
+  free(certbuf);
+  return result;
+}
+
+
 #ifdef SECTRANSP_PINNEDPUBKEY
 static CURLcode pkp_pin_peer_pubkey(struct Curl_easy *data,
                                     SSLContextRef ctx,
@@ -2520,8 +2547,10 @@ sectransp_connect_step2(struct Curl_easy *data, struct connectdata *conn,
       /* The below is errSSLServerAuthCompleted; it's not defined in
         Leopard's headers */
       case -9841:
-        if(SSL_CONN_CONFIG(CAfile) && SSL_CONN_CONFIG(verifypeer)) {
-          CURLcode result = verify_cert(SSL_CONN_CONFIG(CAfile), data,
+        if((SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
+           SSL_CONN_CONFIG(verifypeer)) {
+          CURLcode result = verify_cert(data, SSL_CONN_CONFIG(CAfile),
+                                        SSL_CONN_CONFIG(ca_info_blob),
                                         backend->ssl_ctx);
           if(result)
             return result;
@@ -3365,8 +3394,10 @@ static ssize_t sectransp_recv(struct Curl_easy *data,
         /* The below is errSSLPeerAuthCompleted; it's not defined in
            Leopard's headers */
       case -9841:
-        if(SSL_CONN_CONFIG(CAfile) && SSL_CONN_CONFIG(verifypeer)) {
-          CURLcode result = verify_cert(SSL_CONN_CONFIG(CAfile), data,
+        if((SSL_CONN_CONFIG(CAfile) || SSL_CONN_CONFIG(ca_info_blob)) &&
+           SSL_CONN_CONFIG(verifypeer)) {
+          CURLcode result = verify_cert(data, SSL_CONN_CONFIG(CAfile),
+                                        SSL_CONN_CONFIG(ca_info_blob),
                                         backend->ssl_ctx);
           if(result)
             return result;
@@ -3393,6 +3424,7 @@ static void *sectransp_get_internals(struct ssl_connect_data *connssl,
 const struct Curl_ssl Curl_ssl_sectransp = {
   { CURLSSLBACKEND_SECURETRANSPORT, "secure-transport" }, /* info */
 
+  SSLSUPP_CAINFO_BLOB |
 #ifdef SECTRANSP_PINNEDPUBKEY
   SSLSUPP_PINNEDPUBKEY,
 #else

+ 3 - 0
lib/vtls/vtls.c

@@ -135,6 +135,7 @@ Curl_ssl_config_matches(struct ssl_primary_config *data,
      (data->verifyhost == needle->verifyhost) &&
      (data->verifystatus == needle->verifystatus) &&
      blobcmp(data->cert_blob, needle->cert_blob) &&
+     blobcmp(data->ca_info_blob, needle->ca_info_blob) &&
      Curl_safe_strcasecompare(data->CApath, needle->CApath) &&
      Curl_safe_strcasecompare(data->CAfile, needle->CAfile) &&
      Curl_safe_strcasecompare(data->clientcert, needle->clientcert) &&
@@ -161,6 +162,7 @@ Curl_clone_primary_ssl_config(struct ssl_primary_config *source,
   dest->sessionid = source->sessionid;
 
   CLONE_BLOB(cert_blob);
+  CLONE_BLOB(ca_info_blob);
   CLONE_STRING(CApath);
   CLONE_STRING(CAfile);
   CLONE_STRING(clientcert);
@@ -185,6 +187,7 @@ void Curl_free_primary_ssl_config(struct ssl_primary_config *sslc)
   Curl_safefree(sslc->cipher_list13);
   Curl_safefree(sslc->pinned_key);
   Curl_safefree(sslc->cert_blob);
+  Curl_safefree(sslc->ca_info_blob);
   Curl_safefree(sslc->curves);
 }
 

+ 1 - 0
lib/vtls/vtls.h

@@ -32,6 +32,7 @@ struct ssl_connect_data;
 #define SSLSUPP_SSL_CTX      (1<<3) /* supports CURLOPT_SSL_CTX */
 #define SSLSUPP_HTTPS_PROXY  (1<<4) /* supports access via HTTPS proxies */
 #define SSLSUPP_TLS13_CIPHERSUITES (1<<5) /* supports TLS 1.3 ciphersuites */
+#define SSLSUPP_CAINFO_BLOB  (1<<6)
 
 struct Curl_ssl {
   /*

+ 4 - 0
packages/OS400/curl.inc.in

@@ -1579,6 +1579,10 @@
      d                 c                   00307
      d  CURLOPT_DOH_SSL_VERIFYSTATUS...
      d                 c                   00308
+     d  CURLOPT_CAINFO_BLOB...
+     d                 c                   40309
+     d  CURLOPT_PROXY_CAINFO_BLOB...
+     d                 c                   40310
       *
       /if not defined(CURL_NO_OLDIES)
      d  CURLOPT_FILE   c                   10001

+ 1 - 1
tests/data/Makefile.inc

@@ -90,7 +90,7 @@ test635 test636 test637 test638 test639 test640 test641 test642 \
 test643 test644 test645 test646 test647 test648 test649 test650 test651 \
 test652 test653 test654 test655 test656 test658 test659 test660 test661 \
 test662 test663 test664 test665 test666 test667 test668 test669 \
-test670 test671 test672 test673 test674 test675 test676 \
+test670 test671 test672 test673 test674 test675 test676         test678 \
 \
 test700 test701 test702 test703 test704 test705 test706 test707 test708 \
 test709 test710 test711 test712 test713 test714 test715 test716 test717 \

+ 59 - 0
tests/data/test678

@@ -0,0 +1,59 @@
+<testcase>
+<info>
+<keywords>
+HTTPS
+HTTP GET
+PEM certificate
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 7
+
+MooMoo
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+SSL
+</features>
+<server>
+https Server-localhost-sv.pem
+</server>
+ <name>
+HTTPS GET using CURLOPT_CAINFO_BLOB
+ </name>
+<tool>
+lib%TESTNUMBER
+</tool>
+# provide URL and ca-cert
+<command>
+https://localhost:%HTTPSPORT/%TESTNUMBER %SRCDIR/certs/EdelCurlRoot-ca.crt
+</command>
+# Ensure that we're running on localhost because we're checking the host name
+<precheck>
+./libtest/lib%TESTNUMBER check
+</precheck>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1
+Host: localhost:%HTTPSPORT
+User-Agent: CURLOPT_CAINFO_BLOB
+Accept: */*
+
+</protocol>
+</verify>
+</testcase>

+ 5 - 1
tests/libtest/Makefile.inc

@@ -48,7 +48,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect                \
  lib599 \
  lib643 lib644 lib645 lib650 lib651 lib652 lib653 lib654 lib655 lib658   \
  lib659 lib661 lib666 lib667 lib668 \
- lib670 lib671 lib672 lib673 lib674 lib676 \
+ lib670 lib671 lib672 lib673 lib674 lib676 lib678 \
  lib1156 \
  lib1500 lib1501 lib1502 lib1503 lib1504 lib1505 lib1506 lib1507 lib1508 \
  lib1509 lib1510 lib1511 lib1512 lib1513 lib1514 lib1515         lib1517 \
@@ -409,6 +409,10 @@ lib676_SOURCES = lib676.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib676_LDADD = $(TESTUTIL_LIBS)
 lib676_CPPFLAGS = $(AM_CPPFLAGS)
 
+lib678_SOURCES = lib678.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib678_LDADD = $(TESTUTIL_LIBS)
+lib678_CPPFLAGS = $(AM_CPPFLAGS)
+
 lib1500_SOURCES = lib1500.c $(SUPPORTFILES) $(TESTUTIL)
 lib1500_LDADD = $(TESTUTIL_LIBS)
 lib1500_CPPFLAGS = $(AM_CPPFLAGS)

+ 120 - 0
tests/libtest/lib678.c

@@ -0,0 +1,120 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2021, 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.
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "testutil.h"
+#include "warnless.h"
+#include "memdebug.h"
+
+static int loadfile(const char *filename, void **filedata, size_t *filesize)
+{
+  size_t datasize = 0;
+  void *data = NULL;
+  if(filename) {
+    FILE *fInCert = fopen(filename, "rb");
+
+    if(fInCert) {
+      long cert_tell = 0;
+      bool continue_reading = fseek(fInCert, 0, SEEK_END) == 0;
+      if(continue_reading)
+        cert_tell = ftell(fInCert);
+      if(cert_tell < 0)
+        continue_reading = FALSE;
+      else
+        datasize = (size_t)cert_tell;
+      if(continue_reading)
+         continue_reading = fseek(fInCert, 0, SEEK_SET) == 0;
+      if(continue_reading)
+          data = malloc(datasize + 1);
+      if((!data) ||
+          ((int)fread(data, datasize, 1, fInCert) != 1))
+          continue_reading = FALSE;
+      fclose(fInCert);
+      if(!continue_reading) {
+        free(data);
+        datasize = 0;
+        data = NULL;
+      }
+   }
+  }
+  *filesize = datasize;
+  *filedata = data;
+  return data ? 1 : 0;
+}
+
+static int test_cert_blob(const char *url, const char *cafile)
+{
+  CURLcode code = CURLE_OUT_OF_MEMORY;
+  CURL *curl;
+  struct curl_blob blob;
+  size_t certsize;
+  void *certdata;
+
+  curl = curl_easy_init();
+  if(!curl) {
+    fprintf(stderr, "curl_easy_init() failed\n");
+    return CURLE_FAILED_INIT;
+  }
+
+  if(loadfile(cafile, &certdata, &certsize)) {
+    curl_easy_setopt(curl, CURLOPT_VERBOSE,     1L);
+    curl_easy_setopt(curl, CURLOPT_HEADER,      1L);
+    curl_easy_setopt(curl, CURLOPT_URL,         url);
+    curl_easy_setopt(curl, CURLOPT_USERAGENT,   "CURLOPT_CAINFO_BLOB");
+    curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS,
+                     CURLSSLOPT_REVOKE_BEST_EFFORT);
+
+    blob.data = certdata;
+    blob.len = certsize;
+    blob.flags = CURL_BLOB_COPY;
+    curl_easy_setopt(curl, CURLOPT_CAINFO_BLOB, &blob);
+    free(certdata);
+    code = curl_easy_perform(curl);
+  }
+  curl_easy_cleanup(curl);
+
+  return (int)code;
+}
+
+int test(char *URL)
+{
+  int res = 0;
+  curl_global_init(CURL_GLOBAL_DEFAULT);
+  if(!strcmp("check", URL)) {
+    CURL *e;
+    CURLcode w = CURLE_OK;
+    struct curl_blob blob = {0};
+    e = curl_easy_init();
+    if(e) {
+      w = curl_easy_setopt(e, CURLOPT_CAINFO_BLOB, &blob);
+      if(w)
+        printf("CURLOPT_CAINFO_BLOB is not supported\n");
+      curl_easy_cleanup(e);
+    }
+    res = (int)w;
+  }
+  else
+    res = test_cert_blob(URL, libtest_arg2);
+
+  curl_global_cleanup();
+  return res;
+}