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