https://bugs.gentoo.org/471512 upstream apache has merged alpn into trunk: https://issues.apache.org/bugzilla/show_bug.cgi?id=52210 note: the bug is closed INVALID due to the npn discussion; go to the bottom to see alpn merged into it trunk. unfortunately, it wasn't merged into the 2.4 branch. the mod_h2 project has backported it to the 2.4 branch: https://github.com/icing/mod_h2/tree/master/sandbox/httpd/patches commit 73e4d0e9c813b58581a32a6948780fa948094cc1 --- modules/ssl/mod_ssl.c +++ modules/ssl/mod_ssl.c @@ -273,6 +273,12 @@ "OpenSSL configuration command") #endif +#ifdef HAVE_TLS_ALPN + SSL_CMD_SRV(ALPNPreference, ITERATE, + "Preference in Application-Layer Protocol Negotiation (ALPN), " + "protocols are chosen in the specified order") +#endif + /* Deprecated directives. */ AP_INIT_RAW_ARGS("SSLLog", ap_set_deprecated, NULL, OR_ALL, "SSLLog directive is no longer supported - use ErrorLog."), @@ -423,12 +448,44 @@ return 1; } +static int modssl_register_alpn(conn_rec *c, + ssl_alpn_propose_protos advertisefn, + ssl_alpn_proto_negotiated negotiatedfn) +{ +#ifdef HAVE_TLS_ALPN + SSLConnRec *sslconn = myConnConfig(c); + + if (!sslconn) { + return DECLINED; + } + + if (!sslconn->alpn_proposefns) { + sslconn->alpn_proposefns = + apr_array_make(c->pool, 5, sizeof(ssl_alpn_propose_protos)); + sslconn->alpn_negofns = + apr_array_make(c->pool, 5, sizeof(ssl_alpn_proto_negotiated)); + } + + if (advertisefn) + APR_ARRAY_PUSH(sslconn->alpn_proposefns, ssl_alpn_propose_protos) = + advertisefn; + if (negotiatedfn) + APR_ARRAY_PUSH(sslconn->alpn_negofns, ssl_alpn_proto_negotiated) = + negotiatedfn; + + return OK; +#else + return DECLINED; +#endif +} + int ssl_init_ssl_connection(conn_rec *c, request_rec *r) { SSLSrvConfigRec *sc; SSL *ssl; SSLConnRec *sslconn = myConnConfig(c); char *vhost_md5; + int rc; modssl_ctx_t *mctx; server_rec *server; @@ -585,6 +647,7 @@ APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); + APR_REGISTER_OPTIONAL_FN(modssl_register_alpn); ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl", AUTHZ_PROVIDER_VERSION, --- modules/ssl/mod_ssl.h +++ modules/ssl/mod_ssl.h @@ -63,5 +93,46 @@ APR_DECLARE_OPTIONAL_FN(int, ssl_engine_disable, (conn_rec *)); +/** The alpn_propose_proto callback allows other modules to propose + * the name of the protocol that will be chosen during the + * Application-Layer Protocol Negotiation (ALPN) portion of the SSL handshake. + * The callback is given the connection and a list of NULL-terminated + * protocol strings as supported by the client. If this client_protos is + * non-empty, it must pick its preferred protocol from that list. Otherwise + * it should add its supported protocols in order of precedence. + * The callback should not yet modify the connection or install any filters + * as its proposal(s) may be overridden by another callback or server + * configuration. + * It should return OK or, to prevent further processing of (other modules') + * callbacks, return DONE. + */ +typedef int (*ssl_alpn_propose_protos)(conn_rec *connection, + apr_array_header_t *client_protos, + apr_array_header_t *proposed_protos); + +/** The alpn_proto_negotiated callback allows other modules to discover + * the name of the protocol that was chosen during the Application-Layer + * Protocol Negotiation (ALPN) portion of the SSL handshake. + * The callback is given the connection, a + * non-NUL-terminated string containing the protocol name, and the + * length of the string; it should do something appropriate + * (i.e. insert or remove filters) and return OK. To prevent further + * processing of (other modules') callbacks, return DONE. */ +typedef int (*ssl_alpn_proto_negotiated)(conn_rec *connection, + const char *proto_name, + apr_size_t proto_name_len); + +/* An optional function which can be used to register a pair of callbacks + * for ALPN handling. + * This optional function should be invoked from a pre_connection hook + * which runs *after* mod_ssl.c's pre_connection hook. The function returns + * OK if the callbacks are registered, or DECLINED otherwise (for example if + * mod_ssl does not support ALPN). + */ +APR_DECLARE_OPTIONAL_FN(int, modssl_register_alpn, + (conn_rec *conn, + ssl_alpn_propose_protos proposefn, + ssl_alpn_proto_negotiated negotiatedfn)); + #endif /* __MOD_SSL_H__ */ /** @} */ --- modules/ssl/ssl_engine_config.c +++ modules/ssl/ssl_engine_config.c @@ -159,6 +160,9 @@ SSL_CONF_CTX_set_flags(mctx->ssl_ctx_config, SSL_CONF_FLAG_CERTIFICATE); mctx->ssl_ctx_param = apr_array_make(p, 5, sizeof(ssl_ctx_param_t)); #endif +#ifdef HAVE_TLS_ALPN + mctx->ssl_alpn_pref = apr_array_make(p, 5, sizeof(const char *)); +#endif } static void modssl_ctx_init_proxy(SSLSrvConfigRec *sc, @@ -301,6 +307,9 @@ #ifdef HAVE_SSL_CONF_CMD cfgMergeArray(ssl_ctx_param); #endif +#ifdef HAVE_TLS_ALPN + cfgMergeArray(ssl_alpn_pref); +#endif } static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p, @@ -1875,6 +1868,16 @@ } #endif +#ifdef HAVE_TLS_ALPN +const char *ssl_cmd_SSLALPNPreference(cmd_parms *cmd, void *dcfg, + const char *protocol) +{ + SSLSrvConfigRec *sc = mySrvConfig(cmd->server); + APR_ARRAY_PUSH(sc->server->ssl_alpn_pref, const char *) = protocol; + return NULL; +} +#endif + #ifdef HAVE_SRP const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, --- modules/ssl/ssl_engine_init.c +++ modules/ssl/ssl_engine_init.c @@ -623,6 +646,11 @@ SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH); 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 } static apr_status_t ssl_init_ctx_verify(server_rec *s, --- modules/ssl/ssl_engine_io.c +++ modules/ssl/ssl_engine_io.c @@ -28,6 +28,7 @@ core keeps dumping.'' -- Unknown */ #include "ssl_private.h" +#include "mod_ssl.h" #include "apr_date.h" /* _________________________________________________________________ @@ -297,6 +315,9 @@ apr_pool_t *pool; char buffer[AP_IOBUFSIZE]; ssl_filter_ctx_t *filter_ctx; +#ifdef HAVE_TLS_ALPN + int alpn_finished; /* 1 if ALPN has finished, 0 otherwise */ +#endif } bio_filter_in_ctx_t; /* @@ -1412,6 +1485,37 @@ APR_BRIGADE_INSERT_TAIL(bb, bucket); } +#ifdef HAVE_TLS_ALPN + /* By this point, Application-Layer Protocol Negotiation (ALPN) should be + * completed (if our version of OpenSSL supports it). If we haven't already, + * find out which protocol was decided upon and inform other modules + * by calling alpn_proto_negotiated_hook. + */ + if (!inctx->alpn_finished) { + SSLConnRec *sslconn = myConnConfig(f->c); + const unsigned char *next_proto = NULL; + unsigned next_proto_len = 0; + int n; + + if (sslconn->alpn_negofns) { + SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c, + APLOGNO(02836) "SSL negotiated protocol: '%s'", + (next_proto && next_proto_len)? + apr_pstrmemdup(f->c->pool, (const char *)next_proto, + next_proto_len) : "(null)"); + for (n = 0; n < sslconn->alpn_negofns->nelts; n++) { + ssl_alpn_proto_negotiated fn = + APR_ARRAY_IDX(sslconn->alpn_negofns, n, ssl_alpn_proto_negotiated); + + if (fn(f->c, (const char *)next_proto, next_proto_len) == DONE) + break; + } + } + inctx->alpn_finished = 1; + } +#endif + return APR_SUCCESS; } @@ -1893,6 +1996,9 @@ inctx->block = APR_BLOCK_READ; inctx->pool = c->pool; inctx->filter_ctx = filter_ctx; +#ifdef HAVE_TLS_ALPN + inctx->alpn_finished = 0; +#endif } /* The request_rec pointer is passed in here only to ensure that the --- modules/ssl/ssl_engine_kernel.c +++ modules/ssl/ssl_engine_kernel.c @@ -29,6 +29,7 @@ time I was too famous.'' -- Unknown */ #include "ssl_private.h" +#include "mod_ssl.h" #include "util_md5.h" static void ssl_configure_env(request_rec *r, SSLConnRec *sslconn); @@ -2137,6 +2162,153 @@ } #endif /* HAVE_TLS_SESSION_TICKETS */ +#ifdef HAVE_TLS_ALPN +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; +} + +/* + * Compare two 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; + size_t len; + + /* 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(02837) + "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(02838) + "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; + } + + proposed_protos = apr_array_make(c->pool, client_protos->nelts+1, + sizeof(char *)); + + 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) { + /* Regardless of installed hooks, the http/1.1 protocol is always + * supported by us. Choose it if none other matches. */ + if (ssl_array_index(client_protos, alpn_http1) < 0) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02839) + "none of the client ALPN protocols are supported"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *out = (const unsigned char*)alpn_http1; + *outlen = (unsigned char)strlen(alpn_http1); + return SSL_TLSEXT_ERR_OK; + } + + /* 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; + } + } + + len = strlen((const char*)*out); + if (len > 255) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02840) + "ALPN negotiated protocol name too long"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *outlen = (unsigned char)len; + + return SSL_TLSEXT_ERR_OK; +} +#endif + #ifdef HAVE_SRP int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg) --- modules/ssl/ssl_private.h +++ modules/ssl/ssl_private.h @@ -182,6 +182,11 @@ #include #endif +/* ALPN Protocol Negotiation */ +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) +#define HAVE_TLS_ALPN +#endif + #endif /* !defined(OPENSSL_NO_TLSEXT) && defined(SSL_set_tlsext_host_name) */ /* mod_ssl headers */ @@ -443,6 +438,12 @@ * connection */ } reneg_state; +#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 + server_rec *server; } SSLConnRec; @@ -633,6 +633,10 @@ SSL_CONF_CTX *ssl_ctx_config; /* Configuration context */ apr_array_header_t *ssl_ctx_param; /* parameters to pass to SSL_CTX */ #endif + +#ifdef HAVE_TLS_ALPN + apr_array_header_t *ssl_alpn_pref; /* protocol names in order of preference */ +#endif } modssl_ctx_t; struct SSLSrvConfigRec { @@ -763,6 +763,10 @@ const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2); #endif +#ifdef HAVE_TLS_ALPN +const char *ssl_cmd_SSLALPNPreference(cmd_parms *cmd, void *dcfg, const char *protocol); +#endif + #ifdef HAVE_SRP const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, const char *arg); const char *ssl_cmd_SSLSRPUnknownUserSeed(cmd_parms *cmd, void *dcfg, const char *arg); @@ -815,6 +815,12 @@ EVP_CIPHER_CTX *, HMAC_CTX *, int); #endif +#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 + /** Session Cache Support */ apr_status_t ssl_scache_init(server_rec *, apr_pool_t *); void ssl_scache_status_register(apr_pool_t *p);