Home | History | Annotate | Line # | Download | only in tls
      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 
    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_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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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