Skip to main content

veilid_core/crypto/
mod.rs

1mod dh_cache;
2mod envelope;
3mod guard;
4mod receipt;
5mod types;
6
7pub mod crypto_system;
8
9#[cfg(any(test, feature = "test-util"))]
10#[doc(hidden)]
11pub mod tests_crypto;
12
13pub use crypto_system::*;
14use dh_cache::*;
15pub(crate) use envelope::*;
16pub use guard::*;
17pub(crate) use receipt::*;
18pub use types::*;
19
20use super::*;
21use core::convert::TryInto;
22use hashlink::linked_hash_map::Entry;
23use hashlink::LruCache;
24
25impl_veilid_log_facility!("crypto");
26
27cfg_if! {
28    if #[cfg(all(feature = "enable-crypto-none", feature = "enable-crypto-vld0"))] {
29        /// Crypto kinds in order of preference, best cryptosystem is the first one, worst is the last one
30        pub const VALID_CRYPTO_KINDS: [CryptoKind; 2] = [CRYPTO_KIND_VLD0, CRYPTO_KIND_NONE];
31    }
32    else if #[cfg(feature = "enable-crypto-none")] {
33        /// Crypto kinds in order of preference, best cryptosystem is the first one, worst is the last one
34        pub const VALID_CRYPTO_KINDS: [CryptoKind; 1] = [CRYPTO_KIND_NONE];
35    }
36    else if #[cfg(feature = "enable-crypto-vld0")] {
37        /// Crypto kinds in order of preference, best cryptosystem is the first one, worst is the last one
38        pub const VALID_CRYPTO_KINDS: [CryptoKind; 1] = [CRYPTO_KIND_VLD0];
39    }
40    // else if #[cfg(feature = "enable-crypto-vld1")] {
41    //     /// Crypto kinds in order of preference, best cryptosystem is the first one, worst is the last one
42    //     pub const VALID_CRYPTO_KINDS: [CryptoKind; 2] = [CRYPTO_KIND_VLD1, CRYPTO_KIND_VLD0];
43    // }
44    else {
45        compile_error!("No crypto kinds enabled, specify an enable-crypto- feature");
46    }
47}
48/// Number of cryptosystem signatures to keep on structures if many are present beyond the ones we consider valid
49pub const MAX_CRYPTO_KINDS: usize = 3;
50
51/// Return the best cryptosystem kind we support
52pub(crate) fn best_crypto_kind() -> CryptoKind {
53    VALID_CRYPTO_KINDS[0]
54}
55
56struct CryptoInner {
57    dh_cache: DHCache,
58    dh_cache_misses: usize,
59    dh_cache_hits: usize,
60    dh_cache_lru: usize,
61}
62
63impl fmt::Debug for CryptoInner {
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        f.debug_struct("CryptoInner")
66            //.field("dh_cache", &self.dh_cache)
67            .field("dh_cache_misses", &self.dh_cache_misses)
68            .field("dh_cache_hits", &self.dh_cache_hits)
69            .field("dh_cache_lru", &self.dh_cache_lru)
70            // .field("crypto_vld0", &self.crypto_vld0)
71            // .field("crypto_none", &self.crypto_none)
72            .finish()
73    }
74}
75
76/// Crypto factory implementation
77#[must_use]
78pub struct Crypto {
79    registry: VeilidComponentRegistry,
80    inner: Mutex<CryptoInner>,
81    #[cfg(feature = "enable-crypto-vld0")]
82    crypto_vld0: Arc<dyn CryptoSystem + Send + Sync>,
83    #[cfg(feature = "enable-crypto-none")]
84    crypto_none: Arc<dyn CryptoSystem + Send + Sync>,
85}
86
87impl_veilid_component!(Crypto);
88
89impl fmt::Debug for Crypto {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        f.debug_struct("Crypto")
92            //.field("registry", &self.registry)
93            .field("inner", &self.inner)
94            // .field("crypto_vld0", &self.crypto_vld0)
95            // .field("crypto_none", &self.crypto_none)
96            .finish()
97    }
98}
99
100impl Crypto {
101    fn new_inner() -> CryptoInner {
102        CryptoInner {
103            dh_cache: DHCache::new(DH_CACHE_SIZE),
104            dh_cache_misses: 0,
105            dh_cache_hits: 0,
106            dh_cache_lru: 0,
107        }
108    }
109
110    pub(crate) fn new(registry: VeilidComponentRegistry) -> Self {
111        Self {
112            registry: registry.clone(),
113            inner: Mutex::new(Self::new_inner()),
114            #[cfg(feature = "enable-crypto-vld0")]
115            crypto_vld0: Arc::new(vld0::CryptoSystemVLD0::new(registry.clone())),
116            #[cfg(feature = "enable-crypto-none")]
117            crypto_none: Arc::new(none::CryptoSystemNONE::new(registry.clone())),
118        }
119    }
120
121    fn log_facilities_impl(&self) -> VeilidComponentLogFacilities {
122        VeilidComponentLogFacilities::new().with_facility(
123            VeilidComponentLogFacility::try_new_with_tags("crypto", ["#common"]).unwrap(),
124        )
125    }
126
127    #[cfg_attr(
128        feature = "instrument",
129        instrument(level = "trace", target = "crypto", skip_all, err, fields(__VEILID_LOG_KEY = self.log_key()))
130    )]
131    #[allow(clippy::unused_async)]
132    async fn init_async(&self) -> EyreResult<()> {
133        // Nothing to initialize at this time
134        Ok(())
135    }
136
137    // Setup called by table store after it get initialized
138    #[cfg_attr(
139        feature = "instrument",
140        instrument(level = "trace", target = "crypto", skip_all, err, fields(__VEILID_LOG_KEY = self.log_key()))
141    )]
142    pub(crate) async fn table_store_setup(&self, table_store: &TableStore) -> EyreResult<()> {
143        // load caches if they are valid for this node id
144        let caches_valid = if let Ok(db) = table_store.open("crypto_caches", 1).await {
145            match db.load(0, b"dh_cache").await {
146                Ok(Some(b)) => {
147                    let mut inner = self.inner.lock();
148                    if let Ok(dh_cache) = bytes_to_cache(&b) {
149                        inner.dh_cache = dh_cache;
150                        true
151                    } else {
152                        false
153                    }
154                }
155                Ok(None) => false,
156                Err(e) => {
157                    veilid_log!(self error "failed to load dh_cache: {}", e);
158                    false
159                }
160            }
161        } else {
162            veilid_log!(self error "failed to open crypto_caches, purging");
163            false
164        };
165
166        if !caches_valid {
167            if let Err(e) = table_store.delete("crypto_caches").await {
168                veilid_log!(self error "failed to delete crypto_caches: {}", e);
169            }
170        }
171
172        Ok(())
173    }
174
175    #[cfg_attr(
176        feature = "instrument",
177        instrument(level = "trace", target = "crypto", skip_all, err, fields(__VEILID_LOG_KEY = self.log_key()))
178    )]
179    #[allow(clippy::unused_async)]
180    async fn post_init_async(&self) -> EyreResult<()> {
181        Ok(())
182    }
183
184    pub async fn flush(&self) -> EyreResult<()> {
185        let cache_bytes = {
186            let inner = self.inner.lock();
187            cache_to_bytes(&inner.dh_cache)
188        };
189
190        let db = self.table_store().open("crypto_caches", 1).await?;
191        db.store(0, b"dh_cache", &cache_bytes).await?;
192        Ok(())
193    }
194
195    async fn pre_terminate_async(&self) {
196        veilid_log!(self trace "starting termination flush");
197        match self.flush().await {
198            Ok(_) => {
199                veilid_log!(self trace "finished termination flush");
200            }
201            Err(e) => {
202                error!("failed termination flush: {}", e);
203            }
204        };
205    }
206
207    #[expect(clippy::unused_async)]
208    async fn terminate_async(&self) {
209        // Nothing to terminate at this time
210    }
211
212    /// Factory method to get a specific crypto version
213    pub fn get(&self, kind: CryptoKind) -> Option<CryptoSystemGuard<'_>> {
214        match kind {
215            #[cfg(feature = "enable-crypto-vld0")]
216            CRYPTO_KIND_VLD0 => Some(CryptoSystemGuard::new(self.crypto_vld0.clone())),
217            #[cfg(feature = "enable-crypto-none")]
218            CRYPTO_KIND_NONE => Some(CryptoSystemGuard::new(self.crypto_none.clone())),
219            _ => None,
220        }
221    }
222
223    /// Factory method to get a specific crypto version for async use
224    pub fn get_async(&self, kind: CryptoKind) -> Option<AsyncCryptoSystemGuard<'_>> {
225        self.get(kind).map(|x| x.as_async())
226    }
227
228    // Factory method to get the best crypto version
229    pub(crate) fn best(&self) -> CryptoSystemGuard<'_> {
230        self.get(best_crypto_kind()).unwrap_or_log()
231    }
232
233    // Factory method to get the best crypto version for async use
234    pub(crate) fn best_async(&self) -> AsyncCryptoSystemGuard<'_> {
235        self.get_async(best_crypto_kind()).unwrap_or_log()
236    }
237
238    // Convenience validators
239    pub fn check_shared_secret(&self, secret: &SharedSecret) -> VeilidAPIResult<()> {
240        let Some(vcrypto) = self.get(secret.kind()) else {
241            apibail_generic!("unsupported crypto kind");
242        };
243        vcrypto.check_shared_secret(secret)
244    }
245
246    pub fn check_hash_digest(&self, hash: &HashDigest) -> VeilidAPIResult<()> {
247        let Some(vcrypto) = self.get(hash.kind()) else {
248            apibail_generic!("unsupported crypto kind");
249        };
250        vcrypto.check_hash_digest(hash)
251    }
252    pub fn check_public_key(&self, key: &PublicKey) -> VeilidAPIResult<()> {
253        let Some(vcrypto) = self.get(key.kind()) else {
254            apibail_generic!("unsupported crypto kind");
255        };
256        vcrypto.check_public_key(key)
257    }
258    pub fn check_secret_key(&self, key: &SecretKey) -> VeilidAPIResult<()> {
259        let Some(vcrypto) = self.get(key.kind()) else {
260            apibail_generic!("unsupported crypto kind");
261        };
262        vcrypto.check_secret_key(key)
263    }
264    pub fn check_signature(&self, signature: &Signature) -> VeilidAPIResult<()> {
265        let Some(vcrypto) = self.get(signature.kind()) else {
266            apibail_generic!("unsupported crypto kind");
267        };
268        vcrypto.check_signature(signature)
269    }
270    pub fn check_keypair(&self, key_pair: &KeyPair) -> VeilidAPIResult<()> {
271        let Some(vcrypto) = self.get(key_pair.kind()) else {
272            apibail_generic!("unsupported crypto kind");
273        };
274        vcrypto.check_keypair(key_pair)
275    }
276
277    /// BareSignature set verification
278    /// Returns Some() the set of signature cryptokinds that validate and are supported
279    /// Returns None if any cryptokinds are supported and do not validate
280    pub fn verify_signatures(
281        &self,
282        public_keys: &[PublicKey],
283        data: &[u8],
284        signatures: &[Signature],
285    ) -> VeilidAPIResult<Option<PublicKeyGroup>> {
286        let mut out = PublicKeyGroup::with_capacity(public_keys.len());
287        for signature in signatures {
288            for public_key in public_keys {
289                if public_key.kind() == signature.kind() {
290                    if let Some(vcrypto) = self.get(signature.kind()) {
291                        if !vcrypto.verify(public_key, data, signature)? {
292                            return Ok(None);
293                        }
294                        out.add(public_key.clone());
295                    }
296                }
297            }
298        }
299        Ok(Some(out))
300    }
301
302    /// BareSignature set generation
303    /// Generates the set of signatures that are supported
304    /// Any cryptokinds that are not supported are silently dropped
305    pub fn generate_signatures<F, R>(
306        &self,
307        data: &[u8],
308        key_pairs: &[KeyPair],
309        transform: F,
310    ) -> VeilidAPIResult<Vec<R>>
311    where
312        F: Fn(&KeyPair, Signature) -> R,
313    {
314        let mut out = Vec::<R>::with_capacity(key_pairs.len());
315        for kp in key_pairs {
316            if let Some(vcrypto) = self.get(kp.kind()) {
317                let sig = vcrypto.sign(&kp.key(), &kp.secret(), data)?;
318                out.push(transform(kp, sig))
319            }
320        }
321        Ok(out)
322    }
323
324    /// Generate keypair
325    /// Does not require startup/init
326    pub fn generate_keypair(crypto_kind: CryptoKind) -> VeilidAPIResult<KeyPair> {
327        #[cfg(feature = "enable-crypto-vld0")]
328        if crypto_kind == CRYPTO_KIND_VLD0 {
329            let kp = vld0_generate_keypair();
330            return Ok(kp);
331        }
332        #[cfg(feature = "enable-crypto-none")]
333        if crypto_kind == CRYPTO_KIND_NONE {
334            let kp = none_generate_keypair();
335            return Ok(kp);
336        }
337        Err(VeilidAPIError::generic("invalid crypto kind"))
338    }
339
340    // Internal utilities
341
342    fn cached_dh_internal<T: CryptoSystem>(
343        &self,
344        vcrypto: &T,
345        key: &PublicKey,
346        secret: &SecretKey,
347    ) -> VeilidAPIResult<SharedSecret> {
348        vcrypto.check_public_key(key)?;
349        vcrypto.check_secret_key(secret)?;
350
351        let dh_cache_key = DHCacheKey {
352            key: key.clone(),
353            secret: secret.clone(),
354        };
355
356        {
357            let inner = &mut *self.inner.lock();
358            if let Some(value) = inner.dh_cache.get(&dh_cache_key) {
359                inner.dh_cache_hits += 1;
360                return Ok(value.shared_secret.clone());
361            }
362        }
363        let shared_secret = vcrypto.compute_dh(key, secret)?;
364
365        {
366            let inner = &mut *self.inner.lock();
367            let res = inner.dh_cache.entry_with_callback(dh_cache_key, |_, _| {
368                inner.dh_cache_lru += 1;
369            });
370            match res {
371                Entry::Occupied(_) => {
372                    inner.dh_cache_hits += 1;
373                }
374                Entry::Vacant(e) => {
375                    inner.dh_cache_misses += 1;
376                    e.insert(DHCacheValue {
377                        shared_secret: shared_secret.clone(),
378                    });
379                }
380            }
381        }
382
383        Ok(shared_secret)
384    }
385
386    pub(crate) fn validate_crypto_kind(kind: CryptoKind) -> VeilidAPIResult<()> {
387        if !VALID_CRYPTO_KINDS.contains(&kind) {
388            apibail_generic!("invalid crypto kind");
389        }
390        Ok(())
391    }
392
393    pub(crate) fn debug_info_nodeinfo(&self) -> String {
394        let inner = self.inner.lock();
395        format!(
396            "Crypto Stats:\n  DH Cache Hits/Misses/LRU: {} / {} / {}\n",
397            inner.dh_cache_hits, inner.dh_cache_misses, inner.dh_cache_lru
398        )
399    }
400}