Skip to main content

lib_q_aead/
provider.rs

1//! lib-Q AEAD provider: implements [`AeadOperations`](lib_q_core::api::AeadOperations) for the AEAD registry.
2//!
3//! Mirrors the pattern used by `lib-q-hash` (`LibQHashProvider`) and `lib-q-kem` (`LibQKemProvider`).
4
5#[cfg(feature = "alloc")]
6extern crate alloc;
7
8#[cfg(feature = "alloc")]
9use alloc::vec::Vec;
10
11#[cfg(feature = "alloc")]
12use lib_q_core::api::{
13    AeadOperations,
14    Algorithm,
15    AlgorithmCategory,
16    CryptoProvider,
17    HashOperations,
18    KemOperations,
19    SignatureOperations,
20};
21#[cfg(feature = "alloc")]
22use lib_q_core::error::Result;
23#[cfg(feature = "alloc")]
24use lib_q_core::security::SecurityValidator;
25#[cfg(feature = "alloc")]
26use lib_q_core::traits::{
27    AeadKey,
28    Nonce,
29};
30
31#[cfg(feature = "alloc")]
32use crate::{
33    Aead,
34    create_aead,
35};
36
37/// Registry-backed AEAD provider for integration with `lib-q-core` contexts.
38#[cfg(feature = "alloc")]
39#[derive(Clone)]
40pub struct LibQAeadProvider {
41    security_validator: SecurityValidator,
42}
43
44#[cfg(feature = "alloc")]
45impl core::fmt::Debug for LibQAeadProvider {
46    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
47        f.debug_struct("LibQAeadProvider")
48            .field("security_validator", &"<SecurityValidator>")
49            .finish()
50    }
51}
52
53#[cfg(feature = "alloc")]
54impl LibQAeadProvider {
55    /// Create a new AEAD provider with security validation initialized.
56    pub fn new() -> Result<Self> {
57        Ok(Self {
58            security_validator: SecurityValidator::new()?,
59        })
60    }
61
62    /// Security validator used for input checks.
63    pub fn security_validator(&self) -> &SecurityValidator {
64        &self.security_validator
65    }
66}
67
68#[cfg(feature = "alloc")]
69impl AeadOperations for LibQAeadProvider {
70    fn encrypt(
71        &self,
72        algorithm: Algorithm,
73        key: &AeadKey,
74        nonce: &Nonce,
75        plaintext: &[u8],
76        associated_data: Option<&[u8]>,
77    ) -> Result<Vec<u8>> {
78        self.security_validator
79            .validate_algorithm_category(algorithm, AlgorithmCategory::Aead)?;
80        self.security_validator
81            .validate_key_material(key.as_bytes())?;
82        self.security_validator.validate_nonce(nonce.as_bytes())?;
83        self.security_validator.validate_aead_message(plaintext)?;
84        if let Some(ad) = associated_data {
85            self.security_validator.validate_aead_message(ad)?;
86        }
87
88        let aead = create_aead(algorithm)?;
89        Aead::encrypt(&*aead, key, nonce, plaintext, associated_data)
90    }
91
92    fn decrypt(
93        &self,
94        algorithm: Algorithm,
95        key: &AeadKey,
96        nonce: &Nonce,
97        ciphertext: &[u8],
98        associated_data: Option<&[u8]>,
99    ) -> Result<Vec<u8>> {
100        self.security_validator
101            .validate_algorithm_category(algorithm, AlgorithmCategory::Aead)?;
102        self.security_validator
103            .validate_key_material(key.as_bytes())?;
104        self.security_validator.validate_nonce(nonce.as_bytes())?;
105        // AEAD ciphertexts are variable-length; do not use `validate_ciphertext` (KEM-only sizes).
106        if ciphertext.is_empty() {
107            return Err(lib_q_core::Error::InvalidCiphertextSize {
108                expected: 1,
109                actual: 0,
110            });
111        }
112        self.security_validator.validate_aead_message(ciphertext)?;
113        if let Some(ad) = associated_data {
114            self.security_validator.validate_aead_message(ad)?;
115        }
116
117        let aead = create_aead(algorithm)?;
118        Aead::decrypt(&*aead, key, nonce, ciphertext, associated_data)
119    }
120}
121
122#[cfg(feature = "alloc")]
123impl CryptoProvider for LibQAeadProvider {
124    fn kem(&self) -> Option<&dyn KemOperations> {
125        None
126    }
127
128    fn signature(&self) -> Option<&dyn SignatureOperations> {
129        None
130    }
131
132    fn hash(&self) -> Option<&dyn HashOperations> {
133        None
134    }
135
136    fn aead(&self) -> Option<&dyn AeadOperations> {
137        Some(self)
138    }
139}
140
141#[cfg(all(test, feature = "alloc"))]
142mod tests {
143    #[cfg(not(feature = "std"))]
144    use alloc::vec;
145
146    use lib_q_core::{
147        Algorithm,
148        Error,
149    };
150
151    use super::*;
152
153    #[test]
154    fn test_libq_aead_provider_creation() {
155        assert!(LibQAeadProvider::new().is_ok());
156    }
157
158    #[test]
159    fn test_non_aead_algorithm_rejected() {
160        let provider = LibQAeadProvider::new().unwrap();
161        let key = AeadKey::new(vec![0u8; 32]);
162        let nonce = Nonce::new(vec![0u8; 16]);
163        let result = provider.encrypt(Algorithm::MlKem512, &key, &nonce, b"test", None);
164        assert!(result.is_err());
165        assert!(matches!(result, Err(Error::InvalidAlgorithm { .. })));
166    }
167
168    #[test]
169    fn test_provider_security_validator_accessor() {
170        let provider = LibQAeadProvider::new().unwrap();
171        let _validator = provider.security_validator();
172    }
173
174    #[test]
175    fn test_non_aead_algorithm_rejected_on_decrypt() {
176        let provider = LibQAeadProvider::new().unwrap();
177        let key = AeadKey::new(vec![0u8; 32]);
178        let nonce = Nonce::new(vec![0u8; 16]);
179        let result = provider.decrypt(Algorithm::MlKem512, &key, &nonce, b"ciphertext", None);
180        assert!(matches!(result, Err(Error::InvalidAlgorithm { .. })));
181    }
182
183    #[cfg(feature = "shake256")]
184    #[test]
185    fn test_empty_ciphertext_rejected_before_backend_decrypt() {
186        let provider = LibQAeadProvider::new().unwrap();
187        let mut key_bytes = vec![0u8; 32];
188        let mut nonce_bytes = vec![0u8; 16];
189        for (i, b) in key_bytes.iter_mut().enumerate() {
190            *b = (i as u8).wrapping_mul(0x17).wrapping_add(0x41);
191        }
192        for (i, b) in nonce_bytes.iter_mut().enumerate() {
193            *b = (i as u8).wrapping_mul(0x29).wrapping_add(0x13);
194        }
195        let key = AeadKey::new(key_bytes);
196        let nonce = Nonce::new(nonce_bytes);
197        let result = provider.decrypt(Algorithm::Shake256Aead, &key, &nonce, b"", None);
198        assert!(matches!(
199            result,
200            Err(Error::InvalidCiphertextSize {
201                expected: 1,
202                actual: 0
203            })
204        ));
205    }
206
207    #[cfg(feature = "shake256")]
208    #[test]
209    fn test_shake256_aead_roundtrip_via_provider() {
210        let provider = LibQAeadProvider::new().unwrap();
211        let mut key_bytes = vec![0u8; 32];
212        let mut nonce_bytes = vec![0u8; 16];
213        for (i, b) in key_bytes.iter_mut().enumerate() {
214            *b = (i as u8).wrapping_mul(0x1F).wrapping_add(0x2B);
215        }
216        for (i, b) in nonce_bytes.iter_mut().enumerate() {
217            *b = (i as u8).wrapping_mul(0x3D).wrapping_add(0x7E);
218        }
219        let key = AeadKey::new(key_bytes);
220        let nonce = Nonce::new(nonce_bytes);
221        let pt = b"provider roundtrip";
222        let ad = b"ad";
223
224        let ct = AeadOperations::encrypt(
225            &provider,
226            Algorithm::Shake256Aead,
227            &key,
228            &nonce,
229            pt.as_slice(),
230            Some(ad.as_slice()),
231        )
232        .expect("encrypt");
233        let out = AeadOperations::decrypt(
234            &provider,
235            Algorithm::Shake256Aead,
236            &key,
237            &nonce,
238            &ct,
239            Some(ad.as_slice()),
240        )
241        .expect("decrypt");
242        assert_eq!(out.as_slice(), pt.as_slice());
243    }
244
245    #[test]
246    fn test_crypto_provider_trait_exposes_only_aead_operations() {
247        let provider = LibQAeadProvider::new().unwrap();
248        let crypto_provider: &dyn CryptoProvider = &provider;
249        assert!(crypto_provider.kem().is_none());
250        assert!(crypto_provider.signature().is_none());
251        assert!(crypto_provider.hash().is_none());
252        assert!(crypto_provider.aead().is_some());
253    }
254}