1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2012 - 2013, Marc Hoersken, <info@marc-hoersken.de>
9 * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
10 * Copyright (C) 2012 - 2013, Daniel Stenberg, <daniel@haxx.se>, et al.
12 * This software is licensed as described in the file COPYING, which
13 * you should have received as part of this distribution. The terms
14 * are also available at http://curl.haxx.se/docs/copyright.html.
16 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
17 * copies of the Software, and permit persons to whom the Software is
18 * furnished to do so, under the terms of the COPYING file.
20 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21 * KIND, either express or implied.
23 ***************************************************************************/
26 * Source file for all SChannel-specific code for the TLS/SSL layer. No code
27 * but sslgen.c should ever call or use these functions.
32 * Based upon the PolarSSL implementation in polarssl.c and polarssl.h:
33 * Copyright (C) 2010, 2011, Hoi-Ho Chan, <hoiho.chan@gmail.com>
35 * Based upon the CyaSSL implementation in cyassl.c and cyassl.h:
36 * Copyright (C) 1998 - 2012, Daniel Stenberg, <daniel@haxx.se>, et al.
38 * Thanks for code and inspiration!
42 * TODO list for TLS/SSL implementation:
43 * - implement client certificate authentication
44 * - implement custom server certificate validation
45 * - implement cipher/algorithm option
47 * Related articles on MSDN:
48 * - Getting a Certificate for Schannel
49 * http://msdn.microsoft.com/en-us/library/windows/desktop/aa375447.aspx
50 * - Specifying Schannel Ciphers and Cipher Strengths
51 * http://msdn.microsoft.com/en-us/library/windows/desktop/aa380161.aspx
54 #include "curl_setup.h"
58 #ifndef USE_WINDOWS_SSPI
59 # error "Can't compile SCHANNEL support without SSPI."
62 #include "curl_sspi.h"
63 #include "curl_schannel.h"
66 #include "connect.h" /* for the connect timeout */
68 #include "select.h" /* for the socket readyness */
69 #include "inet_pton.h" /* for IP addr SNI check */
70 #include "curl_multibyte.h"
73 #define _MPRINTF_REPLACE /* use our functions only */
74 #include <curl/mprintf.h>
76 #include "curl_memory.h"
77 /* The last #include file should be: */
80 /* Uncomment to force verbose output
81 * #define infof(x, y, ...) printf(y, __VA_ARGS__)
82 * #define failf(x, y, ...) printf(y, __VA_ARGS__)
85 static Curl_recv schannel_recv;
86 static Curl_send schannel_send;
89 static CURLcode verify_certificate(struct connectdata *conn, int sockindex);
92 static void InitSecBuffer(SecBuffer *buffer, unsigned long BufType,
93 void *BufDataPtr, unsigned long BufByteSize)
95 buffer->cbBuffer = BufByteSize;
96 buffer->BufferType = BufType;
97 buffer->pvBuffer = BufDataPtr;
100 static void InitSecBufferDesc(SecBufferDesc *desc, SecBuffer *BufArr,
101 unsigned long NumArrElem)
103 desc->ulVersion = SECBUFFER_VERSION;
104 desc->pBuffers = BufArr;
105 desc->cBuffers = NumArrElem;
109 schannel_connect_step1(struct connectdata *conn, int sockindex)
111 ssize_t written = -1;
112 struct SessionHandle *data = conn->data;
113 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
115 SecBufferDesc outbuf_desc;
116 SCHANNEL_CRED schannel_cred;
117 SECURITY_STATUS sspi_status = SEC_E_OK;
118 struct curl_schannel_cred *old_cred = NULL;
121 struct in6_addr addr6;
126 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 1/3)\n",
127 conn->host.name, conn->remote_port);
129 /* check for an existing re-usable credential handle */
130 if(!Curl_ssl_getsessionid(conn, (void**)&old_cred, NULL)) {
131 connssl->cred = old_cred;
132 infof(data, "schannel: re-using existing credential handle\n");
135 /* setup Schannel API options */
136 memset(&schannel_cred, 0, sizeof(schannel_cred));
137 schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
139 if(data->set.ssl.verifypeer) {
141 /* certificate validation on CE doesn't seem to work right; we'll
142 do it following a more manual process. */
143 schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION |
144 SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
145 SCH_CRED_IGNORE_REVOCATION_OFFLINE;
147 schannel_cred.dwFlags = SCH_CRED_AUTO_CRED_VALIDATION |
148 SCH_CRED_REVOCATION_CHECK_CHAIN;
150 infof(data, "schannel: checking server certificate revocation\n");
153 schannel_cred.dwFlags = SCH_CRED_MANUAL_CRED_VALIDATION |
154 SCH_CRED_IGNORE_NO_REVOCATION_CHECK |
155 SCH_CRED_IGNORE_REVOCATION_OFFLINE;
156 infof(data, "schannel: disable server certificate revocation checks\n");
159 if(Curl_inet_pton(AF_INET, conn->host.name, &addr)
161 || Curl_inet_pton(AF_INET6, conn->host.name, &addr6)
164 schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
165 infof(data, "schannel: using IP address, SNI is being disabled by "
166 "disabling the servername check against the "
167 "subject names in server certificates.\n");
170 if(!data->set.ssl.verifyhost) {
171 schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
172 infof(data, "schannel: verifyhost setting prevents Schannel from "
173 "comparing the supplied target name with the subject "
174 "names in server certificates. Also disables SNI.\n");
177 switch(data->set.ssl.version) {
178 case CURL_SSLVERSION_TLSv1:
179 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT |
180 SP_PROT_TLS1_1_CLIENT |
181 SP_PROT_TLS1_2_CLIENT;
183 case CURL_SSLVERSION_TLSv1_0:
184 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT;
186 case CURL_SSLVERSION_TLSv1_1:
187 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_1_CLIENT;
189 case CURL_SSLVERSION_TLSv1_2:
190 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT;
192 case CURL_SSLVERSION_SSLv3:
193 schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
195 case CURL_SSLVERSION_SSLv2:
196 schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
200 /* allocate memory for the re-usable credential handle */
201 connssl->cred = malloc(sizeof(struct curl_schannel_cred));
203 failf(data, "schannel: unable to allocate memory");
204 return CURLE_OUT_OF_MEMORY;
206 memset(connssl->cred, 0, sizeof(struct curl_schannel_cred));
208 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx */
209 sspi_status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
210 SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL,
211 &connssl->cred->cred_handle, &connssl->cred->time_stamp);
213 if(sspi_status != SEC_E_OK) {
214 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
215 failf(data, "schannel: SNI or certificate check failed: %s",
216 Curl_sspi_strerror(conn, sspi_status));
218 failf(data, "schannel: AcquireCredentialsHandle failed: %s",
219 Curl_sspi_strerror(conn, sspi_status));
220 Curl_safefree(connssl->cred);
221 return CURLE_SSL_CONNECT_ERROR;
225 /* setup output buffer */
226 InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
227 InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
229 /* setup request flags */
230 connssl->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
231 ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
234 /* allocate memory for the security context handle */
235 connssl->ctxt = malloc(sizeof(struct curl_schannel_ctxt));
237 failf(data, "schannel: unable to allocate memory");
238 return CURLE_OUT_OF_MEMORY;
240 memset(connssl->ctxt, 0, sizeof(struct curl_schannel_ctxt));
242 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
244 return CURLE_OUT_OF_MEMORY;
246 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */
248 sspi_status = s_pSecFn->InitializeSecurityContext(
249 &connssl->cred->cred_handle, NULL, host_name,
250 connssl->req_flags, 0, 0, NULL, 0, &connssl->ctxt->ctxt_handle,
251 &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp);
253 Curl_unicodefree(host_name);
255 if(sspi_status != SEC_I_CONTINUE_NEEDED) {
256 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
257 failf(data, "schannel: SNI or certificate check failed: %s",
258 Curl_sspi_strerror(conn, sspi_status));
260 failf(data, "schannel: initial InitializeSecurityContext failed: %s",
261 Curl_sspi_strerror(conn, sspi_status));
262 Curl_safefree(connssl->ctxt);
263 return CURLE_SSL_CONNECT_ERROR;
266 infof(data, "schannel: sending initial handshake data: "
267 "sending %lu bytes...\n", outbuf.cbBuffer);
269 /* send initial handshake data which is now stored in output buffer */
270 code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
271 outbuf.cbBuffer, &written);
272 s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
273 if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
274 failf(data, "schannel: failed to send initial handshake data: "
275 "sent %zd of %lu bytes", written, outbuf.cbBuffer);
276 return CURLE_SSL_CONNECT_ERROR;
279 infof(data, "schannel: sent initial handshake data: "
280 "sent %zd bytes\n", written);
282 /* continue to second handshake step */
283 connssl->connecting_state = ssl_connect_2;
289 schannel_connect_step2(struct connectdata *conn, int sockindex)
292 ssize_t nread = -1, written = -1;
293 struct SessionHandle *data = conn->data;
294 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
296 SecBufferDesc outbuf_desc;
298 SecBufferDesc inbuf_desc;
299 SECURITY_STATUS sspi_status = SEC_E_OK;
304 doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
306 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 2/3)\n",
307 conn->host.name, conn->remote_port);
309 /* buffer to store previously received and encrypted data */
310 if(connssl->encdata_buffer == NULL) {
311 connssl->encdata_offset = 0;
312 connssl->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
313 connssl->encdata_buffer = malloc(connssl->encdata_length);
314 if(connssl->encdata_buffer == NULL) {
315 failf(data, "schannel: unable to allocate memory");
316 return CURLE_OUT_OF_MEMORY;
320 /* if we need a bigger buffer to read a full message, increase buffer now */
321 if(connssl->encdata_length - connssl->encdata_offset <
322 CURL_SCHANNEL_BUFFER_FREE_SIZE) {
323 /* increase internal encrypted data buffer */
324 connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
325 connssl->encdata_buffer = realloc(connssl->encdata_buffer,
326 connssl->encdata_length);
328 if(connssl->encdata_buffer == NULL) {
329 failf(data, "schannel: unable to re-allocate memory");
330 return CURLE_OUT_OF_MEMORY;
336 /* read encrypted handshake data from socket */
337 code = Curl_read_plain(conn->sock[sockindex],
338 (char *) (connssl->encdata_buffer + connssl->encdata_offset),
339 connssl->encdata_length - connssl->encdata_offset,
341 if(code == CURLE_AGAIN) {
342 if(connssl->connecting_state != ssl_connect_2_writing)
343 connssl->connecting_state = ssl_connect_2_reading;
344 infof(data, "schannel: failed to receive handshake, "
348 else if((code != CURLE_OK) || (nread == 0)) {
349 failf(data, "schannel: failed to receive handshake, "
350 "SSL/TLS connection failed");
351 return CURLE_SSL_CONNECT_ERROR;
354 /* increase encrypted data buffer offset */
355 connssl->encdata_offset += nread;
358 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
359 connssl->encdata_offset, connssl->encdata_length);
361 /* setup input buffers */
362 InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(connssl->encdata_offset),
363 curlx_uztoul(connssl->encdata_offset));
364 InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
365 InitSecBufferDesc(&inbuf_desc, inbuf, 2);
367 /* setup output buffers */
368 InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
369 InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
370 InitSecBufferDesc(&outbuf_desc, outbuf, 2);
372 if(inbuf[0].pvBuffer == NULL) {
373 failf(data, "schannel: unable to allocate memory");
374 return CURLE_OUT_OF_MEMORY;
377 /* copy received handshake data into input buffer */
378 memcpy(inbuf[0].pvBuffer, connssl->encdata_buffer,
379 connssl->encdata_offset);
381 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
383 return CURLE_OUT_OF_MEMORY;
385 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */
387 sspi_status = s_pSecFn->InitializeSecurityContext(
388 &connssl->cred->cred_handle, &connssl->ctxt->ctxt_handle,
389 host_name, connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL,
390 &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp);
392 Curl_unicodefree(host_name);
394 /* free buffer for received handshake data */
395 Curl_safefree(inbuf[0].pvBuffer);
397 /* check if the handshake was incomplete */
398 if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
399 connssl->connecting_state = ssl_connect_2_reading;
400 infof(data, "schannel: received incomplete message, need more data\n");
404 /* check if the handshake needs to be continued */
405 if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
406 for(i = 0; i < 2; i++) {
407 /* search for handshake tokens that need to be send */
408 if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
409 infof(data, "schannel: sending next handshake data: "
410 "sending %lu bytes...\n", outbuf[i].cbBuffer);
412 /* send handshake token to server */
413 code = Curl_write_plain(conn, conn->sock[sockindex],
414 outbuf[i].pvBuffer, outbuf[i].cbBuffer,
416 if((code != CURLE_OK) || (outbuf[i].cbBuffer != (size_t)written)) {
417 failf(data, "schannel: failed to send next handshake data: "
418 "sent %zd of %lu bytes", written, outbuf[i].cbBuffer);
419 return CURLE_SSL_CONNECT_ERROR;
423 /* free obsolete buffer */
424 if(outbuf[i].pvBuffer != NULL) {
425 s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer);
430 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
431 failf(data, "schannel: SNI or certificate check failed: %s",
432 Curl_sspi_strerror(conn, sspi_status));
434 failf(data, "schannel: next InitializeSecurityContext failed: %s",
435 Curl_sspi_strerror(conn, sspi_status));
436 return CURLE_SSL_CONNECT_ERROR;
439 /* check if there was additional remaining encrypted data */
440 if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
441 infof(data, "schannel: encrypted data length: %lu\n", inbuf[1].cbBuffer);
443 There are two cases where we could be getting extra data here:
444 1) If we're renegotiating a connection and the handshake is already
445 complete (from the server perspective), it can encrypted app data
446 (not handshake data) in an extra buffer at this point.
447 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
448 connection and this extra data is part of the handshake.
449 We should process the data immediately; waiting for the socket to
450 be ready may fail since the server is done sending handshake data.
452 /* check if the remaining data is less than the total amount
453 and therefore begins after the already processed data */
454 if(connssl->encdata_offset > inbuf[1].cbBuffer) {
455 memmove(connssl->encdata_buffer,
456 (connssl->encdata_buffer + connssl->encdata_offset) -
457 inbuf[1].cbBuffer, inbuf[1].cbBuffer);
458 connssl->encdata_offset = inbuf[1].cbBuffer;
459 if(sspi_status == SEC_I_CONTINUE_NEEDED) {
466 connssl->encdata_offset = 0;
471 /* check if the handshake needs to be continued */
472 if(sspi_status == SEC_I_CONTINUE_NEEDED) {
473 connssl->connecting_state = ssl_connect_2_reading;
477 /* check if the handshake is complete */
478 if(sspi_status == SEC_E_OK) {
479 connssl->connecting_state = ssl_connect_3;
480 infof(data, "schannel: SSL/TLS handshake complete\n");
484 /* Windows CE doesn't do any server certificate validation.
485 We have to do it manually. */
486 if(data->set.ssl.verifypeer)
487 return verify_certificate(conn, sockindex);
494 schannel_connect_step3(struct connectdata *conn, int sockindex)
496 CURLcode retcode = CURLE_OK;
497 struct SessionHandle *data = conn->data;
498 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
499 struct curl_schannel_cred *old_cred = NULL;
502 DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
504 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 3/3)\n",
505 conn->host.name, conn->remote_port);
507 /* check if the required context attributes are met */
508 if(connssl->ret_flags != connssl->req_flags) {
509 if(!(connssl->ret_flags & ISC_RET_SEQUENCE_DETECT))
510 failf(data, "schannel: failed to setup sequence detection");
511 if(!(connssl->ret_flags & ISC_RET_REPLAY_DETECT))
512 failf(data, "schannel: failed to setup replay detection");
513 if(!(connssl->ret_flags & ISC_RET_CONFIDENTIALITY))
514 failf(data, "schannel: failed to setup confidentiality");
515 if(!(connssl->ret_flags & ISC_RET_ALLOCATED_MEMORY))
516 failf(data, "schannel: failed to setup memory allocation");
517 if(!(connssl->ret_flags & ISC_RET_STREAM))
518 failf(data, "schannel: failed to setup stream orientation");
519 return CURLE_SSL_CONNECT_ERROR;
522 /* increment the reference counter of the credential/session handle */
523 if(connssl->cred && connssl->ctxt) {
524 connssl->cred->refcount++;
525 infof(data, "schannel: incremented credential handle refcount = %d\n",
526 connssl->cred->refcount);
529 /* save the current session data for possible re-use */
530 incache = !(Curl_ssl_getsessionid(conn, (void**)&old_cred, NULL));
532 if(old_cred != connssl->cred) {
533 infof(data, "schannel: old credential handle is stale, removing\n");
534 Curl_ssl_delsessionid(conn, (void*)old_cred);
539 retcode = Curl_ssl_addsessionid(conn, (void*)connssl->cred,
540 sizeof(struct curl_schannel_cred));
542 failf(data, "schannel: failed to store credential handle");
546 connssl->cred->cached = TRUE;
547 infof(data, "schannel: stored credential handle in session cache\n");
551 connssl->connecting_state = ssl_connect_done;
557 schannel_connect_common(struct connectdata *conn, int sockindex,
558 bool nonblocking, bool *done)
561 struct SessionHandle *data = conn->data;
562 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
563 curl_socket_t sockfd = conn->sock[sockindex];
567 /* check if the connection has already been established */
568 if(ssl_connection_complete == connssl->state) {
573 if(ssl_connect_1 == connssl->connecting_state) {
574 /* check out how much more time we're allowed */
575 timeout_ms = Curl_timeleft(data, NULL, TRUE);
578 /* no need to continue if time already is up */
579 failf(data, "SSL/TLS connection timeout");
580 return CURLE_OPERATION_TIMEDOUT;
583 retcode = schannel_connect_step1(conn, sockindex);
588 while(ssl_connect_2 == connssl->connecting_state ||
589 ssl_connect_2_reading == connssl->connecting_state ||
590 ssl_connect_2_writing == connssl->connecting_state) {
592 /* check out how much more time we're allowed */
593 timeout_ms = Curl_timeleft(data, NULL, TRUE);
596 /* no need to continue if time already is up */
597 failf(data, "SSL/TLS connection timeout");
598 return CURLE_OPERATION_TIMEDOUT;
601 /* if ssl is expecting something, check if it's available. */
602 if(connssl->connecting_state == ssl_connect_2_reading
603 || connssl->connecting_state == ssl_connect_2_writing) {
605 curl_socket_t writefd = ssl_connect_2_writing ==
606 connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
607 curl_socket_t readfd = ssl_connect_2_reading ==
608 connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
610 what = Curl_socket_ready(readfd, writefd, nonblocking ? 0 : timeout_ms);
613 failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO);
614 return CURLE_SSL_CONNECT_ERROR;
623 failf(data, "SSL/TLS connection timeout");
624 return CURLE_OPERATION_TIMEDOUT;
627 /* socket is readable or writable */
630 /* Run transaction, and return to the caller if it failed or if
631 * this connection is part of a multi handle and this loop would
632 * execute again. This permits the owner of a multi handle to
633 * abort a connection attempt before step2 has completed while
634 * ensuring that a client using select() or epoll() will always
635 * have a valid fdset to wait on.
637 retcode = schannel_connect_step2(conn, sockindex);
638 if(retcode || (nonblocking &&
639 (ssl_connect_2 == connssl->connecting_state ||
640 ssl_connect_2_reading == connssl->connecting_state ||
641 ssl_connect_2_writing == connssl->connecting_state)))
644 } /* repeat step2 until all transactions are done. */
646 if(ssl_connect_3 == connssl->connecting_state) {
647 retcode = schannel_connect_step3(conn, sockindex);
652 if(ssl_connect_done == connssl->connecting_state) {
653 connssl->state = ssl_connection_complete;
654 conn->recv[sockindex] = schannel_recv;
655 conn->send[sockindex] = schannel_send;
661 /* reset our connection state machine */
662 connssl->connecting_state = ssl_connect_1;
668 schannel_send(struct connectdata *conn, int sockindex,
669 const void *buf, size_t len, CURLcode *err)
671 ssize_t written = -1;
673 unsigned char *data = NULL;
674 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
676 SecBufferDesc outbuf_desc;
677 SECURITY_STATUS sspi_status = SEC_E_OK;
680 /* check if the maximum stream sizes were queried */
681 if(connssl->stream_sizes.cbMaximumMessage == 0) {
682 sspi_status = s_pSecFn->QueryContextAttributes(
683 &connssl->ctxt->ctxt_handle,
684 SECPKG_ATTR_STREAM_SIZES,
685 &connssl->stream_sizes);
686 if(sspi_status != SEC_E_OK) {
687 *err = CURLE_SEND_ERROR;
692 /* check if the buffer is longer than the maximum message length */
693 if(len > connssl->stream_sizes.cbMaximumMessage) {
694 *err = CURLE_SEND_ERROR;
698 /* calculate the complete message length and allocate a buffer for it */
699 data_len = connssl->stream_sizes.cbHeader + len +
700 connssl->stream_sizes.cbTrailer;
701 data = (unsigned char*) malloc(data_len);
703 *err = CURLE_OUT_OF_MEMORY;
707 /* setup output buffers (header, data, trailer, empty) */
708 InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER,
709 data, connssl->stream_sizes.cbHeader);
710 InitSecBuffer(&outbuf[1], SECBUFFER_DATA,
711 data + connssl->stream_sizes.cbHeader, curlx_uztoul(len));
712 InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER,
713 data + connssl->stream_sizes.cbHeader + len,
714 connssl->stream_sizes.cbTrailer);
715 InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0);
716 InitSecBufferDesc(&outbuf_desc, outbuf, 4);
718 /* copy data into output buffer */
719 memcpy(outbuf[1].pvBuffer, buf, len);
721 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */
722 sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt->ctxt_handle, 0,
725 /* check if the message was encrypted */
726 if(sspi_status == SEC_E_OK) {
729 /* send the encrypted message including header, data and trailer */
730 len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
733 It's important to send the full message which includes the header,
734 encrypted payload, and trailer. Until the client receives all the
735 data a coherent message has not been delivered and the client
736 can't read any of it.
738 If we wanted to buffer the unwritten encrypted bytes, we would
739 tell the client that all data it has requested to be sent has been
740 sent. The unwritten encrypted bytes would be the first bytes to
741 send on the next invocation.
742 Here's the catch with this - if we tell the client that all the
743 bytes have been sent, will the client call this method again to
744 send the buffered data? Looking at who calls this function, it
745 seems the answer is NO.
748 /* send entire message or fail */
749 while(len > (size_t)written) {
756 timeleft = Curl_timeleft(conn->data, NULL, TRUE);
758 /* we already got the timeout */
759 failf(conn->data, "schannel: timed out sending data "
760 "(bytes sent: %zd)", written);
761 *err = CURLE_OPERATION_TIMEDOUT;
766 what = Curl_socket_ready(CURL_SOCKET_BAD, conn->sock[sockindex],
770 failf(conn->data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
771 *err = CURLE_SEND_ERROR;
776 failf(conn->data, "schannel: timed out sending data "
777 "(bytes sent: %zd)", written);
778 *err = CURLE_OPERATION_TIMEDOUT;
782 /* socket is writable */
784 code = Curl_write_plain(conn, conn->sock[sockindex], data + written,
785 len - written, &this_write);
786 if(code == CURLE_AGAIN)
788 else if(code != CURLE_OK) {
794 written += this_write;
797 else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) {
798 *err = CURLE_OUT_OF_MEMORY;
801 *err = CURLE_SEND_ERROR;
806 if(len == (size_t)written)
807 /* Encrypted message including header, data and trailer entirely sent.
808 The return value is the number of unencrypted bytes that were sent. */
809 written = outbuf[1].cbBuffer;
815 schannel_recv(struct connectdata *conn, int sockindex,
816 char *buf, size_t len, CURLcode *err)
819 ssize_t nread = 0, ret = -1;
821 struct SessionHandle *data = conn->data;
822 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
825 SecBufferDesc inbuf_desc;
826 SECURITY_STATUS sspi_status = SEC_E_OK;
828 infof(data, "schannel: client wants to read %zu bytes\n", len);
831 /* buffer to store previously received and decrypted data */
832 if(connssl->decdata_buffer == NULL) {
833 connssl->decdata_offset = 0;
834 connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
835 connssl->decdata_buffer = malloc(connssl->decdata_length);
836 if(connssl->decdata_buffer == NULL) {
837 failf(data, "schannel: unable to allocate memory");
838 *err = CURLE_OUT_OF_MEMORY;
843 /* increase buffer in order to fit the requested amount of data */
844 while(connssl->encdata_length - connssl->encdata_offset <
845 CURL_SCHANNEL_BUFFER_FREE_SIZE || connssl->encdata_length < len) {
846 /* increase internal encrypted data buffer */
847 connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
848 connssl->encdata_buffer = realloc(connssl->encdata_buffer,
849 connssl->encdata_length);
851 if(connssl->encdata_buffer == NULL) {
852 failf(data, "schannel: unable to re-allocate memory");
853 *err = CURLE_OUT_OF_MEMORY;
858 /* read encrypted data from socket */
859 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
860 connssl->encdata_offset, connssl->encdata_length);
861 size = connssl->encdata_length - connssl->encdata_offset;
863 *err = Curl_read_plain(conn->sock[sockindex],
864 (char *) (connssl->encdata_buffer + connssl->encdata_offset),
866 /* check for received data */
871 /* increase encrypted data buffer offset */
872 connssl->encdata_offset += nread;
875 infof(data, "schannel: encrypted data got %zd\n", ret);
878 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
879 connssl->encdata_offset, connssl->encdata_length);
881 /* check if we still have some data in our buffers */
882 while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK &&
883 connssl->decdata_offset < len) {
884 /* prepare data buffer for DecryptMessage call */
885 InitSecBuffer(&inbuf[0], SECBUFFER_DATA, connssl->encdata_buffer,
886 curlx_uztoul(connssl->encdata_offset));
888 /* we need 3 more empty input buffers for possible output */
889 InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
890 InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0);
891 InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0);
893 InitSecBufferDesc(&inbuf_desc, inbuf, 4);
895 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */
896 sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle,
897 &inbuf_desc, 0, NULL);
899 /* check if we need more data */
900 if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
901 infof(data, "schannel: failed to decrypt data, need more data\n");
906 /* check if everything went fine (server may want to renegotiate
908 if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE ||
909 sspi_status == SEC_I_CONTEXT_EXPIRED) {
910 /* check for successfully decrypted data */
911 if(inbuf[1].BufferType == SECBUFFER_DATA) {
912 infof(data, "schannel: decrypted data length: %lu\n",
915 /* increase buffer in order to fit the received amount of data */
916 size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ?
917 inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE;
918 while(connssl->decdata_length - connssl->decdata_offset < size ||
919 connssl->decdata_length < len) {
920 /* increase internal decrypted data buffer */
921 connssl->decdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
922 connssl->decdata_buffer = realloc(connssl->decdata_buffer,
923 connssl->decdata_length);
925 if(connssl->decdata_buffer == NULL) {
926 failf(data, "schannel: unable to re-allocate memory");
927 *err = CURLE_OUT_OF_MEMORY;
932 /* copy decrypted data to internal buffer */
933 size = inbuf[1].cbBuffer;
935 memcpy(connssl->decdata_buffer + connssl->decdata_offset,
936 inbuf[1].pvBuffer, size);
937 connssl->decdata_offset += size;
940 infof(data, "schannel: decrypted data added: %zu\n", size);
941 infof(data, "schannel: decrypted data cached: offset %zu length %zu\n",
942 connssl->decdata_offset, connssl->decdata_length);
945 /* check for remaining encrypted data */
946 if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
947 infof(data, "schannel: encrypted data length: %lu\n",
950 /* check if the remaining data is less than the total amount
951 * and therefore begins after the already processed data
953 if(connssl->encdata_offset > inbuf[3].cbBuffer) {
954 /* move remaining encrypted data forward to the beginning of
956 memmove(connssl->encdata_buffer,
957 (connssl->encdata_buffer + connssl->encdata_offset) -
958 inbuf[3].cbBuffer, inbuf[3].cbBuffer);
959 connssl->encdata_offset = inbuf[3].cbBuffer;
962 infof(data, "schannel: encrypted data cached: offset %zu length %zu\n",
963 connssl->encdata_offset, connssl->encdata_length);
966 /* reset encrypted buffer offset, because there is no data remaining */
967 connssl->encdata_offset = 0;
971 /* check if server wants to renegotiate the connection context */
972 if(sspi_status == SEC_I_RENEGOTIATE) {
973 infof(data, "schannel: remote party requests SSL/TLS renegotiation\n");
975 /* begin renegotiation */
976 infof(data, "schannel: renegotiating SSL/TLS connection\n");
977 connssl->state = ssl_connection_negotiating;
978 connssl->connecting_state = ssl_connect_2_writing;
979 retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
983 infof(data, "schannel: SSL/TLS connection renegotiated\n");
984 /* now retry receiving data */
985 return schannel_recv(conn, sockindex, buf, len, err);
990 infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
991 connssl->decdata_offset, connssl->decdata_length);
993 /* copy requested decrypted data to supplied buffer */
994 size = len < connssl->decdata_offset ? len : connssl->decdata_offset;
996 memcpy(buf, connssl->decdata_buffer, size);
999 /* move remaining decrypted data forward to the beginning of buffer */
1000 memmove(connssl->decdata_buffer, connssl->decdata_buffer + size,
1001 connssl->decdata_offset - size);
1002 connssl->decdata_offset -= size;
1004 infof(data, "schannel: decrypted data returned %zd\n", size);
1005 infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
1006 connssl->decdata_offset, connssl->decdata_length);
1009 /* check if the server closed the connection */
1010 if(ret <= 0 && ( /* special check for Windows 2000 Professional */
1011 sspi_status == SEC_I_CONTEXT_EXPIRED || (sspi_status == SEC_E_OK &&
1012 connssl->encdata_offset > 0 && connssl->encdata_buffer[0] == 0x15))) {
1013 infof(data, "schannel: server closed the connection\n");
1018 /* check if something went wrong and we need to return an error */
1019 if(ret < 0 && sspi_status != SEC_E_OK) {
1020 infof(data, "schannel: failed to read data from server: %s\n",
1021 Curl_sspi_strerror(conn, sspi_status));
1022 *err = CURLE_RECV_ERROR;
1030 Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex,
1033 return schannel_connect_common(conn, sockindex, TRUE, done);
1037 Curl_schannel_connect(struct connectdata *conn, int sockindex)
1042 retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
1051 bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex)
1053 const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1055 if(connssl->use) /* SSL/TLS is in use */
1056 return (connssl->encdata_offset > 0 ||
1057 connssl->decdata_offset > 0 ) ? TRUE : FALSE;
1062 void Curl_schannel_close(struct connectdata *conn, int sockindex)
1064 if(conn->ssl[sockindex].use)
1065 /* if the SSL/TLS channel hasn't been shut down yet, do that now. */
1066 Curl_ssl_shutdown(conn, sockindex);
1069 int Curl_schannel_shutdown(struct connectdata *conn, int sockindex)
1071 /* See http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
1072 * Shutting Down an Schannel Connection
1074 struct SessionHandle *data = conn->data;
1075 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1077 infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
1078 conn->host.name, conn->remote_port);
1080 if(connssl->cred && connssl->ctxt) {
1081 SecBufferDesc BuffDesc;
1083 SECURITY_STATUS sspi_status;
1085 SecBufferDesc outbuf_desc;
1088 DWORD dwshut = SCHANNEL_SHUTDOWN;
1090 InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
1091 InitSecBufferDesc(&BuffDesc, &Buffer, 1);
1093 sspi_status = s_pSecFn->ApplyControlToken(&connssl->ctxt->ctxt_handle,
1096 if(sspi_status != SEC_E_OK)
1097 failf(data, "schannel: ApplyControlToken failure: %s",
1098 Curl_sspi_strerror(conn, sspi_status));
1100 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
1102 return CURLE_OUT_OF_MEMORY;
1104 /* setup output buffer */
1105 InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
1106 InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
1108 sspi_status = s_pSecFn->InitializeSecurityContext(
1109 &connssl->cred->cred_handle,
1110 &connssl->ctxt->ctxt_handle,
1117 &connssl->ctxt->ctxt_handle,
1119 &connssl->ret_flags,
1120 &connssl->ctxt->time_stamp);
1122 Curl_unicodefree(host_name);
1124 if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
1125 /* send close message which is in output buffer */
1127 code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
1128 outbuf.cbBuffer, &written);
1130 s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
1131 if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
1132 infof(data, "schannel: failed to send close msg: %s"
1133 " (bytes written: %zd)\n", curl_easy_strerror(code), written);
1137 /* free SSPI Schannel API security context handle */
1139 infof(data, "schannel: clear security context handle\n");
1140 s_pSecFn->DeleteSecurityContext(&connssl->ctxt->ctxt_handle);
1141 Curl_safefree(connssl->ctxt);
1144 /* free SSPI Schannel API credential handle */
1146 /* decrement the reference counter of the credential/session handle */
1147 if(connssl->cred->refcount > 0) {
1148 connssl->cred->refcount--;
1149 infof(data, "schannel: decremented credential handle refcount = %d\n",
1150 connssl->cred->refcount);
1153 /* if the handle was not cached and the refcount is zero */
1154 if(!connssl->cred->cached && connssl->cred->refcount == 0) {
1155 infof(data, "schannel: clear credential handle\n");
1156 s_pSecFn->FreeCredentialsHandle(&connssl->cred->cred_handle);
1157 Curl_safefree(connssl->cred);
1162 /* free internal buffer for received encrypted data */
1163 if(connssl->encdata_buffer != NULL) {
1164 Curl_safefree(connssl->encdata_buffer);
1165 connssl->encdata_length = 0;
1166 connssl->encdata_offset = 0;
1169 /* free internal buffer for received decrypted data */
1170 if(connssl->decdata_buffer != NULL) {
1171 Curl_safefree(connssl->decdata_buffer);
1172 connssl->decdata_length = 0;
1173 connssl->decdata_offset = 0;
1179 void Curl_schannel_session_free(void *ptr)
1181 struct curl_schannel_cred *cred = ptr;
1183 if(cred && cred->cached && cred->refcount == 0) {
1184 s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
1185 Curl_safefree(cred);
1189 int Curl_schannel_init(void)
1191 return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
1194 void Curl_schannel_cleanup(void)
1196 Curl_sspi_global_cleanup();
1199 size_t Curl_schannel_version(char *buffer, size_t size)
1201 size = snprintf(buffer, size, "WinSSL");
1207 static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
1209 SECURITY_STATUS status;
1210 struct SessionHandle *data = conn->data;
1211 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1212 CURLcode result = CURLE_OK;
1213 CERT_CONTEXT *pCertContextServer = NULL;
1214 const CERT_CHAIN_CONTEXT *pChainContext = NULL;
1216 status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle,
1217 SECPKG_ATTR_REMOTE_CERT_CONTEXT,
1218 &pCertContextServer);
1220 if((status != SEC_E_OK) || (pCertContextServer == NULL)) {
1221 failf(data, "schannel: Failed to read remote certificate context: %s",
1222 Curl_sspi_strerror(conn, status));
1223 result = CURLE_PEER_FAILED_VERIFICATION;
1226 if(result == CURLE_OK) {
1227 CERT_CHAIN_PARA ChainPara;
1228 memset(&ChainPara, 0, sizeof(ChainPara));
1229 ChainPara.cbSize = sizeof(ChainPara);
1231 if(!CertGetCertificateChain(NULL,
1234 pCertContextServer->hCertStore,
1239 failf(data, "schannel: CertGetCertificateChain failed: %s",
1240 Curl_sspi_strerror(conn, GetLastError()));
1241 pChainContext = NULL;
1242 result = CURLE_PEER_FAILED_VERIFICATION;
1245 if(result == CURLE_OK) {
1246 CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
1247 DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED|
1248 CERT_TRUST_REVOCATION_STATUS_UNKNOWN);
1249 dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
1250 if(dwTrustErrorMask) {
1251 if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
1252 failf(data, "schannel: CertGetCertificateChain trust error"
1253 " CERT_TRUST_IS_PARTIAL_CHAIN");
1254 if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
1255 failf(data, "schannel: CertGetCertificateChain trust error"
1256 " CERT_TRUST_IS_UNTRUSTED_ROOT");
1257 if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
1258 failf(data, "schannel: CertGetCertificateChain trust error"
1259 " CERT_TRUST_IS_NOT_TIME_VALID");
1260 failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
1262 result = CURLE_PEER_FAILED_VERIFICATION;
1267 if(result == CURLE_OK) {
1268 if(data->set.ssl.verifyhost) {
1269 TCHAR cert_hostname_buff[128];
1271 xcharp_u cert_hostname;
1274 cert_hostname.const_tchar_ptr = cert_hostname_buff;
1275 hostname.tchar_ptr = Curl_convert_UTF8_to_tchar(conn->host.name);
1277 len = CertGetNameString(pCertContextServer,
1281 cert_hostname.tchar_ptr,
1283 if(len > 0 && *cert_hostname.tchar_ptr == '*') {
1284 /* this is a wildcard cert. try matching the last len - 1 chars */
1285 int hostname_len = strlen(conn->host.name);
1286 cert_hostname.tchar_ptr++;
1287 if(_tcsicmp(cert_hostname.const_tchar_ptr,
1288 hostname.const_tchar_ptr + hostname_len - len + 2) != 0)
1289 result = CURLE_PEER_FAILED_VERIFICATION;
1291 else if(len == 0 || _tcsicmp(hostname.const_tchar_ptr,
1292 cert_hostname.const_tchar_ptr) != 0) {
1293 result = CURLE_PEER_FAILED_VERIFICATION;
1295 if(result == CURLE_PEER_FAILED_VERIFICATION) {
1296 char *_cert_hostname;
1297 _cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname.tchar_ptr);
1298 failf(data, "schannel: CertGetNameString() certificate hostname "
1299 "(%s) did not match connection (%s)",
1300 _cert_hostname, conn->host.name);
1301 Curl_unicodefree(_cert_hostname);
1303 Curl_unicodefree(hostname.tchar_ptr);
1308 CertFreeCertificateChain(pChainContext);
1310 if(pCertContextServer)
1311 CertFreeCertificateContext(pCertContextServer);
1315 #endif /* _WIN32_WCE */
1317 #endif /* USE_SCHANNEL */