source: mod_gnutls/src/gnutls_cache.c @ 3aff94d

debian/master
Last change on this file since 3aff94d was 3aff94d, checked in by Fiona Klute <fiona.klute@…>, 14 months ago

Include cache status in mod_status reports

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