xref: /third_party/mbedtls/library/ssl_cache.c (revision a8e1175b)
1/*
2 *  SSL session cache implementation
3 *
4 *  Copyright The Mbed TLS Contributors
5 *  SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
6 */
7/*
8 * These session callbacks use a simple chained list
9 * to store and retrieve the session information.
10 */
11
12#include "common.h"
13
14#if defined(MBEDTLS_SSL_CACHE_C)
15
16#include "mbedtls/platform.h"
17
18#include "mbedtls/ssl_cache.h"
19#include "ssl_misc.h"
20#include "mbedtls/error.h"
21
22#include <string.h>
23
24void mbedtls_ssl_cache_init(mbedtls_ssl_cache_context *cache)
25{
26    memset(cache, 0, sizeof(mbedtls_ssl_cache_context));
27
28    cache->timeout = MBEDTLS_SSL_CACHE_DEFAULT_TIMEOUT;
29    cache->max_entries = MBEDTLS_SSL_CACHE_DEFAULT_MAX_ENTRIES;
30
31#if defined(MBEDTLS_THREADING_C)
32    mbedtls_mutex_init(&cache->mutex);
33#endif
34}
35
36MBEDTLS_CHECK_RETURN_CRITICAL
37static int ssl_cache_find_entry(mbedtls_ssl_cache_context *cache,
38                                unsigned char const *session_id,
39                                size_t session_id_len,
40                                mbedtls_ssl_cache_entry **dst)
41{
42    int ret = MBEDTLS_ERR_SSL_CACHE_ENTRY_NOT_FOUND;
43#if defined(MBEDTLS_HAVE_TIME)
44    mbedtls_time_t t = mbedtls_time(NULL);
45#endif
46    mbedtls_ssl_cache_entry *cur;
47
48    for (cur = cache->chain; cur != NULL; cur = cur->next) {
49#if defined(MBEDTLS_HAVE_TIME)
50        if (cache->timeout != 0 &&
51            (int) (t - cur->timestamp) > cache->timeout) {
52            continue;
53        }
54#endif
55
56        if (session_id_len != cur->session_id_len ||
57            memcmp(session_id, cur->session_id,
58                   cur->session_id_len) != 0) {
59            continue;
60        }
61
62        break;
63    }
64
65    if (cur != NULL) {
66        *dst = cur;
67        ret = 0;
68    }
69
70    return ret;
71}
72
73
74int mbedtls_ssl_cache_get(void *data,
75                          unsigned char const *session_id,
76                          size_t session_id_len,
77                          mbedtls_ssl_session *session)
78{
79    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
80    mbedtls_ssl_cache_context *cache = (mbedtls_ssl_cache_context *) data;
81    mbedtls_ssl_cache_entry *entry;
82
83#if defined(MBEDTLS_THREADING_C)
84    if ((ret = mbedtls_mutex_lock(&cache->mutex)) != 0) {
85        return ret;
86    }
87#endif
88
89    ret = ssl_cache_find_entry(cache, session_id, session_id_len, &entry);
90    if (ret != 0) {
91        goto exit;
92    }
93
94    ret = mbedtls_ssl_session_load(session,
95                                   entry->session,
96                                   entry->session_len);
97    if (ret != 0) {
98        goto exit;
99    }
100
101    ret = 0;
102
103exit:
104#if defined(MBEDTLS_THREADING_C)
105    if (mbedtls_mutex_unlock(&cache->mutex) != 0) {
106        ret = MBEDTLS_ERR_THREADING_MUTEX_ERROR;
107    }
108#endif
109
110    return ret;
111}
112
113/* zeroize a cache entry */
114static void ssl_cache_entry_zeroize(mbedtls_ssl_cache_entry *entry)
115{
116    if (entry == NULL) {
117        return;
118    }
119
120    /* zeroize and free session structure */
121    if (entry->session != NULL) {
122        mbedtls_zeroize_and_free(entry->session, entry->session_len);
123    }
124
125    /* zeroize the whole entry structure */
126    mbedtls_platform_zeroize(entry, sizeof(mbedtls_ssl_cache_entry));
127}
128
129MBEDTLS_CHECK_RETURN_CRITICAL
130static int ssl_cache_pick_writing_slot(mbedtls_ssl_cache_context *cache,
131                                       unsigned char const *session_id,
132                                       size_t session_id_len,
133                                       mbedtls_ssl_cache_entry **dst)
134{
135#if defined(MBEDTLS_HAVE_TIME)
136    mbedtls_time_t t = mbedtls_time(NULL), oldest = 0;
137#endif /* MBEDTLS_HAVE_TIME */
138
139    mbedtls_ssl_cache_entry *old = NULL;
140    int count = 0;
141    mbedtls_ssl_cache_entry *cur, *last;
142
143    /* Check 1: Is there already an entry with the given session ID?
144     *
145     * If yes, overwrite it.
146     *
147     * If not, `count` will hold the size of the session cache
148     * at the end of this loop, and `last` will point to the last
149     * entry, both of which will be used later. */
150
151    last = NULL;
152    for (cur = cache->chain; cur != NULL; cur = cur->next) {
153        count++;
154        if (session_id_len == cur->session_id_len &&
155            memcmp(session_id, cur->session_id, cur->session_id_len) == 0) {
156            goto found;
157        }
158        last = cur;
159    }
160
161    /* Check 2: Is there an outdated entry in the cache?
162     *
163     * If so, overwrite it.
164     *
165     * If not, remember the oldest entry in `old` for later.
166     */
167
168#if defined(MBEDTLS_HAVE_TIME)
169    for (cur = cache->chain; cur != NULL; cur = cur->next) {
170        if (cache->timeout != 0 &&
171            (int) (t - cur->timestamp) > cache->timeout) {
172            goto found;
173        }
174
175        if (oldest == 0 || cur->timestamp < oldest) {
176            oldest = cur->timestamp;
177            old = cur;
178        }
179    }
180#endif /* MBEDTLS_HAVE_TIME */
181
182    /* Check 3: Is there free space in the cache? */
183
184    if (count < cache->max_entries) {
185        /* Create new entry */
186        cur = mbedtls_calloc(1, sizeof(mbedtls_ssl_cache_entry));
187        if (cur == NULL) {
188            return MBEDTLS_ERR_SSL_ALLOC_FAILED;
189        }
190
191        /* Append to the end of the linked list. */
192        if (last == NULL) {
193            cache->chain = cur;
194        } else {
195            last->next = cur;
196        }
197
198        goto found;
199    }
200
201    /* Last resort: The cache is full and doesn't contain any outdated
202     * elements. In this case, we evict the oldest one, judged by timestamp
203     * (if present) or cache-order. */
204
205#if defined(MBEDTLS_HAVE_TIME)
206    if (old == NULL) {
207        /* This should only happen on an ill-configured cache
208         * with max_entries == 0. */
209        return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
210    }
211#else /* MBEDTLS_HAVE_TIME */
212    /* Reuse first entry in chain, but move to last place. */
213    if (cache->chain == NULL) {
214        /* This should never happen */
215        return MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
216    }
217
218    old = cache->chain;
219    cache->chain = old->next;
220    old->next = NULL;
221    last->next = old;
222#endif /* MBEDTLS_HAVE_TIME */
223
224    /* Now `old` points to the oldest entry to be overwritten. */
225    cur = old;
226
227found:
228
229    /* If we're reusing an entry, free it first. */
230    if (cur->session != NULL) {
231        /* `ssl_cache_entry_zeroize` would break the chain,
232         * so we reuse `old` to record `next` temporarily. */
233        old = cur->next;
234        ssl_cache_entry_zeroize(cur);
235        cur->next = old;
236    }
237
238#if defined(MBEDTLS_HAVE_TIME)
239    cur->timestamp = t;
240#endif
241
242    *dst = cur;
243    return 0;
244}
245
246int mbedtls_ssl_cache_set(void *data,
247                          unsigned char const *session_id,
248                          size_t session_id_len,
249                          const mbedtls_ssl_session *session)
250{
251    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
252    mbedtls_ssl_cache_context *cache = (mbedtls_ssl_cache_context *) data;
253    mbedtls_ssl_cache_entry *cur;
254
255    size_t session_serialized_len = 0;
256    unsigned char *session_serialized = NULL;
257
258#if defined(MBEDTLS_THREADING_C)
259    if ((ret = mbedtls_mutex_lock(&cache->mutex)) != 0) {
260        return ret;
261    }
262#endif
263
264    ret = ssl_cache_pick_writing_slot(cache,
265                                      session_id, session_id_len,
266                                      &cur);
267    if (ret != 0) {
268        goto exit;
269    }
270
271    /* Check how much space we need to serialize the session
272     * and allocate a sufficiently large buffer. */
273    ret = mbedtls_ssl_session_save(session, NULL, 0, &session_serialized_len);
274    if (ret != MBEDTLS_ERR_SSL_BUFFER_TOO_SMALL) {
275        goto exit;
276    }
277
278    session_serialized = mbedtls_calloc(1, session_serialized_len);
279    if (session_serialized == NULL) {
280        ret = MBEDTLS_ERR_SSL_ALLOC_FAILED;
281        goto exit;
282    }
283
284    /* Now serialize the session into the allocated buffer. */
285    ret = mbedtls_ssl_session_save(session,
286                                   session_serialized,
287                                   session_serialized_len,
288                                   &session_serialized_len);
289    if (ret != 0) {
290        goto exit;
291    }
292
293    if (session_id_len > sizeof(cur->session_id)) {
294        ret = MBEDTLS_ERR_SSL_BAD_INPUT_DATA;
295        goto exit;
296    }
297    cur->session_id_len = session_id_len;
298    memcpy(cur->session_id, session_id, session_id_len);
299
300    cur->session = session_serialized;
301    cur->session_len = session_serialized_len;
302    session_serialized = NULL;
303
304    ret = 0;
305
306exit:
307#if defined(MBEDTLS_THREADING_C)
308    if (mbedtls_mutex_unlock(&cache->mutex) != 0) {
309        ret = MBEDTLS_ERR_THREADING_MUTEX_ERROR;
310    }
311#endif
312
313    if (session_serialized != NULL) {
314        mbedtls_zeroize_and_free(session_serialized, session_serialized_len);
315        session_serialized = NULL;
316    }
317
318    return ret;
319}
320
321int mbedtls_ssl_cache_remove(void *data,
322                             unsigned char const *session_id,
323                             size_t session_id_len)
324{
325    int ret = MBEDTLS_ERR_ERROR_CORRUPTION_DETECTED;
326    mbedtls_ssl_cache_context *cache = (mbedtls_ssl_cache_context *) data;
327    mbedtls_ssl_cache_entry *entry;
328    mbedtls_ssl_cache_entry *prev;
329
330#if defined(MBEDTLS_THREADING_C)
331    if ((ret = mbedtls_mutex_lock(&cache->mutex)) != 0) {
332        return ret;
333    }
334#endif
335
336    ret = ssl_cache_find_entry(cache, session_id, session_id_len, &entry);
337    /* No valid entry found, exit with success */
338    if (ret != 0) {
339        ret = 0;
340        goto exit;
341    }
342
343    /* Now we remove the entry from the chain */
344    if (entry == cache->chain) {
345        cache->chain = entry->next;
346        goto free;
347    }
348    for (prev = cache->chain; prev->next != NULL; prev = prev->next) {
349        if (prev->next == entry) {
350            prev->next = entry->next;
351            break;
352        }
353    }
354
355free:
356    ssl_cache_entry_zeroize(entry);
357    mbedtls_free(entry);
358    ret = 0;
359
360exit:
361#if defined(MBEDTLS_THREADING_C)
362    if (mbedtls_mutex_unlock(&cache->mutex) != 0) {
363        ret = MBEDTLS_ERR_THREADING_MUTEX_ERROR;
364    }
365#endif
366
367    return ret;
368}
369
370#if defined(MBEDTLS_HAVE_TIME)
371void mbedtls_ssl_cache_set_timeout(mbedtls_ssl_cache_context *cache, int timeout)
372{
373    if (timeout < 0) {
374        timeout = 0;
375    }
376
377    cache->timeout = timeout;
378}
379#endif /* MBEDTLS_HAVE_TIME */
380
381void mbedtls_ssl_cache_set_max_entries(mbedtls_ssl_cache_context *cache, int max)
382{
383    if (max < 0) {
384        max = 0;
385    }
386
387    cache->max_entries = max;
388}
389
390void mbedtls_ssl_cache_free(mbedtls_ssl_cache_context *cache)
391{
392    mbedtls_ssl_cache_entry *cur, *prv;
393
394    cur = cache->chain;
395
396    while (cur != NULL) {
397        prv = cur;
398        cur = cur->next;
399
400        ssl_cache_entry_zeroize(prv);
401        mbedtls_free(prv);
402    }
403
404#if defined(MBEDTLS_THREADING_C)
405    mbedtls_mutex_free(&cache->mutex);
406#endif
407    cache->chain = NULL;
408}
409
410#endif /* MBEDTLS_SSL_CACHE_C */
411