diff options
-rw-r--r-- | lib/curl_schannel.c | 848 | ||||
-rw-r--r-- | lib/curl_schannel.h | 65 | ||||
-rw-r--r-- | lib/sslgen.c | 2 | ||||
-rw-r--r-- | lib/urldata.h | 17 |
4 files changed, 932 insertions, 0 deletions
diff --git a/lib/curl_schannel.c b/lib/curl_schannel.c new file mode 100644 index 000000000..cfac6ef0d --- /dev/null +++ b/lib/curl_schannel.c @@ -0,0 +1,848 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ + +/* + * Source file for all SChannel-specific code for the TLS/SSL layer. No code + * but sslgen.c should ever call or use these functions. + * + */ + +/* + * Based upon the PolarSSL implementation in polarssl.c and polarssl.h: + * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com> + * + * Based upon the CyaSSL implementation in cyassl.c and cyassl.h: + * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al. + * + * Thanks for code and inspiration! + */ + +/* + * TODO list for TLS/SSL implementation: + * - implement session handling and re-use + * - implement write buffering + * - implement verification options + * - implement verification results + * - implement SSL/TLS shutdown + * - special cases: negotiation, certificates, algorithms + */ + +#include "setup.h" + +#ifdef USE_WINDOWS_SSPI +#ifdef USE_SCHANNEL + +#include <schnlsp.h> + +#include "urldata.h" +#include "curl_sspi.h" +#include "curl_schannel.h" +#include "sslgen.h" +#include "sendf.h" +#include "connect.h" /* for the connect timeout */ +#include "select.h" /* for the socket readyness */ +#include "inet_pton.h" /* for IP addr SNI check */ + +#define _MPRINTF_REPLACE /* use our functions only */ +#include <curl/mprintf.h> +#include "curl_memory.h" +/* The last #include file should be: */ +#include "memdebug.h" + +/* Uncomment to force verbose output + * #define infof(x, y, ...) printf(y, __VA_ARGS__) + * #define failf(x, y, ...) printf(y, __VA_ARGS__) + */ + +static Curl_recv schannel_recv; +static Curl_send schannel_send; + +static CURLcode +schannel_connect_step1(struct connectdata *conn, int sockindex) { + ssize_t write = -1; + struct SessionHandle *data = conn->data; + struct ssl_connect_data* connssl = &conn->ssl[sockindex]; + SecBuffer outbuf; + SecBufferDesc outbuf_desc; + SCHANNEL_CRED schannel_cred; + SECURITY_STATUS sspi_status = SEC_E_OK; + struct in_addr addr; +#ifdef ENABLE_IPV6 + struct in6_addr addr6; +#endif + + infof(data, "schannel: Connecting to %s:%d (step 1/3)\n", + conn->host.name, conn->remote_port); + + /* setup Schannel API options */ + memset(&schannel_cred, 0, sizeof(schannel_cred)); + schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; + schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION | + SCH_CRED_REVOCATION_CHECK_CHAIN; + + if(Curl_inet_pton(AF_INET, conn->host.name, &addr) || +#ifdef ENABLE_IPV6 + Curl_inet_pton(AF_INET6, conn->host.name, &addr6) || +#endif + data->set.ssl.verifyhost < 2) { + schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK; + infof(data, "schannel: using IP address, disable SNI servername check\n"); + } + + switch(data->set.ssl.version) { + 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_SSLv3: + schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT; + break; + case CURL_SSLVERSION_SSLv2: + schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT; + break; + } + + /* TODO: implement verification options */ + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx */ + sspi_status = s_pSecFn->AcquireCredentialsHandleA(NULL, + UNISP_NAME_A, SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, + NULL, NULL, &connssl->cred_handle, &connssl->time_stamp); + + if(sspi_status != SEC_E_OK) { + if(sspi_status == SEC_E_WRONG_PRINCIPAL) + failf(data, "schannel: SNI or certificate check failed\n"); + else + failf(data, "schannel: AcquireCredentialsHandleA failed: %d\n", + sspi_status); + return CURLE_SSL_CONNECT_ERROR; + } + + connssl->schannel = TRUE; + + /* setup output buffer */ + outbuf.pvBuffer = NULL; + outbuf.cbBuffer = 0; + outbuf.BufferType = SECBUFFER_EMPTY; + + outbuf_desc.pBuffers = &outbuf; + outbuf_desc.cBuffers = 1; + outbuf_desc.ulVersion = SECBUFFER_VERSION; + + /* setup request flags */ + connssl->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT | + ISC_REQ_CONFIDENTIALITY | ISC_REQ_INTEGRITY | + ISC_REQ_EXTENDED_ERROR | ISC_REQ_ALLOCATE_MEMORY | + ISC_REQ_STREAM; + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */ + sspi_status = s_pSecFn->InitializeSecurityContextA(&connssl->cred_handle, + NULL, conn->host.name, connssl->req_flags, 0, 0, NULL, 0, + &connssl->ctxt_handle, &outbuf_desc, + &connssl->ret_flags, &connssl->time_stamp); + + if(sspi_status != SEC_I_CONTINUE_NEEDED) { + if(sspi_status == SEC_E_WRONG_PRINCIPAL) + failf(data, "schannel: SNI or certificate check failed\n"); + else + failf(data, "schannel: initial InitializeSecurityContextA failed: %d\n", + sspi_status); + return CURLE_SSL_CONNECT_ERROR; + } + + infof(data, "schannel: sending initial handshake data: %d ...\n", + outbuf.cbBuffer); + + /* send initial handshake data which is now stored in output buffer */ + write = swrite(conn->sock[sockindex], outbuf.pvBuffer, outbuf.cbBuffer); + s_pSecFn->FreeContextBuffer(outbuf.pvBuffer); + if(write != outbuf.cbBuffer) { + failf(data, "schannel: failed to send initial handshake data: %d\n", write); + return CURLE_SSL_CONNECT_ERROR; + } + + infof(data, "schannel: sent initial handshake data: %d\n", write); + + /* continue to second handshake step */ + connssl->connecting_state = ssl_connect_2; + + return CURLE_OK; +} + +static CURLcode +schannel_connect_step2(struct connectdata *conn, int sockindex) { + int i; + ssize_t read = -1, write = -1; + struct SessionHandle *data = conn->data; + struct ssl_connect_data* connssl = &conn->ssl[sockindex]; + SecBuffer outbuf[2]; + SecBufferDesc outbuf_desc; + SecBuffer inbuf[2]; + SecBufferDesc inbuf_desc; + SECURITY_STATUS sspi_status = SEC_E_OK; + + infof(data, "schannel: Connecting to %s:%d (step 2/3)\n", + conn->host.name, conn->remote_port); + + connssl->connecting_state = ssl_connect_2; + + /* buffer to store previously received and encrypted data */ + if(connssl->encdata_buffer == NULL) { + connssl->encdata_offset = 0; + connssl->encdata_length = 4096; + connssl->encdata_buffer = malloc(connssl->encdata_length); + if(connssl->encdata_buffer == NULL) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + } + + /* read encrypted handshake data from socket */ + read = sread(conn->sock[sockindex], + connssl->encdata_buffer + connssl->encdata_offset, + connssl->encdata_length - connssl->encdata_offset); + if(read < 0) { + connssl->connecting_state = ssl_connect_2_reading; + infof(data, "schannel: failed to receive handshake, waiting for more: %d\n", + read); + return CURLE_OK; + } + else if(read == 0) { + failf(data, "schannel: failed to receive handshake, connection failed\n"); + return CURLE_SSL_CONNECT_ERROR; + } + else if(read > 0) { + /* increase encrypted data buffer offset */ + connssl->encdata_offset += read; + } + + infof(data, "schannel: encrypted data buffer %d/%d\n", + connssl->encdata_offset, connssl->encdata_length); + + /* setup input buffers */ + inbuf[0].pvBuffer = malloc(connssl->encdata_offset); + inbuf[0].cbBuffer = connssl->encdata_offset; + inbuf[0].BufferType = SECBUFFER_TOKEN; + + inbuf[1].pvBuffer = NULL; + inbuf[1].cbBuffer = 0; + inbuf[1].BufferType = SECBUFFER_EMPTY; + + inbuf_desc.pBuffers = &inbuf[0]; + inbuf_desc.cBuffers = 2; + inbuf_desc.ulVersion = SECBUFFER_VERSION; + + /* setup output buffers */ + outbuf[0].pvBuffer = NULL; + outbuf[0].cbBuffer = 0; + outbuf[0].BufferType = SECBUFFER_TOKEN; + + outbuf[1].pvBuffer = NULL; + outbuf[1].cbBuffer = 0; + outbuf[1].BufferType = SECBUFFER_ALERT; + + outbuf_desc.pBuffers = &outbuf[0]; + outbuf_desc.cBuffers = 2; + outbuf_desc.ulVersion = SECBUFFER_VERSION; + + if(inbuf[0].pvBuffer == NULL) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + + /* copy received handshake data into input buffer */ + memcpy(inbuf[0].pvBuffer, connssl->encdata_buffer, connssl->encdata_offset); + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */ + sspi_status = s_pSecFn->InitializeSecurityContextA( + &connssl->cred_handle, &connssl->ctxt_handle, conn->host.name, + connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL, &outbuf_desc, + &connssl->ret_flags, &connssl->time_stamp); + + /* free buffer for received handshake data */ + free(inbuf[0].pvBuffer); + + /* check if the handshake was incomplete */ + if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) { + connssl->connecting_state = ssl_connect_2_reading; + infof(data, "schannel: received incomplete message, need more data: %d\n", + sspi_status); + 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 < 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: %d ...\n", + outbuf[i].cbBuffer); + + /* send handshake token to server */ + write = swrite(conn->sock[sockindex], + outbuf[i].pvBuffer, outbuf[i].cbBuffer); + if(write != outbuf[i].cbBuffer) { + failf(data, "schannel: failed to send next handshake data: %d\n", + write); + return CURLE_SSL_CONNECT_ERROR; + } + } + + /* free obsolete buffer */ + if(outbuf[i].pvBuffer != NULL) { + s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer); + } + } + } + else { + if(sspi_status == SEC_E_WRONG_PRINCIPAL) + failf(data, "schannel: SNI or certificate check failed\n"); + else + failf(data, "schannel: next InitializeSecurityContextA failed: %d\n", + sspi_status); + return CURLE_SSL_CONNECT_ERROR; + } + + /* check if there was additional remaining encrypted data */ + if(inbuf[1].BufferType = SECBUFFER_EXTRA) { + infof(data, "schannel: encrypted data length: %d\n", inbuf[1].cbBuffer); + + /* 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); + connssl->encdata_offset = inbuf[1].cbBuffer; + } + } + else { + connssl->encdata_offset = 0; + } + + /* check if the handshake needs to be continued */ + if(sspi_status == SEC_I_CONTINUE_NEEDED) { + connssl->connecting_state = ssl_connect_2_reading; + return CURLE_OK; + } + + /* check if the handshake is complete */ + if(sspi_status == SEC_E_OK) { + infof(data, "schannel: handshake complete\n"); + + /* TODO: implement verification results */ + + connssl->connecting_state = ssl_connect_3; + infof(data, "SSL connected\n"); + } + + return CURLE_OK; +} + +static CURLcode +schannel_connect_step3(struct connectdata *conn, int sockindex) { + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); + + connssl->connecting_state = ssl_connect_done; + + return CURLE_OK; +} + +static CURLcode +schannel_connect_common(struct connectdata *conn, int sockindex, + bool nonblocking, bool *done) { + CURLcode retcode; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + curl_socket_t sockfd = conn->sock[sockindex]; + long timeout_ms; + int what; + + /* check if the connection has already been established */ + if(ssl_connection_complete == connssl->state) { + *done = TRUE; + return CURLE_OK; + } + + if(ssl_connect_1 == connssl->connecting_state) { + /* check out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + retcode = schannel_connect_step1(conn, sockindex); + if(retcode) + return retcode; + } + + while(ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state) { + + /* check out how much more time we're allowed */ + timeout_ms = Curl_timeleft(data, NULL, TRUE); + + if(timeout_ms < 0) { + /* no need to continue if time already is up */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + + /* if ssl is expecting something, check if it's available. */ + 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_ready(readfd, writefd, nonblocking ? 0 : timeout_ms); + if(what < 0) { + /* fatal error */ + failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO); + return CURLE_SSL_CONNECT_ERROR; + } + else if(0 == what) { + if(nonblocking) { + *done = FALSE; + return CURLE_OK; + } + else { + /* timeout */ + failf(data, "SSL connection timeout"); + return CURLE_OPERATION_TIMEDOUT; + } + } + /* socket is readable or writable */ + } + + /* Run transaction, and return to the caller if it failed or if + * this connection is part of a multi handle and this loop would + * execute again. This permits the owner of a multi handle to + * abort a connection attempt before step2 has completed while + * ensuring that a client using select() or epoll() will always + * have a valid fdset to wait on. + */ + retcode = schannel_connect_step2(conn, sockindex); + if(retcode || (nonblocking && + (ssl_connect_2 == connssl->connecting_state || + ssl_connect_2_reading == connssl->connecting_state || + ssl_connect_2_writing == connssl->connecting_state))) + return retcode; + + } /* repeat step2 until all transactions are done. */ + + if(ssl_connect_3 == connssl->connecting_state) { + retcode = schannel_connect_step3(conn, sockindex); + if(retcode) + return retcode; + } + + if(ssl_connect_done == connssl->connecting_state) { + connssl->state = ssl_connection_complete; + conn->recv[sockindex] = schannel_recv; + conn->send[sockindex] = schannel_send; + *done = TRUE; + } + else + *done = FALSE; + + /* reset our connection state machine */ + 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 ret = -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; + + /* check if the maximum stream sizes were queried */ + if(connssl->stream_sizes.cbMaximumMessage == 0) { + sspi_status = s_pSecFn->QueryContextAttributesA(&connssl->ctxt_handle, + SECPKG_ATTR_STREAM_SIZES, + &connssl->stream_sizes); + if(sspi_status != SEC_E_OK) { + *err = CURLE_SEND_ERROR; + return -1; + } + } + + /* check if the buffer is longer than the maximum message length */ + if(len > connssl->stream_sizes.cbMaximumMessage) { + *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; + data = (unsigned char*) malloc(data_len); + if(data == NULL) { + *err = CURLE_OUT_OF_MEMORY; + return -1; + } + + /* setup output buffers (header, data, trailer, empty) */ + outbuf[0].pvBuffer = data; + outbuf[0].cbBuffer = connssl->stream_sizes.cbHeader; + outbuf[0].BufferType = SECBUFFER_STREAM_HEADER; + + outbuf[1].pvBuffer = data + connssl->stream_sizes.cbHeader; + outbuf[1].cbBuffer = len; + outbuf[1].BufferType = SECBUFFER_DATA; + + outbuf[2].pvBuffer = data + connssl->stream_sizes.cbHeader + len; + outbuf[2].cbBuffer = connssl->stream_sizes.cbTrailer; + outbuf[2].BufferType = SECBUFFER_STREAM_TRAILER; + + outbuf[3].pvBuffer = NULL; + outbuf[3].cbBuffer = 0; + outbuf[3].BufferType = SECBUFFER_EMPTY; + + outbuf_desc.pBuffers = &outbuf[0]; + outbuf_desc.cBuffers = 4; + outbuf_desc.ulVersion = SECBUFFER_VERSION; + + /* copy data into output buffer */ + memcpy(outbuf[1].pvBuffer, buf, len); + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ + sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt_handle, 0, + &outbuf_desc, 0); + + /* check if the message was encrypted */ + if(sspi_status == SEC_E_OK) { + /* send the encrypted message including header, data and trailer */ + len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer; + ret = swrite(conn->sock[sockindex], data, len); + /* TODO: implement write buffering */ + } + else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) { + *err = CURLE_OUT_OF_MEMORY; + } + else{ + *err = CURLE_SEND_ERROR; + } + + free(data); + + return ret; +} + +static ssize_t +schannel_recv(struct connectdata *conn, int sockindex, + char *buf, size_t len, CURLcode *err) { + int i = 0; + size_t size = 0; + ssize_t read = 0, ret = -1; + CURLcode retcode; + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + bool done = FALSE; + SecBuffer inbuf[4]; + SecBufferDesc inbuf_desc; + SECURITY_STATUS sspi_status = SEC_E_OK; + + infof(data, "schannel: client wants to read %d\n", len); + *err = CURLE_OK; + + /* buffer to store previously received and decrypted data */ + if(connssl->decdata_buffer == NULL) { + connssl->decdata_offset = 0; + connssl->decdata_length = 4096; + connssl->decdata_buffer = malloc(connssl->decdata_length); + if(connssl->decdata_buffer == NULL) { + failf(data, "schannel: unable to allocate memory"); + return CURLE_OUT_OF_MEMORY; + } + } + + /* increase buffers in order to fit the requested amount of data */ + while(connssl->encdata_length - connssl->encdata_offset < 2048 || + connssl->encdata_length < len) { + /* increase internal encrypted data buffer */ + connssl->encdata_length += 2048; + connssl->encdata_buffer = realloc(connssl->encdata_buffer, + connssl->encdata_length); + if(connssl->encdata_buffer == NULL) { + failf(data, "schannel: unable to re-allocate memory"); + *err = CURLE_OUT_OF_MEMORY; + return -1; + } + + /* increase internal decrypted data buffer */ + connssl->decdata_length += 2048; + connssl->decdata_buffer = realloc(connssl->decdata_buffer, + connssl->decdata_length); + if(connssl->decdata_buffer == NULL) { + failf(data, "schannel: unable to re-allocate memory"); + *err = CURLE_OUT_OF_MEMORY; + return -1; + } + } + + /* read encrypted data from socket */ + infof(data, "schannel: encrypted data buffer %d/%d\n", + connssl->encdata_offset, connssl->encdata_length); + size = connssl->encdata_length - connssl->encdata_offset; + if(size > 0) { + read = sread(conn->sock[sockindex], + connssl->encdata_buffer + connssl->encdata_offset, size); + infof(data, "schannel: encrypted data received %d\n", read); + + /* check for received data */ + if(read > 0) { + /* increase encrypted data buffer offset */ + connssl->encdata_offset += read; + } + } + + infof(data, "schannel: encrypted data buffer %d/%d\n", + connssl->encdata_offset, connssl->encdata_length); + + /* check if we still have some data in our buffers */ + while(connssl->encdata_offset > 0 && + sspi_status != SEC_E_INCOMPLETE_MESSAGE) { + + /* prepare data buffer for DecryptMessage call */ + inbuf[0].pvBuffer = connssl->encdata_buffer; + inbuf[0].cbBuffer = connssl->encdata_offset; + inbuf[0].BufferType = SECBUFFER_DATA; + + /* we need 3 more empty input buffers for possible output */ + inbuf[1].pvBuffer = NULL; + inbuf[1].cbBuffer = 0; + inbuf[1].BufferType = SECBUFFER_EMPTY; + + inbuf[2].pvBuffer = NULL; + inbuf[2].cbBuffer = 0; + inbuf[2].BufferType = SECBUFFER_EMPTY; + + inbuf[3].pvBuffer = NULL; + inbuf[3].cbBuffer = 0; + inbuf[3].BufferType = SECBUFFER_EMPTY; + + inbuf_desc.pBuffers = &inbuf[0]; + inbuf_desc.cBuffers = 4; + inbuf_desc.ulVersion = SECBUFFER_VERSION; + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */ + sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt_handle, + &inbuf_desc, 0, NULL); + infof(data, "schannel: DecryptMessage %d\n", sspi_status); + + /* check if everything went fine (server may want to renegotiate context) */ + if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE) { + /* check for successfully decrypted data */ + if(inbuf[1].BufferType == SECBUFFER_DATA) { + infof(data, "schannel: decrypted data length: %d\n", inbuf[1].cbBuffer); + + /* copy decrypted data to internal buffer */ + size = connssl->decdata_length - connssl->decdata_offset; + size = size < inbuf[1].cbBuffer ? size : inbuf[1].cbBuffer; + if(size > 0) { + memcpy(connssl->decdata_buffer + connssl->decdata_offset, + inbuf[1].pvBuffer, size); + connssl->decdata_offset += size; + } + + infof(data, "schannel: decrypted data added: %d\n", size); + infof(data, "schannel: decrypted data cached: %d/%d\n", + connssl->decdata_offset, connssl->decdata_length); + } + + /* check for remaining encrypted data */ + if(inbuf[3].BufferType = SECBUFFER_EXTRA) { + infof(data, "schannel: encrypted data length: %d\n", inbuf[3].cbBuffer); + + /* 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: %d/%d\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: client needs to renegotiate with server\n"); + + /* begin renegotiation */ + connssl->state = ssl_connection_negotiating; + retcode = schannel_connect_common(conn, sockindex, FALSE, &done); + if(retcode) + *err = retcode; + } + } + + /* copy requested decrypted data to supplied buffer */ + size = len < connssl->decdata_offset ? len : connssl->decdata_offset; + 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; + } + + /* reduce internal buffer length to reduce memory usage */ + if(connssl->encdata_length > 4096) { + connssl->encdata_length = connssl->encdata_offset > 0 ? + connssl->encdata_offset + 2048 : 4096; + connssl->encdata_buffer = realloc(connssl->encdata_buffer, + connssl->encdata_length); + } + if(connssl->decdata_length > 4096) { + connssl->decdata_length = connssl->decdata_offset > 0 ? + connssl->decdata_offset + 2048 : 4096; + connssl->decdata_buffer = realloc(connssl->decdata_buffer, + connssl->decdata_length); + } + + /* check if something went wrong and we need to return an error */ + if(ret < 0) { + if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) + *err = CURLE_AGAIN; + else if(sspi_status != SEC_E_OK) + *err = CURLE_RECV_ERROR; + return -1; + } + + /* everything went fine */ + infof(data, "schannel: read returns %d with error code %d\n", ret, *err); + + return ret; +} + +CURLcode +Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex, + bool *done) { + return schannel_connect_common(conn, sockindex, TRUE, done); +} + +CURLcode +Curl_schannel_connect(struct connectdata *conn, int sockindex) { + CURLcode retcode; + bool done = FALSE; + + retcode = schannel_connect_common(conn, sockindex, FALSE, &done); + if(retcode) + return retcode; + + 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->schannel) /* SSL 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) { + struct SessionHandle *data = conn->data; + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + + infof(data, "schannel: Closing connection with %s:%d\n", + conn->host.name, conn->remote_port); + + /* free SSPI Schannel API context and handle */ + if(connssl->schannel) { + s_pSecFn->DeleteSecurityContext(&connssl->ctxt_handle); + s_pSecFn->FreeCredentialsHandle(&connssl->cred_handle); + } + + /* free internal buffer for received encrypted data */ + if(connssl->encdata_buffer != NULL) { + free(connssl->encdata_buffer); + connssl->encdata_buffer = NULL; + connssl->encdata_length = 0; + connssl->encdata_offset = 0; + } + + /* free internal buffer for received decrypted data */ + if(connssl->decdata_buffer != NULL) { + free(connssl->decdata_buffer); + connssl->decdata_buffer = NULL; + connssl->decdata_length = 0; + connssl->decdata_offset = 0; + } +} + +int Curl_schannel_shutdown(struct connectdata *conn, int sockindex) { + return CURLE_NOT_BUILT_IN; /* TODO: implement SSL/TLS shutdown */ +} + +int Curl_schannel_init() { + return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0); +} + +void Curl_schannel_cleanup() { + Curl_sspi_global_cleanup(); +} + +size_t Curl_schannel_version(char *buffer, size_t size) +{ + unsigned long version = s_pSecFn ? s_pSecFn->dwVersion : 0; + return snprintf(buffer, size, "Schannel/%d.%d.%d.%d", + (version>>0)&0xff, (version>>8)&0xff, + (version>>16)&0xff, (version>>24)&0xff); +} + +#endif /* USE_SCHANNEL */ +#endif /* USE_WINDOWS_SSPI */ diff --git a/lib/curl_schannel.h b/lib/curl_schannel.h new file mode 100644 index 000000000..3de932b9d --- /dev/null +++ b/lib/curl_schannel.h @@ -0,0 +1,65 @@ +#ifndef HEADER_SCHANNEL_H +#define HEADER_SCHANNEL_H +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 2012, Marc Hoersken, <info@marc-hoersken.de>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + ***************************************************************************/ +#include "setup.h" + +#ifdef USE_WINDOWS_SSPI +#ifdef USE_SCHANNEL + +#ifndef UNISP_NAME_A +#define UNISP_NAME_A "Microsoft Unified Security Protocol Provider" +#endif + +CURLcode Curl_schannel_connect(struct connectdata *conn, int sockindex); + +CURLcode Curl_schannel_connect_nonblocking(struct connectdata *conn, + int sockindex, + bool *done); + +bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex); +void Curl_schannel_close(struct connectdata *conn, int sockindex); +int Curl_schannel_shutdown(struct connectdata *conn, int sockindex); + +int Curl_schannel_init(); +void Curl_schannel_cleanup(); +size_t Curl_schannel_version(char *buffer, size_t size); + +/* API setup for Schannel */ +#define curlssl_init Curl_schannel_init +#define curlssl_cleanup Curl_schannel_cleanup +#define curlssl_connect Curl_schannel_connect +#define curlssl_connect_nonblocking Curl_schannel_connect_nonblocking +#define curlssl_session_free(x) (x=x, CURLE_NOT_BUILT_IN) +#define curlssl_close_all(x) (x=x, CURLE_NOT_BUILT_IN) +#define curlssl_close Curl_schannel_close +#define curlssl_shutdown Curl_schannel_shutdown +#define curlssl_set_engine(x,y) (x=x, y=y, CURLE_NOT_BUILT_IN) +#define curlssl_set_engine_default(x) (x=x, CURLE_NOT_BUILT_IN) +#define curlssl_engines_list(x) (x=x, (struct curl_slist *)NULL) +#define curlssl_version Curl_schannel_version +#define curlssl_check_cxn(x) (x=x, -1) +#define curlssl_data_pending Curl_schannel_data_pending + +#endif /* USE_SCHANNEL */ +#endif /* USE_WINDOWS_SSPI */ +#endif /* HEADER_SCHANNEL_H */ diff --git a/lib/sslgen.c b/lib/sslgen.c index 14649a9ec..28326ddb1 100644 --- a/lib/sslgen.c +++ b/lib/sslgen.c @@ -33,6 +33,7 @@ Curl_nss_ - prefix for NSS ones Curl_polarssl_ - prefix for PolarSSL ones Curl_cyassl_ - prefix for CyaSSL ones + Curl_schannel_ - prefix for Schannel SSPI ones Note that this source code uses curlssl_* functions, and they are all defines/macros #defined by the lib-specific header files. @@ -57,6 +58,7 @@ #include "polarssl.h" /* PolarSSL versions */ #include "axtls.h" /* axTLS versions */ #include "cyassl.h" /* CyaSSL versions */ +#include "curl_schannel.h" /* Schannel SSPI version */ #include "sendf.h" #include "rawstr.h" #include "url.h" diff --git a/lib/urldata.h b/lib/urldata.h index 20519cf2c..26c0581fe 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -131,6 +131,11 @@ #undef realloc #endif /* USE_AXTLS */ +#ifdef USE_SCHANNEL +#include <schnlsp.h> +#include "curl_sspi.h" +#endif + #ifdef HAVE_NETINET_IN_H #include <netinet/in.h> #endif @@ -282,6 +287,18 @@ struct ssl_connect_data { SSL_CTX* ssl_ctx; SSL* ssl; #endif /* USE_AXTLS */ +#ifdef USE_SCHANNEL + bool schannel; + TimeStamp time_stamp; + CredHandle cred_handle; + CtxtHandle ctxt_handle; + SecPkgContext_StreamSizes stream_sizes; + ssl_connect_state connecting_state; + size_t encdata_length, decdata_length; + size_t encdata_offset, decdata_offset; + unsigned char *encdata_buffer, *decdata_buffer; + unsigned long req_flags, ret_flags; +#endif /* USE_SCHANNEL */ }; struct ssl_config_data { |