]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
CURLOPT_SSL_OPTIONS: add *_NATIVE_CA to use Windows CA store (with openssl)
authorGilles Vollant <info@winimage.com>
Fri, 13 Sep 2019 09:24:00 +0000 (11:24 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 8 May 2020 13:55:04 +0000 (15:55 +0200)
Closes #4346

docs/libcurl/opts/CURLOPT_SSL_OPTIONS.3
docs/libcurl/symbols-in-versions
include/curl/curl.h
lib/setopt.c
lib/urldata.h
lib/vtls/openssl.c
src/tool_cfgable.h
src/tool_operate.c
src/tool_setopt.c

index 1dfe37b7e74c764e0fc33e41d60edc323816a098..3a2b88cc2803f880a80f419585f06a1c5767412b 100644 (file)
@@ -55,6 +55,10 @@ offline distribution points for those SSL backends where such behavior is
 present. This option is only supported for Schannel (the native Windows SSL
 library). If combined with \fICURLSSLOPT_NO_REVOKE\fP, the latter takes
 precedence. (Added in 7.70.0)
+.IP CURLSSLOPT_NATIVE_CA
+Tell libcurl to use the operating system's native CA store for certificate
+verifiction. Works only on Windows when built to use OpenSSL. This option
+overrides \fICURLOPT_CAINFO(3)\fP if both are set. (Added in 7.71.0)
 .SH DEFAULT
 0
 .SH PROTOCOLS
index 3b340ed8eae023b73ff730ad4e4bd82b143b3534..4264547bc56d0328b4880a6af72007f2abbc215d 100644 (file)
@@ -743,6 +743,7 @@ CURLSSLBACKEND_SCHANNEL         7.34.0
 CURLSSLBACKEND_SECURETRANSPORT  7.64.1
 CURLSSLBACKEND_WOLFSSL          7.49.0
 CURLSSLOPT_ALLOW_BEAST          7.25.0
+CURLSSLOPT_NATIVE_CA            7.71.0
 CURLSSLOPT_NO_PARTIALCHAIN      7.68.0
 CURLSSLOPT_NO_REVOKE            7.44.0
 CURLSSLSET_NO_BACKENDS          7.56.0
index 11246ea3038d3493ac67c6150448ca98fb8be6fe..a3da3da86cb64919ed2ed601d602a1ab512b19f1 100644 (file)
@@ -838,6 +838,10 @@ typedef enum {
    behavior is present. */
 #define CURLSSLOPT_REVOKE_BEST_EFFORT (1<<3)
 
+/* - CURLSSLOPT_NATIVE_CA tells libcurl to use standard certificate store of
+   operating system. Currently implemented under MS-Windows. */
+#define CURLSSLOPT_NATIVE_CA (1<<4)
+
 /* The default connection attempt delay in milliseconds for happy eyeballs.
    CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document
    this value, keep them in sync. */
index 04785a6820ed503d9f32411042fe3f6fd978de33..93ba0975a9a5deda44bcba286a10b66e517a467c 100644 (file)
@@ -2135,6 +2135,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
     data->set.ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE);
     data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN);
     data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT);
+    data->set.ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA);
     break;
 
 #ifndef CURL_DISABLE_PROXY
@@ -2144,6 +2145,7 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
       (bool)((arg&CURLSSLOPT_ALLOW_BEAST) ? TRUE : FALSE);
     data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE);
     data->set.proxy_ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN);
+    data->set.proxy_ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA);
     data->set.proxy_ssl.revoke_best_effort =
       !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT);
     break;
index 757218d83538a36975f747723a7a47f96cd269ee..e4d0464006fb6f7c4624e4fa95c369e831a6051d 100644 (file)
@@ -259,6 +259,7 @@ struct ssl_config_data {
   BIT(no_partialchain); /* don't accept partial certificate chains */
   BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation
                               list errors */
+  BIT(native_ca_store); /* use the native ca store of operating system */
 };
 
 struct ssl_general_config {
index 176fa522a5e3df267d5f4229765c003a75d7fc6e..6f6b604c26fe6a8b64cfa0b720e6f444953db9bc 100644 (file)
 #include "multiif.h"
 #include "strerror.h"
 #include "curl_printf.h"
+
+#if defined(USE_WIN32_CRYPTO)
+#include <wincrypt.h>
+#endif
+
 #include <openssl/ssl.h>
 #include <openssl/rand.h>
 #include <openssl/x509v3.h>
@@ -2720,31 +2725,184 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex)
   }
 #endif
 
