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