1 /***************************************************************************
3 * Project ___| | | | _ \| |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
8 * Copyright (C) 2012 - 2014, Marc Hoersken, <info@marc-hoersken.de>
9 * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com>
10 * Copyright (C) 2012 - 2014, 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 vtls.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(!data->set.ssl.verifyhost) {
160 schannel_cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
161 infof(data, "schannel: verifyhost setting prevents Schannel from "
162 "comparing the supplied target name with the subject "
163 "names in server certificates. Also disables SNI.\n");
166 switch(data->set.ssl.version) {
167 case CURL_SSLVERSION_TLSv1:
168 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT |
169 SP_PROT_TLS1_1_CLIENT |
170 SP_PROT_TLS1_2_CLIENT;
172 case CURL_SSLVERSION_TLSv1_0:
173 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT;
175 case CURL_SSLVERSION_TLSv1_1:
176 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_1_CLIENT;
178 case CURL_SSLVERSION_TLSv1_2:
179 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_2_CLIENT;
181 case CURL_SSLVERSION_SSLv3:
182 schannel_cred.grbitEnabledProtocols = SP_PROT_SSL3_CLIENT;
184 case CURL_SSLVERSION_SSLv2:
185 schannel_cred.grbitEnabledProtocols = SP_PROT_SSL2_CLIENT;
188 schannel_cred.grbitEnabledProtocols = SP_PROT_TLS1_0_CLIENT |
189 SP_PROT_TLS1_1_CLIENT |
190 SP_PROT_TLS1_2_CLIENT |
195 /* allocate memory for the re-usable credential handle */
196 connssl->cred = (struct curl_schannel_cred *)
197 malloc(sizeof(struct curl_schannel_cred));
199 failf(data, "schannel: unable to allocate memory");
200 return CURLE_OUT_OF_MEMORY;
202 memset(connssl->cred, 0, sizeof(struct curl_schannel_cred));
204 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx */
205 sspi_status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME,
206 SECPKG_CRED_OUTBOUND, NULL, &schannel_cred, NULL, NULL,
207 &connssl->cred->cred_handle, &connssl->cred->time_stamp);
209 if(sspi_status != SEC_E_OK) {
210 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
211 failf(data, "schannel: SNI or certificate check failed: %s",
212 Curl_sspi_strerror(conn, sspi_status));
214 failf(data, "schannel: AcquireCredentialsHandle failed: %s",
215 Curl_sspi_strerror(conn, sspi_status));
216 Curl_safefree(connssl->cred);
217 return CURLE_SSL_CONNECT_ERROR;
221 /* Warn if SNI is disabled due to use of an IP address */
222 if(Curl_inet_pton(AF_INET, conn->host.name, &addr)
224 || Curl_inet_pton(AF_INET6, conn->host.name, &addr6)
227 infof(data, "schannel: using IP address, SNI is not supported by OS.\n");
230 /* setup output buffer */
231 InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
232 InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
234 /* setup request flags */
235 connssl->req_flags = ISC_REQ_SEQUENCE_DETECT | ISC_REQ_REPLAY_DETECT |
236 ISC_REQ_CONFIDENTIALITY | ISC_REQ_ALLOCATE_MEMORY |
239 /* allocate memory for the security context handle */
240 connssl->ctxt = (struct curl_schannel_ctxt *)
241 malloc(sizeof(struct curl_schannel_ctxt));
243 failf(data, "schannel: unable to allocate memory");
244 return CURLE_OUT_OF_MEMORY;
246 memset(connssl->ctxt, 0, sizeof(struct curl_schannel_ctxt));
248 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
250 return CURLE_OUT_OF_MEMORY;
252 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */
254 sspi_status = s_pSecFn->InitializeSecurityContext(
255 &connssl->cred->cred_handle, NULL, host_name,
256 connssl->req_flags, 0, 0, NULL, 0, &connssl->ctxt->ctxt_handle,
257 &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp);
259 Curl_unicodefree(host_name);
261 if(sspi_status != SEC_I_CONTINUE_NEEDED) {
262 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
263 failf(data, "schannel: SNI or certificate check failed: %s",
264 Curl_sspi_strerror(conn, sspi_status));
266 failf(data, "schannel: initial InitializeSecurityContext failed: %s",
267 Curl_sspi_strerror(conn, sspi_status));
268 Curl_safefree(connssl->ctxt);
269 return CURLE_SSL_CONNECT_ERROR;
272 infof(data, "schannel: sending initial handshake data: "
273 "sending %lu bytes...\n", outbuf.cbBuffer);
275 /* send initial handshake data which is now stored in output buffer */
276 code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
277 outbuf.cbBuffer, &written);
278 s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
279 if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
280 failf(data, "schannel: failed to send initial handshake data: "
281 "sent %zd of %lu bytes", written, outbuf.cbBuffer);
282 return CURLE_SSL_CONNECT_ERROR;
285 infof(data, "schannel: sent initial handshake data: "
286 "sent %zd bytes\n", written);
288 /* continue to second handshake step */
289 connssl->connecting_state = ssl_connect_2;
295 schannel_connect_step2(struct connectdata *conn, int sockindex)
298 ssize_t nread = -1, written = -1;
299 struct SessionHandle *data = conn->data;
300 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
302 SecBufferDesc outbuf_desc;
304 SecBufferDesc inbuf_desc;
305 SECURITY_STATUS sspi_status = SEC_E_OK;
310 doread = (connssl->connecting_state != ssl_connect_2_writing) ? TRUE : FALSE;
312 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 2/3)\n",
313 conn->host.name, conn->remote_port);
315 if(!connssl->cred || !connssl->ctxt)
316 return CURLE_SSL_CONNECT_ERROR;
318 /* buffer to store previously received and encrypted data */
319 if(connssl->encdata_buffer == NULL) {
320 connssl->encdata_offset = 0;
321 connssl->encdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
322 connssl->encdata_buffer = malloc(connssl->encdata_length);
323 if(connssl->encdata_buffer == NULL) {
324 failf(data, "schannel: unable to allocate memory");
325 return CURLE_OUT_OF_MEMORY;
329 /* if we need a bigger buffer to read a full message, increase buffer now */
330 if(connssl->encdata_length - connssl->encdata_offset <
331 CURL_SCHANNEL_BUFFER_FREE_SIZE) {
332 /* increase internal encrypted data buffer */
333 connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
334 connssl->encdata_buffer = realloc(connssl->encdata_buffer,
335 connssl->encdata_length);
337 if(connssl->encdata_buffer == NULL) {
338 failf(data, "schannel: unable to re-allocate memory");
339 return CURLE_OUT_OF_MEMORY;
345 /* read encrypted handshake data from socket */
346 code = Curl_read_plain(conn->sock[sockindex],
347 (char *) (connssl->encdata_buffer + connssl->encdata_offset),
348 connssl->encdata_length - connssl->encdata_offset,
350 if(code == CURLE_AGAIN) {
351 if(connssl->connecting_state != ssl_connect_2_writing)
352 connssl->connecting_state = ssl_connect_2_reading;
353 infof(data, "schannel: failed to receive handshake, "
357 else if((code != CURLE_OK) || (nread == 0)) {
358 failf(data, "schannel: failed to receive handshake, "
359 "SSL/TLS connection failed");
360 return CURLE_SSL_CONNECT_ERROR;
363 /* increase encrypted data buffer offset */
364 connssl->encdata_offset += nread;
367 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
368 connssl->encdata_offset, connssl->encdata_length);
370 /* setup input buffers */
371 InitSecBuffer(&inbuf[0], SECBUFFER_TOKEN, malloc(connssl->encdata_offset),
372 curlx_uztoul(connssl->encdata_offset));
373 InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
374 InitSecBufferDesc(&inbuf_desc, inbuf, 2);
376 /* setup output buffers */
377 InitSecBuffer(&outbuf[0], SECBUFFER_TOKEN, NULL, 0);
378 InitSecBuffer(&outbuf[1], SECBUFFER_ALERT, NULL, 0);
379 InitSecBufferDesc(&outbuf_desc, outbuf, 2);
381 if(inbuf[0].pvBuffer == NULL) {
382 failf(data, "schannel: unable to allocate memory");
383 return CURLE_OUT_OF_MEMORY;
386 /* copy received handshake data into input buffer */
387 memcpy(inbuf[0].pvBuffer, connssl->encdata_buffer,
388 connssl->encdata_offset);
390 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
392 return CURLE_OUT_OF_MEMORY;
394 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */
396 sspi_status = s_pSecFn->InitializeSecurityContext(
397 &connssl->cred->cred_handle, &connssl->ctxt->ctxt_handle,
398 host_name, connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL,
399 &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp);
401 Curl_unicodefree(host_name);
403 /* free buffer for received handshake data */
404 Curl_safefree(inbuf[0].pvBuffer);
406 /* check if the handshake was incomplete */
407 if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
408 connssl->connecting_state = ssl_connect_2_reading;
409 infof(data, "schannel: received incomplete message, need more data\n");
413 /* check if the handshake needs to be continued */
414 if(sspi_status == SEC_I_CONTINUE_NEEDED || sspi_status == SEC_E_OK) {
415 for(i = 0; i < 2; i++) {
416 /* search for handshake tokens that need to be send */
417 if(outbuf[i].BufferType == SECBUFFER_TOKEN && outbuf[i].cbBuffer > 0) {
418 infof(data, "schannel: sending next handshake data: "
419 "sending %lu bytes...\n", outbuf[i].cbBuffer);
421 /* send handshake token to server */
422 code = Curl_write_plain(conn, conn->sock[sockindex],
423 outbuf[i].pvBuffer, outbuf[i].cbBuffer,
425 if((code != CURLE_OK) || (outbuf[i].cbBuffer != (size_t)written)) {
426 failf(data, "schannel: failed to send next handshake data: "
427 "sent %zd of %lu bytes", written, outbuf[i].cbBuffer);
428 return CURLE_SSL_CONNECT_ERROR;
432 /* free obsolete buffer */
433 if(outbuf[i].pvBuffer != NULL) {
434 s_pSecFn->FreeContextBuffer(outbuf[i].pvBuffer);
439 if(sspi_status == SEC_E_WRONG_PRINCIPAL)
440 failf(data, "schannel: SNI or certificate check failed: %s",
441 Curl_sspi_strerror(conn, sspi_status));
443 failf(data, "schannel: next InitializeSecurityContext failed: %s",
444 Curl_sspi_strerror(conn, sspi_status));
445 return CURLE_SSL_CONNECT_ERROR;
448 /* check if there was additional remaining encrypted data */
449 if(inbuf[1].BufferType == SECBUFFER_EXTRA && inbuf[1].cbBuffer > 0) {
450 infof(data, "schannel: encrypted data length: %lu\n", inbuf[1].cbBuffer);
452 There are two cases where we could be getting extra data here:
453 1) If we're renegotiating a connection and the handshake is already
454 complete (from the server perspective), it can encrypted app data
455 (not handshake data) in an extra buffer at this point.
456 2) (sspi_status == SEC_I_CONTINUE_NEEDED) We are negotiating a
457 connection and this extra data is part of the handshake.
458 We should process the data immediately; waiting for the socket to
459 be ready may fail since the server is done sending handshake data.
461 /* check if the remaining data is less than the total amount
462 and therefore begins after the already processed data */
463 if(connssl->encdata_offset > inbuf[1].cbBuffer) {
464 memmove(connssl->encdata_buffer,
465 (connssl->encdata_buffer + connssl->encdata_offset) -
466 inbuf[1].cbBuffer, inbuf[1].cbBuffer);
467 connssl->encdata_offset = inbuf[1].cbBuffer;
468 if(sspi_status == SEC_I_CONTINUE_NEEDED) {
475 connssl->encdata_offset = 0;
480 /* check if the handshake needs to be continued */
481 if(sspi_status == SEC_I_CONTINUE_NEEDED) {
482 connssl->connecting_state = ssl_connect_2_reading;
486 /* check if the handshake is complete */
487 if(sspi_status == SEC_E_OK) {
488 connssl->connecting_state = ssl_connect_3;
489 infof(data, "schannel: SSL/TLS handshake complete\n");
493 /* Windows CE doesn't do any server certificate validation.
494 We have to do it manually. */
495 if(data->set.ssl.verifypeer)
496 return verify_certificate(conn, sockindex);
503 schannel_connect_step3(struct connectdata *conn, int sockindex)
505 CURLcode retcode = CURLE_OK;
506 struct SessionHandle *data = conn->data;
507 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
508 struct curl_schannel_cred *old_cred = NULL;
511 DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
513 infof(data, "schannel: SSL/TLS connection with %s port %hu (step 3/3)\n",
514 conn->host.name, conn->remote_port);
517 return CURLE_SSL_CONNECT_ERROR;
519 /* check if the required context attributes are met */
520 if(connssl->ret_flags != connssl->req_flags) {
521 if(!(connssl->ret_flags & ISC_RET_SEQUENCE_DETECT))
522 failf(data, "schannel: failed to setup sequence detection");
523 if(!(connssl->ret_flags & ISC_RET_REPLAY_DETECT))
524 failf(data, "schannel: failed to setup replay detection");
525 if(!(connssl->ret_flags & ISC_RET_CONFIDENTIALITY))
526 failf(data, "schannel: failed to setup confidentiality");
527 if(!(connssl->ret_flags & ISC_RET_ALLOCATED_MEMORY))
528 failf(data, "schannel: failed to setup memory allocation");
529 if(!(connssl->ret_flags & ISC_RET_STREAM))
530 failf(data, "schannel: failed to setup stream orientation");
531 return CURLE_SSL_CONNECT_ERROR;
534 /* increment the reference counter of the credential/session handle */
535 if(connssl->cred && connssl->ctxt) {
536 connssl->cred->refcount++;
537 infof(data, "schannel: incremented credential handle refcount = %d\n",
538 connssl->cred->refcount);
541 /* save the current session data for possible re-use */
542 incache = !(Curl_ssl_getsessionid(conn, (void**)&old_cred, NULL));
544 if(old_cred != connssl->cred) {
545 infof(data, "schannel: old credential handle is stale, removing\n");
546 Curl_ssl_delsessionid(conn, (void*)old_cred);
551 retcode = Curl_ssl_addsessionid(conn, (void*)connssl->cred,
552 sizeof(struct curl_schannel_cred));
554 failf(data, "schannel: failed to store credential handle");
558 connssl->cred->cached = TRUE;
559 infof(data, "schannel: stored credential handle in session cache\n");
563 connssl->connecting_state = ssl_connect_done;
569 schannel_connect_common(struct connectdata *conn, int sockindex,
570 bool nonblocking, bool *done)
573 struct SessionHandle *data = conn->data;
574 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
575 curl_socket_t sockfd = conn->sock[sockindex];
579 /* check if the connection has already been established */
580 if(ssl_connection_complete == connssl->state) {
585 if(ssl_connect_1 == connssl->connecting_state) {
586 /* check out how much more time we're allowed */
587 timeout_ms = Curl_timeleft(data, NULL, TRUE);
590 /* no need to continue if time already is up */
591 failf(data, "SSL/TLS connection timeout");
592 return CURLE_OPERATION_TIMEDOUT;
595 retcode = schannel_connect_step1(conn, sockindex);
600 while(ssl_connect_2 == connssl->connecting_state ||
601 ssl_connect_2_reading == connssl->connecting_state ||
602 ssl_connect_2_writing == connssl->connecting_state) {
604 /* check out how much more time we're allowed */
605 timeout_ms = Curl_timeleft(data, NULL, TRUE);
608 /* no need to continue if time already is up */
609 failf(data, "SSL/TLS connection timeout");
610 return CURLE_OPERATION_TIMEDOUT;
613 /* if ssl is expecting something, check if it's available. */
614 if(connssl->connecting_state == ssl_connect_2_reading
615 || connssl->connecting_state == ssl_connect_2_writing) {
617 curl_socket_t writefd = ssl_connect_2_writing ==
618 connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
619 curl_socket_t readfd = ssl_connect_2_reading ==
620 connssl->connecting_state ? sockfd : CURL_SOCKET_BAD;
622 what = Curl_socket_ready(readfd, writefd, nonblocking ? 0 : timeout_ms);
625 failf(data, "select/poll on SSL/TLS socket, errno: %d", SOCKERRNO);
626 return CURLE_SSL_CONNECT_ERROR;
635 failf(data, "SSL/TLS connection timeout");
636 return CURLE_OPERATION_TIMEDOUT;
639 /* socket is readable or writable */
642 /* Run transaction, and return to the caller if it failed or if
643 * this connection is part of a multi handle and this loop would
644 * execute again. This permits the owner of a multi handle to
645 * abort a connection attempt before step2 has completed while
646 * ensuring that a client using select() or epoll() will always
647 * have a valid fdset to wait on.
649 retcode = schannel_connect_step2(conn, sockindex);
650 if(retcode || (nonblocking &&
651 (ssl_connect_2 == connssl->connecting_state ||
652 ssl_connect_2_reading == connssl->connecting_state ||
653 ssl_connect_2_writing == connssl->connecting_state)))
656 } /* repeat step2 until all transactions are done. */
658 if(ssl_connect_3 == connssl->connecting_state) {
659 retcode = schannel_connect_step3(conn, sockindex);
664 if(ssl_connect_done == connssl->connecting_state) {
665 connssl->state = ssl_connection_complete;
666 conn->recv[sockindex] = schannel_recv;
667 conn->send[sockindex] = schannel_send;
673 /* reset our connection state machine */
674 connssl->connecting_state = ssl_connect_1;
680 schannel_send(struct connectdata *conn, int sockindex,
681 const void *buf, size_t len, CURLcode *err)
683 ssize_t written = -1;
685 unsigned char *data = NULL;
686 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
688 SecBufferDesc outbuf_desc;
689 SECURITY_STATUS sspi_status = SEC_E_OK;
692 /* check if the maximum stream sizes were queried */
693 if(connssl->stream_sizes.cbMaximumMessage == 0) {
694 sspi_status = s_pSecFn->QueryContextAttributes(
695 &connssl->ctxt->ctxt_handle,
696 SECPKG_ATTR_STREAM_SIZES,
697 &connssl->stream_sizes);
698 if(sspi_status != SEC_E_OK) {
699 *err = CURLE_SEND_ERROR;
704 /* check if the buffer is longer than the maximum message length */
705 if(len > connssl->stream_sizes.cbMaximumMessage) {
706 *err = CURLE_SEND_ERROR;
710 /* calculate the complete message length and allocate a buffer for it */
711 data_len = connssl->stream_sizes.cbHeader + len +
712 connssl->stream_sizes.cbTrailer;
713 data = (unsigned char*) malloc(data_len);
715 *err = CURLE_OUT_OF_MEMORY;
719 /* setup output buffers (header, data, trailer, empty) */
720 InitSecBuffer(&outbuf[0], SECBUFFER_STREAM_HEADER,
721 data, connssl->stream_sizes.cbHeader);
722 InitSecBuffer(&outbuf[1], SECBUFFER_DATA,
723 data + connssl->stream_sizes.cbHeader, curlx_uztoul(len));
724 InitSecBuffer(&outbuf[2], SECBUFFER_STREAM_TRAILER,
725 data + connssl->stream_sizes.cbHeader + len,
726 connssl->stream_sizes.cbTrailer);
727 InitSecBuffer(&outbuf[3], SECBUFFER_EMPTY, NULL, 0);
728 InitSecBufferDesc(&outbuf_desc, outbuf, 4);
730 /* copy data into output buffer */
731 memcpy(outbuf[1].pvBuffer, buf, len);
733 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */
734 sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt->ctxt_handle, 0,
737 /* check if the message was encrypted */
738 if(sspi_status == SEC_E_OK) {
741 /* send the encrypted message including header, data and trailer */
742 len = outbuf[0].cbBuffer + outbuf[1].cbBuffer + outbuf[2].cbBuffer;
745 It's important to send the full message which includes the header,
746 encrypted payload, and trailer. Until the client receives all the
747 data a coherent message has not been delivered and the client
748 can't read any of it.
750 If we wanted to buffer the unwritten encrypted bytes, we would
751 tell the client that all data it has requested to be sent has been
752 sent. The unwritten encrypted bytes would be the first bytes to
753 send on the next invocation.
754 Here's the catch with this - if we tell the client that all the
755 bytes have been sent, will the client call this method again to
756 send the buffered data? Looking at who calls this function, it
757 seems the answer is NO.
760 /* send entire message or fail */
761 while(len > (size_t)written) {
768 timeleft = Curl_timeleft(conn->data, NULL, FALSE);
770 /* we already got the timeout */
771 failf(conn->data, "schannel: timed out sending data "
772 "(bytes sent: %zd)", written);
773 *err = CURLE_OPERATION_TIMEDOUT;
778 what = Curl_socket_ready(CURL_SOCKET_BAD, conn->sock[sockindex],
782 failf(conn->data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
783 *err = CURLE_SEND_ERROR;
788 failf(conn->data, "schannel: timed out sending data "
789 "(bytes sent: %zd)", written);
790 *err = CURLE_OPERATION_TIMEDOUT;
794 /* socket is writable */
796 code = Curl_write_plain(conn, conn->sock[sockindex], data + written,
797 len - written, &this_write);
798 if(code == CURLE_AGAIN)
800 else if(code != CURLE_OK) {
806 written += this_write;
809 else if(sspi_status == SEC_E_INSUFFICIENT_MEMORY) {
810 *err = CURLE_OUT_OF_MEMORY;
813 *err = CURLE_SEND_ERROR;
818 if(len == (size_t)written)
819 /* Encrypted message including header, data and trailer entirely sent.
820 The return value is the number of unencrypted bytes that were sent. */
821 written = outbuf[1].cbBuffer;
827 schannel_recv(struct connectdata *conn, int sockindex,
828 char *buf, size_t len, CURLcode *err)
831 ssize_t nread = 0, ret = -1;
833 struct SessionHandle *data = conn->data;
834 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
837 SecBufferDesc inbuf_desc;
838 SECURITY_STATUS sspi_status = SEC_E_OK;
840 infof(data, "schannel: client wants to read %zu bytes\n", len);
843 /* buffer to store previously received and decrypted data */
844 if(connssl->decdata_buffer == NULL) {
845 connssl->decdata_offset = 0;
846 connssl->decdata_length = CURL_SCHANNEL_BUFFER_INIT_SIZE;
847 connssl->decdata_buffer = malloc(connssl->decdata_length);
848 if(connssl->decdata_buffer == NULL) {
849 failf(data, "schannel: unable to allocate memory");
850 *err = CURLE_OUT_OF_MEMORY;
855 /* increase buffer in order to fit the requested amount of data */
856 while(connssl->encdata_length - connssl->encdata_offset <
857 CURL_SCHANNEL_BUFFER_FREE_SIZE || connssl->encdata_length < len) {
858 /* increase internal encrypted data buffer */
859 connssl->encdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
860 connssl->encdata_buffer = realloc(connssl->encdata_buffer,
861 connssl->encdata_length);
863 if(connssl->encdata_buffer == NULL) {
864 failf(data, "schannel: unable to re-allocate memory");
865 *err = CURLE_OUT_OF_MEMORY;
870 /* read encrypted data from socket */
871 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
872 connssl->encdata_offset, connssl->encdata_length);
873 size = connssl->encdata_length - connssl->encdata_offset;
875 *err = Curl_read_plain(conn->sock[sockindex],
876 (char *) (connssl->encdata_buffer + connssl->encdata_offset),
878 /* check for received data */
883 /* increase encrypted data buffer offset */
884 connssl->encdata_offset += nread;
887 infof(data, "schannel: encrypted data got %zd\n", ret);
890 infof(data, "schannel: encrypted data buffer: offset %zu length %zu\n",
891 connssl->encdata_offset, connssl->encdata_length);
893 /* check if we still have some data in our buffers */
894 while(connssl->encdata_offset > 0 && sspi_status == SEC_E_OK &&
895 connssl->decdata_offset < len) {
896 /* prepare data buffer for DecryptMessage call */
897 InitSecBuffer(&inbuf[0], SECBUFFER_DATA, connssl->encdata_buffer,
898 curlx_uztoul(connssl->encdata_offset));
900 /* we need 3 more empty input buffers for possible output */
901 InitSecBuffer(&inbuf[1], SECBUFFER_EMPTY, NULL, 0);
902 InitSecBuffer(&inbuf[2], SECBUFFER_EMPTY, NULL, 0);
903 InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0);
905 InitSecBufferDesc(&inbuf_desc, inbuf, 4);
907 /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */
908 sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle,
909 &inbuf_desc, 0, NULL);
911 /* check if we need more data */
912 if(sspi_status == SEC_E_INCOMPLETE_MESSAGE) {
913 infof(data, "schannel: failed to decrypt data, need more data\n");
918 /* check if everything went fine (server may want to renegotiate
920 if(sspi_status == SEC_E_OK || sspi_status == SEC_I_RENEGOTIATE ||
921 sspi_status == SEC_I_CONTEXT_EXPIRED) {
922 /* check for successfully decrypted data */
923 if(inbuf[1].BufferType == SECBUFFER_DATA) {
924 infof(data, "schannel: decrypted data length: %lu\n",
927 /* increase buffer in order to fit the received amount of data */
928 size = inbuf[1].cbBuffer > CURL_SCHANNEL_BUFFER_FREE_SIZE ?
929 inbuf[1].cbBuffer : CURL_SCHANNEL_BUFFER_FREE_SIZE;
930 while(connssl->decdata_length - connssl->decdata_offset < size ||
931 connssl->decdata_length < len) {
932 /* increase internal decrypted data buffer */
933 connssl->decdata_length *= CURL_SCHANNEL_BUFFER_STEP_FACTOR;
934 connssl->decdata_buffer = realloc(connssl->decdata_buffer,
935 connssl->decdata_length);
937 if(connssl->decdata_buffer == NULL) {
938 failf(data, "schannel: unable to re-allocate memory");
939 *err = CURLE_OUT_OF_MEMORY;
944 /* copy decrypted data to internal buffer */
945 size = inbuf[1].cbBuffer;
947 memcpy(connssl->decdata_buffer + connssl->decdata_offset,
948 inbuf[1].pvBuffer, size);
949 connssl->decdata_offset += size;
952 infof(data, "schannel: decrypted data added: %zu\n", size);
953 infof(data, "schannel: decrypted data cached: offset %zu length %zu\n",
954 connssl->decdata_offset, connssl->decdata_length);
957 /* check for remaining encrypted data */
958 if(inbuf[3].BufferType == SECBUFFER_EXTRA && inbuf[3].cbBuffer > 0) {
959 infof(data, "schannel: encrypted data length: %lu\n",
962 /* check if the remaining data is less than the total amount
963 * and therefore begins after the already processed data
965 if(connssl->encdata_offset > inbuf[3].cbBuffer) {
966 /* move remaining encrypted data forward to the beginning of
968 memmove(connssl->encdata_buffer,
969 (connssl->encdata_buffer + connssl->encdata_offset) -
970 inbuf[3].cbBuffer, inbuf[3].cbBuffer);
971 connssl->encdata_offset = inbuf[3].cbBuffer;
974 infof(data, "schannel: encrypted data cached: offset %zu length %zu\n",
975 connssl->encdata_offset, connssl->encdata_length);
978 /* reset encrypted buffer offset, because there is no data remaining */
979 connssl->encdata_offset = 0;
983 /* check if server wants to renegotiate the connection context */
984 if(sspi_status == SEC_I_RENEGOTIATE) {
985 infof(data, "schannel: remote party requests SSL/TLS renegotiation\n");
987 /* begin renegotiation */
988 infof(data, "schannel: renegotiating SSL/TLS connection\n");
989 connssl->state = ssl_connection_negotiating;
990 connssl->connecting_state = ssl_connect_2_writing;
991 retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
995 infof(data, "schannel: SSL/TLS connection renegotiated\n");
996 /* now retry receiving data */
997 return schannel_recv(conn, sockindex, buf, len, err);
1002 infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
1003 connssl->decdata_offset, connssl->decdata_length);
1005 /* copy requested decrypted data to supplied buffer */
1006 size = len < connssl->decdata_offset ? len : connssl->decdata_offset;
1008 memcpy(buf, connssl->decdata_buffer, size);
1011 /* move remaining decrypted data forward to the beginning of buffer */
1012 memmove(connssl->decdata_buffer, connssl->decdata_buffer + size,
1013 connssl->decdata_offset - size);
1014 connssl->decdata_offset -= size;
1016 infof(data, "schannel: decrypted data returned %zd\n", size);
1017 infof(data, "schannel: decrypted data buffer: offset %zu length %zu\n",
1018 connssl->decdata_offset, connssl->decdata_length);
1021 /* check if the server closed the connection */
1022 if(ret <= 0 && ( /* special check for Windows 2000 Professional */
1023 sspi_status == SEC_I_CONTEXT_EXPIRED || (sspi_status == SEC_E_OK &&
1024 connssl->encdata_offset > 0 && connssl->encdata_buffer[0] == 0x15))) {
1025 infof(data, "schannel: server closed the connection\n");
1030 /* check if something went wrong and we need to return an error */
1031 if(ret < 0 && sspi_status != SEC_E_OK) {
1032 infof(data, "schannel: failed to read data from server: %s\n",
1033 Curl_sspi_strerror(conn, sspi_status));
1034 *err = CURLE_RECV_ERROR;
1042 Curl_schannel_connect_nonblocking(struct connectdata *conn, int sockindex,
1045 return schannel_connect_common(conn, sockindex, TRUE, done);
1049 Curl_schannel_connect(struct connectdata *conn, int sockindex)
1054 retcode = schannel_connect_common(conn, sockindex, FALSE, &done);
1063 bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex)
1065 const struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1067 if(connssl->use) /* SSL/TLS is in use */
1068 return (connssl->encdata_offset > 0 ||
1069 connssl->decdata_offset > 0 ) ? TRUE : FALSE;
1074 void Curl_schannel_close(struct connectdata *conn, int sockindex)
1076 if(conn->ssl[sockindex].use)
1077 /* if the SSL/TLS channel hasn't been shut down yet, do that now. */
1078 Curl_ssl_shutdown(conn, sockindex);
1081 int Curl_schannel_shutdown(struct connectdata *conn, int sockindex)
1083 /* See http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx
1084 * Shutting Down an Schannel Connection
1086 struct SessionHandle *data = conn->data;
1087 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1089 infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n",
1090 conn->host.name, conn->remote_port);
1092 if(connssl->cred && connssl->ctxt) {
1093 SecBufferDesc BuffDesc;
1095 SECURITY_STATUS sspi_status;
1097 SecBufferDesc outbuf_desc;
1100 DWORD dwshut = SCHANNEL_SHUTDOWN;
1102 InitSecBuffer(&Buffer, SECBUFFER_TOKEN, &dwshut, sizeof(dwshut));
1103 InitSecBufferDesc(&BuffDesc, &Buffer, 1);
1105 sspi_status = s_pSecFn->ApplyControlToken(&connssl->ctxt->ctxt_handle,
1108 if(sspi_status != SEC_E_OK)
1109 failf(data, "schannel: ApplyControlToken failure: %s",
1110 Curl_sspi_strerror(conn, sspi_status));
1112 host_name = Curl_convert_UTF8_to_tchar(conn->host.name);
1114 return CURLE_OUT_OF_MEMORY;
1116 /* setup output buffer */
1117 InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0);
1118 InitSecBufferDesc(&outbuf_desc, &outbuf, 1);
1120 sspi_status = s_pSecFn->InitializeSecurityContext(
1121 &connssl->cred->cred_handle,
1122 &connssl->ctxt->ctxt_handle,
1129 &connssl->ctxt->ctxt_handle,
1131 &connssl->ret_flags,
1132 &connssl->ctxt->time_stamp);
1134 Curl_unicodefree(host_name);
1136 if((sspi_status == SEC_E_OK) || (sspi_status == SEC_I_CONTEXT_EXPIRED)) {
1137 /* send close message which is in output buffer */
1139 code = Curl_write_plain(conn, conn->sock[sockindex], outbuf.pvBuffer,
1140 outbuf.cbBuffer, &written);
1142 s_pSecFn->FreeContextBuffer(outbuf.pvBuffer);
1143 if((code != CURLE_OK) || (outbuf.cbBuffer != (size_t)written)) {
1144 infof(data, "schannel: failed to send close msg: %s"
1145 " (bytes written: %zd)\n", curl_easy_strerror(code), written);
1149 /* free SSPI Schannel API security context handle */
1151 infof(data, "schannel: clear security context handle\n");
1152 s_pSecFn->DeleteSecurityContext(&connssl->ctxt->ctxt_handle);
1153 Curl_safefree(connssl->ctxt);
1156 /* free SSPI Schannel API credential handle */
1158 /* decrement the reference counter of the credential/session handle */
1159 if(connssl->cred->refcount > 0) {
1160 connssl->cred->refcount--;
1161 infof(data, "schannel: decremented credential handle refcount = %d\n",
1162 connssl->cred->refcount);
1165 /* if the handle was not cached and the refcount is zero */
1166 if(!connssl->cred->cached && connssl->cred->refcount == 0) {
1167 infof(data, "schannel: clear credential handle\n");
1168 s_pSecFn->FreeCredentialsHandle(&connssl->cred->cred_handle);
1169 Curl_safefree(connssl->cred);
1174 /* free internal buffer for received encrypted data */
1175 if(connssl->encdata_buffer != NULL) {
1176 Curl_safefree(connssl->encdata_buffer);
1177 connssl->encdata_length = 0;
1178 connssl->encdata_offset = 0;
1181 /* free internal buffer for received decrypted data */
1182 if(connssl->decdata_buffer != NULL) {
1183 Curl_safefree(connssl->decdata_buffer);
1184 connssl->decdata_length = 0;
1185 connssl->decdata_offset = 0;
1191 void Curl_schannel_session_free(void *ptr)
1193 struct curl_schannel_cred *cred = ptr;
1195 if(cred && cred->cached && cred->refcount == 0) {
1196 s_pSecFn->FreeCredentialsHandle(&cred->cred_handle);
1197 Curl_safefree(cred);
1201 int Curl_schannel_init(void)
1203 return (Curl_sspi_global_init() == CURLE_OK ? 1 : 0);
1206 void Curl_schannel_cleanup(void)
1208 Curl_sspi_global_cleanup();
1211 size_t Curl_schannel_version(char *buffer, size_t size)
1213 size = snprintf(buffer, size, "WinSSL");
1219 static CURLcode verify_certificate(struct connectdata *conn, int sockindex)
1221 SECURITY_STATUS status;
1222 struct SessionHandle *data = conn->data;
1223 struct ssl_connect_data *connssl = &conn->ssl[sockindex];
1224 CURLcode result = CURLE_OK;
1225 CERT_CONTEXT *pCertContextServer = NULL;
1226 const CERT_CHAIN_CONTEXT *pChainContext = NULL;
1228 status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle,
1229 SECPKG_ATTR_REMOTE_CERT_CONTEXT,
1230 &pCertContextServer);
1232 if((status != SEC_E_OK) || (pCertContextServer == NULL)) {
1233 failf(data, "schannel: Failed to read remote certificate context: %s",
1234 Curl_sspi_strerror(conn, status));
1235 result = CURLE_PEER_FAILED_VERIFICATION;
1238 if(result == CURLE_OK) {
1239 CERT_CHAIN_PARA ChainPara;
1240 memset(&ChainPara, 0, sizeof(ChainPara));
1241 ChainPara.cbSize = sizeof(ChainPara);
1243 if(!CertGetCertificateChain(NULL,
1246 pCertContextServer->hCertStore,
1251 failf(data, "schannel: CertGetCertificateChain failed: %s",
1252 Curl_sspi_strerror(conn, GetLastError()));
1253 pChainContext = NULL;
1254 result = CURLE_PEER_FAILED_VERIFICATION;
1257 if(result == CURLE_OK) {
1258 CERT_SIMPLE_CHAIN *pSimpleChain = pChainContext->rgpChain[0];
1259 DWORD dwTrustErrorMask = ~(DWORD)(CERT_TRUST_IS_NOT_TIME_NESTED|
1260 CERT_TRUST_REVOCATION_STATUS_UNKNOWN);
1261 dwTrustErrorMask &= pSimpleChain->TrustStatus.dwErrorStatus;
1262 if(dwTrustErrorMask) {
1263 if(dwTrustErrorMask & CERT_TRUST_IS_PARTIAL_CHAIN)
1264 failf(data, "schannel: CertGetCertificateChain trust error"
1265 " CERT_TRUST_IS_PARTIAL_CHAIN");
1266 if(dwTrustErrorMask & CERT_TRUST_IS_UNTRUSTED_ROOT)
1267 failf(data, "schannel: CertGetCertificateChain trust error"
1268 " CERT_TRUST_IS_UNTRUSTED_ROOT");
1269 if(dwTrustErrorMask & CERT_TRUST_IS_NOT_TIME_VALID)
1270 failf(data, "schannel: CertGetCertificateChain trust error"
1271 " CERT_TRUST_IS_NOT_TIME_VALID");
1272 failf(data, "schannel: CertGetCertificateChain error mask: 0x%08x",
1274 result = CURLE_PEER_FAILED_VERIFICATION;
1279 if(result == CURLE_OK) {
1280 if(data->set.ssl.verifyhost) {
1281 TCHAR cert_hostname_buff[128];
1283 xcharp_u cert_hostname;
1286 cert_hostname.const_tchar_ptr = cert_hostname_buff;
1287 hostname.tchar_ptr = Curl_convert_UTF8_to_tchar(conn->host.name);
1289 len = CertGetNameString(pCertContextServer,
1293 cert_hostname.tchar_ptr,
1295 if(len > 0 && *cert_hostname.tchar_ptr == '*') {
1296 /* this is a wildcard cert. try matching the last len - 1 chars */
1297 int hostname_len = strlen(conn->host.name);
1298 cert_hostname.tchar_ptr++;
1299 if(_tcsicmp(cert_hostname.const_tchar_ptr,
1300 hostname.const_tchar_ptr + hostname_len - len + 2) != 0)
1301 result = CURLE_PEER_FAILED_VERIFICATION;
1303 else if(len == 0 || _tcsicmp(hostname.const_tchar_ptr,
1304 cert_hostname.const_tchar_ptr) != 0) {
1305 result = CURLE_PEER_FAILED_VERIFICATION;
1307 if(result == CURLE_PEER_FAILED_VERIFICATION) {
1308 char *_cert_hostname;
1309 _cert_hostname = Curl_convert_tchar_to_UTF8(cert_hostname.tchar_ptr);
1310 failf(data, "schannel: CertGetNameString() certificate hostname "
1311 "(%s) did not match connection (%s)",
1312 _cert_hostname, conn->host.name);
1313 Curl_unicodefree(_cert_hostname);
1315 Curl_unicodefree(hostname.tchar_ptr);
1320 CertFreeCertificateChain(pChainContext);
1322 if(pCertContextServer)
1323 CertFreeCertificateContext(pCertContextServer);
1327 #endif /* _WIN32_WCE */
1329 #endif /* USE_SCHANNEL */