+
+#if defined(USE_WIN32_CRYPTO)
+  /* Import certificates from the Windows root certificate store if requested.
+     https://stackoverflow.com/questions/9507184/
+     https://github.com/d3x0r/SACK/blob/master/src/netlib/ssl_layer.c#L1037
+     https://tools.ietf.org/html/rfc5280 */
+  if((SSL_CONN_CONFIG(verifypeer) || SSL_CONN_CONFIG(verifyhost)) &&
+     (SSL_SET_OPTION(native_ca_store))) {
+    X509_STORE *store = SSL_CTX_get_cert_store(backend->ctx);
+    HCERTSTORE hStore = CertOpenSystemStoreA((HCRYPTPROV_LEGACY)NULL, "ROOT");
+
+    if(hStore) {
+      PCCERT_CONTEXT pContext = NULL;
+      /* The array of enhanced key usage OIDs will vary per certificate and is
+         declared outside of the loop so that rather than malloc/free each
+         iteration we can grow it with realloc, when necessary. */
+      CERT_ENHKEY_USAGE *enhkey_usage = NULL;
+      DWORD enhkey_usage_size = 0;
+
+      /* This loop makes a best effort to import all valid certificates from
+         the MS root store. If a certificate cannot be imported it is skipped.
+         'result' is used to store only hard-fail conditions (such as out of
+         memory) that cause an early break. */
+      result = CURLE_OK;
+      for(;;) {
+        X509 *x509;
+        FILETIME now;
+        BYTE key_usage[2];
+        DWORD req_size;
+        const unsigned char *encoded_cert;
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+        char cert_name[256];
+#endif
+
+        pContext = CertEnumCertificatesInStore(hStore, pContext);
+        if(!pContext)
+          break;
+
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+        if(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
+                               NULL, cert_name, sizeof(cert_name))) {
+          strcpy(cert_name, "Unknown");
+        }
+        infof(data, "SSL: Checking cert \"%s\"\n", cert_name);
+#endif
+
+        encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
+        if(!encoded_cert)
+          continue;
+
+        GetSystemTimeAsFileTime(&now);
+        if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
+           CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
+          continue;
+
+        /* If key usage exists check for signing attribute */
+        if(CertGetIntendedKeyUsage(pContext->dwCertEncodingType,
+                                   pContext->pCertInfo,
+                                   key_usage, sizeof(key_usage))) {
+          if(!(key_usage[0] & CERT_KEY_CERT_SIGN_KEY_USAGE))
+            continue;
+        }
+        else if(GetLastError())
+          continue;
+
+        /* If enhanced key usage exists check for server auth attribute.
+         *
+         * Note "In a Microsoft environment, a certificate might also have EKU
+         * extended properties that specify valid uses for the certificate."
+         * The call below checks both, and behavior varies depending on what is
+         * found. For more details see CertGetEnhancedKeyUsage doc.
+         */
+        if(CertGetEnhancedKeyUsage(pContext, 0, NULL, &req_size)) {
+          if(req_size && req_size > enhkey_usage_size) {
+            void *tmp = realloc(enhkey_usage, req_size);
+
+            if(!tmp) {
+              failf(data, "SSL: Out of memory allocating for OID list");
+              result = CURLE_OUT_OF_MEMORY;
+              break;
+            }
+
+            enhkey_usage = (CERT_ENHKEY_USAGE *)tmp;
+            enhkey_usage_size = req_size;
+          }
+
+          if(CertGetEnhancedKeyUsage(pContext, 0, enhkey_usage, &req_size)) {
+            if(!enhkey_usage->cUsageIdentifier) {
+              /* "If GetLastError returns CRYPT_E_NOT_FOUND, the certificate is
+                 good for all uses. If it returns zero, the certificate has no
+                 valid uses." */
+              if(GetLastError() != CRYPT_E_NOT_FOUND)
+                continue;
+            }
+            else {
+              DWORD i;
+              bool found = false;
+
+              for(i = 0; i < enhkey_usage->cUsageIdentifier; ++i) {
+                if(!strcmp("1.3.6.1.5.5.7.3.1" /* OID server auth */,
+                           enhkey_usage->rgpszUsageIdentifier[i])) {
+                  found = true;
+                  break;
+                }
+              }
+
+              if(!found)
+                continue;
+            }
+          }
+          else
+            continue;
+        }
+        else
+          continue;
+
+        x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
+        if(!x509)
+          continue;
+
+        /* Try to import the certificate. This may fail for legitimate reasons
+           such as duplicate certificate, which is allowed by MS but not
+           OpenSSL. */
+        if(X509_STORE_add_cert(store, x509) == 1) {
+#if defined(DEBUGBUILD) && !defined(CURL_DISABLE_VERBOSE_STRINGS)
+          infof(data, "SSL: Imported cert \"%s\"\n", cert_name);
+#else
+          do {} while(0);
+#endif
+        }
+        X509_free(x509);
+      }
+
+      free(enhkey_usage);
+      CertFreeCertificateContext(pContext);
+      CertCloseStore(hStore, 0);
+
+      if(result)
+        return result;
+
+      infof(data, "successfully set certificate verify locations "
+            "to windows ca store\n");
+    }
+    else {
+      infof(data, "error setting certificate verify locations "
+            "to windows ca store, continuing anyway\n");
+    }
+  }
+  else
+#endif
+
 #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
   /* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
-  if(ssl_cafile) {
-    if(!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
-      if(verifypeer) {
-        /* Fail if we insist on successfully verifying the server. */
-        failf(data, "error setting certificate file: %s", ssl_cafile);
-        return CURLE_SSL_CACERT_BADFILE;
+  {
+    if(ssl_cafile) {
+      if(!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
+        if(verifypeer) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate file: %s", ssl_cafile);
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        /* Continue with a warning if no certificate verif is required. */
+        infof(data, "error setting certificate file, continuing anyway\n");
       }
-      /* Continue with a warning if no certificate verification is required. */
-      infof(data, "error setting certificate file, continuing anyway\n");
+      infof(data, "  CAfile: %s\n", ssl_cafile);
     }
-    infof(data, "  CAfile: %s\n", ssl_cafile);
-  }
-  if(ssl_capath) {
-    if(!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
-      if(verifypeer) {
-        /* Fail if we insist on successfully verifying the server. */
-        failf(data, "error setting certificate path: %s", ssl_capath);
-        return CURLE_SSL_CACERT_BADFILE;
+    if(ssl_capath) {
+      if(!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
+        if(verifypeer) {
+          /* Fail if we insist on successfully verifying the server. */
+          failf(data, "error setting certificate path: %s", ssl_capath);
+          return CURLE_SSL_CACERT_BADFILE;
+        }
+        /* Continue with a warning if no certificate verif is required. */
+        infof(data, "error setting certificate path, continuing anyway\n");
       }
-      /* Continue with a warning if no certificate verification is required. */
-      infof(data, "error setting certificate path, continuing anyway\n");
+      infof(data, "  CApath: %s\n", ssl_capath);
     }
-    infof(data, "  CApath: %s\n", ssl_capath);
   }
 #else
   if(ssl_cafile || ssl_capath) {
index 2ae7944e375b3b3abc10f7297a25bcbbb093e003..d7eebf59801442cdbc6317b7aa0e050990329711 100644 (file)
@@ -257,6 +257,8 @@ struct OperationConfig {
   bool ssl_revoke_best_effort; /* ignore SSL revocation offline/missing
                                   revocation list errors */
 
+  bool native_ca_store;        /* use the native os ca store */
+
   bool use_metalink;        /* process given URLs as metalink XML file */
   metalinkfile *metalinkfile_list; /* point to the first node */
   metalinkfile *metalinkfile_last; /* point to the last/current node */
index fa8be45ed34195f9faa98c956fa05714c4a581a7..81ee7c1365ec4215c32043b1926441c5ebadb417 100644 (file)
@@ -1905,7 +1905,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
           long mask = (config->ssl_allow_beast ? CURLSSLOPT_ALLOW_BEAST : 0) |
                       (config->ssl_revoke_best_effort ?
                        CURLSSLOPT_REVOKE_BEST_EFFORT : 0) |
+                      (config->native_ca_store ?
+                       CURLSSLOPT_NATIVE_CA : 0) |
                       (config->ssl_no_revoke ? CURLSSLOPT_NO_REVOKE : 0);
+
           if(mask)
             my_setopt_bitmask(curl, CURLOPT_SSL_OPTIONS, mask);
         }
@@ -2332,6 +2335,14 @@ static CURLcode transfer_per_config(struct GlobalConfig *global,
       else {
         result = FindWin32CACert(config, tls_backend_info->backend,
                                  "curl-ca-bundle.crt");
+#if defined(USE_WIN32_CRYPTO)
+        if(!config->cacert && !config->capath) {
+          /* user, and environement did not specify any ca file or path
+             and there is no "curl-ca-bundle.crt" file in standard path
+             so the only possible solution is using the windows ca store */
+          config->native_ca_store = TRUE;
+        }
+#endif
       }
 #endif
     }
index f244ba49042af65a842739793880762f835030d8..449359b8a28247e83b58b56610c0ab472fe8c52c 100644 (file)
@@ -126,6 +126,7 @@ const NameValueUnsigned setopt_nv_CURLSSLOPT[] = {
   NV(CURLSSLOPT_NO_REVOKE),
   NV(CURLSSLOPT_NO_PARTIALCHAIN),
   NV(CURLSSLOPT_REVOKE_BEST_EFFORT),
+  NV(CURLSSLOPT_NATIVE_CA),
   NVEND,
 };