source: mod_gnutls/src/gnutls_cache.c @ 81018a4

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

Remove compatibility code for GnuTLS version before 3.6.3

  • Property mode set to 100644
File size: 17.8 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 <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
171/** 8K is the maximum size accepted when receiving OCSP responses,
172 * sessions cache entries should be much smaller. The buffer is
173 * reallocated to actual size after fetching, so memory waste is
174 * minimal and temporary. */
175#define SOCACHE_FETCH_BUF_SIZE (8 * 1024)
176
177gnutls_datum_t mgs_cache_fetch(mgs_cache_t cache, server_rec *server,
178                               gnutls_datum_t key, apr_pool_t *pool)
179{
180    gnutls_datum_t data = {NULL, 0};
181    data.data = gnutls_malloc(SOCACHE_FETCH_BUF_SIZE);
182    if (data.data == NULL)
183        return data;
184    data.size = SOCACHE_FETCH_BUF_SIZE;
185
186    apr_pool_t *spool;
187    apr_pool_create(&spool, pool);
188
189    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
190        apr_global_mutex_lock(cache->mutex);
191    apr_status_t rv = cache->prov->retrieve(cache->socache, server,
192                                            key.data, key.size,
193                                            data.data, &data.size,
194                                            spool);
195    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
196        apr_global_mutex_unlock(cache->mutex);
197
198    if (rv != APR_SUCCESS)
199    {
200        /* APR_NOTFOUND means there's no such object. */
201        if (rv == APR_NOTFOUND)
202            ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
203                         "requested entry not found in cache '%s:%s'.",
204                         cache->prov->name, cache->config);
205        else
206            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, server,
207                         "error fetching from cache '%s:%s'",
208                         cache->prov->name, cache->config);
209        /* free unused buffer */
210        gnutls_free(data.data);
211        data.data = NULL;
212        data.size = 0;
213    }
214    else
215    {
216        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
217                     "fetched %u bytes from cache '%s:%s'",
218                     data.size, cache->prov->name, cache->config);
219
220        /* Realloc buffer to data.size. Data size must be less than or
221         * equal to the initial buffer size, so this REALLY should not
222         * fail. */
223        data.data = gnutls_realloc(data.data, data.size);
224        if (__builtin_expect(data.data == NULL, 0))
225        {
226            ap_log_error(APLOG_MARK, APLOG_CRIT, APR_ENOMEM, server,
227                         "%s: Could not realloc fetch buffer to data size!",
228                         __func__);
229            data.size = 0;
230        }
231    }
232    apr_pool_destroy(spool);
233
234    return data;
235}
236
237
238
239/**
240 * Fetch function for the GnuTLS session cache, see
241 * gnutls_db_set_retrieve_function().
242 *
243 * *Warning*: The `data` element of the returned `gnutls_datum_t` is
244 * allocated using `gnutls_malloc()` for compatibility with the GnuTLS
245 * session caching API, and must be released using `gnutls_free()`.
246 *
247 * @param baton mgs_handle_t for the connection, as set via
248 * gnutls_db_set_ptr()
249 *
250 * @param key object key to fetch
251 *
252 * @return the requested cache entry, or `{NULL, 0}`
253 */
254static gnutls_datum_t socache_fetch_session(void *baton, gnutls_datum_t key)
255{
256    gnutls_datum_t data = {NULL, 0};
257    gnutls_datum_t dbmkey;
258    mgs_handle_t *ctxt = baton;
259
260    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &dbmkey) < 0)
261        return data;
262
263    return mgs_cache_fetch(ctxt->sc->cache, ctxt->c->base_server,
264                           dbmkey, ctxt->c->pool);
265}
266
267
268
269/**
270 * Remove function for the GnuTLS session cache, see
271 * gnutls_db_set_remove_function().
272 *
273 * @param baton mgs_handle_t for the connection, as set via
274 * gnutls_db_set_ptr()
275 *
276 * @param key object key to remove
277 *
278 * @return `0` in case of success, `-1` in case of failure
279 */
280static int socache_delete_session(void *baton, gnutls_datum_t key)
281{
282    gnutls_datum_t tmpkey;
283    mgs_handle_t *ctxt = baton;
284
285    if (mgs_session_id2dbm(ctxt->c, key.data, key.size, &tmpkey) < 0)
286        return -1;
287
288    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
289        apr_global_mutex_lock(ctxt->sc->cache->mutex);
290    apr_status_t rv = ctxt->sc->cache->prov->remove(ctxt->sc->cache->socache,
291                                                    ctxt->c->base_server,
292                                                    key.data, key.size,
293                                                    ctxt->c->pool);
294    if (ctxt->sc->cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
295        apr_global_mutex_unlock(ctxt->sc->cache->mutex);
296
297    if (rv != APR_SUCCESS) {
298        ap_log_error(APLOG_MARK, APLOG_NOTICE, rv,
299                     ctxt->c->base_server,
300                     "error deleting from cache '%s:%s'",
301                     ctxt->sc->cache->prov->name, ctxt->sc->cache->config);
302        return -1;
303    }
304    return 0;
305}
306
307
308
309const char *mgs_cache_inst_config(mgs_cache_t *cache, server_rec *server,
310                                  const char* type, const char* config,
311                                  apr_pool_t *pconf, apr_pool_t *ptemp)
312{
313    /* Allocate cache structure, will be assigned to *cache after
314     * successful configuration. */
315    mgs_cache_t c = apr_pcalloc(pconf, sizeof(struct mgs_cache));
316    if (c == NULL)
317        return "Could not allocate memory for cache configuration!";
318
319    /* Find the right socache provider */
320    c->prov = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
321                                 type,
322                                 AP_SOCACHE_PROVIDER_VERSION);
323    if (c->prov == NULL)
324    {
325        return apr_psprintf(ptemp,
326                            "Could not find socache provider '%s', please "
327                            "make sure that the provider name is valid and "
328                            "the appropriate module is loaded (maybe "
329                            "mod_socache_%s.so?).",
330                            type, type);
331    }
332
333    /* shmcb works fine with NULL, but make sure there's a valid (if
334     * empty) string for logging */
335    if (config != NULL)
336        c->config = apr_pstrdup(pconf, config);
337    else
338        c->config = "";
339
340    /* Create and configure the cache instance. */
341    const char *err = c->prov->create(&c->socache, c->config, ptemp, pconf);
342    if (err != NULL)
343    {
344        return apr_psprintf(ptemp,
345                            "Creating cache '%s:%s' failed: %s",
346                            c->prov->name, c->config, err);
347    }
348    ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, server,
349                 "%s: Socache '%s:%s' created.",
350                 __func__, c->prov->name, c->config);
351
352    /* assign configured cache structure to server */
353    *cache = c;
354
355    return NULL;
356}
357
358
359
360/**
361 * This function is supposed to be called during post_config to
362 * initialize mutex and socache instance associated with an
363 * mgs_cache_t.
364 *
365 * @param cache the mod_gnutls cache structure
366 *
367 * @param cache_name name for socache initialization
368 *
369 * @param mutex_name name to pass to ap_global_mutex_create(), must
370 * have been registered during pre_config.
371 *
372 * @param server server for logging purposes
373 *
374 * @param pconf memory pool for server configuration
375 */
376static apr_status_t mgs_cache_inst_init(mgs_cache_t cache,
377                                        const char *cache_name,
378                                        const char *mutex_name,
379                                        server_rec *server,
380                                        apr_pool_t *pconf)
381{
382    apr_status_t rv = APR_SUCCESS;
383
384    if (cache->mutex == NULL)
385    {
386        rv = ap_global_mutex_create(&cache->mutex, NULL,
387                                    mutex_name,
388                                    NULL, server, pconf, 0);
389        ap_log_error(APLOG_MARK, APLOG_TRACE1, rv, server,
390                     "%s: create mutex", __func__);
391        if (rv != APR_SUCCESS)
392            return rv;
393    }
394
395    rv = cache->prov->init(cache->socache, cache_name, NULL, server, pconf);
396    if (rv != APR_SUCCESS)
397        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, server,
398                     "Initializing cache '%s:%s' failed!",
399                     cache->prov->name, cache->config);
400    else
401        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, server,
402                     "%s: socache '%s:%s' initialized.", __func__,
403                     cache->prov->name, cache->config);
404    return rv;
405}
406
407
408
409static apr_status_t cleanup_socache(void *data)
410{
411    server_rec *s = data;
412    mgs_srvconf_rec *sc = (mgs_srvconf_rec *)
413        ap_get_module_config(s->module_config, &gnutls_module);
414    if (sc->cache)
415    {
416        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
417                     "Cleaning up session cache '%s:%s'",
418                     sc->cache->prov->name, sc->cache->config);
419        sc->cache->prov->destroy(sc->cache->socache, s);
420    }
421    if (sc->ocsp_cache)
422    {
423        ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, s,
424                     "Cleaning up OCSP cache '%s:%s'",
425                     sc->ocsp_cache->prov->name, sc->ocsp_cache->config);
426        sc->ocsp_cache->prov->destroy(sc->ocsp_cache->socache, s);
427    }
428    return APR_SUCCESS;
429}
430
431
432
433int mgs_cache_post_config(apr_pool_t *pconf, apr_pool_t *ptemp,
434                          server_rec *s, mgs_srvconf_rec *sc)
435{
436    apr_status_t rv = APR_SUCCESS;
437
438    /* If the OCSP cache is unconfigured initialize it with
439     * defaults. */
440    if (sc->ocsp_cache == NULL)
441    {
442        ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
443                     "%s: OCSP cache unconfigured, using '%s:%s'.", __func__,
444                     DEFAULT_OCSP_CACHE_TYPE, DEFAULT_OCSP_CACHE_CONF);
445        const char *err = mgs_cache_inst_config(&sc->ocsp_cache, s,
446                                                DEFAULT_OCSP_CACHE_TYPE,
447                                                DEFAULT_OCSP_CACHE_CONF,
448                                                pconf, ptemp);
449        if (err != NULL)
450            ap_log_error(APLOG_MARK, APLOG_WARNING, rv, s,
451                         "%s: Configuring default OCSP cache '%s:%s' failed, "
452                         "make sure that mod_socache_%s is loaded.", __func__,
453                         DEFAULT_OCSP_CACHE_TYPE, DEFAULT_OCSP_CACHE_CONF,
454                         DEFAULT_OCSP_CACHE_TYPE);
455    }
456
457    /* Initialize the OCSP cache first so it's not skipped if the
458     * session cache is disabled. */
459    if (sc->ocsp_cache != NULL)
460    {
461        /* TODO: Maybe initialize only if explicitly enabled OR at
462         * least one (virtual) host has OCSP enabled? */
463        rv = mgs_cache_inst_init(sc->ocsp_cache, MGS_OCSP_CACHE_NAME,
464                                 MGS_OCSP_CACHE_MUTEX_NAME, s, pconf);
465        if (rv != APR_SUCCESS)
466            return HTTP_INSUFFICIENT_STORAGE;
467    }
468
469    /* GnuTLSCache was never explicitly set or is disabled: */
470    if (sc->cache_enable == GNUTLS_ENABLED_UNSET
471        || sc->cache_enable == GNUTLS_ENABLED_FALSE)
472    {
473        sc->cache_enable = GNUTLS_ENABLED_FALSE;
474        /* Cache disabled, done. */
475        return APR_SUCCESS;
476    }
477    /* if GnuTLSCacheTimeout was never explicitly set: */
478    if (sc->cache_timeout == MGS_TIMEOUT_UNSET)
479        sc->cache_timeout = apr_time_from_sec(MGS_DEFAULT_CACHE_TIMEOUT);
480
481    rv = mgs_cache_inst_init(sc->cache, MGS_SESSION_CACHE_NAME,
482                             MGS_CACHE_MUTEX_NAME, s, pconf);
483    if (rv != APR_SUCCESS)
484        return HTTP_INSUFFICIENT_STORAGE;
485
486    apr_pool_pre_cleanup_register(pconf, s, cleanup_socache);
487
488    return APR_SUCCESS;
489}
490
491int mgs_cache_child_init(apr_pool_t *p, server_rec *server,
492                         mgs_cache_t cache, const char *mutex_name)
493{
494    /* reinit cache mutex */
495    const char *lockfile = apr_global_mutex_lockfile(cache->mutex);
496    apr_status_t rv = apr_global_mutex_child_init(&cache->mutex,
497                                                  lockfile, p);
498    if (rv != APR_SUCCESS)
499        ap_log_error(APLOG_MARK, APLOG_EMERG, rv, server,
500                     "Failed to reinit mutex '%s'", mutex_name);
501
502    return rv;
503}
504
505int mgs_cache_session_init(mgs_handle_t * ctxt)
506{
507    if (ctxt->sc->cache_enable)
508    {
509        gnutls_db_set_retrieve_function(ctxt->session,
510                                        socache_fetch_session);
511        gnutls_db_set_remove_function(ctxt->session,
512                                      socache_delete_session);
513        gnutls_db_set_store_function(ctxt->session,
514                                     socache_store_session);
515        gnutls_db_set_ptr(ctxt->session, ctxt);
516    }
517    return 0;
518}
519
520
521
522int mgs_cache_status(mgs_cache_t cache, const char *header_title,
523                     request_rec *r, int flags)
524{
525    if (!(flags & AP_STATUS_SHORT))
526        ap_rprintf(r, "<h3>%s:</h3>\n", header_title);
527    else
528        ap_rprintf(r, "%s:\n", header_title);
529
530    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
531        apr_global_mutex_lock(cache->mutex);
532    cache->prov->status(cache->socache, r, flags);
533    if (cache->prov->flags & AP_SOCACHE_FLAG_NOTMPSAFE)
534        apr_global_mutex_unlock(cache->mutex);
535
536    return OK;
537}
Note: See TracBrowser for help on using the repository browser.