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 pub const VALID_CRYPTO_KINDS: [CryptoKind; 2] = [CRYPTO_KIND_VLD0, CRYPTO_KIND_NONE];
31 }
32 else if #[cfg(feature = "enable-crypto-none")] {
33 pub const VALID_CRYPTO_KINDS: [CryptoKind; 1] = [CRYPTO_KIND_NONE];
35 }
36 else if #[cfg(feature = "enable-crypto-vld0")] {
37 pub const VALID_CRYPTO_KINDS: [CryptoKind; 1] = [CRYPTO_KIND_VLD0];
39 }
40 else {
45 compile_error!("No crypto kinds enabled, specify an enable-crypto- feature");
46 }
47}
48pub const MAX_CRYPTO_KINDS: usize = 3;
50
51pub(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_misses", &self.dh_cache_misses)
68 .field("dh_cache_hits", &self.dh_cache_hits)
69 .field("dh_cache_lru", &self.dh_cache_lru)
70 .finish()
73 }
74}
75
76#[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("inner", &self.inner)
94 .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 Ok(())
135 }
136
137 #[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 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 }
211
212 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 pub fn get_async(&self, kind: CryptoKind) -> Option<AsyncCryptoSystemGuard<'_>> {
225 self.get(kind).map(|x| x.as_async())
226 }
227
228 pub(crate) fn best(&self) -> CryptoSystemGuard<'_> {
230 self.get(best_crypto_kind()).unwrap_or_log()
231 }
232
233 pub(crate) fn best_async(&self) -> AsyncCryptoSystemGuard<'_> {
235 self.get_async(best_crypto_kind()).unwrap_or_log()
236 }
237
238 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 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 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 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 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}