From: Rainer Jung Date: Tue, 31 Mar 2015 19:15:32 +0000 (+0000) Subject: Followup to r1670397+r1670434: even more ALPN goodness. X-Git-Tag: 2.5.0-alpha~3341 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=71cf0244463a5ded55c8188748d8f505268275f7;p=apache Followup to r1670397+r1670434: even more ALPN goodness. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1670436 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/modules/ssl/ssl_engine_kernel.c b/modules/ssl/ssl_engine_kernel.c index 29fccccd91..ca589d1218 100644 --- a/modules/ssl/ssl_engine_kernel.c +++ b/modules/ssl/ssl_engine_kernel.c @@ -2397,6 +2397,151 @@ int ssl_callback_AdvertiseNextProtos(SSL *ssl, const unsigned char **data_out, #endif /* 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(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; + } + + /* 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(02839) + "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(02840) + "alpn negotiated protocol name too long"); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *outlen = (unsigned char)len; + + return SSL_TLSEXT_ERR_OK; +} +#endif /* HAVE_TLS_ALPN */ + #ifdef HAVE_SRP int ssl_callback_SRPServerParams(SSL *ssl, int *ad, void *arg)