source: mod_gnutls/src/gnutls_cache.c @ 2b1b52f

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

Actually use the computed socache key in socache_delete_session()

We rely on the socache expiration rather than GnuTLS deleting cache
entries, so this function was likely never called, but if it was it
would have failed because it used the raw session ID provided by
GnuTLS rather than the derived key mod_gnutls uses for store and
fetch.

  • Property mode set to 100644
File size: 17.6 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
271/**
272 * Remove function for the GnuTLS session cache, see
273 * gnutls_db_set_remove_function().
274 *
275 * @param baton mgs_handle_t for the connection, as set via
276 * gnutls_db_set_ptr()
277 *
278 * @param key object key to remove
279 *
280 * @return `0` in case of success, `-1` in case of failure
281 */
282static int socache_delete_session(void *baton, gnutls_datum_t key)
283{
284    gnutls_datum_t dbmkey;
285    mgs_handle_t *ctxt = baton;
286
287    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
288        return -1;
289
290    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
291        apr_global_mutex_lock(ctxt->sc->cache->mutex);
292    apr_status_t rv = ctxt->sc->cache->prov->remove(ctxt->sc->cache->socache,
293                                                    ctxt->c->base_server,
294                                                    dbmkey.data, dbmkey.size,
295                                                    ctxt->c->pool);
296    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
297        apr_global_mutex_unlock(ctxt->sc->cache->mutex);
298
299    if (rv != APR_SUCCESS) {
300        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv,
301                     ctxt->c->base_server,
302                     "error deleting from cache '%s:%s'",
303                     ctxt->sc->cache->prov->name, ctxt->sc->cache->config);
304        return -1;
305    }
306    return 0;
307}
308
309
310
311const char *mgs_cache_inst_config(mgs_cache_t *cache, server_rec *server,
312                                  const char* type, const char* config,
313                                  apr_pool_t *pconf, apr_pool_t *ptemp)
314{
315    /* Allocate cache structure, will be assigned to *cache after
316     * successful configuration. */
317    mgs_cache_t c = apr_pcalloc(pconf, sizeof(struct mgs_cache));
318    if (c == NULL)
319        return "Could not allocate memory for cache configuration!";
320
321    /* Find the right socache provider */
322    c->prov = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
323                                 type,
324                                 AP_SOCACHE_PROVIDER_VERSION);
325    if (c->prov == NULL)
326    {
327        return apr_psprintf(ptemp,
328                            "Could not find socache provider '%s', please "
329                            "make sure that the provider name is valid and "
330                            "the appropriate module is loaded (maybe "
331                            "mod_socache_%s.so?).",
332                            type, type);
333    }
334
335    /* shmcb works fine with NULL, but make sure there's a valid (if
336     * empty) string for logging */
337    if (config != NULL)
338        c->config = apr_pstrdup(pconf, config);
339    else
340        c->config = "";
341
342    /* Create and configure the cache instance. */
343    const char *err = c->prov->create(&c->socache, c->config, ptemp, pconf);
344    if (err != NULL)
345    {
346        return apr_psprintf(ptemp,
347                            "Creating cache '%s:%s' failed: %s",
348                            c->prov->name, c->config, err);
349    }
350    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, server,
351                 "%s: Socache '%s:%s' created.",
352                 __func__, c->prov->name, c->config);
353
354    /* assign configured cache structure to server */
355    *cache = c;
356
357    return NULL;
358}
359
360
361
362/**
363 * This function is supposed to be called during post_config to
364 * initialize mutex and socache instance associated with an
365 * mgs_cache_t.
366 *
367 * @param cache the mod_gnutls cache structure
368 *
369 * @param cache_name name for socache initialization
370 *
371 * @param mutex_name name to pass to ap_global_mutex_create(), must
372 * have been registered during pre_config.
373 *
374 * @param server server for logging purposes
375 *
376 * @param pconf memory pool for server configuration
377 */
378static apr_status_t mgs_cache_inst_init(mgs_cache_t cache,
379                                        const char *cache_name,
380                                        const char *mutex_name,
381                                        server_rec *server,
382                                        apr_pool_t *pconf)
383{
384    apr_status_t rv = APR_SUCCESS;
385
386    if (cache->mutex == NULL)
387    {
388        rv = ap_global_mutex_create(&cache->mutex, NULL,
389                                    mutex_name,
390                                    NULL, server, pconf, 0);
391        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
392                     "%s: create mutex", __func__);
393        if (rv != APR_SUCCESS)
394            return rv;
395    }
396
397    rv = cache->prov->init(cache->socache, cache_name, NULL, server, pconf);
398    if (rv != APR_SUCCESS)
399        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, server,
400                     "Initializing cache '%s:%s' failed!",
401                     cache->prov->name, cache->config);
402    else
403        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
404                     "%s: socache '%s:%s' initialized.", __func__,
405                     cache->prov->name, cache->config);
406    return rv;
407}
408
409
410
411static apr_status_t cleanup_socache(void *data)
412{
413    server_rec *s = data;
414    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
415        ap_get_module_config(s->module_config, &gnutls_module);
416    if (sc->cache)
417    {
418        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
419                     "Cleaning up session cache '%s:%s'",
420                     sc->cache->prov->name, sc->cache->config);
421        sc->cache->prov->destroy(sc->cache->socache, s);
422    }
423    if (sc->ocsp_cache)
424    {
425        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
426                     "Cleaning up OCSP cache '%s:%s'",
427                     sc->ocsp_cache->prov->name, sc->ocsp_cache->config);
428        sc->ocsp_cache->prov->destroy(sc->ocsp_cache->socache, s);
429    }
430    return APR_SUCCESS;
431}
432
433
434
435int mgs_cache_post_config(apr_pool_t *pconf, apr_pool_t *ptemp,
436                          server_rec *s, mgs_srvconf_rec *sc)
437{
438    apr_status_t rv = APR_SUCCESS;
439
440    /* If the OCSP cache is unconfigured initialize it with
441     * defaults. */
442    if (sc->ocsp_cache == NULL)
443    {
444        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
445                     "%s: OCSP cache unconfigured, using '%s:%s'.", __func__,
446                     DEFAULT_OCSP_CACHE_TYPE, DEFAULT_OCSP_CACHE_CONF);
447        const char *err = mgs_cache_inst_config(&sc->ocsp_cache, s,
448                                                DEFAULT_OCSP_CACHE_TYPE,
449                                                DEFAULT_OCSP_CACHE_CONF,
450                                                pconf, ptemp);
451        if (err != NULL)
452            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
453                         "%s: Configuring default OCSP cache '%s:%s' failed, "
454                         "make sure that mod_socache_%s is loaded.", __func__,
455                         DEFAULT_OCSP_CACHE_TYPE, DEFAULT_OCSP_CACHE_CONF,
456                         DEFAULT_OCSP_CACHE_TYPE);
457    }
458
459    /* Initialize the OCSP cache first so it's not skipped if the
460     * session cache is disabled. */
461    if (sc->ocsp_cache != NULL)
462    {
463        /* TODO: Maybe initialize only if explicitly enabled OR at
464         * least one (virtual) host has OCSP enabled? */
465        rv = mgs_cache_inst_init(sc->ocsp_cache, MGS_OCSP_CACHE_NAME,
466                                 MGS_OCSP_CACHE_MUTEX_NAME, s, pconf);
467        if (rv != APR_SUCCESS)
468            return HTTP_INSUFFICIENT_STORAGE;
469    }
470
471    /* GnuTLSCache was never explicitly set or is disabled: */
472    if (sc->cache_enable == GNUTLS_ENABLED_UNSET
473        || sc->cache_enable == GNUTLS_ENABLED_FALSE)
474    {
475        sc->cache_enable = GNUTLS_ENABLED_FALSE;
476        /* Cache disabled, done. */
477        return APR_SUCCESS;
478    }
479    /* if GnuTLSCacheTimeout was never explicitly set: */
480    if (sc->cache_timeout == MGS_TIMEOUT_UNSET)
481        sc->cache_timeout = apr_time_from_sec(MGS_DEFAULT_CACHE_TIMEOUT);
482
483    rv = mgs_cache_inst_init(sc->cache, MGS_SESSION_CACHE_NAME,
484                             MGS_CACHE_MUTEX_NAME, s, pconf);
485    if (rv != APR_SUCCESS)
486        return HTTP_INSUFFICIENT_STORAGE;
487
488    apr_pool_pre_cleanup_register(pconf, s, cleanup_socache);
489
490    return APR_SUCCESS;
491}
492
493int mgs_cache_child_init(apr_pool_t *p, server_rec *server,
494                         mgs_cache_t cache, const char *mutex_name)
495{
496    /* reinit cache mutex */
497    const char *lockfile = apr_global_mutex_lockfile(cache->mutex);
498    apr_status_t rv = apr_global_mutex_child_init(&cache->mutex,
499                                                  lockfile, p);
500    if (rv != APR_SUCCESS)
501        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, server,
502                     "Failed to reinit mutex '%s'", mutex_name);
503
504    return rv;
505}
506
507int mgs_cache_session_init(mgs_handle_t * ctxt)
508{
509    if (ctxt->sc->cache_enable)
510    {
511        gnutls_db_set_retrieve_function(ctxt->session,
512                                        socache_fetch_session);
513        gnutls_db_set_remove_function(ctxt->session,
514                                      socache_delete_session);
515        gnutls_db_set_store_function(ctxt->session,
516                                     socache_store_session);
517        gnutls_db_set_ptr(ctxt->session, ctxt);
518    }
519    return 0;
520}
521
522
523
524int mgs_cache_status(mgs_cache_t cache, const char *header_title,
525                     request_rec *r, int flags)
526{
527    if (!(flags & AP_STATUS_SHORT))
528        ap_rprintf(r, "<h3>%s:</h3>\n", header_title);
529    else
530        ap_rprintf(r, "%s:\n", header_title);
531
532    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
533        apr_global_mutex_lock(cache->mutex);
534    cache->prov->status(cache->socache, r, flags);
535    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
536        apr_global_mutex_unlock(cache->mutex);
537
538    return OK;
539}
Note: See TracBrowser for help on using the repository browser.