mirror of
https://github.com/curl/curl.git
synced 2024-12-27 06:59:43 +08:00
schannel: verify hostname independent of verify cert
Prior to this change when CURLOPT_SSL_VERIFYPEER (verifypeer) was off and CURLOPT_SSL_VERIFYHOST (verifyhost) was on we did not verify the hostname in schannel code. This fixes KNOWN_BUG 2.8 "Schannel disable CURLOPT_SSL_VERIFYPEER and verify hostname". We discussed a fix several years ago in #3285 but it went stale. Assisted-by: Daniel Stenberg Bug: https://curl.haxx.se/mail/lib-2018-10/0113.html Reported-by: Martin Galvan Ref: https://github.com/curl/curl/pull/3285 Fixes https://github.com/curl/curl/issues/3284 Closes https://github.com/curl/curl/pull/10056
This commit is contained in:
parent
c7056759be
commit
889c071d3c
@ -21,7 +21,6 @@ problems may have been fixed or changed somewhat since this was written.
|
|||||||
2.4 Secure Transport will not import PKCS#12 client certificates without a password
|
2.4 Secure Transport will not import PKCS#12 client certificates without a password
|
||||||
2.5 Client cert handling with Issuer DN differs between backends
|
2.5 Client cert handling with Issuer DN differs between backends
|
||||||
2.7 Client cert (MTLS) issues with Schannel
|
2.7 Client cert (MTLS) issues with Schannel
|
||||||
2.8 Schannel disable CURLOPT_SSL_VERIFYPEER and verify hostname
|
|
||||||
2.11 Schannel TLS 1.2 handshake bug in old Windows versions
|
2.11 Schannel TLS 1.2 handshake bug in old Windows versions
|
||||||
2.12 FTPS with Schannel times out file list operation
|
2.12 FTPS with Schannel times out file list operation
|
||||||
2.13 CURLOPT_CERTINFO results in CURLE_OUT_OF_MEMORY with Schannel
|
2.13 CURLOPT_CERTINFO results in CURLE_OUT_OF_MEMORY with Schannel
|
||||||
@ -163,12 +162,6 @@ problems may have been fixed or changed somewhat since this was written.
|
|||||||
|
|
||||||
See https://github.com/curl/curl/issues/3145
|
See https://github.com/curl/curl/issues/3145
|
||||||
|
|
||||||
2.8 Schannel disable CURLOPT_SSL_VERIFYPEER and verify hostname
|
|
||||||
|
|
||||||
This seems to be a limitation in the underlying Schannel API.
|
|
||||||
|
|
||||||
https://github.com/curl/curl/issues/3284
|
|
||||||
|
|
||||||
2.11 Schannel TLS 1.2 handshake bug in old Windows versions
|
2.11 Schannel TLS 1.2 handshake bug in old Windows versions
|
||||||
|
|
||||||
In old versions of Windows such as 7 and 8.1 the Schannel TLS 1.2 handshake
|
In old versions of Windows such as 7 and 8.1 the Schannel TLS 1.2 handshake
|
||||||
|
@ -810,9 +810,9 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf,
|
|||||||
|
|
||||||
SCH_CREDENTIALS credentials = { 0 };
|
SCH_CREDENTIALS credentials = { 0 };
|
||||||
TLS_PARAMETERS tls_parameters = { 0 };
|
TLS_PARAMETERS tls_parameters = { 0 };
|
||||||
CRYPTO_SETTINGS crypto_settings[4] = { 0 };
|
CRYPTO_SETTINGS crypto_settings[4] = { { 0 } };
|
||||||
UNICODE_STRING blocked_ccm_modes[1] = { 0 };
|
UNICODE_STRING blocked_ccm_modes[1] = { { 0 } };
|
||||||
UNICODE_STRING blocked_gcm_modes[1] = { 0 };
|
UNICODE_STRING blocked_gcm_modes[1] = { { 0 } };
|
||||||
|
|
||||||
int crypto_settings_idx = 0;
|
int crypto_settings_idx = 0;
|
||||||
|
|
||||||
@ -1634,10 +1634,16 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
|
|||||||
|
|
||||||
#ifdef HAS_MANUAL_VERIFY_API
|
#ifdef HAS_MANUAL_VERIFY_API
|
||||||
if(conn_config->verifypeer && backend->use_manual_cred_validation) {
|
if(conn_config->verifypeer && backend->use_manual_cred_validation) {
|
||||||
|
/* Certificate verification also verifies the hostname if verifyhost */
|
||||||
return Curl_verify_certificate(cf, data);
|
return Curl_verify_certificate(cf, data);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Verify the hostname manually when certificate verification is disabled,
|
||||||
|
because in that case Schannel won't verify it. */
|
||||||
|
if(!conn_config->verifypeer && conn_config->verifyhost)
|
||||||
|
return Curl_verify_host(cf, data);
|
||||||
|
|
||||||
return CURLE_OK;
|
return CURLE_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,6 +76,9 @@
|
|||||||
|
|
||||||
extern const struct Curl_ssl Curl_ssl_schannel;
|
extern const struct Curl_ssl Curl_ssl_schannel;
|
||||||
|
|
||||||
|
CURLcode Curl_verify_host(struct Curl_cfilter *cf,
|
||||||
|
struct Curl_easy *data);
|
||||||
|
|
||||||
CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
|
CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data);
|
struct Curl_easy *data);
|
||||||
|
|
||||||
|
@ -43,6 +43,58 @@
|
|||||||
#define HAS_CLIENT_CERT_PATH
|
#define HAS_CLIENT_CERT_PATH
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef CRYPT_DECODE_NOCOPY_FLAG
|
||||||
|
#define CRYPT_DECODE_NOCOPY_FLAG 0x1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CRYPT_DECODE_ALLOC_FLAG
|
||||||
|
#define CRYPT_DECODE_ALLOC_FLAG 0x8000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CERT_ALT_NAME_DNS_NAME
|
||||||
|
#define CERT_ALT_NAME_DNS_NAME 3
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CERT_ALT_NAME_IP_ADDRESS
|
||||||
|
#define CERT_ALT_NAME_IP_ADDRESS 8
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
|
||||||
|
/* Original mingw is missing CERT structs or they're disabled.
|
||||||
|
Refer to w32api-5.0.2-mingw32-dev\include\wincrypt.h. */
|
||||||
|
|
||||||
|
/* !checksrc! disable TYPEDEFSTRUCT 4 */
|
||||||
|
typedef struct _CERT_OTHER_NAME {
|
||||||
|
LPSTR pszObjId;
|
||||||
|
CRYPT_OBJID_BLOB Value;
|
||||||
|
} CERT_OTHER_NAME, *PCERT_OTHER_NAME;
|
||||||
|
|
||||||
|
typedef struct _CERT_ALT_NAME_ENTRY {
|
||||||
|
DWORD dwAltNameChoice;
|
||||||
|
union {
|
||||||
|
PCERT_OTHER_NAME pOtherName;
|
||||||
|
LPWSTR pwszRfc822Name;
|
||||||
|
LPWSTR pwszDNSName;
|
||||||
|
CERT_NAME_BLOB DirectoryName;
|
||||||
|
LPWSTR pwszURL;
|
||||||
|
CRYPT_DATA_BLOB IPAddress;
|
||||||
|
LPSTR pszRegisteredID;
|
||||||
|
};
|
||||||
|
} CERT_ALT_NAME_ENTRY, *PCERT_ALT_NAME_ENTRY;
|
||||||
|
|
||||||
|
typedef struct _CERT_ALT_NAME_INFO {
|
||||||
|
DWORD cAltEntry;
|
||||||
|
PCERT_ALT_NAME_ENTRY rgAltEntry;
|
||||||
|
} CERT_ALT_NAME_INFO, *PCERT_ALT_NAME_INFO;
|
||||||
|
|
||||||
|
typedef struct _CRYPT_DECODE_PARA {
|
||||||
|
DWORD cbSize;
|
||||||
|
PFN_CRYPT_ALLOC pfnAlloc;
|
||||||
|
PFN_CRYPT_FREE pfnFree;
|
||||||
|
} CRYPT_DECODE_PARA, *PCRYPT_DECODE_PARA;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef SCH_CREDENTIALS_VERSION
|
#ifndef SCH_CREDENTIALS_VERSION
|
||||||
|
|
||||||
#define SCH_CREDENTIALS_VERSION 0x00000005
|
#define SCH_CREDENTIALS_VERSION 0x00000005
|
||||||
|
@ -39,8 +39,6 @@
|
|||||||
#include "schannel.h"
|
#include "schannel.h"
|
||||||
#include "schannel_int.h"
|
#include "schannel_int.h"
|
||||||
|
|
||||||
#ifdef HAS_MANUAL_VERIFY_API
|
|
||||||
|
|
||||||
#include "vtls.h"
|
#include "vtls.h"
|
||||||
#include "vtls_int.h"
|
#include "vtls_int.h"
|
||||||
#include "sendf.h"
|
#include "sendf.h"
|
||||||
@ -56,6 +54,9 @@
|
|||||||
|
|
||||||
#define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend)
|
#define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAS_MANUAL_VERIFY_API
|
||||||
|
|
||||||
#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */
|
#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */
|
||||||
#define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
|
#define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
|
||||||
#define END_CERT "\n-----END CERTIFICATE-----"
|
#define END_CERT "\n-----END CERTIFICATE-----"
|
||||||
@ -330,6 +331,8 @@ cleanup:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#endif /* HAS_MANUAL_VERIFY_API */
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Returns the number of characters necessary to populate all the host_names.
|
* Returns the number of characters necessary to populate all the host_names.
|
||||||
* If host_names is not NULL, populate it with all the host names. Each string
|
* If host_names is not NULL, populate it with all the host names. Each string
|
||||||
@ -353,10 +356,10 @@ static DWORD cert_get_name_string(struct Curl_easy *data,
|
|||||||
LPTSTR current_pos = NULL;
|
LPTSTR current_pos = NULL;
|
||||||
DWORD i;
|
DWORD i;
|
||||||
|
|
||||||
|
#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
|
||||||
/* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
|
/* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
|
||||||
if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
|
if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
|
||||||
VERSION_GREATER_THAN_EQUAL)) {
|
VERSION_GREATER_THAN_EQUAL)) {
|
||||||
#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
|
|
||||||
/* CertGetNameString will provide the 8-bit character string without
|
/* CertGetNameString will provide the 8-bit character string without
|
||||||
* any decoding */
|
* any decoding */
|
||||||
DWORD name_flags =
|
DWORD name_flags =
|
||||||
@ -368,8 +371,8 @@ static DWORD cert_get_name_string(struct Curl_easy *data,
|
|||||||
host_names,
|
host_names,
|
||||||
length);
|
length);
|
||||||
return actual_length;
|
return actual_length;
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
compute_content = host_names != NULL && length != 0;
|
compute_content = host_names != NULL && length != 0;
|
||||||
|
|
||||||
@ -457,17 +460,34 @@ static DWORD cert_get_name_string(struct Curl_easy *data,
|
|||||||
return actual_length;
|
return actual_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
static CURLcode verify_host(struct Curl_easy *data,
|
/* Verify the server's hostname */
|
||||||
CERT_CONTEXT *pCertContextServer,
|
CURLcode Curl_verify_host(struct Curl_cfilter *cf,
|
||||||
const char *conn_hostname)
|
struct Curl_easy *data)
|
||||||
{
|
{
|
||||||
|
struct ssl_connect_data *connssl = cf->ctx;
|
||||||
|
SECURITY_STATUS sspi_status;
|
||||||
CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
|
CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
|
||||||
|
CERT_CONTEXT *pCertContextServer = NULL;
|
||||||
TCHAR *cert_hostname_buff = NULL;
|
TCHAR *cert_hostname_buff = NULL;
|
||||||
size_t cert_hostname_buff_index = 0;
|
size_t cert_hostname_buff_index = 0;
|
||||||
|
const char *conn_hostname = connssl->hostname;
|
||||||
size_t hostlen = strlen(conn_hostname);
|
size_t hostlen = strlen(conn_hostname);
|
||||||
DWORD len = 0;
|
DWORD len = 0;
|
||||||
DWORD actual_len = 0;
|
DWORD actual_len = 0;
|
||||||
|
|
||||||
|
sspi_status =
|
||||||
|
s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
|
||||||
|
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
|
||||||
|
&pCertContextServer);
|
||||||
|
|
||||||
|
if((sspi_status != SEC_E_OK) || !pCertContextServer) {
|
||||||
|
char buffer[STRERROR_LEN];
|
||||||
|
failf(data, "schannel: Failed to read remote certificate context: %s",
|
||||||
|
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
|
||||||
|
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
/* Determine the size of the string needed for the cert hostname */
|
/* Determine the size of the string needed for the cert hostname */
|
||||||
len = cert_get_name_string(data, pCertContextServer, NULL, 0);
|
len = cert_get_name_string(data, pCertContextServer, NULL, 0);
|
||||||
if(len == 0) {
|
if(len == 0) {
|
||||||
@ -498,10 +518,9 @@ static CURLcode verify_host(struct Curl_easy *data,
|
|||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output
|
/* cert_hostname_buff contains all DNS names, where each name is
|
||||||
* will contain all DNS names, where each name is null-terminated
|
* null-terminated and the last DNS name is double null-terminated. Due to
|
||||||
* and the last DNS name is double null-terminated. Due to this
|
* this encoding, use the length of the buffer to iterate over all names.
|
||||||
* encoding, use the length of the buffer to iterate over all names.
|
|
||||||
*/
|
*/
|
||||||
result = CURLE_PEER_FAILED_VERIFICATION;
|
result = CURLE_PEER_FAILED_VERIFICATION;
|
||||||
while(cert_hostname_buff_index < len &&
|
while(cert_hostname_buff_index < len &&
|
||||||
@ -560,9 +579,15 @@ static CURLcode verify_host(struct Curl_easy *data,
|
|||||||
cleanup:
|
cleanup:
|
||||||
Curl_safefree(cert_hostname_buff);
|
Curl_safefree(cert_hostname_buff);
|
||||||
|
|
||||||
|
if(pCertContextServer)
|
||||||
|
CertFreeCertificateContext(pCertContextServer);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef HAS_MANUAL_VERIFY_API
|
||||||
|
/* Verify the server's certificate and hostname */
|
||||||
CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
|
CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
|
||||||
struct Curl_easy *data)
|
struct Curl_easy *data)
|
||||||
{
|
{
|
||||||
@ -721,7 +746,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
|
|||||||
|
|
||||||
if(result == CURLE_OK) {
|
if(result == CURLE_OK) {
|
||||||
if(conn_config->verifyhost) {
|
if(conn_config->verifyhost) {
|
||||||
result = verify_host(data, pCertContextServer, connssl->hostname);
|
result = Curl_verify_host(cf, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user