From: Jim Jagielski Date: Tue, 31 Mar 2015 19:12:08 +0000 (+0000) Subject: More ALPN goodness X-Git-Tag: 2.5.0-alpha~3342 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=7a9999187470a20c7b485676971120b2da198270;p=apache More ALPN goodness git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1670434 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index 4aebbd400c..a9324830e5 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -483,7 +483,7 @@ static int modssl_register_alpn(conn_rec *c, ssl_alpn_propose_protos advertisefn, ssl_alpn_proto_negotiated negotiatedfn) { -#if defined(HAVE_TLS_ALPN) || defined(HAVE_TLS_NPN) +#ifdef HAVE_TLS_ALPN SSLConnRec *sslconn = myConnConfig(c); if (!sslconn) { diff --git a/modules/ssl/ssl_engine_init.c b/modules/ssl/ssl_engine_init.c index 728ee01693..34457250df 100644 --- a/modules/ssl/ssl_engine_init.c +++ b/modules/ssl/ssl_engine_init.c @@ -645,6 +645,11 @@ static void ssl_init_ctx_callbacks(server_rec *s, SSL_CTX_set_info_callback(ctx, ssl_callback_Info); +#ifdef HAVE_TLS_ALPN + SSL_CTX_set_alpn_select_cb( + ctx, ssl_callback_alpn_select, NULL); +#endif + #ifdef HAVE_TLS_NPN SSL_CTX_set_next_protos_advertised_cb( ctx, ssl_callback_AdvertiseNextProtos, NULL); diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 0d5588db6d..29fccccd91 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -2162,7 +2162,151 @@ int ssl_callback_SessionTicket(SSL *ssl, } #endif /* HAVE_TLS_SESSION_TICKETS */ -#ifdef HAVE_TLS_NPN +static int ssl_array_index(apr_array_header_t *array, + const char *s) +{ + int i; + for (i = 0; i < array->nelts; i++) { + const char *p = APR_ARRAY_IDX(array, i, const char*); + if (!strcmp(p, s)) { + return i; + } + } + return -1; +} + +#ifdef HAVE_TLS_ALPN +/* + * Compare to ALPN protocol proposal. Result is similar to strcmp(): + * 0 gives same precedence, >0 means proto1 is prefered. + */ +static int ssl_cmp_alpn_protos(modssl_ctx_t *ctx, + const char *proto1, + const char *proto2) +{ + /* TODO: we should have a mod_ssl configuration parameter. */ + if (ctx && ctx->ssl_alpn_pref) { + int index1 = ssl_array_index(ctx->ssl_alpn_pref, proto1); + int index2 = ssl_array_index(ctx->ssl_alpn_pref, proto2); + if (index2 > index1) { + return (index1 >= 0)? 1 : -1; + } + else if (index1 > index2) { + return (index2 >= 0)? -1 : 1; + } + } + /* both have the same index (mabye -1 or no pref configured) and we compare + * the names so that spdy3 gets precedence over spdy2. That makes + * the outcome at least deterministic. */ + return strcmp((const char *)proto1, (const char *)proto2); +} + +/* + * This callback function is executed when the TLS Application Layer + * Protocol Negotiate Extension (ALPN, RFC 7301) is triggered by the client + * hello, giving a list of desired protocol names (in descending preference) + * to the server. + * The callback has to select a protocol name or return an error if none of + * the clients preferences is supported. + * The selected protocol does not have to be on the client list, according + * to RFC 7301, so no checks are performed. + * The client protocol list is serialized as length byte followed by ascii + * characters (not null-terminated), followed by the next protocol name. + */ +int ssl_callback_alpn_select(SSL *ssl, + const unsigned char **out, unsigned char *outlen, + const unsigned char *in, unsigned int inlen, void *arg) +{ + conn_rec *c = (conn_rec*)SSL_get_app_data(ssl); + SSLConnRec *sslconn = myConnConfig(c); + server_rec *s = mySrvFromConn(c); + SSLSrvConfigRec *sc = mySrvConfig(s); + modssl_ctx_t *mctx = myCtxConfig(sslconn, sc); + const char *alpn_http1 = "http/1.1"; + apr_array_header_t *client_protos; + apr_array_header_t *proposed_protos; + int i; + + /* If the connection object is not available, + * then there's nothing for us to do. */ + if (c == NULL) { + return SSL_TLSEXT_ERR_OK; + } + + if (inlen == 0) { + // someone tries to trick us? + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO() + "alpn client protocol list empty"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + client_protos = apr_array_make(c->pool, 0, sizeof(char *)); + for (i = 0; i < inlen; /**/) { + unsigned int plen = in[i++]; + if (plen + i > inlen) { + // someone tries to trick us? + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO() + "alpn protocol identier too long"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + APR_ARRAY_PUSH(client_protos, char*) = + apr_pstrndup(c->pool, (const char *)in+i, plen); + i += plen; + } + + /* Regardless of installed hooks, the http/1.1 protocol is always + * supported by us. Add it to the proposals if the client also + * offers it. */ + proposed_protos = apr_array_make(c->pool, client_protos->nelts+1, + sizeof(char *)); + if (ssl_array_index(client_protos, alpn_http1) >= 0) { + APR_ARRAY_PUSH(proposed_protos, const char*) = alpn_http1; + } + + if (sslconn->alpn_proposefns != NULL) { + /* Invoke our alpn_propos_proto hooks, giving other modules a chance to + * propose protocol names for selection. We might have several such + * hooks installed and if two make a proposal, we need to give + * preference to one. + */ + for (i = 0; i < sslconn->alpn_proposefns->nelts; i++) { + ssl_alpn_propose_protos fn = + APR_ARRAY_IDX(sslconn->alpn_proposefns, i, + ssl_alpn_propose_protos); + + if (fn(c, client_protos, proposed_protos) == DONE) + break; + } + } + + if (proposed_protos->nelts <= 0) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO() + "none of the client alpn protocols are supported"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + /* Now select the most preferred protocol from the proposals. */ + *out = APR_ARRAY_IDX(proposed_protos, 0, const unsigned char *); + for (i = 1; i < proposed_protos->nelts; ++i) { + const char *proto = APR_ARRAY_IDX(proposed_protos, i, const char*); + /* Do we prefer it over existing candidate? */ + if (ssl_cmp_alpn_protos(mctx, (const char *)*out, proto) < 0) { + *out = (const unsigned char*)proto; + } + } + + size_t len = strlen((const char*)*out); + if (len > 255) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO() + "alpn negotiated protocol name too long"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *outlen = (unsigned char)len; + + return SSL_TLSEXT_ERR_OK; +} +#endif +#if defined(HAVE_TLS_NPN) /* * This callback function is executed when SSL needs to decide what protocols * to advertise during Next Protocol Negotiation (NPN). It must produce a diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index b21e7948af..fbd92cef2a 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -454,8 +454,8 @@ typedef struct { apr_array_header_t *npn_negofns; /* list of ssl_npn_proto_negotiated callbacks. */ #endif -#if defined(HAVE_TLS_ALPN) || defined(HAVE_TLS_NPN) - /* Poor man's inter-module optional hooks for NPN. */ +#ifdef HAVE_TLS_ALPN + /* Poor man's inter-module optional hooks for ALPN. */ apr_array_header_t *alpn_proposefns; /* list of ssl_alpn_propose_protos callbacks */ apr_array_header_t *alpn_negofns; /* list of ssl_alpn_proto_negotiated callbacks. */ #endif @@ -821,14 +821,13 @@ int ssl_callback_ServerNameIndication(SSL *, int *, modssl_ctx_t *); int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *, EVP_CIPHER_CTX *, HMAC_CTX *, int); #endif -int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg); #ifdef HAVE_TLS_ALPN int ssl_callback_alpn_select(SSL *ssl, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg); #endif -#if defined(HAVE_TLS_NPN) +#ifdef HAVE_TLS_NPN int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data, unsigned int *len, void *arg); #endif