Skip to content

Commit adc159c

Browse files
jayptitSeb
authored andcommitted
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 curl#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: curl#3285 Fixes curl#3284 Closes curl#10056
1 parent 4aa0a30 commit adc159c

File tree

5 files changed

+101
-22
lines changed

5 files changed

+101
-22
lines changed

docs/KNOWN_BUGS

-7
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ problems may have been fixed or changed somewhat since this was written.
2121
2.4 Secure Transport will not import PKCS#12 client certificates without a password
2222
2.5 Client cert handling with Issuer DN differs between backends
2323
2.7 Client cert (MTLS) issues with Schannel
24-
2.8 Schannel disable CURLOPT_SSL_VERIFYPEER and verify hostname
2524
2.11 Schannel TLS 1.2 handshake bug in old Windows versions
2625
2.12 FTPS with Schannel times out file list operation
2726
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.
163162

164163
See https://github.com/curl/curl/issues/3145
165164

166-
2.8 Schannel disable CURLOPT_SSL_VERIFYPEER and verify hostname
167-
168-
This seems to be a limitation in the underlying Schannel API.
169-
170-
https://github.com/curl/curl/issues/3284
171-
172165
2.11 Schannel TLS 1.2 handshake bug in old Windows versions
173166

174167
In old versions of Windows such as 7 and 8.1 the Schannel TLS 1.2 handshake

lib/vtls/schannel.c

+9-3
Original file line numberDiff line numberDiff line change
@@ -810,9 +810,9 @@ schannel_acquire_credential_handle(struct Curl_cfilter *cf,
810810

811811
SCH_CREDENTIALS credentials = { 0 };
812812
TLS_PARAMETERS tls_parameters = { 0 };
813-
CRYPTO_SETTINGS crypto_settings[4] = { 0 };
814-
UNICODE_STRING blocked_ccm_modes[1] = { 0 };
815-
UNICODE_STRING blocked_gcm_modes[1] = { 0 };
813+
CRYPTO_SETTINGS crypto_settings[4] = { { 0 } };
814+
UNICODE_STRING blocked_ccm_modes[1] = { { 0 } };
815+
UNICODE_STRING blocked_gcm_modes[1] = { { 0 } };
816816

817817
int crypto_settings_idx = 0;
818818

@@ -1634,10 +1634,16 @@ schannel_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
16341634

16351635
#ifdef HAS_MANUAL_VERIFY_API
16361636
if(conn_config->verifypeer && backend->use_manual_cred_validation) {
1637+
/* Certificate verification also verifies the hostname if verifyhost */
16371638
return Curl_verify_certificate(cf, data);
16381639
}
16391640
#endif
16401641

1642+
/* Verify the hostname manually when certificate verification is disabled,
1643+
because in that case Schannel won't verify it. */
1644+
if(!conn_config->verifypeer && conn_config->verifyhost)
1645+
return Curl_verify_host(cf, data);
1646+
16411647
return CURLE_OK;
16421648
}
16431649

lib/vtls/schannel.h

+3
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@
7676

7777
extern const struct Curl_ssl Curl_ssl_schannel;
7878

79+
CURLcode Curl_verify_host(struct Curl_cfilter *cf,
80+
struct Curl_easy *data);
81+
7982
CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
8083
struct Curl_easy *data);
8184

lib/vtls/schannel_int.h

+52
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,58 @@
4343
#define HAS_CLIENT_CERT_PATH
4444
#endif
4545

46+
#ifndef CRYPT_DECODE_NOCOPY_FLAG
47+
#define CRYPT_DECODE_NOCOPY_FLAG 0x1
48+
#endif
49+
50+
#ifndef CRYPT_DECODE_ALLOC_FLAG
51+
#define CRYPT_DECODE_ALLOC_FLAG 0x8000
52+
#endif
53+
54+
#ifndef CERT_ALT_NAME_DNS_NAME
55+
#define CERT_ALT_NAME_DNS_NAME 3
56+
#endif
57+
58+
#ifndef CERT_ALT_NAME_IP_ADDRESS
59+
#define CERT_ALT_NAME_IP_ADDRESS 8
60+
#endif
61+
62+
63+
#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
64+
/* Original mingw is missing CERT structs or they're disabled.
65+
Refer to w32api-5.0.2-mingw32-dev\include\wincrypt.h. */
66+
67+
/* !checksrc! disable TYPEDEFSTRUCT 4 */
68+
typedef struct _CERT_OTHER_NAME {
69+
LPSTR pszObjId;
70+
CRYPT_OBJID_BLOB Value;
71+
} CERT_OTHER_NAME, *PCERT_OTHER_NAME;
72+
73+
typedef struct _CERT_ALT_NAME_ENTRY {
74+
DWORD dwAltNameChoice;
75+
union {
76+
PCERT_OTHER_NAME pOtherName;
77+
LPWSTR pwszRfc822Name;
78+
LPWSTR pwszDNSName;
79+
CERT_NAME_BLOB DirectoryName;
80+
LPWSTR pwszURL;
81+
CRYPT_DATA_BLOB IPAddress;
82+
LPSTR pszRegisteredID;
83+
};
84+
} CERT_ALT_NAME_ENTRY, *PCERT_ALT_NAME_ENTRY;
85+
86+
typedef struct _CERT_ALT_NAME_INFO {
87+
DWORD cAltEntry;
88+
PCERT_ALT_NAME_ENTRY rgAltEntry;
89+
} CERT_ALT_NAME_INFO, *PCERT_ALT_NAME_INFO;
90+
91+
typedef struct _CRYPT_DECODE_PARA {
92+
DWORD cbSize;
93+
PFN_CRYPT_ALLOC pfnAlloc;
94+
PFN_CRYPT_FREE pfnFree;
95+
} CRYPT_DECODE_PARA, *PCRYPT_DECODE_PARA;
96+
#endif
97+
4698
#ifndef SCH_CREDENTIALS_VERSION
4799

