#include "curl_setup.h"
#ifdef USE_SCHANNEL
#define EXPOSE_SCHANNEL_INTERNAL_STRUCTS
#ifndef USE_WINDOWS_SSPI
# error "Can't compile SCHANNEL support without SSPI."
#endif
#include "schannel.h"
#include "vtls.h"
#include "sendf.h"
#include "connect.h"
#include "strerror.h"
#include "select.h"
#include "inet_pton.h"
#include "curl_multibyte.h"
#include "warnless.h"
#include "x509asn1.h"
#include "curl_printf.h"
#include "system_win32.h"
#include "curl_memory.h"
#include "memdebug.h"
#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_)
# define HAS_ALPN 1
#endif
#ifndef UNISP_NAME_A
#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider"
#endif
#ifndef UNISP_NAME_W
#define UNISP_NAME_W L"Microsoft Unified Security Protocol Provider"
#endif
#ifndef UNISP_NAME
#ifdef UNICODE
#define UNISP_NAME UNISP_NAME_W
#else
#define UNISP_NAME UNISP_NAME_A
#endif
#endif
#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX)
#define HAS_CLIENT_CERT_PATH
#endif
#ifdef HAS_CLIENT_CERT_PATH
#ifdef UNICODE
#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_W
#else
#define CURL_CERT_STORE_PROV_SYSTEM CERT_STORE_PROV_SYSTEM_A
#endif
#endif
#ifndef SP_PROT_SSL2_CLIENT
#define SP_PROT_SSL2_CLIENT 0x00000008
#endif
#ifndef SP_PROT_SSL3_CLIENT
#define SP_PROT_SSL3_CLIENT 0x00000008
#endif
#ifndef SP_PROT_TLS1_CLIENT
#define SP_PROT_TLS1_CLIENT 0x00000080
#endif
#ifndef SP_PROT_TLS1_0_CLIENT
#define SP_PROT_TLS1_0_CLIENT SP_PROT_TLS1_CLIENT
#endif
#ifndef SP_PROT_TLS1_1_CLIENT
#define SP_PROT_TLS1_1_CLIENT 0x00000200
#endif
#ifndef SP_PROT_TLS1_2_CLIENT
#define SP_PROT_TLS1_2_CLIENT 0x00000800
#endif
#ifndef SECBUFFER_ALERT
#define SECBUFFER_ALERT 17
#endif
#define CURL_SCHANNEL_BUFFER_INIT_SIZE 4096
#define CURL_SCHANNEL_BUFFER_FREE_SIZE 1024
#define CERT_THUMBPRINT_STR_LEN 40
#define CERT_THUMBPRINT_DATA_LEN 20
#ifndef CALG_SHA_256
# define CALG_SHA_256 0x0000800c
#endif
#define BACKEND connssl->backend
static Curl_recv schannel_recv;
static Curl_send schannel_send;
static CURLcode pkp_pin_peer_pubkey(struct connectdata *conn, int sockindex,
const char *pinnedpubkey);
static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType,
void *BufDataPtr, unsigned long BufByteSize)
{
buffer->cbBuffer = BufByteSize;
buffer->BufferType = BufType;
buffer->pvBuffer = BufDataPtr;
}
static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr,
unsigned long NumArrElem)
{
desc->ulVersion = SECBUFFER_VERSION;
desc->pBuffers = BufArr;
desc->cBuffers = NumArrElem;
}
static CURLcode
set_ssl_version_min_max(SCHANNEL_CRED *schannel_cred, struct connectdata *conn)
{
struct Curl_easy *data = conn->data;
long ssl_version = SSL_CONN_CONFIG(version);
long ssl_version_max = SSL_CONN_CONFIG(version_max);
long i = ssl_version;
switch(ssl_version_max) {
case CURL_SSLVERSION_MAX_NONE:
case CURL_SSLVERSION_MAX_DEFAULT:
ssl_version_max = CURL_SSLVERSION_MAX_TLSv1_2;
break;
}
for(; i <= (ssl_version_max >> 16); ++i) {
switch(i) {
case CURL_SSLVERSION_TLSv1_0:
schannel_cred->grbitEnabledProtocols |= SP_PROT_TLS1_0_CLIENT;
break;
case CURL_SSLVERSION_TLSv1_1:
schannel_cred->grbitEnabledProtocols |= SP_PROT_TLS1_1_CLIENT;
break;
case CURL_SSLVERSION_TLSv1_2:
schannel_cred->grbitEnabledProtocols |= SP_PROT_TLS1_2_CLIENT;
break;
case CURL_SSLVERSION_TLSv1_3:
failf(data, "schannel: TLS 1.3 is not yet supported");
return CURLE_SSL_CONNECT_ERROR;
}
}
return CURLE_OK;
}
#define LONGEST_ALG_ID 32
#define CIPHEROPTION(X) \
if(strcmp(#X, tmp) == 0) \
return X
static int
get_alg_id_by_name(char *name)
{
char tmp[LONGEST_ALG_ID] = { 0 };
char *nameEnd = strchr(name, ':');
size_t n = nameEnd ? min((size_t)(nameEnd - name), LONGEST_ALG_ID - 1) : \
min(strlen(name), LONGEST_ALG_ID - 1);
strncpy(tmp, name, n);
tmp[n] = 0;
CIPHEROPTION(CALG_MD2);
CIPHEROPTION(CALG_MD4);
CIPHEROPTION(CALG_MD5);
CIPHEROPTION(CALG_SHA);
CIPHEROPTION(CALG_SHA1);
CIPHEROPTION(CALG_MAC);
CIPHEROPTION(CALG_RSA_SIGN);
CIPHEROPTION(CALG_DSS_SIGN);
#ifdef CALG_NO_SIGN
CIPHEROPTION(CALG_NO_SIGN);
#endif
CIPHEROPTION(CALG_RSA_KEYX);
CIPHEROPTION(CALG_DES);
#ifdef CALG_3DES_112
CIPHEROPTION(CALG_3DES_112);
#endif
CIPHEROPTION(CALG_3DES);
CIPHEROPTION(CALG_DESX);
CIPHEROPTION(CALG_RC2);
CIPHEROPTION(CALG_RC4);
CIPHEROPTION(CALG_SEAL);
#ifdef CALG_DH_SF
CIPHEROPTION(CALG_DH_SF);
#endif
CIPHEROPTION(CALG_DH_EPHEM);
#ifdef CALG_AGREEDKEY_ANY
CIPHEROPTION(CALG_AGREEDKEY_ANY);
#endif
#ifdef CALG_HUGHES_MD5
CIPHEROPTION(CALG_HUGHES_MD5);
#endif
CIPHEROPTION(CALG_SKIPJACK);
#ifdef CALG_TEK
CIPHEROPTION(CALG_TEK);
#endif
CIPHEROPTION(CALG_CYLINK_MEK);
CIPHEROPTION(CALG_SSL3_SHAMD5);
#ifdef CALG_SSL3_MASTER
CIPHEROPTION(CALG_SSL3_MASTER);
#endif
#ifdef CALG_SCHANNEL_MASTER_HASH
CIPHEROPTION(CALG_SCHANNEL_MASTER_HASH);
#endif
#ifdef CALG_SCHANNEL_MAC_KEY
CIPHEROPTION(CALG_SCHANNEL_MAC_KEY);
#endif
#ifdef CALG_SCHANNEL_ENC_KEY
CIPHEROPTION(CALG_SCHANNEL_ENC_KEY);
#endif
#ifdef CALG_PCT1_MASTER
CIPHEROPTION(CALG_PCT1_MASTER);
#endif
#ifdef CALG_SSL2_MASTER
CIPHEROPTION(CALG_SSL2_MASTER);
#endif
#ifdef CALG_TLS1_MASTER
CIPHEROPTION(CALG_TLS1_MASTER);
#endif
#ifdef CALG_RC5
CIPHEROPTION(CALG_RC5);
#endif
#ifdef CALG_HMAC
CIPHEROPTION(CALG_HMAC);
#endif
#if !defined(__W32API_MAJOR_VERSION) || \
!defined(__W32API_MINOR_VERSION) || \
defined(__MINGW64_VERSION_MAJOR) || \
(__W32API_MAJOR_VERSION > 5) || \
((__W32API_MAJOR_VERSION == 5) && (__W32API_MINOR_VERSION > 0))
CIPHEROPTION(CALG_TLS1PRF);
#endif
#ifdef CALG_HASH_REPLACE_OWF
CIPHEROPTION(CALG_HASH_REPLACE_OWF);
#endif
#ifdef CALG_AES_128
CIPHEROPTION(CALG_AES_128);
#endif
#ifdef CALG_AES_192
CIPHEROPTION(CALG_AES_192);
#endif
#ifdef CALG_AES_256
CIPHEROPTION(CALG_AES_256);
#endif
#ifdef CALG_AES
CIPHEROPTION(CALG_AES);
#endif
#ifdef CALG_SHA_256
CIPHEROPTION(CALG_SHA_256);
#endif
#ifdef CALG_SHA_384
CIPHEROPTION(CALG_SHA_384);
#endif
#ifdef CALG_SHA_512
CIPHEROPTION(CALG_SHA_512);
#endif
#ifdef CALG_ECDH
CIPHEROPTION(CALG_ECDH);
#endif
#ifdef CALG_ECMQV
CIPHEROPTION(CALG_ECMQV);
#endif
#ifdef CALG_ECDSA
CIPHEROPTION(CALG_ECDSA);
#endif
#ifdef CALG_ECDH_EPHEM
CIPHEROPTION(CALG_ECDH_EPHEM);
#endif
return 0;
}
static CURLcode
set_ssl_ciphers(SCHANNEL_CRED *schannel_cred, char *ciphers)
{
char *startCur = ciphers;
int algCount = 0;
static ALG_ID algIds[45];
while(startCur && (0 != *startCur) && (algCount < 45)) {
long alg = strtol(startCur, 0, 0);
if(!alg)
alg = get_alg_id_by_name(startCur);
if(alg)
algIds[algCount++] = alg;
else
return CURLE_SSL_CIPHER;
startCur = strchr(startCur, ':');
if(startCur)
startCur++;
}
schannel_cred->palgSupportedAlgs = algIds;
schannel_cred->cSupportedAlgs = algCount;
return CURLE_OK;
}
#ifdef HAS_CLIENT_CERT_PATH
static CURLcode
get_cert_location(TCHAR *path, DWORD *store_name, TCHAR **store_path,
TCHAR **thumbprint)
{
TCHAR *sep;
TCHAR *store_path_start;
size_t store_name_len;
sep = _tcschr(path, TEXT('\\'));
if(sep == NULL)
return CURLE_SSL_CERTPROBLEM;
store_name_len = sep - path;
if(_tcsnccmp(path, TEXT("CurrentUser"), store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_CURRENT_USER;
else if(_tcsnccmp(path, TEXT("LocalMachine"), store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE;
else if(_tcsnccmp(path, TEXT("CurrentService"), store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_CURRENT_SERVICE;
else if(_tcsnccmp(path, TEXT("Services"), store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_SERVICES;
else if(_tcsnccmp(path, TEXT("Users"), store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_USERS;
else if(_tcsnccmp(path, TEXT("CurrentUserGroupPolicy"),
store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_CURRENT_USER_GROUP_POLICY;
else if(_tcsnccmp(path, TEXT("LocalMachineGroupPolicy"),
store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_GROUP_POLICY;
else if(_tcsnccmp(path, TEXT("LocalMachineEnterprise"),
store_name_len) == 0)
*store_name = CERT_SYSTEM_STORE_LOCAL_MACHINE_ENTERPRISE;
else
return CURLE_SSL_CERTPROBLEM;
store_path_start = sep + 1;
sep = _tcschr(store_path_start, TEXT('\\'));
if(sep == NULL)
return CURLE_SSL_CERTPROBLEM;
*sep = TEXT('\0');
*store_path = _tcsdup(store_path_start);
*sep = TEXT('\\');
if(*store_path == NULL)
return CURLE_OUT_OF_MEMORY;
*thumbprint = sep + 1;
if(_tcslen(*thumbprint) != CERT_THUMBPRINT_STR_LEN)
return CURLE_SSL_CERTPROBLEM;
return CURLE_OK;
}
#endif
static CURLcode
schannel_connect_step1(struct connectdata *conn, int sockindex)
{
ssize_t written = -1;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
SecBuffer outbuf;
SecBufferDesc outbuf_desc;
SecBuffer inbuf;
SecBufferDesc inbuf_desc;
#ifdef HAS_ALPN
unsigned char alpn_buffer[128];
#endif
SCHANNEL_CRED schannel_cred;
PCCERT_CONTEXT client_certs[1] = { NULL };
SECURITY_STATUS sspi_status = SEC_E_OK;
struct curl_schannel_cred *old_cred = NULL;
struct in_addr addr;
#ifdef ENABLE_IPV6
struct in6_addr addr6;
#endif
TCHAR *host_name;
CURLcode result;
char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
DEBUGF(infof(data,
"schannel: SSL/TLS connection with %s port %hu (step 1/3)\n",
hostname, conn->remote_port));
if(Curl_verify_windows_version(5, 1, PLATFORM_WINNT,
VERSION_LESS_THAN_EQUAL)) {
infof(data, "schannel: Windows version is old and may not be able to "
"connect to some servers due to lack of SNI, algorithms, etc.\n");
}
#ifdef HAS_ALPN
BACKEND->use_alpn = conn->bits.tls_enable_alpn &&
!GetProcAddress(GetModuleHandleA("ntdll"),
"wine_get_version") &&
Curl_verify_windows_version(6, 3, PLATFORM_WINNT,
VERSION_GREATER_THAN_EQUAL);
#else
BACKEND->use_alpn = false;
#endif
#ifdef _WIN32_WCE
#ifdef HAS_MANUAL_VERIFY_API
BACKEND->use_manual_cred_validation = true;
#else
#error "compiler too old to support requisite manual cert verify for Win CE"
#endif
#else
#ifdef HAS_MANUAL_VERIFY_API
if(SSL_CONN_CONFIG(CAfile)) {
if(Curl_verify_windows_version(6, 1, PLATFORM_WINNT,
VERSION_GREATER_THAN_EQUAL)) {
BACKEND->use_manual_cred_validation = true;
}
else {
failf(data, "schannel: this version of Windows is too old to support "
"certificate verification via CA bundle file.");
return CURLE_SSL_CACERT_BADFILE;
}
}
else
BACKEND->use_manual_cred_validation = false;
#else
if(SSL_CONN_CONFIG(CAfile)) {
failf(data, "schannel: CA cert support not built in");
return CURLE_NOT_BUILT_IN;
}
#endif
#endif
BACKEND->cred = NULL;
if(SSL_SET_OPTION(primary.sessionid)) {
Curl_ssl_sessionid_lock(conn);
if(!Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL, sockindex)) {
BACKEND->cred = old_cred;
DEBUGF(infof(data, "schannel: re-using existing credential handle\n"));
BACKEND->cred->refcount++;
DEBUGF(infof(data,
"schannel: incremented credential handle refcount = %d\n",
BACKEND->cred->refcount));
}
Curl_ssl_sessionid_unlock(conn);
}
if(!BACKEND->cred) {
memset(&schannel_cred, 0, sizeof(schannel_cred));
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
if(conn->ssl_config.verifypeer) {
#ifdef HAS_MANUAL_VERIFY_API
if(BACKEND->use_manual_cred_validation)
schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION;
else
#endif
schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION;
if(data->set.ssl.no_revoke) {
schannel_cred.dwFlags |= SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
SCH_CRED_IGNORE_REVOCATION_OFFLINE;
DEBUGF(infof(data, "schannel: disabled server certificate revocation "
"checks\n"));
}
else {
schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN;
DEBUGF(infof(data,
"schannel: checking server certificate revocation\n"));
}
}
else {
schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION |
SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
SCH_CRED_IGNORE_REVOCATION_OFFLINE;
DEBUGF(infof(data,
"schannel: disabled server cert revocation checks\n"));
}
if(!conn->ssl_config.verifyhost) {
schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
DEBUGF(infof(data, "schannel: verifyhost setting prevents Schannel from "
"comparing the supplied target name with the subject "
"names in server certificates.\n"));
}
switch(conn->ssl_config.version) {
case CURL_SSLVERSION_DEFAULT:
case CURL_SSLVERSION_TLSv1:
schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT |
SP_PROT_TLS1_1_CLIENT |
SP_PROT_TLS1_2_CLIENT;
break;
case CURL_SSLVERSION_TLSv1_0:
case CURL_SSLVERSION_TLSv1_1:
case CURL_SSLVERSION_TLSv1_2:
case CURL_SSLVERSION_TLSv1_3:
{
result = set_ssl_version_min_max(&schannel_cred, conn);
if(result != CURLE_OK)
return result;
break;
}
case CURL_SSLVERSION_SSLv3:
schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
break;
case CURL_SSLVERSION_SSLv2:
schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
break;
default:
failf(data, "Unrecognized parameter passed via CURLOPT_SSLVERSION");
return CURLE_SSL_CONNECT_ERROR;
}
if(SSL_CONN_CONFIG(cipher_list)) {
result = set_ssl_ciphers(&schannel_cred, SSL_CONN_CONFIG(cipher_list));
if(CURLE_OK != result) {
failf(data, "Unable to set ciphers to passed via SSL_CONN_CONFIG");
return result;
}
}
#ifdef HAS_CLIENT_CERT_PATH
if(data->set.ssl.cert) {
DWORD cert_store_name;
TCHAR *cert_store_path;
TCHAR *cert_thumbprint_str;
CRYPT_HASH_BLOB cert_thumbprint;
BYTE cert_thumbprint_data[CERT_THUMBPRINT_DATA_LEN];
HCERTSTORE cert_store;
TCHAR *cert_path = Curl_convert_UTF8_to_tchar(data->set.ssl.cert);
if(!cert_path)
return CURLE_OUT_OF_MEMORY;
result = get_cert_location(cert_path, &cert_store_name,
&cert_store_path, &cert_thumbprint_str);
if(result != CURLE_OK) {
failf(data, "schannel: Failed to get certificate location for %s",
cert_path);
Curl_unicodefree(cert_path);
return result;
}
cert_store =
CertOpenStore(CURL_CERT_STORE_PROV_SYSTEM, 0,
(HCRYPTPROV)NULL,
CERT_STORE_OPEN_EXISTING_FLAG | cert_store_name,
cert_store_path);
if(!cert_store) {
failf(data, "schannel: Failed to open cert store %x %s, "
"last error is %x",
cert_store_name, cert_store_path, GetLastError());
free(cert_store_path);
Curl_unicodefree(cert_path);
return CURLE_SSL_CERTPROBLEM;
}
free(cert_store_path);
cert_thumbprint.pbData = cert_thumbprint_data;
cert_thumbprint.cbData = CERT_THUMBPRINT_DATA_LEN;
if(!CryptStringToBinary(cert_thumbprint_str, CERT_THUMBPRINT_STR_LEN,
CRYPT_STRING_HEX,
cert_thumbprint_data, &cert_thumbprint.cbData,
NULL, NULL)) {
Curl_unicodefree(cert_path);
return CURLE_SSL_CERTPROBLEM;
}
client_certs[0] = CertFindCertificateInStore(
cert_store, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,
CERT_FIND_HASH, &cert_thumbprint, NULL);
Curl_unicodefree(cert_path);
if(client_certs[0]) {
schannel_cred.cCreds = 1;
schannel_cred.paCred = client_certs;
}
else {
return CURLE_SSL_CERTPROBLEM;
}
CertCloseStore(cert_store, 0);
}
#else
if(data->set.ssl.cert) {
failf(data, "schannel: client cert support not built in");
return CURLE_NOT_BUILT_IN;
}
#endif
BACKEND->cred = (struct curl_schannel_cred *)
calloc(1, sizeof(struct curl_schannel_cred));
if(!BACKEND->cred) {
failf(data, "schannel: unable to allocate memory");
if(client_certs[0])
CertFreeCertificateContext(client_certs[0]);
return CURLE_OUT_OF_MEMORY;
}
BACKEND->cred->refcount = 1;
sspi_status =
s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
SECPKG_CRED_OUTBOUND, NULL,
&schannel_cred, NULL, NULL,
&BACKEND->cred->cred_handle,
&BACKEND->cred->time_stamp);
if(client_certs[0])
CertFreeCertificateContext(client_certs[0]);
if(sspi_status != SEC_E_OK) {
char buffer[STRERROR_LEN];
failf(data, "schannel: AcquireCredentialsHandle failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
Curl_safefree(BACKEND->cred);
switch(sspi_status) {
case SEC_E_INSUFFICIENT_MEMORY:
return CURLE_OUT_OF_MEMORY;
case SEC_E_NO_CREDENTIALS:
case SEC_E_SECPKG_NOT_FOUND:
case SEC_E_NOT_OWNER:
case SEC_E_UNKNOWN_CREDENTIALS:
case SEC_E_INTERNAL_ERROR:
default:
return CURLE_SSL_CONNECT_ERROR;
}
}
}
if(Curl_inet_pton(AF_INET, hostname, &addr)
#ifdef ENABLE_IPV6
|| Curl_inet_pton(AF_INET6, hostname, &addr6)
#endif
) {
infof(data, "schannel: using IP address, SNI is not supported by OS.\n");
}
#ifdef HAS_ALPN
if(BACKEND->use_alpn) {
int cur = 0;
int list_start_index = 0;
unsigned int *extension_len = NULL;
unsigned short* list_len = NULL;
extension_len = (unsigned int *)(&alpn_buffer[cur]);
cur += sizeof(unsigned int);
*(unsigned int *)&alpn_buffer[cur] =
SecApplicationProtocolNegotiationExt_ALPN;
cur += sizeof(unsigned int);
list_len = (unsigned short*)(&alpn_buffer[cur]);
cur += sizeof(unsigned short);
list_start_index = cur;
#ifdef USE_NGHTTP2
if(data->set.httpversion >= CURL_HTTP_VERSION_2) {
memcpy(&alpn_buffer[cur], NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN);
cur += NGHTTP2_PROTO_ALPN_LEN;
infof(data, "schannel: ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID);
}
#endif
alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH;
memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH);
cur += ALPN_HTTP_1_1_LENGTH;
infof(data, "schannel: ALPN, offering %s\n", ALPN_HTTP_1_1);
*list_len = curlx_uitous(cur - list_start_index);
*extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short);
InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur);
InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
}
else {
InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
}
#else
InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&inbuf_desc, &inbuf, 1);
#endif
InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
BACKEND->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
ISC_REQ_STREAM;
BACKEND->ctxt = (struct curl_schannel_ctxt *)
calloc(1, sizeof(struct curl_schannel_ctxt));
if(!BACKEND->ctxt) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OUT_OF_MEMORY;
}
host_name = Curl_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
sspi_status = s_pSecFn->InitializeSecurityContext(
&BACKEND->cred->cred_handle, NULL, host_name, BACKEND->req_flags, 0, 0,
(BACKEND->use_alpn ? &inbuf_desc : NULL),
0, &BACKEND->ctxt->ctxt_handle,
&outbuf_desc, &BACKEND->ret_flags, &BACKEND->ctxt->time_stamp);
Curl_unicodefree(host_name);
if(sspi_status != SEC_I_CONTINUE_NEEDED) {
char buffer[STRERROR_LEN];
Curl_safefree(BACKEND->ctxt);
switch(sspi_status) {
case SEC_E_INSUFFICIENT_MEMORY:
failf(data, "schannel: initial InitializeSecurityContext failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
return CURLE_OUT_OF_MEMORY;
case SEC_E_WRONG_PRINCIPAL:
failf(data, "schannel: SNI or certificate check failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
return CURLE_PEER_FAILED_VERIFICATION;
default:
failf(data, "schannel: initial InitializeSecurityContext failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
return CURLE_SSL_CONNECT_ERROR;
}
}
DEBUGF(infof(data, "schannel: sending initial handshake data: "
"sending %lu bytes...\n", outbuf.cbBuffer));
result = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
outbuf.cbBuffer, &written);
s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) {
failf(data, "schannel: failed to send initial handshake data: "
"sent %zd of %lu bytes", written, outbuf.cbBuffer);
return CURLE_SSL_CONNECT_ERROR;
}
DEBUGF(infof(data, "schannel: sent initial handshake data: "
"sent %zd bytes\n", written));
BACKEND->recv_unrecoverable_err = CURLE_OK;
BACKEND->recv_sspi_close_notify = false;
BACKEND->recv_connection_closed = false;
BACKEND->encdata_is_incomplete = false;
connssl->connecting_state = ssl_connect_2;
return CURLE_OK;
}
static CURLcode
schannel_connect_step2(struct connectdata *conn, int sockindex)
{
int i;
ssize_t nread = -1, written = -1;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
unsigned char *reallocated_buffer;
size_t reallocated_length;
SecBuffer outbuf[3];
SecBufferDesc outbuf_desc;
SecBuffer inbuf[2];
SecBufferDesc inbuf_desc;
SECURITY_STATUS sspi_status = SEC_E_OK;
TCHAR *host_name;
CURLcode result;
bool doread;
char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
const char *pubkey_ptr;
doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
DEBUGF(infof(data,
"schannel: SSL/TLS connection with %s port %hu (step 2/3)\n",
hostname, conn->remote_port));
if(!BACKEND->cred || !BACKEND->ctxt)
return CURLE_SSL_CONNECT_ERROR;
if(BACKEND->decdata_buffer == NULL) {
BACKEND->decdata_offset = 0;
BACKEND->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
BACKEND->decdata_buffer = malloc(BACKEND->decdata_length);
if(BACKEND->decdata_buffer == NULL) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OUT_OF_MEMORY;
}
}
if(BACKEND->encdata_buffer == NULL) {
BACKEND->encdata_is_incomplete = false;
BACKEND->encdata_offset = 0;
BACKEND->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
BACKEND->encdata_buffer = malloc(BACKEND->encdata_length);
if(BACKEND->encdata_buffer == NULL) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OUT_OF_MEMORY;
}
}
if(BACKEND->encdata_length - BACKEND->encdata_offset <
CURL_SCHANNEL_BUFFER_FREE_SIZE) {
reallocated_length = BACKEND->encdata_offset +
CURL_SCHANNEL_BUFFER_FREE_SIZE;
reallocated_buffer = realloc(BACKEND->encdata_buffer,
reallocated_length);
if(reallocated_buffer == NULL) {
failf(data, "schannel: unable to re-allocate memory");
return CURLE_OUT_OF_MEMORY;
}
else {
BACKEND->encdata_buffer = reallocated_buffer;
BACKEND->encdata_length = reallocated_length;
}
}
for(;;) {
if(doread) {
result = Curl_read_plain(conn->sock[sockindex],
(char *) (BACKEND->encdata_buffer +
BACKEND->encdata_offset),
BACKEND->encdata_length -
BACKEND->encdata_offset,
&nread);
if(result == CURLE_AGAIN) {
if(connssl->connecting_state != ssl_connect_2_writing)
connssl->connecting_state = ssl_connect_2_reading;
DEBUGF(infof(data, "schannel: failed to receive handshake, "
"need more data\n"));
return CURLE_OK;
}
else if((result != CURLE_OK) || (nread == 0)) {
failf(data, "schannel: failed to receive handshake, "
"SSL/TLS connection failed");
return CURLE_SSL_CONNECT_ERROR;
}
BACKEND->encdata_offset += nread;
BACKEND->encdata_is_incomplete = false;
DEBUGF(infof(data, "schannel: encrypted data got %zd\n", nread));
}
DEBUGF(infof(data,
"schannel: encrypted data buffer: offset %zu length %zu\n",
BACKEND->encdata_offset, BACKEND->encdata_length));
InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(BACKEND->encdata_offset),
curlx_uztoul(BACKEND->encdata_offset));
InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&inbuf_desc, inbuf, 2);
InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
InitSecBuffer(&outbuf[2], SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&outbuf_desc, outbuf, 3);
if(inbuf[0].pvBuffer == NULL) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OUT_OF_MEMORY;
}
memcpy(inbuf[0].pvBuffer, BACKEND->encdata_buffer,
BACKEND->encdata_offset);
host_name = Curl_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
sspi_status = s_pSecFn->InitializeSecurityContext(
&BACKEND->cred->cred_handle, &BACKEND->ctxt->ctxt_handle,
host_name, BACKEND->req_flags, 0, 0, &inbuf_desc, 0, NULL,
&outbuf_desc, &BACKEND->ret_flags, &BACKEND->ctxt->time_stamp);
Curl_unicodefree(host_name);
Curl_safefree(inbuf[0].pvBuffer);
if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
BACKEND->encdata_is_incomplete = true;
connssl->connecting_state = ssl_connect_2_reading;
DEBUGF(infof(data,
"schannel: received incomplete message, need more data\n"));
return CURLE_OK;
}
if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS &&
!(BACKEND->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) {
BACKEND->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
connssl->connecting_state = ssl_connect_2_writing;
DEBUGF(infof(data,
"schannel: a client certificate has been requested\n"));
return CURLE_OK;
}
if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
for(i = 0; i < 3; i++) {
if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
DEBUGF(infof(data, "schannel: sending next handshake data: "
"sending %lu bytes...\n", outbuf[i].cbBuffer));
result = Curl_write_plain(conn, conn->sock[sockindex],
outbuf[i].pvBuffer, outbuf[i].cbBuffer,
&written);
if((result != CURLE_OK) ||
(outbuf[i].cbBuffer != (size_t) written)) {
failf(data, "schannel: failed to send next handshake data: "
"sent %zd of %lu bytes", written, outbuf[i].cbBuffer);
return CURLE_SSL_CONNECT_ERROR;
}
}
if(outbuf[i].pvBuffer != NULL) {
s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer);
}
}
}
else {
char buffer[STRERROR_LEN];
switch(sspi_status) {
case SEC_E_INSUFFICIENT_MEMORY:
failf(data, "schannel: next InitializeSecurityContext failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
return CURLE_OUT_OF_MEMORY;
case SEC_E_WRONG_PRINCIPAL:
failf(data, "schannel: SNI or certificate check failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
return CURLE_PEER_FAILED_VERIFICATION;
default:
failf(data, "schannel: next InitializeSecurityContext failed: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
return CURLE_SSL_CONNECT_ERROR;
}
}
if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
DEBUGF(infof(data, "schannel: encrypted data length: %lu\n",
inbuf[1].cbBuffer));
if(BACKEND->encdata_offset > inbuf[1].cbBuffer) {
memmove(BACKEND->encdata_buffer,
(BACKEND->encdata_buffer + BACKEND->encdata_offset) -
inbuf[1].cbBuffer, inbuf[1].cbBuffer);
BACKEND->encdata_offset = inbuf[1].cbBuffer;
if(sspi_status == SEC_I_CONTINUE_NEEDED) {
doread = FALSE;
continue;
}
}
}
else {
BACKEND->encdata_offset = 0;
}
break;
}
if(sspi_status == SEC_I_CONTINUE_NEEDED) {
connssl->connecting_state = ssl_connect_2_reading;
return CURLE_OK;
}
if(sspi_status == SEC_E_OK) {
connssl->connecting_state = ssl_connect_3;
DEBUGF(infof(data, "schannel: SSL/TLS handshake complete\n"));
}
pubkey_ptr = SSL_IS_PROXY() ?
data->set.str[STRING_SSL_PINNEDPUBLICKEY_PROXY] :
data->set.str[STRING_SSL_PINNEDPUBLICKEY_ORIG];
if(pubkey_ptr) {
result = pkp_pin_peer_pubkey(conn, sockindex, pubkey_ptr);
if(result) {
failf(data, "SSL: public key does not match pinned public key!");
return result;
}
}
#ifdef HAS_MANUAL_VERIFY_API
if(conn->ssl_config.verifypeer && BACKEND->use_manual_cred_validation) {
return Curl_verify_certificate(conn, sockindex);
}
#endif
return CURLE_OK;
}
static bool
valid_cert_encoding(const CERT_CONTEXT *cert_context)
{
return (cert_context != NULL) &&
((cert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) &&
(cert_context->pbCertEncoded != NULL) &&
(cert_context->cbCertEncoded > 0);
}
typedef bool(*Read_crt_func)(const CERT_CONTEXT *ccert_context, void *arg);
static void
traverse_cert_store(const CERT_CONTEXT *context, Read_crt_func func,
void *arg)
{
const CERT_CONTEXT *current_context = NULL;
bool should_continue = true;
while(should_continue &&
(current_context = CertEnumCertificatesInStore(
context->hCertStore,
current_context)) != NULL)
should_continue = func(current_context, arg);
if(current_context)
CertFreeCertificateContext(current_context);
}
static bool
cert_counter_callback(const CERT_CONTEXT *ccert_context, void *certs_count)
{
if(valid_cert_encoding(ccert_context))
(*(int *)certs_count)++;
return true;
}
struct Adder_args
{
struct connectdata *conn;
CURLcode result;
int idx;
};
static bool
add_cert_to_certinfo(const CERT_CONTEXT *ccert_context, void *raw_arg)
{
struct Adder_args *args = (struct Adder_args*)raw_arg;
args->result = CURLE_OK;
if(valid_cert_encoding(ccert_context)) {
const char *beg = (const char *) ccert_context->pbCertEncoded;
const char *end = beg + ccert_context->cbCertEncoded;
args->result = Curl_extract_certinfo(args->conn, (args->idx)++, beg, end);
}
return args->result == CURLE_OK;
}
static CURLcode
schannel_connect_step3(struct connectdata *conn, int sockindex)
{
CURLcode result = CURLE_OK;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
SECURITY_STATUS sspi_status = SEC_E_OK;
CERT_CONTEXT *ccert_context = NULL;
#ifdef DEBUGBUILD
const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
#endif
#ifdef HAS_ALPN
SecPkgContext_ApplicationProtocol alpn_result;
#endif
DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
DEBUGF(infof(data,
"schannel: SSL/TLS connection with %s port %hu (step 3/3)\n",
hostname, conn->remote_port));
if(!BACKEND->cred)
return CURLE_SSL_CONNECT_ERROR;
if(BACKEND->ret_flags != BACKEND->req_flags) {
if(!(BACKEND->ret_flags & ISC_RET_SEQUENCE_DETECT))
failf(data, "schannel: failed to setup sequence detection");
if(!(BACKEND->ret_flags & ISC_RET_REPLAY_DETECT))
failf(data, "schannel: failed to setup replay detection");
if(!(BACKEND->ret_flags & ISC_RET_CONFIDENTIALITY))
failf(data, "schannel: failed to setup confidentiality");
if(!(BACKEND->ret_flags & ISC_RET_ALLOCATED_MEMORY))
failf(data, "schannel: failed to setup memory allocation");
if(!(BACKEND->ret_flags & ISC_RET_STREAM))
failf(data, "schannel: failed to setup stream orientation");
return CURLE_SSL_CONNECT_ERROR;
}
#ifdef HAS_ALPN
if(BACKEND->use_alpn) {
sspi_status = s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result);
if(sspi_status != SEC_E_OK) {
failf(data, "schannel: failed to retrieve ALPN result");
return CURLE_SSL_CONNECT_ERROR;
}
if(alpn_result.ProtoNegoStatus ==
SecApplicationProtocolNegotiationStatus_Success) {
infof(data, "schannel: ALPN, server accepted to use %.*s\n",
alpn_result.ProtocolIdSize, alpn_result.ProtocolId);
#ifdef USE_NGHTTP2
if(alpn_result.ProtocolIdSize == NGHTTP2_PROTO_VERSION_ID_LEN &&
!memcmp(NGHTTP2_PROTO_VERSION_ID, alpn_result.ProtocolId,
NGHTTP2_PROTO_VERSION_ID_LEN)) {
conn->negnpn = CURL_HTTP_VERSION_2;
}
else
#endif
if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH &&
!memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId,
ALPN_HTTP_1_1_LENGTH)) {
conn->negnpn = CURL_HTTP_VERSION_1_1;
}
}
else
infof(data, "ALPN, server did not agree to a protocol\n");
}
#endif
if(SSL_SET_OPTION(primary.sessionid)) {
bool incache;
struct curl_schannel_cred *old_cred = NULL;
Curl_ssl_sessionid_lock(conn);
incache = !(Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL,
sockindex));
if(incache) {
if(old_cred != BACKEND->cred) {
DEBUGF(infof(data,
"schannel: old credential handle is stale, removing\n"));
Curl_ssl_delsessionid(conn, (void *)old_cred);
incache = FALSE;
}
}
if(!incache) {
result = Curl_ssl_addsessionid(conn, (void *)BACKEND->cred,
sizeof(struct curl_schannel_cred),
sockindex);
if(result) {
Curl_ssl_sessionid_unlock(conn);
failf(data, "schannel: failed to store credential handle");
return result;
}
else {
BACKEND->cred->refcount++;
DEBUGF(infof(data,
"schannel: stored credential handle in session cache\n"));
}
}
Curl_ssl_sessionid_unlock(conn);
}
if(data->set.ssl.certinfo) {
int certs_count = 0;
sspi_status = s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
SECPKG_ATTR_REMOTE_CERT_CONTEXT, &ccert_context);
if((sspi_status != SEC_E_OK) || (ccert_context == NULL)) {
failf(data, "schannel: failed to retrieve remote cert context");
return CURLE_PEER_FAILED_VERIFICATION;
}
traverse_cert_store(ccert_context, cert_counter_callback, &certs_count);
result = Curl_ssl_init_certinfo(data, certs_count);
if(!result) {
struct Adder_args args;
args.conn = conn;
args.idx = 0;
traverse_cert_store(ccert_context, add_cert_to_certinfo, &args);
result = args.result;
}
CertFreeCertificateContext(ccert_context);
if(result)
return result;
}
connssl->connecting_state = ssl_connect_done;
return CURLE_OK;
}
static CURLcode
schannel_connect_common(struct connectdata *conn, int sockindex,
bool nonblocking, bool *done)
{
CURLcode result;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
curl_socket_t sockfd = conn->sock[sockindex];
time_t timeout_ms;
int what;
if(ssl_connection_complete == connssl->state) {
*done = TRUE;
return CURLE_OK;
}
if(ssl_connect_1 == connssl->connecting_state) {
timeout_ms = Curl_timeleft(data, NULL, TRUE);
if(timeout_ms < 0) {
failf(data, "SSL/TLS connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
result = schannel_connect_step1(conn, sockindex);
if(result)
return result;
}
while(ssl_connect_2 == connssl->connecting_state ||
ssl_connect_2_reading == connssl->connecting_state ||
ssl_connect_2_writing == connssl->connecting_state) {
timeout_ms = Curl_timeleft(data, NULL, TRUE);
if(timeout_ms < 0) {
failf(data, "SSL/TLS connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
if(connssl->connecting_state == ssl_connect_2_reading
|| connssl->connecting_state == ssl_connect_2_writing) {
curl_socket_t writefd = ssl_connect_2_writing ==
connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
curl_socket_t readfd = ssl_connect_2_reading ==
connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
what = Curl_socket_check(readfd, CURL_SOCKET_BAD, writefd,
nonblocking ? 0 : timeout_ms);
if(what < 0) {
failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO);
return CURLE_SSL_CONNECT_ERROR;
}
else if(0 == what) {
if(nonblocking) {
*done = FALSE;
return CURLE_OK;
}
else {
failf(data, "SSL/TLS connection timeout");
return CURLE_OPERATION_TIMEDOUT;
}
}
}
result = schannel_connect_step2(conn, sockindex);
if(result || (nonblocking &&
(ssl_connect_2 == connssl->connecting_state ||
ssl_connect_2_reading == connssl->connecting_state ||
ssl_connect_2_writing == connssl->connecting_state)))
return result;
}
if(ssl_connect_3 == connssl->connecting_state) {
result = schannel_connect_step3(conn, sockindex);
if(result)
return result;
}
if(ssl_connect_done == connssl->connecting_state) {
connssl->state = ssl_connection_complete;
conn->recv[sockindex] = schannel_recv;
conn->send[sockindex] = schannel_send;
#ifdef SECPKG_ATTR_ENDPOINT_BINDINGS
conn->sslContext = &BACKEND->ctxt->ctxt_handle;
#endif
*done = TRUE;
}
else
*done = FALSE;
connssl->connecting_state = ssl_connect_1;
return CURLE_OK;
}
static ssize_t
schannel_send(struct connectdata *conn, int sockindex,
const void *buf, size_t len, CURLcode *err)
{
ssize_t written = -1;
size_t data_len = 0;
unsigned char *data = NULL;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
SecBuffer outbuf[4];
SecBufferDesc outbuf_desc;
SECURITY_STATUS sspi_status = SEC_E_OK;
CURLcode result;
if(BACKEND->stream_sizes.cbMaximumMessage == 0) {
sspi_status = s_pSecFn->QueryContextAttributes(
&BACKEND->ctxt->ctxt_handle,
SECPKG_ATTR_STREAM_SIZES,
&BACKEND->stream_sizes);
if(sspi_status != SEC_E_OK) {
*err = CURLE_SEND_ERROR;
return -1;
}
}
if(len > BACKEND->stream_sizes.cbMaximumMessage) {
len = BACKEND->stream_sizes.cbMaximumMessage;
}
data_len = BACKEND->stream_sizes.cbHeader + len +
BACKEND->stream_sizes.cbTrailer;
data = (unsigned char *) malloc(data_len);
if(data == NULL) {
*err = CURLE_OUT_OF_MEMORY;
return -1;
}
InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER,
data, BACKEND->stream_sizes.cbHeader);
InitSecBuffer(&outbuf[1], SECBUFFER_DATA,
data + BACKEND->stream_sizes.cbHeader, curlx_uztoul(len));
InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER,
data + BACKEND->stream_sizes.cbHeader + len,
BACKEND->stream_sizes.cbTrailer);
InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&outbuf_desc, outbuf, 4);
memcpy(outbuf[1].pvBuffer, buf, len);
sspi_status = s_pSecFn->EncryptMessage(&BACKEND->ctxt->ctxt_handle, 0,
&outbuf_desc, 0);
if(sspi_status == SEC_E_OK) {
written = 0;
len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
while(len > (size_t)written) {
ssize_t this_write;
time_t timeleft;
int what;
this_write = 0;
timeleft = Curl_timeleft(conn->data, NULL, FALSE);
if(timeleft < 0) {
failf(conn->data, "schannel: timed out sending data "
"(bytes sent: %zd)", written);
*err = CURLE_OPERATION_TIMEDOUT;
written = -1;
break;
}
what = SOCKET_WRITABLE(conn->sock[sockindex], timeleft);
if(what < 0) {
failf(conn->data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
*err = CURLE_SEND_ERROR;
written = -1;
break;
}
else if(0 == what) {
failf(conn->data, "schannel: timed out sending data "
"(bytes sent: %zd)", written);
*err = CURLE_OPERATION_TIMEDOUT;
written = -1;
break;
}
result = Curl_write_plain(conn, conn->sock[sockindex], data + written,
len - written, &this_write);
if(result == CURLE_AGAIN)
continue;
else if(result != CURLE_OK) {
*err = result;
written = -1;
break;
}
written += this_write;
}
}
else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) {
*err = CURLE_OUT_OF_MEMORY;
}
else{
*err = CURLE_SEND_ERROR;
}
Curl_safefree(data);
if(len == (size_t)written)
written = outbuf[1].cbBuffer;
return written;
}
static ssize_t
schannel_recv(struct connectdata *conn, int sockindex,
char *buf, size_t len, CURLcode *err)
{
size_t size = 0;
ssize_t nread = -1;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
unsigned char *reallocated_buffer;
size_t reallocated_length;
bool done = FALSE;
SecBuffer inbuf[4];
SecBufferDesc inbuf_desc;
SECURITY_STATUS sspi_status = SEC_E_OK;
size_t min_encdata_length = len + CURL_SCHANNEL_BUFFER_FREE_SIZE;
DEBUGF(infof(data, "schannel: client wants to read %zu bytes\n", len));
*err = CURLE_OK;
if(len && len <= BACKEND->decdata_offset) {
infof(data, "schannel: enough decrypted data is already available\n");
goto cleanup;
}
else if(BACKEND->recv_unrecoverable_err) {
*err = BACKEND->recv_unrecoverable_err;
infof(data, "schannel: an unrecoverable error occurred in a prior call\n");
goto cleanup;
}
else if(BACKEND->recv_sspi_close_notify) {
infof(data, "schannel: server indicated shutdown in a prior call\n");
goto cleanup;
}
else if(!len) {
;
}
else if(!BACKEND->recv_connection_closed) {
size = BACKEND->encdata_length - BACKEND->encdata_offset;
if(size < CURL_SCHANNEL_BUFFER_FREE_SIZE ||
BACKEND->encdata_length < min_encdata_length) {
reallocated_length = BACKEND->encdata_offset +
CURL_SCHANNEL_BUFFER_FREE_SIZE;
if(reallocated_length < min_encdata_length) {
reallocated_length = min_encdata_length;
}
reallocated_buffer = realloc(BACKEND->encdata_buffer,
reallocated_length);
if(reallocated_buffer == NULL) {
*err = CURLE_OUT_OF_MEMORY;
failf(data, "schannel: unable to re-allocate memory");
goto cleanup;
}
BACKEND->encdata_buffer = reallocated_buffer;
BACKEND->encdata_length = reallocated_length;
size = BACKEND->encdata_length - BACKEND->encdata_offset;
DEBUGF(infof(data, "schannel: encdata_buffer resized %zu\n",
BACKEND->encdata_length));
}
DEBUGF(infof(data,
"schannel: encrypted data buffer: offset %zu length %zu\n",
BACKEND->encdata_offset, BACKEND->encdata_length));
*err = Curl_read_plain(conn->sock[sockindex],
(char *)(BACKEND->encdata_buffer +
BACKEND->encdata_offset),
size, &nread);
if(*err) {
nread = -1;
if(*err == CURLE_AGAIN)
DEBUGF(infof(data,
"schannel: Curl_read_plain returned CURLE_AGAIN\n"));
else if(*err == CURLE_RECV_ERROR)
infof(data, "schannel: Curl_read_plain returned CURLE_RECV_ERROR\n");
else
infof(data, "schannel: Curl_read_plain returned error %d\n", *err);
}
else if(nread == 0) {
BACKEND->recv_connection_closed = true;
DEBUGF(infof(data, "schannel: server closed the connection\n"));
}
else if(nread > 0) {
BACKEND->encdata_offset += (size_t)nread;
BACKEND->encdata_is_incomplete = false;
DEBUGF(infof(data, "schannel: encrypted data got %zd\n", nread));
}
}
DEBUGF(infof(data,
"schannel: encrypted data buffer: offset %zu length %zu\n",
BACKEND->encdata_offset, BACKEND->encdata_length));
while(BACKEND->encdata_offset > 0 && sspi_status == SEC_E_OK &&
(!len || BACKEND->decdata_offset < len ||
BACKEND->recv_connection_closed)) {
InitSecBuffer(&inbuf[0], SECBUFFER_DATA, BACKEND->encdata_buffer,
curlx_uztoul(BACKEND->encdata_offset));
InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0);
InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&inbuf_desc, inbuf, 4);
sspi_status = s_pSecFn->DecryptMessage(&BACKEND->ctxt->ctxt_handle,
&inbuf_desc, 0, NULL);
if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE ||
sspi_status == SEC_I_CONTEXT_EXPIRED) {
if(inbuf[1].BufferType == SECBUFFER_DATA) {
DEBUGF(infof(data, "schannel: decrypted data length: %lu\n",
inbuf[1].cbBuffer));
size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ?
inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE;
if(BACKEND->decdata_length - BACKEND->decdata_offset < size ||
BACKEND->decdata_length < len) {
reallocated_length = BACKEND->decdata_offset + size;
if(reallocated_length < len) {
reallocated_length = len;
}
reallocated_buffer = realloc(BACKEND->decdata_buffer,
reallocated_length);
if(reallocated_buffer == NULL) {
*err = CURLE_OUT_OF_MEMORY;
failf(data, "schannel: unable to re-allocate memory");
goto cleanup;
}
BACKEND->decdata_buffer = reallocated_buffer;
BACKEND->decdata_length = reallocated_length;
}
size = inbuf[1].cbBuffer;
if(size) {
memcpy(BACKEND->decdata_buffer + BACKEND->decdata_offset,
inbuf[1].pvBuffer, size);
BACKEND->decdata_offset += size;
}
DEBUGF(infof(data, "schannel: decrypted data added: %zu\n", size));
DEBUGF(infof(data,
"schannel: decrypted cached: offset %zu length %zu\n",
BACKEND->decdata_offset, BACKEND->decdata_length));
}
if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
DEBUGF(infof(data, "schannel: encrypted data length: %lu\n",
inbuf[3].cbBuffer));
if(BACKEND->encdata_offset > inbuf[3].cbBuffer) {
memmove(BACKEND->encdata_buffer,
(BACKEND->encdata_buffer + BACKEND->encdata_offset) -
inbuf[3].cbBuffer, inbuf[3].cbBuffer);
BACKEND->encdata_offset = inbuf[3].cbBuffer;
}
DEBUGF(infof(data,
"schannel: encrypted cached: offset %zu length %zu\n",
BACKEND->encdata_offset, BACKEND->encdata_length));
}
else {
BACKEND->encdata_offset = 0;
}
if(sspi_status == SEC_I_RENEGOTIATE) {
infof(data, "schannel: remote party requests renegotiation\n");
if(*err && *err != CURLE_AGAIN) {
infof(data, "schannel: can't renogotiate, an error is pending\n");
goto cleanup;
}
if(BACKEND->encdata_offset) {
*err = CURLE_RECV_ERROR;
infof(data, "schannel: can't renogotiate, "
"encrypted data available\n");
goto cleanup;
}
infof(data, "schannel: renegotiating SSL/TLS connection\n");
connssl->state = ssl_connection_negotiating;
connssl->connecting_state = ssl_connect_2_writing;
*err = schannel_connect_common(conn, sockindex, FALSE, &done);
if(*err) {
infof(data, "schannel: renegotiation failed\n");
goto cleanup;
}
sspi_status = SEC_E_OK;
infof(data, "schannel: SSL/TLS connection renegotiated\n");
continue;
}
else if(sspi_status == SEC_I_CONTEXT_EXPIRED) {
BACKEND->recv_sspi_close_notify = true;
if(!BACKEND->recv_connection_closed) {
BACKEND->recv_connection_closed = true;
infof(data, "schannel: server closed the connection\n");
}
goto cleanup;
}
}
else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
BACKEND->encdata_is_incomplete = true;
if(!*err)
*err = CURLE_AGAIN;
infof(data, "schannel: failed to decrypt data, need more data\n");
goto cleanup;
}
else {
char buffer[STRERROR_LEN];
*err = CURLE_RECV_ERROR;
infof(data, "schannel: failed to read data from server: %s\n",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
goto cleanup;
}
}
DEBUGF(infof(data,
"schannel: encrypted data buffer: offset %zu length %zu\n",
BACKEND->encdata_offset, BACKEND->encdata_length));
DEBUGF(infof(data,
"schannel: decrypted data buffer: offset %zu length %zu\n",
BACKEND->decdata_offset, BACKEND->decdata_length));
cleanup:
DEBUGF(infof(data, "schannel: schannel_recv cleanup\n"));
if(len && !BACKEND->decdata_offset && BACKEND->recv_connection_closed &&
!BACKEND->recv_sspi_close_notify) {
bool isWin2k = Curl_verify_windows_version(5, 0, PLATFORM_WINNT,
VERSION_EQUAL);
if(isWin2k && sspi_status == SEC_E_OK)
BACKEND->recv_sspi_close_notify = true;
else {
*err = CURLE_RECV_ERROR;
infof(data, "schannel: server closed abruptly (missing close_notify)\n");
}
}
if(*err && *err != CURLE_AGAIN)
BACKEND->recv_unrecoverable_err = *err;
size = len < BACKEND->decdata_offset ? len : BACKEND->decdata_offset;
if(size) {
memcpy(buf, BACKEND->decdata_buffer, size);
memmove(BACKEND->decdata_buffer, BACKEND->decdata_buffer + size,
BACKEND->decdata_offset - size);
BACKEND->decdata_offset -= size;
DEBUGF(infof(data, "schannel: decrypted data returned %zu\n", size));
DEBUGF(infof(data,
"schannel: decrypted data buffer: offset %zu length %zu\n",
BACKEND->decdata_offset, BACKEND->decdata_length));
*err = CURLE_OK;
return (ssize_t)size;
}
if(!*err && !BACKEND->recv_connection_closed)
*err = CURLE_AGAIN;
if(!len)
*err = CURLE_OK;
return *err ? -1 : 0;
}
static CURLcode Curl_schannel_connect_nonblocking(struct connectdata *conn,
int sockindex, bool *done)
{
return schannel_connect_common(conn, sockindex, TRUE, done);
}
static CURLcode Curl_schannel_connect(struct connectdata *conn, int sockindex)
{
CURLcode result;
bool done = FALSE;
result = schannel_connect_common(conn, sockindex, FALSE, &done);
if(result)
return result;
DEBUGASSERT(done);
return CURLE_OK;
}
static bool Curl_schannel_data_pending(const struct connectdata *conn,
int sockindex)
{
const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
if(connssl->use)
return (BACKEND->decdata_offset > 0 ||
(BACKEND->encdata_offset > 0 && !BACKEND->encdata_is_incomplete));
else
return FALSE;
}
static void Curl_schannel_close(struct connectdata *conn, int sockindex)
{
if(conn->ssl[sockindex].use)
Curl_ssl_shutdown(conn, sockindex);
}
static void Curl_schannel_session_free(void *ptr)
{
struct curl_schannel_cred *cred = ptr;
cred->refcount--;
if(cred->refcount == 0) {
s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
Curl_safefree(cred);
}
}
static int Curl_schannel_shutdown(struct connectdata *conn, int sockindex)
{
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
DEBUGASSERT(data);
infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
hostname, conn->remote_port);
if(BACKEND->cred && BACKEND->ctxt) {
SecBufferDesc BuffDesc;
SecBuffer Buffer;
SECURITY_STATUS sspi_status;
SecBuffer outbuf;
SecBufferDesc outbuf_desc;
CURLcode result;
TCHAR *host_name;
DWORD dwshut = SCHANNEL_SHUTDOWN;
InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
InitSecBufferDesc(&BuffDesc, &Buffer, 1);
sspi_status = s_pSecFn->ApplyControlToken(&BACKEND->ctxt->ctxt_handle,
&BuffDesc);
if(sspi_status != SEC_E_OK) {
char buffer[STRERROR_LEN];
failf(data, "schannel: ApplyControlToken failure: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
}
host_name = Curl_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
sspi_status = s_pSecFn->InitializeSecurityContext(
&BACKEND->cred->cred_handle,
&BACKEND->ctxt->ctxt_handle,
host_name,
BACKEND->req_flags,
0,
0,
NULL,
0,
&BACKEND->ctxt->ctxt_handle,
&outbuf_desc,
&BACKEND->ret_flags,
&BACKEND->ctxt->time_stamp);
Curl_unicodefree(host_name);
if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
ssize_t written;
result = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
outbuf.cbBuffer, &written);
s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
if((result != CURLE_OK) || (outbuf.cbBuffer != (size_t) written)) {
infof(data, "schannel: failed to send close msg: %s"
" (bytes written: %zd)\n", curl_easy_strerror(result), written);
}
}
}
if(BACKEND->ctxt) {
DEBUGF(infof(data, "schannel: clear security context handle\n"));
s_pSecFn->DeleteSecurityContext(&BACKEND->ctxt->ctxt_handle);
Curl_safefree(BACKEND->ctxt);
}
if(BACKEND->cred) {
Curl_ssl_sessionid_lock(conn);
Curl_schannel_session_free(BACKEND->cred);
Curl_ssl_sessionid_unlock(conn);
BACKEND->cred = NULL;
}
if(BACKEND->encdata_buffer != NULL) {
Curl_safefree(BACKEND->encdata_buffer);
BACKEND->encdata_length = 0;
BACKEND->encdata_offset = 0;
BACKEND->encdata_is_incomplete = false;
}
if(BACKEND->decdata_buffer != NULL) {
Curl_safefree(BACKEND->decdata_buffer);
BACKEND->decdata_length = 0;
BACKEND->decdata_offset = 0;
}
return CURLE_OK;
}
static int Curl_schannel_init(void)
{
return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
}
static void Curl_schannel_cleanup(void)
{
Curl_sspi_global_cleanup();
}
static size_t Curl_schannel_version(char *buffer, size_t size)
{
size = msnprintf(buffer, size, "Schannel");
return size;
}
static CURLcode Curl_schannel_random(struct Curl_easy *data UNUSED_PARAM,
unsigned char *entropy, size_t length)
{
HCRYPTPROV hCryptProv = 0;
(void)data;
if(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
return CURLE_FAILED_INIT;
if(!CryptGenRandom(hCryptProv, (DWORD)length, entropy)) {
CryptReleaseContext(hCryptProv, 0UL);
return CURLE_FAILED_INIT;
}
CryptReleaseContext(hCryptProv, 0UL);
return CURLE_OK;
}
static CURLcode pkp_pin_peer_pubkey(struct connectdata *conn, int sockindex,
const char *pinnedpubkey)
{
SECURITY_STATUS sspi_status;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
CERT_CONTEXT *pCertContextServer = NULL;
const char *x509_der;
DWORD x509_der_len;
curl_X509certificate x509_parsed;
curl_asn1Element *pubkey;
CURLcode result = CURLE_SSL_PINNEDPUBKEYNOTMATCH;
if(!pinnedpubkey)
return CURLE_OK;
do {
sspi_status =
s_pSecFn->QueryContextAttributes(&BACKEND->ctxt->ctxt_handle,
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
&pCertContextServer);
if((sspi_status != SEC_E_OK) || (pCertContextServer == NULL)) {
char buffer[STRERROR_LEN];
failf(data, "schannel: Failed to read remote certificate context: %s",
Curl_sspi_strerror(sspi_status, buffer, sizeof(buffer)));
break;
}
if(!(((pCertContextServer->dwCertEncodingType & X509_ASN_ENCODING) != 0) &&
(pCertContextServer->cbCertEncoded > 0)))
break;
x509_der = (const char *)pCertContextServer->pbCertEncoded;
x509_der_len = pCertContextServer->cbCertEncoded;
memset(&x509_parsed, 0, sizeof(x509_parsed));
if(Curl_parseX509(&x509_parsed, x509_der, x509_der + x509_der_len))
break;
pubkey = &x509_parsed.subjectPublicKeyInfo;
if(!pubkey->header || pubkey->end <= pubkey->header) {
failf(data, "SSL: failed retrieving public key from server certificate");
break;
}
result = Curl_pin_peer_pubkey(data,
pinnedpubkey,
(const unsigned char *)pubkey->header,
(size_t)(pubkey->end - pubkey->header));
if(result) {
failf(data, "SSL: public key does not match pinned public key!");
}
} while(0);
if(pCertContextServer)
CertFreeCertificateContext(pCertContextServer);
return result;
}
static void Curl_schannel_checksum(const unsigned char *input,
size_t inputlen,
unsigned char *checksum,
size_t checksumlen,
DWORD provType,
const unsigned int algId)
{
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
DWORD cbHashSize = 0;
DWORD dwHashSizeLen = (DWORD)sizeof(cbHashSize);
DWORD dwChecksumLen = (DWORD)checksumlen;
memset(checksum, 0, checksumlen);
if(!CryptAcquireContext(&hProv, NULL, NULL, provType,
CRYPT_VERIFYCONTEXT))
return;
do {
if(!CryptCreateHash(hProv, algId, 0, 0, &hHash))
break;
if(!CryptHashData(hHash, (BYTE*)input, (DWORD)inputlen, 0))
break;
if(!CryptGetHashParam(hHash, HP_HASHSIZE, (BYTE *)&cbHashSize,
&dwHashSizeLen, 0))
break;
if(checksumlen < cbHashSize)
break;
if(CryptGetHashParam(hHash, HP_HASHVAL, checksum, &dwChecksumLen, 0))
break;
} while(0);
if(hHash)
CryptDestroyHash(hHash);
if(hProv)
CryptReleaseContext(hProv, 0);
}
static CURLcode Curl_schannel_md5sum(unsigned char *input,
size_t inputlen,
unsigned char *md5sum,
size_t md5len)
{
Curl_schannel_checksum(input, inputlen, md5sum, md5len,
PROV_RSA_FULL, CALG_MD5);
return CURLE_OK;
}
static CURLcode Curl_schannel_sha256sum(const unsigned char *input,
size_t inputlen,
unsigned char *sha256sum,
size_t sha256len)
{
Curl_schannel_checksum(input, inputlen, sha256sum, sha256len,
PROV_RSA_AES, CALG_SHA_256);
return CURLE_OK;
}
static void *Curl_schannel_get_internals(struct ssl_connect_data *connssl,
CURLINFO info UNUSED_PARAM)
{
(void)info;
return &BACKEND->ctxt->ctxt_handle;
}
const struct Curl_ssl Curl_ssl_schannel = {
{ CURLSSLBACKEND_SCHANNEL, "schannel" },
SSLSUPP_CERTINFO |
SSLSUPP_PINNEDPUBKEY,
sizeof(struct ssl_backend_data),
Curl_schannel_init,
Curl_schannel_cleanup,
Curl_schannel_version,
Curl_none_check_cxn,
Curl_schannel_shutdown,
Curl_schannel_data_pending,
Curl_schannel_random,
Curl_none_cert_status_request,
Curl_schannel_connect,
Curl_schannel_connect_nonblocking,
Curl_schannel_get_internals,
Curl_schannel_close,
Curl_none_close_all,
Curl_schannel_session_free,
Curl_none_set_engine,
Curl_none_set_engine_default,
Curl_none_engines_list,
Curl_none_false_start,
Curl_schannel_md5sum,
Curl_schannel_sha256sum
};
#endif