source: mod_gnutls/src/gnutls_proxy.c @ 641d11b

Last change on this file since 641d11b was 641d11b, checked in by Fiona Klute <fiona.klute@…>, 6 months ago

Delete session tickets for proxy connections when using them

Tickets should not be reused because an attacker could correlate
connections using the same ticket. Cache deletion code has been
extracted from socache_delete_session() into a generic function.

  • Property mode set to 100644
File size: 17.3 KB
Line 
1/*
2 *  Copyright 2015-2020 Fiona Klute
3 *
4 *  Licensed under the Apache License, Version 2.0 (the "License");
5 *  you may not use this file except in compliance with the License.
6 *  You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 */
16
17#include "mod_gnutls.h"
18#include "gnutls_cache.h"
19#include "gnutls_proxy.h"
20#include "gnutls_util.h"
21
22#include <apr_strings.h>
23#include <gnutls/gnutls.h>
24
25APLOG_USE_MODULE(gnutls);
26
27/*
28 * Callback to check the server certificate for proxy HTTPS
29 * connections, to be used with
30 * gnutls_certificate_set_verify_function.
31
32 * Returns: 0 if certificate check was successful (certificate
33 * trusted), non-zero otherwise (error during check or untrusted
34 * certificate).
35 */
36static int gtls_check_server_cert(gnutls_session_t session)
37{
38    mgs_handle_t *ctxt = (mgs_handle_t *) gnutls_session_get_ptr(session);
39    unsigned int status;
40
41    /* Get peer hostname from a note left by mod_proxy */
42    const char *peer_hostname =
43        apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
44    if (peer_hostname == NULL)
45        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
46                      "%s: " PROXY_SNI_NOTE " NULL, cannot check "
47                      "peer's hostname", __func__);
48
49    /* Verify certificate, including hostname match. Should
50     * peer_hostname be NULL for some reason, the name is not
51     * checked. */
52    int err = gnutls_certificate_verify_peers3(session, peer_hostname,
53                                               &status);
54    if (err != GNUTLS_E_SUCCESS)
55    {
56        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
57                      "%s: server certificate check failed: %s (%d)",
58                      __func__, gnutls_strerror(err), err);
59        return err;
60    }
61
62    if (status == 0)
63        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
64                      "%s: server certificate is trusted.",
65                      __func__);
66    else
67    {
68        gnutls_datum_t out;
69        /* GNUTLS_CRT_X509: ATM, only X509 is supported for proxy
70         * certs 0: according to function API, the last argument
71         * should be 0 */
72        err = gnutls_certificate_verification_status_print(status,
73                                                           GNUTLS_CRT_X509,
74                                                           &out, 0);
75        if (err != GNUTLS_E_SUCCESS)
76            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
77                          "%s: server verify print failed: %s (%d)",
78                          __func__, gnutls_strerror(err), err);
79        else
80            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
81                          "%s: %s",
82                          __func__, out.data);
83        gnutls_free(out.data);
84    }
85
86    return status;
87}
88
89
90
91static apr_status_t cleanup_proxy_x509_credentials(void *arg)
92{
93    mgs_srvconf_rec *sc = (mgs_srvconf_rec *) arg;
94
95    if (sc->proxy_x509_creds)
96    {
97        /* This implicitly releases the associated trust list
98         * sc->proxy_x509_tl, too. */
99        gnutls_certificate_free_credentials(sc->proxy_x509_creds);
100        sc->proxy_x509_creds = NULL;
101        sc->proxy_x509_tl = NULL;
102    }
103
104    if (sc->anon_client_creds)
105    {
106        gnutls_anon_free_client_credentials(sc->anon_client_creds);
107        sc->anon_client_creds = NULL;
108    }
109
110    /* Deinit proxy priorities only if set from
111     * sc->proxy_priorities_str. Otherwise the server is using the
112     * default global priority cache, which must not be deinitialized
113     * here. */
114    if (sc->proxy_priorities_str && sc->proxy_priorities)
115    {
116        gnutls_priority_deinit(sc->proxy_priorities);
117        sc->proxy_priorities = NULL;
118    }
119
120    return APR_SUCCESS;
121}
122
123
124
125apr_status_t load_proxy_x509_credentials(apr_pool_t *pconf,
126                                         apr_pool_t *ptemp,
127                                         server_rec *s)
128{
129    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
130        ap_get_module_config(s->module_config, &gnutls_module);
131
132    if (sc == NULL)
133        return APR_EGENERAL;
134
135    apr_status_t ret = APR_EINIT;
136    int err = GNUTLS_E_SUCCESS;
137
138    /* Cleanup function for the GnuTLS structures allocated below */
139    apr_pool_cleanup_register(pconf, sc, cleanup_proxy_x509_credentials,
140                              apr_pool_cleanup_null);
141
142    /* Function pool, gets destroyed before exit. */
143    apr_pool_t *pool;
144    ret = apr_pool_create(&pool, ptemp);
145    if (ret != APR_SUCCESS)
146    {
147        ap_log_error(APLOG_MARK, APLOG_ERR, ret, s,
148                     "%s: failed to allocate function memory pool.", __func__);
149        return ret;
150    }
151
152    /* allocate credentials structures */
153    err = gnutls_certificate_allocate_credentials(&sc->proxy_x509_creds);
154    if (err != GNUTLS_E_SUCCESS)
155    {
156        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
157                     "%s: Failed to initialize proxy credentials: (%d) %s",
158                     __func__, err, gnutls_strerror(err));
159        return APR_EGENERAL;
160    }
161    err = gnutls_anon_allocate_client_credentials(&sc->anon_client_creds);
162    if (err != GNUTLS_E_SUCCESS)
163    {
164        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
165                     "%s: Failed to initialize anon credentials for proxy: "
166                     "(%d) %s", __func__, err, gnutls_strerror(err));
167        return APR_EGENERAL;
168    }
169
170    /* Check if the proxy priorities have been set, fail immediately
171     * if not */
172    if (sc->proxy_priorities_str == NULL)
173    {
174        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
175                     "No GnuTLSProxyPriorities directive for host '%s:%d', "
176                     "using default '%s'.",
177                     s->server_hostname, s->addrs->host_port,
178                     MGS_DEFAULT_PRIORITY);
179        sc->proxy_priorities = mgs_get_default_prio();
180    }
181    else
182    {
183        /* parse proxy priorities */
184        const char *err_pos = NULL;
185        err = gnutls_priority_init(&sc->proxy_priorities,
186                                   sc->proxy_priorities_str, &err_pos);
187        if (err != GNUTLS_E_SUCCESS)
188        {
189            if (ret == GNUTLS_E_INVALID_REQUEST)
190                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
191                             "%s: Syntax error parsing proxy priorities "
192                             "string at: %s",
193                             __func__, err_pos);
194            else
195                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
196                             "Error setting proxy priorities: %s (%d)",
197                             gnutls_strerror(err), err);
198            ret = APR_EGENERAL;
199        }
200    }
201
202    /* load certificate and key for client auth, if configured */
203    if (sc->proxy_x509_key_file && sc->proxy_x509_cert_file)
204    {
205        char* cert_file = ap_server_root_relative(pool,
206                                                  sc->proxy_x509_cert_file);
207        char* key_file = ap_server_root_relative(pool,
208                                                 sc->proxy_x509_key_file);
209        err = gnutls_certificate_set_x509_key_file(sc->proxy_x509_creds,
210                                                   cert_file,
211                                                   key_file,
212                                                   GNUTLS_X509_FMT_PEM);
213        if (err != GNUTLS_E_SUCCESS)
214        {
215            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
216                         "%s: loading proxy client credentials failed: %s (%d)",
217                         __func__, gnutls_strerror(err), err);
218            ret = APR_EGENERAL;
219        }
220    }
221    else if (!sc->proxy_x509_key_file && sc->proxy_x509_cert_file)
222    {
223        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
224                     "%s: proxy key file not set!", __func__);
225        ret = APR_EGENERAL;
226    }
227    else if (!sc->proxy_x509_cert_file && sc->proxy_x509_key_file)
228    {
229        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
230                     "%s: proxy certificate file not set!", __func__);
231        ret = APR_EGENERAL;
232    }
233    else
234        /* if both key and cert are NULL, client auth is not used */
235        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
236                     "%s: no client credentials for proxy", __func__);
237
238    /* must be set if the server certificate is to be checked */
239    if (sc->proxy_x509_ca_file)
240    {
241        /* initialize the trust list */
242        err = gnutls_x509_trust_list_init(&sc->proxy_x509_tl, 0);
243        if (err != GNUTLS_E_SUCCESS)
244        {
245            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
246                         "%s: gnutls_x509_trust_list_init failed: %s (%d)",
247                         __func__, gnutls_strerror(err), err);
248            ret = APR_EGENERAL;
249        }
250
251        char* ca_file = ap_server_root_relative(pool,
252                                                sc->proxy_x509_ca_file);
253        /* if no CRL is used, sc->proxy_x509_crl_file is NULL */
254        char* crl_file = NULL;
255        if (sc->proxy_x509_crl_file)
256            crl_file = ap_server_root_relative(pool,
257                                               sc->proxy_x509_crl_file);
258
259        /* returns number of loaded elements */
260        err = gnutls_x509_trust_list_add_trust_file(sc->proxy_x509_tl,
261                                                    ca_file,
262                                                    crl_file,
263                                                    GNUTLS_X509_FMT_PEM,
264                                                    0 /* tl_flags */,
265                                                    0 /* tl_vflags */);
266        if (err > 0)
267            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
268                         "%s: proxy CA trust list: %d structures loaded",
269                         __func__, err);
270        else if (err == 0)
271            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
272                         "%s: proxy CA trust list is empty (%d)",
273                         __func__, err);
274        else /* err < 0 */
275        {
276            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
277                         "%s: error loading proxy CA trust list: %s (%d)",
278                         __func__, gnutls_strerror(err), err);
279            ret = APR_EGENERAL;
280        }
281
282        /* attach trust list to credentials */
283        gnutls_certificate_set_trust_list(sc->proxy_x509_creds,
284                                          sc->proxy_x509_tl, 0);
285    }
286    else
287        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
288                     "%s: no CA trust list for proxy connections, "
289                     "TLS connections will fail!", __func__);
290
291    gnutls_certificate_set_verify_function(sc->proxy_x509_creds,
292                                           gtls_check_server_cert);
293    apr_pool_destroy(pool);
294    return ret;
295}
296
297
298
299/**
300 * Returns either a valid hostname for use with SNI, or NULL.
301 */
302static const char *get_proxy_sni_name(mgs_handle_t *ctxt)
303{
304    /* Get peer hostname from note left by mod_proxy */
305    const char *peer_hostname =
306        apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
307
308    /* Used only as target for apr_ipsubnet_create() */
309    apr_ipsubnet_t *probe;
310    /* If the note is present (!= NULL) check that the value is NOT an
311     * IP address, which wouldn't be valid for SNI. */
312    if ((peer_hostname != NULL)
313        && (apr_ipsubnet_create(&probe, peer_hostname, NULL, ctxt->c->pool)
314            == APR_SUCCESS))
315        return NULL;
316
317    return peer_hostname;
318}
319
320
321
322static void proxy_conn_set_sni(mgs_handle_t *ctxt)
323{
324    const char *peer_hostname = get_proxy_sni_name(ctxt);
325    if (peer_hostname != NULL)
326    {
327        int ret = gnutls_server_name_set(ctxt->session, GNUTLS_NAME_DNS,
328                                         peer_hostname, strlen(peer_hostname));
329        if (ret != GNUTLS_E_SUCCESS)
330            ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
331                          "Could not set SNI '%s' for proxy connection: "
332                          "%s (%d)",
333                          peer_hostname, gnutls_strerror(ret), ret);
334    }
335}
336
337
338
339/** Initial size for the APR array storing ALPN protocol
340 * names. Currently only mod_proxy_http2 uses ALPN for proxy
341 * connections and proposes "h2" exclusively. This provides enough
342 * room without additional allocation even if an HTTP/1.1 fallback
343 * should be added while still being small. */
344#define INIT_ALPN_ARR_SIZE 2
345
346/**
347 * Set ALPN proposals for a proxy handshake based on the note from the
348 * proxy module (see `PROXY_SNI_NOTE`). The note is expected to
349 * contain a string, multiple protocol names can be separated by ","
350 * or " ", or a combination of them.
351 *
352 * @param ctxt the mod_gnutls connection handle
353 */
354static void proxy_conn_set_alpn(mgs_handle_t *ctxt)
355{
356    const char *proxy_alpn =
357        apr_table_get(ctxt->c->notes, PROXY_ALPN_NOTE);
358    if (proxy_alpn == NULL)
359        return;
360    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
361                  "%s: proxy module ALPN note is '%s', "
362                  "length %" APR_SIZE_T_FMT ".",
363                  __func__, proxy_alpn, strlen(proxy_alpn));
364
365    apr_array_header_t* protocols =
366        apr_array_make(ctxt->c->pool, INIT_ALPN_ARR_SIZE,
367                       sizeof(const char *));
368
369    /* mod_ssl tokenizes the note by "," or " " to allow multiple
370     * protocols. We need to copy the note because apr_strtok()
371     * modifies the string to make each token NULL terminated. On the
372     * plus side that means we do not need to copy individual
373     * tokens. */
374    char *tok = apr_pstrdup(ctxt->c->pool, proxy_alpn);
375    /* state for apr_strtok, pointer to character following current
376     * token */
377    char *last = NULL;
378    while ((tok = apr_strtok(tok, ", ", &last)))
379    {
380        APR_ARRAY_PUSH(protocols, const char *) = tok;
381        tok = NULL;
382    }
383
384    gnutls_datum_t* alpn_protos =
385        mgs_str_array_to_datum_array(protocols,
386                                     ctxt->c->pool,
387                                     protocols->nelts);
388    int ret = gnutls_alpn_set_protocols(ctxt->session,
389                                        alpn_protos,
390                                        protocols->nelts,
391                                        0 /* flags */);
392    if (ret != GNUTLS_E_SUCCESS)
393        ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
394                      "Could not set ALPN proposals for proxy "
395                      "connection: %s (%d)",
396                      gnutls_strerror(ret), ret);
397}
398
399
400
401/**
402 * Check if there is a cached session for the connection, and load it
403 * if yes. The session is deleted from the cache after that, because
404 * tickets should not be reused for forward secrecy.
405 *
406 * @param ctxt the mod_gnutls connection handle
407 */
408static void proxy_conn_load_session(mgs_handle_t *ctxt)
409{
410    /* No cache means there cannot be a cached ticket. */
411    if (!ctxt->sc->cache_enable)
412        return;
413
414    gnutls_datum_t data = {NULL, 0};
415    data.data = gnutls_malloc(MGS_SESSION_FETCH_BUF_SIZE);
416    if (data.data == NULL)
417        return;
418    data.size = MGS_SESSION_FETCH_BUF_SIZE;
419
420    apr_status_t rv = mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
421                                      ctxt->proxy_ticket_key, &data,
422                                      ctxt->c->pool);
423    if (rv != APR_SUCCESS)
424    {
425        gnutls_free(data.data);
426        return;
427    }
428
429    /* Best effort attempt to avoid ticket reuse. Unfortunately
430     * another thread or process could update (or remove) the cache in
431     * between, but that can't be avoided without forcing use of a
432     * global mutex even with a multiprocess-safe socache provider. */
433    mgs_cache_delete(ctxt->sc->cache, ctxt->c->base_server,
434                     ctxt->proxy_ticket_key, ctxt->c->pool);
435
436    int ret = gnutls_session_set_data(ctxt->session, data.data, data.size);
437    if (ret == GNUTLS_E_SUCCESS)
438        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
439                      "%s: Cached session loaded.", __func__);
440    else
441        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
442                      "%s: Loading cached session failed: %s (%d)",
443                      __func__, gnutls_strerror(ret), ret);
444    gnutls_free(data.data);
445}
446
447
448
449gnutls_datum_t mgs_proxy_ticket_id(mgs_handle_t *ctxt, apr_pool_t *pool)
450{
451    apr_pool_t *tmp;
452    if (pool)
453        tmp = pool;
454    else
455        tmp = ctxt->c->pool;
456
457    /* c->client_addr->port and c->client_ip actually contain
458     * information on the remote server for outgoing proxy
459     * connections, prefer SNI hostname over IP.
460     *
461     * The server_hostname is used to tie the cache entry to a
462     * specific vhost, because different vhosts may have different
463     * settings for the same backend server.
464     */
465    const char *peer_hostname = get_proxy_sni_name(ctxt);
466    gnutls_datum_t key;
467    key.data = (unsigned char *)
468        apr_psprintf(tmp, "proxy:%s:%s:%d",
469                     ctxt->c->base_server->server_hostname,
470                     peer_hostname ? peer_hostname : ctxt->c->client_ip,
471                     ctxt->c->client_addr->port);
472    key.size = strlen((const char*) key.data);
473    return key;
474}
475
476
477
478void mgs_set_proxy_handshake_ext(mgs_handle_t *ctxt)
479{
480    proxy_conn_set_sni(ctxt);
481    proxy_conn_set_alpn(ctxt);
482    proxy_conn_load_session(ctxt);
483}
Note: See TracBrowser for help on using the repository browser.