Skip to main content

veilid_core/crypto/
mod.rs

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