Added general-purpose API for AEAD algorithms, and GCM implementation.
[BearSSL] / src / ssl / ssl_lru.c
1 /*
2 * Copyright (c) 2016 Thomas Pornin <pornin@bolet.org>
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files (the
6 * "Software"), to deal in the Software without restriction, including
7 * without limitation the rights to use, copy, modify, merge, publish,
8 * distribute, sublicense, and/or sell copies of the Software, and to
9 * permit persons to whom the Software is furnished to do so, subject to
10 * the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
19 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
20 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 */
24
25 #include "inner.h"
26
27 /*
28 * Each entry consists in a fixed number of bytes. Entries are concatenated
29 * in the store block. "Addresses" are really offsets in the block,
30 * expressed over 32 bits (so the cache may have size at most 4 GB, which
31 * "ought to be enough for everyone"). The "null address" is 0xFFFFFFFF.
32 * Note that since the storage block alignment is in no way guaranteed, we
33 * perform only accesses that can handle unaligned data.
34 *
35 * Two concurrent data structures are maintained:
36 *
37 * -- Entries are organised in a doubly-linked list; saved entries are added
38 * at the head, and loaded entries are moved to the head. Eviction uses
39 * the list tail (this is the LRU algorithm).
40 *
41 * -- Entries are indexed with a binary tree: all left descendants of a
42 * node have a lower session ID (in lexicographic order), while all
43 * right descendants have a higher session ID. The tree is heuristically
44 * balanced.
45 *
46 * Entry format:
47 *
48 * session ID 32 bytes
49 * master secret 48 bytes
50 * protocol version 2 bytes (big endian)
51 * cipher suite 2 bytes (big endian)
52 * list prev 4 bytes (big endian)
53 * list next 4 bytes (big endian)
54 * tree left child 4 bytes (big endian)
55 * tree right child 4 bytes (big endian)
56 *
57 * We need to keep the tree balanced because an attacker could make
58 * handshakes, selecting some specific sessions (by reusing them) to
59 * try to make us make an imbalanced tree that makes lookups expensive
60 * (a denial-of-service attack that would persist as long as the cache
61 * remains, i.e. even after the attacker made all his connections).
62 * To do that, we replace the session ID (or the start of the session ID)
63 * with a HMAC value computed over the replaced part; the hash function
64 * implementation and the key are obtained from the server context upon
65 * first save() call.
66 *
67 * Theoretically, an attacker could use the exact timing of the lookup
68 * to infer the current tree topology, and try to revive entries to make
69 * it as unbalanced as possible. However, since the session ID are
70 * chosen randomly by the server, and the attacker cannot see the
71 * indexing values and must thus rely on blind selection, it should be
72 * exponentially difficult for the attacker to maintain a large
73 * imbalance.
74 */
75 #define SESSION_ID_LEN 32
76 #define MASTER_SECRET_LEN 48
77
78 #define SESSION_ID_OFF 0
79 #define MASTER_SECRET_OFF 32
80 #define VERSION_OFF 80
81 #define CIPHER_SUITE_OFF 82
82 #define LIST_PREV_OFF 84
83 #define LIST_NEXT_OFF 88
84 #define TREE_LEFT_OFF 92
85 #define TREE_RIGHT_OFF 96
86
87 #define LRU_ENTRY_LEN 100
88
89 #define ADDR_NULL ((uint32_t)-1)
90
91 #define GETSET(name, off) \
92 static inline uint32_t get_ ## name(br_ssl_session_cache_lru *cc, uint32_t x) \
93 { \
94 return br_dec32be(cc->store + x + (off)); \
95 } \
96 static inline void set_ ## name(br_ssl_session_cache_lru *cc, \
97 uint32_t x, uint32_t val) \
98 { \
99 br_enc32be(cc->store + x + (off), val); \
100 }
101
102 GETSET(prev, LIST_PREV_OFF)
103 GETSET(next, LIST_NEXT_OFF)
104 GETSET(left, TREE_LEFT_OFF)
105 GETSET(right, TREE_RIGHT_OFF)
106
107 /*
108 * Transform the session ID by replacing the first N bytes with a HMAC
109 * value computed over these bytes, using the random key K (the HMAC
110 * value is truncated if needed). HMAC will use the same hash function
111 * as the DRBG in the SSL server context, so with SHA-256, SHA-384,
112 * or SHA-1, depending on what is available.
113 *
114 * The risk of collision is considered too small to be a concern; and
115 * the impact of a collision is low (the handshake won't succeed). This
116 * risk is much lower than any transmission error, which would lead to
117 * the same consequences.
118 *
119 * Source and destination arrays msut be disjoint.
120 */
121 static void
122 mask_id(br_ssl_session_cache_lru *cc,
123 const unsigned char *src, unsigned char *dst)
124 {
125 br_hmac_key_context hkc;
126 br_hmac_context hc;
127
128 memcpy(dst, src, SESSION_ID_LEN);
129 br_hmac_key_init(&hkc, cc->hash, cc->index_key, sizeof cc->index_key);
130 br_hmac_init(&hc, &hkc, SESSION_ID_LEN);
131 br_hmac_update(&hc, src, SESSION_ID_LEN);
132 br_hmac_out(&hc, dst);
133 }
134
135 /*
136 * Find a node by ID. Returned value is the node address, or ADDR_NULL if
137 * the node is not found.
138 *
139 * If addr_link is not NULL, then '*addr_link' is set to the address of the
140 * last followed link. If the found node is the root, or if the tree is
141 * empty, then '*addr_link' is set to ADDR_NULL.
142 */
143 static uint32_t
144 find_node(br_ssl_session_cache_lru *cc, const unsigned char *id,
145 uint32_t *addr_link)
146 {
147 uint32_t x, y;
148
149 x = cc->root;
150 y = ADDR_NULL;
151 while (x != ADDR_NULL) {
152 int r;
153
154 r = memcmp(id, cc->store + x + SESSION_ID_OFF, SESSION_ID_LEN);
155 if (r < 0) {
156 y = x + TREE_LEFT_OFF;
157 x = get_left(cc, x);
158 } else if (r == 0) {
159 if (addr_link != NULL) {
160 *addr_link = y;
161 }
162 return x;
163 } else {
164 y = x + TREE_RIGHT_OFF;
165 x = get_right(cc, x);
166 }
167 }
168 if (addr_link != NULL) {
169 *addr_link = y;
170 }
171 return ADDR_NULL;
172 }
173
174 /*
175 * For node x, find its replacement upon removal.
176 *
177 * -- If node x has no child, then this returns ADDR_NULL.
178 * -- Otherwise, if node x has a left child, then the replacement is the
179 * rightmost left-descendent.
180 * -- Otherwise, the replacement is the leftmost right-descendent.
181 *
182 * If a node is returned, then '*al' is set to the address of the field
183 * that points to that node. Otherwise (node x has no child), '*al' is
184 * set to ADDR_NULL.
185 *
186 * Note that the replacement node, when found, is always a descendent
187 * of node 'x', so it cannot be the tree root. Thus, '*al' can be set
188 * to ADDR_NULL only when no node is found and ADDR_NULL is returned.
189 */
190 static uint32_t
191 find_replacement_node(br_ssl_session_cache_lru *cc, uint32_t x, uint32_t *al)
192 {
193 uint32_t y1, y2;
194
195 y1 = get_left(cc, x);
196 if (y1 != ADDR_NULL) {
197 y2 = x + TREE_LEFT_OFF;
198 for (;;) {
199 uint32_t z;
200
201 z = get_right(cc, y1);
202 if (z == ADDR_NULL) {
203 *al = y2;
204 return y1;
205 }
206 y2 = y1 + TREE_RIGHT_OFF;
207 y1 = z;
208 }
209 }
210 y1 = get_right(cc, x);
211 if (y1 != ADDR_NULL) {
212 y2 = x + TREE_RIGHT_OFF;
213 for (;;) {
214 uint32_t z;
215
216 z = get_left(cc, y1);
217 if (z == ADDR_NULL) {
218 *al = y2;
219 return y1;
220 }
221 y2 = y1 + TREE_LEFT_OFF;
222 y1 = z;
223 }
224 }
225 *al = ADDR_NULL;
226 return ADDR_NULL;
227 }
228
229 /*
230 * Set the link at address 'alx' to point to node 'x'. If 'alx' is
231 * ADDR_NULL, then this sets the tree root to 'x'.
232 */
233 static inline void
234 set_link(br_ssl_session_cache_lru *cc, uint32_t alx, uint32_t x)
235 {
236 if (alx == ADDR_NULL) {
237 cc->root = x;
238 } else {
239 br_enc32be(cc->store + alx, x);
240 }
241 }
242
243 /*
244 * Remove node 'x' from the tree. This function shall not be called if
245 * node 'x' is not part of the tree.
246 */
247 static void
248 remove_node(br_ssl_session_cache_lru *cc, uint32_t x)
249 {
250 uint32_t alx, y, aly;
251
252 /*
253 * Removal algorithm:
254 * ------------------
255 *
256 * - If we remove the root, then the tree becomes empty.
257 *
258 * - If the removed node has no child, then we can simply remove
259 * it, with nothing else to do.
260 *
261 * - Otherwise, the removed node must be replaced by either its
262 * rightmost left-descendent, or its leftmost right-descendent.
263 * The replacement node itself must be removed from its current
264 * place. By definition, that replacement node has either no
265 * child, or at most a single child that will replace it in the
266 * tree.
267 */
268
269 /*
270 * Find node back and its ancestor link. If the node was the
271 * root, then alx is set to ADDR_NULL.
272 */
273 find_node(cc, cc->store + x + SESSION_ID_OFF, &alx);
274
275 /*
276 * Find replacement node 'y', and 'aly' is set to the address of
277 * the link to that replacement node. If the removed node has no
278 * child, then both 'y' and 'aly' are set to ADDR_NULL.
279 */
280 y = find_replacement_node(cc, x, &aly);
281
282 if (y != ADDR_NULL) {
283 uint32_t z;
284
285 /*
286 * The unlinked replacement node may have one child (but
287 * not two) that takes its place.
288 */
289 z = get_left(cc, y);
290 if (z == ADDR_NULL) {
291 z = get_right(cc, y);
292 }
293 set_link(cc, aly, z);
294
295 /*
296 * Link the replacement node in its new place, overwriting
297 * the current link to the node 'x' (which removes 'x').
298 */
299 set_link(cc, alx, y);
300
301 /*
302 * The replacement node adopts the left and right children
303 * of the removed node. Note that this also works even if
304 * the replacement node was a direct descendent of the
305 * removed node, since we unlinked it previously.
306 */
307 set_left(cc, y, get_left(cc, x));
308 set_right(cc, y, get_right(cc, x));
309 } else {
310 /*
311 * No replacement, we simply unlink the node 'x'.
312 */
313 set_link(cc, alx, ADDR_NULL);
314 }
315 }
316
317 static void
318 lru_save(const br_ssl_session_cache_class **ctx,
319 br_ssl_server_context *server_ctx,
320 const br_ssl_session_parameters *params)
321 {
322 br_ssl_session_cache_lru *cc;
323 unsigned char id[SESSION_ID_LEN];
324 uint32_t x, alx;
325
326 cc = (br_ssl_session_cache_lru *)ctx;
327
328 /*
329 * If the buffer is too small, we don't record anything. This
330 * test avoids problems in subsequent code.
331 */
332 if (cc->store_len < LRU_ENTRY_LEN) {
333 return;
334 }
335
336 /*
337 * Upon the first save in a session cache instance, we obtain
338 * a random key for our indexing.
339 */
340 if (!cc->init_done) {
341 br_hmac_drbg_generate(&server_ctx->eng.rng,
342 cc->index_key, sizeof cc->index_key);
343 cc->hash = br_hmac_drbg_get_hash(&server_ctx->eng.rng);
344 cc->init_done = 1;
345 }
346 mask_id(cc, params->session_id, id);
347
348 /*
349 * Look for the node in the tree. If the same ID is already used,
350 * then reject it. This is a collision event, which should be
351 * exceedingly rare.
352 * Note: we do NOT record the emplacement here, because the
353 * removal of an entry may change the tree topology.
354 */
355 if (find_node(cc, id, NULL) != ADDR_NULL) {
356 return;
357 }
358
359 /*
360 * Find some room for the new parameters. If the cache is not
361 * full yet, add it to the end of the area and bump the pointer up.
362 * Otherwise, evict the list tail entry. Note that we already
363 * filtered out the case of a ridiculously small buffer that
364 * cannot hold any entry at all; thus, if there is no room for an
365 * extra entry, then the cache cannot be empty.
366 */
367 if (cc->store_ptr > (cc->store_len - LRU_ENTRY_LEN)) {
368 /*
369 * Evict tail. If the buffer has room for a single entry,
370 * then this may also be the head.
371 */
372 x = cc->tail;
373 cc->tail = get_prev(cc, x);
374 if (cc->tail == ADDR_NULL) {
375 cc->head = ADDR_NULL;
376 } else {
377 set_next(cc, cc->tail, ADDR_NULL);
378 }
379
380 /*
381 * Remove the node from the tree.
382 */
383 remove_node(cc, x);
384 } else {
385 /*
386 * Allocate room for new node.
387 */
388 x = cc->store_ptr;
389 cc->store_ptr += LRU_ENTRY_LEN;
390 }
391
392 /*
393 * Find the emplacement for the new node, and link it.
394 */
395 find_node(cc, id, &alx);
396 set_link(cc, alx, x);
397 set_left(cc, x, ADDR_NULL);
398 set_right(cc, x, ADDR_NULL);
399
400 /*
401 * New entry becomes new list head. It may also become the list
402 * tail if the cache was empty at that point.
403 */
404 if (cc->head == ADDR_NULL) {
405 cc->tail = x;
406 } else {
407 set_prev(cc, cc->head, x);
408 }
409 set_prev(cc, x, ADDR_NULL);
410 set_next(cc, x, cc->head);
411 cc->head = x;
412
413 /*
414 * Fill data in the entry.
415 */
416 memcpy(cc->store + x + SESSION_ID_OFF, id, SESSION_ID_LEN);
417 memcpy(cc->store + x + MASTER_SECRET_OFF,
418 params->master_secret, MASTER_SECRET_LEN);
419 br_enc16be(cc->store + x + VERSION_OFF, params->version);
420 br_enc16be(cc->store + x + CIPHER_SUITE_OFF, params->cipher_suite);
421 }
422
423 static int
424 lru_load(const br_ssl_session_cache_class **ctx,
425 br_ssl_server_context *server_ctx,
426 br_ssl_session_parameters *params)
427 {
428 br_ssl_session_cache_lru *cc;
429 unsigned char id[SESSION_ID_LEN];
430 uint32_t x;
431
432 (void)server_ctx;
433 cc = (br_ssl_session_cache_lru *)ctx;
434 if (!cc->init_done) {
435 return 0;
436 }
437 mask_id(cc, params->session_id, id);
438 x = find_node(cc, id, NULL);
439 if (x != ADDR_NULL) {
440 params->version = br_dec16be(
441 cc->store + x + VERSION_OFF);
442 params->cipher_suite = br_dec16be(
443 cc->store + x + CIPHER_SUITE_OFF);
444 memcpy(params->master_secret,
445 cc->store + x + MASTER_SECRET_OFF,
446 MASTER_SECRET_LEN);
447 if (x != cc->head) {
448 /*
449 * Found node is not at list head, so move
450 * it to the head.
451 */
452 uint32_t p, n;
453
454 p = get_prev(cc, x);
455 n = get_next(cc, x);
456 set_next(cc, p, n);
457 if (n == ADDR_NULL) {
458 cc->tail = p;
459 } else {
460 set_prev(cc, n, p);
461 }
462 set_prev(cc, cc->head, x);
463 set_next(cc, x, cc->head);
464 set_prev(cc, x, ADDR_NULL);
465 cc->head = x;
466 }
467 return 1;
468 }
469 return 0;
470 }
471
472 static const br_ssl_session_cache_class lru_class = {
473 sizeof(br_ssl_session_cache_lru),
474 &lru_save,
475 &lru_load
476 };
477
478 /* see inner.h */
479 void
480 br_ssl_session_cache_lru_init(br_ssl_session_cache_lru *cc,
481 unsigned char *store, size_t store_len)
482 {
483 cc->vtable = &lru_class;
484 cc->store = store;
485 cc->store_len = store_len;
486 cc->store_ptr = 0;
487 cc->init_done = 0;
488 cc->head = ADDR_NULL;
489 cc->tail = ADDR_NULL;
490 cc->root = ADDR_NULL;
491 }