source: mod_gnutls/src/gnutls_proxy.c @ e6d9e47

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

Move the callback for incoming tickets into gnutls_proxy.c

It's only used for proxy connection, so that's the appropriate
location.

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