Newer
Older
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);
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
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) {
DWORD winver_full, winver_major, winver_minor;
winver_full = GetVersion();
winver_major = (DWORD)(LOBYTE(LOWORD(winver_full)));
winver_minor = (DWORD)(HIBYTE(LOWORD(winver_full)));
if(winver_major == 5 && winver_minor == 0 && 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 ||
connssl->decdata_offset > 0 ) ? TRUE : FALSE;
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 http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
* Shutting Down an Schannel Connection
*/
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
conn->host.name, conn->remote_port);
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(conn->host.name);
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) {
/* decrement the reference counter of the credential/session handle */
if(connssl->cred->refcount > 0) {
connssl->cred->refcount--;
infof(data, "schannel: decremented credential handle refcount = %d\n",
connssl->cred->refcount);
}
/* if the handle was not cached and the refcount is zero */
if(!connssl->cred->cached && connssl->cred->refcount == 0) {
infof(data, "schannel: clear credential handle\n");
s_pSecFn->FreeCredentialsHandle(&connssl->cred->cred_handle);
Curl_safefree(connssl->cred);
}
/* 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)
{
struct curl_schannel_cred *cred = ptr;
if(cred && cred->cached) {
if(cred->refcount == 0) {
s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
Curl_safefree(cred);
}
else {
cred->cached = FALSE;
}
}
}
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 SessionHandle *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;
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
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(data->set.ssl.verifyhost) {
TCHAR cert_hostname_buff[128];
xcharp_u hostname;
xcharp_u cert_hostname;
cert_hostname.const_tchar_ptr = cert_hostname_buff;
hostname.tchar_ptr = Curl_convert_UTF8_to_tchar(conn->host.name);
/* 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,
0,
NULL,
cert_hostname.tchar_ptr,
128);
if(len > 0 && *cert_hostname.tchar_ptr == '*') {
/* this is a wildcard cert. try matching the last len - 1 chars */
int hostname_len = strlen(conn->host.name);
cert_hostname.tchar_ptr++;
if(_tcsicmp(cert_hostname.const_tchar_ptr,
hostname.const_tchar_ptr + hostname_len - len + 2) != 0)
result = CURLE_PEER_FAILED_VERIFICATION;
}
else if(len == 0 || _tcsicmp(hostname.const_tchar_ptr,
cert_hostname.const_tchar_ptr) != 0) {
result = CURLE_PEER_FAILED_VERIFICATION;
}
if(result == CURLE_PEER_FAILED_VERIFICATION) {
char *_cert_hostname;
_cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname.tchar_ptr);
failf(data, "schannel: CertGetNameString() certificate hostname "
"(%s) did not match connection (%s)",
_cert_hostname, conn->host.name);
}
}
if(pChainContext)
CertFreeCertificateChain(pChainContext);
if(pCertContextServer)
CertFreeCertificateContext(pCertContextServer);
return result;
}
#endif /* _WIN32_WCE */
#endif /* USE_SCHANNEL */