snow/resolvers/
ring.rs

1use super::CryptoResolver;
2use crate::{
3    constants::TAGLEN,
4    params::{CipherChoice, DHChoice, HashChoice},
5    types::{Cipher, Dh, Hash, Random},
6    Error,
7};
8use ring::{
9    aead::{self, LessSafeKey, UnboundKey},
10    digest,
11    rand::{SecureRandom, SystemRandom},
12};
13
14/// A resolver that chooses [ring](https://github.com/briansmith/ring)-backed
15/// primitives when available.
16#[derive(Default)]
17pub struct RingResolver;
18
19#[cfg(feature = "ring")]
20impl CryptoResolver for RingResolver {
21    fn resolve_rng(&self) -> Option<Box<dyn Random>> {
22        Some(Box::new(RingRng::default()))
23    }
24
25    fn resolve_dh(&self, _choice: &DHChoice) -> Option<Box<dyn Dh>> {
26        None
27    }
28
29    fn resolve_hash(&self, choice: &HashChoice) -> Option<Box<dyn Hash>> {
30        match *choice {
31            HashChoice::SHA256 => Some(Box::new(HashSHA256::default())),
32            HashChoice::SHA512 => Some(Box::new(HashSHA512::default())),
33            _ => None,
34        }
35    }
36
37    fn resolve_cipher(&self, choice: &CipherChoice) -> Option<Box<dyn Cipher>> {
38        match *choice {
39            CipherChoice::AESGCM => Some(Box::new(CipherAESGCM::default())),
40            CipherChoice::ChaChaPoly => Some(Box::new(CipherChaChaPoly::default())),
41            #[cfg(feature = "xchachapoly")]
42            CipherChoice::XChaChaPoly => None,
43        }
44    }
45}
46
47struct RingRng {
48    rng: SystemRandom,
49}
50
51impl Default for RingRng {
52    fn default() -> Self {
53        Self { rng: SystemRandom::new() }
54    }
55}
56
57impl rand_core::RngCore for RingRng {
58    fn next_u32(&mut self) -> u32 {
59        rand_core::impls::next_u32_via_fill(self)
60    }
61
62    fn next_u64(&mut self) -> u64 {
63        rand_core::impls::next_u64_via_fill(self)
64    }
65
66    fn fill_bytes(&mut self, dest: &mut [u8]) {
67        self.try_fill_bytes(dest).unwrap();
68    }
69
70    fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> {
71        self.rng.fill(dest).map_err(|e| rand_core::Error::new(e))
72    }
73}
74
75impl rand_core::CryptoRng for RingRng {}
76
77impl Random for RingRng {}
78
79struct CipherAESGCM {
80    // NOTE: LessSafeKey is chosen here because nonce atomicity is handled outside of this structure.
81    // See ring documentation for more details on the naming choices.
82    key: LessSafeKey,
83}
84
85impl Default for CipherAESGCM {
86    fn default() -> Self {
87        CipherAESGCM {
88            key: LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, &[0u8; 32]).unwrap()),
89        }
90    }
91}
92
93impl Cipher for CipherAESGCM {
94    fn name(&self) -> &'static str {
95        "AESGCM"
96    }
97
98    fn set(&mut self, key: &[u8]) {
99        self.key = aead::LessSafeKey::new(UnboundKey::new(&aead::AES_256_GCM, key).unwrap());
100    }
101
102    fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize {
103        let mut nonce_bytes = [0u8; 12];
104        copy_slices!(&nonce.to_be_bytes(), &mut nonce_bytes[4..]);
105
106        out[..plaintext.len()].copy_from_slice(plaintext);
107
108        let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
109
110        let tag = self
111            .key
112            .seal_in_place_separate_tag(
113                nonce,
114                aead::Aad::from(authtext),
115                &mut out[..plaintext.len()],
116            )
117            .unwrap();
118        out[plaintext.len()..plaintext.len() + TAGLEN].copy_from_slice(tag.as_ref());
119
120        plaintext.len() + TAGLEN
121    }
122
123    fn decrypt(
124        &self,
125        nonce: u64,
126        authtext: &[u8],
127        ciphertext: &[u8],
128        out: &mut [u8],
129    ) -> Result<usize, Error> {
130        let mut nonce_bytes = [0u8; 12];
131        copy_slices!(&nonce.to_be_bytes(), &mut nonce_bytes[4..]);
132        let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
133
134        if out.len() >= ciphertext.len() {
135            let in_out = &mut out[..ciphertext.len()];
136            in_out.copy_from_slice(ciphertext);
137
138            let len = self
139                .key
140                .open_in_place(nonce, aead::Aad::from(authtext), in_out)
141                .map_err(|_| Error::Decrypt)?
142                .len();
143
144            Ok(len)
145        } else {
146            let mut in_out = ciphertext.to_vec();
147
148            let out0 = self
149                .key
150                .open_in_place(nonce, aead::Aad::from(authtext), &mut in_out)
151                .map_err(|_| Error::Decrypt)?;
152
153            out[..out0.len()].copy_from_slice(out0);
154            Ok(out0.len())
155        }
156    }
157}
158
159struct CipherChaChaPoly {
160    // NOTE: LessSafeKey is chosen here because nonce atomicity is to be ensured outside of this structure.
161    // See ring documentation for more details on the naming choices.
162    key: aead::LessSafeKey,
163}
164
165impl Default for CipherChaChaPoly {
166    fn default() -> Self {
167        Self {
168            key: LessSafeKey::new(UnboundKey::new(&aead::CHACHA20_POLY1305, &[0u8; 32]).unwrap()),
169        }
170    }
171}
172
173impl Cipher for CipherChaChaPoly {
174    fn name(&self) -> &'static str {
175        "ChaChaPoly"
176    }
177
178    fn set(&mut self, key: &[u8]) {
179        self.key = LessSafeKey::new(UnboundKey::new(&aead::CHACHA20_POLY1305, key).unwrap());
180    }
181
182    fn encrypt(&self, nonce: u64, authtext: &[u8], plaintext: &[u8], out: &mut [u8]) -> usize {
183        let mut nonce_bytes = [0u8; 12];
184        copy_slices!(&nonce.to_le_bytes(), &mut nonce_bytes[4..]);
185        let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
186
187        out[..plaintext.len()].copy_from_slice(plaintext);
188
189        let tag = self
190            .key
191            .seal_in_place_separate_tag(
192                nonce,
193                aead::Aad::from(authtext),
194                &mut out[..plaintext.len()],
195            )
196            .unwrap();
197        out[plaintext.len()..plaintext.len() + TAGLEN].copy_from_slice(tag.as_ref());
198
199        plaintext.len() + TAGLEN
200    }
201
202    fn decrypt(
203        &self,
204        nonce: u64,
205        authtext: &[u8],
206        ciphertext: &[u8],
207        out: &mut [u8],
208    ) -> Result<usize, Error> {
209        let mut nonce_bytes = [0u8; 12];
210        copy_slices!(&nonce.to_le_bytes(), &mut nonce_bytes[4..]);
211        let nonce = aead::Nonce::assume_unique_for_key(nonce_bytes);
212
213        if out.len() >= ciphertext.len() {
214            let in_out = &mut out[..ciphertext.len()];
215            in_out.copy_from_slice(ciphertext);
216
217            let len = self
218                .key
219                .open_in_place(nonce, aead::Aad::from(authtext), in_out)
220                .map_err(|_| Error::Decrypt)?
221                .len();
222
223            Ok(len)
224        } else {
225            let mut in_out = ciphertext.to_vec();
226
227            let out0 = self
228                .key
229                .open_in_place(nonce, aead::Aad::from(authtext), &mut in_out)
230                .map_err(|_| Error::Decrypt)?;
231
232            out[..out0.len()].copy_from_slice(out0);
233            Ok(out0.len())
234        }
235    }
236}
237struct HashSHA256 {
238    context: digest::Context,
239}
240
241impl Default for HashSHA256 {
242    fn default() -> Self {
243        Self { context: digest::Context::new(&digest::SHA256) }
244    }
245}
246
247impl Hash for HashSHA256 {
248    fn name(&self) -> &'static str {
249        "SHA256"
250    }
251
252    fn block_len(&self) -> usize {
253        64
254    }
255
256    fn hash_len(&self) -> usize {
257        32
258    }
259
260    fn reset(&mut self) {
261        self.context = digest::Context::new(&digest::SHA256);
262    }
263
264    fn input(&mut self, data: &[u8]) {
265        self.context.update(data);
266    }
267
268    fn result(&mut self, out: &mut [u8]) {
269        out[..32].copy_from_slice(self.context.clone().finish().as_ref());
270    }
271}
272
273struct HashSHA512 {
274    context: digest::Context,
275}
276
277impl Default for HashSHA512 {
278    fn default() -> Self {
279        Self { context: digest::Context::new(&digest::SHA512) }
280    }
281}
282
283impl Hash for HashSHA512 {
284    fn name(&self) -> &'static str {
285        "SHA512"
286    }
287
288    fn block_len(&self) -> usize {
289        128
290    }
291
292    fn hash_len(&self) -> usize {
293        64
294    }
295
296    fn reset(&mut self) {
297        self.context = digest::Context::new(&digest::SHA512);
298    }
299
300    fn input(&mut self, data: &[u8]) {
301        self.context.update(data);
302    }
303
304    fn result(&mut self, out: &mut [u8]) {
305        out[..64].copy_from_slice(self.context.clone().finish().as_ref());
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312    use rand_core::RngCore;
313
314    #[test]
315    fn test_randomness_sanity() {
316        use std::collections::HashSet;
317
318        let mut samples = HashSet::new();
319        let mut rng = RingRng::default();
320        for _ in 0..100_000 {
321            let mut buf = vec![0u8; 128];
322            rng.fill_bytes(&mut buf);
323            assert!(samples.insert(buf));
324        }
325    }
326}