sentinel_crypto/
lib.rs

1//! # Sentinel Crypto
2//!
3//! A modular, secure cryptographic library for the Sentinel document database.
4//! This crate provides hashing and digital signature operations with a focus
5//! on maintainability, security, and performance.
6//!
7//! ## Design Principles
8//!
9//! - **Modular Architecture**: Traits are separated from implementations, allowing easy algorithm
10//!   switching and testing.
11//! - **Security First**: All sensitive data is automatically zeroized. Sealed traits prevent
12//!   external insecure implementations.
13//! - **Unified Error Handling**: Single `CryptoError` enum for consistent error handling across all
14//!   operations.
15//! - **RustCrypto Only**: Uses only audited rustcrypto crates (blake3, ed25519-dalek) for
16//!   cryptographic primitives.
17//! - **Parallel Performance**: BLAKE3 supports parallel computation for large inputs.
18//!
19//! ## Security Features
20//!
21//! - **Memory Protection**: `SigningKey` and other sensitive types automatically zeroize memory
22//!   when dropped.
23//! - **Sealed Traits**: Prevents external implementations that might bypass security.
24//! - **Type Safety**: Associated types ensure compile-time correctness.
25//! - **Error Abstraction**: Errors don't leak sensitive information.
26//!
27//! ## Performance
28//!
29//! - BLAKE3: High-performance hash function with parallel support.
30//! - Ed25519: Fast elliptic curve signatures with 128-bit security.
31//!
32//! ## Usage
33//!
34//! ```rust
35//! use sentinel_crypto::{hash_data, sign_hash, verify_signature, SigningKey};
36//!
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! let data = serde_json::json!({"key": "value"});
39//! let hash = hash_data(&data).await?;
40//!
41//! let key = SigningKey::from_bytes(&rand::random::<[u8; 32]>());
42//! let signature = sign_hash(&hash, &key).await?;
43//!
44//! let public_key = key.verifying_key();
45//! assert!(verify_signature(&hash, &signature, &public_key).await?);
46//! # Ok(())
47//! # }
48//! ```
49
50pub mod crypto_config;
51pub mod encrypt;
52pub mod encrypt_trait;
53pub mod error;
54pub mod hash;
55pub mod hash_trait;
56pub mod key_derivation;
57pub mod key_derivation_trait;
58pub mod sign;
59pub mod sign_trait;
60
61// Re-export crypto types for convenience
62pub use crypto_config::*;
63pub use ed25519_dalek::{Signature, SigningKey, VerifyingKey};
64pub use encrypt::{Aes256GcmSivEncryptor, Ascon128Encryptor, EncryptionKeyManager, XChaCha20Poly1305Encryptor};
65pub use encrypt_trait::EncryptionAlgorithm;
66pub use error::CryptoError;
67pub use hash_trait::HashFunction;
68pub use key_derivation::{Argon2KeyDerivation, Pbkdf2KeyDerivation};
69pub use key_derivation_trait::KeyDerivationFunction;
70// Convenience functions using default implementations
71use serde_json::Value;
72pub use sign::{Ed25519Signer, SigningKeyManager};
73pub use sign_trait::SignatureAlgorithm;
74use tracing::{debug, trace};
75
76/// Computes the hash of the given JSON data using the globally configured algorithm.
77pub async fn hash_data(data: &Value) -> Result<String, CryptoError> {
78    trace!("Hashing data using global config");
79    let config = get_global_crypto_config().await?;
80    let result = match config.hash_algorithm {
81        HashAlgorithmChoice::Blake3 => crate::hash::Blake3Hasher::hash_data(data),
82    };
83    if let Ok(ref hash) = result {
84        debug!("Data hashed successfully: {}", hash);
85    }
86    result
87}
88
89/// Signs the given hash using the globally configured algorithm.
90pub async fn sign_hash(hash: &str, private_key: &SigningKey) -> Result<String, CryptoError> {
91    trace!("Signing hash using global config");
92    let config = get_global_crypto_config().await?;
93    let result = match config.signature_algorithm {
94        SignatureAlgorithmChoice::Ed25519 => Ed25519Signer::sign_hash(hash, private_key),
95    };
96    if let Ok(ref sig) = result {
97        debug!("Hash signed successfully: {}", sig);
98    }
99    result
100}
101
102/// Verifies the signature of the given hash using the globally configured algorithm.
103pub async fn verify_signature(hash: &str, signature: &str, public_key: &VerifyingKey) -> Result<bool, CryptoError> {
104    trace!("Verifying signature using global config");
105    let config = get_global_crypto_config().await?;
106    let result = match config.signature_algorithm {
107        SignatureAlgorithmChoice::Ed25519 => Ed25519Signer::verify_signature(hash, signature, public_key),
108    };
109    debug!("Signature verification result: {:?}", result);
110    result
111}
112
113/// Encrypts data using the globally configured algorithm.
114pub async fn encrypt_data(data: &[u8], key: &[u8; 32]) -> Result<String, CryptoError> {
115    trace!(
116        "Encrypting data using global config, data length: {}",
117        data.len()
118    );
119    let config = get_global_crypto_config().await?;
120    let result = match config.encryption_algorithm {
121        EncryptionAlgorithmChoice::XChaCha20Poly1305 => XChaCha20Poly1305Encryptor::encrypt_data(data, key),
122        EncryptionAlgorithmChoice::Aes256GcmSiv => Aes256GcmSivEncryptor::encrypt_data(data, key),
123        EncryptionAlgorithmChoice::Ascon128 => Ascon128Encryptor::encrypt_data(data, key),
124    };
125    if let Ok(ref encrypted) = result {
126        debug!(
127            "Data encrypted successfully, encrypted length: {}",
128            encrypted.len()
129        );
130    }
131    result
132}
133
134/// Decrypts data using the globally configured algorithm.
135pub async fn decrypt_data(encrypted_data: &str, key: &[u8; 32]) -> Result<Vec<u8>, CryptoError> {
136    trace!(
137        "Decrypting data using global config, encrypted length: {}",
138        encrypted_data.len()
139    );
140    let config = get_global_crypto_config().await?;
141    let result = match config.encryption_algorithm {
142        EncryptionAlgorithmChoice::XChaCha20Poly1305 => XChaCha20Poly1305Encryptor::decrypt_data(encrypted_data, key),
143        EncryptionAlgorithmChoice::Aes256GcmSiv => Aes256GcmSivEncryptor::decrypt_data(encrypted_data, key),
144        EncryptionAlgorithmChoice::Ascon128 => Ascon128Encryptor::decrypt_data(encrypted_data, key),
145    };
146    if let Ok(ref decrypted) = result {
147        debug!(
148            "Data decrypted successfully, plaintext length: {}",
149            decrypted.len()
150        );
151    }
152    result
153}
154
155/// Derives a 32-byte key from a passphrase using the globally configured algorithm.
156/// Returns the randomly generated salt and the derived key.
157pub async fn derive_key_from_passphrase(passphrase: &str) -> Result<(Vec<u8>, [u8; 32]), CryptoError> {
158    trace!("Deriving key from passphrase using global config");
159    let config = get_global_crypto_config().await?;
160    let result = match config.key_derivation_algorithm {
161        KeyDerivationAlgorithmChoice::Argon2id => Argon2KeyDerivation::derive_key_from_passphrase(passphrase),
162        KeyDerivationAlgorithmChoice::Pbkdf2 => Pbkdf2KeyDerivation::derive_key_from_passphrase(passphrase),
163    };
164    if result.is_ok() {
165        debug!("Key derivation completed successfully");
166    }
167    result
168}
169
170/// Derives a 32-byte key from a passphrase using the provided salt and the globally configured
171/// algorithm.
172pub async fn derive_key_from_passphrase_with_salt(passphrase: &str, salt: &[u8]) -> Result<[u8; 32], CryptoError> {
173    trace!("Deriving key from passphrase with salt using global config");
174    let config = get_global_crypto_config().await?;
175    let result = match config.key_derivation_algorithm {
176        KeyDerivationAlgorithmChoice::Argon2id => {
177            Argon2KeyDerivation::derive_key_from_passphrase_with_salt(passphrase, salt)
178        },
179        KeyDerivationAlgorithmChoice::Pbkdf2 => {
180            Pbkdf2KeyDerivation::derive_key_from_passphrase_with_salt(passphrase, salt)
181        },
182    };
183    if result.is_ok() {
184        debug!("Key derivation with salt completed successfully");
185    }
186    result
187}
188
189#[cfg(test)]
190mod tests {
191    use tracing_subscriber;
192
193    use super::*;
194
195    fn init_logging() {
196        let _ = tracing_subscriber::fmt()
197            .with_max_level(tracing::Level::TRACE)
198            .try_init();
199    }
200
201    #[tokio::test]
202    async fn test_hash_data() {
203        init_logging();
204        let data = serde_json::json!({"key": "value", "number": 42});
205        let hash = hash_data(&data).await.unwrap();
206        assert_eq!(hash.len(), 64);
207        let hash2 = hash_data(&data).await.unwrap();
208        assert_eq!(hash, hash2);
209    }
210
211    #[tokio::test]
212    #[serial_test::serial]
213    async fn test_set_global_crypto_config_already_set() {
214        init_logging();
215        reset_global_crypto_config_for_tests().await;
216        let config = CryptoConfig {
217            hash_algorithm:           HashAlgorithmChoice::Blake3,
218            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
219            encryption_algorithm:     EncryptionAlgorithmChoice::Aes256GcmSiv, // Different from default
220            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Argon2id,
221        };
222
223        // First set should succeed
224        let result1 = set_global_crypto_config(config.clone()).await;
225        assert!(result1.is_ok());
226
227        // Second set should succeed but emit a warning
228        let result2 = set_global_crypto_config(config).await;
229        assert!(result2.is_ok());
230    }
231
232    #[tokio::test]
233    #[serial_test::serial]
234    async fn test_aes256gcm_siv_encryption() {
235        init_logging();
236        reset_global_crypto_config_for_tests().await;
237        let config = CryptoConfig {
238            hash_algorithm:           HashAlgorithmChoice::Blake3,
239            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
240            encryption_algorithm:     EncryptionAlgorithmChoice::Aes256GcmSiv,
241            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Argon2id,
242        };
243        set_global_crypto_config(config).await.unwrap();
244
245        let key = [0u8; 32];
246        let test_data = b"test data";
247        let encrypted = encrypt_data(test_data, &key).await.unwrap();
248        // Immediately decrypt to avoid config changes
249        let decrypted = decrypt_data(&encrypted, &key).await.unwrap();
250        assert_eq!(test_data, decrypted.as_slice());
251    }
252
253    #[tokio::test]
254    #[serial_test::serial]
255    async fn test_ascon128_encryption() {
256        init_logging();
257        reset_global_crypto_config_for_tests().await;
258        let config = CryptoConfig {
259            hash_algorithm:           HashAlgorithmChoice::Blake3,
260            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
261            encryption_algorithm:     EncryptionAlgorithmChoice::Ascon128,
262            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Argon2id,
263        };
264        set_global_crypto_config(config).await.unwrap();
265
266        let key = [0u8; 32];
267        let test_data = b"test data";
268        let encrypted = encrypt_data(test_data, &key).await.unwrap();
269        // Immediately decrypt to avoid config changes
270        let decrypted = decrypt_data(&encrypted, &key).await.unwrap();
271        assert_eq!(test_data, decrypted.as_slice());
272    }
273
274    #[tokio::test]
275    #[serial_test::serial]
276    async fn test_pbkdf2_key_derivation() {
277        init_logging();
278        reset_global_crypto_config_for_tests().await;
279        let config = CryptoConfig {
280            hash_algorithm:           HashAlgorithmChoice::Blake3,
281            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
282            encryption_algorithm:     EncryptionAlgorithmChoice::XChaCha20Poly1305,
283            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Pbkdf2,
284        };
285        let _ = set_global_crypto_config(config).await; // Ignore if already set
286
287        let derived = derive_key_from_passphrase("test passphrase").await.unwrap();
288        assert_eq!(derived.1.len(), 32);
289        assert_eq!(derived.0.len(), 32);
290    }
291
292    #[tokio::test]
293    async fn test_sign_and_verify_hash() {
294        init_logging();
295        let data = serde_json::json!({"key": "value"});
296        let hash = hash_data(&data).await.unwrap();
297        assert_eq!(hash.len(), 64);
298
299        let key_bytes = [0u8; 32];
300        let key = SigningKey::from_bytes(&key_bytes);
301        let signature = sign_hash(&hash, &key).await.unwrap();
302
303        let public_key = key.verifying_key();
304        let verified = verify_signature(&hash, &signature, &public_key)
305            .await
306            .unwrap();
307        assert!(verified);
308
309        // Test invalid signature (wrong hash)
310        let invalid_verified = verify_signature("wrong_hash", &signature, &public_key)
311            .await
312            .unwrap();
313        assert!(!invalid_verified);
314
315        // Test invalid hex
316        let hex_error = verify_signature(&hash, "invalid", &public_key).await;
317        assert!(hex_error.is_err());
318    }
319
320    #[tokio::test]
321    #[serial_test::serial]
322    async fn test_decrypt_corrupted_data() {
323        init_logging();
324        reset_global_crypto_config_for_tests().await;
325        let config = CryptoConfig {
326            hash_algorithm:           HashAlgorithmChoice::Blake3,
327            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
328            encryption_algorithm:     EncryptionAlgorithmChoice::XChaCha20Poly1305,
329            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Argon2id,
330        };
331        let _ = set_global_crypto_config(config).await;
332
333        let key = [0u8; 32];
334        let test_data = b"test data";
335        let encrypted = encrypt_data(test_data, &key).await.unwrap();
336
337        // Corrupt the encrypted data
338        let mut corrupted = encrypted.clone();
339        if let Some(last) = corrupted.chars().last() {
340            let new_last = if last == 'a' { 'b' } else { 'a' };
341            corrupted = corrupted[.. encrypted.len() - 1].to_string() + &new_last.to_string();
342        }
343
344        let result = decrypt_data(&corrupted, &key).await;
345        assert!(result.is_err());
346        // Should be Decryption error
347    }
348
349    #[tokio::test]
350    async fn test_verify_signature_invalid_hex() {
351        init_logging();
352        let data = serde_json::json!({"key": "value"});
353        let hash = hash_data(&data).await.unwrap();
354
355        let key_bytes = [0u8; 32];
356        let key = SigningKey::from_bytes(&key_bytes);
357
358        // Test with invalid hex
359        let result = verify_signature(&hash, "invalid_hex", &key.verifying_key()).await;
360        assert!(result.is_err());
361        // Should be Hex error
362    }
363
364    #[tokio::test]
365    async fn test_verify_signature_wrong_signature() {
366        init_logging();
367        let data = serde_json::json!({"key": "value"});
368        let hash = hash_data(&data).await.unwrap();
369
370        let key_bytes = [0u8; 32];
371        let key = SigningKey::from_bytes(&key_bytes);
372        let signature = sign_hash(&hash, &key).await.unwrap();
373
374        let wrong_key_bytes = [1u8; 32];
375        let wrong_key = SigningKey::from_bytes(&wrong_key_bytes);
376
377        let result = verify_signature(&hash, &signature, &wrong_key.verifying_key()).await;
378        assert!(matches!(result, Ok(false)));
379        // Verification should fail
380    }
381
382    #[tokio::test]
383    #[serial_test::serial]
384    async fn test_derive_key_from_passphrase_with_empty_passphrase() {
385        init_logging();
386        reset_global_crypto_config_for_tests().await;
387        let config = CryptoConfig {
388            hash_algorithm:           HashAlgorithmChoice::Blake3,
389            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
390            encryption_algorithm:     EncryptionAlgorithmChoice::XChaCha20Poly1305,
391            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Argon2id,
392        };
393        set_global_crypto_config(config).await.unwrap();
394
395        let passphrase = "";
396        let salt = b"1234567890123456"; // 16-byte salt as recommended
397
398        let result = derive_key_from_passphrase_with_salt(passphrase, salt).await;
399        // Empty passphrase should succeed (crypto libraries allow it)
400        assert!(result.is_ok());
401    }
402
403    #[tokio::test]
404    #[serial_test::serial]
405    async fn test_decrypt_short_ciphertext() {
406        init_logging();
407        reset_global_crypto_config_for_tests().await;
408        let config = CryptoConfig {
409            hash_algorithm:           HashAlgorithmChoice::Blake3,
410            signature_algorithm:      SignatureAlgorithmChoice::Ed25519,
411            encryption_algorithm:     EncryptionAlgorithmChoice::XChaCha20Poly1305,
412            key_derivation_algorithm: KeyDerivationAlgorithmChoice::Argon2id,
413        };
414        let _ = set_global_crypto_config(config).await;
415
416        let key = [0u8; 32];
417        let short_ciphertext = "short";
418
419        let result = decrypt_data(short_ciphertext, &key).await;
420        assert!(result.is_err());
421        // Should be Decryption error
422    }
423}