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