From f8d88a491397a13f0e676bd0b2d0f275a728b860 Mon Sep 17 00:00:00 2001 From: JDepooter Date: Sun, 20 Mar 2016 23:59:57 -0700 Subject: [PATCH] schannel: Add ALPN support Add ALPN support for schannel. This allows cURL to negotiate HTTP/2.0 connections when built with schannel. Closes #724 --- docs/HTTP2.md | 3 +- lib/vtls/schannel.c | 106 +++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 107 insertions(+), 2 deletions(-) diff --git a/docs/HTTP2.md b/docs/HTTP2.md index bf5382070..6f7ece431 100644 --- a/docs/HTTP2.md +++ b/docs/HTTP2.md @@ -7,7 +7,7 @@ HTTP/2 with curl Build prerequisites ------------------- - nghttp2 - - OpenSSL, NSS, GnutTLS or PolarSSL with a new enough version + - OpenSSL, NSS, GnutTLS, PolarSSL or SChannel with a new enough version [nghttp2](https://nghttp2.org/) ------------------------------- @@ -58,6 +58,7 @@ provide the necessary TLS features. Right now we support: - NSS: ALPN and NPN - GnuTLS: ALPN - PolarSSL: ALPN + - SChannel: ALPN Multiplexing ------------ diff --git a/lib/vtls/schannel.c b/lib/vtls/schannel.c index 7f7bd357d..82dff6d53 100644 --- a/lib/vtls/schannel.c +++ b/lib/vtls/schannel.c @@ -61,6 +61,12 @@ /* 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*/ +#if defined(_MSC_VER) && (_MSC_VER >= 1800) +# 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__) @@ -97,6 +103,11 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) 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; @@ -219,6 +230,60 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) infof(data, "schannel: using IP address, SNI is not supported by OS.\n"); } +#ifdef HAS_ALPN + if(data->set.ssl_enable_alpn) { + 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 = 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); @@ -245,7 +310,7 @@ schannel_connect_step1(struct connectdata *conn, int sockindex) 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); @@ -535,6 +600,10 @@ schannel_connect_step3(struct connectdata *conn, int sockindex) struct SessionHandle *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; struct curl_schannel_cred *old_cred = NULL; +#ifdef HAS_ALPN + SECURITY_STATUS sspi_status = SEC_E_OK; + SecPkgContext_ApplicationProtocol alpn_result; +#endif bool incache; DEBUGASSERT(ssl_connect_3 == connssl->connecting_state); @@ -560,6 +629,41 @@ schannel_connect_step3(struct connectdata *conn, int sockindex) return CURLE_SSL_CONNECT_ERROR; } +#ifdef HAS_ALPN + if(data->set.ssl_enable_alpn) { + 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 + /* increment the reference counter of the credential/session handle */ if(connssl->cred && connssl->ctxt) { connssl->cred->refcount++;