Path: blob/main/crypto/openssl/ssl/quic/quic_srtm.c
48261 views
/*1* Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved.2*3* Licensed under the Apache License 2.0 (the "License"). You may not use4* this file except in compliance with the License. You can obtain a copy5* in the file LICENSE in the source distribution or at6* https://www.openssl.org/source/license.html7*/89#include "internal/quic_srtm.h"10#include "internal/common.h"11#include <openssl/lhash.h>12#include <openssl/core_names.h>13#include <openssl/rand.h>1415/*16* QUIC Stateless Reset Token Manager17* ==================================18*/19typedef struct srtm_item_st SRTM_ITEM;2021#define BLINDED_SRT_LEN 162223DEFINE_LHASH_OF_EX(SRTM_ITEM);2425/*26* The SRTM is implemented using two LHASH instances, one matching opaque pointers to27* an item structure, and another matching a SRT-derived value to an item28* structure. Multiple items with different seq_num values under a given opaque,29* and duplicate SRTs, are handled using sorted singly-linked lists.30*31* The O(n) insert and lookup performance is tolerated on the basis that the32* total number of entries for a given opaque (total number of extant CIDs for a33* connection) should be quite small, and the QUIC protocol allows us to place a34* hard limit on this via the active_connection_id_limit TPARAM. Thus there is35* no risk of a large number of SRTs needing to be registered under a given36* opaque.37*38* It is expected one SRTM will exist per QUIC_PORT and track all SRTs across39* all connections for that QUIC_PORT.40*/41struct srtm_item_st {42SRTM_ITEM *next_by_srt_blinded; /* SORT BY opaque DESC */43SRTM_ITEM *next_by_seq_num; /* SORT BY seq_num DESC */44void *opaque; /* \__ unique identity for item */45uint64_t seq_num; /* / */46QUIC_STATELESS_RESET_TOKEN srt;47unsigned char srt_blinded[BLINDED_SRT_LEN]; /* H(srt) */4849#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION50uint32_t debug_token;51#endif52};5354struct quic_srtm_st {55/* Crypto context used to calculate blinded SRTs H(srt). */56EVP_CIPHER_CTX *blind_ctx; /* kept with key */5758LHASH_OF(SRTM_ITEM) *items_fwd; /* (opaque) -> SRTM_ITEM */59LHASH_OF(SRTM_ITEM) *items_rev; /* (H(srt)) -> SRTM_ITEM */6061/*62* Monotonically transitions to 1 in event of allocation failure. The only63* valid operation on such an object is to free it.64*/65unsigned int alloc_failed : 1;66};6768static unsigned long items_fwd_hash(const SRTM_ITEM *item)69{70return (unsigned long)(uintptr_t)item->opaque;71}7273static int items_fwd_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)74{75return a->opaque != b->opaque;76}7778static unsigned long items_rev_hash(const SRTM_ITEM *item)79{80/*81* srt_blinded has already been through a crypto-grade hash function, so we82* can just use bits from that.83*/84unsigned long l;8586memcpy(&l, item->srt_blinded, sizeof(l));87return l;88}8990static int items_rev_cmp(const SRTM_ITEM *a, const SRTM_ITEM *b)91{92/*93* We don't need to use CRYPTO_memcmp here as the relationship of94* srt_blinded to srt is already cryptographically obfuscated.95*/96return memcmp(a->srt_blinded, b->srt_blinded, sizeof(a->srt_blinded));97}9899static int srtm_check_lh(QUIC_SRTM *srtm, LHASH_OF(SRTM_ITEM) *lh)100{101if (lh_SRTM_ITEM_error(lh)) {102srtm->alloc_failed = 1;103return 0;104}105106return 1;107}108109QUIC_SRTM *ossl_quic_srtm_new(OSSL_LIB_CTX *libctx, const char *propq)110{111QUIC_SRTM *srtm = NULL;112unsigned char key[16];113EVP_CIPHER *ecb = NULL;114115if (RAND_priv_bytes_ex(libctx, key, sizeof(key), sizeof(key) * 8) != 1)116goto err;117118if ((srtm = OPENSSL_zalloc(sizeof(*srtm))) == NULL)119return NULL;120121/* Use AES-128-ECB as a permutation over 128-bit SRTs. */122if ((ecb = EVP_CIPHER_fetch(libctx, "AES-128-ECB", propq)) == NULL)123goto err;124125if ((srtm->blind_ctx = EVP_CIPHER_CTX_new()) == NULL)126goto err;127128if (!EVP_EncryptInit_ex2(srtm->blind_ctx, ecb, key, NULL, NULL))129goto err;130131EVP_CIPHER_free(ecb);132ecb = NULL;133134/* Create mappings. */135if ((srtm->items_fwd = lh_SRTM_ITEM_new(items_fwd_hash, items_fwd_cmp)) == NULL136|| (srtm->items_rev = lh_SRTM_ITEM_new(items_rev_hash, items_rev_cmp)) == NULL)137goto err;138139return srtm;140141err:142/*143* No cleansing of key needed as blinding exists only for side channel144* mitigation.145*/146ossl_quic_srtm_free(srtm);147EVP_CIPHER_free(ecb);148return NULL;149}150151static void srtm_free_each(SRTM_ITEM *ihead)152{153SRTM_ITEM *inext, *item = ihead;154155for (item = item->next_by_seq_num; item != NULL; item = inext) {156inext = item->next_by_seq_num;157OPENSSL_free(item);158}159160OPENSSL_free(ihead);161}162163void ossl_quic_srtm_free(QUIC_SRTM *srtm)164{165if (srtm == NULL)166return;167168lh_SRTM_ITEM_free(srtm->items_rev);169if (srtm->items_fwd != NULL) {170lh_SRTM_ITEM_doall(srtm->items_fwd, srtm_free_each);171lh_SRTM_ITEM_free(srtm->items_fwd);172}173174EVP_CIPHER_CTX_free(srtm->blind_ctx);175OPENSSL_free(srtm);176}177178/*179* Find a SRTM_ITEM by (opaque, seq_num). Returns NULL if no match.180* If head is non-NULL, writes the head of the relevant opaque list to *head if181* there is one.182* If prev is non-NULL, writes the previous node to *prev or NULL if it is183* the first item.184*/185static SRTM_ITEM *srtm_find(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,186SRTM_ITEM **head_p, SRTM_ITEM **prev_p)187{188SRTM_ITEM key, *item = NULL, *prev = NULL;189190key.opaque = opaque;191192item = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key);193if (head_p != NULL)194*head_p = item;195196for (; item != NULL; prev = item, item = item->next_by_seq_num)197if (item->seq_num == seq_num) {198break;199} else if (item->seq_num < seq_num) {200/*201* List is sorted in descending order so there can't be any match202* after this.203*/204item = NULL;205break;206}207208if (prev_p != NULL)209*prev_p = prev;210211return item;212}213214/*215* Inserts a SRTM_ITEM into the singly-linked by-sequence-number linked list.216* The new head pointer is written to *new_head (which may or may not be217* unchanged).218*/219static void sorted_insert_seq_num(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)220{221uint64_t seq_num = item->seq_num;222SRTM_ITEM *cur = head, **fixup = new_head;223224*new_head = head;225226while (cur != NULL && cur->seq_num > seq_num) {227fixup = &cur->next_by_seq_num;228cur = cur->next_by_seq_num;229}230231item->next_by_seq_num = *fixup;232*fixup = item;233}234235/*236* Inserts a SRTM_ITEM into the singly-linked by-SRT list.237* The new head pointer is written to *new_head (which may or may not be238* unchanged).239*/240static void sorted_insert_srt(SRTM_ITEM *head, SRTM_ITEM *item, SRTM_ITEM **new_head)241{242uintptr_t opaque = (uintptr_t)item->opaque;243SRTM_ITEM *cur = head, **fixup = new_head;244245*new_head = head;246247while (cur != NULL && (uintptr_t)cur->opaque > opaque) {248fixup = &cur->next_by_srt_blinded;249cur = cur->next_by_srt_blinded;250}251252item->next_by_srt_blinded = *fixup;253*fixup = item;254}255256/*257* Computes the blinded SRT value used for internal lookup for side channel258* mitigation purposes. We compute this once as a cached value when an SRTM_ITEM259* is formed.260*/261static int srtm_compute_blinded(QUIC_SRTM *srtm, SRTM_ITEM *item,262const QUIC_STATELESS_RESET_TOKEN *token)263{264int outl = 0;265266/*267* We use AES-128-ECB as a permutation using a random key to facilitate268* blinding for side-channel purposes. Encrypt the token as a single AES269* block.270*/271if (!EVP_EncryptUpdate(srtm->blind_ctx, item->srt_blinded, &outl,272(const unsigned char *)token, sizeof(*token)))273return 0;274275if (!ossl_assert(outl == sizeof(*token)))276return 0;277278return 1;279}280281int ossl_quic_srtm_add(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num,282const QUIC_STATELESS_RESET_TOKEN *token)283{284SRTM_ITEM *item = NULL, *head = NULL, *new_head, *r_item;285286if (srtm->alloc_failed)287return 0;288289/* (opaque, seq_num) duplicates not allowed */290if ((item = srtm_find(srtm, opaque, seq_num, &head, NULL)) != NULL)291return 0;292293if ((item = OPENSSL_zalloc(sizeof(*item))) == NULL)294return 0;295296item->opaque = opaque;297item->seq_num = seq_num;298item->srt = *token;299if (!srtm_compute_blinded(srtm, item, &item->srt)) {300OPENSSL_free(item);301return 0;302}303304/* Add to forward mapping. */305if (head == NULL) {306/* First item under this opaque */307lh_SRTM_ITEM_insert(srtm->items_fwd, item);308if (!srtm_check_lh(srtm, srtm->items_fwd)) {309OPENSSL_free(item);310return 0;311}312} else {313sorted_insert_seq_num(head, item, &new_head);314if (new_head != head) { /* head changed, update in lhash */315lh_SRTM_ITEM_insert(srtm->items_fwd, new_head);316if (!srtm_check_lh(srtm, srtm->items_fwd)) {317OPENSSL_free(item);318return 0;319}320}321}322323/* Add to reverse mapping. */324r_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);325if (r_item == NULL) {326/* First item under this blinded SRT */327lh_SRTM_ITEM_insert(srtm->items_rev, item);328if (!srtm_check_lh(srtm, srtm->items_rev))329/*330* Can't free the item now as we would have to undo the insertion331* into the forward mapping which would require an insert operation332* to restore the previous value. which might also fail. However,333* the item will be freed OK when we free the entire SRTM.334*/335return 0;336} else {337sorted_insert_srt(r_item, item, &new_head);338if (new_head != r_item) { /* head changed, update in lhash */339lh_SRTM_ITEM_insert(srtm->items_rev, new_head);340if (!srtm_check_lh(srtm, srtm->items_rev))341/* As above. */342return 0;343}344}345346return 1;347}348349/* Remove item from reverse mapping. */350static int srtm_remove_from_rev(QUIC_SRTM *srtm, SRTM_ITEM *item)351{352SRTM_ITEM *rh_item;353354rh_item = lh_SRTM_ITEM_retrieve(srtm->items_rev, item);355assert(rh_item != NULL);356if (rh_item == item) {357/*358* Change lhash to point to item after this one, or remove the entry if359* this is the last one.360*/361if (item->next_by_srt_blinded != NULL) {362lh_SRTM_ITEM_insert(srtm->items_rev, item->next_by_srt_blinded);363if (!srtm_check_lh(srtm, srtm->items_rev))364return 0;365} else {366lh_SRTM_ITEM_delete(srtm->items_rev, item);367}368} else {369/* Find our entry in the SRT list */370for (; rh_item->next_by_srt_blinded != item;371rh_item = rh_item->next_by_srt_blinded);372rh_item->next_by_srt_blinded = item->next_by_srt_blinded;373}374375return 1;376}377378int ossl_quic_srtm_remove(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num)379{380SRTM_ITEM *item, *prev = NULL;381382if (srtm->alloc_failed)383return 0;384385if ((item = srtm_find(srtm, opaque, seq_num, NULL, &prev)) == NULL)386/* No match */387return 0;388389/* Remove from forward mapping. */390if (prev == NULL) {391/*392* Change lhash to point to item after this one, or remove the entry if393* this is the last one.394*/395if (item->next_by_seq_num != NULL) {396lh_SRTM_ITEM_insert(srtm->items_fwd, item->next_by_seq_num);397if (!srtm_check_lh(srtm, srtm->items_fwd))398return 0;399} else {400lh_SRTM_ITEM_delete(srtm->items_fwd, item);401}402} else {403prev->next_by_seq_num = item->next_by_seq_num;404}405406/* Remove from reverse mapping. */407if (!srtm_remove_from_rev(srtm, item))408return 0;409410OPENSSL_free(item);411return 1;412}413414int ossl_quic_srtm_cull(QUIC_SRTM *srtm, void *opaque)415{416SRTM_ITEM key, *item = NULL, *inext, *ihead;417418key.opaque = opaque;419420if (srtm->alloc_failed)421return 0;422423if ((ihead = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key)) == NULL)424return 1; /* nothing removed is a success condition */425426for (item = ihead; item != NULL; item = inext) {427inext = item->next_by_seq_num;428if (item != ihead) {429srtm_remove_from_rev(srtm, item);430OPENSSL_free(item);431}432}433434lh_SRTM_ITEM_delete(srtm->items_fwd, ihead);435srtm_remove_from_rev(srtm, ihead);436OPENSSL_free(ihead);437return 1;438}439440int ossl_quic_srtm_lookup(QUIC_SRTM *srtm,441const QUIC_STATELESS_RESET_TOKEN *token,442size_t idx,443void **opaque, uint64_t *seq_num)444{445SRTM_ITEM key, *item;446447if (srtm->alloc_failed)448return 0;449450if (!srtm_compute_blinded(srtm, &key, token))451return 0;452453item = lh_SRTM_ITEM_retrieve(srtm->items_rev, &key);454for (; idx > 0 && item != NULL; --idx, item = item->next_by_srt_blinded);455if (item == NULL)456return 0;457458if (opaque != NULL)459*opaque = item->opaque;460if (seq_num != NULL)461*seq_num = item->seq_num;462463return 1;464}465466#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION467468static uint32_t token_next = 0x5eadbeef;469static size_t tokens_seen;470471struct check_args {472uint32_t token;473int mode;474};475476static void check_mark(SRTM_ITEM *item, void *arg)477{478struct check_args *arg_ = arg;479uint32_t token = arg_->token;480uint64_t prev_seq_num = 0;481void *prev_opaque = NULL;482int have_prev = 0;483484assert(item != NULL);485486while (item != NULL) {487if (have_prev) {488assert(!(item->opaque == prev_opaque && item->seq_num == prev_seq_num));489if (!arg_->mode)490assert(item->opaque != prev_opaque || item->seq_num < prev_seq_num);491}492493++tokens_seen;494item->debug_token = token;495prev_opaque = item->opaque;496prev_seq_num = item->seq_num;497have_prev = 1;498499if (arg_->mode)500item = item->next_by_srt_blinded;501else502item = item->next_by_seq_num;503}504}505506static void check_count(SRTM_ITEM *item, void *arg)507{508struct check_args *arg_ = arg;509uint32_t token = arg_->token;510511assert(item != NULL);512513while (item != NULL) {514++tokens_seen;515assert(item->debug_token == token);516517if (arg_->mode)518item = item->next_by_seq_num;519else520item = item->next_by_srt_blinded;521}522}523524#endif525526void ossl_quic_srtm_check(const QUIC_SRTM *srtm)527{528#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION529struct check_args args = {0};530size_t tokens_expected, tokens_expected_old;531532args.token = token_next;533++token_next;534535assert(srtm != NULL);536assert(srtm->blind_ctx != NULL);537assert(srtm->items_fwd != NULL);538assert(srtm->items_rev != NULL);539540tokens_seen = 0;541lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_mark, &args);542543tokens_expected = tokens_seen;544tokens_seen = 0;545lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_count, &args);546547assert(tokens_seen == tokens_expected);548tokens_expected_old = tokens_expected;549550args.token = token_next;551++token_next;552553args.mode = 1;554tokens_seen = 0;555lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_mark, &args);556557tokens_expected = tokens_seen;558tokens_seen = 0;559lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_count, &args);560561assert(tokens_seen == tokens_expected);562assert(tokens_seen == tokens_expected_old);563#endif564}565566567