veilid_core/crypto/crypto_system/mod.rs
1use std::ops::Range;
2
3use super::*;
4
5mod buffer;
6
7pub(crate) use buffer::*;
8
9#[cfg(any(feature = "enable-crypto-vld0", feature = "enable-crypto-none"))]
10pub(crate) mod hpke;
11#[cfg(feature = "enable-crypto-none")]
12pub(crate) mod none;
13#[cfg(feature = "enable-crypto-vld0")]
14pub(crate) mod vld0;
15// #[cfg(feature = "enable-crypto-vld1")]
16// pub(crate) mod vld1;
17
18pub(crate) const VEILID_DOMAIN_API: &[u8] = b"VEILID_API";
19
20#[cfg(feature = "enable-crypto-none")]
21pub use none::*;
22#[cfg(feature = "enable-crypto-vld0")]
23pub use vld0::*;
24// #[cfg(feature = "enable-crypto-vld1")]
25// pub use vld1::*;
26
27/// The set of cryptographic primitives a single cryptosystem provides: key generation, signing
28/// and verification, AEAD and unauthenticated encryption, Diffie-Hellman key exchange and shared
29/// secret derivation, hashing, password hashing, and random byte generation.
30///
31/// Each implementation is tagged by a [`CryptoKind`] fourcc; keys, signatures, nonces, and digests
32/// carry that kind and are only accepted by the matching cryptosystem. VLD0 is the current
33/// implementation.
34pub trait CryptoSystem {
35 // Accessors
36 /// The [`CryptoKind`] fourcc identifying this cryptosystem.
37 fn kind(&self) -> CryptoKind;
38 /// Component guard for the parent [`Crypto`] registry, used to reach cross-cryptosystem caches.
39 fn crypto(&self) -> VeilidComponentGuard<'_, Crypto>;
40
41 // Cached Operations
42 /// Diffie-Hellman shared secret for the given public/secret key pair, memoized in the
43 /// [`Crypto`] DH cache to avoid recomputing the same exchange. See [`compute_dh`](Self::compute_dh).
44 ///
45 /// Local CPU only; on a cache miss runs the same scalar multiplication as `compute_dh`. Takes the
46 /// `Crypto` inner lock to read/update the cache.
47 ///
48 /// Errors `VeilidAPIError::Generic` if `key` or `secret` carries the wrong kind or length, plus
49 /// the [`compute_dh`](Self::compute_dh) errors on a cache miss.
50 fn cached_dh(&self, key: &PublicKey, secret: &SecretKey) -> VeilidAPIResult<SharedSecret>;
51
52 // Generation
53 /// Fill a new `Vec` of `len` bytes from the cryptographic RNG.
54 fn random_bytes(&self, len: usize) -> Vec<u8>;
55 /// Hash a password with the given salt, returning a self-describing PHC hash string suitable
56 /// for storage and later [`verify_password`](Self::verify_password).
57 ///
58 /// CPU-heavy (Argon2); blocks the calling thread for the full KDF. No network or disk.
59 ///
60 /// Errors `VeilidAPIError::Generic` if `salt` length is outside the Argon2 bounds or the KDF
61 /// itself fails, `VeilidAPIError::ParseError` if the salt fails base64 encoding.
62 fn hash_password(&self, password: &[u8], salt: &[u8]) -> VeilidAPIResult<String>;
63 /// Check a password against a PHC hash string produced by [`hash_password`](Self::hash_password).
64 /// Returns `Ok(false)` on mismatch; errors only on a malformed hash string.
65 ///
66 /// CPU-heavy (Argon2); blocks the calling thread for the full KDF. No network or disk.
67 ///
68 /// Errors `VeilidAPIError::ParseError` if `password_hash` is not a valid PHC string.
69 fn verify_password(&self, password: &[u8], password_hash: &str) -> VeilidAPIResult<bool>;
70 /// Derive a shared secret from a password and salt via a password-hashing KDF. Deterministic:
71 /// the same password and salt always yield the same secret. Distinct from
72 /// [`generate_shared_secret`](Self::generate_shared_secret), which uses key exchange.
73 ///
74 /// CPU-heavy (Argon2); blocks the calling thread for the full KDF. No network or disk.
75 ///
76 /// Errors `VeilidAPIError::Generic` if `salt` length is outside the Argon2 bounds or the KDF fails.
77 fn derive_shared_secret(&self, password: &[u8], salt: &[u8]) -> VeilidAPIResult<SharedSecret>;
78 /// A fresh random nonce of [`nonce_length`](Self::nonce_length) bytes.
79 fn random_nonce(&self) -> Nonce;
80 /// A fresh random shared secret of [`shared_secret_length`](Self::shared_secret_length) bytes.
81 fn random_shared_secret(&self) -> SharedSecret;
82 /// Raw Diffie-Hellman shared secret for the given public/secret key pair, with no caching.
83 ///
84 /// Local CPU only (scalar multiplication); recomputes every call. Use
85 /// [`cached_dh`](Self::cached_dh) to memoize repeated exchanges.
86 ///
87 /// Errors `VeilidAPIError::Internal` if `key` is not a valid curve point, `VeilidAPIError::Generic`
88 /// if the exchange is non-contributory (low-order public key).
89 fn compute_dh(&self, key: &PublicKey, secret: &SecretKey) -> VeilidAPIResult<SharedSecret>;
90 /// Derive a domain-separated shared secret from a key exchange: computes the DH secret, then
91 /// hashes it together with `domain` and the Veilid API domain tag. Distinct `domain` values
92 /// yield independent secrets from the same key pair.
93 ///
94 /// Errors with the [`compute_dh`](Self::compute_dh) errors if the key exchange fails.
95 fn generate_shared_secret(
96 &self,
97 key: &PublicKey,
98 secret: &SecretKey,
99 domain: &[u8],
100 ) -> VeilidAPIResult<SharedSecret> {
101 let dh = self.compute_dh(key, secret)?;
102 let hash = self.generate_hash(&[&dh.into_value(), domain, VEILID_DOMAIN_API].concat());
103 Ok(SharedSecret::new(
104 hash.kind(),
105 BareSharedSecret::new(&hash.into_value()),
106 ))
107 }
108 /// Seal `plaintext` to `recipient` with HPKE base mode (RFC 9180), single-shot. `aad` is
109 /// authenticated but not encrypted, and must be supplied again to open. Returns a
110 /// self-describing sealed blob: a version byte, this cryptosystem's kind fourcc, the
111 /// encapsulated KEM key, and the ciphertext with appended tag.
112 ///
113 /// Sealing is one-way: only the holder of the recipient's [`DecapsulationKey`] can open the
114 /// blob; the sealer cannot decrypt what it just sealed. This differs from the DH pattern,
115 /// where the shared secret let the encrypting party decrypt its own blobs. A sealer that
116 /// needs to re-read stored blobs must also seal them to its own key. Callers who already
117 /// share a symmetric key want [`encrypt_aead`](Self::encrypt_aead) instead; HPKE is for
118 /// encrypting to a recipient's key when no shared secret exists.
119 ///
120 /// Errors `VeilidAPIError::InvalidArgument` if `recipient` is not a valid key,
121 /// `VeilidAPIError::Generic` if encapsulation fails (including a low-order key).
122 fn hpke_seal(
123 &self,
124 recipient: &EncapsulationKey,
125 aad: &[u8],
126 plaintext: &[u8],
127 ) -> VeilidAPIResult<Vec<u8>>;
128 /// Open a sealed blob produced by [`hpke_seal`](Self::hpke_seal) with the recipient `secret`,
129 /// returning the plaintext. `aad` must match what was supplied at seal. Only the recipient
130 /// can open a sealed blob; the sealer cannot.
131 ///
132 /// Errors `VeilidAPIError::ParseError` if the blob is truncated or its version is unknown,
133 /// `VeilidAPIError::InvalidArgument` if the blob's kind is not this cryptosystem's kind or
134 /// `secret` is not a valid key, `VeilidAPIError::Generic` if decryption fails (tampered blob,
135 /// wrong recipient, or mismatched `aad`).
136 fn hpke_open(
137 &self,
138 secret: &DecapsulationKey,
139 aad: &[u8],
140 sealed: &[u8],
141 ) -> VeilidAPIResult<Vec<u8>>;
142 /// Generate a fresh random signing key pair for this cryptosystem.
143 fn generate_keypair(&self) -> KeyPair;
144 /// Generate a fresh random KEM key pair for this cryptosystem.
145 fn generate_kem_keypair(&self) -> KemKeyPair;
146 /// Derive the KEM encapsulation key corresponding to a signing public key.
147 ///
148 /// VLD0-only bridge (ed25519 to x25519): kinds whose signing and KEM keys are unrelated
149 /// (VLD1 ML-DSA/ML-KEM) error `VeilidAPIError::Unimplemented`.
150 ///
151 /// Errors `VeilidAPIError::InvalidArgument` if `key` is not a valid signing public key.
152 fn encapsulation_key_from_signing_key(
153 &self,
154 key: &PublicKey,
155 ) -> VeilidAPIResult<EncapsulationKey>;
156 /// Derive the KEM decapsulation key corresponding to a signing secret key.
157 ///
158 /// VLD0-only bridge (ed25519 to x25519): kinds whose signing and KEM keys are unrelated
159 /// (VLD1 ML-DSA/ML-KEM) error `VeilidAPIError::Unimplemented`.
160 ///
161 /// Errors `VeilidAPIError::InvalidArgument` if `secret` is not a valid signing secret key.
162 fn decapsulation_key_from_signing_secret(
163 &self,
164 secret: &SecretKey,
165 ) -> VeilidAPIResult<DecapsulationKey>;
166 /// Hash a byte slice, returning a digest tagged with this cryptosystem's kind.
167 fn generate_hash(&self, data: &[u8]) -> HashDigest;
168 /// Hash a stream by reading it to end, returning the digest as a `PublicKey` (the digest and
169 /// public key share a byte length in this cryptosystem). Errors on read failure.
170 ///
171 /// Errors `VeilidAPIError::Generic` if reading from `reader` fails.
172 fn generate_hash_reader(&self, reader: &mut dyn std::io::Read) -> VeilidAPIResult<PublicKey>;
173
174 // Validation
175 /// Byte length of a shared secret.
176 fn shared_secret_length(&self) -> usize;
177 /// Byte length of a nonce.
178 fn nonce_length(&self) -> usize;
179 /// Byte length of a hash digest.
180 fn hash_digest_length(&self) -> usize;
181 /// Byte length of a public key.
182 fn public_key_length(&self) -> usize;
183 /// Byte length of a secret key.
184 fn secret_key_length(&self) -> usize;
185 /// Byte length of a KEM encapsulation key.
186 fn encapsulation_key_length(&self) -> usize;
187 /// Byte length of a KEM decapsulation key.
188 fn decapsulation_key_length(&self) -> usize;
189 /// Byte length of a signature.
190 fn signature_length(&self) -> usize;
191 /// Default salt length in bytes for password hashing and KDF operations.
192 fn default_salt_length(&self) -> usize;
193 /// Bytes an AEAD operation adds to the ciphertext (the authentication tag length).
194 fn aead_overhead(&self) -> usize;
195
196 /// Verify a shared secret carries this cryptosystem's kind and the correct length.
197 ///
198 /// Errors `VeilidAPIError::Generic` if `secret` has the wrong kind or length.
199 fn check_shared_secret(&self, secret: &SharedSecret) -> VeilidAPIResult<()> {
200 if secret.kind() != self.kind() {
201 apibail_generic!("incorrect shared secret kind");
202 }
203 if secret.value().len() != self.shared_secret_length() {
204 apibail_generic!(
205 "invalid shared secret length: {} != {}",
206 secret.value().len(),
207 self.shared_secret_length()
208 );
209 }
210 Ok(())
211 }
212 /// Verify a nonce has the correct length.
213 ///
214 /// Errors `VeilidAPIError::Generic` if `nonce` has the wrong length.
215 fn check_nonce(&self, nonce: &Nonce) -> VeilidAPIResult<()> {
216 if nonce.len() != self.nonce_length() {
217 apibail_generic!(
218 "invalid nonce length: {} != {}",
219 nonce.len(),
220 self.nonce_length()
221 );
222 }
223 Ok(())
224 }
225 /// Verify a hash digest carries this cryptosystem's kind and the correct length.
226 ///
227 /// Errors `VeilidAPIError::Generic` if `hash` has the wrong kind or length.
228 fn check_hash_digest(&self, hash: &HashDigest) -> VeilidAPIResult<()> {
229 if hash.kind() != self.kind() {
230 apibail_generic!("incorrect hash digest kind");
231 }
232 if hash.value().len() != self.hash_digest_length() {
233 apibail_generic!(
234 "invalid hash digest length: {} != {}",
235 hash.value().len(),
236 self.hash_digest_length()
237 );
238 }
239 Ok(())
240 }
241 /// Verify a public key carries this cryptosystem's kind and the correct length.
242 ///
243 /// Errors `VeilidAPIError::Generic` if `key` has the wrong kind or length.
244 fn check_public_key(&self, key: &PublicKey) -> VeilidAPIResult<()> {
245 if key.kind() != self.kind() {
246 apibail_generic!("incorrect public key kind");
247 }
248 if key.value().len() != self.public_key_length() {
249 apibail_generic!(
250 "invalid public key length: {} != {}",
251 key.value().len(),
252 self.public_key_length()
253 );
254 }
255 Ok(())
256 }
257 /// Verify a secret key carries this cryptosystem's kind and the correct length.
258 ///
259 /// Errors `VeilidAPIError::Generic` if `key` has the wrong kind or length.
260 fn check_secret_key(&self, key: &SecretKey) -> VeilidAPIResult<()> {
261 if key.kind() != self.kind() {
262 apibail_generic!("incorrect secret key kind");
263 }
264 if key.value().len() != self.secret_key_length() {
265 apibail_generic!(
266 "invalid secret key length: {} != {}",
267 key.value().len(),
268 self.secret_key_length()
269 );
270 }
271 Ok(())
272 }
273 /// Verify a signature carries this cryptosystem's kind and the correct length.
274 ///
275 /// Errors `VeilidAPIError::Generic` if `signature` has the wrong kind or length.
276 fn check_signature(&self, signature: &Signature) -> VeilidAPIResult<()> {
277 if signature.kind() != self.kind() {
278 apibail_generic!("incorrect signature kind");
279 }
280 if signature.value().len() != self.signature_length() {
281 apibail_generic!(
282 "invalid signature length: {} != {}",
283 signature.value().len(),
284 self.signature_length()
285 );
286 }
287 Ok(())
288 }
289 /// Verify a key pair's kind and that both its public and secret keys have the correct length.
290 /// This is a structural check only; it does not verify the keys form a valid pair (see
291 /// [`validate_keypair`](Self::validate_keypair)).
292 ///
293 /// Errors `VeilidAPIError::Generic` if the pair or either key has the wrong kind or length.
294 fn check_keypair(&self, keypair: &KeyPair) -> VeilidAPIResult<()> {
295 if keypair.kind() != self.kind() {
296 apibail_generic!("incorrect keypair kind");
297 }
298 self.check_public_key(&keypair.key())?;
299 self.check_secret_key(&keypair.secret())?;
300 Ok(())
301 }
302
303 /// Check that a public and secret key form a usable signing pair by signing test data and
304 /// verifying it. Returns `Ok(false)` if they do not match; errors only on a malformed key.
305 ///
306 /// Errors `VeilidAPIError::Generic` if `key` or `secret` has the wrong kind or length.
307 fn validate_keypair(&self, key: &PublicKey, secret: &SecretKey) -> VeilidAPIResult<bool>;
308 /// Recompute the hash of `data` and compare it against `hash`. Returns `Ok(true)` on match.
309 ///
310 /// Errors `VeilidAPIError::Generic` if `hash` has the wrong kind or length.
311 fn validate_hash(&self, data: &[u8], hash: &HashDigest) -> VeilidAPIResult<bool>;
312 /// Hash a stream by reading it to end and compare against `hash`. Returns `Ok(true)` on match;
313 /// errors on read failure.
314 ///
315 /// Errors `VeilidAPIError::Generic` if `hash` has the wrong kind or length, or if reading from
316 /// `reader` fails.
317 fn validate_hash_reader(
318 &self,
319 reader: &mut dyn std::io::Read,
320 hash: &HashDigest,
321 ) -> VeilidAPIResult<bool>;
322
323 // Authentication
324 /// Sign `data` with the given key pair, returning a detached signature.
325 ///
326 /// Errors `VeilidAPIError::Generic` if `public_key` or `secret` has the wrong kind or length,
327 /// `VeilidAPIError::ParseError` if they do not form a valid ed25519 keypair,
328 /// `VeilidAPIError::Internal` if signing fails.
329 fn sign(
330 &self,
331 public_key: &PublicKey,
332 secret: &SecretKey,
333 data: &[u8],
334 ) -> VeilidAPIResult<Signature>;
335 /// Sign the bytes of `data[range]` and write the signature into `data` at `sig_idx`, in place.
336 /// Used to sign a buffer and embed its own signature. Errors if `range` or the signature slot
337 /// is out of bounds.
338 ///
339 /// Errors `VeilidAPIError::Generic` if `public_key` or `secret` has the wrong kind or length,
340 /// `VeilidAPIError::ParseError` if they do not form a valid ed25519 keypair or `sig_idx` is out
341 /// of bounds, `VeilidAPIError::InvalidArgument` if `range` is out of bounds,
342 /// `VeilidAPIError::Internal` if signing fails.
343 fn sign_in_place(
344 &self,
345 public_key: &PublicKey,
346 secret: &SecretKey,
347 data: &mut [u8],
348 range: Range<usize>,
349 sig_idx: usize,
350 ) -> VeilidAPIResult<()>;
351 /// Verify a detached `signature` over `data` for `public_key`. Returns `Ok(true)` if valid,
352 /// `Ok(false)` if not; errors only on a malformed key or signature.
353 ///
354 /// Errors `VeilidAPIError::Generic` if `public_key` or `signature` has the wrong kind or length,
355 /// `VeilidAPIError::ParseError` if `public_key` is not a valid ed25519 point. A signature that
356 /// does not match returns `Ok(false)`, not an error.
357 fn verify(
358 &self,
359 public_key: &PublicKey,
360 data: &[u8],
361 signature: &Signature,
362 ) -> VeilidAPIResult<bool>;
363 /// Verify a signature embedded in `data` at `sig_idx` against the bytes of `data[range]`.
364 /// The inverse of [`sign_in_place`](Self::sign_in_place). Returns `Ok(true)` if valid.
365 ///
366 /// Errors `VeilidAPIError::Generic` if `public_key` has the wrong kind or length,
367 /// `VeilidAPIError::ParseError` if `public_key` is not a valid ed25519 point,
368 /// `VeilidAPIError::Internal` if `range` or `sig_idx` is out of bounds. A signature that does
369 /// not match returns `Ok(false)`, not an error.
370 fn verify_in_place(
371 &self,
372 public_key: &PublicKey,
373 data: &[u8],
374 range: Range<usize>,
375 sig_idx: usize,
376 ) -> VeilidAPIResult<bool>;
377
378 // AEAD Encrypt/Decrypt
379 /// Decrypt and authenticate `body` in place, removing the authentication tag on success.
380 /// `associated_data` must match what was supplied at encryption. Errors if authentication
381 /// fails (tampered ciphertext, wrong key/nonce, or mismatched associated data).
382 ///
383 /// Errors `VeilidAPIError::Generic` if `shared_secret` has the wrong kind or length, or if
384 /// authentication fails; `VeilidAPIError::Internal` on an internal length conversion failure.
385 fn decrypt_in_place_aead(
386 &self,
387 body: &mut dyn CryptoSystemBuffer,
388 nonce: &Nonce,
389 shared_secret: &SharedSecret,
390 associated_data: Option<&[u8]>,
391 ) -> VeilidAPIResult<()>;
392 /// Decrypt and authenticate `body`, returning the plaintext. Allocating form of
393 /// [`decrypt_in_place_aead`](Self::decrypt_in_place_aead).
394 ///
395 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length,
396 /// or if authentication fails; `VeilidAPIError::Internal` on an internal length conversion failure.
397 fn decrypt_aead(
398 &self,
399 body: &[u8],
400 nonce: &Nonce,
401 shared_secret: &SharedSecret,
402 associated_data: Option<&[u8]>,
403 ) -> VeilidAPIResult<Vec<u8>>;
404 /// Encrypt and authenticate `body` in place, appending the authentication tag. `associated_data`
405 /// is authenticated but not encrypted, and must be supplied again at decryption. The same nonce
406 /// must never be reused with the same shared secret.
407 ///
408 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length;
409 /// `VeilidAPIError::Internal` on an internal length conversion failure.
410 fn encrypt_in_place_aead(
411 &self,
412 body: &mut dyn CryptoSystemBuffer,
413 nonce: &Nonce,
414 shared_secret: &SharedSecret,
415 associated_data: Option<&[u8]>,
416 ) -> VeilidAPIResult<()>;
417 /// Encrypt and authenticate `body`, returning the ciphertext with appended tag. Allocating
418 /// form of [`encrypt_in_place_aead`](Self::encrypt_in_place_aead).
419 ///
420 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length;
421 /// `VeilidAPIError::Internal` on an internal length conversion failure.
422 fn encrypt_aead(
423 &self,
424 body: &[u8],
425 nonce: &Nonce,
426 shared_secret: &SharedSecret,
427 associated_data: Option<&[u8]>,
428 ) -> VeilidAPIResult<Vec<u8>>;
429
430 // NoAuth Encrypt/Decrypt
431 /// Apply the stream cipher to `body` in place, without authentication. Same operation for both
432 /// directions: re-applying with the same nonce and secret reverses it. Provides confidentiality
433 /// only, no integrity; callers needing tamper detection must use the AEAD variants.
434 ///
435 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length;
436 /// `VeilidAPIError::Internal` on an internal length conversion failure.
437 fn crypt_in_place_no_auth(
438 &self,
439 body: &mut [u8],
440 nonce: &Nonce,
441 shared_secret: &SharedSecret,
442 ) -> VeilidAPIResult<()>;
443 /// Apply the stream cipher from `in_buf` into `out_buf` (buffer-to-buffer), without
444 /// authentication. `out_buf` must be at least as long as `in_buf`. See
445 /// [`crypt_in_place_no_auth`](Self::crypt_in_place_no_auth) for the integrity caveat.
446 ///
447 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length;
448 /// `VeilidAPIError::Internal` on an internal length conversion failure.
449 fn crypt_b2b_no_auth(
450 &self,
451 in_buf: &[u8],
452 out_buf: &mut [u8],
453 nonce: &Nonce,
454 shared_secret: &SharedSecret,
455 ) -> VeilidAPIResult<()>;
456 /// Stream-cipher `body` into a freshly allocated 8-byte-aligned buffer, without authentication.
457 ///
458 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length;
459 /// `VeilidAPIError::Internal` on an internal length conversion failure.
460 fn crypt_no_auth_aligned_8(
461 &self,
462 body: &[u8],
463 nonce: &Nonce,
464 shared_secret: &SharedSecret,
465 ) -> VeilidAPIResult<Vec<u8>>;
466 /// Stream-cipher `body` into a freshly allocated unaligned buffer, without authentication.
467 ///
468 /// Errors `VeilidAPIError::Generic` if `nonce` or `shared_secret` has the wrong kind or length;
469 /// `VeilidAPIError::Internal` on an internal length conversion failure.
470 fn crypt_no_auth_unaligned(
471 &self,
472 body: &[u8],
473 nonce: &Nonce,
474 shared_secret: &SharedSecret,
475 ) -> VeilidAPIResult<Vec<u8>>;
476}