Skip to main content

aws_lc_rs/aead/
unbound_key.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4use super::aead_ctx::AeadCtx;
5use super::{
6    Algorithm, Nonce, Tag, AES_128_GCM, AES_128_GCM_SIV, AES_192_GCM, AES_256_GCM, AES_256_GCM_SIV,
7    CHACHA20_POLY1305, MAX_KEY_LEN, MAX_TAG_LEN, NONCE_LEN,
8};
9use crate::aws_lc::{
10    EVP_AEAD_CTX_open, EVP_AEAD_CTX_open_gather, EVP_AEAD_CTX_seal, EVP_AEAD_CTX_seal_scatter,
11};
12use crate::error::Unspecified;
13use crate::fips::indicator_check;
14use crate::hkdf;
15use crate::iv::FixedLength;
16use core::fmt::Debug;
17use core::mem::MaybeUninit;
18use core::ops::RangeFrom;
19use core::ptr::null;
20
21/// The maximum length of a nonce returned by our AEAD API.
22const MAX_NONCE_LEN: usize = NONCE_LEN;
23
24/// The maximum required tag buffer needed if using AWS-LC generated nonce construction
25const MAX_TAG_NONCE_BUFFER_LEN: usize = MAX_TAG_LEN + MAX_NONCE_LEN;
26
27/// An AEAD key without a designated role or nonce sequence.
28pub struct UnboundKey {
29    ctx: AeadCtx,
30    algorithm: &'static Algorithm,
31}
32
33#[allow(clippy::missing_fields_in_debug)]
34impl Debug for UnboundKey {
35    fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
36        f.debug_struct("UnboundKey")
37            .field("algorithm", &self.algorithm)
38            .finish()
39    }
40}
41
42impl UnboundKey {
43    /// Constructs an `UnboundKey`.
44    /// # Errors
45    /// `error::Unspecified` if `key_bytes.len() != algorithm.key_len()`.
46    pub fn new(algorithm: &'static Algorithm, key_bytes: &[u8]) -> Result<Self, Unspecified> {
47        Ok(Self {
48            ctx: (algorithm.init)(key_bytes, algorithm.tag_len())?,
49            algorithm,
50        })
51    }
52
53    #[inline]
54    pub(crate) fn open_within<'in_out>(
55        &self,
56        nonce: Nonce,
57        aad: &[u8],
58        in_out: &'in_out mut [u8],
59        ciphertext_and_tag: RangeFrom<usize>,
60    ) -> Result<&'in_out mut [u8], Unspecified> {
61        let in_prefix_len = ciphertext_and_tag.start;
62        let ciphertext_and_tag_len = in_out.len().checked_sub(in_prefix_len).ok_or(Unspecified)?;
63        let ciphertext_len = ciphertext_and_tag_len
64            .checked_sub(self.algorithm().tag_len())
65            .ok_or(Unspecified)?;
66        self.check_per_nonce_max_bytes(ciphertext_len)?;
67
68        match self.ctx {
69            AeadCtx::AES_128_GCM_RANDNONCE(_) | AeadCtx::AES_256_GCM_RANDNONCE(_) => {
70                self.open_combined_randnonce(nonce, aad, &mut in_out[in_prefix_len..])
71            }
72            _ => self.open_combined(nonce, aad.as_ref(), &mut in_out[in_prefix_len..]),
73        }?;
74
75        // shift the plaintext to the left
76        in_out.copy_within(in_prefix_len..in_prefix_len + ciphertext_len, 0);
77
78        // `ciphertext_len` is also the plaintext length.
79        Ok(&mut in_out[..ciphertext_len])
80    }
81
82    #[inline]
83    pub(crate) fn open_separate_gather(
84        &self,
85        nonce: &Nonce,
86        aad: &[u8],
87        in_ciphertext: &[u8],
88        in_tag: &[u8],
89        out_plaintext: &mut [u8],
90    ) -> Result<(), Unspecified> {
91        self.open_separate_gather_impl(
92            nonce,
93            aad,
94            in_ciphertext.as_ptr(),
95            in_ciphertext.len(),
96            in_tag,
97            out_plaintext.as_mut_ptr(),
98            out_plaintext.len(),
99        )
100    }
101
102    #[inline]
103    pub(crate) fn open_in_place_separate_tag(
104        &self,
105        nonce: &Nonce,
106        aad: &[u8],
107        in_tag: &[u8],
108        in_out: &mut [u8],
109    ) -> Result<(), Unspecified> {
110        let ptr = in_out.as_mut_ptr();
111        let len = in_out.len();
112        self.open_separate_gather_impl(nonce, aad, ptr.cast_const(), len, in_tag, ptr, len)
113    }
114
115    /// Common FFI path for `EVP_AEAD_CTX_open_gather`-based opening.
116    ///
117    /// `in_ciphertext` / `out_plaintext` may alias (exactly, i.e. same base
118    /// pointer and length). `EVP_AEAD_CTX_open_gather` explicitly permits
119    /// `out == in`, which is how `open_in_place_separate_tag` works.
120    ///
121    /// Callers must ensure:
122    /// * `in_ciphertext` is valid for reads of `in_ciphertext_len` bytes.
123    /// * `out_plaintext` is valid for writes of `out_plaintext_len` bytes.
124    /// * If the two pointers alias, they must alias exactly (same base, same length).
125    #[inline]
126    #[allow(clippy::too_many_arguments)]
127    fn open_separate_gather_impl(
128        &self,
129        nonce: &Nonce,
130        aad: &[u8],
131        in_ciphertext: *const u8,
132        in_ciphertext_len: usize,
133        in_tag: &[u8],
134        out_plaintext: *mut u8,
135        out_plaintext_len: usize,
136    ) -> Result<(), Unspecified> {
137        self.check_per_nonce_max_bytes(in_ciphertext_len)?;
138
139        // ensure that the lengths match
140        if in_ciphertext_len != out_plaintext_len {
141            return Err(Unspecified);
142        }
143
144        unsafe {
145            let aead_ctx = self.ctx.as_ref();
146            let nonce = nonce.as_ref();
147
148            if 1 != EVP_AEAD_CTX_open_gather(
149                aead_ctx.as_const_ptr(),
150                out_plaintext,
151                nonce.as_ptr(),
152                nonce.len(),
153                in_ciphertext,
154                in_ciphertext_len,
155                in_tag.as_ptr(),
156                in_tag.len(),
157                aad.as_ptr(),
158                aad.len(),
159            ) {
160                return Err(Unspecified);
161            }
162            Ok(())
163        }
164    }
165
166    #[inline]
167    pub(crate) fn seal_in_place_append_tag<'a, InOut>(
168        &self,
169        nonce: Option<Nonce>,
170        aad: &[u8],
171        in_out: &'a mut InOut,
172    ) -> Result<Nonce, Unspecified>
173    where
174        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
175    {
176        self.check_per_nonce_max_bytes(in_out.as_mut().len())?;
177        match nonce {
178            Some(nonce) => self.seal_combined(nonce, aad, in_out),
179            None => self.seal_combined_randnonce(aad, in_out),
180        }
181    }
182
183    #[inline]
184    pub(crate) fn seal_in_place_separate_tag(
185        &self,
186        nonce: Option<Nonce>,
187        aad: &[u8],
188        in_out: &mut [u8],
189    ) -> Result<(Nonce, Tag), Unspecified> {
190        self.check_per_nonce_max_bytes(in_out.len())?;
191        match nonce {
192            Some(nonce) => self.seal_separate(nonce, aad, in_out),
193            None => self.seal_separate_randnonce(aad, in_out),
194        }
195    }
196
197    #[inline]
198    #[allow(clippy::needless_pass_by_value)]
199    pub(crate) fn seal_in_place_separate_scatter(
200        &self,
201        nonce: Nonce,
202        aad: &[u8],
203        in_out: &mut [u8],
204        extra_in: &[u8],
205        extra_out_and_tag: &mut [u8],
206    ) -> Result<(), Unspecified> {
207        self.check_per_nonce_max_bytes(in_out.len())?;
208        // ensure that the extra lengths match
209        {
210            let actual = extra_in.len() + self.algorithm().tag_len();
211            let expected = extra_out_and_tag.len();
212
213            if actual != expected {
214                return Err(Unspecified);
215            }
216        }
217
218        let nonce = nonce.as_ref();
219        let mut out_tag_len = extra_out_and_tag.len();
220
221        if 1 != unsafe {
222            EVP_AEAD_CTX_seal_scatter(
223                self.ctx.as_ref().as_const_ptr(),
224                in_out.as_mut_ptr(),
225                extra_out_and_tag.as_mut_ptr(),
226                &mut out_tag_len,
227                extra_out_and_tag.len(),
228                nonce.as_ptr(),
229                nonce.len(),
230                in_out.as_ptr(),
231                in_out.len(),
232                extra_in.as_ptr(),
233                extra_in.len(),
234                aad.as_ptr(),
235                aad.len(),
236            )
237        } {
238            return Err(Unspecified);
239        }
240        Ok(())
241    }
242
243    /// The key's AEAD algorithm.
244    #[inline]
245    #[must_use]
246    pub fn algorithm(&self) -> &'static Algorithm {
247        self.algorithm
248    }
249
250    #[inline]
251    pub(crate) fn check_per_nonce_max_bytes(&self, in_out_len: usize) -> Result<(), Unspecified> {
252        if in_out_len as u64 > self.algorithm().max_input_len {
253            return Err(Unspecified);
254        }
255        Ok(())
256    }
257
258    #[inline]
259    #[allow(clippy::needless_pass_by_value)]
260    fn open_combined(
261        &self,
262        nonce: Nonce,
263        aad: &[u8],
264        in_out: &mut [u8],
265    ) -> Result<(), Unspecified> {
266        let nonce = nonce.as_ref();
267
268        debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
269
270        let plaintext_len = in_out.len() - self.algorithm().tag_len();
271
272        let mut out_len = MaybeUninit::<usize>::uninit();
273        if 1 != indicator_check!(unsafe {
274            EVP_AEAD_CTX_open(
275                self.ctx.as_ref().as_const_ptr(),
276                in_out.as_mut_ptr(),
277                out_len.as_mut_ptr(),
278                plaintext_len,
279                nonce.as_ptr(),
280                nonce.len(),
281                in_out.as_ptr(),
282                plaintext_len + self.algorithm().tag_len(),
283                aad.as_ptr(),
284                aad.len(),
285            )
286        }) {
287            return Err(Unspecified);
288        }
289
290        Ok(())
291    }
292
293    #[inline]
294    #[allow(clippy::needless_pass_by_value)]
295    fn open_combined_randnonce(
296        &self,
297        nonce: Nonce,
298        aad: &[u8],
299        in_out: &mut [u8],
300    ) -> Result<(), Unspecified> {
301        let nonce = nonce.as_ref();
302
303        let alg_nonce_len = self.algorithm().nonce_len();
304        let alg_tag_len = self.algorithm().tag_len();
305
306        debug_assert_eq!(nonce.len(), alg_nonce_len);
307        debug_assert!(alg_tag_len + alg_nonce_len <= MAX_TAG_NONCE_BUFFER_LEN);
308
309        let plaintext_len = in_out.len() - alg_tag_len;
310
311        let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
312
313        tag_buffer[..alg_tag_len]
314            .copy_from_slice(&in_out[plaintext_len..plaintext_len + alg_tag_len]);
315        tag_buffer[alg_tag_len..alg_tag_len + alg_nonce_len].copy_from_slice(nonce);
316
317        let tag_slice = &tag_buffer[0..alg_tag_len + alg_nonce_len];
318
319        if 1 != indicator_check!(unsafe {
320            EVP_AEAD_CTX_open_gather(
321                self.ctx.as_ref().as_const_ptr(),
322                in_out.as_mut_ptr(),
323                null(),
324                0,
325                in_out.as_ptr(),
326                plaintext_len,
327                tag_slice.as_ptr(),
328                tag_slice.len(),
329                aad.as_ptr(),
330                aad.len(),
331            )
332        }) {
333            return Err(Unspecified);
334        }
335
336        Ok(())
337    }
338
339    #[inline]
340    fn seal_combined<InOut>(
341        &self,
342        nonce: Nonce,
343        aad: &[u8],
344        in_out: &mut InOut,
345    ) -> Result<Nonce, Unspecified>
346    where
347        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
348    {
349        let plaintext_len = in_out.as_mut().len();
350
351        let alg_tag_len = self.algorithm().tag_len();
352
353        debug_assert!(alg_tag_len <= MAX_TAG_LEN);
354
355        let tag_buffer = [0u8; MAX_TAG_LEN];
356
357        in_out.extend(tag_buffer[..alg_tag_len].iter());
358
359        let mut out_len = MaybeUninit::<usize>::uninit();
360        let mut_in_out = in_out.as_mut();
361
362        {
363            let nonce = nonce.as_ref();
364
365            debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
366
367            if 1 != indicator_check!(unsafe {
368                EVP_AEAD_CTX_seal(
369                    self.ctx.as_ref().as_const_ptr(),
370                    mut_in_out.as_mut_ptr(),
371                    out_len.as_mut_ptr(),
372                    plaintext_len + alg_tag_len,
373                    nonce.as_ptr(),
374                    nonce.len(),
375                    mut_in_out.as_ptr(),
376                    plaintext_len,
377                    aad.as_ptr(),
378                    aad.len(),
379                )
380            }) {
381                return Err(Unspecified);
382            }
383        }
384
385        Ok(nonce)
386    }
387
388    #[inline]
389    fn seal_combined_randnonce<InOut>(
390        &self,
391        aad: &[u8],
392        in_out: &mut InOut,
393    ) -> Result<Nonce, Unspecified>
394    where
395        InOut: AsMut<[u8]> + for<'in_out> Extend<&'in_out u8>,
396    {
397        let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
398
399        let mut out_tag_len = MaybeUninit::<usize>::uninit();
400
401        {
402            let plaintext_len = in_out.as_mut().len();
403            let in_out = in_out.as_mut();
404
405            if 1 != indicator_check!(unsafe {
406                EVP_AEAD_CTX_seal_scatter(
407                    self.ctx.as_ref().as_const_ptr(),
408                    in_out.as_mut_ptr(),
409                    tag_buffer.as_mut_ptr(),
410                    out_tag_len.as_mut_ptr(),
411                    tag_buffer.len(),
412                    null(),
413                    0,
414                    in_out.as_ptr(),
415                    plaintext_len,
416                    null(),
417                    0,
418                    aad.as_ptr(),
419                    aad.len(),
420                )
421            }) {
422                return Err(Unspecified);
423            }
424        }
425
426        let tag_len = self.algorithm().tag_len();
427        let nonce_len = self.algorithm().nonce_len();
428
429        let nonce = Nonce(FixedLength::<NONCE_LEN>::try_from(
430            &tag_buffer[tag_len..tag_len + nonce_len],
431        )?);
432
433        in_out.extend(&tag_buffer[..tag_len]);
434
435        Ok(nonce)
436    }
437
438    #[inline]
439    fn seal_separate(
440        &self,
441        nonce: Nonce,
442        aad: &[u8],
443        in_out: &mut [u8],
444    ) -> Result<(Nonce, Tag), Unspecified> {
445        let mut tag = [0u8; MAX_TAG_LEN];
446        let mut out_tag_len = MaybeUninit::<usize>::uninit();
447        {
448            let nonce = nonce.as_ref();
449
450            debug_assert_eq!(nonce.len(), self.algorithm().nonce_len());
451
452            if 1 != indicator_check!(unsafe {
453                EVP_AEAD_CTX_seal_scatter(
454                    self.ctx.as_ref().as_const_ptr(),
455                    in_out.as_mut_ptr(),
456                    tag.as_mut_ptr(),
457                    out_tag_len.as_mut_ptr(),
458                    tag.len(),
459                    nonce.as_ptr(),
460                    nonce.len(),
461                    in_out.as_ptr(),
462                    in_out.len(),
463                    null(),
464                    0usize,
465                    aad.as_ptr(),
466                    aad.len(),
467                )
468            }) {
469                return Err(Unspecified);
470            }
471        }
472        Ok((nonce, Tag(tag, unsafe { out_tag_len.assume_init() })))
473    }
474
475    #[inline]
476    fn seal_separate_randnonce(
477        &self,
478        aad: &[u8],
479        in_out: &mut [u8],
480    ) -> Result<(Nonce, Tag), Unspecified> {
481        let mut tag_buffer = [0u8; MAX_TAG_NONCE_BUFFER_LEN];
482
483        debug_assert!(
484            self.algorithm().tag_len() + self.algorithm().nonce_len() <= tag_buffer.len()
485        );
486
487        let mut out_tag_len = MaybeUninit::<usize>::uninit();
488
489        if 1 != indicator_check!(unsafe {
490            EVP_AEAD_CTX_seal_scatter(
491                self.ctx.as_ref().as_const_ptr(),
492                in_out.as_mut_ptr(),
493                tag_buffer.as_mut_ptr(),
494                out_tag_len.as_mut_ptr(),
495                tag_buffer.len(),
496                null(),
497                0,
498                in_out.as_ptr(),
499                in_out.len(),
500                null(),
501                0usize,
502                aad.as_ptr(),
503                aad.len(),
504            )
505        }) {
506            return Err(Unspecified);
507        }
508
509        let tag_len = self.algorithm().tag_len();
510        let nonce_len = self.algorithm().nonce_len();
511
512        let nonce = Nonce(FixedLength::<NONCE_LEN>::try_from(
513            &tag_buffer[tag_len..tag_len + nonce_len],
514        )?);
515
516        let mut tag = [0u8; MAX_TAG_LEN];
517        tag.copy_from_slice(&tag_buffer[..tag_len]);
518
519        Ok((nonce, Tag(tag, tag_len)))
520    }
521}
522
523impl From<AeadCtx> for UnboundKey {
524    fn from(value: AeadCtx) -> Self {
525        let algorithm = match value {
526            AeadCtx::AES_128_GCM(_)
527            | AeadCtx::AES_128_GCM_TLS12(_)
528            | AeadCtx::AES_128_GCM_TLS13(_)
529            | AeadCtx::AES_128_GCM_RANDNONCE(_) => &AES_128_GCM,
530            AeadCtx::AES_192_GCM(_) => &AES_192_GCM,
531            AeadCtx::AES_128_GCM_SIV(_) => &AES_128_GCM_SIV,
532            AeadCtx::AES_256_GCM(_)
533            | AeadCtx::AES_256_GCM_RANDNONCE(_)
534            | AeadCtx::AES_256_GCM_TLS12(_)
535            | AeadCtx::AES_256_GCM_TLS13(_) => &AES_256_GCM,
536            AeadCtx::AES_256_GCM_SIV(_) => &AES_256_GCM_SIV,
537            AeadCtx::CHACHA20_POLY1305(_) => &CHACHA20_POLY1305,
538        };
539        Self {
540            ctx: value,
541            algorithm,
542        }
543    }
544}
545
546impl From<hkdf::Okm<'_, &'static Algorithm>> for UnboundKey {
547    fn from(okm: hkdf::Okm<&'static Algorithm>) -> Self {
548        let mut key_bytes = [0; MAX_KEY_LEN];
549        let key_bytes = &mut key_bytes[..okm.len().key_len];
550        let algorithm = *okm.len();
551        okm.fill(key_bytes).unwrap();
552        Self::new(algorithm, key_bytes).unwrap()
553    }
554}