* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
- * Copyright (C) 2012 - 2015, Marc Hoersken, <info@marc-hoersken.de>
+ * Copyright (C) 2012 - 2014, Marc Hoersken, <info@marc-hoersken.de>
* Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
- * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al.
+ * Copyright (C) 2012 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
*
* This software is licensed as described in the file COPYING, which
* you should have received as part of this distribution. The terms
* Thanks for code and inspiration!
*/
+/*
+ * TODO list for TLS/SSL implementation:
+ * - implement client certificate authentication
+ * - implement custom server certificate validation
+ * - implement cipher/algorithm option
+ *
+ * Related articles on MSDN:
+ * - Getting a Certificate for Schannel
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa375447.aspx
+ * - Specifying Schannel Ciphers and Cipher Strengths
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa380161.aspx
+ */
+
#include "curl_setup.h"
#ifdef USE_SCHANNEL
#endif
#include "curl_sspi.h"
-#include "schannel.h"
+#include "curl_schannel.h"
#include "vtls.h"
#include "sendf.h"
#include "connect.h" /* for the connect timeout */
#include "inet_pton.h" /* for IP addr SNI check */
#include "curl_multibyte.h"
#include "warnless.h"
-#include "curl_printf.h"
+
+#define _MPRINTF_REPLACE /* use our functions only */
+#include <curl/mprintf.h>
+
#include "curl_memory.h"
/* The last #include file should be: */
#include "memdebug.h"
struct in6_addr addr6;
#endif
TCHAR *host_name;
- CURLcode result;
+ CURLcode code;
infof(data, "schannel: SSL/TLS connection with %s port %hu (step 1/3)\n",
conn->host.name, conn->remote_port);
/* certificate validation on CE doesn't seem to work right; we'll
do it following a more manual process. */
schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION |
- SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
- SCH_CRED_IGNORE_REVOCATION_OFFLINE;
+ SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE;
#else
- 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;
- else
- schannel_cred.dwFlags |= SCH_CRED_REVOCATION_CHECK_CHAIN;
+ schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION |
+ SCH_CRED_REVOCATION_CHECK_CHAIN;
#endif
- if(data->set.ssl_no_revoke)
- infof(data, "schannel: disabled server certificate revocation "
- "checks\n");
- else
- infof(data, "schannel: checking server certificate revocation\n");
+ 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;
- infof(data, "schannel: disabled server certificate revocation checks\n");
+ SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
+ SCH_CRED_IGNORE_REVOCATION_OFFLINE;
+ infof(data, "schannel: disable server certificate revocation checks\n");
}
if(!data->set.ssl.verifyhost) {
schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
infof(data, "schannel: verifyhost setting prevents Schannel from "
- "comparing the supplied target name with the subject "
- "names in server certificates. Also disables SNI.\n");
+ "comparing the supplied target name with the subject "
+ "names in server certificates. Also disables SNI.\n");
}
switch(data->set.ssl.version) {
- default:
- 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:
- 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_SSLv3:
- schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
- break;
- case CURL_SSLVERSION_SSLv2:
- schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
- break;
+ default:
+ 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:
+ 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_SSLv3:
+ schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
+ break;
+ case CURL_SSLVERSION_SSLv2:
+ schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
+ break;
}
/* allocate memory for the re-usable credential handle */
connssl->cred = (struct curl_schannel_cred *)
- malloc(sizeof(struct curl_schannel_cred));
+ malloc(sizeof(struct curl_schannel_cred));
if(!connssl->cred) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OUT_OF_MEMORY;
memset(connssl->cred, 0, sizeof(struct curl_schannel_cred));
/* http://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx */
- sspi_status =
- s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
- SECPKG_CRED_OUTBOUND, NULL,
- &schannel_cred, NULL, NULL,
- &connssl->cred->cred_handle,
- &connssl->cred->time_stamp);
+ sspi_status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
+ SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL,
+ &connssl->cred->cred_handle, &connssl->cred->time_stamp);
if(sspi_status != SEC_E_OK) {
if(sspi_status == SEC_E_WRONG_PRINCIPAL)
/* setup request flags */
connssl->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
- ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
- ISC_REQ_STREAM;
+ ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
+ ISC_REQ_STREAM;
/* allocate memory for the security context handle */
connssl->ctxt = (struct curl_schannel_ctxt *)
- malloc(sizeof(struct curl_schannel_ctxt));
+ malloc(sizeof(struct curl_schannel_ctxt));
if(!connssl->ctxt) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OUT_OF_MEMORY;
"sending %lu bytes...\n", outbuf.cbBuffer);
/* send initial handshake data which is now stored in output buffer */
- result = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
- outbuf.cbBuffer, &written);
+ code = 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)) {
+ if((code != 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;
infof(data, "schannel: sent initial handshake data: "
"sent %zd bytes\n", written);
- connssl->recv_unrecoverable_err = CURLE_OK;
- connssl->recv_sspi_close_notify = false;
- connssl->recv_connection_closed = false;
-
/* continue to second handshake step */
connssl->connecting_state = ssl_connect_2;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
unsigned char *reallocated_buffer;
size_t reallocated_length;
- SecBuffer outbuf[3];
+ SecBuffer outbuf[2];
SecBufferDesc outbuf_desc;
SecBuffer inbuf[2];
SecBufferDesc inbuf_desc;
SECURITY_STATUS sspi_status = SEC_E_OK;
TCHAR *host_name;
- CURLcode result;
+ CURLcode code;
bool doread;
doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
if(!connssl->cred || !connssl->ctxt)
return CURLE_SSL_CONNECT_ERROR;
- /* buffer to store previously received and decrypted data */
- if(connssl->decdata_buffer == NULL) {
- connssl->decdata_offset = 0;
- connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
- connssl->decdata_buffer = malloc(connssl->decdata_length);
- if(connssl->decdata_buffer == NULL) {
- failf(data, "schannel: unable to allocate memory");
- return CURLE_OUT_OF_MEMORY;
- }
- }
-
/* buffer to store previously received and encrypted data */
if(connssl->encdata_buffer == NULL) {
connssl->encdata_offset = 0;
CURL_SCHANNEL_BUFFER_FREE_SIZE) {
/* increase internal encrypted data buffer */
reallocated_length = connssl->encdata_offset +
- CURL_SCHANNEL_BUFFER_FREE_SIZE;
+ CURL_SCHANNEL_BUFFER_FREE_SIZE;
reallocated_buffer = realloc(connssl->encdata_buffer,
reallocated_length);
for(;;) {
if(doread) {
/* read encrypted handshake data from socket */
- result = Curl_read_plain(conn->sock[sockindex],
- (char *) (connssl->encdata_buffer +
- connssl->encdata_offset),
- connssl->encdata_length -
- connssl->encdata_offset,
- &nread);
- if(result == CURLE_AGAIN) {
+ code = Curl_read_plain(conn->sock[sockindex],
+ (char *) (connssl->encdata_buffer + connssl->encdata_offset),
+ connssl->encdata_length - connssl->encdata_offset,
+ &nread);
+ if(code == CURLE_AGAIN) {
if(connssl->connecting_state != ssl_connect_2_writing)
connssl->connecting_state = ssl_connect_2_reading;
infof(data, "schannel: failed to receive handshake, "
"need more data\n");
return CURLE_OK;
}
- else if((result != CURLE_OK) || (nread == 0)) {
+ else if((code != CURLE_OK) || (nread == 0)) {
failf(data, "schannel: failed to receive handshake, "
"SSL/TLS connection failed");
return CURLE_SSL_CONNECT_ERROR;
}
infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
- connssl->encdata_offset, connssl->encdata_length);
+ connssl->encdata_offset, connssl->encdata_length);
/* setup input buffers */
InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(connssl->encdata_offset),
/* setup output buffers */
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);
+ InitSecBufferDesc(&outbuf_desc, outbuf, 2);
if(inbuf[0].pvBuffer == NULL) {
failf(data, "schannel: unable to allocate memory");
return CURLE_OK;
}
- /* If the server has requested a client certificate, attempt to continue
- the handshake without one. This will allow connections to servers which
- request a client certificate but do not require it. */
- if(sspi_status == SEC_I_INCOMPLETE_CREDENTIALS &&
- !(connssl->req_flags & ISC_REQ_USE_SUPPLIED_CREDS)) {
- connssl->req_flags |= ISC_REQ_USE_SUPPLIED_CREDS;
- connssl->connecting_state = ssl_connect_2_writing;
- infof(data, "schannel: a client certificate has been requested\n");
- return CURLE_OK;
- }
-
/* check if the handshake needs to be continued */
if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
- for(i = 0; i < 3; i++) {
+ for(i = 0; i < 2; i++) {
/* search for handshake tokens that need to be send */
if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
infof(data, "schannel: sending next handshake data: "
"sending %lu bytes...\n", outbuf[i].cbBuffer);
/* send handshake token to server */
- 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)) {
+ code = Curl_write_plain(conn, conn->sock[sockindex],
+ outbuf[i].pvBuffer, outbuf[i].cbBuffer,
+ &written);
+ if((code != 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(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
infof(data, "schannel: encrypted data length: %lu\n", inbuf[1].cbBuffer);
/*
- There are two cases where we could be getting extra data here:
- 1) If we're renegotiating a connection and the handshake is already
- complete (from the server perspective), it can encrypted app data
- (not handshake data) in an extra buffer at this point.
- 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
- connection and this extra data is part of the handshake.
- We should process the data immediately; waiting for the socket to
- be ready may fail since the server is done sending handshake data.
- */
+ There are two cases where we could be getting extra data here:
+ 1) If we're renegotiating a connection and the handshake is already
+ complete (from the server perspective), it can encrypted app data
+ (not handshake data) in an extra buffer at this point.
+ 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
+ connection and this extra data is part of the handshake.
+ We should process the data immediately; waiting for the socket to
+ be ready may fail since the server is done sending handshake data.
+ */
/* check if the remaining data is less than the total amount
and therefore begins after the already processed data */
if(connssl->encdata_offset > inbuf[1].cbBuffer) {
memmove(connssl->encdata_buffer,
(connssl->encdata_buffer + connssl->encdata_offset) -
- inbuf[1].cbBuffer, inbuf[1].cbBuffer);
+ inbuf[1].cbBuffer, inbuf[1].cbBuffer);
connssl->encdata_offset = inbuf[1].cbBuffer;
if(sspi_status == SEC_I_CONTINUE_NEEDED) {
doread = FALSE;
SecBuffer outbuf[4];
SecBufferDesc outbuf_desc;
SECURITY_STATUS sspi_status = SEC_E_OK;
- CURLcode result;
+ CURLcode code;
/* check if the maximum stream sizes were queried */
if(connssl->stream_sizes.cbMaximumMessage == 0) {
sspi_status = s_pSecFn->QueryContextAttributes(
- &connssl->ctxt->ctxt_handle,
- SECPKG_ATTR_STREAM_SIZES,
- &connssl->stream_sizes);
+ &connssl->ctxt->ctxt_handle,
+ SECPKG_ATTR_STREAM_SIZES,
+ &connssl->stream_sizes);
if(sspi_status != SEC_E_OK) {
*err = CURLE_SEND_ERROR;
return -1;
/* calculate the complete message length and allocate a buffer for it */
data_len = connssl->stream_sizes.cbHeader + len +
- connssl->stream_sizes.cbTrailer;
+ connssl->stream_sizes.cbTrailer;
data = (unsigned char *) malloc(data_len);
if(data == NULL) {
*err = CURLE_OUT_OF_MEMORY;
len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
/*
- It's important to send the full message which includes the header,
- encrypted payload, and trailer. Until the client receives all the
- data a coherent message has not been delivered and the client
- can't read any of it.
-
- If we wanted to buffer the unwritten encrypted bytes, we would
- tell the client that all data it has requested to be sent has been
- sent. The unwritten encrypted bytes would be the first bytes to
- send on the next invocation.
- Here's the catch with this - if we tell the client that all the
- bytes have been sent, will the client call this method again to
- send the buffered data? Looking at who calls this function, it
- seems the answer is NO.
+ It's important to send the full message which includes the header,
+ encrypted payload, and trailer. Until the client receives all the
+ data a coherent message has not been delivered and the client
+ can't read any of it.
+
+ If we wanted to buffer the unwritten encrypted bytes, we would
+ tell the client that all data it has requested to be sent has been
+ sent. The unwritten encrypted bytes would be the first bytes to
+ send on the next invocation.
+ Here's the catch with this - if we tell the client that all the
+ bytes have been sent, will the client call this method again to
+ send the buffered data? Looking at who calls this function, it
+ seems the answer is NO.
*/
/* send entire message or fail */
}
/* socket is writable */
- result = Curl_write_plain(conn, conn->sock[sockindex], data + written,
- len - written, &this_write);
- if(result == CURLE_AGAIN)
+ code = Curl_write_plain(conn, conn->sock[sockindex], data + written,
+ len - written, &this_write);
+ if(code == CURLE_AGAIN)
continue;
- else if(result != CURLE_OK) {
- *err = result;
+ else if(code != CURLE_OK) {
+ *err = code;
written = -1;
break;
}
char *buf, size_t len, CURLcode *err)
{
size_t size = 0;
- ssize_t nread = -1;
+ ssize_t nread = 0, ret = -1;
+ CURLcode result;
struct SessionHandle *data = conn->data;
struct ssl_connect_data *connssl = &conn->ssl[sockindex];
unsigned char *reallocated_buffer;
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;
- }
- 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 */
+ /* buffer to store previously received and decrypted data */
+ if(connssl->decdata_buffer == NULL) {
+ connssl->decdata_offset = 0;
+ connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
+ connssl->decdata_buffer = malloc(connssl->decdata_length);
+ if(connssl->decdata_buffer == NULL) {
+ failf(data, "schannel: unable to allocate memory");
+ *err = CURLE_OUT_OF_MEMORY;
+ return -1;
+ }
}
- 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;
- }
+ /* increase buffer in order to fit the requested amount of data */
+ if(connssl->encdata_length - connssl->encdata_offset <
+ CURL_SCHANNEL_BUFFER_FREE_SIZE || connssl->encdata_length < len) {
+ /* increase internal encrypted data buffer */
+ reallocated_length = connssl->encdata_offset +
+ CURL_SCHANNEL_BUFFER_FREE_SIZE;
+ /* make sure that the requested amount of data fits */
+ if(reallocated_length < len) {
+ reallocated_length = len;
+ }
+ reallocated_buffer = realloc(connssl->encdata_buffer,
+ reallocated_length);
+
+ if(reallocated_buffer == NULL) {
+ failf(data, "schannel: unable to re-allocate memory");
+ *err = CURLE_OUT_OF_MEMORY;
+ return -1;
+ }
+ else {
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 */
+ /* read encrypted data from socket */
+ infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
+ connssl->encdata_offset, connssl->encdata_length);
+ size = connssl->encdata_length - connssl->encdata_offset;
+ if(size > 0) {
*err = Curl_read_plain(conn->sock[sockindex],
- (char *)(connssl->encdata_buffer +
- connssl->encdata_offset),
+ (char *) (connssl->encdata_buffer + connssl->encdata_offset),
size, &nread);
- 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");
- }
- else if(nread > 0) {
- connssl->encdata_offset += (size_t)nread;
- infof(data, "schannel: encrypted data got %zd\n", nread);
+ /* check for received data */
+ if(*err != CURLE_OK)
+ ret = -1;
+ else {
+ if(nread > 0)
+ /* increase encrypted data buffer offset */
+ connssl->encdata_offset += nread;
+ ret = nread;
}
+ infof(data, "schannel: encrypted data got %zd\n", ret);
}
infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
connssl->encdata_offset, connssl->encdata_length);
- /* decrypt loop */
+ /* check if we still have some data in our buffers */
while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK &&
- (!len || connssl->decdata_offset < len ||
- connssl->recv_connection_closed)) {
+ connssl->decdata_offset < len) {
/* prepare data buffer for DecryptMessage call */
InitSecBuffer(&inbuf[0], SECBUFFER_DATA, connssl->encdata_buffer,
curlx_uztoul(connssl->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);
/* http://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 we need more data */
+ if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
+ infof(data, "schannel: failed to decrypt data, need more data\n");
+ *err = CURLE_AGAIN;
+ return -1;
+ }
+
/* 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) {
+ 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) {
}
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;
+ *err = CURLE_OUT_OF_MEMORY;
+ return -1;
+ }
+ else {
+ connssl->decdata_buffer = reallocated_buffer;
+ connssl->decdata_length = reallocated_length;
}
- connssl->decdata_buffer = reallocated_buffer;
- connssl->decdata_length = reallocated_length;
}
/* copy decrypted data to internal buffer */
size = inbuf[1].cbBuffer;
- if(size) {
+ if(size > 0) {
memcpy(connssl->decdata_buffer + connssl->decdata_offset,
inbuf[1].pvBuffer, size);
connssl->decdata_offset += size;
/* 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);
+ 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 {
+ 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;
+ /* check if server wants to renegotiate the connection context */
+ if(sspi_status == SEC_I_RENEGOTIATE) {
+ infof(data, "schannel: remote party requests SSL/TLS renegotiation\n");
+
+ /* begin renegotiation */
+ infof(data, "schannel: renegotiating SSL/TLS connection\n");
+ connssl->state = ssl_connection_negotiating;
+ connssl->connecting_state = ssl_connect_2_writing;
+ result = schannel_connect_common(conn, sockindex, FALSE, &done);
+ if(result)
+ *err = result;
+ else {
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");
- }
- goto cleanup;
+ /* now retry receiving data */
+ return schannel_recv(conn, sockindex, buf, len, err);
}
}
- 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 {
- *err = CURLE_RECV_ERROR;
- infof(data, "schannel: failed to read data from server: %s\n",
- Curl_sspi_strerror(conn, sspi_status));
- goto cleanup;
- }
}
- 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;
- ULONGLONG cm;
- OSVERSIONINFOEX osver;
-
- memset(&osver, 0, sizeof(osver));
- osver.dwOSVersionInfoSize = sizeof(osver);
- osver.dwMajorVersion = 5;
-
- cm = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL);
- cm = VerSetConditionMask(cm, VER_MINORVERSION, VER_EQUAL);
- cm = VerSetConditionMask(cm, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
- cm = VerSetConditionMask(cm, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
-
- isWin2k = VerifyVersionInfo(&osver,
- (VER_MAJORVERSION | VER_MINORVERSION |
- VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR),
- cm);
-
- 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;
-
+ /* copy requested decrypted data to supplied buffer */
size = len < connssl->decdata_offset ? len : connssl->decdata_offset;
- if(size) {
+ if(size > 0) {
memcpy(buf, connssl->decdata_buffer, size);
+ ret = size;
+
+ /* move remaining decrypted data forward to the beginning of buffer */
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 returned %zd\n", size);
infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
connssl->decdata_offset, connssl->decdata_length);
- *err = CURLE_OK;
- return (ssize_t)size;
}
+ else
+ ret = 0;
- 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)
+ /* check if the server closed the connection */
+ if(ret <= 0 && ( /* special check for Windows 2000 Professional */
+ sspi_status == SEC_I_CONTEXT_EXPIRED || (sspi_status == SEC_E_OK &&
+ connssl->encdata_offset > 0 && connssl->encdata_buffer[0] == 0x15))) {
+ infof(data, "schannel: server closed the connection\n");
*err = CURLE_OK;
+ return 0;
+ }
- return *err ? -1 : 0;
+ /* check if something went wrong and we need to return an error */
+ if(ret < 0 && sspi_status != SEC_E_OK) {
+ infof(data, "schannel: failed to read data from server: %s\n",
+ Curl_sspi_strerror(conn, sspi_status));
+ *err = CURLE_RECV_ERROR;
+ return -1;
+ }
+
+ return ret;
}
CURLcode
SECURITY_STATUS sspi_status;
SecBuffer outbuf;
SecBufferDesc outbuf_desc;
- CURLcode result;
+ CURLcode code;
TCHAR *host_name;
DWORD dwshut = SCHANNEL_SHUTDOWN;
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);
+ &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);
Curl_unicodefree(host_name);
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);
+ code = 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)) {
+ if((code != 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);
+ " (bytes written: %zd)\n", curl_easy_strerror(code), written);
}
}
}
{
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;
- }
+ if(cred && cred->cached && cred->refcount == 0) {
+ s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
+ Curl_safefree(cred);
}
}
NULL,
pCertContextServer->hCertStore,
&ChainPara,
- (data->set.ssl_no_revoke ? 0 :
- CERT_CHAIN_REVOCATION_CHECK_CHAIN),
+ 0,
NULL,
&pChainContext)) {
failf(data, "schannel: CertGetCertificateChain failed: %s",
if(result == CURLE_OK) {
CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
- DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED);
+ DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED|
+ CERT_TRUST_REVOCATION_STATUS_UNKNOWN);
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)
+ 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)
+ " CERT_TRUST_IS_PARTIAL_CHAIN");
+ 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)
+ " CERT_TRUST_IS_UNTRUSTED_ROOT");
+ 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);
+ " CERT_TRUST_IS_NOT_TIME_VALID");
+ failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
+ dwTrustErrorMask);
result = CURLE_PEER_FAILED_VERIFICATION;
}
}
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,