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