source: mod_gnutls/src/gnutls_proxy.c @ e151b6f

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

Do not try to cache or load cached proxy sessions without a session cache

  • Property mode set to 100644
File size: 17.0 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    // TODO: delete the cache entry
430
431    int ret = gnutls_session_set_data(ctxt->session, data.data, data.size);
432    if (ret == GNUTLS_E_SUCCESS)
433        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, ctxt->c,
434                      "%s: Cached session loaded.", __func__);
435    else
436        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EGENERAL, ctxt->c,
437                      "%s: Loading cached session failed: %s (%d)",
438                      __func__, gnutls_strerror(ret), ret);
439    gnutls_free(data.data);
440}
441
442
443
444gnutls_datum_t mgs_proxy_ticket_id(mgs_handle_t *ctxt, apr_pool_t *pool)
445{
446    apr_pool_t *tmp;
447    if (pool)
448        tmp = pool;
449    else
450        tmp = ctxt->c->pool;
451
452    /* c->client_addr->port and c->client_ip actually contain
453     * information on the remote server for outgoing proxy
454     * connections, prefer SNI hostname over IP.
455     *
456     * The server_hostname is used to tie the cache entry to a
457     * specific vhost, because different vhosts may have different
458     * settings for the same backend server.
459     */
460    const char *peer_hostname = get_proxy_sni_name(ctxt);
461    gnutls_datum_t key;
462    key.data = (unsigned char *)
463        apr_psprintf(tmp, "proxy:%s:%s:%d",
464                     ctxt->c->base_server->server_hostname,
465                     peer_hostname ? peer_hostname : ctxt->c->client_ip,
466                     ctxt->c->client_addr->port);
467    key.size = strlen((const char*) key.data);
468    return key;
469}
470
471
472
473void mgs_set_proxy_handshake_ext(mgs_handle_t *ctxt)
474{
475    proxy_conn_set_sni(ctxt);
476    proxy_conn_set_alpn(ctxt);
477    proxy_conn_load_session(ctxt);
478}
Note: See TracBrowser for help on using the repository browser.