shadowforge_lib/adapters/
crypto.rs1use bytes::Bytes;
8use rand_chacha::ChaCha20Rng;
9use rand_core::SeedableRng;
10
11use crate::domain::crypto::{
12 decapsulate_kem, decrypt_aes_gcm, encapsulate_kem, encrypt_aes_gcm, generate_dsa_keypair,
13 generate_kem_keypair, sign_dsa, verify_dsa,
14};
15use crate::domain::errors::CryptoError;
16use crate::domain::ports::{Encryptor, Signer, SymmetricCipher};
17use crate::domain::types::{KeyPair, Signature};
18
19fn fresh_rng() -> ChaCha20Rng {
23 ChaCha20Rng::from_rng(&mut rand::rng())
24}
25
26#[derive(Debug, Default)]
34pub struct MlKemEncryptor;
35
36impl Encryptor for MlKemEncryptor {
37 fn generate_keypair(&self) -> Result<KeyPair, CryptoError> {
38 generate_kem_keypair(&mut fresh_rng())
39 }
40
41 fn encapsulate(&self, public_key: &[u8]) -> Result<(Bytes, Bytes), CryptoError> {
42 encapsulate_kem(public_key, &mut fresh_rng())
43 }
44
45 fn decapsulate(&self, secret_key: &[u8], ciphertext: &[u8]) -> Result<Bytes, CryptoError> {
46 decapsulate_kem(secret_key, ciphertext)
47 }
48}
49
50#[derive(Debug, Default)]
57pub struct MlDsaSigner;
58
59impl Signer for MlDsaSigner {
60 fn generate_keypair(&self) -> Result<KeyPair, CryptoError> {
61 generate_dsa_keypair(&mut fresh_rng())
62 }
63
64 fn sign(&self, secret_key: &[u8], message: &[u8]) -> Result<Signature, CryptoError> {
65 sign_dsa(secret_key, message)
66 }
67
68 fn verify(
69 &self,
70 public_key: &[u8],
71 message: &[u8],
72 signature: &Signature,
73 ) -> Result<bool, CryptoError> {
74 verify_dsa(public_key, message, signature)
75 }
76}
77
78#[derive(Debug, Default)]
84pub struct Aes256GcmCipher;
85
86impl SymmetricCipher for Aes256GcmCipher {
87 fn encrypt(&self, key: &[u8], nonce: &[u8], plaintext: &[u8]) -> Result<Bytes, CryptoError> {
88 encrypt_aes_gcm(key, nonce, plaintext)
89 }
90
91 fn decrypt(&self, key: &[u8], nonce: &[u8], ciphertext: &[u8]) -> Result<Bytes, CryptoError> {
92 decrypt_aes_gcm(key, nonce, ciphertext)
93 }
94}
95
96#[cfg(test)]
99mod tests {
100 use super::*;
101
102 type TestResult = Result<(), Box<dyn std::error::Error>>;
103
104 #[test]
105 fn test_encryptor_adapter_roundtrip() -> TestResult {
106 let enc = MlKemEncryptor;
107 let kp = enc.generate_keypair()?;
108 let (ct, ss1) = enc.encapsulate(&kp.public_key)?;
109 let ss2 = enc.decapsulate(&kp.secret_key, &ct)?;
110 assert_eq!(ss1.as_ref(), ss2.as_ref());
111 Ok(())
112 }
113
114 #[test]
115 fn test_signer_adapter_roundtrip() -> TestResult {
116 let signer = MlDsaSigner;
117 let kp = signer.generate_keypair()?;
118 let msg = b"test message for adapter";
119 let sig = signer.sign(&kp.secret_key, msg)?;
120 let ok = signer.verify(&kp.public_key, msg, &sig)?;
121 assert!(ok, "valid sig must verify via adapter");
122 Ok(())
123 }
124
125 #[test]
126 fn test_signer_adapter_wrong_message() -> TestResult {
127 let signer = MlDsaSigner;
128 let kp = signer.generate_keypair()?;
129 let sig = signer.sign(&kp.secret_key, b"original")?;
130 let ok = signer.verify(&kp.public_key, b"tampered", &sig)?;
131 assert!(
132 !ok,
133 "sig over original must not verify against tampered msg"
134 );
135 Ok(())
136 }
137
138 #[test]
139 fn test_symmetric_adapter_roundtrip() -> TestResult {
140 let cipher = Aes256GcmCipher;
141 let key = vec![0u8; 32];
142 let nonce = vec![1u8; 12];
143 let plaintext = b"test message";
144 let ciphertext = cipher.encrypt(&key, &nonce, plaintext)?;
145 let recovered = cipher.decrypt(&key, &nonce, &ciphertext)?;
146 assert_eq!(recovered.as_ref(), plaintext);
147 Ok(())
148 }
149
150 #[test]
151 fn test_symmetric_adapter_tamper() -> TestResult {
152 let cipher = Aes256GcmCipher;
153 let key = vec![0u8; 32];
154 let nonce = vec![1u8; 12];
155 let plaintext = b"test message";
156 let mut ciphertext = cipher.encrypt(&key, &nonce, plaintext)?.to_vec();
157 *ciphertext.get_mut(0).ok_or("out of bounds")? ^= 0xFF;
158 let result = cipher.decrypt(&key, &nonce, &ciphertext);
159 assert!(result.is_err(), "tampered ciphertext must fail to decrypt");
160 Ok(())
161 }
162}