source: mod_gnutls/src/gnutls_proxy.c

asyncio
Last change on this file was d8c4d75, checked in by Fiona Klute <fiona.klute@…>, 5 months ago

Remove redundant initializations

Found by CppCheck?.

  • Property mode set to 100644
File size: 19.4 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    /* Cleanup function for the GnuTLS structures allocated below */
136    apr_pool_cleanup_register(pconf, sc, cleanup_proxy_x509_credentials,
137                              apr_pool_cleanup_null);
138
139    /* Function pool, gets destroyed before exit. */
140    apr_pool_t *pool;
141    apr_status_t ret = apr_pool_create(&pool, ptemp);
142    if (ret != APR_SUCCESS)
143    {
144        ap_log_error(APLOG_MARK, APLOG_ERR, ret, s,
145                     "%s: failed to allocate function memory pool.", __func__);
146        return ret;
147    }
148
149    /* allocate credentials structures */
150    int err = gnutls_certificate_allocate_credentials(&sc->proxy_x509_creds);
151    if (err != GNUTLS_E_SUCCESS)
152    {
153        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
154                     "%s: Failed to initialize proxy credentials: (%d) %s",
155                     __func__, err, gnutls_strerror(err));
156        return APR_EGENERAL;
157    }
158    err = gnutls_anon_allocate_client_credentials(&sc->anon_client_creds);
159    if (err != GNUTLS_E_SUCCESS)
160    {
161        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
162                     "%s: Failed to initialize anon credentials for proxy: "
163                     "(%d) %s", __func__, err, gnutls_strerror(err));
164        return APR_EGENERAL;
165    }
166
167    /* Check if the proxy priorities have been set, fail immediately
168     * if not */
169    if (sc->proxy_priorities_str == NULL)
170    {
171        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
172                     "No GnuTLSProxyPriorities directive for host '%s:%d', "
173                     "using default '" MGS_DEFAULT_PRIORITY "'.",
174                     s->server_hostname, s->addrs->host_port);
175        sc->proxy_priorities = mgs_get_default_prio();
176    }
177    else
178    {
179        /* parse proxy priorities */
180        const char *err_pos = NULL;
181        err = gnutls_priority_init(&sc->proxy_priorities,
182                                   sc->proxy_priorities_str, &err_pos);
183        if (err != GNUTLS_E_SUCCESS)
184        {
185            if (ret == GNUTLS_E_INVALID_REQUEST)
186                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
187                             "%s: Syntax error parsing proxy priorities "
188                             "string at: %s",
189                             __func__, err_pos);
190            else
191                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
192                             "Error setting proxy priorities: %s (%d)",
193                             gnutls_strerror(err), err);
194            ret = APR_EGENERAL;
195        }
196    }
197
198    /* load certificate and key for client auth, if configured */
199    if (sc->proxy_x509_key_file && sc->proxy_x509_cert_file)
200    {
201        char* cert_file = ap_server_root_relative(pool,
202                                                  sc->proxy_x509_cert_file);
203        char* key_file = ap_server_root_relative(pool,
204                                                 sc->proxy_x509_key_file);
205        err = gnutls_certificate_set_x509_key_file(sc->proxy_x509_creds,
206                                                   cert_file,
207                                                   key_file,
208                                                   GNUTLS_X509_FMT_PEM);
209        if (err != GNUTLS_E_SUCCESS)
210        {
211            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
212                         "%s: loading proxy client credentials failed: %s (%d)",
213                         __func__, gnutls_strerror(err), err);
214            ret = APR_EGENERAL;
215        }
216    }
217    else if (!sc->proxy_x509_key_file && sc->proxy_x509_cert_file)
218    {
219        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
220                     "%s: proxy key file not set!", __func__);
221        ret = APR_EGENERAL;
222    }
223    else if (!sc->proxy_x509_cert_file && sc->proxy_x509_key_file)
224    {
225        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
226                     "%s: proxy certificate file not set!", __func__);
227        ret = APR_EGENERAL;
228    }
229    else
230        /* if both key and cert are NULL, client auth is not used */
231        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
232                     "%s: no client credentials for proxy", __func__);
233
234    /* must be set if the server certificate is to be checked */
235    if (sc->proxy_x509_ca_file)
236    {
237        /* initialize the trust list */
238        err = gnutls_x509_trust_list_init(&sc->proxy_x509_tl, 0);
239        if (err != GNUTLS_E_SUCCESS)
240        {
241            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
242                         "%s: gnutls_x509_trust_list_init failed: %s (%d)",
243                         __func__, gnutls_strerror(err), err);
244            ret = APR_EGENERAL;
245        }
246
247        char* ca_file = ap_server_root_relative(pool,
248                                                sc->proxy_x509_ca_file);
249        /* if no CRL is used, sc->proxy_x509_crl_file is NULL */
250        char* crl_file = NULL;
251        if (sc->proxy_x509_crl_file)
252            crl_file = ap_server_root_relative(pool,
253                                               sc->proxy_x509_crl_file);
254
255        /* returns number of loaded elements */
256        err = gnutls_x509_trust_list_add_trust_file(sc->proxy_x509_tl,
257                                                    ca_file,
258                                                    crl_file,
259                                                    GNUTLS_X509_FMT_PEM,
260                                                    0 /* tl_flags */,
261                                                    0 /* tl_vflags */);
262        if (err > 0)
263            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
264                         "%s: proxy CA trust list: %d structures loaded",
265                         __func__, err);
266        else if (err == 0)
267            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
268                         "%s: proxy CA trust list is empty (%d)",
269                         __func__, err);
270        else /* err < 0 */
271        {
272            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
273                         "%s: error loading proxy CA trust list: %s (%d)",
274                         __func__, gnutls_strerror(err), err);
275            ret = APR_EGENERAL;
276        }
277
278        /* attach trust list to credentials */
279        gnutls_certificate_set_trust_list(sc->proxy_x509_creds,
280                                          sc->proxy_x509_tl, 0);
281    }
282    else
283        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
284                     "%s: no CA trust list for proxy connections, "
285                     "TLS connections will fail!", __func__);
286
287    gnutls_certificate_set_verify_function(sc->proxy_x509_creds,
288                                           gtls_check_server_cert);
289    apr_pool_destroy(pool);
290    return ret;
291}
292
293
294
295int mgs_proxy_got_ticket_func(gnutls_session_t session,
296                              unsigned int htype,
297                              unsigned when,
298                              unsigned int incoming __attribute__((unused)),
299                              const gnutls_datum_t *msg __attribute__((unused)))
300{
301    /* Ignore all unexpected messages */
302    if (htype != GNUTLS_HANDSHAKE_NEW_SESSION_TICKET
303        || when != GNUTLS_HOOK_POST)
304        return GNUTLS_E_SUCCESS;
305
306    mgs_handle_t *ctxt = gnutls_session_get_ptr(session);
307
308    /* No cache means we cannot cache tickets. */
309    if (!ctxt->sc->cache_enable)
310        return GNUTLS_E_SUCCESS;
311
312    if (gnutls_protocol_get_version(ctxt->session) != GNUTLS_TLS1_3)
313    {
314        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
315                      "%s: session tickets for proxy connections are used "
316                      "only with TLS 1.3.", __func__);
317        return GNUTLS_E_SUCCESS;
318    }
319
320    if (!(gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET))
321    {
322        ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_SUCCESS, ctxt->c,
323                      "%s called but session has no ticket!",
324                      __func__);
325        /* Tickets are optional, so don't break the session on
326         * errors. */
327        return GNUTLS_E_SUCCESS;
328    }
329
330    gnutls_datum_t ticket;
331    int ret = gnutls_session_get_data2(session, &ticket);
332    if (ret != GNUTLS_E_SUCCESS)
333    {
334        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
335                      "%s: error reading session ticket: %s (%d)",
336                      __func__, gnutls_strerror(ret), ret);
337        if (ticket.data)
338            gnutls_free(ticket.data);
339        return GNUTLS_E_SUCCESS;
340    }
341
342    apr_time_t expiry = apr_time_now() + ctxt->sc->cache_timeout;
343    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
344                  "%s: caching session ticket for %s (%u bytes)",
345                  __func__, ctxt->proxy_ticket_key.data, ticket.size);
346    mgs_cache_store(ctxt->sc->cache, ctxt->c->base_server,
347                    ctxt->proxy_ticket_key, ticket, expiry);
348    gnutls_free(ticket.data);
349    return GNUTLS_E_SUCCESS;
350}
351
352
353
354/**
355 * Returns either a valid hostname for use with SNI, or NULL.
356 */
357static const char *get_proxy_sni_name(mgs_handle_t *ctxt)
358{
359    /* Get peer hostname from note left by mod_proxy */
360    const char *peer_hostname =
361        apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
362
363    /* Used only as target for apr_ipsubnet_create() */
364    apr_ipsubnet_t *probe;
365    /* If the note is present (!= NULL) check that the value is NOT an
366     * IP address, which wouldn't be valid for SNI. */
367    if ((peer_hostname != NULL)
368        && (apr_ipsubnet_create(&probe, peer_hostname, NULL, ctxt->c->pool)
369            == APR_SUCCESS))
370        return NULL;
371
372    return peer_hostname;
373}
374
375
376
377static void proxy_conn_set_sni(mgs_handle_t *ctxt)
378{
379    const char *peer_hostname = get_proxy_sni_name(ctxt);
380    if (peer_hostname != NULL)
381    {
382        int ret = gnutls_server_name_set(ctxt->session, GNUTLS_NAME_DNS,
383                                         peer_hostname, strlen(peer_hostname));
384        if (ret != GNUTLS_E_SUCCESS)
385            ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
386                          "Could not set SNI '%s' for proxy connection: "
387                          "%s (%d)",
388                          peer_hostname, gnutls_strerror(ret), ret);
389    }
390}
391
392
393
394/** Initial size for the APR array storing ALPN protocol
395 * names. Currently only mod_proxy_http2 uses ALPN for proxy
396 * connections and proposes "h2" exclusively. This provides enough
397 * room without additional allocation even if an HTTP/1.1 fallback
398 * should be added while still being small. */
399#define INIT_ALPN_ARR_SIZE 2
400
401/**
402 * Set ALPN proposals for a proxy handshake based on the note from the
403 * proxy module (see `PROXY_SNI_NOTE`). The note is expected to
404 * contain a string, multiple protocol names can be separated by ","
405 * or " ", or a combination of them.
406 *
407 * @param ctxt the mod_gnutls connection handle
408 */
409static void proxy_conn_set_alpn(mgs_handle_t *ctxt)
410{
411    const char *proxy_alpn =
412        apr_table_get(ctxt->c->notes, PROXY_ALPN_NOTE);
413    if (proxy_alpn == NULL)
414        return;
415    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
416                  "%s: proxy module ALPN note is '%s', "
417                  "length %" APR_SIZE_T_FMT ".",
418                  __func__, proxy_alpn, strlen(proxy_alpn));
419
420    apr_array_header_t* protocols =
421        apr_array_make(ctxt->c->pool, INIT_ALPN_ARR_SIZE,
422                       sizeof(const char *));
423
424    /* mod_ssl tokenizes the note by "," or " " to allow multiple
425     * protocols. We need to copy the note because apr_strtok()
426     * modifies the string to make each token NULL terminated. On the
427     * plus side that means we do not need to copy individual
428     * tokens. */
429    char *tok = apr_pstrdup(ctxt->c->pool, proxy_alpn);
430    /* state for apr_strtok, pointer to character following current
431     * token */
432    char *last = NULL;
433    while ((tok = apr_strtok(tok, ", ", &last)))
434    {
435        APR_ARRAY_PUSH(protocols, const char *) = tok;
436        tok = NULL;
437    }
438
439    gnutls_datum_t* alpn_protos =
440        mgs_str_array_to_datum_array(protocols,
441                                     ctxt->c->pool,
442                                     protocols->nelts);
443    int ret = gnutls_alpn_set_protocols(ctxt->session,
444                                        alpn_protos,
445                                        protocols->nelts,
446                                        0 /* flags */);
447    if (ret != GNUTLS_E_SUCCESS)
448        ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
449                      "Could not set ALPN proposals for proxy "
450                      "connection: %s (%d)",
451                      gnutls_strerror(ret), ret);
452}
453
454
455
456/**
457 * Check if there is a cached session for the connection, and load it
458 * if yes. The session is deleted from the cache after that, because
459 * tickets should not be reused for forward secrecy.
460 *
461 * @param ctxt the mod_gnutls connection handle
462 */
463static void proxy_conn_load_session(mgs_handle_t *ctxt)
464{
465    /* No cache means there cannot be a cached ticket. */
466    if (!ctxt->sc->cache_enable)
467        return;
468
469    gnutls_datum_t data = {NULL, 0};
470    data.data = gnutls_malloc(MGS_SESSION_FETCH_BUF_SIZE);
471    if (data.data == NULL)
472        return;
473    data.size = MGS_SESSION_FETCH_BUF_SIZE;
474
475    apr_status_t rv = mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
476                                      ctxt->proxy_ticket_key, &data,
477                                      ctxt->c->pool);
478    if (rv != APR_SUCCESS)
479    {
480        gnutls_free(data.data);
481        return;
482    }
483
484    /* Best effort attempt to avoid ticket reuse. Unfortunately
485     * another thread or process could update (or remove) the cache in
486     * between, but that can't be avoided without forcing use of a
487     * global mutex even with a multiprocess-safe socache provider. */
488    mgs_cache_delete(ctxt->sc->cache, ctxt->c->base_server,
489                     ctxt->proxy_ticket_key, ctxt->c->pool);
490
491    int ret = gnutls_session_set_data(ctxt->session, data.data, data.size);
492    if (ret == GNUTLS_E_SUCCESS)
493        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
494                      "%s: Cached session loaded.", __func__);
495    else
496        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
497                      "%s: Loading cached session failed: %s (%d)",
498                      __func__, gnutls_strerror(ret), ret);
499    gnutls_free(data.data);
500}
501
502
503
504gnutls_datum_t mgs_proxy_ticket_id(mgs_handle_t *ctxt, apr_pool_t *pool)
505{
506    apr_pool_t *tmp;
507    if (pool)
508        tmp = pool;
509    else
510        tmp = ctxt->c->pool;
511
512    /* c->client_addr->port and c->client_ip actually contain
513     * information on the remote server for outgoing proxy
514     * connections, prefer SNI hostname over IP.
515     *
516     * The server_hostname is used to tie the cache entry to a
517     * specific vhost, because different vhosts may have different
518     * settings for the same backend server.
519     */
520    const char *peer_hostname = get_proxy_sni_name(ctxt);
521    gnutls_datum_t key;
522    key.data = (unsigned char *)
523        apr_psprintf(tmp, "proxy:%s:%s:%d",
524                     ctxt->c->base_server->server_hostname,
525                     peer_hostname ? peer_hostname : ctxt->c->client_ip,
526                     ctxt->c->client_addr->port);
527    key.size = strlen((const char*) key.data);
528    return key;
529}
530
531
532
533void mgs_set_proxy_handshake_ext(mgs_handle_t *ctxt)
534{
535    proxy_conn_set_sni(ctxt);
536    proxy_conn_set_alpn(ctxt);
537    proxy_conn_load_session(ctxt);
538}
Note: See TracBrowser for help on using the repository browser.