Newer
Older
*err = CURLE_OPERATION_TIMEDOUT;
written = -1;
break;
}
/* socket is writable */
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)
/* Encrypted message including header, data and trailer entirely sent.
The return value is the number of unencrypted bytes that were sent. */
written = outbuf[1].cbBuffer;
}
static ssize_t
schannel_recv(struct connectdata *conn, int sockindex,
char *buf, size_t len, CURLcode *err)
{
size_t size = 0;
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;
/* we want the length of the encrypted buffer to be at least large enough
that it can hold all the bytes requested and some TLS record overhead. */
size_t min_encdata_length = len + CURL_SCHANNEL_BUFFER_FREE_SIZE;
/****************************************************************************
* Don't return or set connssl->recv_unrecoverable_err unless in the cleanup.
* The pattern for return error is set *err, optional infof, goto cleanup.
*
* Our priority is to always return as much decrypted data to the caller as
* possible, even if an error occurs. The state of the decrypted buffer must
* always be valid. Transfer of decrypted data to the caller's buffer is
* handled in the cleanup.
*/
infof(data, "schannel: client wants to read %zu bytes\n", len);
*err = CURLE_OK;
if(len && len <= connssl->decdata_offset) {
infof(data, "schannel: enough decrypted data is already available\n");
goto cleanup;
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
else if(connssl->recv_unrecoverable_err) {
*err = connssl->recv_unrecoverable_err;
infof(data, "schannel: an unrecoverable error occurred in a prior call\n");
goto cleanup;
}
else if(connssl->recv_sspi_close_notify) {
/* once a server has indicated shutdown there is no more encrypted data */
infof(data, "schannel: server indicated shutdown in a prior call\n");
goto cleanup;
}
else if(!len) {
/* It's debatable what to return when !len. Regardless we can't return
immediately because there may be data to decrypt (in the case we want to
decrypt all encrypted cached data) so handle !len later in cleanup.
*/
; /* do nothing */
}
else if(!connssl->recv_connection_closed) {
/* increase enc buffer in order to fit the requested amount of data */
size = connssl->encdata_length - connssl->encdata_offset;
if(size < CURL_SCHANNEL_BUFFER_FREE_SIZE ||
connssl->encdata_length < min_encdata_length) {
reallocated_length = connssl->encdata_offset +
CURL_SCHANNEL_BUFFER_FREE_SIZE;
if(reallocated_length < min_encdata_length) {
reallocated_length = min_encdata_length;
}
reallocated_buffer = realloc(connssl->encdata_buffer,
reallocated_length);
if(reallocated_buffer == NULL) {
*err = CURLE_OUT_OF_MEMORY;
failf(data, "schannel: unable to re-allocate memory");
goto cleanup;
}
connssl->encdata_buffer = reallocated_buffer;
connssl->encdata_length = reallocated_length;
size = connssl->encdata_length - connssl->encdata_offset;
infof(data, "schannel: encdata_buffer resized %zu\n",
connssl->encdata_length);
infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
connssl->encdata_offset, connssl->encdata_length);
/* read encrypted data from socket */
*err = Curl_read_plain(conn->sock[sockindex],
(char *)(connssl->encdata_buffer +
connssl->encdata_offset),
if(*err) {
nread = -1;
if(*err == CURLE_AGAIN)
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) {
connssl->recv_connection_closed = true;
infof(data, "schannel: server closed the connection\n");
connssl->encdata_offset += (size_t)nread;
infof(data, "schannel: encrypted data got %zd\n", nread);
}
infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
connssl->encdata_offset, connssl->encdata_length);
while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK &&
(!len || connssl->decdata_offset < len ||
connssl->recv_connection_closed)) {
/* prepare data buffer for DecryptMessage call */
InitSecBuffer(&inbuf[0], SECBUFFER_DATA, connssl->encdata_buffer,
curlx_uztoul(connssl->encdata_offset));
/* we need 3 more empty input buffers for possible output */
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);
/* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx
*/
sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle,
&inbuf_desc, 0, NULL);
/* check if everything went fine (server may want to renegotiate
or shutdown the connection context) */
if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE ||
sspi_status == SEC_I_CONTEXT_EXPIRED) {
/* check for successfully decrypted data, even before actual
renegotiation or shutdown of the connection context */
if(inbuf[1].BufferType == SECBUFFER_DATA) {
infof(data, "schannel: decrypted data length: %lu\n",
/* increase buffer in order to fit the received amount of data */
size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ?
inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE;
if(connssl->decdata_length - connssl->decdata_offset < size ||
connssl->decdata_length < len) {
/* increase internal decrypted data buffer */
reallocated_length = connssl->decdata_offset + size;
/* make sure that the requested amount of data fits */
if(reallocated_length < len) {
reallocated_length = len;
}
reallocated_buffer = realloc(connssl->decdata_buffer,
reallocated_length);
if(reallocated_buffer == NULL) {
*err = CURLE_OUT_OF_MEMORY;
failf(data, "schannel: unable to re-allocate memory");
goto cleanup;
connssl->decdata_buffer = reallocated_buffer;
connssl->decdata_length = reallocated_length;
}
/* copy decrypted data to internal buffer */
size = inbuf[1].cbBuffer;
memcpy(connssl->decdata_buffer + connssl->decdata_offset,
inbuf[1].pvBuffer, size);
connssl->decdata_offset += size;
}
infof(data, "schannel: decrypted data added: %zu\n", size);
infof(data, "schannel: decrypted data cached: offset %zu length %zu\n",
connssl->decdata_offset, connssl->decdata_length);
}
/* check for remaining encrypted data */
if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
infof(data, "schannel: encrypted data length: %lu\n",
/* check if the remaining data is less than the total amount
* and therefore begins after the already processed data
if(connssl->encdata_offset > inbuf[3].cbBuffer) {
/* move remaining encrypted data forward to the beginning of
buffer */
memmove(connssl->encdata_buffer,
(connssl->encdata_buffer + connssl->encdata_offset) -
inbuf[3].cbBuffer, inbuf[3].cbBuffer);
connssl->encdata_offset = inbuf[3].cbBuffer;
}
infof(data, "schannel: encrypted data cached: offset %zu length %zu\n",
connssl->encdata_offset, connssl->encdata_length);
}
else {
/* reset encrypted buffer offset, because there is no data remaining */
connssl->encdata_offset = 0;
}
/* check if server wants to renegotiate the connection context */
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(connssl->encdata_offset) {
*err = CURLE_RECV_ERROR;
infof(data, "schannel: can't renogotiate, "
"encrypted data available\n");
goto cleanup;
}
/* begin renegotiation */
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;
}
/* now retry receiving data */
sspi_status = SEC_E_OK;
infof(data, "schannel: SSL/TLS connection renegotiated\n");
continue;
}
/* check if the server closed the connection */
else if(sspi_status == SEC_I_CONTEXT_EXPIRED) {
/* In Windows 2000 SEC_I_CONTEXT_EXPIRED (close_notify) is not
returned so we have to work around that in cleanup. */
connssl->recv_sspi_close_notify = true;
if(!connssl->recv_connection_closed) {
connssl->recv_connection_closed = true;
infof(data, "schannel: server closed the connection\n");
else if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
if(!*err)
*err = CURLE_AGAIN;
infof(data, "schannel: failed to decrypt data, need more data\n");
goto cleanup;
}
else {
infof(data, "schannel: failed to read data from server: %s\n",
Curl_sspi_strerror(conn, sspi_status));
}
}
infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
connssl->encdata_offset, connssl->encdata_length);
infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
connssl->decdata_offset, connssl->decdata_length);
cleanup:
/* Warning- there is no guarantee the encdata state is valid at this point */
infof(data, "schannel: schannel_recv cleanup\n");
/* Error if the connection has closed without a close_notify.
Behavior here is a matter of debate. We don't want to be vulnerable to a
truncation attack however there's some browser precedent for ignoring the
close_notify for compatibility reasons.
Additionally, Windows 2000 (v5.0) is a special case since it seems it doesn't
return close_notify. In that case if the connection was closed we assume it
was graceful (close_notify) since there doesn't seem to be a way to tell.
*/
if(len && !connssl->decdata_offset && connssl->recv_connection_closed &&
!connssl->recv_sspi_close_notify) {
bool isWin2k = Curl_verify_windows_version(5, 0, PLATFORM_WINNT,
VERSION_EQUAL);
if(isWin2k && sspi_status == SEC_E_OK)
connssl->recv_sspi_close_notify = true;
else {
*err = CURLE_RECV_ERROR;
infof(data, "schannel: server closed abruptly (missing close_notify)\n");
}
}
/* Any error other than CURLE_AGAIN is an unrecoverable error. */
if(*err && *err != CURLE_AGAIN)
connssl->recv_unrecoverable_err = *err;
size = len < connssl->decdata_offset ? len : connssl->decdata_offset;
memcpy(buf, connssl->decdata_buffer, size);
memmove(connssl->decdata_buffer, connssl->decdata_buffer + size,
connssl->decdata_offset - size);
connssl->decdata_offset -= size;
infof(data, "schannel: decrypted data returned %zu\n", size);
infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
connssl->decdata_offset, connssl->decdata_length);
}
if(!*err && !connssl->recv_connection_closed)
*err = CURLE_AGAIN;
/* It's debatable what to return when !len. We could return whatever error we
got from decryption but instead we override here so the return is consistent.
*/
if(!len)
*err = CURLE_OK;
return *err ? -1 : 0;
}
CURLcode
Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex,
return schannel_connect_common(conn, sockindex, TRUE, done);
}
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;
}
bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex)
{
const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
if(connssl->use) /* SSL/TLS is in use */
return (connssl->encdata_offset > 0 ||
else
return FALSE;
}
void Curl_schannel_close(struct connectdata *conn, int sockindex)
{
/* if the SSL/TLS channel hasn't been shut down yet, do that now. */
Curl_ssl_shutdown(conn, sockindex);
}
int Curl_schannel_shutdown(struct connectdata *conn, int sockindex)
{
/* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
* Shutting Down an Schannel Connection
*/
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
const char * const hostname = SSL_IS_PROXY() ? conn->http_proxy.host.name :
conn->host.name;
infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
if(connssl->cred && connssl->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(&connssl->ctxt->ctxt_handle,
&BuffDesc);
if(sspi_status != SEC_E_OK)
failf(data, "schannel: ApplyControlToken failure: %s",
Curl_sspi_strerror(conn, sspi_status));
host_name = Curl_convert_UTF8_to_tchar(hostname);
if(!host_name)
return CURLE_OUT_OF_MEMORY;
/* setup output buffer */
InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
sspi_status = s_pSecFn->InitializeSecurityContext(
&connssl->cred->cred_handle,
&connssl->ctxt->ctxt_handle,
host_name,
connssl->req_flags,
0,
0,
NULL,
0,
&connssl->ctxt->ctxt_handle,
&outbuf_desc,
&connssl->ret_flags,
&connssl->ctxt->time_stamp);
if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
/* send close message which is in output buffer */
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);
/* free SSPI Schannel API security context handle */
if(connssl->ctxt) {
infof(data, "schannel: clear security context handle\n");
s_pSecFn->DeleteSecurityContext(&connssl->ctxt->ctxt_handle);
Curl_safefree(connssl->ctxt);
}
/* free SSPI Schannel API credential handle */
if(connssl->cred) {
Curl_ssl_sessionid_lock(conn);
Curl_schannel_session_free(connssl->cred);
Curl_ssl_sessionid_unlock(conn);
connssl->cred = NULL;
}
/* free internal buffer for received encrypted data */
if(connssl->encdata_buffer != NULL) {
Curl_safefree(connssl->encdata_buffer);
connssl->encdata_length = 0;
connssl->encdata_offset = 0;
}
/* free internal buffer for received decrypted data */
if(connssl->decdata_buffer != NULL) {
Curl_safefree(connssl->decdata_buffer);
connssl->decdata_length = 0;
connssl->decdata_offset = 0;
}
}
void Curl_schannel_session_free(void *ptr)
{
/* this is expected to be called under sessionid lock */
struct curl_schannel_cred *cred = ptr;
cred->refcount--;
if(cred->refcount == 0) {
s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
Curl_safefree(cred);
}
}
int Curl_schannel_init(void)
return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
}
void Curl_schannel_cleanup(void)
Curl_sspi_global_cleanup();
}
size_t Curl_schannel_version(char *buffer, size_t size)
{
size = snprintf(buffer, size, "WinSSL");
}
int Curl_schannel_random(unsigned char *entropy, size_t length)
{
HCRYPTPROV hCryptProv = 0;
if(!CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL,
CRYPT_VERIFYCONTEXT | CRYPT_SILENT))
return 1;
if(!CryptGenRandom(hCryptProv, (DWORD)length, entropy)) {
CryptReleaseContext(hCryptProv, 0UL);
return 1;
}
CryptReleaseContext(hCryptProv, 0UL);
return 0;
}
#ifdef _WIN32_WCE
static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
{
SECURITY_STATUS status;
struct Curl_easy *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
CURLcode result = CURLE_OK;
CERT_CONTEXT *pCertContextServer = NULL;
const CERT_CHAIN_CONTEXT *pChainContext = NULL;
const char * const conn_hostname = SSL_IS_PROXY() ?
conn->http_proxy.host.name :
conn->host.name;
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle,
SECPKG_ATTR_REMOTE_CERT_CONTEXT,
&pCertContextServer);
if((status != SEC_E_OK) || (pCertContextServer == NULL)) {
failf(data, "schannel: Failed to read remote certificate context: %s",
Curl_sspi_strerror(conn, status));
result = CURLE_PEER_FAILED_VERIFICATION;
}
if(result == CURLE_OK) {
CERT_CHAIN_PARA ChainPara;
memset(&ChainPara, 0, sizeof(ChainPara));
ChainPara.cbSize = sizeof(ChainPara);
if(!CertGetCertificateChain(NULL,
pCertContextServer,
NULL,
pCertContextServer->hCertStore,
&ChainPara,
(data->set.ssl.no_revoke ? 0 :
CERT_CHAIN_REVOCATION_CHECK_CHAIN),
NULL,
&pChainContext)) {
failf(data, "schannel: CertGetCertificateChain failed: %s",
Curl_sspi_strerror(conn, GetLastError()));
pChainContext = NULL;
result = CURLE_PEER_FAILED_VERIFICATION;
}
if(result == CURLE_OK) {
CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED);
dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
if(dwTrustErrorMask) {
if(dwTrustErrorMask & CERT_TRUST_IS_REVOKED)
failf(data, "schannel: CertGetCertificateChain trust error"
" CERT_TRUST_IS_REVOKED");
else if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
failf(data, "schannel: CertGetCertificateChain trust error"
" CERT_TRUST_IS_PARTIAL_CHAIN");
else if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
failf(data, "schannel: CertGetCertificateChain trust error"
" CERT_TRUST_IS_UNTRUSTED_ROOT");
else if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
failf(data, "schannel: CertGetCertificateChain trust error"
" CERT_TRUST_IS_NOT_TIME_VALID");
else
failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
dwTrustErrorMask);
result = CURLE_PEER_FAILED_VERIFICATION;
}
}
}
if(result == CURLE_OK) {
if(conn->ssl_config.verifyhost) {
TCHAR cert_hostname_buff[256];
/* TODO: Fix this for certificates with multiple alternative names.
Right now we're only asking for the first preferred alternative name.
Instead we'd need to do all via CERT_NAME_SEARCH_ALL_NAMES_FLAG
(if WinCE supports that?) and run this section in a loop for each.
https://msdn.microsoft.com/en-us/library/windows/desktop/aa376086.aspx
curl: (51) schannel: CertGetNameString() certificate hostname
(.google.com) did not match connection (google.com)
*/
len = CertGetNameString(pCertContextServer,
CERT_NAME_DNS_TYPE,
CERT_NAME_DISABLE_IE4_UTF8_FLAG,
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
cert_hostname_buff,
256);
if(len > 0) {
const char *cert_hostname;
/* Comparing the cert name and the connection hostname encoded as UTF-8
* is acceptable since both values are assumed to use ASCII
* (or some equivalent) encoding
*/
cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname_buff);
if(!cert_hostname) {
result = CURLE_OUT_OF_MEMORY;
}
else{
int match_result;
match_result = Curl_cert_hostcheck(cert_hostname, conn->host.name);
if(match_result == CURL_HOST_MATCH) {
infof(data,
"schannel: connection hostname (%s) validated "
"against certificate name (%s)\n",
conn->host.name,
cert_hostname);
result = CURLE_OK;
}
else{
failf(data,
"schannel: connection hostname (%s) "
"does not match certificate name (%s)",
conn->host.name,
cert_hostname);
result = CURLE_PEER_FAILED_VERIFICATION;
}
Curl_unicodefree(cert_hostname);
}
else {
failf(data,
"schannel: CertGetNameString did not provide any "
"certificate name information");
result = CURLE_PEER_FAILED_VERIFICATION;
}
}
}
if(pChainContext)
CertFreeCertificateChain(pChainContext);
if(pCertContextServer)
CertFreeCertificateContext(pCertContextServer);
return result;
}
#endif /* _WIN32_WCE */
#endif /* USE_SCHANNEL */