diff -ru gen/httpd-2.4.x/modules/ssl/mod_ssl.c modules/ssl/mod_ssl.c --- gen/httpd-2.4.x/modules/ssl/mod_ssl.c 2015-01-19 16:52:30.000000000 +0100 +++ modules/ssl/mod_ssl.c 2015-01-19 15:42:53.904000000 +0100 @@ -273,6 +270,12 @@ "OpenSSL configuration command") #endif +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) + SSL_CMD_SRV(AlpnPreference, ITERATE, + "Preference in Application-Layer Protocol Negotiation (ALPN), " + "protocols are chosed 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,6 +423,37 @@ return 1; } +static int modssl_register_alpn(conn_rec *c, + ssl_alpn_propose_protos advertisefn, + ssl_alpn_proto_negotiated negotiatedfn) +{ +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) + 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; @@ -585,6 +616,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, diff -ru gen/httpd-2.4.x/modules/ssl/mod_ssl.h modules/ssl/mod_ssl.h --- gen/httpd-2.4.x/modules/ssl/mod_ssl.h 2015-01-07 17:03:34.000000000 +0100 +++ modules/ssl/mod_ssl.h 2015-01-19 15:42:53.904000000 +0100 @@ -63,5 +63,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__ */ /** @} */ diff -ru gen/httpd-2.4.x/modules/ssl/ssl_engine_config.c modules/ssl/ssl_engine_config.c --- gen/httpd-2.4.x/modules/ssl/ssl_engine_config.c 2015-01-19 16:52:30.000000000 +0100 +++ modules/ssl/ssl_engine_config.c 2015-01-19 16:34:55.944000000 +0100 @@ -159,6 +159,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 +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) + mctx->ssl_alpn_pref = apr_array_make(p, 5, sizeof(const char *)); +#endif } static void modssl_ctx_init_proxy(SSLSrvConfigRec *sc, @@ -298,6 +300,9 @@ #ifdef HAVE_SSL_CONF_CMD cfgMergeArray(ssl_ctx_param); #endif +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) + cfgMergeArray(ssl_alpn_pref); +#endif } static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p, @@ -1868,6 +1861,16 @@ return NULL; } #endif + +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) +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 diff -ru gen/httpd-2.4.x/modules/ssl/ssl_engine_init.c modules/ssl/ssl_engine_init.c --- gen/httpd-2.4.x/modules/ssl/ssl_engine_init.c 2015-01-19 16:52:30.000000000 +0100 +++ modules/ssl/ssl_engine_init.c 2015-01-19 15:42:53.908000000 +0100 @@ -623,6 +613,14 @@ SSL_CTX_set_tmp_dh_callback(ctx, ssl_callback_TmpDH); SSL_CTX_set_info_callback(ctx, ssl_callback_Info); + +#if defined(HAVE_TLS_ALPN) + SSL_CTX_set_alpn_select_cb( + ctx, ssl_callback_alpn_select, NULL); +#elif defined(HAVE_TLS_NPN) + SSL_CTX_set_next_protos_advertised_cb( + ctx, ssl_callback_AdvertiseNextProtos, NULL); +#endif } static apr_status_t ssl_init_ctx_verify(server_rec *s, diff -ru gen/httpd-2.4.x/modules/ssl/ssl_engine_io.c modules/ssl/ssl_engine_io.c --- gen/httpd-2.4.x/modules/ssl/ssl_engine_io.c 2015-01-19 16:52:30.000000000 +0100 +++ modules/ssl/ssl_engine_io.c 2015-01-19 15:42:53.908000000 +0100 @@ -28,6 +28,7 @@ core keeps dumping.'' -- Unknown */ #include "ssl_private.h" +#include "mod_ssl.h" #include "apr_date.h" /* _________________________________________________________________ @@ -297,6 +298,7 @@ apr_pool_t *pool; char buffer[AP_IOBUFSIZE]; ssl_filter_ctx_t *filter_ctx; + int alpn_finished; /* 1 if ALPN has finished, 0 otherwise */ } bio_filter_in_ctx_t; /* @@ -1412,6 +1414,43 @@ APR_BRIGADE_INSERT_TAIL(bb, bucket); } +#if defined(HAVE_TLS_ALPN) || defined(HAVE_TLS_NPN) + /* 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) { + #ifdef HAVE_TLS_ALPN + SSL_get0_alpn_selected(inctx->ssl, &next_proto, &next_proto_len); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c, + APLOGNO(02306) "SSL ALPN negotiated protocol: '%*s'", + next_proto_len, (const char*)next_proto); + #else + SSL_get0_next_proto_negotiated( + inctx->ssl, &next_proto, &next_proto_len); + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->c, + APLOGNO(02306) "SSL NPN negotiated protocol: '%*s'", + next_proto_len, (const char*)next_proto); + #endif + 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 +1932,7 @@ inctx->block = APR_BLOCK_READ; inctx->pool = c->pool; inctx->filter_ctx = filter_ctx; + inctx->alpn_finished = 0; } /* The request_rec pointer is passed in here only to ensure that the diff -ru gen/httpd-2.4.x/modules/ssl/ssl_engine_kernel.c modules/ssl/ssl_engine_kernel.c --- gen/httpd-2.4.x/modules/ssl/ssl_engine_kernel.c 2015-01-19 16:52:30.000000000 +0100 +++ modules/ssl/ssl_engine_kernel.c 2015-01-19 16:37:09.536000000 +0100 @@ -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); @@ -2136,6 +2131,267 @@ } #endif /* HAVE_TLS_SESSION_TICKETS */ +static int ssl_array_index(apr_array_header_t *array, + const unsigned char *s) +{ + int i; + for (i = 0; i < array->nelts; i++) { + const unsigned char *p = APR_ARRAY_IDX(array, i, const unsigned char*); + if (!strcmp((const char *)p, (const char *)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 unsigned char *proto1, + const unsigned 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 unsigned char *alpn_http1 = (const unsigned char*)"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(02306) + "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(02306) + "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 unsigned 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(02306) + "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 unsigned char *proto = APR_ARRAY_IDX(proposed_protos, i, + const unsigned char*); + /* Do we prefer it over existing candidate? */ + if (ssl_cmp_alpn_protos(mctx, *out, proto) < 0) { + *out = proto; + } + } + + size_t len = strlen((const char*)*out); + if (len > 255) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02306) + "alpn negotiated protocol name too long"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *outlen = (unsigned char)len; + + return SSL_TLSEXT_ERR_OK; +} + +#elif 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 + * string in wire format -- a sequence of length-prefixed strings -- indicating + * the advertised protocols. Refer to SSL_CTX_set_next_protos_advertised_cb + * in OpenSSL for reference. + */ +int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data_out, + unsigned int *size_out, 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); + apr_array_header_t *protos; + int num_protos; + unsigned int size; + int i, j; + unsigned char *data; + unsigned char *start; + + *data_out = NULL; + *size_out = 0; + + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02306) + "advertisingNextProtos"); + + /* If the connection object is not available, or there are no NPN + * hooks registered, then there's nothing for us to do. */ + if (c == NULL || sslconn->alpn_proposefns == NULL) { + return SSL_TLSEXT_ERR_OK; + } + + /* Invoke our alpn_propose_proto hook, giving other modules a chance to + * add alternate protocol names to advertise. */ + protos = apr_array_make(c->pool, 0, sizeof(char *)); + 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, NULL, protos) == DONE) + break; + } + num_protos = protos->nelts; + + ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, c, APLOGNO(02306) + "alpn protos %d to advertise, %d in pref config", num_protos, mctx->ssl_alpn_pref->nelts ); + if (num_protos > 1 && mctx->ssl_alpn_pref && mctx->ssl_alpn_pref->nelts > 0) { + /* Sort the protocol names according to our configured preferences. */ + int insert_idx = 0; + for (i = 0; i < mctx->ssl_alpn_pref->nelts; ++i) { + const char *proto = APR_ARRAY_IDX(mctx->ssl_alpn_pref, i, const char*); + int idx = ssl_array_index(protos, proto); + if (idx > insert_idx) { + /* bubble found protocol up */ + for (j = idx; j > insert_idx; --j) { + ((const char **)protos->elts)[j] = ((const char **)protos->elts)[j-1]; + } + ((const char **)protos->elts)[insert_idx] = proto; + ++insert_idx; + } + } + } + + /* We now have a list of null-terminated strings; we need to concatenate + * them together into a single string, where each protocol name is prefixed + * by its length. First, calculate how long that string will be. */ + size = 0; + for (i = 0; i < num_protos; ++i) { + const char *string = APR_ARRAY_IDX(protos, i, const char*); + unsigned int length = strlen(string); + /* If the protocol name is too long (the length must fit in one byte), + * then log an error and skip it. */ + if (length > 255) { + ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02307) + "SSL NPN protocol name too long (length=%u): %s", + length, string); + continue; + } + /* Leave room for the length prefix (one byte) plus the protocol name + * itself. */ + size += 1 + length; + } + + /* If there is nothing to advertise (either because no modules added + * anything to the protos array, or because all strings added to the array + * were skipped), then we're done. */ + if (size == 0) { + return SSL_TLSEXT_ERR_OK; + } + + /* Now we can build the string. Copy each protocol name string into the + * larger string, prefixed by its length. */ + data = apr_palloc(c->pool, size * sizeof(unsigned char)); + start = data; + for (i = 0; i < num_protos; ++i) { + const char *string = APR_ARRAY_IDX(protos, i, const char*); + apr_size_t length = strlen(string); + if (length > 255) + continue; + *start = (unsigned char)length; + ++start; + memcpy(start, string, length * sizeof(unsigned char)); + start += length; + } + + /* Success. */ + *data_out = data; + *size_out = size; + return SSL_TLSEXT_ERR_OK; +} + +#endif /* HAVE_TLS_NPN */ + #ifdef HAVE_SRP int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg) diff -ru gen/httpd-2.4.x/modules/ssl/ssl_private.h modules/ssl/ssl_private.h --- gen/httpd-2.4.x/modules/ssl/ssl_private.h 2015-01-19 16:52:30.000000000 +0100 +++ modules/ssl/ssl_private.h 2015-01-19 15:42:53.908000000 +0100 @@ -176,6 +169,16 @@ #endif #endif +/* ALPN Protocol Negotiation */ +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(OPENSSL_NO_TLSEXT) +#define HAVE_TLS_ALPN +#endif + +/* Next Protocol Negotiation */ +#if !defined(OPENSSL_NO_NEXTPROTONEG) && defined(OPENSSL_NPN_NEGOTIATED) +#define HAVE_TLS_NPN +#endif + /* Secure Remote Password */ #if !defined(OPENSSL_NO_SRP) && defined(SSL_CTRL_SET_TLS_EXT_SRP_USERNAME_CB) #define HAVE_SRP @@ -443,6 +446,12 @@ * connection */ } reneg_state; +#ifdef HAVE_TLS_NPN + /* Poor man's inter-module optional hooks for NPN. */ + 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; @@ -622,6 +631,10 @@ SSL_CONF_CTX *ssl_ctx_config; /* Configuration context */ apr_array_header_t *ssl_ctx_param; /* parameters to pass to SSL_CTX */ #endif + +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) + apr_array_header_t *ssl_alpn_pref; /* protocol names in order of preference */ +#endif } modssl_ctx_t; struct SSLSrvConfigRec { @@ -748,6 +759,10 @@ const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2); #endif +#if defined(HAVE_ALPN_NPN) || defined(HAVE_TLS_NPN) +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); @@ -796,6 +811,14 @@ 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); +#elif defined(HAVE_TLS_NPN) +int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data, unsigned int *len, 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);