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 : }
|