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_SSLv3:
184 schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
186 case CURL_SSLVERSION_SSLv2:
187 schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
191 /* allocate memory for the re-usable credential handle */
192 connssl->cred = malloc(sizeof(struct curl_schannel_cred));
194 failf(data, "schannel: unable to allocate memory");
195 return CURLE_OUT_OF_MEMORY;
197 memset(connssl->cred, 0, sizeof(struct curl_schannel_cred));
199 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx */
200 sspi_status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
201 SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL,
202 &connssl->cred->cred_handle, &connssl->cred->time_stamp);
204 if(sspi_status != SEC_E_OK) {
205 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
206 failf(data, "schannel: SNI or certificate check failed: %s",
207 Curl_sspi_strerror(conn, sspi_status));
209 failf(data, "schannel: AcquireCredentialsHandle failed: %s",
210 Curl_sspi_strerror(conn, sspi_status));
211 Curl_safefree(connssl->cred);
212 return CURLE_SSL_CONNECT_ERROR;
216 /* setup output buffer */
217 InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
218 InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
220 /* setup request flags */
221 connssl->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
222 ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
225 /* allocate memory for the security context handle */
226 connssl->ctxt = malloc(sizeof(struct curl_schannel_ctxt));
228 failf(data, "schannel: unable to allocate memory");
229 return CURLE_OUT_OF_MEMORY;
231 memset(connssl->ctxt, 0, sizeof(struct curl_schannel_ctxt));
233 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
235 return CURLE_OUT_OF_MEMORY;
237 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */
239 sspi_status = s_pSecFn->InitializeSecurityContext(
240 &connssl->cred->cred_handle, NULL, host_name,
241 connssl->req_flags, 0, 0, NULL, 0, &connssl->ctxt->ctxt_handle,
242 &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp);
244 Curl_unicodefree(host_name);
246 if(sspi_status != SEC_I_CONTINUE_NEEDED) {
247 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
248 failf(data, "schannel: SNI or certificate check failed: %s",
249 Curl_sspi_strerror(conn, sspi_status));
251 failf(data, "schannel: initial InitializeSecurityContext failed: %s",
252 Curl_sspi_strerror(conn, sspi_status));
253 Curl_safefree(connssl->ctxt);
254 return CURLE_SSL_CONNECT_ERROR;
257 infof(data, "schannel: sending initial handshake data: "
258 "sending %lu bytes...\n", outbuf.cbBuffer);
260 /* send initial handshake data which is now stored in output buffer */
261 code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
262 outbuf.cbBuffer, &written);
263 s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
264 if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
265 failf(data, "schannel: failed to send initial handshake data: "
266 "sent %zd of %lu bytes", written, outbuf.cbBuffer);
267 return CURLE_SSL_CONNECT_ERROR;
270 infof(data, "schannel: sent initial handshake data: "
271 "sent %zd bytes\n", written);
273 /* continue to second handshake step */
274 connssl->connecting_state = ssl_connect_2;
280 schannel_connect_step2(struct connectdata *conn, int sockindex)
283 ssize_t nread = -1, written = -1;
284 struct SessionHandle *data = conn->data;
285 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
287 SecBufferDesc outbuf_desc;
289 SecBufferDesc inbuf_desc;
290 SECURITY_STATUS sspi_status = SEC_E_OK;
295 doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
297 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 2/3)\n",
298 conn->host.name, conn->remote_port);
300 /* buffer to store previously received and encrypted data */
301 if(connssl->encdata_buffer == NULL) {
302 connssl->encdata_offset = 0;
303 connssl->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
304 connssl->encdata_buffer = malloc(connssl->encdata_length);
305 if(connssl->encdata_buffer == NULL) {
306 failf(data, "schannel: unable to allocate memory");
307 return CURLE_OUT_OF_MEMORY;
311 /* if we need a bigger buffer to read a full message, increase buffer now */
312 if(connssl->encdata_length - connssl->encdata_offset <
313 CURL_SCHANNEL_BUFFER_FREE_SIZE) {
314 /* increase internal encrypted data buffer */
315 connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
316 connssl->encdata_buffer = realloc(connssl->encdata_buffer,
317 connssl->encdata_length);
319 if(connssl->encdata_buffer == NULL) {
320 failf(data, "schannel: unable to re-allocate memory");
321 return CURLE_OUT_OF_MEMORY;
327 /* read encrypted handshake data from socket */
328 code = Curl_read_plain(conn->sock[sockindex],
329 (char *) (connssl->encdata_buffer + connssl->encdata_offset),
330 connssl->encdata_length - connssl->encdata_offset,
332 if(code == CURLE_AGAIN) {
333 if(connssl->connecting_state != ssl_connect_2_writing)
334 connssl->connecting_state = ssl_connect_2_reading;
335 infof(data, "schannel: failed to receive handshake, "
339 else if((code != CURLE_OK) || (nread == 0)) {
340 failf(data, "schannel: failed to receive handshake, "
341 "SSL/TLS connection failed");
342 return CURLE_SSL_CONNECT_ERROR;
345 /* increase encrypted data buffer offset */
346 connssl->encdata_offset += nread;
349 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
350 connssl->encdata_offset, connssl->encdata_length);
352 /* setup input buffers */
353 InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(connssl->encdata_offset),
354 curlx_uztoul(connssl->encdata_offset));
355 InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
356 InitSecBufferDesc(&inbuf_desc, inbuf, 2);
358 /* setup output buffers */
359 InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
360 InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
361 InitSecBufferDesc(&outbuf_desc, outbuf, 2);
363 if(inbuf[0].pvBuffer == NULL) {
364 failf(data, "schannel: unable to allocate memory");
365 return CURLE_OUT_OF_MEMORY;
368 /* copy received handshake data into input buffer */
369 memcpy(inbuf[0].pvBuffer, connssl->encdata_buffer,
370 connssl->encdata_offset);
372 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
374 return CURLE_OUT_OF_MEMORY;
376 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */
378 sspi_status = s_pSecFn->InitializeSecurityContext(
379 &connssl->cred->cred_handle, &connssl->ctxt->ctxt_handle,
380 host_name, connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL,
381 &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp);
383 Curl_unicodefree(host_name);
385 /* free buffer for received handshake data */
386 Curl_safefree(inbuf[0].pvBuffer);
388 /* check if the handshake was incomplete */
389 if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
390 connssl->connecting_state = ssl_connect_2_reading;
391 infof(data, "schannel: received incomplete message, need more data\n");
395 /* check if the handshake needs to be continued */
396 if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
397 for(i = 0; i < 2; i++) {
398 /* search for handshake tokens that need to be send */
399 if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
400 infof(data, "schannel: sending next handshake data: "
401 "sending %lu bytes...\n", outbuf[i].cbBuffer);
403 /* send handshake token to server */
404 code = Curl_write_plain(conn, conn->sock[sockindex],
405 outbuf[i].pvBuffer, outbuf[i].cbBuffer,
407 if((code != CURLE_OK) || (outbuf[i].cbBuffer != (size_t)written)) {
408 failf(data, "schannel: failed to send next handshake data: "
409 "sent %zd of %lu bytes", written, outbuf[i].cbBuffer);
410 return CURLE_SSL_CONNECT_ERROR;
414 /* free obsolete buffer */
415 if(outbuf[i].pvBuffer != NULL) {
416 s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer);
421 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
422 failf(data, "schannel: SNI or certificate check failed: %s",
423 Curl_sspi_strerror(conn, sspi_status));
425 failf(data, "schannel: next InitializeSecurityContext failed: %s",
426 Curl_sspi_strerror(conn, sspi_status));
427 return CURLE_SSL_CONNECT_ERROR;
430 /* check if there was additional remaining encrypted data */
431 if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
432 infof(data, "schannel: encrypted data length: %lu\n", inbuf[1].cbBuffer);
434 There are two cases where we could be getting extra data here:
435 1) If we're renegotiating a connection and the handshake is already
436 complete (from the server perspective), it can encrypted app data
437 (not handshake data) in an extra buffer at this point.
438 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
439 connection and this extra data is part of the handshake.
440 We should process the data immediately; waiting for the socket to
441 be ready may fail since the server is done sending handshake data.
443 /* check if the remaining data is less than the total amount
444 and therefore begins after the already processed data */
445 if(connssl->encdata_offset > inbuf[1].cbBuffer) {
446 memmove(connssl->encdata_buffer,
447 (connssl->encdata_buffer + connssl->encdata_offset) -
448 inbuf[1].cbBuffer, inbuf[1].cbBuffer);
449 connssl->encdata_offset = inbuf[1].cbBuffer;
450 if(sspi_status == SEC_I_CONTINUE_NEEDED) {
457 connssl->encdata_offset = 0;
462 /* check if the handshake needs to be continued */
463 if(sspi_status == SEC_I_CONTINUE_NEEDED) {
464 connssl->connecting_state = ssl_connect_2_reading;
468 /* check if the handshake is complete */
469 if(sspi_status == SEC_E_OK) {
470 connssl->connecting_state = ssl_connect_3;
471 infof(data, "schannel: SSL/TLS handshake complete\n");
475 /* Windows CE doesn't do any server certificate validation.
476 We have to do it manually. */
477 if(data->set.ssl.verifypeer)
478 return verify_certificate(conn, sockindex);
485 schannel_connect_step3(struct connectdata *conn, int sockindex)
487 CURLcode retcode = CURLE_OK;
488 struct SessionHandle *data = conn->data;
489 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
490 struct curl_schannel_cred *old_cred = NULL;
493 DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
495 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 3/3)\n",
496 conn->host.name, conn->remote_port);
498 /* check if the required context attributes are met */
499 if(connssl->ret_flags != connssl->req_flags) {
500 if(!(connssl->ret_flags & ISC_RET_SEQUENCE_DETECT))
501 failf(data, "schannel: failed to setup sequence detection");
502 if(!(connssl->ret_flags & ISC_RET_REPLAY_DETECT))
503 failf(data, "schannel: failed to setup replay detection");
504 if(!(connssl->ret_flags & ISC_RET_CONFIDENTIALITY))
505 failf(data, "schannel: failed to setup confidentiality");
506 if(!(connssl->ret_flags & ISC_RET_ALLOCATED_MEMORY))
507 failf(data, "schannel: failed to setup memory allocation");
508 if(!(connssl->ret_flags & ISC_RET_STREAM))
509 failf(data, "schannel: failed to setup stream orientation");
510 return CURLE_SSL_CONNECT_ERROR;
513 /* increment the reference counter of the credential/session handle */
514 if(connssl->cred && connssl->ctxt) {
515 connssl->cred->refcount++;
516 infof(data, "schannel: incremented credential handle refcount = %d\n",
517 connssl->cred->refcount);
520 /* save the current session data for possible re-use */
521 incache = !(Curl_ssl_getsessionid(conn, (void**)&old_cred, NULL));
523 if(old_cred != connssl->cred) {
524 infof(data, "schannel: old credential handle is stale, removing\n");
525 Curl_ssl_delsessionid(conn, (void*)old_cred);
530 retcode = Curl_ssl_addsessionid(conn, (void*)connssl->cred,
531 sizeof(struct curl_schannel_cred));
533 failf(data, "schannel: failed to store credential handle");
537 connssl->cred->cached = TRUE;
538 infof(data, "schannel: stored credential handle in session cache\n");
542 connssl->connecting_state = ssl_connect_done;
548 schannel_connect_common(struct connectdata *conn, int sockindex,
549 bool nonblocking, bool *done)
552 struct SessionHandle *data = conn->data;
553 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
554 curl_socket_t sockfd = conn->sock[sockindex];
558 /* check if the connection has already been established */
559 if(ssl_connection_complete == connssl->state) {
564 if(ssl_connect_1 == connssl->connecting_state) {
565 /* check out how much more time we're allowed */
566 timeout_ms = Curl_timeleft(data, NULL, TRUE);
569 /* no need to continue if time already is up */
570 failf(data, "SSL/TLS connection timeout");
571 return CURLE_OPERATION_TIMEDOUT;
574 retcode = schannel_connect_step1(conn, sockindex);
579 while(ssl_connect_2 == connssl->connecting_state ||
580 ssl_connect_2_reading == connssl->connecting_state ||
581 ssl_connect_2_writing == connssl->connecting_state) {
583 /* check out how much more time we're allowed */
584 timeout_ms = Curl_timeleft(data, NULL, TRUE);
587 /* no need to continue if time already is up */
588 failf(data, "SSL/TLS connection timeout");
589 return CURLE_OPERATION_TIMEDOUT;
592 /* if ssl is expecting something, check if it's available. */
593 if(connssl->connecting_state == ssl_connect_2_reading
594 || connssl->connecting_state == ssl_connect_2_writing) {
596 curl_socket_t writefd = ssl_connect_2_writing ==
597 connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
598 curl_socket_t readfd = ssl_connect_2_reading ==
599 connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
601 what = Curl_socket_ready(readfd, writefd, nonblocking ? 0 : timeout_ms);
604 failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO);
605 return CURLE_SSL_CONNECT_ERROR;
614 failf(data, "SSL/TLS connection timeout");
615 return CURLE_OPERATION_TIMEDOUT;
618 /* socket is readable or writable */
621 /* Run transaction, and return to the caller if it failed or if
622 * this connection is part of a multi handle and this loop would
623 * execute again. This permits the owner of a multi handle to
624 * abort a connection attempt before step2 has completed while
625 * ensuring that a client using select() or epoll() will always
626 * have a valid fdset to wait on.
628 retcode = schannel_connect_step2(conn, sockindex);
629 if(retcode || (nonblocking &&
630 (ssl_connect_2 == connssl->connecting_state ||
631 ssl_connect_2_reading == connssl->connecting_state ||
632 ssl_connect_2_writing == connssl->connecting_state)))
635 } /* repeat step2 until all transactions are done. */
637 if(ssl_connect_3 == connssl->connecting_state) {
638 retcode = schannel_connect_step3(conn, sockindex);
643 if(ssl_connect_done == connssl->connecting_state) {
644 connssl->state = ssl_connection_complete;
645 conn->recv[sockindex] = schannel_recv;
646 conn->send[sockindex] = schannel_send;
652 /* reset our connection state machine */
653 connssl->connecting_state = ssl_connect_1;
659 schannel_send(struct connectdata *conn, int sockindex,
660 const void *buf, size_t len, CURLcode *err)
662 ssize_t written = -1;
664 unsigned char *data = NULL;
665 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
667 SecBufferDesc outbuf_desc;
668 SECURITY_STATUS sspi_status = SEC_E_OK;
671 /* check if the maximum stream sizes were queried */
672 if(connssl->stream_sizes.cbMaximumMessage == 0) {
673 sspi_status = s_pSecFn->QueryContextAttributes(
674 &connssl->ctxt->ctxt_handle,
675 SECPKG_ATTR_STREAM_SIZES,
676 &connssl->stream_sizes);
677 if(sspi_status != SEC_E_OK) {
678 *err = CURLE_SEND_ERROR;
683 /* check if the buffer is longer than the maximum message length */
684 if(len > connssl->stream_sizes.cbMaximumMessage) {
685 *err = CURLE_SEND_ERROR;
689 /* calculate the complete message length and allocate a buffer for it */
690 data_len = connssl->stream_sizes.cbHeader + len +
691 connssl->stream_sizes.cbTrailer;
692 data = (unsigned char*) malloc(data_len);
694 *err = CURLE_OUT_OF_MEMORY;
698 /* setup output buffers (header, data, trailer, empty) */
699 InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER,
700 data, connssl->stream_sizes.cbHeader);
701 InitSecBuffer(&outbuf[1], SECBUFFER_DATA,
702 data + connssl->stream_sizes.cbHeader, curlx_uztoul(len));
703 InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER,
704 data + connssl->stream_sizes.cbHeader + len,
705 connssl->stream_sizes.cbTrailer);
706 InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0);
707 InitSecBufferDesc(&outbuf_desc, outbuf, 4);
709 /* copy data into output buffer */
710 memcpy(outbuf[1].pvBuffer, buf, len);
712 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */
713 sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt->ctxt_handle, 0,
716 /* check if the message was encrypted */
717 if(sspi_status == SEC_E_OK) {
720 /* send the encrypted message including header, data and trailer */
721 len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
724 It's important to send the full message which includes the header,
725 encrypted payload, and trailer. Until the client receives all the
726 data a coherent message has not been delivered and the client
727 can't read any of it.
729 If we wanted to buffer the unwritten encrypted bytes, we would
730 tell the client that all data it has requested to be sent has been
731 sent. The unwritten encrypted bytes would be the first bytes to
732 send on the next invocation.
733 Here's the catch with this - if we tell the client that all the
734 bytes have been sent, will the client call this method again to
735 send the buffered data? Looking at who calls this function, it
736 seems the answer is NO.
739 /* send entire message or fail */
740 while(len > (size_t)written) {
747 timeleft = Curl_timeleft(conn->data, NULL, TRUE);
749 /* we already got the timeout */
750 failf(conn->data, "schannel: timed out sending data "
751 "(bytes sent: %zd)", written);
752 *err = CURLE_OPERATION_TIMEDOUT;
757 what = Curl_socket_ready(CURL_SOCKET_BAD, conn->sock[sockindex],
761 failf(conn->data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
762 *err = CURLE_SEND_ERROR;
767 failf(conn->data, "schannel: timed out sending data "
768 "(bytes sent: %zd)", written);
769 *err = CURLE_OPERATION_TIMEDOUT;
773 /* socket is writable */
775 code = Curl_write_plain(conn, conn->sock[sockindex], data + written,
776 len - written, &this_write);
777 if(code == CURLE_AGAIN)
779 else if(code != CURLE_OK) {
785 written += this_write;
788 else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) {
789 *err = CURLE_OUT_OF_MEMORY;
792 *err = CURLE_SEND_ERROR;
797 if(len == (size_t)written)
798 /* Encrypted message including header, data and trailer entirely sent.
799 The return value is the number of unencrypted bytes that were sent. */
800 written = outbuf[1].cbBuffer;
806 schannel_recv(struct connectdata *conn, int sockindex,
807 char *buf, size_t len, CURLcode *err)
810 ssize_t nread = 0, ret = -1;
812 struct SessionHandle *data = conn->data;
813 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
816 SecBufferDesc inbuf_desc;
817 SECURITY_STATUS sspi_status = SEC_E_OK;
819 infof(data, "schannel: client wants to read %zu bytes\n", len);
822 /* buffer to store previously received and decrypted data */
823 if(connssl->decdata_buffer == NULL) {
824 connssl->decdata_offset = 0;
825 connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
826 connssl->decdata_buffer = malloc(connssl->decdata_length);
827 if(connssl->decdata_buffer == NULL) {
828 failf(data, "schannel: unable to allocate memory");
829 *err = CURLE_OUT_OF_MEMORY;
834 /* increase buffer in order to fit the requested amount of data */
835 while(connssl->encdata_length - connssl->encdata_offset <
836 CURL_SCHANNEL_BUFFER_FREE_SIZE || connssl->encdata_length < len) {
837 /* increase internal encrypted data buffer */
838 connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
839 connssl->encdata_buffer = realloc(connssl->encdata_buffer,
840 connssl->encdata_length);
842 if(connssl->encdata_buffer == NULL) {
843 failf(data, "schannel: unable to re-allocate memory");
844 *err = CURLE_OUT_OF_MEMORY;
849 /* read encrypted data from socket */
850 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
851 connssl->encdata_offset, connssl->encdata_length);
852 size = connssl->encdata_length - connssl->encdata_offset;
854 *err = Curl_read_plain(conn->sock[sockindex],
855 (char *) (connssl->encdata_buffer + connssl->encdata_offset),
857 /* check for received data */
862 /* increase encrypted data buffer offset */
863 connssl->encdata_offset += nread;
866 infof(data, "schannel: encrypted data got %zd\n", ret);
869 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
870 connssl->encdata_offset, connssl->encdata_length);
872 /* check if we still have some data in our buffers */
873 while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK &&
874 connssl->decdata_offset < len) {
875 /* prepare data buffer for DecryptMessage call */
876 InitSecBuffer(&inbuf[0], SECBUFFER_DATA, connssl->encdata_buffer,
877 curlx_uztoul(connssl->encdata_offset));
879 /* we need 3 more empty input buffers for possible output */
880 InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
881 InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0);
882 InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0);
884 InitSecBufferDesc(&inbuf_desc, inbuf, 4);
886 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */
887 sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle,
888 &inbuf_desc, 0, NULL);
890 /* check if we need more data */
891 if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
892 infof(data, "schannel: failed to decrypt data, need more data\n");
897 /* check if everything went fine (server may want to renegotiate
899 if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE ||
900 sspi_status == SEC_I_CONTEXT_EXPIRED) {
901 /* check for successfully decrypted data */
902 if(inbuf[1].BufferType == SECBUFFER_DATA) {
903 infof(data, "schannel: decrypted data length: %lu\n",
906 /* increase buffer in order to fit the received amount of data */
907 size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ?
908 inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE;
909 while(connssl->decdata_length - connssl->decdata_offset < size ||
910 connssl->decdata_length < len) {
911 /* increase internal decrypted data buffer */
912 connssl->decdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
913 connssl->decdata_buffer = realloc(connssl->decdata_buffer,
914 connssl->decdata_length);
916 if(connssl->decdata_buffer == NULL) {
917 failf(data, "schannel: unable to re-allocate memory");
918 *err = CURLE_OUT_OF_MEMORY;
923 /* copy decrypted data to internal buffer */
924 size = inbuf[1].cbBuffer;
926 memcpy(connssl->decdata_buffer + connssl->decdata_offset,
927 inbuf[1].pvBuffer, size);
928 connssl->decdata_offset += size;
931 infof(data, "schannel: decrypted data added: %zu\n", size);
932 infof(data, "schannel: decrypted data cached: offset %zu length %zu\n",
933 connssl->decdata_offset, connssl->decdata_length);
936 /* check for remaining encrypted data */
937 if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
938 infof(data, "schannel: encrypted data length: %lu\n",
941 /* check if the remaining data is less than the total amount
942 * and therefore begins after the already processed data
944 if(connssl->encdata_offset > inbuf[3].cbBuffer) {
945 /* move remaining encrypted data forward to the beginning of
947 memmove(connssl->encdata_buffer,
948 (connssl->encdata_buffer + connssl->encdata_offset) -
949 inbuf[3].cbBuffer, inbuf[3].cbBuffer);
950 connssl->encdata_offset = inbuf[3].cbBuffer;
953 infof(data, "schannel: encrypted data cached: offset %zu length %zu\n",
954 connssl->encdata_offset, connssl->encdata_length);
957 /* reset encrypted buffer offset, because there is no data remaining */
958 connssl->encdata_offset = 0;
962 /* check if server wants to renegotiate the connection context */
963 if(sspi_status == SEC_I_RENEGOTIATE) {
964 infof(data, "schannel: remote party requests SSL/TLS renegotiation\n");
966 /* begin renegotiation */
967 infof(data, "schannel: renegotiating SSL/TLS connection\n");
968 connssl->state = ssl_connection_negotiating;
969 connssl->connecting_state = ssl_connect_2_writing;
970 retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
974 infof(data, "schannel: SSL/TLS connection renegotiated\n");
975 /* now retry receiving data */
976 return schannel_recv(conn, sockindex, buf, len, err);
981 infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
982 connssl->decdata_offset, connssl->decdata_length);
984 /* copy requested decrypted data to supplied buffer */
985 size = len < connssl->decdata_offset ? len : connssl->decdata_offset;
987 memcpy(buf, connssl->decdata_buffer, size);
990 /* move remaining decrypted data forward to the beginning of buffer */
991 memmove(connssl->decdata_buffer, connssl->decdata_buffer + size,
992 connssl->decdata_offset - size);
993 connssl->decdata_offset -= size;
995 infof(data, "schannel: decrypted data returned %zd\n", size);
996 infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
997 connssl->decdata_offset, connssl->decdata_length);
1000 /* check if the server closed the connection */
1001 if(ret <= 0 && ( /* special check for Windows 2000 Professional */
1002 sspi_status == SEC_I_CONTEXT_EXPIRED || (sspi_status == SEC_E_OK &&
1003 connssl->encdata_offset > 0 && connssl->encdata_buffer[0] == 0x15))) {
1004 infof(data, "schannel: server closed the connection\n");
1009 /* check if something went wrong and we need to return an error */
1010 if(ret < 0 && sspi_status != SEC_E_OK) {
1011 infof(data, "schannel: failed to read data from server: %s\n",
1012 Curl_sspi_strerror(conn, sspi_status));
1013 *err = CURLE_RECV_ERROR;
1021 Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex,
1024 return schannel_connect_common(conn, sockindex, TRUE, done);
1028 Curl_schannel_connect(struct connectdata *conn, int sockindex)
1033 retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
1042 bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex)
1044 const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1046 if(connssl->use) /* SSL/TLS is in use */
1047 return (connssl->encdata_offset > 0 ||
1048 connssl->decdata_offset > 0 ) ? TRUE : FALSE;
1053 void Curl_schannel_close(struct connectdata *conn, int sockindex)
1055 if(conn->ssl[sockindex].use)
1056 /* if the SSL/TLS channel hasn't been shut down yet, do that now. */
1057 Curl_ssl_shutdown(conn, sockindex);
1060 int Curl_schannel_shutdown(struct connectdata *conn, int sockindex)
1062 /* See http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
1063 * Shutting Down an Schannel Connection
1065 struct SessionHandle *data = conn->data;
1066 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1068 infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
1069 conn->host.name, conn->remote_port);
1071 if(connssl->cred && connssl->ctxt) {
1072 SecBufferDesc BuffDesc;
1074 SECURITY_STATUS sspi_status;
1076 SecBufferDesc outbuf_desc;
1079 DWORD dwshut = SCHANNEL_SHUTDOWN;
1081 InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
1082 InitSecBufferDesc(&BuffDesc, &Buffer, 1);
1084 sspi_status = s_pSecFn->ApplyControlToken(&connssl->ctxt->ctxt_handle,
1087 if(sspi_status != SEC_E_OK)
1088 failf(data, "schannel: ApplyControlToken failure: %s",
1089 Curl_sspi_strerror(conn, sspi_status));
1091 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
1093 return CURLE_OUT_OF_MEMORY;
1095 /* setup output buffer */
1096 InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
1097 InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
1099 sspi_status = s_pSecFn->InitializeSecurityContext(
1100 &connssl->cred->cred_handle,
1101 &connssl->ctxt->ctxt_handle,
1108 &connssl->ctxt->ctxt_handle,
1110 &connssl->ret_flags,
1111 &connssl->ctxt->time_stamp);
1113 Curl_unicodefree(host_name);
1115 if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
1116 /* send close message which is in output buffer */
1118 code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
1119 outbuf.cbBuffer, &written);
1121 s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
1122 if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
1123 infof(data, "schannel: failed to send close msg: %s"
1124 " (bytes written: %zd)\n", curl_easy_strerror(code), written);
1128 /* free SSPI Schannel API security context handle */
1130 infof(data, "schannel: clear security context handle\n");
1131 s_pSecFn->DeleteSecurityContext(&connssl->ctxt->ctxt_handle);
1132 Curl_safefree(connssl->ctxt);
1135 /* free SSPI Schannel API credential handle */
1137 /* decrement the reference counter of the credential/session handle */
1138 if(connssl->cred->refcount > 0) {
1139 connssl->cred->refcount--;
1140 infof(data, "schannel: decremented credential handle refcount = %d\n",
1141 connssl->cred->refcount);
1144 /* if the handle was not cached and the refcount is zero */
1145 if(!connssl->cred->cached && connssl->cred->refcount == 0) {
1146 infof(data, "schannel: clear credential handle\n");
1147 s_pSecFn->FreeCredentialsHandle(&connssl->cred->cred_handle);
1148 Curl_safefree(connssl->cred);
1153 /* free internal buffer for received encrypted data */
1154 if(connssl->encdata_buffer != NULL) {
1155 Curl_safefree(connssl->encdata_buffer);
1156 connssl->encdata_length = 0;
1157 connssl->encdata_offset = 0;
1160 /* free internal buffer for received decrypted data */
1161 if(connssl->decdata_buffer != NULL) {
1162 Curl_safefree(connssl->decdata_buffer);
1163 connssl->decdata_length = 0;
1164 connssl->decdata_offset = 0;
1170 void Curl_schannel_session_free(void *ptr)
1172 struct curl_schannel_cred *cred = ptr;
1174 if(cred && cred->cached && cred->refcount == 0) {
1175 s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
1176 Curl_safefree(cred);
1180 int Curl_schannel_init(void)
1182 return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
1185 void Curl_schannel_cleanup(void)
1187 Curl_sspi_global_cleanup();
1190 size_t Curl_schannel_version(char *buffer, size_t size)
1192 size = snprintf(buffer, size, "WinSSL");
1198 static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
1200 SECURITY_STATUS status;
1201 struct SessionHandle *data = conn->data;
1202 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1203 CURLcode result = CURLE_OK;
1204 CERT_CONTEXT *pCertContextServer = NULL;
1205 const CERT_CHAIN_CONTEXT *pChainContext = NULL;
1207 status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle,
1208 SECPKG_ATTR_REMOTE_CERT_CONTEXT,
1209 &pCertContextServer);
1211 if((status != SEC_E_OK) || (pCertContextServer == NULL)) {
1212 failf(data, "schannel: Failed to read remote certificate context: %s",
1213 Curl_sspi_strerror(conn, status));
1214 result = CURLE_PEER_FAILED_VERIFICATION;
1217 if(result == CURLE_OK) {
1218 CERT_CHAIN_PARA ChainPara;
1219 memset(&ChainPara, 0, sizeof(ChainPara));
1220 ChainPara.cbSize = sizeof(ChainPara);
1222 if(!CertGetCertificateChain(NULL,
1225 pCertContextServer->hCertStore,
1230 failf(data, "schannel: CertGetCertificateChain failed: %s",
1231 Curl_sspi_strerror(conn, GetLastError()));
1232 pChainContext = NULL;
1233 result = CURLE_PEER_FAILED_VERIFICATION;
1236 if(result == CURLE_OK) {
1237 CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
1238 DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED|
1239 CERT_TRUST_REVOCATION_STATUS_UNKNOWN);
1240 dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
1241 if(dwTrustErrorMask) {
1242 if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
1243 failf(data, "schannel: CertGetCertificateChain trust error"
1244 " CERT_TRUST_IS_PARTIAL_CHAIN");
1245 if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
1246 failf(data, "schannel: CertGetCertificateChain trust error"
1247 " CERT_TRUST_IS_UNTRUSTED_ROOT");
1248 if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
1249 failf(data, "schannel: CertGetCertificateChain trust error"
1250 " CERT_TRUST_IS_NOT_TIME_VALID");
1251 failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
1253 result = CURLE_PEER_FAILED_VERIFICATION;
1258 if(result == CURLE_OK) {
1259 if(data->set.ssl.verifyhost) {
1260 TCHAR cert_hostname_buff[128];
1262 xcharp_u cert_hostname;
1265 cert_hostname.const_tchar_ptr = cert_hostname_buff;
1266 hostname.tchar_ptr = Curl_convert_UTF8_to_tchar(conn->host.name);
1268 len = CertGetNameString(pCertContextServer,
1272 cert_hostname.tchar_ptr,
1274 if(len > 0 && *cert_hostname.tchar_ptr == '*') {
1275 /* this is a wildcard cert. try matching the last len - 1 chars */
1276 int hostname_len = strlen(conn->host.name);
1277 cert_hostname.tchar_ptr++;
1278 if(_tcsicmp(cert_hostname.const_tchar_ptr,
1279 hostname.const_tchar_ptr + hostname_len - len + 2) != 0)
1280 result = CURLE_PEER_FAILED_VERIFICATION;
1282 else if(len == 0 || _tcsicmp(hostname.const_tchar_ptr,
1283 cert_hostname.const_tchar_ptr) != 0) {
1284 result = CURLE_PEER_FAILED_VERIFICATION;
1286 if(result == CURLE_PEER_FAILED_VERIFICATION) {
1287 char *_cert_hostname;
1288 _cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname.tchar_ptr);
1289 failf(data, "schannel: CertGetNameString() certificate hostname "
1290 "(%s) did not match connection (%s)",
1291 _cert_hostname, conn->host.name);
1292 Curl_unicodefree(_cert_hostname);
1294 Curl_unicodefree(hostname.tchar_ptr);
1299 CertFreeCertificateChain(pChainContext);
1301 if(pCertContextServer)
1302 CertFreeCertificateContext(pCertContextServer);
1306 #endif /* _WIN32_WCE */
1308 #endif /* USE_SCHANNEL */