48100
#define SCH_CREDENTIALS_VERSION 0x00000005

lib/vtls/schannel_verify.c

+37-12
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@
3939
#include "schannel.h"
4040
#include "schannel_int.h"
4141

42-
#ifdef HAS_MANUAL_VERIFY_API
43-
4442
#include "vtls.h"
4543
#include "vtls_int.h"
4644
#include "sendf.h"
@@ -56,6 +54,9 @@
5654

5755
#define BACKEND ((struct schannel_ssl_backend_data *)connssl->backend)
5856

57+
58+
#ifdef HAS_MANUAL_VERIFY_API
59+
5960
#define MAX_CAFILE_SIZE 1048576 /* 1 MiB */
6061
#define BEGIN_CERT "-----BEGIN CERTIFICATE-----"
6162
#define END_CERT "\n-----END CERTIFICATE-----"
@@ -330,6 +331,8 @@ static CURLcode add_certs_file_to_store(HCERTSTORE trust_store,
330331
return result;
331332
}
332333

334+
#endif /* HAS_MANUAL_VERIFY_API */
335+
333336
/*
334337
* Returns the number of characters necessary to populate all the host_names.
335338
* 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,
353356
LPTSTR current_pos = NULL;
354357
DWORD i;
355358

359+
#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
356360
/* CERT_NAME_SEARCH_ALL_NAMES_FLAG is available from Windows 8 onwards. */
357361
if(curlx_verify_windows_version(6, 2, 0, PLATFORM_WINNT,
358362
VERSION_GREATER_THAN_EQUAL)) {
359-
#ifdef CERT_NAME_SEARCH_ALL_NAMES_FLAG
360363
/* CertGetNameString will provide the 8-bit character string without
361364
* any decoding */
362365
DWORD name_flags =
@@ -368,8 +371,8 @@ static DWORD cert_get_name_string(struct Curl_easy *data,
368371
host_names,
369372
length);
370373
return actual_length;
371-
#endif
372374
}
375+
#endif
373376

374377
compute_content = host_names != NULL && length != 0;
375378

@@ -457,17 +460,34 @@ static DWORD cert_get_name_string(struct Curl_easy *data,
457460
return actual_length;
458461
}
459462

460-
static CURLcode verify_host(struct Curl_easy *data,
461-
CERT_CONTEXT *pCertContextServer,
462-
const char *conn_hostname)
463+
/* Verify the server's hostname */
464+
CURLcode Curl_verify_host(struct Curl_cfilter *cf,
465+
struct Curl_easy *data)
463466
{
467+
struct ssl_connect_data *connssl = cf->ctx;
468+
SECURITY_STATUS sspi_status;
464469
CURLcode result = CURLE_PEER_FAILED_VERIFICATION;
470+
CERT_CONTEXT *pCertContextServer = NULL;
465471
TCHAR *cert_hostname_buff = NULL;
466472
size_t cert_hostname_buff_index = 0;
473+
const char *conn_hostname = connssl->hostname;
467474
size_t hostlen = strlen(conn_hostname);
468475
DWORD len = 0;
469476
DWORD actual_len = 0;
470477

478+
sspi_status =
479+
s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
480+
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
481+
&pCertContextServer);
482+
483+
if((sspi_status != SEC_E_OK) || !pCertContextServer) {
484+
char buffer[STRERROR_LEN];
485+
failf(data, "schannel: Failed to read remote certificate context: %s",
486+
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
487+
result = CURLE_PEER_FAILED_VERIFICATION;
488+
goto cleanup;
489+
}
490+
471491
/* Determine the size of the string needed for the cert hostname */
472492
len = cert_get_name_string(data, pCertContextServer, NULL, 0);
473493
if(len == 0) {
@@ -498,10 +518,9 @@ static CURLcode verify_host(struct Curl_easy *data,
498518
goto cleanup;
499519
}
500520

501-
/* If HAVE_CERT_NAME_SEARCH_ALL_NAMES is available, the output
502-
* will contain all DNS names, where each name is null-terminated
503-
* and the last DNS name is double null-terminated. Due to this
504-
* encoding, use the length of the buffer to iterate over all names.
521+
/* cert_hostname_buff contains all DNS names, where each name is
522+
* null-terminated and the last DNS name is double null-terminated. Due to
523+
* this encoding, use the length of the buffer to iterate over all names.
505524
*/
506525
result = CURLE_PEER_FAILED_VERIFICATION;
507526
while(cert_hostname_buff_index < len &&
@@ -560,9 +579,15 @@ static CURLcode verify_host(struct Curl_easy *data,
560579
cleanup:
561580
Curl_safefree(cert_hostname_buff);
562581

582+
if(pCertContextServer)
583+
CertFreeCertificateContext(pCertContextServer);
584+
563585
return result;
564586
}
565587

588+
589+
#ifdef HAS_MANUAL_VERIFY_API
590+
/* Verify the server's certificate and hostname */
566591
CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
567592
struct Curl_easy *data)
568593
{
@@ -721,7 +746,7 @@ CURLcode Curl_verify_certificate(struct Curl_cfilter *cf,
721746

722747
if(result == CURLE_OK) {
723748
if(conn_config->verifyhost) {
724-
result = verify_host(data, pCertContextServer, connssl->hostname);
749+
result = Curl_verify_host(cf, data);
725750
}
726751
}
727752

0 commit comments

Comments
 (0)