source: mod_gnutls/src/gnutls_proxy.c @ d827d0c

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

Create cache keys for proxy session tickets

The key is based on the vhost name, backend server hostname (from SNI)
or IP, and its port. The vhost name is included because different
vhosts may have different settings for the same backend server. Post
handshake auth is not supported for proxy connections, so we do not
need to consider auth IDs.

  • Property mode set to 100644
File size: 15.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_proxy.h"
19#include "gnutls_util.h"
20
21#include <apr_strings.h>
22#include <gnutls/gnutls.h>
23
24/*
25 * Callback to check the server certificate for proxy HTTPS
26 * connections, to be used with
27 * gnutls_certificate_set_verify_function.
28
29 * Returns: 0 if certificate check was successful (certificate
30 * trusted), non-zero otherwise (error during check or untrusted
31 * certificate).
32 */
33static int gtls_check_server_cert(gnutls_session_t session)
34{
35    mgs_handle_t *ctxt = (mgs_handle_t *) gnutls_session_get_ptr(session);
36    unsigned int status;
37
38    /* Get peer hostname from a note left by mod_proxy */
39    const char *peer_hostname =
40        apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
41    if (peer_hostname == NULL)
42        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
43                      "%s: " PROXY_SNI_NOTE " NULL, cannot check "
44                      "peer's hostname", __func__);
45
46    /* Verify certificate, including hostname match. Should
47     * peer_hostname be NULL for some reason, the name is not
48     * checked. */
49    int err = gnutls_certificate_verify_peers3(session, peer_hostname,
50                                               &status);
51    if (err != GNUTLS_E_SUCCESS)
52    {
53        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, ctxt->c,
54                      "%s: server certificate check failed: %s (%d)",
55                      __func__, gnutls_strerror(err), err);
56        return err;
57    }
58
59    if (status == 0)
60        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctxt->c,
61                      "%s: server certificate is trusted.",
62                      __func__);
63    else
64    {
65        gnutls_datum_t out;
66        /* GNUTLS_CRT_X509: ATM, only X509 is supported for proxy
67         * certs 0: according to function API, the last argument
68         * should be 0 */
69        err = gnutls_certificate_verification_status_print(status,
70                                                           GNUTLS_CRT_X509,
71                                                           &out, 0);
72        if (err != GNUTLS_E_SUCCESS)
73            ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctxt->c,
74                          "%s: server verify print failed: %s (%d)",
75                          __func__, gnutls_strerror(err), err);
76        else
77            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, ctxt->c,
78                          "%s: %s",
79                          __func__, out.data);
80        gnutls_free(out.data);
81    }
82
83    return status;
84}
85
86
87
88static apr_status_t cleanup_proxy_x509_credentials(void *arg)
89{
90    mgs_srvconf_rec *sc = (mgs_srvconf_rec *) arg;
91
92    if (sc->proxy_x509_creds)
93    {
94        /* This implicitly releases the associated trust list
95         * sc->proxy_x509_tl, too. */
96        gnutls_certificate_free_credentials(sc->proxy_x509_creds);
97        sc->proxy_x509_creds = NULL;
98        sc->proxy_x509_tl = NULL;
99    }
100
101    if (sc->anon_client_creds)
102    {
103        gnutls_anon_free_client_credentials(sc->anon_client_creds);
104        sc->anon_client_creds = NULL;
105    }
106
107    /* Deinit proxy priorities only if set from
108     * sc->proxy_priorities_str. Otherwise the server is using the
109     * default global priority cache, which must not be deinitialized
110     * here. */
111    if (sc->proxy_priorities_str && sc->proxy_priorities)
112    {
113        gnutls_priority_deinit(sc->proxy_priorities);
114        sc->proxy_priorities = NULL;
115    }
116
117    return APR_SUCCESS;
118}
119
120
121
122apr_status_t load_proxy_x509_credentials(apr_pool_t *pconf,
123                                         apr_pool_t *ptemp,
124                                         server_rec *s)
125{
126    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
127        ap_get_module_config(s->module_config, &gnutls_module);
128
129    if (sc == NULL)
130        return APR_EGENERAL;
131
132    apr_status_t ret = APR_EINIT;
133    int err = GNUTLS_E_SUCCESS;
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    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    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 '%s'.",
174                     s->server_hostname, s->addrs->host_port,
175                     MGS_DEFAULT_PRIORITY);
176        sc->proxy_priorities = mgs_get_default_prio();
177    }
178    else
179    {
180        /* parse proxy priorities */
181        const char *err_pos = NULL;
182        err = gnutls_priority_init(&sc->proxy_priorities,
183                                   sc->proxy_priorities_str, &err_pos);
184        if (err != GNUTLS_E_SUCCESS)
185        {
186            if (ret == GNUTLS_E_INVALID_REQUEST)
187                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
188                             "%s: Syntax error parsing proxy priorities "
189                             "string at: %s",
190                             __func__, err_pos);
191            else
192                ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
193                             "Error setting proxy priorities: %s (%d)",
194                             gnutls_strerror(err), err);
195            ret = APR_EGENERAL;
196        }
197    }
198
199    /* load certificate and key for client auth, if configured */
200    if (sc->proxy_x509_key_file && sc->proxy_x509_cert_file)
201    {
202        char* cert_file = ap_server_root_relative(pool,
203                                                  sc->proxy_x509_cert_file);
204        char* key_file = ap_server_root_relative(pool,
205                                                 sc->proxy_x509_key_file);
206        err = gnutls_certificate_set_x509_key_file(sc->proxy_x509_creds,
207                                                   cert_file,
208                                                   key_file,
209                                                   GNUTLS_X509_FMT_PEM);
210        if (err != GNUTLS_E_SUCCESS)
211        {
212            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
213                         "%s: loading proxy client credentials failed: %s (%d)",
214                         __func__, gnutls_strerror(err), err);
215            ret = APR_EGENERAL;
216        }
217    }
218    else if (!sc->proxy_x509_key_file && sc->proxy_x509_cert_file)
219    {
220        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
221                     "%s: proxy key file not set!", __func__);
222        ret = APR_EGENERAL;
223    }
224    else if (!sc->proxy_x509_cert_file && sc->proxy_x509_key_file)
225    {
226        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
227                     "%s: proxy certificate file not set!", __func__);
228        ret = APR_EGENERAL;
229    }
230    else
231        /* if both key and cert are NULL, client auth is not used */
232        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
233                     "%s: no client credentials for proxy", __func__);
234
235    /* must be set if the server certificate is to be checked */
236    if (sc->proxy_x509_ca_file)
237    {
238        /* initialize the trust list */
239        err = gnutls_x509_trust_list_init(&sc->proxy_x509_tl, 0);
240        if (err != GNUTLS_E_SUCCESS)
241        {
242            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
243                         "%s: gnutls_x509_trust_list_init failed: %s (%d)",
244                         __func__, gnutls_strerror(err), err);
245            ret = APR_EGENERAL;
246        }
247
248        char* ca_file = ap_server_root_relative(pool,
249                                                sc->proxy_x509_ca_file);
250        /* if no CRL is used, sc->proxy_x509_crl_file is NULL */
251        char* crl_file = NULL;
252        if (sc->proxy_x509_crl_file)
253            crl_file = ap_server_root_relative(pool,
254                                               sc->proxy_x509_crl_file);
255
256        /* returns number of loaded elements */
257        err = gnutls_x509_trust_list_add_trust_file(sc->proxy_x509_tl,
258                                                    ca_file,
259                                                    crl_file,
260                                                    GNUTLS_X509_FMT_PEM,
261                                                    0 /* tl_flags */,
262                                                    0 /* tl_vflags */);
263        if (err > 0)
264            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
265                         "%s: proxy CA trust list: %d structures loaded",
266                         __func__, err);
267        else if (err == 0)
268            ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
269                         "%s: proxy CA trust list is empty (%d)",
270                         __func__, err);
271        else /* err < 0 */
272        {
273            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
274                         "%s: error loading proxy CA trust list: %s (%d)",
275                         __func__, gnutls_strerror(err), err);
276            ret = APR_EGENERAL;
277        }
278
279        /* attach trust list to credentials */
280        gnutls_certificate_set_trust_list(sc->proxy_x509_creds,
281                                          sc->proxy_x509_tl, 0);
282    }
283    else
284        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
285                     "%s: no CA trust list for proxy connections, "
286                     "TLS connections will fail!", __func__);
287
288    gnutls_certificate_set_verify_function(sc->proxy_x509_creds,
289                                           gtls_check_server_cert);
290    apr_pool_destroy(pool);
291    return ret;
292}
293
294
295
296/**
297 * Returns either a valid hostname for use with SNI, or NULL.
298 */
299static const char *get_proxy_sni_name(mgs_handle_t *ctxt)
300{
301    /* Get peer hostname from note left by mod_proxy */
302    const char *peer_hostname =
303        apr_table_get(ctxt->c->notes, PROXY_SNI_NOTE);
304
305    /* Used only as target for apr_ipsubnet_create() */
306    apr_ipsubnet_t *probe;
307    /* If the note is present (!= NULL) check that the value is NOT an
308     * IP address, which wouldn't be valid for SNI. */
309    if ((peer_hostname != NULL)
310        && (apr_ipsubnet_create(&probe, peer_hostname, NULL, ctxt->c->pool)
311            == APR_SUCCESS))
312        return NULL;
313
314    return peer_hostname;
315}
316
317
318
319static void proxy_conn_set_sni(mgs_handle_t *ctxt)
320{
321    const char *peer_hostname = get_proxy_sni_name(ctxt);
322    if (peer_hostname != NULL)
323    {
324        int ret = gnutls_server_name_set(ctxt->session, GNUTLS_NAME_DNS,
325                                         peer_hostname, strlen(peer_hostname));
326        if (ret != GNUTLS_E_SUCCESS)
327            ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
328                          "Could not set SNI '%s' for proxy connection: "
329                          "%s (%d)",
330                          peer_hostname, gnutls_strerror(ret), ret);
331    }
332}
333
334
335
336/** Initial size for the APR array storing ALPN protocol
337 * names. Currently only mod_proxy_http2 uses ALPN for proxy
338 * connections and proposes "h2" exclusively. This provides enough
339 * room without additional allocation even if an HTTP/1.1 fallback
340 * should be added while still being small. */
341#define INIT_ALPN_ARR_SIZE 2
342
343/**
344 * Set ALPN proposals for a proxy handshake based on the note from the
345 * proxy module (see `PROXY_SNI_NOTE`). The note is expected to
346 * contain a string, multiple protocol names can be separated by ","
347 * or " ", or a combination of them.
348 *
349 * @param ctxt the mod_gnutls connection handle
350 */
351static void proxy_conn_set_alpn(mgs_handle_t *ctxt)
352{
353    const char *proxy_alpn =
354        apr_table_get(ctxt->c->notes, PROXY_ALPN_NOTE);
355    if (proxy_alpn == NULL)
356        return;
357    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, APR_SUCCESS, ctxt->c,
358                  "%s: proxy module ALPN note is '%s', "
359                  "length %" APR_SIZE_T_FMT ".",
360                  __func__, proxy_alpn, strlen(proxy_alpn));
361
362    apr_array_header_t* protocols =
363        apr_array_make(ctxt->c->pool, INIT_ALPN_ARR_SIZE,
364                       sizeof(const char *));
365
366    /* mod_ssl tokenizes the note by "," or " " to allow multiple
367     * protocols. We need to copy the note because apr_strtok()
368     * modifies the string to make each token NULL terminated. On the
369     * plus side that means we do not need to copy individual
370     * tokens. */
371    char *tok = apr_pstrdup(ctxt->c->pool, proxy_alpn);
372    /* state for apr_strtok, pointer to character following current
373     * token */
374    char *last = NULL;
375    while ((tok = apr_strtok(tok, ", ", &last)))
376    {
377        APR_ARRAY_PUSH(protocols, const char *) = tok;
378        tok = NULL;
379    }
380
381    gnutls_datum_t* alpn_protos =
382        mgs_str_array_to_datum_array(protocols,
383                                     ctxt->c->pool,
384                                     protocols->nelts);
385    int ret = gnutls_alpn_set_protocols(ctxt->session,
386                                        alpn_protos,
387                                        protocols->nelts,
388                                        0 /* flags */);
389    if (ret != GNUTLS_E_SUCCESS)
390        ap_log_cerror(APLOG_MARK, APLOG_ERR, ret, ctxt->c,
391                      "Could not set ALPN proposals for proxy "
392                      "connection: %s (%d)",
393                      gnutls_strerror(ret), ret);
394}
395
396
397
398char *mgs_proxy_ticket_id(mgs_handle_t *ctxt, apr_pool_t *pool)
399{
400    apr_pool_t *tmp;
401    if (pool)
402        tmp = pool;
403    else
404        tmp = ctxt->c->pool;
405
406    /* c->client_addr->port and c->client_ip actually contain
407     * information on the remote server for outgoing proxy
408     * connections, prefer SNI hostname over IP.
409     *
410     * The server_hostname is used to tie the cache entry to a
411     * specific vhost, because different vhosts may have different
412     * settings for the same backend server.
413     */
414    const char *peer_hostname = get_proxy_sni_name(ctxt);
415    return apr_psprintf(
416        tmp, "proxy:%s:%s:%d",
417        ctxt->c->base_server->server_hostname,
418        peer_hostname ? peer_hostname : ctxt->c->client_ip,
419        ctxt->c->client_addr->port);
420}
421
422
423
424void mgs_set_proxy_handshake_ext(mgs_handle_t *ctxt)
425{
426    proxy_conn_set_sni(ctxt);
427    proxy_conn_set_alpn(ctxt);
428}
Note: See TracBrowser for help on using the repository browser.