source: mod_gnutls/src/gnutls_cache.c @ 86cd5f6

mod_gnutls/0.11.0
Last change on this file since 86cd5f6 was 641d11b, checked in by Fiona Klute <fiona.klute@…>, 5 months ago

Delete session tickets for proxy connections when using them

Tickets should not be reused because an attacker could correlate
connections using the same ticket. Cache deletion code has been
extracted from socache_delete_session() into a generic function.

  • Property mode set to 100644
File size: 18.0 KB
Line 
1/*
2 *  Copyright 2004-2005 Paul Querna
3 *  Copyright 2008 Nikos Mavrogiannopoulos
4 *  Copyright 2011 Dash Shendy
5 *  Copyright 2015-2020 Fiona Klute
6 *
7 *  Licensed under the Apache License, Version 2.0 (the "License");
8 *  you may not use this file except in compliance with the License.
9 *  You may obtain a copy of the License at
10 *
11 *      http://www.apache.org/licenses/LICENSE-2.0
12 *
13 *  Unless required by applicable law or agreed to in writing, software
14 *  distributed under the License is distributed on an "AS IS" BASIS,
15 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 *  See the License for the specific language governing permissions and
17 *  limitations under the License.
18 */
19
20/**
21 * @file gnutls_cache.c
22 *
23 * This file contains the cache implementation used for session
24 * caching and OCSP stapling. The `socache_*_session` functions
25 * implement the GnuTLS session cache API using the configured cache,
26 * using mgs_cache_store() and mgs_cache_fetch() as appropriate (see
27 * gnutls_cache.h).
28 */
29
30#include "gnutls_cache.h"
31#include "mod_gnutls.h"
32#include "gnutls_config.h"
33#include "gnutls_ocsp.h"
34
35#include <ap_socache.h>
36#include <apr_strings.h>
37#include <mod_status.h>
38#include <apr_escape.h>
39#include <util_mutex.h>
40
41/** Default session cache timeout */
42#define MGS_DEFAULT_CACHE_TIMEOUT 300
43
44/** Session cache name */
45#define MGS_SESSION_CACHE_NAME "gnutls_session"
46
47/** Default type for OCSP cache */
48#define DEFAULT_OCSP_CACHE_TYPE "shmcb"
49/** Default config string for OCSP cache */
50#define DEFAULT_OCSP_CACHE_CONF "gnutls_ocsp_cache"
51
52/** Maximum length of the hex string representation of a GnuTLS
53 * session ID: two characters per byte, plus one more for `\0` */
54#define GNUTLS_SESSION_ID_STRING_LEN ((GNUTLS_MAX_SESSION_ID_SIZE * 2) + 1)
55
56#ifdef APLOG_USE_MODULE
57APLOG_USE_MODULE(gnutls);
58#endif
59
60/**
61 * Turn a GnuTLS session ID into the key format we use for
62 * caches. Name the Session ID as `server:port.SessionID` to disallow
63 * resuming sessions on different servers.
64 *
65 * @return `0` on success, `-1` on failure
66 */
67static int mgs_session_id2dbm(conn_rec *c, unsigned char *id, int idlen,
68                              gnutls_datum_t *dbmkey)
69{
70    char sz[GNUTLS_SESSION_ID_STRING_LEN];
71    apr_status_t rv = apr_escape_hex(sz, id, idlen, 0, NULL);
72    if (rv != APR_SUCCESS)
73        return -1;
74
75    char *newkey = apr_psprintf(c->pool, "%s:%d.%s",
76                                c->base_server->server_hostname,
77                                c->base_server->port, sz);
78    dbmkey->size = strlen(newkey);
79    /* signedness does not matter for arbitrary bits */
80    dbmkey->data = (unsigned char*) newkey;
81    return 0;
82}
83
84/** The OPENSSL_TIME_FORMAT macro and mgs_time2sz() serve to print
85 * time in a format compatible with OpenSSL's `ASN1_TIME_print()`
86 * function. */
87#define OPENSSL_TIME_FORMAT "%b %d %k:%M:%S %Y %Z"
88
89char *mgs_time2sz(time_t in_time, char *str, int strsize)
90{
91    apr_time_exp_t vtm;
92    apr_size_t ret_size;
93    apr_time_t t;
94
95
96    apr_time_ansi_put(&t, in_time);
97    apr_time_exp_gmt(&vtm, t);
98    apr_strftime(str, &ret_size, strsize - 1, OPENSSL_TIME_FORMAT, &vtm);
99
100    return str;
101}
102
103
104
105int mgs_cache_store(mgs_cache_t cache, server_rec *server,
106                    gnutls_datum_t key, gnutls_datum_t data,
107                    apr_time_t expiry)
108{
109    apr_pool_t *spool;
110    apr_pool_create(&spool, NULL);
111
112    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
113        apr_global_mutex_lock(cache->mutex);
114    apr_status_t rv = cache->prov->store(cache->socache, server,
115                                         key.data, key.size,
116                                         expiry,
117                                         data.data, data.size,
118                                         spool);
119    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
120        apr_global_mutex_unlock(cache->mutex);
121
122    if (rv != APR_SUCCESS)
123    {
124        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
125                     "error storing in cache '%s:%s'",
126                     cache->prov->name, cache->config);
127        apr_pool_destroy(spool);
128        return -1;
129    }
130
131    ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
132                 "stored %u bytes of data (%u byte key) in cache '%s:%s'",
133                 data.size, key.size,
134                 cache->prov->name, cache->config);
135    apr_pool_destroy(spool);
136    return 0;
137}
138
139
140
141/**
142 * Store function for the GnuTLS session cache, see
143 * gnutls_db_set_store_function().
144 *
145 * @param baton mgs_handle_t for the connection, as set via
146 * gnutls_db_set_ptr()
147 *
148 * @param key object key to store
149 *
150 * @param data the object to store
151 *
152 * @return `0` in case of success, `-1` in case of failure
153 */
154static int socache_store_session(void *baton, gnutls_datum_t key,
155                                 gnutls_datum_t data)
156{
157    mgs_handle_t *ctxt = baton;
158    gnutls_datum_t dbmkey;
159
160    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
161        return -1;
162
163    apr_time_t expiry = apr_time_now() + ctxt->sc->cache_timeout;
164
165    return mgs_cache_store(ctxt->sc->cache, ctxt->c->base_server,
166                           dbmkey, data, expiry);
167}
168
169
170
171apr_status_t mgs_cache_fetch(mgs_cache_t cache, server_rec *server,
172                             gnutls_datum_t key, gnutls_datum_t *output,
173                             apr_pool_t *pool)
174{
175    apr_pool_t *spool;
176    apr_pool_create(&spool, pool);
177
178    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
179        apr_global_mutex_lock(cache->mutex);
180    apr_status_t rv = cache->prov->retrieve(cache->socache, server,
181                                            key.data, key.size,
182                                            output->data, &output->size,
183                                            spool);
184    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
185        apr_global_mutex_unlock(cache->mutex);
186
187    if (rv != APR_SUCCESS)
188    {
189        /* APR_NOTFOUND means there's no such object. */
190        if (rv == APR_NOTFOUND)
191            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
192                         "requested entry not found in cache '%s:%s'.",
193                         cache->prov->name, cache->config);
194        else
195            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, server,
196                         "error fetching from cache '%s:%s'",
197                         cache->prov->name, cache->config);
198    }
199    else
200    {
201        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
202                     "fetched %u bytes from cache '%s:%s'",
203                     output->size, cache->prov->name, cache->config);
204    }
205    apr_pool_destroy(spool);
206
207    return rv;
208}
209
210
211
212/**
213 * Fetch function for the GnuTLS session cache, see
214 * gnutls_db_set_retrieve_function().
215 *
216 * *Warning*: The `data` element of the returned `gnutls_datum_t` is
217 * allocated using `gnutls_malloc()` for compatibility with the GnuTLS
218 * session caching API, and must be released using `gnutls_free()`.
219 *
220 * @param baton mgs_handle_t for the connection, as set via
221 * gnutls_db_set_ptr()
222 *
223 * @param key object key to fetch
224 *
225 * @return the requested cache entry, or `{NULL, 0}`
226 */
227static gnutls_datum_t socache_fetch_session(void *baton, gnutls_datum_t key)
228{
229    gnutls_datum_t data = {NULL, 0};
230    gnutls_datum_t dbmkey;
231    mgs_handle_t *ctxt = baton;
232
233    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
234        return data;
235
236    data.data = gnutls_malloc(MGS_SESSION_FETCH_BUF_SIZE);
237    if (data.data == NULL)
238        return data;
239    data.size = MGS_SESSION_FETCH_BUF_SIZE;
240
241    apr_status_t rv = mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
242                                      dbmkey, &data, ctxt->c->pool);
243
244    if (rv != APR_SUCCESS)
245    {
246        /* free unused buffer */
247        gnutls_free(data.data);
248        data.data = NULL;
249        data.size = 0;
250    }
251    else
252    {
253        /* Realloc buffer to data.size. Data size must be less than or
254         * equal to the initial buffer size, so this REALLY should not
255         * fail. */
256        data.data = gnutls_realloc(data.data, data.size);
257        if (__builtin_expect(data.data == NULL, 0))
258        {
259            ap_log_cerror(APLOG_MARK, APLOG_CRIT, APR_ENOMEM, ctxt->c,
260                         "%s: Could not realloc fetch buffer to data size!",
261                         __func__);
262            data.size = 0;
263        }
264    }
265
266    return data;
267}
268
269
270
271apr_status_t mgs_cache_delete(mgs_cache_t cache, server_rec *server,
272                              gnutls_datum_t key, apr_pool_t *pool)
273{
274    apr_pool_t *spool;
275    apr_pool_create(&spool, pool);
276
277    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
278        apr_global_mutex_lock(cache->mutex);
279    apr_status_t rv = cache->prov->remove(cache->socache, server,
280                                          key.data, key.size,
281                                          spool);
282    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
283        apr_global_mutex_unlock(cache->mutex);
284
285    if (rv != APR_SUCCESS)
286        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv, server,
287                     "error deleting from cache '%s:%s'",
288                     cache->prov->name, cache->config);
289    else
290        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
291                     "deleted entry from cache '%s:%s'",
292                     cache->prov->name, cache->config);
293    apr_pool_destroy(spool);
294    return rv;
295}
296
297
298
299/**
300 * Remove function for the GnuTLS session cache, see
301 * gnutls_db_set_remove_function().
302 *
303 * @param baton mgs_handle_t for the connection, as set via
304 * gnutls_db_set_ptr()
305 *
306 * @param key object key to remove
307 *
308 * @return `0` in case of success, `-1` in case of failure
309 */
310static int socache_delete_session(void *baton, gnutls_datum_t key)
311{
312    gnutls_datum_t dbmkey;
313    mgs_handle_t *ctxt = baton;
314
315    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
316        return -1;
317
318    apr_status_t rv = mgs_cache_delete(ctxt->sc->cache, ctxt->c->base_server,
319                                       dbmkey, ctxt->c->pool);
320    if (rv != APR_SUCCESS)
321        return -1;
322    else
323        return 0;
324}
325
326
327
328const char *mgs_cache_inst_config(mgs_cache_t *cache, server_rec *server,
329                                  const char* type, const char* config,
330                                  apr_pool_t *pconf, apr_pool_t *ptemp)
331{
332    /* Allocate cache structure, will be assigned to *cache after
333     * successful configuration. */
334    mgs_cache_t c = apr_pcalloc(pconf, sizeof(struct mgs_cache));
335    if (c == NULL)
336        return "Could not allocate memory for cache configuration!";
337
338    /* Find the right socache provider */
339    c->prov = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
340                                 type,
341                                 AP_SOCACHE_PROVIDER_VERSION);
342    if (c->prov == NULL)
343    {
344        return apr_psprintf(ptemp,
345                            "Could not find socache provider '%s', please "
346                            "make sure that the provider name is valid and "
347                            "the appropriate module is loaded (maybe "
348                            "mod_socache_%s.so?).",
349                            type, type);
350    }
351
352    /* shmcb works fine with NULL, but make sure there's a valid (if
353     * empty) string for logging */
354    if (config != NULL)
355        c->config = apr_pstrdup(pconf, config);
356    else
357        c->config = "";
358
359    /* Create and configure the cache instance. */
360    const char *err = c->prov->create(&c->socache, c->config, ptemp, pconf);
361    if (err != NULL)
362    {
363        return apr_psprintf(ptemp,
364                            "Creating cache '%s:%s' failed: %s",
365                            c->prov->name, c->config, err);
366    }
367    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, server,
368                 "%s: Socache '%s:%s' created.",
369                 __func__, c->prov->name, c->config);
370
371    /* assign configured cache structure to server */
372    *cache = c;
373
374    return NULL;
375}
376
377
378
379/**
380 * This function is supposed to be called during post_config to
381 * initialize mutex and socache instance associated with an
382 * mgs_cache_t.
383 *
384 * @param cache the mod_gnutls cache structure
385 *
386 * @param cache_name name for socache initialization
387 *
388 * @param mutex_name name to pass to ap_global_mutex_create(), must
389 * have been registered during pre_config.
390 *
391 * @param server server for logging purposes
392 *
393 * @param pconf memory pool for server configuration
394 */
395static apr_status_t mgs_cache_inst_init(mgs_cache_t cache,
396                                        const char *cache_name,
397                                        const char *mutex_name,
398                                        server_rec *server,
399                                        apr_pool_t *pconf)
400{
401    apr_status_t rv = APR_SUCCESS;
402
403    if (cache->mutex == NULL)
404    {
405        rv = ap_global_mutex_create(&cache->mutex, NULL,
406                                    mutex_name,
407                                    NULL, server, pconf, 0);
408        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
409                     "%s: create mutex", __func__);
410        if (rv != APR_SUCCESS)
411            return rv;
412    }
413
414    rv = cache->prov->init(cache->socache, cache_name, NULL, server, pconf);
415    if (rv != APR_SUCCESS)
416        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, server,
417                     "Initializing cache '%s:%s' failed!",
418                     cache->prov->name, cache->config);
419    else
420        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
421                     "%s: socache '%s:%s' initialized.", __func__,
422                     cache->prov->name, cache->config);
423    return rv;
424}
425
426
427
428static apr_status_t cleanup_socache(void *data)
429{
430    server_rec *s = data;
431    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
432        ap_get_module_config(s->module_config, &gnutls_module);
433    if (sc->cache)
434    {
435        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
436                     "Cleaning up session cache '%s:%s'",
437                     sc->cache->prov->name, sc->cache->config);
438        sc->cache->prov->destroy(sc->cache->socache, s);
439    }
440    if (sc->ocsp_cache)
441    {
442        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
443                     "Cleaning up OCSP cache '%s:%s'",
444                     sc->ocsp_cache->prov->name, sc->ocsp_cache->config);
445        sc->ocsp_cache->prov->destroy(sc->ocsp_cache->socache, s);
446    }
447    return APR_SUCCESS;
448}
449
450
451
452int mgs_cache_post_config(apr_pool_t *pconf, apr_pool_t *ptemp,
453                          server_rec *s, mgs_srvconf_rec *sc)
454{
455    apr_status_t rv = APR_SUCCESS;
456
457    /* If the OCSP cache is unconfigured initialize it with
458     * defaults. */
459    if (sc->ocsp_cache == NULL)
460    {
461        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
462                     "%s: OCSP cache unconfigured, using '%s:%s'.", __func__,
463                     DEFAULT_OCSP_CACHE_TYPE, DEFAULT_OCSP_CACHE_CONF);
464        const char *err = mgs_cache_inst_config(&sc->ocsp_cache, s,
465                                                DEFAULT_OCSP_CACHE_TYPE,
466                                                DEFAULT_OCSP_CACHE_CONF,
467                                                pconf, ptemp);
468        if (err != NULL)
469            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
470                         "%s: Configuring default OCSP cache '%s:%s' failed, "
471                         "make sure that mod_socache_%s is loaded.", __func__,
472                         DEFAULT_OCSP_CACHE_TYPE, DEFAULT_OCSP_CACHE_CONF,
473                         DEFAULT_OCSP_CACHE_TYPE);
474    }
475
476    /* Initialize the OCSP cache first so it's not skipped if the
477     * session cache is disabled. */
478    if (sc->ocsp_cache != NULL)
479    {
480        /* TODO: Maybe initialize only if explicitly enabled OR at
481         * least one (virtual) host has OCSP enabled? */
482        rv = mgs_cache_inst_init(sc->ocsp_cache, MGS_OCSP_CACHE_NAME,
483                                 MGS_OCSP_CACHE_MUTEX_NAME, s, pconf);
484        if (rv != APR_SUCCESS)
485            return HTTP_INSUFFICIENT_STORAGE;
486    }
487
488    /* GnuTLSCache was never explicitly set or is disabled: */
489    if (sc->cache_enable == GNUTLS_ENABLED_UNSET
490        || sc->cache_enable == GNUTLS_ENABLED_FALSE)
491    {
492        sc->cache_enable = GNUTLS_ENABLED_FALSE;
493        /* Cache disabled, done. */
494        return APR_SUCCESS;
495    }
496    /* if GnuTLSCacheTimeout was never explicitly set: */
497    if (sc->cache_timeout == MGS_TIMEOUT_UNSET)
498        sc->cache_timeout = apr_time_from_sec(MGS_DEFAULT_CACHE_TIMEOUT);
499
500    rv = mgs_cache_inst_init(sc->cache, MGS_SESSION_CACHE_NAME,
501                             MGS_CACHE_MUTEX_NAME, s, pconf);
502    if (rv != APR_SUCCESS)
503        return HTTP_INSUFFICIENT_STORAGE;
504
505    apr_pool_pre_cleanup_register(pconf, s, cleanup_socache);
506
507    return APR_SUCCESS;
508}
509
510int mgs_cache_child_init(apr_pool_t *p, server_rec *server,
511                         mgs_cache_t cache, const char *mutex_name)
512{
513    /* reinit cache mutex */
514    const char *lockfile = apr_global_mutex_lockfile(cache->mutex);
515    apr_status_t rv = apr_global_mutex_child_init(&cache->mutex,
516                                                  lockfile, p);
517    if (rv != APR_SUCCESS)
518        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, server,
519                     "Failed to reinit mutex '%s'", mutex_name);
520
521    return rv;
522}
523
524int mgs_cache_session_init(mgs_handle_t * ctxt)
525{
526    if (ctxt->sc->cache_enable)
527    {
528        gnutls_db_set_retrieve_function(ctxt->session,
529                                        socache_fetch_session);
530        gnutls_db_set_remove_function(ctxt->session,
531                                      socache_delete_session);
532        gnutls_db_set_store_function(ctxt->session,
533                                     socache_store_session);
534        gnutls_db_set_ptr(ctxt->session, ctxt);
535    }
536    return 0;
537}
538
539
540
541int mgs_cache_status(mgs_cache_t cache, const char *header_title,
542                     request_rec *r, int flags)
543{
544    if (!(flags & AP_STATUS_SHORT))
545        ap_rprintf(r, "<h3>%s:</h3>\n", header_title);
546    else
547        ap_rprintf(r, "%s:\n", header_title);
548
549    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
550        apr_global_mutex_lock(cache->mutex);
551    cache->prov->status(cache->socache, r, flags);
552    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
553        apr_global_mutex_unlock(cache->mutex);
554
555    return OK;
556}
Note: See TracBrowser for help on using the repository browser.