CURLOPT_SSL_OPTIONS: add *_NATIVE_CA to use Windows CA store (with openssl)
Closes #4346
This commit is contained in:
parent
76b9e8de7b
commit
148534db57
@ -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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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. */
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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 */
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user