1 /* $NetBSD: tls_scache.c,v 1.4 2022/10/08 16:12:50 christos Exp $ */
2
3 /*++
4 /* NAME
5 /* tls_scache 3
6 /* SUMMARY
7 /* TLS session cache manager
8 /* SYNOPSIS
9 /* #include <tls_scache.h>
10 /*
11 /* TLS_SCACHE *tls_scache_open(dbname, cache_label, verbose, timeout)
12 /* const char *dbname
13 /* const char *cache_label;
14 /* int verbose;
15 /* int timeout;
16 /*
17 /* void tls_scache_close(cache)
18 /* TLS_SCACHE *cache;
19 /*
20 /* int tls_scache_lookup(cache, cache_id, out_session)
21 /* TLS_SCACHE *cache;
22 /* const char *cache_id;
23 /* VSTRING *out_session;
24 /*
25 /* int tls_scache_update(cache, cache_id, session, session_len)
26 /* TLS_SCACHE *cache;
27 /* const char *cache_id;
28 /* const char *session;
29 /* ssize_t session_len;
30 /*
31 /* int tls_scache_sequence(cache, first_next, out_cache_id,
32 /* VSTRING *out_session)
33 /* TLS_SCACHE *cache;
34 /* int first_next;
35 /* char **out_cache_id;
36 /* VSTRING *out_session;
37 /*
38 /* int tls_scache_delete(cache, cache_id)
39 /* TLS_SCACHE *cache;
40 /* const char *cache_id;
41 /*
42 /* TLS_TICKET_KEY *tls_scache_key(keyname, now, timeout)
43 /* unsigned char *keyname;
44 /* time_t now;
45 /* int timeout;
46 /*
47 /* TLS_TICKET_KEY *tls_scache_key_rotate(newkey)
48 /* TLS_TICKET_KEY *newkey;
49 /* DESCRIPTION
50 /* This module maintains Postfix TLS session cache files.
51 /* each session is stored under a lookup key (hostname or
52 /* session ID).
53 /*
54 /* tls_scache_open() opens the specified TLS session cache
55 /* and returns a handle that must be used for subsequent
56 /* access.
57 /*
58 /* tls_scache_close() closes the specified TLS session cache
59 /* and releases memory that was allocated by tls_scache_open().
60 /*
61 /* tls_scache_lookup() looks up the specified session in the
62 /* specified cache, and applies session timeout restrictions.
63 /* Entries that are too old are silently deleted.
64 /*
65 /* tls_scache_update() updates the specified TLS session cache
66 /* with the specified session information.
67 /*
68 /* tls_scache_sequence() iterates over the specified TLS session
69 /* cache and either returns the first or next entry that has not
70 /* timed out, or returns no data. Entries that are too old are
71 /* silently deleted. Specify TLS_SCACHE_SEQUENCE_NOTHING as the
72 /* third and last argument to disable saving of cache entry
73 /* content or cache entry ID information. This is useful when
74 /* purging expired entries. A result value of zero means that
75 /* the end of the cache was reached.
76 /*
77 /* tls_scache_delete() removes the specified cache entry from
78 /* the specified TLS session cache.
79 /*
80 /* tls_scache_key() locates a TLS session ticket key in a 2-element
81 /* in-memory cache. A null result is returned if no unexpired matching
82 /* key is found.
83 /*
84 /* tls_scache_key_rotate() saves a TLS session tickets key in the
85 /* in-memory cache.
86 /*
87 /* Arguments:
88 /* .IP dbname
89 /* The base name of the session cache file.
90 /* .IP cache_label
91 /* A string that is used in logging and error messages.
92 /* .IP verbose
93 /* Do verbose logging of cache operations? (zero == no)
94 /* .IP timeout
95 /* The time after which a session cache entry is considered too old.
96 /* .IP first_next
97 /* One of DICT_SEQ_FUN_FIRST (first cache element) or DICT_SEQ_FUN_NEXT
98 /* (next cache element).
99 /* .IP cache_id
100 /* Session cache lookup key.
101 /* .IP session
102 /* Storage for session information.
103 /* .IP session_len
104 /* The size of the session information in bytes.
105 /* .IP out_cache_id
106 /* .IP out_session
107 /* Storage for saving the cache_id or session information of the
108 /* current cache entry.
109 /*
110 /* Specify TLS_SCACHE_DONT_NEED_CACHE_ID to avoid saving
111 /* the session cache ID of the cache entry.
112 /*
113 /* Specify TLS_SCACHE_DONT_NEED_SESSION to avoid
114 /* saving the session information in the cache entry.
115 /* .IP keyname
116 /* Is null when requesting the current encryption keys. Otherwise,
117 /* keyname is a pointer to an array of TLS_TICKET_NAMELEN unsigned
118 /* chars (not NUL terminated) that is an identifier for a key
119 /* previously used to encrypt a session ticket.
120 /* .IP now
121 /* Current epoch time passed by caller.
122 /* .IP timeout
123 /* TLS session ticket encryption lifetime.
124 /* .IP newkey
125 /* TLS session ticket key obtained from tlsmgr(8) to be added to
126 * internal cache.
127 /* DIAGNOSTICS
128 /* These routines terminate with a fatal run-time error
129 /* for unrecoverable database errors. This allows the
130 /* program to restart and reset the database to an
131 /* empty initial state.
132 /*
133 /* tls_scache_open() never returns on failure. All other
134 /* functions return non-zero on success, zero when the
135 /* operation could not be completed.
136 /* LICENSE
137 /* .ad
138 /* .fi
139 /* The Secure Mailer license must be distributed with this software.
140 /* AUTHOR(S)
141 /* Wietse Venema
142 /* IBM T.J. Watson Research
143 /* P.O. Box 704
144 /* Yorktown Heights, NY 10598, USA
145 /*--*/
146
147 /* System library. */
148
149 #include <sys_defs.h>
150
151 #ifdef USE_TLS
152
153 #include <string.h>
154 #include <stddef.h>
155
156 /* Utility library. */
157
158 #include <msg.h>
159 #include <dict.h>
160 #include <stringops.h>
161 #include <mymalloc.h>
162 #include <hex_code.h>
163 #include <myflock.h>
164 #include <vstring.h>
165 #include <timecmp.h>
166
167 /* Global library. */
168
169 /* TLS library. */
170
171 #include <tls_scache.h>
172
173 /* Application-specific. */
174
175 /*
176 * Session cache entry format.
177 */
178 typedef struct {
179 time_t timestamp; /* time when saved */
180 char session[1]; /* actually a bunch of bytes */
181 } TLS_SCACHE_ENTRY;
182
183 static TLS_TICKET_KEY *keys[2];
184
185 /*
186 * SLMs.
187 */
188 #define STR(x) vstring_str(x)
189 #define LEN(x) VSTRING_LEN(x)
190
191 /* tls_scache_encode - encode TLS session cache entry */
192
tls_scache_encode(TLS_SCACHE * cp,const char * cache_id,const char * session,ssize_t session_len)193 static VSTRING *tls_scache_encode(TLS_SCACHE *cp, const char *cache_id,
194 const char *session,
195 ssize_t session_len)
196 {
197 TLS_SCACHE_ENTRY *entry;
198 VSTRING *hex_data;
199 ssize_t binary_data_len;
200
201 /*
202 * Assemble the TLS session cache entry.
203 *
204 * We could eliminate some copying by using incremental encoding, but
205 * sessions are so small that it really does not matter.
206 */
207 binary_data_len = session_len + offsetof(TLS_SCACHE_ENTRY, session);
208 entry = (TLS_SCACHE_ENTRY *) mymalloc(binary_data_len);
209 entry->timestamp = time((time_t *) 0);
210 memcpy(entry->session, session, session_len);
211
212 /*
213 * Encode the TLS session cache entry.
214 */
215 hex_data = vstring_alloc(2 * binary_data_len + 1);
216 hex_encode(hex_data, (char *) entry, binary_data_len);
217
218 /*
219 * Logging.
220 */
221 if (cp->verbose)
222 msg_info("write %s TLS cache entry %s: time=%ld [data %ld bytes]",
223 cp->cache_label, cache_id, (long) entry->timestamp,
224 (long) session_len);
225
226 /*
227 * Clean up.
228 */
229 myfree((void *) entry);
230
231 return (hex_data);
232 }
233
234 /* tls_scache_decode - decode TLS session cache entry */
235
tls_scache_decode(TLS_SCACHE * cp,const char * cache_id,const char * hex_data,ssize_t hex_data_len,VSTRING * out_session)236 static int tls_scache_decode(TLS_SCACHE *cp, const char *cache_id,
237 const char *hex_data, ssize_t hex_data_len,
238 VSTRING *out_session)
239 {
240 TLS_SCACHE_ENTRY *entry;
241 VSTRING *bin_data;
242
243 /*
244 * Sanity check.
245 */
246 if (hex_data_len < 2 * (offsetof(TLS_SCACHE_ENTRY, session))) {
247 msg_warn("%s TLS cache: truncated entry for %s: %.100s",
248 cp->cache_label, cache_id, hex_data);
249 return (0);
250 }
251
252 /*
253 * Disassemble the TLS session cache entry.
254 *
255 * No early returns or we have a memory leak.
256 */
257 #define FREE_AND_RETURN(ptr, x) { vstring_free(ptr); return (x); }
258
259 bin_data = vstring_alloc(hex_data_len / 2 + 1);
260 if (hex_decode_opt(bin_data, hex_data, hex_data_len,
261 HEX_DECODE_FLAG_ALLOW_COLON) == 0) {
262 msg_warn("%s TLS cache: malformed entry for %s: %.100s",
263 cp->cache_label, cache_id, hex_data);
264 FREE_AND_RETURN(bin_data, 0);
265 }
266 entry = (TLS_SCACHE_ENTRY *) STR(bin_data);
267
268 /*
269 * Logging.
270 */
271 if (cp->verbose)
272 msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]",
273 cp->cache_label, cache_id, (long) entry->timestamp,
274 (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)));
275
276 /*
277 * Other mandatory restrictions.
278 */
279 if (entry->timestamp + cp->timeout < time((time_t *) 0))
280 FREE_AND_RETURN(bin_data, 0);
281
282 /*
283 * Optional output.
284 */
285 if (out_session != 0)
286 vstring_memcpy(out_session, entry->session,
287 LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
288
289 /*
290 * Clean up.
291 */
292 FREE_AND_RETURN(bin_data, 1);
293 }
294
295 /* tls_scache_lookup - load session from cache */
296
tls_scache_lookup(TLS_SCACHE * cp,const char * cache_id,VSTRING * session)297 int tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id,
298 VSTRING *session)
299 {
300 const char *hex_data;
301
302 /*
303 * Logging.
304 */
305 if (cp->verbose)
306 msg_info("lookup %s session id=%s", cp->cache_label, cache_id);
307
308 /*
309 * Initialize. Don't leak data.
310 */
311 if (session)
312 VSTRING_RESET(session);
313
314 /*
315 * Search the cache database.
316 */
317 if ((hex_data = dict_get(cp->db, cache_id)) == 0)
318 return (0);
319
320 /*
321 * Decode entry and delete if expired or malformed.
322 */
323 if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data),
324 session) == 0) {
325 tls_scache_delete(cp, cache_id);
326 return (0);
327 } else {
328 return (1);
329 }
330 }
331
332 /* tls_scache_update - save session to cache */
333
tls_scache_update(TLS_SCACHE * cp,const char * cache_id,const char * buf,ssize_t len)334 int tls_scache_update(TLS_SCACHE *cp, const char *cache_id,
335 const char *buf, ssize_t len)
336 {
337 VSTRING *hex_data;
338
339 /*
340 * Logging.
341 */
342 if (cp->verbose)
343 msg_info("put %s session id=%s [data %ld bytes]",
344 cp->cache_label, cache_id, (long) len);
345
346 /*
347 * Encode the cache entry.
348 */
349 hex_data = tls_scache_encode(cp, cache_id, buf, len);
350
351 /*
352 * Store the cache entry.
353 *
354 * XXX Berkeley DB supports huge database keys and values. SDBM seems to
355 * have a finite limit, and DBM simply can't be used at all.
356 */
357 dict_put(cp->db, cache_id, STR(hex_data));
358
359 /*
360 * Clean up.
361 */
362 vstring_free(hex_data);
363
364 return (1);
365 }
366
367 /* tls_scache_sequence - get first/next TLS session cache entry */
368
tls_scache_sequence(TLS_SCACHE * cp,int first_next,char ** out_cache_id,VSTRING * out_session)369 int tls_scache_sequence(TLS_SCACHE *cp, int first_next,
370 char **out_cache_id,
371 VSTRING *out_session)
372 {
373 const char *member;
374 const char *value;
375 char *saved_cursor;
376 int found_entry;
377 int keep_entry;
378 char *saved_member;
379
380 /*
381 * XXX Deleting entries while enumerating a map can he tricky. Some map
382 * types have a concept of cursor and support a "delete the current
383 * element" operation. Some map types without cursors don't behave well
384 * when the current first/next entry is deleted (example: with Berkeley
385 * DB < 2, the "next" operation produces garbage). To avoid trouble, we
386 * delete an expired entry after advancing the current first/next
387 * position beyond it, and ignore client requests to delete the current
388 * entry.
389 */
390
391 /*
392 * Find the first or next database entry. Activate the passivated entry
393 * and check the time stamp. Schedule the entry for deletion if it is too
394 * old.
395 *
396 * Save the member (cache id) so that it will not be clobbered by the
397 * tls_scache_lookup() call below.
398 */
399 found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0);
400 if (found_entry) {
401 keep_entry = tls_scache_decode(cp, member, value, strlen(value),
402 out_session);
403 if (keep_entry && out_cache_id)
404 *out_cache_id = mystrdup(member);
405 saved_member = mystrdup(member);
406 }
407
408 /*
409 * Delete behind. This is a no-op if an expired cache entry was updated
410 * in the mean time. Use the saved lookup criteria so that the "delete
411 * behind" operation works as promised.
412 *
413 * The delete-behind strategy assumes that all updates are made by a single
414 * process. Otherwise, delete-behind may remove an entry that was updated
415 * after it was scheduled for deletion.
416 */
417 if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) {
418 cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
419 saved_cursor = cp->saved_cursor;
420 cp->saved_cursor = 0;
421 tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0);
422 myfree(saved_cursor);
423 }
424
425 /*
426 * Otherwise, clean up if this is not the first iteration.
427 */
428 else {
429 if (cp->saved_cursor)
430 myfree(cp->saved_cursor);
431 cp->saved_cursor = 0;
432 }
433
434 /*
435 * Protect the current first/next entry against explicit or implied
436 * client delete requests, and schedule a bad or expired entry for
437 * deletion. Save the lookup criteria so that the "delete behind"
438 * operation will work as promised.
439 */
440 if (found_entry) {
441 cp->saved_cursor = saved_member;
442 if (keep_entry == 0)
443 cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
444 }
445 return (found_entry);
446 }
447
448 /* tls_scache_delete - delete session from cache */
449
tls_scache_delete(TLS_SCACHE * cp,const char * cache_id)450 int tls_scache_delete(TLS_SCACHE *cp, const char *cache_id)
451 {
452
453 /*
454 * Logging.
455 */
456 if (cp->verbose)
457 msg_info("delete %s session id=%s", cp->cache_label, cache_id);
458
459 /*
460 * Do it, unless we would delete the current first/next entry. Some map
461 * types don't have cursors, and some of those don't behave when the
462 * "current" entry is deleted.
463 */
464 return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0)
465 || dict_del(cp->db, cache_id) == 0);
466 }
467
468 /* tls_scache_open - open TLS session cache file */
469
tls_scache_open(const char * dbname,const char * cache_label,int verbose,int timeout)470 TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label,
471 int verbose, int timeout)
472 {
473 TLS_SCACHE *cp;
474 DICT *dict;
475
476 /*
477 * Logging.
478 */
479 if (verbose)
480 msg_info("open %s TLS cache %s", cache_label, dbname);
481
482 /*
483 * Open the dictionary with O_TRUNC, so that we never have to worry about
484 * opening a damaged file after some process terminated abnormally.
485 */
486 #define DICT_FLAGS \
487 (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK | DICT_FLAG_SYNC_UPDATE \
488 | DICT_FLAG_UTF8_REQUEST)
489
490 dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS);
491
492 /*
493 * Sanity checks.
494 */
495 if (dict->update == 0)
496 msg_fatal("dictionary %s does not support update operations", dbname);
497 if (dict->delete == 0)
498 msg_fatal("dictionary %s does not support delete operations", dbname);
499 if (dict->sequence == 0)
500 msg_fatal("dictionary %s does not support sequence operations", dbname);
501
502 /*
503 * Create the TLS_SCACHE object.
504 */
505 cp = (TLS_SCACHE *) mymalloc(sizeof(*cp));
506 cp->flags = 0;
507 cp->db = dict;
508 cp->cache_label = mystrdup(cache_label);
509 cp->verbose = verbose;
510 cp->timeout = timeout;
511 cp->saved_cursor = 0;
512
513 return (cp);
514 }
515
516 /* tls_scache_close - close TLS session cache file */
517
tls_scache_close(TLS_SCACHE * cp)518 void tls_scache_close(TLS_SCACHE *cp)
519 {
520
521 /*
522 * Logging.
523 */
524 if (cp->verbose)
525 msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name);
526
527 /*
528 * Destroy the TLS_SCACHE object.
529 */
530 dict_close(cp->db);
531 myfree(cp->cache_label);
532 if (cp->saved_cursor)
533 myfree(cp->saved_cursor);
534 myfree((void *) cp);
535 }
536
537 /* tls_scache_key - find session ticket key for given key name */
538
tls_scache_key(unsigned char * keyname,time_t now,int timeout)539 TLS_TICKET_KEY *tls_scache_key(unsigned char *keyname, time_t now, int timeout)
540 {
541 int i;
542
543 /*
544 * The keys array contains 2 elements, the current signing key and the
545 * previous key.
546 *
547 * When name == 0 we are issuing a ticket, otherwise decrypting an existing
548 * ticket with the given key name. For new tickets we always use the
549 * current key if unexpired. For existing tickets, we use either the
550 * current or previous key with a validation expiration that is timeout
551 * longer than the signing expiration.
552 */
553 if (keyname) {
554 for (i = 0; i < 2 && keys[i]; ++i) {
555 if (memcmp(keyname, keys[i]->name, TLS_TICKET_NAMELEN) == 0) {
556 if (timecmp(keys[i]->tout + timeout, now) > 0)
557 return (keys[i]);
558 break;
559 }
560 }
561 } else if (keys[0]) {
562 if (timecmp(keys[0]->tout, now) > 0)
563 return (keys[0]);
564 }
565 return (0);
566 }
567
568 /* tls_scache_key_rotate - rotate session ticket keys */
569
tls_scache_key_rotate(TLS_TICKET_KEY * newkey)570 TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *newkey)
571 {
572
573 /*
574 * Allocate or re-use storage of retired key, then overwrite it, since
575 * caller's key data is ephemeral.
576 */
577 if (keys[1] == 0)
578 keys[1] = (TLS_TICKET_KEY *) mymalloc(sizeof(*newkey));
579 *keys[1] = *newkey;
580 newkey = keys[1];
581
582 /*
583 * Rotate if required, ensuring that the keys are sorted by expiration
584 * time with keys[0] expiring last.
585 */
586 if (keys[0] == 0 || keys[0]->tout < keys[1]->tout) {
587 keys[1] = keys[0];
588 keys[0] = newkey;
589 }
590 return (newkey);
591 }
592
593 #endif
594