123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- /*
- * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.
- *
- * Licensed under the Apache License 2.0 (the "License"). You may not use
- * this file except in compliance with the License. You can obtain a copy
- * in the file LICENSE in the source distribution or at
- * https://www.openssl.org/source/license.html
- */
- #include "internal/quic_srtm.h"
- #include "internal/common.h"
- #include <openssl/lhash.h>
- #include <openssl/core_names.h>
- #include <openssl/rand.h>
- /*
- * QUIC Stateless Reset Token Manager
- * ==================================
- */
- typedef struct srtm_item_st SRTM_ITEM;
- #define BLINDED_SRT_LEN 16
- DEFINE_LHASH_OF_EX(SRTM_ITEM);
- /*
- * The SRTM is implemented using two LHASH instances, one matching opaque pointers to
- * an item structure, and another matching a SRT-derived value to an item
- * structure. Multiple items with different seq_num values under a given opaque,
- * and duplicate SRTs, are handled using sorted singly-linked lists.
- *
- * The O(n) insert and lookup performance is tolerated on the basis that the
- * total number of entries for a given opaque (total number of extant CIDs for a
- * connection) should be quite small, and the QUIC protocol allows us to place a
- * hard limit on this via the active_connection_id_limit TPARAM. Thus there is
- * no risk of a large number of SRTs needing to be registered under a given
- * opaque.
- *
- * It is expected one SRTM will exist per QUIC_PORT and track all SRTs across
- * all connections for that QUIC_PORT.
- */
- struct srtm_item_st {
- SRTM_ITEM *next_by_srt_blinded; /* SORT BY opaque DESC */
- SRTM_ITEM *next_by_seq_num; /* SORT BY seq_num DESC */
- void *opaque; /* \__ unique identity for item */
- uint64_t seq_num; /* / */
- QUIC_STATELESS_RESET_TOKEN srt;
- unsigned char srt_blinded[BLINDED_SRT_LEN]; /* H(srt) */
- #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
- uint32_t debug_token;
- #endif
- };
- struct quic_srtm_st {
- /* Crypto context used to calculate blinded SRTs H(srt). */
- EVP_CIPHER_CTX *blind_ctx; /* kept with key */
- LHASH_OF(SRTM_ITEM) *items_fwd; /* (opaque) -> SRTM_ITEM */
- LHASH_OF(SRTM_ITEM) *items_rev; /* (H(srt)) -> SRTM_ITEM */
- /*
- * Monotonically transitions to 1 in event of allocation failure. The only
- * valid operation on such an object is to free it.
- */
- unsigned int alloc_failed : 1;
- };
- static unsigned long items_fwd_hash(const SRTM_ITEM *item)
- {
- return (unsigned long)(uintptr_t)item->opaque;
- }
- static int items_fwd_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)
- {
- return a->opaque != b->opaque;
- }
- static unsigned long items_rev_hash(const SRTM_ITEM *item)
- {
- /*
- * srt_blinded has already been through a crypto-grade hash function, so we
- * can just use bits from that.
- */
- unsigned long l;
- memcpy(&l, item->srt_blinded, sizeof(l));
- return l;
- }
- static int items_rev_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)
- {
- /*
- * We don't need to use CRYPTO_memcmp here as the relationship of
- * srt_blinded to srt is already cryptographically obfuscated.
- */
- return memcmp(a->srt_blinded, b->srt_blinded, sizeof(a->srt_blinded));
- }
- static int srtm_check_lh(QUIC_SRTM *srtm, LHASH_OF(SRTM_ITEM) *lh)
- {
- if (lh_SRTM_ITEM_error(lh)) {
- srtm->alloc_failed = 1;
- return 0;
- }
- return 1;
- }
- QUIC_SRTM *ossl_quic_srtm_new(OSSL_LIB_CTX *libctx, const char *propq)
- {
- QUIC_SRTM *srtm = NULL;
- unsigned char key[16];
- EVP_CIPHER *ecb = NULL;
- if (RAND_priv_bytes_ex(libctx, key, sizeof(key), sizeof(key) * 8) != 1)
- goto err;
- if ((srtm = OPENSSL_zalloc(sizeof(*srtm))) == NULL)
- return NULL;
- /* Use AES-128-ECB as a permutation over 128-bit SRTs. */
- if ((ecb = EVP_CIPHER_fetch(libctx, "AES-128-ECB", propq)) == NULL)
- goto err;
- if ((srtm->blind_ctx = EVP_CIPHER_CTX_new()) == NULL)
- goto err;
- if (!EVP_EncryptInit_ex2(srtm->blind_ctx, ecb, key, NULL, NULL))
- goto err;
- EVP_CIPHER_free(ecb);
- ecb = NULL;
- /* Create mappings. */
- if ((srtm->items_fwd = lh_SRTM_ITEM_new(items_fwd_hash, items_fwd_cmp)) == NULL
- || (srtm->items_rev = lh_SRTM_ITEM_new(items_rev_hash, items_rev_cmp)) == NULL)
- goto err;
- return srtm;
- err:
- /*
- * No cleansing of key needed as blinding exists only for side channel
- * mitigation.
- */
- ossl_quic_srtm_free(srtm);
- EVP_CIPHER_free(ecb);
- return NULL;
- }
- static void srtm_free_each(SRTM_ITEM *ihead)
- {
- SRTM_ITEM *inext, *item = ihead;
- for (item = item->next_by_seq_num; item != NULL; item = inext) {
- inext = item->next_by_seq_num;
- OPENSSL_free(item);
- }
- OPENSSL_free(ihead);
- }
- void ossl_quic_srtm_free(QUIC_SRTM *srtm)
- {
- if (srtm == NULL)
- return;
- lh_SRTM_ITEM_free(srtm->items_rev);
- if (srtm->items_fwd != NULL) {
- lh_SRTM_ITEM_doall(srtm->items_fwd, srtm_free_each);
- lh_SRTM_ITEM_free(srtm->items_fwd);
- }
- EVP_CIPHER_CTX_free(srtm->blind_ctx);
- OPENSSL_free(srtm);
- }
- /*
- * Find a SRTM_ITEM by (opaque, seq_num). Returns NULL if no match.
- * If head is non-NULL, writes the head of the relevant opaque list to *head if
- * there is one.
- * If prev is non-NULL, writes the previous node to *prev or NULL if it is
- * the first item.
- */
- static SRTM_ITEM *srtm_find(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,
- SRTM_ITEM **head_p, SRTM_ITEM **prev_p)
- {
- SRTM_ITEM key, *item = NULL, *prev = NULL;
- key.opaque = opaque;
- item = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key);
- if (head_p != NULL)
- *head_p = item;
- for (; item != NULL; prev = item, item = item->next_by_seq_num)
- if (item->seq_num == seq_num) {
- break;
- } else if (item->seq_num < seq_num) {
- /*
- * List is sorted in descending order so there can't be any match
- * after this.
- */
- item = NULL;
- break;
- }
- if (prev_p != NULL)
- *prev_p = prev;
- return item;
- }
- /*
- * Inserts a SRTM_ITEM into the singly-linked by-sequence-number linked list.
- * The new head pointer is written to *new_head (which may or may not be
- * unchanged).
- */
- static void sorted_insert_seq_num(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)
- {
- uint64_t seq_num = item->seq_num;
- SRTM_ITEM *cur = head, **fixup = new_head;
- *new_head = head;
- while (cur != NULL && cur->seq_num > seq_num) {
- fixup = &cur->next_by_seq_num;
- cur = cur->next_by_seq_num;
- }
- item->next_by_seq_num = *fixup;
- *fixup = item;
- }
- /*
- * Inserts a SRTM_ITEM into the singly-linked by-SRT list.
- * The new head pointer is written to *new_head (which may or may not be
- * unchanged).
- */
- static void sorted_insert_srt(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)
- {
- uintptr_t opaque = (uintptr_t)item->opaque;
- SRTM_ITEM *cur = head, **fixup = new_head;
- *new_head = head;
- while (cur != NULL && (uintptr_t)cur->opaque > opaque) {
- fixup = &cur->next_by_srt_blinded;
- cur = cur->next_by_srt_blinded;
- }
- item->next_by_srt_blinded = *fixup;
- *fixup = item;
- }
- /*
- * Computes the blinded SRT value used for internal lookup for side channel
- * mitigation purposes. We compute this once as a cached value when an SRTM_ITEM
- * is formed.
- */
- static int srtm_compute_blinded(QUIC_SRTM *srtm, SRTM_ITEM *item,
- const QUIC_STATELESS_RESET_TOKEN *token)
- {
- int outl = 0;
- /*
- * We use AES-128-ECB as a permutation using a random key to facilitate
- * blinding for side-channel purposes. Encrypt the token as a single AES
- * block.
- */
- if (!EVP_EncryptUpdate(srtm->blind_ctx, item->srt_blinded, &outl,
- (const unsigned char *)token, sizeof(*token)))
- return 0;
- if (!ossl_assert(outl == sizeof(*token)))
- return 0;
- return 1;
- }
- int ossl_quic_srtm_add(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,
- const QUIC_STATELESS_RESET_TOKEN *token)
- {
- SRTM_ITEM *item = NULL, *head = NULL, *new_head, *r_item;
- if (srtm->alloc_failed)
- return 0;
- /* (opaque, seq_num) duplicates not allowed */
- if ((item = srtm_find(srtm, opaque, seq_num, &head, NULL)) != NULL)
- return 0;
- if ((item = OPENSSL_zalloc(sizeof(*item))) == NULL)
- return 0;
- item->opaque = opaque;
- item->seq_num = seq_num;
- item->srt = *token;
- if (!srtm_compute_blinded(srtm, item, &item->srt)) {
- OPENSSL_free(item);
- return 0;
- }
- /* Add to forward mapping. */
- if (head == NULL) {
- /* First item under this opaque */
- lh_SRTM_ITEM_insert(srtm->items_fwd, item);
- if (!srtm_check_lh(srtm, srtm->items_fwd)) {
- OPENSSL_free(item);
- return 0;
- }
- } else {
- sorted_insert_seq_num(head, item, &new_head);
- if (new_head != head) { /* head changed, update in lhash */
- lh_SRTM_ITEM_insert(srtm->items_fwd, new_head);
- if (!srtm_check_lh(srtm, srtm->items_fwd)) {
- OPENSSL_free(item);
- return 0;
- }
- }
- }
- /* Add to reverse mapping. */
- r_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);
- if (r_item == NULL) {
- /* First item under this blinded SRT */
- lh_SRTM_ITEM_insert(srtm->items_rev, item);
- if (!srtm_check_lh(srtm, srtm->items_rev))
- /*
- * Can't free the item now as we would have to undo the insertion
- * into the forward mapping which would require an insert operation
- * to restore the previous value. which might also fail. However,
- * the item will be freed OK when we free the entire SRTM.
- */
- return 0;
- } else {
- sorted_insert_srt(r_item, item, &new_head);
- if (new_head != r_item) { /* head changed, update in lhash */
- lh_SRTM_ITEM_insert(srtm->items_rev, new_head);
- if (!srtm_check_lh(srtm, srtm->items_rev))
- /* As above. */
- return 0;
- }
- }
- return 1;
- }
- /* Remove item from reverse mapping. */
- static int srtm_remove_from_rev(QUIC_SRTM *srtm, SRTM_ITEM *item)
- {
- SRTM_ITEM *rh_item;
- rh_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);
- assert(rh_item != NULL);
- if (rh_item == item) {
- /*
- * Change lhash to point to item after this one, or remove the entry if
- * this is the last one.
- */
- if (item->next_by_srt_blinded != NULL) {
- lh_SRTM_ITEM_insert(srtm->items_rev, item->next_by_srt_blinded);
- if (!srtm_check_lh(srtm, srtm->items_rev))
- return 0;
- } else {
- lh_SRTM_ITEM_delete(srtm->items_rev, item);
- }
- } else {
- /* Find our entry in the SRT list */
- for (; rh_item->next_by_srt_blinded != item;
- rh_item = rh_item->next_by_srt_blinded);
- rh_item->next_by_srt_blinded = item->next_by_srt_blinded;
- }
- return 1;
- }
- int ossl_quic_srtm_remove(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num)
- {
- SRTM_ITEM *item, *prev = NULL;
- if (srtm->alloc_failed)
- return 0;
- if ((item = srtm_find(srtm, opaque, seq_num, NULL, &prev)) == NULL)
- /* No match */
- return 0;
- /* Remove from forward mapping. */
- if (prev == NULL) {
- /*
- * Change lhash to point to item after this one, or remove the entry if
- * this is the last one.
- */
- if (item->next_by_seq_num != NULL) {
- lh_SRTM_ITEM_insert(srtm->items_fwd, item->next_by_seq_num);
- if (!srtm_check_lh(srtm, srtm->items_fwd))
- return 0;
- } else {
- lh_SRTM_ITEM_delete(srtm->items_fwd, item);
- }
- } else {
- prev->next_by_seq_num = item->next_by_seq_num;
- }
- /* Remove from reverse mapping. */
- if (!srtm_remove_from_rev(srtm, item))
- return 0;
- OPENSSL_free(item);
- return 1;
- }
- int ossl_quic_srtm_cull(QUIC_SRTM *srtm, void *opaque)
- {
- SRTM_ITEM key, *item = NULL, *inext, *ihead;
- key.opaque = opaque;
- if (srtm->alloc_failed)
- return 0;
- if ((ihead = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key)) == NULL)
- return 1; /* nothing removed is a success condition */
- for (item = ihead; item != NULL; item = inext) {
- inext = item->next_by_seq_num;
- if (item != ihead) {
- srtm_remove_from_rev(srtm, item);
- OPENSSL_free(item);
- }
- }
- lh_SRTM_ITEM_delete(srtm->items_fwd, ihead);
- srtm_remove_from_rev(srtm, ihead);
- OPENSSL_free(ihead);
- return 1;
- }
- int ossl_quic_srtm_lookup(QUIC_SRTM *srtm,
- const QUIC_STATELESS_RESET_TOKEN *token,
- size_t idx,
- void **opaque, uint64_t *seq_num)
- {
- SRTM_ITEM key, *item;
- if (srtm->alloc_failed)
- return 0;
- if (!srtm_compute_blinded(srtm, &key, token))
- return 0;
- item = lh_SRTM_ITEM_retrieve(srtm->items_rev, &key);
- for (; idx > 0 && item != NULL; --idx, item = item->next_by_srt_blinded);
- if (item == NULL)
- return 0;
- if (opaque != NULL)
- *opaque = item->opaque;
- if (seq_num != NULL)
- *seq_num = item->seq_num;
- return 1;
- }
- #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
- static uint32_t token_next = 0x5eadbeef;
- static size_t tokens_seen;
- struct check_args {
- uint32_t token;
- int mode;
- };
- static void check_mark(SRTM_ITEM *item, void *arg)
- {
- struct check_args *arg_ = arg;
- uint32_t token = arg_->token;
- uint64_t prev_seq_num = 0;
- void *prev_opaque = NULL;
- int have_prev = 0;
- assert(item != NULL);
- while (item != NULL) {
- if (have_prev) {
- assert(!(item->opaque == prev_opaque && item->seq_num == prev_seq_num));
- if (!arg_->mode)
- assert(item->opaque != prev_opaque || item->seq_num < prev_seq_num);
- }
- ++tokens_seen;
- item->debug_token = token;
- prev_opaque = item->opaque;
- prev_seq_num = item->seq_num;
- have_prev = 1;
- if (arg_->mode)
- item = item->next_by_srt_blinded;
- else
- item = item->next_by_seq_num;
- }
- }
- static void check_count(SRTM_ITEM *item, void *arg)
- {
- struct check_args *arg_ = arg;
- uint32_t token = arg_->token;
- assert(item != NULL);
- while (item != NULL) {
- ++tokens_seen;
- assert(item->debug_token == token);
- if (arg_->mode)
- item = item->next_by_seq_num;
- else
- item = item->next_by_srt_blinded;
- }
- }
- #endif
- void ossl_quic_srtm_check(const QUIC_SRTM *srtm)
- {
- #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
- struct check_args args = {0};
- size_t tokens_expected, tokens_expected_old;
- args.token = token_next;
- ++token_next;
- assert(srtm != NULL);
- assert(srtm->blind_ctx != NULL);
- assert(srtm->items_fwd != NULL);
- assert(srtm->items_rev != NULL);
- tokens_seen = 0;
- lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_mark, &args);
- tokens_expected = tokens_seen;
- tokens_seen = 0;
- lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_count, &args);
- assert(tokens_seen == tokens_expected);
- tokens_expected_old = tokens_expected;
- args.token = token_next;
- ++token_next;
- args.mode = 1;
- tokens_seen = 0;
- lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_mark, &args);
- tokens_expected = tokens_seen;
- tokens_seen = 0;
- lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_count, &args);
- assert(tokens_seen == tokens_expected);
- assert(tokens_seen == tokens_expected_old);
- #endif
- }
|