Skip to main content

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}