CURLOPT_SSL_OPTIONS: add *_NATIVE_CA to use Windows CA store (with openssl)

Closes #4346
This commit is contained in:
Gilles Vollant 2019-09-13 11:24:00 +02:00 committed by Daniel Stenberg
parent 76b9e8de7b
commit 148534db57
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
9 changed files with 203 additions and 19 deletions

View 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 present. This option is only supported for Schannel (the native Windows SSL
library). If combined with \fICURLSSLOPT_NO_REVOKE\fP, the latter takes library). If combined with \fICURLSSLOPT_NO_REVOKE\fP, the latter takes
precedence. (Added in 7.70.0) 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 .SH DEFAULT
0 0
.SH PROTOCOLS .SH PROTOCOLS

View File

@ -743,6 +743,7 @@ CURLSSLBACKEND_SCHANNEL 7.34.0
CURLSSLBACKEND_SECURETRANSPORT 7.64.1 CURLSSLBACKEND_SECURETRANSPORT 7.64.1
CURLSSLBACKEND_WOLFSSL 7.49.0 CURLSSLBACKEND_WOLFSSL 7.49.0
CURLSSLOPT_ALLOW_BEAST 7.25.0 CURLSSLOPT_ALLOW_BEAST 7.25.0
CURLSSLOPT_NATIVE_CA 7.71.0
CURLSSLOPT_NO_PARTIALCHAIN 7.68.0 CURLSSLOPT_NO_PARTIALCHAIN 7.68.0
CURLSSLOPT_NO_REVOKE 7.44.0 CURLSSLOPT_NO_REVOKE 7.44.0
CURLSSLSET_NO_BACKENDS 7.56.0 CURLSSLSET_NO_BACKENDS 7.56.0

View File

@ -838,6 +838,10 @@ typedef enum {
behavior is present. */ behavior is present. */
#define CURLSSLOPT_REVOKE_BEST_EFFORT (1<<3) #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. /* The default connection attempt delay in milliseconds for happy eyeballs.
CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document CURLOPT_HAPPY_EYEBALLS_TIMEOUT_MS.3 and happy-eyeballs-timeout-ms.d document
this value, keep them in sync. */ this value, keep them in sync. */

View 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_revoke = !!(arg & CURLSSLOPT_NO_REVOKE);
data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); data->set.ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN);
data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); data->set.ssl.revoke_best_effort = !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT);
data->set.ssl.native_ca_store = !!(arg & CURLSSLOPT_NATIVE_CA);
break; break;
#ifndef CURL_DISABLE_PROXY #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); (bool)((arg&CURLSSLOPT_ALLOW_BEAST) ? TRUE : FALSE);
data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE); data->set.proxy_ssl.no_revoke = !!(arg & CURLSSLOPT_NO_REVOKE);
data->set.proxy_ssl.no_partialchain = !!(arg & CURLSSLOPT_NO_PARTIALCHAIN); 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 = data->set.proxy_ssl.revoke_best_effort =
!!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT); !!(arg & CURLSSLOPT_REVOKE_BEST_EFFORT);
break; break;

View File

