diff options
Diffstat (limited to 'Utilities/cmcurl/lib/vtls/schannel.c')
-rw-r--r-- | Utilities/cmcurl/lib/vtls/schannel.c | 297 |
1 files changed, 213 insertions, 84 deletions
diff --git a/Utilities/cmcurl/lib/vtls/schannel.c b/Utilities/cmcurl/lib/vtls/schannel.c index 2174e21..f991ec9 100644 --- a/Utilities/cmcurl/lib/vtls/schannel.c +++ b/Utilities/cmcurl/lib/vtls/schannel.c @@ -5,13 +5,13 @@ * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * - * Copyright (C) 2012 - 2015, Marc Hoersken, <info@marc-hoersken.de> + * Copyright (C) 2012 - 2016, Marc Hoersken, <info@marc-hoersken.de> * Copyright (C) 2012, Mark Salisbury, <mark.salisbury@hp.com> - * Copyright (C) 2012 - 2015, Daniel Stenberg, <daniel@haxx.se>, et al. + * Copyright (C) 2012 - 2016, Daniel Stenberg, <daniel@haxx.se>, et al. * * This software is licensed as described in the file COPYING, which * you should have received as part of this distribution. The terms - * are also available at http://curl.haxx.se/docs/copyright.html. + * are also available at https://curl.haxx.se/docs/copyright.html. * * You may opt to use, copy, modify, merge, publish, distribute and/or sell * copies of the Software, and permit persons to whom the Software is @@ -56,11 +56,23 @@ #include "inet_pton.h" /* for IP addr SNI check */ #include "curl_multibyte.h" #include "warnless.h" +#include "x509asn1.h" #include "curl_printf.h" +#include "system_win32.h" + + /* The last #include file should be: */ #include "curl_memory.h" -/* The last #include file should be: */ #include "memdebug.h" +/* ALPN requires version 8.1 of the Windows SDK, which was + shipped with Visual Studio 2013, aka _MSC_VER 1800: + + https://technet.microsoft.com/en-us/library/hh831771%28v=ws.11%29.aspx +*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1800) && !defined(_USING_V110_SDK71_) +# define HAS_ALPN 1 +#endif + /* Uncomment to force verbose output * #define infof(x, y, ...) printf(y, __VA_ARGS__) * #define failf(x, y, ...) printf(y, __VA_ARGS__) @@ -93,10 +105,15 @@ static CURLcode schannel_connect_step1(struct connectdata *conn, int sockindex) { ssize_t written = -1; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; SecBuffer outbuf; SecBufferDesc outbuf_desc; + SecBuffer inbuf; + SecBufferDesc inbuf_desc; +#ifdef HAS_ALPN + unsigned char alpn_buffer[128]; +#endif SCHANNEL_CRED schannel_cred; SECURITY_STATUS sspi_status = SEC_E_OK; struct curl_schannel_cred *old_cred = NULL; @@ -110,12 +127,24 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) infof(data, "schannel: SSL/TLS connection with %s port %hu (step 1/3)\n", conn->host.name, conn->remote_port); + connssl->cred = NULL; + /* check for an existing re-usable credential handle */ - if(!Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL)) { - connssl->cred = old_cred; - infof(data, "schannel: re-using existing credential handle\n"); + if(conn->ssl_config.sessionid) { + Curl_ssl_sessionid_lock(conn); + if(!Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL)) { + connssl->cred = old_cred; + infof(data, "schannel: re-using existing credential handle\n"); + + /* increment the reference counter of the credential/session handle */ + connssl->cred->refcount++; + infof(data, "schannel: incremented credential handle refcount = %d\n", + connssl->cred->refcount); + } + Curl_ssl_sessionid_unlock(conn); } - else { + + if(!connssl->cred) { /* setup Schannel API options */ memset(&schannel_cred, 0, sizeof(schannel_cred)); schannel_cred.dwVersion = SCHANNEL_CRED_VERSION; @@ -188,8 +217,10 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) return CURLE_OUT_OF_MEMORY; } memset(connssl->cred, 0, sizeof(struct curl_schannel_cred)); + connssl->cred->refcount = 1; - /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx */ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa374716.aspx + */ sspi_status = s_pSecFn->AcquireCredentialsHandle(NULL, (TCHAR *)UNISP_NAME, SECPKG_CRED_OUTBOUND, NULL, @@ -218,6 +249,63 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) infof(data, "schannel: using IP address, SNI is not supported by OS.\n"); } +#ifdef HAS_ALPN + /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above */ + if(conn->bits.tls_enable_alpn && + Curl_verify_windows_version(6, 3, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + int cur = 0; + int list_start_index = 0; + unsigned int* extension_len = NULL; + unsigned short* list_len = NULL; + + /* The first four bytes will be an unsigned int indicating number + of bytes of data in the rest of the the buffer. */ + extension_len = (unsigned int*)(&alpn_buffer[cur]); + cur += sizeof(unsigned int); + + /* The next four bytes are an indicator that this buffer will contain + ALPN data, as opposed to NPN, for example. */ + *(unsigned int*)&alpn_buffer[cur] = + SecApplicationProtocolNegotiationExt_ALPN; + cur += sizeof(unsigned int); + + /* The next two bytes will be an unsigned short indicating the number + of bytes used to list the preferred protocols. */ + list_len = (unsigned short*)(&alpn_buffer[cur]); + cur += sizeof(unsigned short); + + list_start_index = cur; + +#ifdef USE_NGHTTP2 + if(data->set.httpversion >= CURL_HTTP_VERSION_2) { + memcpy(&alpn_buffer[cur], NGHTTP2_PROTO_ALPN, NGHTTP2_PROTO_ALPN_LEN); + cur += NGHTTP2_PROTO_ALPN_LEN; + infof(data, "schannel: ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); + } +#endif + + alpn_buffer[cur++] = ALPN_HTTP_1_1_LENGTH; + memcpy(&alpn_buffer[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); + cur += ALPN_HTTP_1_1_LENGTH; + infof(data, "schannel: ALPN, offering %s\n", ALPN_HTTP_1_1); + + *list_len = curlx_uitous(cur - list_start_index); + *extension_len = *list_len + sizeof(unsigned int) + sizeof(unsigned short); + + InitSecBuffer(&inbuf, SECBUFFER_APPLICATION_PROTOCOLS, alpn_buffer, cur); + InitSecBufferDesc(&inbuf_desc, &inbuf, 1); + } + else + { + InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&inbuf_desc, &inbuf, 1); + } +#else /* HAS_ALPN */ + InitSecBuffer(&inbuf, SECBUFFER_EMPTY, NULL, 0); + InitSecBufferDesc(&inbuf_desc, &inbuf, 1); +#endif + /* setup output buffer */ InitSecBuffer(&outbuf, SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&outbuf_desc, &outbuf, 1); @@ -240,11 +328,11 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) if(!host_name) return CURLE_OUT_OF_MEMORY; - /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */ sspi_status = s_pSecFn->InitializeSecurityContext( &connssl->cred->cred_handle, NULL, host_name, - connssl->req_flags, 0, 0, NULL, 0, &connssl->ctxt->ctxt_handle, + connssl->req_flags, 0, 0, &inbuf_desc, 0, &connssl->ctxt->ctxt_handle, &outbuf_desc, &connssl->ret_flags, &connssl->ctxt->time_stamp); Curl_unicodefree(host_name); @@ -291,7 +379,7 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) { int i; ssize_t nread = -1, written = -1; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; unsigned char *reallocated_buffer; size_t reallocated_length; @@ -407,8 +495,8 @@ schannel_connect_step2(struct connectdata *conn, int sockindex) if(!host_name) return CURLE_OUT_OF_MEMORY; - /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx */ - + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375924.aspx + */ sspi_status = s_pSecFn->InitializeSecurityContext( &connssl->cred->cred_handle, &connssl->ctxt->ctxt_handle, host_name, connssl->req_flags, 0, 0, &inbuf_desc, 0, NULL, @@ -531,10 +619,13 @@ static CURLcode schannel_connect_step3(struct connectdata *conn, int sockindex) { CURLcode result = CURLE_OK; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; - struct curl_schannel_cred *old_cred = NULL; - bool incache; + SECURITY_STATUS sspi_status = SEC_E_OK; + CERT_CONTEXT *ccert_context = NULL; +#ifdef HAS_ALPN + SecPkgContext_ApplicationProtocol alpn_result; +#endif DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); @@ -559,34 +650,98 @@ schannel_connect_step3(struct connectdata *conn, int sockindex) return CURLE_SSL_CONNECT_ERROR; } - /* increment the reference counter of the credential/session handle */ - if(connssl->cred && connssl->ctxt) { - connssl->cred->refcount++; - infof(data, "schannel: incremented credential handle refcount = %d\n", - connssl->cred->refcount); +#ifdef HAS_ALPN + /* ALPN is only supported on Windows 8.1 / Server 2012 R2 and above */ + if(conn->bits.tls_enable_alpn && + Curl_verify_windows_version(6, 3, PLATFORM_WINNT, + VERSION_GREATER_THAN_EQUAL)) { + sspi_status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle, + SECPKG_ATTR_APPLICATION_PROTOCOL, &alpn_result); + + if(sspi_status != SEC_E_OK) { + failf(data, "schannel: failed to retrieve ALPN result"); + return CURLE_SSL_CONNECT_ERROR; + } + + if(alpn_result.ProtoNegoStatus == + SecApplicationProtocolNegotiationStatus_Success) { + + infof(data, "schannel: ALPN, server accepted to use %.*s\n", + alpn_result.ProtocolIdSize, alpn_result.ProtocolId); + +#ifdef USE_NGHTTP2 + if(alpn_result.ProtocolIdSize == NGHTTP2_PROTO_VERSION_ID_LEN && + !memcmp(NGHTTP2_PROTO_VERSION_ID, alpn_result.ProtocolId, + NGHTTP2_PROTO_VERSION_ID_LEN)) { + conn->negnpn = CURL_HTTP_VERSION_2; + } + else +#endif + if(alpn_result.ProtocolIdSize == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, alpn_result.ProtocolId, + ALPN_HTTP_1_1_LENGTH)) { + conn->negnpn = CURL_HTTP_VERSION_1_1; + } + } + else + infof(data, "ALPN, server did not agree to a protocol\n"); } +#endif /* save the current session data for possible re-use */ - incache = !(Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL)); - if(incache) { - if(old_cred != connssl->cred) { - infof(data, "schannel: old credential handle is stale, removing\n"); - Curl_ssl_delsessionid(conn, (void *)old_cred); - incache = FALSE; + if(conn->ssl_config.sessionid) { + bool incache; + struct curl_schannel_cred *old_cred = NULL; + + Curl_ssl_sessionid_lock(conn); + incache = !(Curl_ssl_getsessionid(conn, (void **)&old_cred, NULL)); + if(incache) { + if(old_cred != connssl->cred) { + infof(data, "schannel: old credential handle is stale, removing\n"); + /* we're not taking old_cred ownership here, no refcount++ is needed */ + Curl_ssl_delsessionid(conn, (void *)old_cred); + incache = FALSE; + } } + if(!incache) { + result = Curl_ssl_addsessionid(conn, (void *)connssl->cred, + sizeof(struct curl_schannel_cred)); + if(result) { + Curl_ssl_sessionid_unlock(conn); + failf(data, "schannel: failed to store credential handle"); + return result; + } + else { + /* this cred session is now also referenced by sessionid cache */ + connssl->cred->refcount++; + infof(data, "schannel: stored credential handle in session cache\n"); + } + } + Curl_ssl_sessionid_unlock(conn); } - if(!incache) { - result = Curl_ssl_addsessionid(conn, (void *)connssl->cred, - sizeof(struct curl_schannel_cred)); - if(result) { - failf(data, "schannel: failed to store credential handle"); - return result; + if(data->set.ssl.certinfo) { + sspi_status = s_pSecFn->QueryContextAttributes(&connssl->ctxt->ctxt_handle, + SECPKG_ATTR_REMOTE_CERT_CONTEXT, &ccert_context); + + if((sspi_status != SEC_E_OK) || (ccert_context == NULL)) { + failf(data, "schannel: failed to retrieve remote cert context"); + return CURLE_SSL_CONNECT_ERROR; } - else { - connssl->cred->cached = TRUE; - infof(data, "schannel: stored credential handle in session cache\n"); + + result = Curl_ssl_init_certinfo(data, 1); + if(!result) { + if(((ccert_context->dwCertEncodingType & X509_ASN_ENCODING) != 0) && + (ccert_context->cbCertEncoded > 0)) { + + const char *beg = (const char *) ccert_context->pbCertEncoded; + const char *end = beg + ccert_context->cbCertEncoded; + result = Curl_extract_certinfo(conn, 0, beg, end); + } } + CertFreeCertificateContext(ccert_context); + if(result) + return result; } connssl->connecting_state = ssl_connect_done; @@ -599,7 +754,7 @@ schannel_connect_common(struct connectdata *conn, int sockindex, bool nonblocking, bool *done) { CURLcode result; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; curl_socket_t sockfd = conn->sock[sockindex]; long timeout_ms; @@ -759,7 +914,7 @@ schannel_send(struct connectdata *conn, int sockindex, /* copy data into output buffer */ memcpy(outbuf[1].pvBuffer, buf, len); - /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375390.aspx */ sspi_status = s_pSecFn->EncryptMessage(&connssl->ctxt->ctxt_handle, 0, &outbuf_desc, 0); @@ -858,7 +1013,7 @@ schannel_recv(struct connectdata *conn, int sockindex, { size_t size = 0; ssize_t nread = -1; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; unsigned char *reallocated_buffer; size_t reallocated_length; @@ -973,7 +1128,8 @@ schannel_recv(struct connectdata *conn, int sockindex, InitSecBuffer(&inbuf[3], SECBUFFER_EMPTY, NULL, 0); InitSecBufferDesc(&inbuf_desc, inbuf, 4); - /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx */ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/aa375348.aspx + */ sspi_status = s_pSecFn->DecryptMessage(&connssl->ctxt->ctxt_handle, &inbuf_desc, 0, NULL); @@ -1120,23 +1276,8 @@ cleanup: */ if(len && !connssl->decdata_offset && connssl->recv_connection_closed && !connssl->recv_sspi_close_notify) { - BOOL isWin2k; - ULONGLONG cm; - OSVERSIONINFOEX osver; - - memset(&osver, 0, sizeof(osver)); - osver.dwOSVersionInfoSize = sizeof(osver); - osver.dwMajorVersion = 5; - - cm = VerSetConditionMask(0, VER_MAJORVERSION, VER_EQUAL); - cm = VerSetConditionMask(cm, VER_MINORVERSION, VER_EQUAL); - cm = VerSetConditionMask(cm, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - cm = VerSetConditionMask(cm, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - isWin2k = VerifyVersionInfo(&osver, - (VER_MAJORVERSION | VER_MINORVERSION | - VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR), - cm); + bool isWin2k = Curl_verify_windows_version(5, 0, PLATFORM_WINNT, + VERSION_EQUAL); if(isWin2k && sspi_status == SEC_E_OK) connssl->recv_sspi_close_notify = true; @@ -1204,7 +1345,7 @@ bool Curl_schannel_data_pending(const struct connectdata *conn, int sockindex) if(connssl->use) /* SSL/TLS is in use */ return (connssl->encdata_offset > 0 || - connssl->decdata_offset > 0 ) ? TRUE : FALSE; + connssl->decdata_offset > 0) ? TRUE : FALSE; else return FALSE; } @@ -1218,10 +1359,10 @@ void Curl_schannel_close(struct connectdata *conn, int sockindex) int Curl_schannel_shutdown(struct connectdata *conn, int sockindex) { - /* See http://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx + /* See https://msdn.microsoft.com/en-us/library/windows/desktop/aa380138.aspx * Shutting Down an Schannel Connection */ - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; infof(data, "schannel: shutting down SSL/TLS connection with %s port %hu\n", @@ -1294,19 +1435,10 @@ int Curl_schannel_shutdown(struct connectdata *conn, int sockindex) /* free SSPI Schannel API credential handle */ if(connssl->cred) { - /* decrement the reference counter of the credential/session handle */ - if(connssl->cred->refcount > 0) { - connssl->cred->refcount--; - infof(data, "schannel: decremented credential handle refcount = %d\n", - connssl->cred->refcount); - } - - /* if the handle was not cached and the refcount is zero */ - if(!connssl->cred->cached && connssl->cred->refcount == 0) { - infof(data, "schannel: clear credential handle\n"); - s_pSecFn->FreeCredentialsHandle(&connssl->cred->cred_handle); - Curl_safefree(connssl->cred); - } + Curl_ssl_sessionid_lock(conn); + Curl_schannel_session_free(connssl->cred); + Curl_ssl_sessionid_unlock(conn); + connssl->cred = NULL; } /* free internal buffer for received encrypted data */ @@ -1328,16 +1460,13 @@ int Curl_schannel_shutdown(struct connectdata *conn, int sockindex) void Curl_schannel_session_free(void *ptr) { + /* this is expected to be called under sessionid lock */ struct curl_schannel_cred *cred = ptr; - if(cred && cred->cached) { - if(cred->refcount == 0) { - s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); - Curl_safefree(cred); - } - else { - cred->cached = FALSE; - } + cred->refcount--; + if(cred->refcount == 0) { + s_pSecFn->FreeCredentialsHandle(&cred->cred_handle); + Curl_safefree(cred); } } @@ -1379,7 +1508,7 @@ int Curl_schannel_random(unsigned char *entropy, size_t length) static CURLcode verify_certificate(struct connectdata *conn, int sockindex) { SECURITY_STATUS status; - struct SessionHandle *data = conn->data; + struct Curl_easy *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; CURLcode result = CURLE_OK; CERT_CONTEXT *pCertContextServer = NULL; |