1 /* 2 * Copyright 2023-2026 The OpenSSL Project Authors. All Rights Reserved. 3 * 4 * Licensed under the Apache License 2.0 (the "License"). You may not use 5 * this file except in compliance with the License. You can obtain a copy 6 * in the file LICENSE in the source distribution or at 7 * https://www.openssl.org/source/license.html 8 */ 9 10 #include "internal/quic_srtm.h" 11 #include "internal/common.h" 12 #include <openssl/lhash.h> 13 #include <openssl/core_names.h> 14 #include <openssl/rand.h> 15 16 /* 17 * QUIC Stateless Reset Token Manager 18 * ================================== 19 */ 20 typedef struct srtm_item_st SRTM_ITEM; 21 22 #define BLINDED_SRT_LEN 16 23 24 DEFINE_LHASH_OF_EX(SRTM_ITEM); 25 26 /* 27 * The SRTM is implemented using two LHASH instances, one matching opaque pointers to 28 * an item structure, and another matching a SRT-derived value to an item 29 * structure. Multiple items with different seq_num values under a given opaque, 30 * and duplicate SRTs, are handled using sorted singly-linked lists. 31 * 32 * The O(n) insert and lookup performance is tolerated on the basis that the 33 * total number of entries for a given opaque (total number of extant CIDs for a 34 * connection) should be quite small, and the QUIC protocol allows us to place a 35 * hard limit on this via the active_connection_id_limit TPARAM. Thus there is 36 * no risk of a large number of SRTs needing to be registered under a given 37 * opaque. 38 * 39 * It is expected one SRTM will exist per QUIC_PORT and track all SRTs across 40 * all connections for that QUIC_PORT. 41 */ 42 struct srtm_item_st { 43 SRTM_ITEM *next_by_srt_blinded; /* SORT BY opaque DESC */ 44 SRTM_ITEM *next_by_seq_num; /* SORT BY seq_num DESC */ 45 void *opaque; /* \__ unique identity for item */ 46 uint64_t seq_num; /* / */ 47 QUIC_STATELESS_RESET_TOKEN srt; 48 unsigned char srt_blinded[BLINDED_SRT_LEN]; /* H(srt) */ 49 50 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 51 uint32_t debug_token; 52 #endif 53 }; 54 55 struct quic_srtm_st { 56 /* Crypto context used to calculate blinded SRTs H(srt). */ 57 EVP_CIPHER_CTX *blind_ctx; /* kept with key */ 58 59 LHASH_OF(SRTM_ITEM) *items_fwd; /* (opaque) -> SRTM_ITEM */ 60 LHASH_OF(SRTM_ITEM) *items_rev; /* (H(srt)) -> SRTM_ITEM */ 61 62 /* 63 * Monotonically transitions to 1 in event of allocation failure. The only 64 * valid operation on such an object is to free it. 65 */ 66 unsigned int alloc_failed : 1; 67 }; 68 69 static unsigned long items_fwd_hash(const SRTM_ITEM *item) 70 { 71 return (unsigned long)(uintptr_t)item->opaque; 72 } 73 74 static int items_fwd_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b) 75 { 76 return a->opaque != b->opaque; 77 } 78 79 static unsigned long items_rev_hash(const SRTM_ITEM *item) 80 { 81 /* 82 * srt_blinded has already been through a crypto-grade hash function, so we 83 * can just use bits from that. 84 */ 85 unsigned long l; 86 87 memcpy(&l, item->srt_blinded, sizeof(l)); 88 return l; 89 } 90 91 static int items_rev_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b) 92 { 93 /* 94 * We don't need to use CRYPTO_memcmp here as the relationship of 95 * srt_blinded to srt is already cryptographically obfuscated. 96 */ 97 return memcmp(a->srt_blinded, b->srt_blinded, sizeof(a->srt_blinded)); 98 } 99 100 static int srtm_check_lh(QUIC_SRTM *srtm, LHASH_OF(SRTM_ITEM) *lh) 101 { 102 if (lh_SRTM_ITEM_error(lh)) { 103 srtm->alloc_failed = 1; 104 return 0; 105 } 106 107 return 1; 108 } 109 110 QUIC_SRTM *ossl_quic_srtm_new(OSSL_LIB_CTX *libctx, const char *propq) 111 { 112 QUIC_SRTM *srtm = NULL; 113 unsigned char key[16]; 114 EVP_CIPHER *ecb = NULL; 115 116 if (RAND_priv_bytes_ex(libctx, key, sizeof(key), sizeof(key) * 8) != 1) 117 goto err; 118 119 if ((srtm = OPENSSL_zalloc(sizeof(*srtm))) == NULL) 120 return NULL; 121 122 /* Use AES-128-ECB as a permutation over 128-bit SRTs. */ 123 if ((ecb = EVP_CIPHER_fetch(libctx, "AES-128-ECB", propq)) == NULL) 124 goto err; 125 126 if ((srtm->blind_ctx = EVP_CIPHER_CTX_new()) == NULL) 127 goto err; 128 129 if (!EVP_EncryptInit_ex2(srtm->blind_ctx, ecb, key, NULL, NULL)) 130 goto err; 131 132 EVP_CIPHER_free(ecb); 133 ecb = NULL; 134 135 /* Create mappings. */ 136 if ((srtm->items_fwd = lh_SRTM_ITEM_new(items_fwd_hash, items_fwd_cmp)) == NULL 137 || (srtm->items_rev = lh_SRTM_ITEM_new(items_rev_hash, items_rev_cmp)) == NULL) 138 goto err; 139 140 return srtm; 141 142 err: 143 /* 144 * No cleansing of key needed as blinding exists only for side channel 145 * mitigation. 146 */ 147 ossl_quic_srtm_free(srtm); 148 EVP_CIPHER_free(ecb); 149 return NULL; 150 } 151 152 static void srtm_free_each(SRTM_ITEM *ihead) 153 { 154 SRTM_ITEM *inext, *item = ihead; 155 156 for (item = item->next_by_seq_num; item != NULL; item = inext) { 157 inext = item->next_by_seq_num; 158 OPENSSL_free(item); 159 } 160 161 OPENSSL_free(ihead); 162 } 163 164 void ossl_quic_srtm_free(QUIC_SRTM *srtm) 165 { 166 if (srtm == NULL) 167 return; 168 169 lh_SRTM_ITEM_free(srtm->items_rev); 170 if (srtm->items_fwd != NULL) { 171 /* 172 * We don't need to call lh_SRTM_ITEM_set_down_load(..., 0) 173 * here because srtm_free_each() callback for _doall() does 174 * not call to lh_SRTIM_ITEM_delete(). 175 */ 176 lh_SRTM_ITEM_doall(srtm->items_fwd, srtm_free_each); 177 lh_SRTM_ITEM_free(srtm->items_fwd); 178 } 179 180 EVP_CIPHER_CTX_free(srtm->blind_ctx); 181 OPENSSL_free(srtm); 182 } 183 184 /* 185 * Find a SRTM_ITEM by (opaque, seq_num). Returns NULL if no match. 186 * If head is non-NULL, writes the head of the relevant opaque list to *head if 187 * there is one. 188 * If prev is non-NULL, writes the previous node to *prev or NULL if it is 189 * the first item. 190 */ 191 static SRTM_ITEM *srtm_find(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num, 192 SRTM_ITEM **head_p, SRTM_ITEM **prev_p) 193 { 194 SRTM_ITEM key, *item = NULL, *prev = NULL; 195 196 key.opaque = opaque; 197 198 item = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key); 199 if (head_p != NULL) 200 *head_p = item; 201 202 for (; item != NULL; prev = item, item = item->next_by_seq_num) 203 if (item->seq_num == seq_num) { 204 break; 205 } else if (item->seq_num < seq_num) { 206 /* 207 * List is sorted in descending order so there can't be any match 208 * after this. 209 */ 210 item = NULL; 211 break; 212 } 213 214 if (prev_p != NULL) 215 *prev_p = prev; 216 217 return item; 218 } 219 220 /* 221 * Inserts a SRTM_ITEM into the singly-linked by-sequence-number linked list. 222 * The new head pointer is written to *new_head (which may or may not be 223 * unchanged). 224 */ 225 static void sorted_insert_seq_num(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head) 226 { 227 uint64_t seq_num = item->seq_num; 228 SRTM_ITEM *cur = head, **fixup = new_head; 229 230 *new_head = head; 231 232 while (cur != NULL && cur->seq_num > seq_num) { 233 fixup = &cur->next_by_seq_num; 234 cur = cur->next_by_seq_num; 235 } 236 237 item->next_by_seq_num = *fixup; 238 *fixup = item; 239 } 240 241 /* 242 * Inserts a SRTM_ITEM into the singly-linked by-SRT list. 243 * The new head pointer is written to *new_head (which may or may not be 244 * unchanged). 245 */ 246 static void sorted_insert_srt(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head) 247 { 248 uintptr_t opaque = (uintptr_t)item->opaque; 249 SRTM_ITEM *cur = head, **fixup = new_head; 250 251 *new_head = head; 252 253 while (cur != NULL && (uintptr_t)cur->opaque > opaque) { 254 fixup = &cur->next_by_srt_blinded; 255 cur = cur->next_by_srt_blinded; 256 } 257 258 item->next_by_srt_blinded = *fixup; 259 *fixup = item; 260 } 261 262 /* 263 * Computes the blinded SRT value used for internal lookup for side channel 264 * mitigation purposes. We compute this once as a cached value when an SRTM_ITEM 265 * is formed. 266 */ 267 static int srtm_compute_blinded(QUIC_SRTM *srtm, SRTM_ITEM *item, 268 const QUIC_STATELESS_RESET_TOKEN *token) 269 { 270 int outl = 0; 271 272 /* 273 * We use AES-128-ECB as a permutation using a random key to facilitate 274 * blinding for side-channel purposes. Encrypt the token as a single AES 275 * block. 276 */ 277 if (!EVP_EncryptUpdate(srtm->blind_ctx, item->srt_blinded, &outl, 278 (const unsigned char *)token, sizeof(*token))) 279 return 0; 280 281 if (!ossl_assert(outl == sizeof(*token))) 282 return 0; 283 284 return 1; 285 } 286 287 int ossl_quic_srtm_add(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num, 288 const QUIC_STATELESS_RESET_TOKEN *token) 289 { 290 SRTM_ITEM *item = NULL, *head = NULL, *new_head, *r_item; 291 292 if (srtm->alloc_failed) 293 return 0; 294 295 /* (opaque, seq_num) duplicates not allowed */ 296 if ((item = srtm_find(srtm, opaque, seq_num, &head, NULL)) != NULL) 297 return 0; 298 299 if ((item = OPENSSL_zalloc(sizeof(*item))) == NULL) 300 return 0; 301 302 item->opaque = opaque; 303 item->seq_num = seq_num; 304 item->srt = *token; 305 if (!srtm_compute_blinded(srtm, item, &item->srt)) { 306 OPENSSL_free(item); 307 return 0; 308 } 309 310 /* Add to forward mapping. */ 311 if (head == NULL) { 312 /* First item under this opaque */ 313 lh_SRTM_ITEM_insert(srtm->items_fwd, item); 314 if (!srtm_check_lh(srtm, srtm->items_fwd)) { 315 OPENSSL_free(item); 316 return 0; 317 } 318 } else { 319 sorted_insert_seq_num(head, item, &new_head); 320 if (new_head != head) { /* head changed, update in lhash */ 321 lh_SRTM_ITEM_insert(srtm->items_fwd, new_head); 322 if (!srtm_check_lh(srtm, srtm->items_fwd)) { 323 OPENSSL_free(item); 324 return 0; 325 } 326 } 327 } 328 329 /* Add to reverse mapping. */ 330 r_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item); 331 if (r_item == NULL) { 332 /* First item under this blinded SRT */ 333 lh_SRTM_ITEM_insert(srtm->items_rev, item); 334 if (!srtm_check_lh(srtm, srtm->items_rev)) 335 /* 336 * Can't free the item now as we would have to undo the insertion 337 * into the forward mapping which would require an insert operation 338 * to restore the previous value. which might also fail. However, 339 * the item will be freed OK when we free the entire SRTM. 340 */ 341 return 0; 342 } else { 343 sorted_insert_srt(r_item, item, &new_head); 344 if (new_head != r_item) { /* head changed, update in lhash */ 345 lh_SRTM_ITEM_insert(srtm->items_rev, new_head); 346 if (!srtm_check_lh(srtm, srtm->items_rev)) 347 /* As above. */ 348 return 0; 349 } 350 } 351 352 return 1; 353 } 354 355 /* Remove item from reverse mapping. */ 356 static int srtm_remove_from_rev(QUIC_SRTM *srtm, SRTM_ITEM *item) 357 { 358 SRTM_ITEM *rh_item; 359 360 rh_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item); 361 assert(rh_item != NULL); 362 if (rh_item == item) { 363 /* 364 * Change lhash to point to item after this one, or remove the entry if 365 * this is the last one. 366 */ 367 if (item->next_by_srt_blinded != NULL) { 368 lh_SRTM_ITEM_insert(srtm->items_rev, item->next_by_srt_blinded); 369 if (!srtm_check_lh(srtm, srtm->items_rev)) 370 return 0; 371 } else { 372 lh_SRTM_ITEM_delete(srtm->items_rev, item); 373 } 374 } else { 375 /* Find our entry in the SRT list */ 376 for (; rh_item->next_by_srt_blinded != item; 377 rh_item = rh_item->next_by_srt_blinded) 378 ; 379 rh_item->next_by_srt_blinded = item->next_by_srt_blinded; 380 } 381 382 return 1; 383 } 384 385 int ossl_quic_srtm_remove(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num) 386 { 387 SRTM_ITEM *item, *prev = NULL; 388 389 if (srtm->alloc_failed) 390 return 0; 391 392 if ((item = srtm_find(srtm, opaque, seq_num, NULL, &prev)) == NULL) 393 /* No match */ 394 return 0; 395 396 /* Remove from forward mapping. */ 397 if (prev == NULL) { 398 /* 399 * Change lhash to point to item after this one, or remove the entry if 400 * this is the last one. 401 */ 402 if (item->next_by_seq_num != NULL) { 403 lh_SRTM_ITEM_insert(srtm->items_fwd, item->next_by_seq_num); 404 if (!srtm_check_lh(srtm, srtm->items_fwd)) 405 return 0; 406 } else { 407 lh_SRTM_ITEM_delete(srtm->items_fwd, item); 408 } 409 } else { 410 prev->next_by_seq_num = item->next_by_seq_num; 411 } 412 413 /* Remove from reverse mapping. */ 414 if (!srtm_remove_from_rev(srtm, item)) 415 return 0; 416 417 OPENSSL_free(item); 418 return 1; 419 } 420 421 int ossl_quic_srtm_cull(QUIC_SRTM *srtm, void *opaque) 422 { 423 SRTM_ITEM key, *item = NULL, *inext, *ihead; 424 425 key.opaque = opaque; 426 427 if (srtm->alloc_failed) 428 return 0; 429 430 if ((ihead = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key)) == NULL) 431 return 1; /* nothing removed is a success condition */ 432 433 for (item = ihead; item != NULL; item = inext) { 434 inext = item->next_by_seq_num; 435 if (item != ihead) { 436 srtm_remove_from_rev(srtm, item); 437 OPENSSL_free(item); 438 } 439 } 440 441 lh_SRTM_ITEM_delete(srtm->items_fwd, ihead); 442 srtm_remove_from_rev(srtm, ihead); 443 OPENSSL_free(ihead); 444 return 1; 445 } 446 447 int ossl_quic_srtm_lookup(QUIC_SRTM *srtm, 448 const QUIC_STATELESS_RESET_TOKEN *token, 449 size_t idx, 450 void **opaque, uint64_t *seq_num) 451 { 452 SRTM_ITEM key, *item; 453 454 if (srtm->alloc_failed) 455 return 0; 456 457 if (!srtm_compute_blinded(srtm, &key, token)) 458 return 0; 459 460 item = lh_SRTM_ITEM_retrieve(srtm->items_rev, &key); 461 for (; idx > 0 && item != NULL; --idx, item = item->next_by_srt_blinded) 462 ; 463 if (item == NULL) 464 return 0; 465 466 if (opaque != NULL) 467 *opaque = item->opaque; 468 if (seq_num != NULL) 469 *seq_num = item->seq_num; 470 471 return 1; 472 } 473 474 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 475 476 static uint32_t token_next = 0x5eadbeef; 477 static size_t tokens_seen; 478 479 struct check_args { 480 uint32_t token; 481 int mode; 482 }; 483 484 static void check_mark(SRTM_ITEM *item, void *arg) 485 { 486 struct check_args *arg_ = arg; 487 uint32_t token = arg_->token; 488 uint64_t prev_seq_num = 0; 489 void *prev_opaque = NULL; 490 int have_prev = 0; 491 492 assert(item != NULL); 493 494 while (item != NULL) { 495 if (have_prev) { 496 assert(!(item->opaque == prev_opaque && item->seq_num == prev_seq_num)); 497 if (!arg_->mode) 498 assert(item->opaque != prev_opaque || item->seq_num < prev_seq_num); 499 } 500 501 ++tokens_seen; 502 item->debug_token = token; 503 prev_opaque = item->opaque; 504 prev_seq_num = item->seq_num; 505 have_prev = 1; 506 507 if (arg_->mode) 508 item = item->next_by_srt_blinded; 509 else 510 item = item->next_by_seq_num; 511 } 512 } 513 514 static void check_count(SRTM_ITEM *item, void *arg) 515 { 516 struct check_args *arg_ = arg; 517 uint32_t token = arg_->token; 518 519 assert(item != NULL); 520 521 while (item != NULL) { 522 ++tokens_seen; 523 assert(item->debug_token == token); 524 525 if (arg_->mode) 526 item = item->next_by_seq_num; 527 else 528 item = item->next_by_srt_blinded; 529 } 530 } 531 532 #endif 533 534 void ossl_quic_srtm_check(const QUIC_SRTM *srtm) 535 { 536 #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION 537 struct check_args args = { 0 }; 538 size_t tokens_expected, tokens_expected_old; 539 540 args.token = token_next; 541 ++token_next; 542 543 assert(srtm != NULL); 544 assert(srtm->blind_ctx != NULL); 545 assert(srtm->items_fwd != NULL); 546 assert(srtm->items_rev != NULL); 547 548 tokens_seen = 0; 549 lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_mark, &args); 550 551 tokens_expected = tokens_seen; 552 tokens_seen = 0; 553 lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_count, &args); 554 555 assert(tokens_seen == tokens_expected); 556 tokens_expected_old = tokens_expected; 557 558 args.token = token_next; 559 ++token_next; 560 561 args.mode = 1; 562 tokens_seen = 0; 563 lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_mark, &args); 564 565 tokens_expected = tokens_seen; 566 tokens_seen = 0; 567 lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_count, &args); 568 569 assert(tokens_seen == tokens_expected); 570 assert(tokens_seen == tokens_expected_old); 571 #endif 572 } 573