From 17565ac48c618f16192680cbd2511a694db64694 Mon Sep 17 00:00:00 2001 From: Jim Jagielski Date: Tue, 31 Mar 2015 17:12:51 +0000 Subject: [PATCH] ALPN support, based on mod_spdy/mod_h2 patch set git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1670397 13f79535-47bb-0310-9956-ffa450edef68 --- modules/ssl/mod_ssl.c | 38 ++++++++++++++++++++++++++++++ modules/ssl/mod_ssl.h | 41 +++++++++++++++++++++++++++++++++ modules/ssl/ssl_engine_config.c | 16 +++++++++++++ modules/ssl/ssl_engine_io.c | 33 ++++++++++++++++++++++++++ modules/ssl/ssl_private.h | 33 ++++++++++++++++++++++++++ 5 files changed, 161 insertions(+) diff --git a/modules/ssl/mod_ssl.c b/modules/ssl/mod_ssl.c index afd051a582..4aebbd400c 100644 --- a/modules/ssl/mod_ssl.c +++ b/modules/ssl/mod_ssl.c @@ -283,6 +283,12 @@ static const command_rec ssl_config_cmds[] = { "OpenSSL configuration command") #endif +#if defined(HAVE_TLS_ALPN) || 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."), @@ -473,6 +479,37 @@ static int modssl_register_npn(conn_rec *c, #endif } +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) + 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; @@ -642,6 +679,7 @@ static void ssl_register_hooks(apr_pool_t *p) APR_REGISTER_OPTIONAL_FN(ssl_proxy_enable); APR_REGISTER_OPTIONAL_FN(ssl_engine_disable); APR_REGISTER_OPTIONAL_FN(modssl_register_npn); + APR_REGISTER_OPTIONAL_FN(modssl_register_alpn); ap_register_auth_provider(p, AUTHZ_PROVIDER_GROUP, "ssl", AUTHZ_PROVIDER_VERSION, diff --git a/modules/ssl/mod_ssl.h b/modules/ssl/mod_ssl.h index 2a45652853..a3ea694ec3 100644 --- a/modules/ssl/mod_ssl.h +++ b/modules/ssl/mod_ssl.h @@ -128,5 +128,46 @@ APR_DECLARE_OPTIONAL_FN(int, modssl_register_npn, (conn_rec *conn, ssl_npn_advertise_protos advertisefn, ssl_npn_proto_negotiated negotiatedfn)); +/** 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 --git a/modules/ssl/ssl_engine_config.c b/modules/ssl/ssl_engine_config.c index 9f2348d7e5..b15302f033 100644 --- a/modules/ssl/ssl_engine_config.c +++ b/modules/ssl/ssl_engine_config.c @@ -160,6 +160,9 @@ static void modssl_ctx_init(modssl_ctx_t *mctx, apr_pool_t *p) 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_TLS_ALPN) || 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, @@ -304,6 +307,9 @@ static void modssl_ctx_cfg_merge(apr_pool_t *p, #ifdef HAVE_SSL_CONF_CMD cfgMergeArray(ssl_ctx_param); #endif +#if defined(HAVE_TLS_ALPN) || defined(HAVE_TLS_NPN) + cfgMergeArray(ssl_alpn_pref); +#endif } static void modssl_ctx_cfg_merge_proxy(apr_pool_t *p, @@ -1857,6 +1863,16 @@ const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, } #endif +#if defined(HAVE_TLS_ALPN) || 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 const char *ssl_cmd_SSLSRPVerifierFile(cmd_parms *cmd, void *dcfg, diff --git a/modules/ssl/ssl_engine_io.c b/modules/ssl/ssl_engine_io.c index f0f970f827..1480ee3386 100644 --- a/modules/ssl/ssl_engine_io.c +++ b/modules/ssl/ssl_engine_io.c @@ -316,6 +316,7 @@ typedef struct { char buffer[AP_IOBUFSIZE]; ssl_filter_ctx_t *filter_ctx; int npn_finished; /* 1 if NPN has finished, 0 otherwise */ + int alpn_finished; /* 1 if ALPN has finished, 0 otherwise */ } bio_filter_in_ctx_t; /* @@ -1483,6 +1484,37 @@ static apr_status_t ssl_io_filter_input(ap_filter_t *f, 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() "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 + #ifdef HAVE_TLS_NPN /* By this point, Next Protocol Negotiation (NPN) should be completed (if * our version of OpenSSL supports it). If we haven't already, find out @@ -1995,6 +2027,7 @@ static void ssl_io_input_add_filter(ssl_filter_ctx_t *filter_ctx, conn_rec *c, inctx->pool = c->pool; inctx->filter_ctx = filter_ctx; inctx->npn_finished = 0; + inctx->alpn_finished = 0; } /* The request_rec pointer is passed in here only to ensure that the diff --git a/modules/ssl/ssl_private.h b/modules/ssl/ssl_private.h index 9553e594dc..b21e7948af 100644 --- a/modules/ssl/ssl_private.h +++ b/modules/ssl/ssl_private.h @@ -181,6 +181,16 @@ #define HAVE_TLS_NPN #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_NO_TLSEXT) && 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 @@ -444,6 +454,12 @@ 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. */ + 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; @@ -624,6 +640,10 @@ typedef struct { 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_TLS_ALPN) || defined(HAVE_TLS_NPN) + apr_array_header_t *ssl_alpn_pref; /* protocol names in order of preference */ +#endif } modssl_ctx_t; struct SSLSrvConfigRec { @@ -750,6 +770,10 @@ const char *ssl_cmd_SSLOCSPEnable(cmd_parms *cmd, void *dcfg, int flag); const char *ssl_cmd_SSLOpenSSLConfCmd(cmd_parms *cmd, void *dcfg, const char *arg1, const char *arg2); #endif +#if defined(HAVE_TLS_ALPN) || 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); @@ -799,6 +823,15 @@ int ssl_callback_SessionTicket(SSL *, unsigned char *, unsigned char *, #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) +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); -- 2.50.1