Skip to main content

liboscore_cryptobackend/
aead.rs

1use core::mem::MaybeUninit;
2
3use aead::generic_array::GenericArray;
4use typenum::marker_traits::Unsigned;
5
6use super::{c_void, CryptoErr};
7
8/// Expressed as an enum for lack of type variables and/or the unsuitability of the NewAead::new
9/// method as a function pointer constructor due to its generic component.
10///
11/// (Possibly one could make a trait that only has an associated type, have it impl'd by the
12/// algorithms as a ZST, have static single objects for each and pass them as `&'static dyn`, but
13/// that makes them two pointers large. One could probably pick the vtable from trait object
14/// pointers, but that's deep unsafe territory.)
15#[derive(Copy, Clone)]
16#[repr(u8)]
17pub enum Algorithm {
18    #[cfg(feature = "chacha20poly1305")]
19    ChaCha20Poly1305,
20    #[cfg(feature = "aes-ccm")]
21    AesCcm16_64_128,
22    #[cfg(feature = "aes-ccm")]
23    AesCcm16_128_128,
24    #[cfg(feature = "aes-gcm")]
25    A128GCM,
26    #[cfg(feature = "aes-gcm")]
27    A256GCM,
28}
29
30#[cfg(feature = "chacha20poly1305")]
31type AlgtypeChaCha20Poly1305 = chacha20poly1305::ChaCha20Poly1305;
32#[cfg(feature = "aes-ccm")]
33type AlgtypeAesCcm16_64_128 = ccm::Ccm<aes::Aes128, ccm::consts::U8, ccm::consts::U13>;
34#[cfg(feature = "aes-ccm")]
35type AlgtypeAesCcm16_128_128 = ccm::Ccm<aes::Aes128, ccm::consts::U8, ccm::consts::U7>;
36#[cfg(feature = "aes-gcm")]
37type AlgtypeA128GCM = aes_gcm::Aes128Gcm;
38#[cfg(feature = "aes-gcm")]
39type AlgtypeA256GCM = aes_gcm::Aes256Gcm;
40
41/// A fully deparametrized type for variables that might want to be type variables for a `dyn
42/// AeadMutInPlace + KeyInit` but can't for lack of type variables and limitations in object safety
43/// of traits.
44impl Algorithm {
45    fn from_number(num: i32) -> Option<Self> {
46        match num {
47            #[cfg(feature = "chacha20poly1305")]
48            24 => Some(Algorithm::ChaCha20Poly1305),
49            #[cfg(feature = "aes-ccm")]
50            10 => Some(Algorithm::AesCcm16_64_128),
51            #[cfg(feature = "aes-ccm")]
52            30 => Some(Algorithm::AesCcm16_128_128),
53            #[cfg(feature = "aes-gcm")]
54            1 => Some(Algorithm::A128GCM),
55            #[cfg(feature = "aes-gcm")]
56            3 => Some(Algorithm::A256GCM),
57            _ => None,
58        }
59    }
60
61    fn to_number(&self) -> Option<i32> {
62        Some(match self {
63            #[cfg(feature = "chacha20poly1305")]
64            Algorithm::ChaCha20Poly1305 => 24,
65            #[cfg(feature = "aes-ccm")]
66            Algorithm::AesCcm16_64_128 => 10,
67            #[cfg(feature = "aes-ccm")]
68            Algorithm::AesCcm16_128_128 => 30,
69            #[cfg(feature = "aes-gcm")]
70            Algorithm::A128GCM => 1,
71            #[cfg(feature = "aes-gcm")]
72            Algorithm::A256GCM => 3,
73        })
74    }
75
76    fn tag_length(&self) -> usize {
77        match self {
78            #[cfg(feature = "chacha20poly1305")]
79            Algorithm::ChaCha20Poly1305 => {
80                <AlgtypeChaCha20Poly1305 as aead::AeadCore>::TagSize::to_usize()
81            }
82            #[cfg(feature = "aes-ccm")]
83            Algorithm::AesCcm16_64_128 => {
84                <AlgtypeAesCcm16_64_128 as aead::AeadCore>::TagSize::to_usize()
85            }
86            #[cfg(feature = "aes-ccm")]
87            Algorithm::AesCcm16_128_128 => {
88                <AlgtypeAesCcm16_128_128 as aead::AeadCore>::TagSize::to_usize()
89            }
90            #[cfg(feature = "aes-gcm")]
91            Algorithm::A128GCM => <AlgtypeA128GCM as aead::AeadCore>::TagSize::to_usize(),
92            #[cfg(feature = "aes-gcm")]
93            Algorithm::A256GCM => <AlgtypeA256GCM as aead::AeadCore>::TagSize::to_usize(),
94        }
95    }
96
97    fn iv_length(&self) -> usize {
98        match self {
99            #[cfg(feature = "chacha20poly1305")]
100            Algorithm::ChaCha20Poly1305 => {
101                <AlgtypeChaCha20Poly1305 as aead::AeadCore>::NonceSize::to_usize()
102            }
103            #[cfg(feature = "aes-ccm")]
104            Algorithm::AesCcm16_64_128 => {
105                <AlgtypeAesCcm16_64_128 as aead::AeadCore>::NonceSize::to_usize()
106            }
107            #[cfg(feature = "aes-ccm")]
108            Algorithm::AesCcm16_128_128 => {
109                <AlgtypeAesCcm16_128_128 as aead::AeadCore>::NonceSize::to_usize()
110            }
111            #[cfg(feature = "aes-gcm")]
112            Algorithm::A128GCM => <AlgtypeA128GCM as aead::AeadCore>::NonceSize::to_usize(),
113            #[cfg(feature = "aes-gcm")]
114            Algorithm::A256GCM => <AlgtypeA256GCM as aead::AeadCore>::NonceSize::to_usize(),
115        }
116    }
117
118    fn key_length(&self) -> usize {
119        match self {
120            #[cfg(feature = "chacha20poly1305")]
121            Algorithm::ChaCha20Poly1305 => {
122                <AlgtypeChaCha20Poly1305 as aead::KeySizeUser>::key_size()
123            }
124            #[cfg(feature = "aes-ccm")]
125            Algorithm::AesCcm16_64_128 => <AlgtypeAesCcm16_64_128 as aead::KeySizeUser>::key_size(),
126            #[cfg(feature = "aes-ccm")]
127            Algorithm::AesCcm16_128_128 => {
128                <AlgtypeAesCcm16_128_128 as aead::KeySizeUser>::key_size()
129            }
130            #[cfg(feature = "aes-gcm")]
131            Algorithm::A128GCM => <AlgtypeA128GCM as aead::KeySizeUser>::key_size(),
132            #[cfg(feature = "aes-gcm")]
133            Algorithm::A256GCM => <AlgtypeA256GCM as aead::KeySizeUser>::key_size(),
134        }
135    }
136}
137
138const AAD_BUFFER_SIZE: usize = 32;
139
140// Ideally with streaming AAD, those would be enums that union all the intermediate state types of
141// the individual algorithms
142
143pub struct EncryptState {
144    alg: Algorithm,
145    iv: *const u8,
146    key: *const u8,
147    buffered_aad: heapless::Vec<u8, AAD_BUFFER_SIZE>,
148}
149
150#[repr(transparent)]
151pub struct DecryptState {
152    actually_encrypt: EncryptState,
153}
154
155#[no_mangle]
156pub unsafe extern "C" fn oscore_crypto_aead_from_number(
157    alg: &mut MaybeUninit<Algorithm>,
158    num: i32,
159) -> CryptoErr {
160    if let Some(found) = Algorithm::from_number(num) {
161        alg.write(found);
162        CryptoErr::Ok
163    } else {
164        CryptoErr::NoSuchAlgorithm
165    }
166}
167
168#[no_mangle]
169pub unsafe extern "C" fn oscore_crypto_aead_get_number(
170    alg: Algorithm,
171    num: &mut MaybeUninit<i32>,
172) -> CryptoErr {
173    if let Some(found) = alg.to_number() {
174        num.write(found);
175        CryptoErr::Ok
176    } else {
177        CryptoErr::NoIdentifier
178    }
179}
180
181#[no_mangle]
182pub unsafe extern "C" fn oscore_crypto_aead_from_string(
183    _alg: &mut MaybeUninit<Algorithm>,
184    _string: *const u8,
185    _string_len: usize,
186) -> CryptoErr {
187    CryptoErr::NoSuchAlgorithm
188}
189
190#[no_mangle]
191pub unsafe extern "C" fn oscore_crypto_aead_get_taglength(alg: Algorithm) -> usize {
192    alg.tag_length()
193}
194
195#[no_mangle]
196pub unsafe extern "C" fn oscore_crypto_aead_get_ivlength(alg: Algorithm) -> usize {
197    alg.iv_length()
198}
199
200#[no_mangle]
201pub unsafe extern "C" fn oscore_crypto_aead_get_keylength(alg: Algorithm) -> usize {
202    alg.key_length()
203}
204
205#[no_mangle]
206pub unsafe extern "C" fn oscore_crypto_aead_encrypt_start(
207    state: &mut MaybeUninit<EncryptState>,
208    alg: Algorithm,
209    aad_len: usize,
210    _plaintext_len: usize,
211    iv: *const u8,
212    key: *const u8,
213) -> CryptoErr {
214    if aad_len > AAD_BUFFER_SIZE {
215        return CryptoErr::AadPreallocationExceeded;
216    }
217
218    let created = EncryptState {
219        alg,
220        iv,
221        key,
222        buffered_aad: heapless::Vec::new(),
223    };
224    state.write(created);
225
226    CryptoErr::Ok
227}
228
229#[no_mangle]
230pub unsafe extern "C" fn oscore_crypto_aead_encrypt_feed_aad(
231    state: *mut c_void,
232    aad_chunk: *const u8,
233    aad_chunk_len: usize,
234) -> CryptoErr {
235    let state: &mut EncryptState = unsafe { core::mem::transmute(state) };
236
237    let aad_chunk = unsafe { core::slice::from_raw_parts(aad_chunk, aad_chunk_len) };
238    match state.buffered_aad.extend_from_slice(aad_chunk) {
239        Ok(()) => CryptoErr::Ok,
240        Err(_) => CryptoErr::UnexpectedDataLength,
241    }
242}
243
244/// Workhorse of oscore_crypto_aead_encrypt_inplace that is generic and thus can access all the
245/// lengths
246///
247/// This does duplicate some code that during monomorphization that *could* be deduplciated (esp.
248/// the (paincipher, tag) splitting, for which alg.tag_length could be used), but this way it's
249/// easier and duplicate code should be minimal, given that A::TagSize can be used right away.
250fn _encrypt_inplace<A>(state: &mut EncryptState, buffer: *mut u8, buffer_len: usize) -> CryptoErr
251where
252    A: aead::AeadMutInPlace + aead::KeyInit,
253{
254    let taglen = A::TagSize::to_usize();
255
256    let buffer = unsafe { core::slice::from_raw_parts_mut(buffer, buffer_len) };
257    let plaintextlength = match buffer.len().checked_sub(taglen) {
258        Some(x) => x,
259        None => return CryptoErr::BufferShorterThanTag,
260    };
261    let (plaincipher, tag) = buffer.split_at_mut(plaintextlength);
262    log_secrets!("Encrypting plaintext {:?}", plaincipher);
263
264    // The checks in GenericArray initialization should make the intermediary constant go away
265    let keylen = A::KeySize::to_usize();
266    let key = unsafe { core::slice::from_raw_parts(state.key, keylen) };
267    let key = GenericArray::clone_from_slice(key);
268    log_secrets!("Encrypting with key {:?}", key);
269
270    // Same as above
271    let noncelen = A::NonceSize::to_usize();
272    let nonce = unsafe { core::slice::from_raw_parts(state.iv, noncelen) };
273    let nonce = GenericArray::from_slice(nonce);
274    log_secrets!("Encrypting with nonce {:?}", nonce);
275    log_secrets!("Encrypting with AAD {:?}", state.buffered_aad);
276
277    let mut aead = A::new(&key);
278    let tagdata =
279        match aead.encrypt_in_place_detached(nonce, state.buffered_aad.as_ref(), plaincipher) {
280            Ok(tagdata) => tagdata,
281            // There's no real documentation on what that error could be (given that
282            // encrypt_in_place is documented to return an error (only, presumably) if the
283            // buffer has insufficient capacity) which can't be happening here any more
284            Err(_) => return CryptoErr::BufferShorterThanTag,
285        };
286    tag.copy_from_slice(&tagdata);
287
288    log_secrets!("Encrypted ciphertext {:?}", buffer);
289
290    CryptoErr::Ok
291}
292
293#[no_mangle]
294pub unsafe extern "C" fn oscore_crypto_aead_encrypt_inplace(
295    state: &mut EncryptState,
296    buffer: *mut u8,
297    buffer_len: usize,
298) -> CryptoErr {
299    match state.alg {
300        #[cfg(feature = "chacha20poly1305")]
301        Algorithm::ChaCha20Poly1305 => {
302            _encrypt_inplace::<AlgtypeChaCha20Poly1305>(state, buffer, buffer_len)
303        }
304        #[cfg(feature = "aes-ccm")]
305        Algorithm::AesCcm16_64_128 => {
306            _encrypt_inplace::<AlgtypeAesCcm16_64_128>(state, buffer, buffer_len)
307        }
308        #[cfg(feature = "aes-ccm")]
309        Algorithm::AesCcm16_128_128 => {
310            _encrypt_inplace::<AlgtypeAesCcm16_128_128>(state, buffer, buffer_len)
311        }
312        #[cfg(feature = "aes-gcm")]
313        Algorithm::A128GCM => _encrypt_inplace::<AlgtypeA128GCM>(state, buffer, buffer_len),
314        #[cfg(feature = "aes-gcm")]
315        Algorithm::A256GCM => _encrypt_inplace::<AlgtypeA256GCM>(state, buffer, buffer_len),
316    }
317}
318
319#[no_mangle]
320pub unsafe extern "C" fn oscore_crypto_aead_decrypt_start(
321    state: &mut MaybeUninit<DecryptState>,
322    alg: Algorithm,
323    aad_len: usize,
324    plaintext_len: usize,
325    iv: *const u8,
326    key: *const u8,
327) -> CryptoErr {
328    // Hoping the compiler is smart enough to do that right in-place, as we can't initialize a
329    // struct by its fields
330    let mut tempstate = MaybeUninit::uninit();
331    let ret =
332        oscore_crypto_aead_encrypt_start(&mut tempstate, alg, aad_len, plaintext_len, iv, key);
333    if let CryptoErr::Ok = ret {
334        state.write(DecryptState {
335            actually_encrypt: unsafe { tempstate.assume_init() },
336        });
337    }
338    ret
339}
340
341#[no_mangle]
342pub unsafe extern "C" fn oscore_crypto_aead_decrypt_feed_aad(
343    state: *mut c_void,
344    aad_chunk: *const u8,
345    aad_chunk_len: usize,
346) -> CryptoErr {
347    let state: &mut DecryptState = unsafe { core::mem::transmute(state) };
348
349    oscore_crypto_aead_encrypt_feed_aad(
350        unsafe { core::mem::transmute(&mut state.actually_encrypt) },
351        aad_chunk,
352        aad_chunk_len,
353    )
354}
355
356/// Workhorse of oscore_crypto_aead_decrypt_inplace that is generic and thus can access all the
357/// lengths
358///
359/// This does duplicate some code that during monomorphization that *could* be deduplciated (esp.
360/// the (paincipher, tag) splitting, for which alg.tag_length could be used), but this way it's
361/// easier and duplicate code should be minimal, given that A::TagSize can be used right away.
362fn _decrypt_inplace<A>(state: &mut DecryptState, buffer: *mut u8, buffer_len: usize) -> CryptoErr
363where
364    A: aead::AeadMutInPlace + aead::KeyInit,
365{
366    let state = &mut state.actually_encrypt;
367
368    let taglen = state.alg.tag_length();
369
370    let buffer = unsafe { core::slice::from_raw_parts_mut(buffer, buffer_len) };
371    log_secrets!("Decrypting ciphertext {:?}", buffer);
372    let plaintextlength = match buffer.len().checked_sub(taglen) {
373        Some(x) => x,
374        None => return CryptoErr::BufferShorterThanTag,
375    };
376    let (plaincipher, tag) = buffer.split_at_mut(plaintextlength);
377
378    // Suitable const propagation should eliminate this; unfortunately, GenericArray has no
379    // from_raw_part
380    let keylen = state.alg.key_length();
381    let key = unsafe { core::slice::from_raw_parts(state.key, keylen) };
382    let key = GenericArray::clone_from_slice(key);
383    log_secrets!("Decrypting with key {:?}", key);
384
385    // Same as above
386    let noncelen = state.alg.iv_length();
387    let nonce = unsafe { core::slice::from_raw_parts(state.iv, noncelen) };
388    let nonce = GenericArray::from_slice(nonce);
389    log_secrets!("Decrypting with nonce {:?}", nonce);
390    log_secrets!("Decrypting with AAD {:?}", state.buffered_aad);
391
392    // and similar but not quite like
393    let tag = GenericArray::from_slice(tag);
394
395    let _aad: &[u8] = state.buffered_aad.as_ref();
396    let _nonce: &[u8] = nonce.as_ref();
397    let _key: &[u8] = key.as_ref();
398
399    let mut aead = A::new(&key);
400    match aead.decrypt_in_place_detached(nonce, state.buffered_aad.as_ref(), plaincipher, tag) {
401        Ok(()) => {
402            log_secrets!("Decrypted into plaintext {:?}", plaincipher);
403            CryptoErr::Ok
404        }
405        Err(_) => {
406            log_secrets!("Decryption failed"); // We could try printing out the plaincipher buffer,
407                                               // but AEAD libraries make a point of wiping them.
408            CryptoErr::DecryptError
409        }
410    }
411}
412
413#[no_mangle]
414pub unsafe extern "C" fn oscore_crypto_aead_decrypt_inplace(
415    state: &mut DecryptState,
416    buffer: *mut u8,
417    buffer_len: usize,
418) -> CryptoErr {
419    match state.actually_encrypt.alg {
420        #[cfg(feature = "chacha20poly1305")]
421        Algorithm::ChaCha20Poly1305 => {
422            _decrypt_inplace::<AlgtypeChaCha20Poly1305>(state, buffer, buffer_len)
423        }
424        #[cfg(feature = "aes-ccm")]
425        Algorithm::AesCcm16_64_128 => {
426            _decrypt_inplace::<AlgtypeAesCcm16_64_128>(state, buffer, buffer_len)
427        }
428        #[cfg(feature = "aes-ccm")]
429        Algorithm::AesCcm16_128_128 => {
430            _decrypt_inplace::<AlgtypeAesCcm16_128_128>(state, buffer, buffer_len)
431        }
432        #[cfg(feature = "aes-gcm")]
433        Algorithm::A128GCM => _decrypt_inplace::<AlgtypeA128GCM>(state, buffer, buffer_len),
434        #[cfg(feature = "aes-gcm")]
435        Algorithm::A256GCM => _decrypt_inplace::<AlgtypeA256GCM>(state, buffer, buffer_len),
436    }
437}