mirror of
https://github.com/curl/curl.git
synced 2024-11-27 05:50:21 +08:00
schannel: Make CURLOPT_CAINFO work better on Windows 7
- Support hostname verification via alternative names (SAN) in the peer certificate when CURLOPT_CAINFO is used in Windows 7 and earlier. CERT_NAME_SEARCH_ALL_NAMES_FLAG doesn't exist before Windows 8. As a result CertGetNameString doesn't quite work on those versions of Windows. This change provides an alternative solution for CertGetNameString by iterating through CERT_ALT_NAME_INFO for earlier versions of Windows. Prior to this change many certificates failed the hostname validation when CURLOPT_CAINFO was used in Windows 7 and earlier. Most certificates now represent multiple hostnames and rely on the alternative names field exclusively to represent their hostnames. Reported-by: Jeroen Ooms Fixes https://github.com/curl/curl/issues/3711 Closes https://github.com/curl/curl/pull/4761
This commit is contained in:
parent
cbb5429001
commit
29e40a6d8a
@ -293,6 +293,133 @@ cleanup:
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* in the host_names is null-terminated and the last string is double
|
||||
* null-terminated. If no DNS names are found, a single null-terminated empty
|
||||
* string is returned.
|
||||
*/
|
||||
static DWORD cert_get_name_string(struct Curl_easy *data,
|
||||
CERT_CONTEXT *cert_context,
|
||||
LPTSTR host_names,
|
||||
DWORD length)
|
||||
{
|
||||
DWORD actual_length = 0;
|
||||
BOOL compute_content = FALSE;
|
||||
CERT_INFO *cert_info = NULL;
|
||||
CERT_EXTENSION *extension = NULL;
|
||||
CRYPT_DECODE_PARA decode_para = {0, 0, 0};
|
||||
CERT_ALT_NAME_INFO *alt_name_info = NULL;
|
||||
DWORD alt_name_info_size = 0;
|
||||
BOOL ret_val = FALSE;
|
||||
char *current_pos = NULL;
|
||||
DWORD i;
|
||||
|
||||
/* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
|
||||
if(Curl_verify_windows_version(6, 2, PLATFORM_WINNT,
|
||||
VERSION_GREATER_THAN_EQUAL)) {
|
||||
#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
|
||||
/* CertGetNameString will provide the 8-bit character string without
|
||||
* any decoding */
|
||||
DWORD name_flags =
|
||||
CERT_NAME_DISABLE_IE4_UTF8_FLAG | CERT_NAME_SEARCH_ALL_NAMES_FLAG;
|
||||
actual_length = CertGetNameString(cert_context,
|
||||
CERT_NAME_DNS_TYPE,
|
||||
name_flags,
|
||||
NULL,
|
||||
host_names,
|
||||
length);
|
||||
return actual_length;
|
||||
#endif
|
||||
}
|
||||
|
||||
compute_content = host_names != NULL && length != 0;
|
||||
|
||||
/* Initialize default return values. */
|
||||
actual_length = 1;
|
||||
if(compute_content) {
|
||||
*host_names = '\0';
|
||||
}
|
||||
|
||||
if(!cert_context) {
|
||||
failf(data, "schannel: Null certificate context.");
|
||||
return actual_length;
|
||||
}
|
||||
|
||||
cert_info = cert_context->pCertInfo;
|
||||
if(!cert_info) {
|
||||
failf(data, "schannel: Null certificate info.");
|
||||
return actual_length;
|
||||
}
|
||||
|
||||
extension = CertFindExtension(szOID_SUBJECT_ALT_NAME2,
|
||||
cert_info->cExtension,
|
||||
cert_info->rgExtension);
|
||||
if(!extension) {
|
||||
failf(data, "schannel: CertFindExtension() returned no extension.");
|
||||
return actual_length;
|
||||
}
|
||||
|
||||
decode_para.cbSize = sizeof(CRYPT_DECODE_PARA);
|
||||
|
||||
ret_val =
|
||||
CryptDecodeObjectEx(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
|
||||
szOID_SUBJECT_ALT_NAME2,
|
||||
extension->Value.pbData,
|
||||
extension->Value.cbData,
|
||||
CRYPT_DECODE_ALLOC_FLAG | CRYPT_DECODE_NOCOPY_FLAG,
|
||||
&decode_para,
|
||||
&alt_name_info,
|
||||
&alt_name_info_size);
|
||||
if(!ret_val) {
|
||||
failf(data,
|
||||
"schannel: CryptDecodeObjectEx() returned no alternate name "
|
||||
"information.");
|
||||
return actual_length;
|
||||
}
|
||||
|
||||
current_pos = host_names;
|
||||
|
||||
/* Iterate over the alternate names and populate host_names. */
|
||||
for(i = 0; i < alt_name_info->cAltEntry; i++) {
|
||||
const CERT_ALT_NAME_ENTRY *entry = &alt_name_info->rgAltEntry[i];
|
||||
wchar_t *dns_w = NULL;
|
||||
size_t current_length = 0;
|
||||
|
||||
if(entry->dwAltNameChoice != CERT_ALT_NAME_DNS_NAME) {
|
||||
continue;
|
||||
}
|
||||
if(entry->pwszDNSName == NULL) {
|
||||
infof(data, "schannel: Empty DNS name.");
|
||||
continue;
|
||||
}
|
||||
current_length = wcslen(entry->pwszDNSName) + 1;
|
||||
if(!compute_content) {
|
||||
actual_length += (DWORD)current_length;
|
||||
continue;
|
||||
}
|
||||
/* Sanity check to prevent buffer overrun. */
|
||||
if((actual_length + current_length) > length) {
|
||||
failf(data, "schannel: Not enough memory to list all host names.");
|
||||
break;
|
||||
}
|
||||
dns_w = entry->pwszDNSName;
|
||||
/* pwszDNSName is in ia5 string format and hence doesn't contain any
|
||||
* non-ascii characters. */
|
||||
while(*dns_w != '\0') {
|
||||
*current_pos++ = (char)(*dns_w++);
|
||||
}
|
||||
*current_pos++ = '\0';
|
||||
actual_length += (DWORD)current_length;
|
||||
}
|
||||
if(compute_content) {
|
||||
/* Last string has double null-terminator. */
|
||||
*current_pos = '\0';
|
||||
}
|
||||
return actual_length;
|
||||
}
|
||||
|
||||
static CURLcode verify_host(struct Curl_easy *data,
|
||||
CERT_CONTEXT *pCertContextServer,
|
||||
const char * const conn_hostname)
|
||||
@ -303,21 +430,8 @@ static CURLcode verify_host(struct Curl_easy *data,
|
||||
DWORD len = 0;
|
||||
DWORD actual_len = 0;
|
||||
|
||||
/* CertGetNameString will provide the 8-bit character string without
|
||||
* any decoding */
|
||||
DWORD name_flags = CERT_NAME_DISABLE_IE4_UTF8_FLAG;
|
||||
|
||||
#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
|
||||
name_flags |= CERT_NAME_SEARCH_ALL_NAMES_FLAG;
|
||||
#endif
|
||||
|
||||
/* Determine the size of the string needed for the cert hostname */
|
||||
len = CertGetNameString(pCertContextServer,
|
||||
CERT_NAME_DNS_TYPE,
|
||||
name_flags,
|
||||
NULL,
|
||||
NULL,
|
||||
0);
|
||||
len = cert_get_name_string(data, pCertContextServer, NULL, 0);
|
||||
if(len == 0) {
|
||||
failf(data,
|
||||
"schannel: CertGetNameString() returned no "
|
||||
@ -334,12 +448,8 @@ static CURLcode verify_host(struct Curl_easy *data,
|
||||
result = CURLE_OUT_OF_MEMORY;
|
||||
goto cleanup;
|
||||
}
|
||||
actual_len = CertGetNameString(pCertContextServer,
|
||||
CERT_NAME_DNS_TYPE,
|
||||
name_flags,
|
||||
NULL,
|
||||
(LPTSTR) cert_hostname_buff,
|
||||
len);
|
||||
actual_len = cert_get_name_string(
|
||||
data, pCertContextServer, (LPTSTR)cert_hostname_buff, len);
|
||||
|
||||
/* Sanity check */
|
||||
if(actual_len != len) {
|
||||
|
Loading…
Reference in New Issue
Block a user