Skip to main content

ring_native_ossl/
aead.rs

1//! Authenticated encryption with associated data (AEAD), mirroring `ring::aead`.
2//!
3//! Provides AES-128-GCM, AES-256-GCM, and ChaCha20-Poly1305 via OpenSSL.
4//! Key material is stored in [`native_ossl::util::SecretBuf`] and erased on
5//! drop.  The nonce is always 12 bytes; the authentication tag is always 16
6//! bytes for all supported algorithms.
7
8use crate::error::Unspecified;
9use native_ossl::cipher::{AeadDecryptCtx, AeadEncryptCtx, CipherAlg};
10use native_ossl::util::SecretBuf;
11use std::ffi::CStr;
12
13pub const MAX_TAG_LEN: usize = 16;
14pub const NONCE_LEN: usize = 12;
15
16/// AEAD algorithm descriptor (mirrors `ring::aead::Algorithm`).
17#[derive(Debug)]
18pub struct Algorithm {
19    cipher_name: &'static CStr,
20    key_len: usize,
21    tag_len: usize,
22}
23
24pub static AES_128_GCM: Algorithm = Algorithm {
25    cipher_name: c"AES-128-GCM",
26    key_len: 16,
27    tag_len: 16,
28};
29pub static AES_256_GCM: Algorithm = Algorithm {
30    cipher_name: c"AES-256-GCM",
31    key_len: 32,
32    tag_len: 16,
33};
34pub static CHACHA20_POLY1305: Algorithm = Algorithm {
35    cipher_name: c"ChaCha20-Poly1305",
36    key_len: 32,
37    tag_len: 16,
38};
39
40impl Algorithm {
41    #[must_use]
42    pub fn key_len(&self) -> usize {
43        self.key_len
44    }
45
46    #[must_use]
47    pub fn tag_len(&self) -> usize {
48        self.tag_len
49    }
50
51    #[must_use]
52    pub fn nonce_len(&self) -> usize {
53        NONCE_LEN
54    }
55}
56
57/// A 12-byte AEAD nonce (mirrors `ring::aead::Nonce`).
58#[derive(Clone, Copy, Debug)]
59pub struct Nonce(pub [u8; NONCE_LEN]);
60
61impl Nonce {
62    #[must_use]
63    pub fn assume_unique_for_key(bytes: [u8; NONCE_LEN]) -> Self {
64        Self(bytes)
65    }
66
67    /// # Errors
68    ///
69    /// Returns `Unspecified` if `bytes` is not exactly `NONCE_LEN` bytes long.
70    pub fn try_assume_unique_for_key(bytes: &[u8]) -> Result<Self, Unspecified> {
71        let arr: [u8; NONCE_LEN] = bytes.try_into().map_err(|_| Unspecified)?;
72        Ok(Self(arr))
73    }
74}
75
76/// Additional associated data (mirrors `ring::aead::Aad`).
77pub struct Aad<A>(pub A);
78
79impl<A: AsRef<[u8]>> Aad<A> {
80    pub fn from(val: A) -> Self {
81        Aad(val)
82    }
83}
84
85impl Aad<[u8; 0]> {
86    #[must_use]
87    pub fn empty() -> Self {
88        Aad([])
89    }
90}
91
92/// An AEAD key not yet bound to a direction (mirrors `ring::aead::UnboundKey`).
93pub struct UnboundKey {
94    alg: &'static Algorithm,
95    key: SecretBuf,
96}
97
98impl std::fmt::Debug for UnboundKey {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        f.debug_struct("UnboundKey")
101            .field("alg", &self.alg)
102            .finish_non_exhaustive()
103    }
104}
105
106impl std::fmt::Debug for LessSafeKey {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        f.debug_struct("LessSafeKey")
109            .field("alg", &self.alg)
110            .finish_non_exhaustive()
111    }
112}
113
114impl UnboundKey {
115    /// # Errors
116    ///
117    /// Returns `Unspecified` if `key_bytes` length does not match the algorithm's key length.
118    pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self, Unspecified> {
119        if key_bytes.len() != algorithm.key_len {
120            return Err(Unspecified);
121        }
122        Ok(Self {
123            alg: algorithm,
124            key: SecretBuf::from_slice(key_bytes),
125        })
126    }
127
128    #[must_use]
129    pub fn algorithm(&self) -> &'static Algorithm {
130        self.alg
131    }
132}
133
134/// A sealing/opening key that does not enforce nonce uniqueness
135/// (mirrors `ring::aead::LessSafeKey`).
136pub struct LessSafeKey {
137    alg: &'static Algorithm,
138    key: SecretBuf,
139}
140
141impl LessSafeKey {
142    #[must_use]
143    pub fn new(key: UnboundKey) -> Self {
144        Self {
145            alg: key.alg,
146            key: key.key,
147        }
148    }
149
150    #[must_use]
151    pub fn algorithm(&self) -> &'static Algorithm {
152        self.alg
153    }
154
155    /// Encrypt `in_out` in-place and append the authentication tag.
156    ///
157    /// # Errors
158    ///
159    /// Returns `Unspecified` if the underlying OpenSSL AEAD operation fails.
160    pub fn seal_in_place_append_tag<A: AsRef<[u8]>>(
161        &self,
162        nonce: Nonce,
163        aad: &Aad<A>,
164        in_out: &mut Vec<u8>,
165    ) -> Result<(), Unspecified> {
166        let cipher_alg = CipherAlg::fetch(self.alg.cipher_name, None).map_err(|_| Unspecified)?;
167        let mut enc = AeadEncryptCtx::new(&cipher_alg, self.key.as_ref(), &nonce.0, None)
168            .map_err(|_| Unspecified)?;
169        enc.set_aad(aad.0.as_ref()).map_err(|_| Unspecified)?;
170
171        let plaintext_len = in_out.len();
172        // encrypt in a temporary buffer, then write back
173        let mut ciphertext = vec![0u8; plaintext_len + 32];
174        let n = enc
175            .update(in_out, &mut ciphertext)
176            .map_err(|_| Unspecified)?;
177        let n2 = enc
178            .finalize(&mut ciphertext[n..])
179            .map_err(|_| Unspecified)?;
180        let ct_len = n + n2;
181
182        let mut tag = [0u8; 16];
183        enc.tag(&mut tag[..self.alg.tag_len])
184            .map_err(|_| Unspecified)?;
185
186        in_out.clear();
187        in_out.extend_from_slice(&ciphertext[..ct_len]);
188        in_out.extend_from_slice(&tag[..self.alg.tag_len]);
189        Ok(())
190    }
191
192    /// Decrypt `in_out` in-place; on success returns the plaintext slice.
193    ///
194    /// `in_out` must end with the authentication tag.
195    ///
196    /// # Errors
197    ///
198    /// Returns `Unspecified` if authentication fails or the OpenSSL operation fails.
199    pub fn open_in_place<'in_out, A: AsRef<[u8]>>(
200        &self,
201        nonce: Nonce,
202        aad: &Aad<A>,
203        in_out: &'in_out mut [u8],
204    ) -> Result<&'in_out mut [u8], Unspecified> {
205        let tag_len = self.alg.tag_len;
206        if in_out.len() < tag_len {
207            return Err(Unspecified);
208        }
209        let ciphertext_len = in_out.len() - tag_len;
210        let (ciphertext, tag) = in_out.split_at_mut(ciphertext_len);
211        let tag = &*tag; // shared borrow
212
213        let cipher_alg = CipherAlg::fetch(self.alg.cipher_name, None).map_err(|_| Unspecified)?;
214        let mut dec = AeadDecryptCtx::new(&cipher_alg, self.key.as_ref(), &nonce.0, None)
215            .map_err(|_| Unspecified)?;
216        dec.set_aad(aad.0.as_ref()).map_err(|_| Unspecified)?;
217        dec.set_tag(tag).map_err(|_| Unspecified)?;
218
219        let mut plaintext = vec![0u8; ciphertext_len + 32];
220        let n = dec
221            .update(ciphertext, &mut plaintext)
222            .map_err(|_| Unspecified)?;
223        let n2 = dec.finalize(&mut plaintext[n..]).map_err(|_| Unspecified)?;
224        let pt_len = n + n2;
225
226        in_out[..pt_len].copy_from_slice(&plaintext[..pt_len]);
227        Ok(&mut in_out[..pt_len])
228    }
229
230    /// Variant that accepts `in_out` starting at `in_prefix_len` bytes of prefix.
231    ///
232    /// # Errors
233    ///
234    /// Returns `Unspecified` if authentication fails or the buffer is too short.
235    pub fn open_within<'in_out, A: AsRef<[u8]>>(
236        &self,
237        nonce: Nonce,
238        aad: &Aad<A>,
239        in_out: &'in_out mut [u8],
240        in_prefix_len: usize,
241    ) -> Result<&'in_out mut [u8], Unspecified> {
242        let tag_len = self.alg.tag_len;
243        let total = in_out.len();
244        if total < in_prefix_len + tag_len {
245            return Err(Unspecified);
246        }
247        let ciphertext_and_tag = &mut in_out[in_prefix_len..];
248        let result = self.open_in_place(nonce, aad, ciphertext_and_tag)?;
249        let pt_len = result.len();
250        // move plaintext to start of buffer
251        in_out.copy_within(in_prefix_len..in_prefix_len + pt_len, 0);
252        Ok(&mut in_out[..pt_len])
253    }
254}