Changeset 9cee2e9 in mod_gnutls


Ignore:
Timestamp:
Apr 11, 2018, 1:41:07 AM (6 months ago)
Author:
Fiona Klute <fiona.klute@…>
Branches:
debian/master, debian/stretch-backports, master, upstream
Children:
d50dac5
Parents:
2f10643
Message:

Support ALPN (required for secure HTTP/2)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/gnutls_hooks.c

    r2f10643 r9cee2e9  
    135135}
    136136
     137
     138
     139/**
     140 * Get the list of available protocols for this connection and add it
     141 * to the GnuTLS session. Must run before the client hello function.
     142 */
     143static void prepare_alpn_proposals(mgs_handle_t *ctxt)
     144{
     145    /* Check if any protocol upgrades are available
     146     *
     147     * The "report_all" parameter to ap_get_protocol_upgrades() is 0
     148     * (report only more preferable protocols) because setting it to 1
     149     * doesn't actually report ALL protocols, but only all except the
     150     * current one. This way we can at least list the current one as
     151     * available by appending it without potentially negotiating a
     152     * less preferred protocol. */
     153    const apr_array_header_t *pupgrades = NULL;
     154    apr_status_t ret =
     155        ap_get_protocol_upgrades(ctxt->c, NULL, ctxt->c->base_server,
     156                                 /*report_all*/ 0, &pupgrades);
     157    if (ret != APR_SUCCESS)
     158    {
     159        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
     160                      "%s: ap_get_protocol_upgrades() failed, "
     161                      "cannot configure ALPN!", __func__);
     162        return;
     163    }
     164
     165    if (pupgrades == NULL || pupgrades->nelts == 0)
     166    {
     167        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctxt->c,
     168                      "%s: No protocol upgrades available.", __func__);
     169        return;
     170    }
     171
     172    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
     173                  "%s: Found %d protocol upgrade(s) for ALPN: %s",
     174                  __func__, pupgrades->nelts,
     175                  apr_array_pstrcat(ctxt->c->pool, pupgrades, ','));
     176    gnutls_datum_t *alpn_protos =
     177        apr_palloc(ctxt->c->pool,
     178                   (pupgrades->nelts + 1) * sizeof(gnutls_datum_t));
     179    for (int i = 0; i < pupgrades->nelts; i++)
     180    {
     181        alpn_protos[i].data = (void *) APR_ARRAY_IDX(pupgrades, i, char *);
     182        alpn_protos[i].size =
     183            strnlen(APR_ARRAY_IDX(pupgrades, i, char *),
     184                    pupgrades->elt_size);
     185    }
     186
     187    /* Add the current (default) protocol at the end of the list */
     188    alpn_protos[pupgrades->nelts].data =
     189        (void*) apr_pstrdup(ctxt->c->pool, ap_get_protocol(ctxt->c));
     190    alpn_protos[pupgrades->nelts].size =
     191        strlen((char*) alpn_protos[pupgrades->nelts].data);
     192    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctxt->c,
     193                  "%s: Adding current protocol %s to ALPN set.",
     194                  __func__, alpn_protos[pupgrades->nelts].data);
     195
     196    gnutls_alpn_set_protocols(ctxt->session,
     197                              alpn_protos,
     198                              pupgrades->nelts,
     199                              GNUTLS_ALPN_SERVER_PRECEDENCE);
     200}
     201
     202
     203
     204/**
     205 * Check if ALPN selected any protocol upgrade, try to switch if so.
     206 */
     207static int process_alpn_result(mgs_handle_t *ctxt)
     208{
     209    int ret = 0;
     210    gnutls_datum_t alpn_proto;
     211    ret = gnutls_alpn_get_selected_protocol(ctxt->session, &alpn_proto);
     212    if (ret != GNUTLS_E_SUCCESS)
     213    {
     214        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
     215                      "%s: No ALPN result: %s (%d)",
     216                      __func__, gnutls_strerror(ret), ret);
     217        return GNUTLS_E_SUCCESS;
     218    }
     219
     220    apr_array_header_t *client_protos =
     221        apr_array_make(ctxt->c->pool, 1, sizeof(char *));
     222    /* apr_pstrndup to ensure that the protocol is null terminated */
     223    APR_ARRAY_PUSH(client_protos, char *) =
     224        apr_pstrndup(ctxt->c->pool, (char*) alpn_proto.data, alpn_proto.size);
     225    const char *selected =
     226        ap_select_protocol(ctxt->c, NULL, ctxt->c->base_server,
     227                           client_protos);
     228
     229    /* ap_select_protocol() will return NULL if none of the ALPN
     230     * proposals matched. GnuTLS negotiated alpn_proto based on the
     231     * list provided by the server, but the vhost might have changed
     232     * based on SNI. Apache seems to adjust the proposal list to avoid
     233     * such issues though.
     234     *
     235     * GnuTLS will return a fatal "no_application_protocol" alert as
     236     * required by RFC 7301 if the post client hello function returns
     237     * GNUTLS_E_NO_APPLICATION_PROTOCOL. */
     238    if (!selected)
     239    {
     240        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, ctxt->c,
     241                      "%s: ap_select_protocol() returned NULL! Please "
     242                      "make sure any overlapping vhosts have the same "
     243                      "protocols available.",
     244                      __func__);
     245        return GNUTLS_E_NO_APPLICATION_PROTOCOL;
     246    }
     247
     248    if (strcmp(selected, ap_get_protocol(ctxt->c)) == 0)
     249    {
     250        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
     251                      "%s: Already using protocol '%s', nothing to do.",
     252                      __func__, selected);
     253        return GNUTLS_E_SUCCESS;
     254    }
     255
     256    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
     257                  "%s: Switching protocol to '%s' based on ALPN.",
     258                  __func__, selected);
     259    apr_status_t status = ap_switch_protocol(ctxt->c, NULL,
     260                                             ctxt->c->base_server,
     261                                             selected);
     262    if (status != APR_SUCCESS)
     263    {
     264        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, ctxt->c,
     265                      "%s: Protocol switch to '%s' failed!",
     266                      __func__, selected);
     267        return GNUTLS_E_NO_APPLICATION_PROTOCOL;
     268    }
     269    /* ALPN done! */
     270    return GNUTLS_E_SUCCESS;
     271}
     272
     273
     274
    137275/**
    138276 * Post client hello function for GnuTLS, used to configure the TLS
     
    173311#endif
    174312
    175     /* update the priorities - to avoid negotiating a ciphersuite that is not
     313    ret = process_alpn_result(ctxt);
     314    if (ret != GNUTLS_E_SUCCESS)
     315        return ret;
     316
     317    /* Update the priorities - to avoid negotiating a ciphersuite that is not
    176318     * enabled on this virtual server. Note that here we ignore the version
    177      * negotiation.
    178      */
     319     * negotiation. */
    179320    ret = gnutls_priority_set(session, ctxt->sc->priorities);
    180321
     
    10401181    }
    10411182
     1183    prepare_alpn_proposals(ctxt);
     1184
    10421185    /* Initialize Session Cache */
    10431186    mgs_cache_session_init(ctxt);
Note: See TracChangeset for help on using the changeset viewer.