openssl: only use CA_BLOB if verifying peer
Reported-by: Paul Groke Bug: https://curl.se/mail/lib-2023-01/0070.html Fixes #10351 Closes #10359
This commit is contained in:
parent
72d4f7d8f7
commit
a3bcfab4b5
@ -3052,7 +3052,7 @@ static CURLcode load_cacert_from_memory(X509_STORE *store,
|
||||
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);
|
||||
return (count > 0) ? CURLE_OK : CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
|
||||
static CURLcode populate_x509_store(struct Curl_cfilter *cf,
|
||||
@ -3076,218 +3076,214 @@ static CURLcode populate_x509_store(struct Curl_cfilter *cf,
|
||||
if(!store)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
|
||||
if(verifypeer) {
|
||||
#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://datatracker.ietf.org/doc/html/rfc5280 */
|
||||
if((conn_config->verifypeer || conn_config->verifyhost) &&
|
||||
(ssl_config->native_ca_store)) {
|
||||
HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("ROOT"));
|
||||
/* 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://datatracker.ietf.org/doc/html/rfc5280 */
|
||||
if(ssl_config->native_ca_store) {
|
||||
HCERTSTORE hStore = CertOpenSystemStore(0, TEXT("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;
|
||||
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;
|
||||
/* 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];
|
||||
char cert_name[256];
|
||||
#endif
|
||||
|
||||
pContext = CertEnumCertificatesInStore(hStore, pContext);
|
||||
if(!pContext)
|
||||
break;
|
||||
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\"", 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(!CertGetNameStringA(pContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0,
|
||||
NULL, cert_name, sizeof(cert_name))) {
|
||||
strcpy(cert_name, "Unknown");
|
||||
}
|
||||
infof(data, "SSL: Checking cert \"%s\"", cert_name);
|
||||
#endif
|
||||
encoded_cert = (const unsigned char *)pContext->pbCertEncoded;
|
||||
if(!encoded_cert)
|
||||
continue;
|
||||
|
||||
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((HRESULT)GetLastError() != CRYPT_E_NOT_FOUND)
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
DWORD i;
|
||||
bool found = false;
|
||||
GetSystemTimeAsFileTime(&now);
|
||||
if(CompareFileTime(&pContext->pCertInfo->NotBefore, &now) > 0 ||
|
||||
CompareFileTime(&now, &pContext->pCertInfo->NotAfter) > 0)
|
||||
continue;
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
if(!found)
|
||||
continue;
|
||||
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((HRESULT)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;
|
||||
}
|
||||
else
|
||||
continue;
|
||||
|
||||
x509 = d2i_X509(NULL, &encoded_cert, pContext->cbCertEncoded);
|
||||
if(!x509)
|
||||
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) {
|
||||
/* 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\"", cert_name);
|
||||
infof(data, "SSL: Imported cert \"%s\"", cert_name);
|
||||
#endif
|
||||
imported_native_ca = true;
|
||||
imported_native_ca = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
X509_free(x509);
|
||||
|
||||
free(enhkey_usage);
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
|
||||
free(enhkey_usage);
|
||||
CertFreeCertificateContext(pContext);
|
||||
CertCloseStore(hStore, 0);
|
||||
|
||||
if(result)
|
||||
return result;
|
||||
if(imported_native_ca)
|
||||
infof(data, "successfully imported Windows CA store");
|
||||
else
|
||||
infof(data, "error importing Windows CA store, continuing anyway");
|
||||
}
|
||||
if(imported_native_ca)
|
||||
infof(data, "successfully imported Windows CA store");
|
||||
else
|
||||
infof(data, "error importing Windows CA store, continuing anyway");
|
||||
}
|
||||
#endif
|
||||
|
||||
if(ca_info_blob) {
|
||||
result = load_cacert_from_memory(store, ca_info_blob);
|
||||
if(result) {
|
||||
if(result == CURLE_OUT_OF_MEMORY ||
|
||||
(verifypeer && !imported_native_ca)) {
|
||||
if(ca_info_blob) {
|
||||
result = load_cacert_from_memory(store, ca_info_blob);
|
||||
if(result) {
|
||||
failf(data, "error importing CA certificate blob");
|
||||
return result;
|
||||
}
|
||||
/* Only warn if no certificate verification is required. */
|
||||
infof(data, "error importing CA certificate blob, continuing anyway");
|
||||
}
|
||||
else {
|
||||
imported_ca_info_blob = true;
|
||||
infof(data, "successfully imported CA certificate blob");
|
||||
}
|
||||
}
|
||||
|
||||
if(verifypeer && (ssl_cafile || ssl_capath)) {
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
|
||||
if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) {
|
||||
if(!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate file: %s", ssl_cafile);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
else
|
||||
infof(data, "error setting certificate file, continuing anyway");
|
||||
}
|
||||
if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) {
|
||||
if(!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate path: %s", ssl_capath);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
else
|
||||
infof(data, "error setting certificate path, continuing anyway");
|
||||
}
|
||||
#else
|
||||
/* tell OpenSSL where to find CA certificates that are used to verify the
|
||||
server's certificate. */
|
||||
if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
|
||||
if(!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
else {
|
||||
infof(data, "error setting certificate verify locations,"
|
||||
" continuing anyway");
|
||||
imported_ca_info_blob = true;
|
||||
infof(data, "successfully imported CA certificate blob");
|
||||
}
|
||||
}
|
||||
|
||||
if(ssl_cafile || ssl_capath) {
|
||||
#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3)
|
||||
/* OpenSSL 3.0.0 has deprecated SSL_CTX_load_verify_locations */
|
||||
if(ssl_cafile && !X509_STORE_load_file(store, ssl_cafile)) {
|
||||
if(!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate file: %s", ssl_cafile);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
else
|
||||
infof(data, "error setting certificate file, continuing anyway");
|
||||
}
|
||||
if(ssl_capath && !X509_STORE_load_path(store, ssl_capath)) {
|
||||
if(!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate path: %s", ssl_capath);
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
else
|
||||
infof(data, "error setting certificate path, continuing anyway");
|
||||
}
|
||||
#else
|
||||
/* tell OpenSSL where to find CA certificates that are used to verify the
|
||||
server's certificate. */
|
||||
if(!X509_STORE_load_locations(store, ssl_cafile, ssl_capath)) {
|
||||
if(!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* Fail if we insist on successfully verifying the server. */
|
||||
failf(data, "error setting certificate verify locations:"
|
||||
" CAfile: %s CApath: %s",
|
||||
ssl_cafile ? ssl_cafile : "none",
|
||||
ssl_capath ? ssl_capath : "none");
|
||||
return CURLE_SSL_CACERT_BADFILE;
|
||||
}
|
||||
else {
|
||||
infof(data, "error setting certificate verify locations,"
|
||||
" continuing anyway");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
infof(data, " CAfile: %s", ssl_cafile ? ssl_cafile : "none");
|
||||
infof(data, " CApath: %s", ssl_capath ? ssl_capath : "none");
|
||||
}
|
||||
|
||||
#ifdef CURL_CA_FALLBACK
|
||||
if(verifypeer && !ssl_cafile && !ssl_capath &&
|
||||
!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* verifying the peer without any CA certificates won't
|
||||
work so use openssl's built-in default as fallback */
|
||||
X509_STORE_set_default_paths(store);
|
||||
}
|
||||
if(!ssl_cafile && !ssl_capath &&
|
||||
!imported_native_ca && !imported_ca_info_blob) {
|
||||
/* verifying the peer without any CA certificates won't
|
||||
work so use openssl's built-in default as fallback */
|
||||
X509_STORE_set_default_paths(store);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if(ssl_crlfile) {
|
||||
/* tell OpenSSL where to find CRL file that is used to check certificate
|
||||
|
||||
Loading…
Reference in New Issue
Block a user