LCOV - code coverage report
Current view: top level - proxy/src/scram - cache.rs (source / functions) Coverage Total Hit
Test: 1d5975439f3c9882b18414799141ebf9a3922c58.info Lines: 91.7 % 36 33
Test Date: 2025-07-31 15:59:03 Functions: 75.0 % 8 6

            Line data    Source code
       1              : use tokio::time::Instant;
       2              : use zeroize::Zeroize as _;
       3              : 
       4              : use super::pbkdf2;
       5              : use crate::cache::Cached;
       6              : use crate::cache::common::{Cache, count_cache_insert, count_cache_outcome, eviction_listener};
       7              : use crate::intern::{EndpointIdInt, RoleNameInt};
       8              : use crate::metrics::{CacheKind, Metrics};
       9              : 
      10              : pub(crate) struct Pbkdf2Cache(moka::sync::Cache<(EndpointIdInt, RoleNameInt), Pbkdf2CacheEntry>);
      11              : pub(crate) type CachedPbkdf2<'a> = Cached<&'a Pbkdf2Cache>;
      12              : 
      13              : impl Cache for Pbkdf2Cache {
      14              :     type Key = (EndpointIdInt, RoleNameInt);
      15              :     type Value = Pbkdf2CacheEntry;
      16              : 
      17            0 :     fn invalidate(&self, info: &(EndpointIdInt, RoleNameInt)) {
      18            0 :         self.0.invalidate(info);
      19            0 :     }
      20              : }
      21              : 
      22              : /// To speed up password hashing for more active customers, we store the tail results of the
      23              : /// PBKDF2 algorithm. If the output of PBKDF2 is U1 ^ U2 ^ ⋯ ^ Uc, then we store
      24              : /// suffix = U17 ^ U18 ^ ⋯ ^ Uc. We only need to calculate U1 ^ U2 ^ ⋯ ^ U15 ^ U16
      25              : /// to determine the final result.
      26              : ///
      27              : /// The suffix alone isn't enough to crack the password. The stored_key is still required.
      28              : /// While both are cached in memory, given they're in different locations is makes it much
      29              : /// harder to exploit, even if any such memory exploit exists in proxy.
      30              : #[derive(Clone)]
      31              : pub struct Pbkdf2CacheEntry {
      32              :     /// corresponds to [`super::ServerSecret::cached_at`]
      33              :     pub(super) cached_from: Instant,
      34              :     pub(super) suffix: pbkdf2::Block,
      35              : }
      36              : 
      37              : impl Drop for Pbkdf2CacheEntry {
      38            8 :     fn drop(&mut self) {
      39            8 :         self.suffix.zeroize();
      40            8 :     }
      41              : }
      42              : 
      43              : impl Pbkdf2Cache {
      44            7 :     pub fn new() -> Self {
      45              :         const SIZE: u64 = 100;
      46              :         const TTL: std::time::Duration = std::time::Duration::from_secs(60);
      47              : 
      48            7 :         let builder = moka::sync::Cache::builder()
      49            7 :             .name("pbkdf2")
      50            7 :             .max_capacity(SIZE)
      51              :             // We use time_to_live so we don't refresh the lifetime for an invalid password attempt.
      52            7 :             .time_to_live(TTL);
      53              : 
      54            7 :         Metrics::get()
      55            7 :             .cache
      56            7 :             .capacity
      57            7 :             .set(CacheKind::Pbkdf2, SIZE as i64);
      58              : 
      59            7 :         let builder =
      60            7 :             builder.eviction_listener(|_k, _v, cause| eviction_listener(CacheKind::Pbkdf2, cause));
      61              : 
      62            7 :         Self(builder.build())
      63            7 :     }
      64              : 
      65            4 :     pub fn insert(&self, endpoint: EndpointIdInt, role: RoleNameInt, value: Pbkdf2CacheEntry) {
      66            4 :         count_cache_insert(CacheKind::Pbkdf2);
      67            4 :         self.0.insert((endpoint, role), value);
      68            4 :     }
      69              : 
      70            8 :     fn get(&self, endpoint: EndpointIdInt, role: RoleNameInt) -> Option<Pbkdf2CacheEntry> {
      71            8 :         count_cache_outcome(CacheKind::Pbkdf2, self.0.get(&(endpoint, role)))
      72            8 :     }
      73              : 
      74            8 :     pub fn get_entry(
      75            8 :         &self,
      76            8 :         endpoint: EndpointIdInt,
      77            8 :         role: RoleNameInt,
      78            8 :     ) -> Option<CachedPbkdf2<'_>> {
      79            8 :         self.get(endpoint, role).map(|value| Cached {
      80            2 :             token: Some((self, (endpoint, role))),
      81            2 :             value,
      82            2 :         })
      83            8 :     }
      84              : }
        

Generated by: LCOV version 2.1-beta