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 = {
145            let db = table_store
146                .open("crypto_caches", 1)
147                .await
148                .wrap_err("failed to open crypto_caches")?;
149
150            let mut caches_valid = true;
151            if let Some(b) = db.load(0, b"dh_cache").await? {
152                let mut inner = self.inner.lock();
153                if let Ok(dh_cache) = bytes_to_cache(&b) {
154                    inner.dh_cache = dh_cache;
155                } else {
156                    caches_valid = false;
157                }
158            }
159
160            caches_valid
161        };
162
163        if !caches_valid {
164            table_store.delete("crypto_caches").await?;
165        }
166
167        Ok(())
168    }
169
170    #[cfg_attr(
171        feature = "instrument",
172        instrument(level = "trace", target = "crypto", skip_all, err, fields(__VEILID_LOG_KEY = self.log_key()))
173    )]
174    #[allow(clippy::unused_async)]
175    async fn post_init_async(&self) -> EyreResult<()> {
176        Ok(())
177    }
178
179    pub async fn flush(&self) -> EyreResult<()> {
180        let cache_bytes = {
181            let inner = self.inner.lock();
182            cache_to_bytes(&inner.dh_cache)
183        };
184
185        let db = self.table_store().open("crypto_caches", 1).await?;
186        db.store(0, b"dh_cache", &cache_bytes).await?;
187        Ok(())
188    }
189
190    async fn pre_terminate_async(&self) {
191        veilid_log!(self trace "starting termination flush");
192        match self.flush().await {
193            Ok(_) => {
194                veilid_log!(self trace "finished termination flush");
195            }
196            Err(e) => {
197                error!("failed termination flush: {}", e);
198            }
199        };
200    }
201
202    #[expect(clippy::unused_async)]
203    async fn terminate_async(&self) {
204        // Nothing to terminate at this time
205    }
206
207    /// Factory method to get a specific crypto version
208    pub fn get(&self, kind: CryptoKind) -> Option<CryptoSystemGuard<'_>> {
209        match kind {
210            #[cfg(feature = "enable-crypto-vld0")]
211            CRYPTO_KIND_VLD0 => Some(CryptoSystemGuard::new(self.crypto_vld0.clone())),
212            #[cfg(feature = "enable-crypto-none")]
213            CRYPTO_KIND_NONE => Some(CryptoSystemGuard::new(self.crypto_none.clone())),
214            _ => None,
215        }
216    }
217
218    /// Factory method to get a specific crypto version for async use
219    pub fn get_async(&self, kind: CryptoKind) -> Option<AsyncCryptoSystemGuard<'_>> {
220        self.get(kind).map(|x| x.as_async())
221    }
222
223    // Factory method to get the best crypto version
224    pub(crate) fn best(&self) -> CryptoSystemGuard<'_> {
225        self.get(best_crypto_kind()).unwrap_or_log()
226    }
227
228    // Factory method to get the best crypto version for async use
229    pub(crate) fn best_async(&self) -> AsyncCryptoSystemGuard<'_> {
230        self.get_async(best_crypto_kind()).unwrap_or_log()
231    }
232
233    // Convenience validators
234    pub fn check_shared_secret(&self, secret: &SharedSecret) -> VeilidAPIResult<()> {
235        let Some(vcrypto) = self.get(secret.kind()) else {
236            apibail_generic!("unsupported crypto kind");
237        };
238        vcrypto.check_shared_secret(secret)
239    }
240
241    pub fn check_hash_digest(&self, hash: &HashDigest) -> VeilidAPIResult<()> {
242        let Some(vcrypto) = self.get(hash.kind()) else {
243            apibail_generic!("unsupported crypto kind");
244        };
245        vcrypto.check_hash_digest(hash)
246    }
247    pub fn check_public_key(&self, key: &PublicKey) -> VeilidAPIResult<()> {
248        let Some(vcrypto) = self.get(key.kind()) else {
249            apibail_generic!("unsupported crypto kind");
250        };
251        vcrypto.check_public_key(key)
252    }
253    pub fn check_secret_key(&self, key: &SecretKey) -> VeilidAPIResult<()> {
254        let Some(vcrypto) = self.get(key.kind()) else {
255            apibail_generic!("unsupported crypto kind");
256        };
257        vcrypto.check_secret_key(key)
258    }
259    pub fn check_signature(&self, signature: &Signature) -> VeilidAPIResult<()> {
260        let Some(vcrypto) = self.get(signature.kind()) else {
261            apibail_generic!("unsupported crypto kind");
262        };
263        vcrypto.check_signature(signature)
264    }
265    pub fn check_keypair(&self, key_pair: &KeyPair) -> VeilidAPIResult<()> {
266        let Some(vcrypto) = self.get(key_pair.kind()) else {
267            apibail_generic!("unsupported crypto kind");
268        };
269        vcrypto.check_keypair(key_pair)
270    }
271
272    /// BareSignature set verification
273    /// Returns Some() the set of signature cryptokinds that validate and are supported
274    /// Returns None if any cryptokinds are supported and do not validate
275    pub fn verify_signatures(
276        &self,
277        public_keys: &[PublicKey],
278        data: &[u8],
279        signatures: &[Signature],
280    ) -> VeilidAPIResult<Option<PublicKeyGroup>> {
281        let mut out = PublicKeyGroup::with_capacity(public_keys.len());
282        for signature in signatures {
283            for public_key in public_keys {
284                if public_key.kind() == signature.kind() {
285                    if let Some(vcrypto) = self.get(signature.kind()) {
286                        if !vcrypto.verify(public_key, data, signature)? {
287                            return Ok(None);
288                        }
289                        out.add(public_key.clone());
290                    }
291                }
292            }
293        }
294        Ok(Some(out))
295    }
296
297    /// BareSignature set generation
298    /// Generates the set of signatures that are supported
299    /// Any cryptokinds that are not supported are silently dropped
300    pub fn generate_signatures<F, R>(
301        &self,
302        data: &[u8],
303        key_pairs: &[KeyPair],
304        transform: F,
305    ) -> VeilidAPIResult<Vec<R>>
306    where
307        F: Fn(&KeyPair, Signature) -> R,
308    {
309        let mut out = Vec::<R>::with_capacity(key_pairs.len());
310        for kp in key_pairs {
311            if let Some(vcrypto) = self.get(kp.kind()) {
312                let sig = vcrypto.sign(&kp.key(), &kp.secret(), data)?;
313                out.push(transform(kp, sig))
314            }
315        }
316        Ok(out)
317    }
318
319    /// Generate keypair
320    /// Does not require startup/init
321    pub fn generate_keypair(crypto_kind: CryptoKind) -> VeilidAPIResult<KeyPair> {
322        #[cfg(feature = "enable-crypto-vld0")]
323        if crypto_kind == CRYPTO_KIND_VLD0 {
324            let kp = vld0_generate_keypair();
325            return Ok(kp);
326        }
327        #[cfg(feature = "enable-crypto-none")]
328        if crypto_kind == CRYPTO_KIND_NONE {
329            let kp = none_generate_keypair();
330            return Ok(kp);
331        }
332        Err(VeilidAPIError::generic("invalid crypto kind"))
333    }
334
335    // Internal utilities
336
337    fn cached_dh_internal<T: CryptoSystem>(
338        &self,
339        vcrypto: &T,
340        key: &PublicKey,
341        secret: &SecretKey,
342    ) -> VeilidAPIResult<SharedSecret> {
343        vcrypto.check_public_key(key)?;
344        vcrypto.check_secret_key(secret)?;
345        let inner = &mut *self.inner.lock();
346        let dh_cache_key = DHCacheKey {
347            key: key.clone(),
348            secret: secret.clone(),
349        };
350        let res = inner.dh_cache.entry_with_callback(dh_cache_key, |_, _| {
351            inner.dh_cache_lru += 1;
352        });
353        Ok(match res {
354            Entry::Occupied(e) => {
355                inner.dh_cache_hits += 1;
356                e.get().shared_secret.clone()
357            }
358            Entry::Vacant(e) => {
359                inner.dh_cache_misses += 1;
360
361                let shared_secret = vcrypto.compute_dh(key, secret)?;
362                e.insert(DHCacheValue {
363                    shared_secret: shared_secret.clone(),
364                });
365                shared_secret
366            }
367        })
368    }
369
370    pub(crate) fn validate_crypto_kind(kind: CryptoKind) -> VeilidAPIResult<()> {
371        if !VALID_CRYPTO_KINDS.contains(&kind) {
372            apibail_generic!("invalid crypto kind");
373        }
374        Ok(())
375    }
376
377    pub(crate) fn debug_info_nodeinfo(&self) -> String {
378        let inner = self.inner.lock();
379        format!(
380            "Crypto Stats:\n  DH Cache Hits/Misses/LRU: {} / {} / {}\n",
381            inner.dh_cache_hits, inner.dh_cache_misses, inner.dh_cache_lru
382        )
383    }
384}