mirror of
https://github.com/moparisthebest/curl
synced 2024-12-23 16:48:49 -05: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;
|
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,
|
static CURLcode verify_host(struct Curl_easy *data,
|
||||||
CERT_CONTEXT *pCertContextServer,
|
CERT_CONTEXT *pCertContextServer,
|
||||||
const char * const conn_hostname)
|
const char * const conn_hostname)
|
||||||
@ -303,21 +430,8 @@ static CURLcode verify_host(struct Curl_easy *data,
|
|||||||
DWORD len = 0;
|
DWORD len = 0;
|
||||||
DWORD actual_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 */
|
/* Determine the size of the string needed for the cert hostname */
|
||||||
len = CertGetNameString(pCertContextServer,
|
len = cert_get_name_string(data, pCertContextServer, NULL, 0);
|
||||||
CERT_NAME_DNS_TYPE,
|
|
||||||
name_flags,
|
|
||||||
NULL,
|
|
||||||
NULL,
|
|
||||||
0);
|
|
||||||
if(len == 0) {
|
if(len == 0) {
|
||||||
failf(data,
|
failf(data,
|
||||||
"schannel: CertGetNameString() returned no "
|
"schannel: CertGetNameString() returned no "
|
||||||
@ -334,12 +448,8 @@ static CURLcode verify_host(struct Curl_easy *data,
|
|||||||
result = CURLE_OUT_OF_MEMORY;
|
result = CURLE_OUT_OF_MEMORY;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
actual_len = CertGetNameString(pCertContextServer,
|
actual_len = cert_get_name_string(
|
||||||
CERT_NAME_DNS_TYPE,
|
data, pCertContextServer, (LPTSTR)cert_hostname_buff, len);
|
||||||
name_flags,
|
|
||||||
NULL,
|
|
||||||
(LPTSTR) cert_hostname_buff,
|
|
||||||
len);
|
|
||||||
|
|
||||||
/* Sanity check */
|
/* Sanity check */
|
||||||
if(actual_len != len) {
|
if(actual_len != len) {
|
||||||
|
Loading…
Reference in New Issue
Block a user