]> granicus.if.org Git - curl/commitdiff
schannel: Add ALPN support
authorJDepooter <joel.depooter@gmail.com>
Mon, 21 Mar 2016 06:59:57 +0000 (23:59 -0700)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 24 Mar 2016 08:56:12 +0000 (09:56 +0100)
Add ALPN support for schannel. This allows cURL to negotiate
HTTP/2.0 connections when built with schannel.

Closes #724

docs/HTTP2.md
lib/vtls/schannel.c

index bf53820708e3d39afbb435a5571d1d23b19a9b20..6f7ece431c7126257f445a8166145e549c72728e 100644 (file)
@@ -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
 ------------
index 7f7bd357dc697191e990afc2a4969b8a571235ce..82dff6d539acc71d91b40c0294d6a9efd37a32a9 100644 (file)
 /* 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++;