xref: /netbsd-src/external/ibm-public/postfix/dist/src/tls/tls_scache.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: tls_scache.c,v 1.3 2020/03/18 19:05:21 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 
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 
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(bin_data, hex_data, hex_data_len) == 0) {
261 	msg_warn("%s TLS cache: malformed entry for %s: %.100s",
262 		 cp->cache_label, cache_id, hex_data);
263 	FREE_AND_RETURN(bin_data, 0);
264     }
265     entry = (TLS_SCACHE_ENTRY *) STR(bin_data);
266 
267     /*
268      * Logging.
269      */
270     if (cp->verbose)
271 	msg_info("read %s TLS cache entry %s: time=%ld [data %ld bytes]",
272 		 cp->cache_label, cache_id, (long) entry->timestamp,
273 	      (long) (LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session)));
274 
275     /*
276      * Other mandatory restrictions.
277      */
278     if (entry->timestamp + cp->timeout < time((time_t *) 0))
279 	FREE_AND_RETURN(bin_data, 0);
280 
281     /*
282      * Optional output.
283      */
284     if (out_session != 0)
285 	vstring_memcpy(out_session, entry->session,
286 		       LEN(bin_data) - offsetof(TLS_SCACHE_ENTRY, session));
287 
288     /*
289      * Clean up.
290      */
291     FREE_AND_RETURN(bin_data, 1);
292 }
293 
294 /* tls_scache_lookup - load session from cache */
295 
296 int     tls_scache_lookup(TLS_SCACHE *cp, const char *cache_id,
297 			          VSTRING *session)
298 {
299     const char *hex_data;
300 
301     /*
302      * Logging.
303      */
304     if (cp->verbose)
305 	msg_info("lookup %s session id=%s", cp->cache_label, cache_id);
306 
307     /*
308      * Initialize. Don't leak data.
309      */
310     if (session)
311 	VSTRING_RESET(session);
312 
313     /*
314      * Search the cache database.
315      */
316     if ((hex_data = dict_get(cp->db, cache_id)) == 0)
317 	return (0);
318 
319     /*
320      * Decode entry and delete if expired or malformed.
321      */
322     if (tls_scache_decode(cp, cache_id, hex_data, strlen(hex_data),
323 			  session) == 0) {
324 	tls_scache_delete(cp, cache_id);
325 	return (0);
326     } else {
327 	return (1);
328     }
329 }
330 
331 /* tls_scache_update - save session to cache */
332 
333 int     tls_scache_update(TLS_SCACHE *cp, const char *cache_id,
334 			          const char *buf, ssize_t len)
335 {
336     VSTRING *hex_data;
337 
338     /*
339      * Logging.
340      */
341     if (cp->verbose)
342 	msg_info("put %s session id=%s [data %ld bytes]",
343 		 cp->cache_label, cache_id, (long) len);
344 
345     /*
346      * Encode the cache entry.
347      */
348     hex_data = tls_scache_encode(cp, cache_id, buf, len);
349 
350     /*
351      * Store the cache entry.
352      *
353      * XXX Berkeley DB supports huge database keys and values. SDBM seems to
354      * have a finite limit, and DBM simply can't be used at all.
355      */
356     dict_put(cp->db, cache_id, STR(hex_data));
357 
358     /*
359      * Clean up.
360      */
361     vstring_free(hex_data);
362 
363     return (1);
364 }
365 
366 /* tls_scache_sequence - get first/next TLS session cache entry */
367 
368 int     tls_scache_sequence(TLS_SCACHE *cp, int first_next,
369 			            char **out_cache_id,
370 			            VSTRING *out_session)
371 {
372     const char *member;
373     const char *value;
374     char   *saved_cursor;
375     int     found_entry;
376     int     keep_entry;
377     char   *saved_member;
378 
379     /*
380      * XXX Deleting entries while enumerating a map can he tricky. Some map
381      * types have a concept of cursor and support a "delete the current
382      * element" operation. Some map types without cursors don't behave well
383      * when the current first/next entry is deleted (example: with Berkeley
384      * DB < 2, the "next" operation produces garbage). To avoid trouble, we
385      * delete an expired entry after advancing the current first/next
386      * position beyond it, and ignore client requests to delete the current
387      * entry.
388      */
389 
390     /*
391      * Find the first or next database entry. Activate the passivated entry
392      * and check the time stamp. Schedule the entry for deletion if it is too
393      * old.
394      *
395      * Save the member (cache id) so that it will not be clobbered by the
396      * tls_scache_lookup() call below.
397      */
398     found_entry = (dict_seq(cp->db, first_next, &member, &value) == 0);
399     if (found_entry) {
400 	keep_entry = tls_scache_decode(cp, member, value, strlen(value),
401 				       out_session);
402 	if (keep_entry && out_cache_id)
403 	    *out_cache_id = mystrdup(member);
404 	saved_member = mystrdup(member);
405     }
406 
407     /*
408      * Delete behind. This is a no-op if an expired cache entry was updated
409      * in the mean time. Use the saved lookup criteria so that the "delete
410      * behind" operation works as promised.
411      *
412      * The delete-behind strategy assumes that all updates are made by a single
413      * process. Otherwise, delete-behind may remove an entry that was updated
414      * after it was scheduled for deletion.
415      */
416     if (cp->flags & TLS_SCACHE_FLAG_DEL_SAVED_CURSOR) {
417 	cp->flags &= ~TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
418 	saved_cursor = cp->saved_cursor;
419 	cp->saved_cursor = 0;
420 	tls_scache_lookup(cp, saved_cursor, (VSTRING *) 0);
421 	myfree(saved_cursor);
422     }
423 
424     /*
425      * Otherwise, clean up if this is not the first iteration.
426      */
427     else {
428 	if (cp->saved_cursor)
429 	    myfree(cp->saved_cursor);
430 	cp->saved_cursor = 0;
431     }
432 
433     /*
434      * Protect the current first/next entry against explicit or implied
435      * client delete requests, and schedule a bad or expired entry for
436      * deletion. Save the lookup criteria so that the "delete behind"
437      * operation will work as promised.
438      */
439     if (found_entry) {
440 	cp->saved_cursor = saved_member;
441 	if (keep_entry == 0)
442 	    cp->flags |= TLS_SCACHE_FLAG_DEL_SAVED_CURSOR;
443     }
444     return (found_entry);
445 }
446 
447 /* tls_scache_delete - delete session from cache */
448 
449 int     tls_scache_delete(TLS_SCACHE *cp, const char *cache_id)
450 {
451 
452     /*
453      * Logging.
454      */
455     if (cp->verbose)
456 	msg_info("delete %s session id=%s", cp->cache_label, cache_id);
457 
458     /*
459      * Do it, unless we would delete the current first/next entry. Some map
460      * types don't have cursors, and some of those don't behave when the
461      * "current" entry is deleted.
462      */
463     return ((cp->saved_cursor != 0 && strcmp(cp->saved_cursor, cache_id) == 0)
464 	    || dict_del(cp->db, cache_id) == 0);
465 }
466 
467 /* tls_scache_open - open TLS session cache file */
468 
469 TLS_SCACHE *tls_scache_open(const char *dbname, const char *cache_label,
470 			            int verbose, int timeout)
471 {
472     TLS_SCACHE *cp;
473     DICT   *dict;
474 
475     /*
476      * Logging.
477      */
478     if (verbose)
479 	msg_info("open %s TLS cache %s", cache_label, dbname);
480 
481     /*
482      * Open the dictionary with O_TRUNC, so that we never have to worry about
483      * opening a damaged file after some process terminated abnormally.
484      */
485 #ifdef SINGLE_UPDATER
486 #define DICT_FLAGS (DICT_FLAG_DUP_REPLACE | DICT_FLAG_OPEN_LOCK \
487 		    | DICT_FLAG_UTF8_REQUEST)
488 #else
489 #define DICT_FLAGS \
490 	(DICT_FLAG_DUP_REPLACE | DICT_FLAG_LOCK | DICT_FLAG_SYNC_UPDATE \
491 	 | DICT_FLAG_UTF8_REQUEST)
492 #endif
493 
494     dict = dict_open(dbname, O_RDWR | O_CREAT | O_TRUNC, DICT_FLAGS);
495 
496     /*
497      * Sanity checks.
498      */
499     if (dict->update == 0)
500 	msg_fatal("dictionary %s does not support update operations", dbname);
501     if (dict->delete == 0)
502 	msg_fatal("dictionary %s does not support delete operations", dbname);
503     if (dict->sequence == 0)
504 	msg_fatal("dictionary %s does not support sequence operations", dbname);
505 
506     /*
507      * Create the TLS_SCACHE object.
508      */
509     cp = (TLS_SCACHE *) mymalloc(sizeof(*cp));
510     cp->flags = 0;
511     cp->db = dict;
512     cp->cache_label = mystrdup(cache_label);
513     cp->verbose = verbose;
514     cp->timeout = timeout;
515     cp->saved_cursor = 0;
516 
517     return (cp);
518 }
519 
520 /* tls_scache_close - close TLS session cache file */
521 
522 void    tls_scache_close(TLS_SCACHE *cp)
523 {
524 
525     /*
526      * Logging.
527      */
528     if (cp->verbose)
529 	msg_info("close %s TLS cache %s", cp->cache_label, cp->db->name);
530 
531     /*
532      * Destroy the TLS_SCACHE object.
533      */
534     dict_close(cp->db);
535     myfree(cp->cache_label);
536     if (cp->saved_cursor)
537 	myfree(cp->saved_cursor);
538     myfree((void *) cp);
539 }
540 
541 /* tls_scache_key - find session ticket key for given key name */
542 
543 TLS_TICKET_KEY *tls_scache_key(unsigned char *keyname, time_t now, int timeout)
544 {
545     int     i;
546 
547     /*
548      * The keys array contains 2 elements, the current signing key and the
549      * previous key.
550      *
551      * When name == 0 we are issuing a ticket, otherwise decrypting an existing
552      * ticket with the given key name.  For new tickets we always use the
553      * current key if unexpired.  For existing tickets, we use either the
554      * current or previous key with a validation expiration that is timeout
555      * longer than the signing expiration.
556      */
557     if (keyname) {
558 	for (i = 0; i < 2 && keys[i]; ++i) {
559 	    if (memcmp(keyname, keys[i]->name, TLS_TICKET_NAMELEN) == 0) {
560 		if (timecmp(keys[i]->tout + timeout, now) > 0)
561 		    return (keys[i]);
562 		break;
563 	    }
564 	}
565     } else if (keys[0]) {
566 	if (timecmp(keys[0]->tout, now) > 0)
567 	    return (keys[0]);
568     }
569     return (0);
570 }
571 
572 /* tls_scache_key_rotate - rotate session ticket keys */
573 
574 TLS_TICKET_KEY *tls_scache_key_rotate(TLS_TICKET_KEY *newkey)
575 {
576 
577     /*
578      * Allocate or re-use storage of retired key, then overwrite it, since
579      * caller's key data is ephemeral.
580      */
581     if (keys[1] == 0)
582 	keys[1] = (TLS_TICKET_KEY *) mymalloc(sizeof(*newkey));
583     *keys[1] = *newkey;
584     newkey = keys[1];
585 
586     /*
587      * Rotate if required, ensuring that the keys are sorted by expiration
588      * time with keys[0] expiring last.
589      */
590     if (keys[0] == 0 || keys[0]->tout < keys[1]->tout) {
591 	keys[1] = keys[0];
592 	keys[0] = newkey;
593     }
594     return (newkey);
595 }
596 
597 #endif
598