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