source: mod_gnutls/src/gnutls_proxy.c @ 796d9a3

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

Cache session tickets for proxy connections only when using TLS 1.3

TLS 1.3 provides forward secrecy for resumed sessions, TLS 1.2 does
not. The check has to happen before the check for
GNUTLS_SFLAGS_SESSION_TICKET because that flag doesn't seem to get set
for TLS 1.2 sessions, which would lead to misleading error
messages. The check for a cache comes first because it is the most
simple one.

  • Property mode set to 100644
File size: 19.5 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
299int mgs_proxy_got_ticket_func(gnutls_session_t session,
300                              unsigned int htype,
301                              unsigned when,
302                              unsigned int incoming __attribute__((unused)),
303                              const gnutls_datum_t *msg __attribute__((unused)))
304{
305    /* Ignore all unexpected messages */
306    if (htype != GNUTLS_HANDSHAKE_NEW_SESSION_TICKET
307        || when != GNUTLS_HOOK_POST)
308        return GNUTLS_E_SUCCESS;
309
310    mgs_handle_t *ctxt = gnutls_session_get_ptr(session);
311
312    /* No cache means we cannot cache tickets. */
313    if (!ctxt->sc->cache_enable)
314        return GNUTLS_E_SUCCESS;
315
316    if (gnutls_protocol_get_version(ctxt->session) != GNUTLS_TLS1_3)
317    {
318        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
319                      "%s: session tickets for proxy connections are used "
320                      "only with TLS 1.3.", __func__);
321        return GNUTLS_E_SUCCESS;
322    }
323
324    if (!(gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET))
325    {
326        ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, ctxt->c,
327                      "%s called but session has no ticket!",
328                      __func__);
329        /* Tickets are optional, so don't break the session on
330         * errors. */
331        return GNUTLS_E_SUCCESS;
332    }
333
334    gnutls_datum_t ticket;
335    int ret = gnutls_session_get_data2(session, &ticket);
336    if (ret != GNUTLS_E_SUCCESS)
337    {
338        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
339                      "%s: error reading session ticket: %s (%d)",
340                      __func__, gnutls_strerror(ret), ret);
341        if (ticket.data)
342            gnutls_free(ticket.data);
343        return GNUTLS_E_SUCCESS;
344    }
345
346    apr_time_t expiry = apr_time_now() + ctxt->sc->cache_timeout;
347    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
348                  "%s: caching session ticket for %s (%u bytes)",
349                  __func__, ctxt->proxy_ticket_key.data, ticket.size);
350    mgs_cache_store(ctxt->sc->cache, ctxt->c->base_server,
351                    ctxt->proxy_ticket_key, ticket, expiry);
352    gnutls_free(ticket.data);
353    return GNUTLS_E_SUCCESS;
354}
355
356
357
358/**
359 * Returns either a valid hostname for use with SNI, or NULL.
360 */
361static const char *get_proxy_sni_name(mgs_handle_t *ctxt)
362{
363    /* Get peer hostname from note left by mod_proxy */
364    const char *peer_hostname =
365        apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
366
367    /* Used only as target for apr_ipsubnet_create() */
368    apr_ipsubnet_t *probe;
369    /* If the note is present (!= NULL) check that the value is NOT an
370     * IP address, which wouldn't be valid for SNI. */
371    if ((peer_hostname != NULL)
372        && (apr_ipsubnet_create(&probe, peer_hostname, NULL, ctxt->c->pool)
373            == APR_SUCCESS))
374        return NULL;
375
376    return peer_hostname;
377}
378
379
380
381static void proxy_conn_set_sni(mgs_handle_t *ctxt)
382{
383    const char *peer_hostname = get_proxy_sni_name(ctxt);
384    if (peer_hostname != NULL)
385    {
386        int ret = gnutls_server_name_set(ctxt->session, GNUTLS_NAME_DNS,
387                                         peer_hostname, strlen(peer_hostname));
388        if (ret != GNUTLS_E_SUCCESS)
389            ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
390                          "Could not set SNI '%s' for proxy connection: "
391                          "%s (%d)",
392                          peer_hostname, gnutls_strerror(ret), ret);
393    }
394}
395
396
397
398/** Initial size for the APR array storing ALPN protocol
399 * names. Currently only mod_proxy_http2 uses ALPN for proxy
400 * connections and proposes "h2" exclusively. This provides enough
401 * room without additional allocation even if an HTTP/1.1 fallback
402 * should be added while still being small. */
403#define INIT_ALPN_ARR_SIZE 2
404
405/**
406 * Set ALPN proposals for a proxy handshake based on the note from the
407 * proxy module (see `PROXY_SNI_NOTE`). The note is expected to
408 * contain a string, multiple protocol names can be separated by ","
409 * or " ", or a combination of them.
410 *
411 * @param ctxt the mod_gnutls connection handle
412 */
413static void proxy_conn_set_alpn(mgs_handle_t *ctxt)
414{
415    const char *proxy_alpn =
416        apr_table_get(ctxt->c->notes, PROXY_ALPN_NOTE);
417    if (proxy_alpn == NULL)
418        return;
419    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
420                  "%s: proxy module ALPN note is '%s', "
421                  "length %" APR_SIZE_T_FMT ".",
422                  __func__, proxy_alpn, strlen(proxy_alpn));
423
424    apr_array_header_t* protocols =
425        apr_array_make(ctxt->c->pool, INIT_ALPN_ARR_SIZE,
426                       sizeof(const char *));
427
428    /* mod_ssl tokenizes the note by "," or " " to allow multiple
429     * protocols. We need to copy the note because apr_strtok()
430     * modifies the string to make each token NULL terminated. On the
431     * plus side that means we do not need to copy individual
432     * tokens. */
433    char *tok = apr_pstrdup(ctxt->c->pool, proxy_alpn);
434    /* state for apr_strtok, pointer to character following current
435     * token */
436    char *last = NULL;
437    while ((tok = apr_strtok(tok, ", ", &last)))
438    {
439        APR_ARRAY_PUSH(protocols, const char *) = tok;
440        tok = NULL;
441    }
442
443    gnutls_datum_t* alpn_protos =
444        mgs_str_array_to_datum_array(protocols,
445                                     ctxt->c->pool,
446                                     protocols->nelts);
447    int ret = gnutls_alpn_set_protocols(ctxt->session,
448                                        alpn_protos,
449                                        protocols->nelts,
450                                        0 /* flags */);
451    if (ret != GNUTLS_E_SUCCESS)
452        ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
453                      "Could not set ALPN proposals for proxy "
454                      "connection: %s (%d)",
455                      gnutls_strerror(ret), ret);
456}
457
458
459
460/**
461 * Check if there is a cached session for the connection, and load it
462 * if yes. The session is deleted from the cache after that, because
463 * tickets should not be reused for forward secrecy.
464 *
465 * @param ctxt the mod_gnutls connection handle
466 */
467static void proxy_conn_load_session(mgs_handle_t *ctxt)
468{
469    /* No cache means there cannot be a cached ticket. */
470    if (!ctxt->sc->cache_enable)
471        return;
472
473    gnutls_datum_t data = {NULL, 0};
474    data.data = gnutls_malloc(MGS_SESSION_FETCH_BUF_SIZE);
475    if (data.data == NULL)
476        return;
477    data.size = MGS_SESSION_FETCH_BUF_SIZE;
478
479    apr_status_t rv = mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
480                                      ctxt->proxy_ticket_key, &data,
481                                      ctxt->c->pool);
482    if (rv != APR_SUCCESS)
483    {
484        gnutls_free(data.data);
485        return;
486    }
487
488    /* Best effort attempt to avoid ticket reuse. Unfortunately
489     * another thread or process could update (or remove) the cache in
490     * between, but that can't be avoided without forcing use of a
491     * global mutex even with a multiprocess-safe socache provider. */
492    mgs_cache_delete(ctxt->sc->cache, ctxt->c->base_server,
493                     ctxt->proxy_ticket_key, ctxt->c->pool);
494
495    int ret = gnutls_session_set_data(ctxt->session, data.data, data.size);
496    if (ret == GNUTLS_E_SUCCESS)
497        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
498                      "%s: Cached session loaded.", __func__);
499    else
500        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
501                      "%s: Loading cached session failed: %s (%d)",
502                      __func__, gnutls_strerror(ret), ret);
503    gnutls_free(data.data);
504}
505
506
507
508gnutls_datum_t mgs_proxy_ticket_id(mgs_handle_t *ctxt, apr_pool_t *pool)
509{
510    apr_pool_t *tmp;
511    if (pool)
512        tmp = pool;
513    else
514        tmp = ctxt->c->pool;
515
516    /* c->client_addr->port and c->client_ip actually contain
517     * information on the remote server for outgoing proxy
518     * connections, prefer SNI hostname over IP.
519     *
520     * The server_hostname is used to tie the cache entry to a
521     * specific vhost, because different vhosts may have different
522     * settings for the same backend server.
523     */
524    const char *peer_hostname = get_proxy_sni_name(ctxt);
525    gnutls_datum_t key;
526    key.data = (unsigned char *)
527        apr_psprintf(tmp, "proxy:%s:%s:%d",
528                     ctxt->c->base_server->server_hostname,
529                     peer_hostname ? peer_hostname : ctxt->c->client_ip,
530                     ctxt->c->client_addr->port);
531    key.size = strlen((const char*) key.data);
532    return key;
533}
534
535
536
537void mgs_set_proxy_handshake_ext(mgs_handle_t *ctxt)
538{
539    proxy_conn_set_sni(ctxt);
540    proxy_conn_set_alpn(ctxt);
541    proxy_conn_load_session(ctxt);
542}
Note: See TracBrowser for help on using the repository browser.