]> granicus.if.org Git - curl/commitdiff
schannel: Added SSL/TLS support with Microsoft Windows Schannel SSPI
authorMarc Hoersken <info@marc-hoersken.de>
Mon, 9 Apr 2012 13:40:06 +0000 (15:40 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 11 Jun 2012 17:00:29 +0000 (19:00 +0200)
lib/curl_schannel.c [new file with mode: 0644]
lib/curl_schannel.h [new file with mode: 0644]
lib/sslgen.c
lib/urldata.h

diff --git a/lib/curl_schannel.c b/lib/curl_schannel.c
new file mode 100644 (file)
index 0000000..cfac6ef
--- /dev/null
@@ -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 (file)
index 0000000..3de932b
--- /dev/null
@@ -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 */
index 14649a9ec5b5fe6fd925d3b86298cd6e6abee1a0..28326ddb13dd016921f835f54c48a4a4d6c39700 100644 (file)
@@ -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"
index 20519cf2c18588f36980f3a2f5ea25c67a99dfc8..26c0581fe4a4372097a9bb26f9b4861e24dc1e19 100644 (file)
 #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 {