source: mod_gnutls/src/gnutls_cache.c

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

Include apr_strings.h only where needed

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