Path: blob/main/crypto/openssl/ssl/quic/quic_srtm.c
108174 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)372;373rh_item->next_by_srt_blinded = item->next_by_srt_blinded;374}375376return 1;377}378379int ossl_quic_srtm_remove(QUIC_SRTM *srtm, void *opaque, uint64_t seq_num)380{381SRTM_ITEM *item, *prev = NULL;382383if (srtm->alloc_failed)384return 0;385386if ((item = srtm_find(srtm, opaque, seq_num, NULL, &prev)) == NULL)387/* No match */388return 0;389390/* Remove from forward mapping. */391if (prev == NULL) {392/*393* Change lhash to point to item after this one, or remove the entry if394* this is the last one.395*/396if (item->next_by_seq_num != NULL) {397lh_SRTM_ITEM_insert(srtm->items_fwd, item->next_by_seq_num);398if (!srtm_check_lh(srtm, srtm->items_fwd))399return 0;400} else {401lh_SRTM_ITEM_delete(srtm->items_fwd, item);402}403} else {404prev->next_by_seq_num = item->next_by_seq_num;405}406407/* Remove from reverse mapping. */408if (!srtm_remove_from_rev(srtm, item))409return 0;410411OPENSSL_free(item);412return 1;413}414415int ossl_quic_srtm_cull(QUIC_SRTM *srtm, void *opaque)416{417SRTM_ITEM key, *item = NULL, *inext, *ihead;418419key.opaque = opaque;420421if (srtm->alloc_failed)422return 0;423424if ((ihead = lh_SRTM_ITEM_retrieve(srtm->items_fwd, &key)) == NULL)425return 1; /* nothing removed is a success condition */426427for (item = ihead; item != NULL; item = inext) {428inext = item->next_by_seq_num;429if (item != ihead) {430srtm_remove_from_rev(srtm, item);431OPENSSL_free(item);432}433}434435lh_SRTM_ITEM_delete(srtm->items_fwd, ihead);436srtm_remove_from_rev(srtm, ihead);437OPENSSL_free(ihead);438return 1;439}440441int ossl_quic_srtm_lookup(QUIC_SRTM *srtm,442const QUIC_STATELESS_RESET_TOKEN *token,443size_t idx,444void **opaque, uint64_t *seq_num)445{446SRTM_ITEM key, *item;447448if (srtm->alloc_failed)449return 0;450451if (!srtm_compute_blinded(srtm, &key, token))452return 0;453454item = lh_SRTM_ITEM_retrieve(srtm->items_rev, &key);455for (; idx > 0 && item != NULL; --idx, item = item->next_by_srt_blinded)456;457if (item == NULL)458return 0;459460if (opaque != NULL)461*opaque = item->opaque;462if (seq_num != NULL)463*seq_num = item->seq_num;464465return 1;466}467468#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION469470static uint32_t token_next = 0x5eadbeef;471static size_t tokens_seen;472473struct check_args {474uint32_t token;475int mode;476};477478static void check_mark(SRTM_ITEM *item, void *arg)479{480struct check_args *arg_ = arg;481uint32_t token = arg_->token;482uint64_t prev_seq_num = 0;483void *prev_opaque = NULL;484int have_prev = 0;485486assert(item != NULL);487488while (item != NULL) {489if (have_prev) {490assert(!(item->opaque == prev_opaque && item->seq_num == prev_seq_num));491if (!arg_->mode)492assert(item->opaque != prev_opaque || item->seq_num < prev_seq_num);493}494495++tokens_seen;496item->debug_token = token;497prev_opaque = item->opaque;498prev_seq_num = item->seq_num;499have_prev = 1;500501if (arg_->mode)502item = item->next_by_srt_blinded;503else504item = item->next_by_seq_num;505}506}507508static void check_count(SRTM_ITEM *item, void *arg)509{510struct check_args *arg_ = arg;511uint32_t token = arg_->token;512513assert(item != NULL);514515while (item != NULL) {516++tokens_seen;517assert(item->debug_token == token);518519if (arg_->mode)520item = item->next_by_seq_num;521else522item = item->next_by_srt_blinded;523}524}525526#endif527528void ossl_quic_srtm_check(const QUIC_SRTM *srtm)529{530#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION531struct check_args args = { 0 };532size_t tokens_expected, tokens_expected_old;533534args.token = token_next;535++token_next;536537assert(srtm != NULL);538assert(srtm->blind_ctx != NULL);539assert(srtm->items_fwd != NULL);540assert(srtm->items_rev != NULL);541542tokens_seen = 0;543lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_mark, &args);544545tokens_expected = tokens_seen;546tokens_seen = 0;547lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_count, &args);548549assert(tokens_seen == tokens_expected);550tokens_expected_old = tokens_expected;551552args.token = token_next;553++token_next;554555args.mode = 1;556tokens_seen = 0;557lh_SRTM_ITEM_doall_arg(srtm->items_rev, check_mark, &args);558559tokens_expected = tokens_seen;560tokens_seen = 0;561lh_SRTM_ITEM_doall_arg(srtm->items_fwd, check_count, &args);562563assert(tokens_seen == tokens_expected);564assert(tokens_seen == tokens_expected_old);565#endif566}567568569