Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

schannel: Add ALPN support #724

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/HTTP2.md
Expand Up @@ -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/)
-------------------------------
Expand Down Expand Up @@ -58,6 +58,7 @@ provide the necessary TLS features. Right now we support:
- NSS: ALPN and NPN
- GnuTLS: ALPN
- PolarSSL: ALPN
- SChannel: ALPN

Multiplexing
------------
Expand Down
106 changes: 105 additions & 1 deletion lib/vtls/schannel.c
Expand Up @@ -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__)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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++;
Expand Down