From 148534db57dda611cf8516e92e4d6e35fc1e5074 Mon Sep 17 00:00:00 2001 From: Gilles Vollant Date: Fri, 13 Sep 2019 11:24:00 +0200 Subject: CURLOPT_SSL_OPTIONS: add *_NATIVE_CA to use Windows CA store (with openssl) Closes #4346 --- docs/libcurl/opts/CURLOPT_SSL_OPTIONS.3 | 4 + docs/libcurl/symbols-in-versions | 1 + include/curl/curl.h | 4 + lib/setopt.c | 2 + lib/urldata.h | 1 + lib/vtls/openssl.c | 196 ++++++++++++++++++++++++++++---- src/tool_cfgable.h | 2 + src/tool_operate.c | 11 ++ src/tool_setopt.c | 1 + 9 files changed, 203 insertions(+), 19 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.3 b/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.3 index 1dfe37b7e..3a2b88cc2 100644 --- a/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.3 +++ b/docs/libcurl/opts/CURLOPT_SSL_OPTIONS.3 @@ -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 diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions index 3b340ed8e..4264547bc 100644 --- a/docs/libcurl/symbols-in-versions +++ b/docs/libcurl/symbols-in-versions @@ -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 diff --git a/include/curl/curl.h b/include/curl/curl.h index 11246ea30..a3da3da86 100644 --- a/include/curl/curl.h +++ b/include/curl/curl.h @@ -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. */ diff --git a/lib/setopt.c b/lib/setopt.c index 04785a682..93ba0975a 100644 --- a/lib/setopt.c +++ b/lib/setopt.c @@ -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; diff --git a/lib/urldata.h b/lib/urldata.h index 757218d83..e4d046400 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -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 { diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 176fa522a..6f6b604c2 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -46,6 +46,11 @@ #include "multiif.h" #include "strerror.h" #include "curl_printf.h" + +#if defined(USE_WIN32_CRYPTO) +#include +#endif + #include #include #include @@ -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) { diff --git a/src/tool_cfgable.h b/src/tool_cfgable.h index 2ae7944e3..d7eebf598 100644 --- a/src/tool_cfgable.h +++ b/src/tool_cfgable.h @@ -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 */ diff --git a/src/tool_operate.c b/src/tool_operate.c index fa8be45ed..81ee7c136 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -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 } diff --git a/src/tool_setopt.c b/src/tool_setopt.c index f244ba490..449359b8a 100644 --- a/src/tool_setopt.c +++ b/src/tool_setopt.c @@ -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, }; -- cgit v1.2.3