Hello, I've been looking into the NPN and ALPN support in the various TLS backends to make it possible to use both extensions without requiring HTTP2 support to be built and enabled.
This was to implement TLS False Start checks like firefox and chrome do, but IMO I also managed to simplify and clean-up the code, so implementing additional protocols (e.g. SPDY) would be much easier. See attached patches. Cheers
From f45ac4ef2df38d87ae719481ac419dd008264012 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini <[email protected]> Date: Mon, 16 Feb 2015 16:47:56 +0100 Subject: [PATCH 1/5] openssl: make it possible to enable ALPN/NPN without HTTP2 --- lib/vtls/openssl.c | 106 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 61 insertions(+), 45 deletions(-) diff --git a/lib/vtls/openssl.c b/lib/vtls/openssl.c index 55ab3fe..3af4c75 100644 --- a/lib/vtls/openssl.c +++ b/lib/vtls/openssl.c @@ -1603,8 +1603,6 @@ static void ssl_tls_trace(int direction, int ssl_ver, int content_type, # define use_sni(x) Curl_nop_stmt #endif -#ifdef USE_NGHTTP2 - /* Check for OpenSSL 1.0.2 which has ALPN support. */ #undef HAS_ALPN #if OPENSSL_VERSION_NUMBER >= 0x10002000L \ @@ -1626,6 +1624,23 @@ static void ssl_tls_trace(int direction, int ssl_ver, int content_type, * in is a list of lenght prefixed strings. this function has to select * the protocol we want to use from the list and write its string into out. */ + +static int +select_next_protocol(unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, + const char *key, unsigned int keylen) +{ + unsigned int i; + for(i = 0; i + keylen <= inlen; i += in[i] + 1) { + if(memcmp(&in[i + 1], key, keylen) == 0) { + *out = (unsigned char *) &in[i + 1]; + *outlen = in[i]; + return 0; + } + } + return -1; +} + static int select_next_proto_cb(SSL *ssl, unsigned char **out, unsigned char *outlen, @@ -1633,33 +1648,36 @@ select_next_proto_cb(SSL *ssl, void *arg) { struct connectdata *conn = (struct connectdata*) arg; - int retval = nghttp2_select_next_protocol(out, outlen, in, inlen); (void)ssl; - if(retval == 1) { +#ifdef USE_NGHTTP2 + if(conn->data->set.httpversion == CURL_HTTP_VERSION_2_0 && + !select_next_protocol(out, outlen, in, inlen, NGHTTP2_PROTO_VERSION_ID, + NGHTTP2_PROTO_VERSION_ID_LEN)) { infof(conn->data, "NPN, negotiated HTTP2 (%s)\n", NGHTTP2_PROTO_VERSION_ID); conn->negnpn = NPN_HTTP2; + return SSL_TLSEXT_ERR_OK; } - else if(retval == 0) { +#endif + + if(!select_next_protocol(out, outlen, in, inlen, ALPN_HTTP_1_1, + ALPN_HTTP_1_1_LENGTH)) { infof(conn->data, "NPN, negotiated HTTP1.1\n"); conn->negnpn = NPN_HTTP1_1; - } - else { - infof(conn->data, "NPN, no overlap, use HTTP1.1\n", - NGHTTP2_PROTO_VERSION_ID); - *out = (unsigned char*)"http/1.1"; - *outlen = sizeof("http/1.1") - 1; - conn->negnpn = NPN_HTTP1_1; + return SSL_TLSEXT_ERR_OK; } + infof(conn->data, "NPN, no overlap, use HTTP1.1\n"); + *out = ALPN_HTTP_1_1; + *outlen = ALPN_HTTP_1_1_LENGTH; + conn->negnpn = NPN_HTTP1_1; + return SSL_TLSEXT_ERR_OK; } #endif /* HAS_NPN */ -#endif /* USE_NGHTTP2 */ - static const char * get_ssl_version_txt(SSL *ssl) { @@ -1702,9 +1720,6 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) struct in_addr addr; #endif #endif -#ifdef HAS_ALPN - unsigned char protocols[128]; -#endif DEBUGASSERT(ssl_connect_1 == connssl->connecting_state); @@ -1900,36 +1915,36 @@ static CURLcode ossl_connect_step1(struct connectdata *conn, int sockindex) SSL_CTX_set_options(connssl->ctx, ctx_options); -#ifdef USE_NGHTTP2 - if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { #ifdef HAS_NPN - if(data->set.ssl_enable_npn) { - SSL_CTX_set_next_proto_select_cb(connssl->ctx, select_next_proto_cb, - conn); - } + if(data->set.ssl_enable_npn) + SSL_CTX_set_next_proto_select_cb(connssl->ctx, select_next_proto_cb, conn); #endif #ifdef HAS_ALPN - if(data->set.ssl_enable_alpn) { - protocols[0] = NGHTTP2_PROTO_VERSION_ID_LEN; - memcpy(&protocols[1], NGHTTP2_PROTO_VERSION_ID, - NGHTTP2_PROTO_VERSION_ID_LEN); + if(data->set.ssl_enable_alpn) { + int cur = 0; + unsigned char protocols[128]; - protocols[NGHTTP2_PROTO_VERSION_ID_LEN+1] = ALPN_HTTP_1_1_LENGTH; - memcpy(&protocols[NGHTTP2_PROTO_VERSION_ID_LEN+2], ALPN_HTTP_1_1, - ALPN_HTTP_1_1_LENGTH); - - /* expects length prefixed preference ordered list of protocols in wire - * format - */ - SSL_CTX_set_alpn_protos(connssl->ctx, protocols, - NGHTTP2_PROTO_VERSION_ID_LEN + ALPN_HTTP_1_1_LENGTH + 2); +#ifdef USE_NGHTTP2 + if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { + protocols[cur++] = NGHTTP2_PROTO_VERSION_ID_LEN; - infof(data, "ALPN, offering %s, %s\n", NGHTTP2_PROTO_VERSION_ID, - ALPN_HTTP_1_1); - connssl->asked_for_h2 = TRUE; + memcpy(&protocols[cur], NGHTTP2_PROTO_VERSION_ID, + NGHTTP2_PROTO_VERSION_ID_LEN); + cur += NGHTTP2_PROTO_VERSION_ID_LEN; + infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); } #endif + + protocols[cur++] = ALPN_HTTP_1_1_LENGTH; + memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); + cur += ALPN_HTTP_1_1_LENGTH; + infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); + + /* expects length prefixed preference ordered list of protocols in wire + * format + */ + SSL_CTX_set_alpn_protos(connssl->ctx, protocols, cur); } #endif @@ -2219,18 +2234,19 @@ static CURLcode ossl_connect_step2(struct connectdata *conn, int sockindex) if(len != 0) { infof(data, "ALPN, server accepted to use %.*s\n", len, neg_protocol); +#ifdef USE_NGHTTP2 if(len == NGHTTP2_PROTO_VERSION_ID_LEN && - memcmp(NGHTTP2_PROTO_VERSION_ID, neg_protocol, len) == 0) { + !memcmp(NGHTTP2_PROTO_VERSION_ID, neg_protocol, len)) { conn->negnpn = NPN_HTTP2; } - else if(len == - ALPN_HTTP_1_1_LENGTH && memcmp(ALPN_HTTP_1_1, - neg_protocol, - ALPN_HTTP_1_1_LENGTH) == 0) { + else +#endif + if(len == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, neg_protocol, ALPN_HTTP_1_1_LENGTH)) { conn->negnpn = NPN_HTTP1_1; } } - else if(connssl->asked_for_h2) + else infof(data, "ALPN, server did not agree to a protocol\n"); } #endif -- 2.1.4
From 2eadd22b116bd9f75e093fb7e42d8ce7ea29ae26 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini <[email protected]> Date: Thu, 19 Feb 2015 16:22:07 +0100 Subject: [PATCH 2/5] gtls: make it possible to enable ALPN/NPN without HTTP2 --- lib/vtls/gtls.c | 56 ++++++++++++++++++++++++++++---------------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/lib/vtls/gtls.c b/lib/vtls/gtls.c index 5b91883..5efe6b6 100644 --- a/lib/vtls/gtls.c +++ b/lib/vtls/gtls.c @@ -92,11 +92,8 @@ static bool gtls_inited = FALSE; # define GNUTLS_MAPS_WINSOCK_ERRORS 1 # endif -# ifdef USE_NGHTTP2 -# undef HAS_ALPN -# if (GNUTLS_VERSION_NUMBER >= 0x030200) -# define HAS_ALPN -# endif +# if (GNUTLS_VERSION_NUMBER >= 0x030200) +# define HAS_ALPN # endif # if (GNUTLS_VERSION_NUMBER >= 0x03020d) @@ -400,10 +397,6 @@ gtls_connect_step1(struct connectdata *conn, const char* prioritylist; const char *err = NULL; #endif -#ifdef HAS_ALPN - int protocols_size = 2; - gnutls_datum_t protocols[2]; -#endif if(conn->ssl[sockindex].state == ssl_connection_complete) /* to make us tolerant against being called more than once for the @@ -617,20 +610,25 @@ gtls_connect_step1(struct connectdata *conn, #endif #ifdef HAS_ALPN - if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { - if(data->set.ssl_enable_alpn) { - protocols[0].data = NGHTTP2_PROTO_VERSION_ID; - protocols[0].size = NGHTTP2_PROTO_VERSION_ID_LEN; - protocols[1].data = ALPN_HTTP_1_1; - protocols[1].size = ALPN_HTTP_1_1_LENGTH; - gnutls_alpn_set_protocols(session, protocols, protocols_size, 0); - infof(data, "ALPN, offering %s, %s\n", NGHTTP2_PROTO_VERSION_ID, - ALPN_HTTP_1_1); - conn->ssl[sockindex].asked_for_h2 = TRUE; - } - else { - infof(data, "SSL, can't negotiate HTTP/2.0 without ALPN\n"); + if(data->set.ssl_enable_alpn) { + int cur = 0; + gnutls_datum_t protocols[2]; + +#ifdef USE_NGHTTP2 + if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { + protocols[cur].data = NGHTTP2_PROTO_VERSION_ID; + protocols[cur].size = NGHTTP2_PROTO_VERSION_ID_LEN; + cur++; + infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); } +#endif + + protocols[cur].data = ALPN_HTTP_1_1; + protocols[cur].size = ALPN_HTTP_1_1_LENGTH; + cur++; + infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); + + gnutls_alpn_set_protocols(session, protocols, cur, 0); } #endif @@ -1073,19 +1071,21 @@ gtls_connect_step3(struct connectdata *conn, infof(data, "ALPN, server accepted to use %.*s\n", proto.size, proto.data); +#ifdef USE_NGHTTP2 if(proto.size == NGHTTP2_PROTO_VERSION_ID_LEN && - memcmp(NGHTTP2_PROTO_VERSION_ID, proto.data, - NGHTTP2_PROTO_VERSION_ID_LEN) == 0) { + !memcmp(NGHTTP2_PROTO_VERSION_ID, proto.data, + NGHTTP2_PROTO_VERSION_ID_LEN)) { conn->negnpn = NPN_HTTP2; } - else if(proto.size == ALPN_HTTP_1_1_LENGTH && memcmp(ALPN_HTTP_1_1, - proto.data, ALPN_HTTP_1_1_LENGTH) == 0) { + else +#endif + if(proto.size == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, proto.data, ALPN_HTTP_1_1_LENGTH)) { conn->negnpn = NPN_HTTP1_1; } } - else if(conn->ssl[sockindex].asked_for_h2) { + else infof(data, "ALPN, server did not agree to a protocol\n"); - } } #endif -- 2.1.4
From 2540455ba18b71ecda6b8f3f0b91dd97fdf9cc9c Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini <[email protected]> Date: Mon, 16 Feb 2015 16:41:57 +0100 Subject: [PATCH 3/5] nss: make it possible to enable ALPN/NPN without HTTP2 --- lib/vtls/nss.c | 76 ++++++++++++++++++++++++---------------------------------- 1 file changed, 31 insertions(+), 45 deletions(-) diff --git a/lib/vtls/nss.c b/lib/vtls/nss.c index 16b9124..42d1f16 100644 --- a/lib/vtls/nss.c +++ b/lib/vtls/nss.c @@ -686,14 +686,15 @@ static SECStatus nss_auth_cert_hook(void *arg, PRFileDesc *fd, PRBool checksig, */ static void HandshakeCallback(PRFileDesc *sock, void *arg) { -#ifdef USE_NGHTTP2 struct connectdata *conn = (struct connectdata*) arg; unsigned int buflenmax = 50; unsigned char buf[50]; unsigned int buflen; SSLNextProtoState state; +#ifdef USE_NGHTTP2 struct ssl_connect_data *connssl = &conn->ssl[FIRSTSOCKET]; +#endif if(!conn->data->set.ssl_enable_npn && !conn->data->set.ssl_enable_alpn) { return; @@ -704,8 +705,7 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg) switch(state) { case SSL_NEXT_PROTO_NO_SUPPORT: case SSL_NEXT_PROTO_NO_OVERLAP: - if(connssl->asked_for_h2) - infof(conn->data, "TLS, neither ALPN nor NPN succeeded\n"); + infof(conn->data, "ALPN/NPN, server did not agree to a protocol\n"); return; #ifdef SSL_ENABLE_ALPN case SSL_NEXT_PROTO_SELECTED: @@ -717,19 +717,18 @@ static void HandshakeCallback(PRFileDesc *sock, void *arg) break; } +#ifdef USE_NGHTTP2 if(buflen == NGHTTP2_PROTO_VERSION_ID_LEN && !memcmp(NGHTTP2_PROTO_VERSION_ID, buf, NGHTTP2_PROTO_VERSION_ID_LEN)) { conn->negnpn = NPN_HTTP2; } - else if(buflen == ALPN_HTTP_1_1_LENGTH && - !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) { + else +#endif + if(buflen == ALPN_HTTP_1_1_LENGTH && + !memcmp(ALPN_HTTP_1_1, buf, ALPN_HTTP_1_1_LENGTH)) { conn->negnpn = NPN_HTTP1_1; } } -#else - (void)sock; - (void)arg; -#endif } static void display_cert_info(struct SessionHandle *data, @@ -1455,16 +1454,6 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) SSL_LIBRARY_VERSION_TLS_1_0 /* max */ }; -#ifdef USE_NGHTTP2 -#if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN) - unsigned int alpn_protos_len = NGHTTP2_PROTO_VERSION_ID_LEN + - ALPN_HTTP_1_1_LENGTH + 2; - unsigned char alpn_protos[NGHTTP2_PROTO_VERSION_ID_LEN + ALPN_HTTP_1_1_LENGTH - + 2]; - int cur = 0; -#endif -#endif - connssl->data = data; /* list of all NSS objects we need to destroy in Curl_nss_close() */ @@ -1652,43 +1641,40 @@ static CURLcode nss_setup_connect(struct connectdata *conn, int sockindex) } #endif -#ifdef USE_NGHTTP2 - if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { #ifdef SSL_ENABLE_NPN - if(data->set.ssl_enable_npn) { - if(SSL_OptionSet(connssl->handle, SSL_ENABLE_NPN, PR_TRUE) != SECSuccess) - goto error; - } + if(data->set.ssl_enable_npn) { + if(SSL_OptionSet(connssl->handle, SSL_ENABLE_NPN, PR_TRUE) != SECSuccess) + goto error; + } #endif #ifdef SSL_ENABLE_ALPN - if(data->set.ssl_enable_alpn) { - if(SSL_OptionSet(connssl->handle, SSL_ENABLE_ALPN, PR_TRUE) - != SECSuccess) - goto error; - } + if(data->set.ssl_enable_alpn) { + if(SSL_OptionSet(connssl->handle, SSL_ENABLE_ALPN, PR_TRUE) + != SECSuccess) + goto error; + } #endif #if defined(SSL_ENABLE_NPN) || defined(SSL_ENABLE_ALPN) - if(data->set.ssl_enable_npn || data->set.ssl_enable_alpn) { - alpn_protos[cur] = NGHTTP2_PROTO_VERSION_ID_LEN; - cur++; - memcpy(&alpn_protos[cur], NGHTTP2_PROTO_VERSION_ID, + if(data->set.ssl_enable_npn || data->set.ssl_enable_alpn) { + int cur = 0; + unsigned char protocols[128]; + +#ifdef USE_NGHTTP2 + if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { + protocols[cur++] = NGHTTP2_PROTO_VERSION_ID_LEN; + memcpy(&protocols[cur], NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN); cur += NGHTTP2_PROTO_VERSION_ID_LEN; - alpn_protos[cur] = ALPN_HTTP_1_1_LENGTH; - cur++; - memcpy(&alpn_protos[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); - - if(SSL_SetNextProtoNego(connssl->handle, alpn_protos, alpn_protos_len) - != SECSuccess) - goto error; - connssl->asked_for_h2 = TRUE; - } - else { - infof(data, "SSL, can't negotiate HTTP/2.0 with neither NPN nor ALPN\n"); } #endif + protocols[cur++] = ALPN_HTTP_1_1_LENGTH; + memcpy(&protocols[cur], ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH); + cur += ALPN_HTTP_1_1_LENGTH; + + if(SSL_SetNextProtoNego(connssl->handle, protocols, cur) != SECSuccess) + goto error; } #endif -- 2.1.4
From 26ccdff5a669d21462b205ce8f58242a5d050153 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini <[email protected]> Date: Thu, 19 Feb 2015 19:41:48 +0100 Subject: [PATCH 4/5] polarssl: make it possible to enable ALPN/NPN without HTTP2 --- lib/vtls/polarssl.c | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/vtls/polarssl.c b/lib/vtls/polarssl.c index 5c75197..a3b8c40 100644 --- a/lib/vtls/polarssl.c +++ b/lib/vtls/polarssl.c @@ -120,11 +120,8 @@ static void polarssl_debug(void *context, int level, const char *line) #endif /* ALPN for http2? */ -#ifdef USE_NGHTTP2 -# undef HAS_ALPN -# ifdef POLARSSL_SSL_ALPN -# define HAS_ALPN -# endif +#ifdef POLARSSL_SSL_ALPN +# define HAS_ALPN #endif static Curl_recv polarssl_recv; @@ -359,16 +356,23 @@ polarssl_connect_step1(struct connectdata *conn, } #ifdef HAS_ALPN - if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { - if(data->set.ssl_enable_alpn) { - static const char* protocols[] = { - NGHTTP2_PROTO_VERSION_ID, ALPN_HTTP_1_1, NULL - }; - ssl_set_alpn_protocols(&connssl->ssl, protocols); - infof(data, "ALPN, offering %s, %s\n", protocols[0], - protocols[1]); - connssl->asked_for_h2 = TRUE; + if(data->set.ssl_enable_alpn) { + static const char* protocols[3]; + int cur = 0; + +#ifdef USE_NGHTTP2 + if(data->set.httpversion == CURL_HTTP_VERSION_2_0) { + protocols[cur++] = NGHTTP2_PROTO_VERSION_ID; + infof(data, "ALPN, offering %s\n", NGHTTP2_PROTO_VERSION_ID); } +#endif + + protocols[cur++] = ALPN_HTTP_1_1; + infof(data, "ALPN, offering %s\n", ALPN_HTTP_1_1); + + protocols[cur] = NULL; + + ssl_set_alpn_protocols(&connssl->ssl, protocols); } #endif @@ -390,10 +394,6 @@ polarssl_connect_step2(struct connectdata *conn, struct ssl_connect_data* connssl = &conn->ssl[sockindex]; char buffer[1024]; -#ifdef HAS_ALPN - const char* next_protocol; -#endif - char errorbuf[128]; errorbuf[0] = 0; @@ -463,22 +463,24 @@ polarssl_connect_step2(struct connectdata *conn, #ifdef HAS_ALPN if(data->set.ssl_enable_alpn) { - next_protocol = ssl_get_alpn_protocol(&connssl->ssl); + const char *next_protocol = ssl_get_alpn_protocol(&connssl->ssl); if(next_protocol != NULL) { infof(data, "ALPN, server accepted to use %s\n", next_protocol); +#ifdef USE_NGHTTP2 if(!strncmp(next_protocol, NGHTTP2_PROTO_VERSION_ID, NGHTTP2_PROTO_VERSION_ID_LEN)) { conn->negnpn = NPN_HTTP2; } - else if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) { + else +#endif + if(!strncmp(next_protocol, ALPN_HTTP_1_1, ALPN_HTTP_1_1_LENGTH)) { conn->negnpn = NPN_HTTP1_1; } } - else if(connssl->asked_for_h2) { + else infof(data, "ALPN, server did not agree to a protocol\n"); - } } #endif -- 2.1.4
From de4ef6ee118265dbd4d7b809865336a44eeffe28 Mon Sep 17 00:00:00 2001 From: Alessandro Ghedini <[email protected]> Date: Wed, 25 Feb 2015 11:33:12 +0100 Subject: [PATCH 5/5] urldata: remove unused asked_for_h2 field --- lib/urldata.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/urldata.h b/lib/urldata.h index 50a745f..db2618a 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -288,9 +288,6 @@ struct ssl_connect_data { current state of the connection. */ bool use; ssl_connection_state state; -#ifdef USE_NGHTTP2 - bool asked_for_h2; -#endif #ifdef USE_SSLEAY /* these ones requires specific SSL-types */ SSL_CTX* ctx; -- 2.1.4
signature.asc
Description: Digital signature
------------------------------------------------------------------- List admin: http://cool.haxx.se/list/listinfo/curl-library Etiquette: http://curl.haxx.se/mail/etiquette.html