@ -259,6 +259,7 @@ struct ssl_config_data {
BIT(no_partialchain); /* don't accept partial certificate chains */ BIT(no_partialchain); /* don't accept partial certificate chains */
BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation BIT(revoke_best_effort); /* ignore SSL revocation offline/missing revocation
list errors */ list errors */
BIT(native_ca_store); /* use the native ca store of operating system */
}; };
struct ssl_general_config { struct ssl_general_config {

View File

@ -46,6 +46,11 @@
#include "multiif.h" #include "multiif.h"
#include "strerror.h" #include "strerror.h"
#include "curl_printf.h" #include "curl_printf.h"
#if defined(USE_WIN32_CRYPTO)
#include <wincrypt.h>
#endif
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/rand.h> #include <openssl/rand.h>
#include <openssl/x509v3.h> #include <openssl/x509v3.h>
@ -2720,31 +2725,184 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex)
} }
#endif #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) #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */ /* 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(ssl_cafile) {
if(verifypeer) { if(!SSL_CTX_load_verify_file(backend->ctx, ssl_cafile)) {
/* Fail if we insist on successfully verifying the server. */ if(verifypeer) {
failf(data, "error setting certificate file: %s", ssl_cafile); /* Fail if we insist on successfully verifying the server. */
return CURLE_SSL_CACERT_BADFILE; 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, " CAfile: %s\n", ssl_cafile);
infof(data, "error setting certificate file, continuing anyway\n");
} }
infof(data, " CAfile: %s\n", ssl_cafile); if(ssl_capath) {
} if(!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) {
if(ssl_capath) { if(verifypeer) {
if(!SSL_CTX_load_verify_dir(backend->ctx, ssl_capath)) { /* Fail if we insist on successfully verifying the server. */
if(verifypeer) { failf(data, "error setting certificate path: %s", ssl_capath);
/* Fail if we insist on successfully verifying the server. */ return CURLE_SSL_CACERT_BADFILE;
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, " CApath: %s\n", ssl_capath);
infof(data, "error setting certificate path, continuing anyway\n");
} }
infof(data, " CApath: %s\n", ssl_capath);
} }
#else #else
if(ssl_cafile || ssl_capath) { if(ssl_cafile || ssl_capath) {

View File

@ -257,6 +257,8 @@ struct OperationConfig {
bool ssl_revoke_best_effort; /* ignore SSL revocation offline/missing bool ssl_revoke_best_effort; /* ignore SSL revocation offline/missing
revocation list errors */ revocation list errors */
bool native_ca_store; /* use the native os ca store */
bool use_metalink; /* process given URLs as metalink XML file */ bool use_metalink; /* process given URLs as metalink XML file */
metalinkfile *metalinkfile_list; /* point to the first node */ metalinkfile *metalinkfile_list; /* point to the first node */
metalinkfile *metalinkfile_last; /* point to the last/current node */ metalinkfile *metalinkfile_last; /* point to the last/current node */

View File

@ -1905,7 +1905,10 @@ static CURLcode single_transfer(struct GlobalConfig *global,
long mask = (config->ssl_allow_beast ? CURLSSLOPT_ALLOW_BEAST : 0) | long mask = (config->ssl_allow_beast ? CURLSSLOPT_ALLOW_BEAST : 0) |
(config->ssl_revoke_best_effort ? (config->ssl_revoke_best_effort ?
CURLSSLOPT_REVOKE_BEST_EFFORT : 0) | CURLSSLOPT_REVOKE_BEST_EFFORT : 0) |
(config->native_ca_store ?
CURLSSLOPT_NATIVE_CA : 0) |
(config->ssl_no_revoke ? CURLSSLOPT_NO_REVOKE : 0); (config->ssl_no_revoke ? CURLSSLOPT_NO_REVOKE : 0);
if(mask) if(mask)
my_setopt_bitmask(curl, CURLOPT_SSL_OPTIONS, mask); my_setopt_bitmask(curl, CURLOPT_SSL_OPTIONS, mask);
} }
@ -2332,6 +2335,14 @@ static CURLcode transfer_per_config(struct GlobalConfig *global,
else { else {
result = FindWin32CACert(config, tls_backend_info->backend, result = FindWin32CACert(config, tls_backend_info->backend,
"curl-ca-bundle.crt"); "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 #endif
} }

View File

@ -126,6 +126,7 @@ const NameValueUnsigned setopt_nv_CURLSSLOPT[] = {
NV(CURLSSLOPT_NO_REVOKE), NV(CURLSSLOPT_NO_REVOKE),
NV(CURLSSLOPT_NO_PARTIALCHAIN), NV(CURLSSLOPT_NO_PARTIALCHAIN),
NV(CURLSSLOPT_REVOKE_BEST_EFFORT), NV(CURLSSLOPT_REVOKE_BEST_EFFORT),
NV(CURLSSLOPT_NATIVE_CA),
NVEND, NVEND,
}; };