Skip to main content

aws_lc_rs/
key_wrap.rs

1// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2// SPDX-License-Identifier: Apache-2.0 OR ISC
3
4//! Key Wrap Algorithms.
5//!
6//! # Examples
7//! ```rust
8//! # use std::error::Error;
9//! # fn main() -> Result<(), Box<dyn Error>> {
10//! use aws_lc_rs::key_wrap::{AesKek, KeyWrapPadded, AES_128};
11//!
12//! const KEY: &[u8] = &[
13//!     0xa8, 0xe0, 0x6d, 0xa6, 0x25, 0xa6, 0x5b, 0x25, 0xcf, 0x50, 0x30, 0x82, 0x68, 0x30, 0xb6,
14//!     0x61,
15//! ];
16//! const PLAINTEXT: &[u8] = &[0x43, 0xac, 0xff, 0x29, 0x31, 0x20, 0xdd, 0x5d];
17//!
18//! let kek = AesKek::new(&AES_128, KEY)?;
19//!
20//! let mut output = vec![0u8; PLAINTEXT.len() + 15];
21//!
22//! let ciphertext = kek.wrap_with_padding(PLAINTEXT, &mut output)?;
23//!
24//! let kek = AesKek::new(&AES_128, KEY)?;
25//!
26//! let mut output = vec![0u8; ciphertext.len()];
27//!
28//! let plaintext = kek.unwrap_with_padding(&*ciphertext, &mut output)?;
29//!
30//! assert_eq!(PLAINTEXT, plaintext);
31//! # Ok(())
32//! # }
33//! ```
34
35use crate::aws_lc::{
36    AES_set_decrypt_key, AES_set_encrypt_key, AES_unwrap_key, AES_unwrap_key_padded, AES_wrap_key,
37    AES_wrap_key_padded, AES_KEY,
38};
39use crate::error::Unspecified;
40use crate::fips::indicator_check;
41use crate::sealed::Sealed;
42use core::fmt::Debug;
43use core::mem::MaybeUninit;
44use core::ptr::null;
45use zeroize::Zeroize;
46
47mod tests;
48
49/// The Key Wrapping Algorithm Identifier
50#[derive(Debug, PartialEq, Eq, Clone, Copy)]
51#[non_exhaustive]
52pub enum BlockCipherId {
53    /// AES Block Cipher with 128-bit key.
54    Aes128,
55
56    /// AES Block Cipher with 256-bit key.
57    Aes256,
58}
59
60/// A key wrap block cipher.
61pub trait BlockCipher: 'static + Debug + Sealed {
62    /// The block cipher identifier.
63    fn id(&self) -> BlockCipherId;
64
65    /// The key size in bytes to be used with the block cipher.
66    fn key_len(&self) -> usize;
67}
68
69/// An AES Block Cipher
70pub struct AesBlockCipher {
71    id: BlockCipherId,
72    key_len: usize,
73}
74
75impl BlockCipher for AesBlockCipher {
76    /// Returns the algorithm identifier.
77    #[inline]
78    fn id(&self) -> BlockCipherId {
79        self.id
80    }
81
82    /// Returns the algorithm key length.
83    #[inline]
84    fn key_len(&self) -> usize {
85        self.key_len
86    }
87}
88
89impl Sealed for AesBlockCipher {}
90
91impl Debug for AesBlockCipher {
92    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
93        Debug::fmt(&self.id, f)
94    }
95}
96
97/// AES Block Cipher with 128-bit key.
98pub const AES_128: AesBlockCipher = AesBlockCipher {
99    id: BlockCipherId::Aes128,
100    key_len: 16,
101};
102
103/// AES Block Cipher with 256-bit key.
104pub const AES_256: AesBlockCipher = AesBlockCipher {
105    id: BlockCipherId::Aes256,
106    key_len: 32,
107};
108
109/// A Key Wrap (KW) algorithm implementation.
110#[allow(clippy::module_name_repetitions)]
111pub trait KeyWrap: Sealed {
112    /// Performs the key wrap encryption algorithm using a block cipher.
113    /// It wraps `plaintext` and writes the corresponding ciphertext to `output`.
114    ///
115    /// # Errors
116    /// * [`Unspecified`]: Any error that has occurred performing the operation.
117    fn wrap<'output>(
118        self,
119        plaintext: &[u8],
120        output: &'output mut [u8],
121    ) -> Result<&'output mut [u8], Unspecified>;
122
123    /// Performs the key wrap decryption algorithm using a block cipher.
124    /// It unwraps `ciphertext` and writes the corresponding plaintext to `output`.
125    ///
126    /// # Errors
127    /// * [`Unspecified`]: Any error that has occurred performing the operation.
128    fn unwrap<'output>(
129        self,
130        ciphertext: &[u8],
131        output: &'output mut [u8],
132    ) -> Result<&'output mut [u8], Unspecified>;
133}
134
135/// A Key Wrap with Padding (KWP) algorithm implementation.
136#[allow(clippy::module_name_repetitions)]
137pub trait KeyWrapPadded: Sealed {
138    /// Performs the key wrap padding encryption algorithm using a block cipher.
139    /// It wraps and pads `plaintext` writes the corresponding ciphertext to `output`.
140    ///
141    /// # Errors
142    /// * [`Unspecified`]: Any error that has occurred performing the operation.
143    fn wrap_with_padding<'output>(
144        self,
145        plaintext: &[u8],
146        output: &'output mut [u8],
147    ) -> Result<&'output mut [u8], Unspecified>;
148
149    /// Performs the key wrap padding decryption algorithm using a block cipher.
150    /// It unwraps the padded `ciphertext` and writes the corresponding plaintext to `output`.
151    ///
152    /// # Errors
153    /// * [`Unspecified`]: Any error that has occurred performing the operation.
154    fn unwrap_with_padding<'output>(
155        self,
156        ciphertext: &[u8],
157        output: &'output mut [u8],
158    ) -> Result<&'output mut [u8], Unspecified>;
159}
160
161/// AES Key Encryption Key.
162pub type AesKek = KeyEncryptionKey<AesBlockCipher>;
163
164/// The key-encryption key used with the selected cipher algorithm to wrap or unwrap a key.
165///
166/// Implements the NIST SP 800-38F key wrapping algorithm.
167///
168/// The NIST specification is similar to that of RFC 3394 but with the following caveats:
169/// * Specifies a maximum plaintext length that can be accepted.
170/// * Allows implementations to specify a subset of valid lengths accepted.
171/// * Allows for the usage of other 128-bit block ciphers other than AES.
172pub struct KeyEncryptionKey<Cipher: BlockCipher> {
173    cipher: &'static Cipher,
174    key: Box<[u8]>,
175}
176
177impl<Cipher: BlockCipher> KeyEncryptionKey<Cipher> {
178    /// Construct a new Key Encryption Key.
179    ///
180    /// # Errors
181    /// * [`Unspecified`]: Any error that occurs constructing the key encryption key.
182    pub fn new(cipher: &'static Cipher, key: &[u8]) -> Result<Self, Unspecified> {
183        if key.len() != cipher.key_len() {
184            return Err(Unspecified);
185        }
186
187        let key = Vec::from(key).into_boxed_slice();
188
189        Ok(Self { cipher, key })
190    }
191
192    /// Returns the block cipher algorithm identifier configured for the key.
193    #[must_use]
194    pub fn block_cipher_id(&self) -> BlockCipherId {
195        self.cipher.id()
196    }
197}
198
199impl<Cipher: BlockCipher> Sealed for KeyEncryptionKey<Cipher> {}
200
201impl KeyWrap for KeyEncryptionKey<AesBlockCipher> {
202    /// Performs the key wrap encryption algorithm using `KeyEncryptionKey`'s configured block cipher.
203    /// It wraps `plaintext` and writes the corresponding ciphertext to `output`.
204    ///
205    /// # Validation
206    /// * `plaintext.len()` must be a multiple of eight
207    /// * `output.len() >= (input.len() + 8)`
208    ///
209    /// # Errors
210    /// * [`Unspecified`]: An error occurred either due to `output` being insufficiently sized, `input` exceeding
211    ///   the allowed input size, or for other unspecified reasons.
212    fn wrap<'output>(
213        self,
214        plaintext: &[u8],
215        output: &'output mut [u8],
216    ) -> Result<&'output mut [u8], Unspecified> {
217        if output.len() < plaintext.len() + 8 {
218            return Err(Unspecified);
219        }
220
221        let mut aes_key = MaybeUninit::<AES_KEY>::uninit();
222
223        let key_bits: u32 = (self.key.len() * 8).try_into().map_err(|_| Unspecified)?;
224
225        if 0 != unsafe { AES_set_encrypt_key(self.key.as_ptr(), key_bits, aes_key.as_mut_ptr()) } {
226            return Err(Unspecified);
227        }
228
229        let aes_key = unsafe { aes_key.assume_init() };
230
231        // AWS-LC validates the following:
232        // * in_len <= INT_MAX - 8
233        // * in_len >= 16
234        // * in_len % 8 == 0
235        let out_len = indicator_check!(unsafe {
236            AES_wrap_key(
237                &aes_key,
238                null(),
239                output.as_mut_ptr(),
240                plaintext.as_ptr(),
241                plaintext.len(),
242            )
243        });
244
245        if out_len == -1 {
246            return Err(Unspecified);
247        }
248
249        let out_len: usize = out_len.try_into().map_err(|_| Unspecified)?;
250
251        debug_assert_eq!(out_len, plaintext.len() + 8);
252
253        Ok(&mut output[..out_len])
254    }
255
256    /// Performs the key wrap decryption algorithm using `KeyEncryptionKey`'s configured block cipher.
257    /// It unwraps `ciphertext` and writes the corresponding plaintext to `output`.
258    ///
259    /// # Validation
260    /// * `ciphertext.len()` must be a multiple of 8
261    /// * `output.len() >= (input.len() - 8)`
262    ///
263    /// # Errors
264    /// * [`Unspecified`]: An error occurred either due to `output` being insufficiently sized, `input` exceeding
265    ///   the allowed input size, or for other unspecified reasons.
266    fn unwrap<'output>(
267        self,
268        ciphertext: &[u8],
269        output: &'output mut [u8],
270    ) -> Result<&'output mut [u8], Unspecified> {
271        if ciphertext.len() < 8 || output.len() < ciphertext.len() - 8 {
272            return Err(Unspecified);
273        }
274
275        let mut aes_key = MaybeUninit::<AES_KEY>::uninit();
276
277        if 0 != unsafe {
278            AES_set_decrypt_key(
279                self.key.as_ptr(),
280                (self.key.len() * 8).try_into().map_err(|_| Unspecified)?,
281                aes_key.as_mut_ptr(),
282            )
283        } {
284            return Err(Unspecified);
285        }
286
287        let aes_key = unsafe { aes_key.assume_init() };
288
289        // AWS-LC validates the following:
290        // * in_len <= INT_MAX
291        // * in_len >= 24
292        // * in_len % 8 == 0
293        let out_len = indicator_check!(unsafe {
294            AES_unwrap_key(
295                &aes_key,
296                null(),
297                output.as_mut_ptr(),
298                ciphertext.as_ptr(),
299                ciphertext.len(),
300            )
301        });
302
303        if out_len == -1 {
304            return Err(Unspecified);
305        }
306
307        let out_len: usize = out_len.try_into().map_err(|_| Unspecified)?;
308
309        debug_assert_eq!(out_len, ciphertext.len() - 8);
310
311        Ok(&mut output[..out_len])
312    }
313}
314
315impl KeyWrapPadded for KeyEncryptionKey<AesBlockCipher> {
316    /// Performs the key wrap padding encryption algorithm using `KeyEncryptionKey`'s configured block cipher.
317    /// It wraps and pads `plaintext` writes the corresponding ciphertext to `output`.
318    ///
319    /// # Validation
320    /// * `output.len() >= (input.len() + 15)`
321    ///
322    /// # Errors
323    /// * [`Unspecified`]: An error occurred either due to `output` being insufficiently sized, `input` exceeding
324    ///   the allowed input size, or for other unspecified reasons.
325    fn wrap_with_padding<'output>(
326        self,
327        plaintext: &[u8],
328        output: &'output mut [u8],
329    ) -> Result<&'output mut [u8], Unspecified> {
330        let mut aes_key = MaybeUninit::<AES_KEY>::uninit();
331
332        let key_bits: u32 = (self.key.len() * 8).try_into().map_err(|_| Unspecified)?;
333
334        if 0 != unsafe { AES_set_encrypt_key(self.key.as_ptr(), key_bits, aes_key.as_mut_ptr()) } {
335            return Err(Unspecified);
336        }
337
338        let aes_key = unsafe { aes_key.assume_init() };
339
340        let mut out_len: usize = 0;
341
342        // AWS-LC validates the following:
343        // * in_len != 0
344        // * in_len <= INT_MAX
345        // * max_out >= required_padding + 8
346        if 1 != indicator_check!(unsafe {
347            AES_wrap_key_padded(
348                &aes_key,
349                output.as_mut_ptr(),
350                &mut out_len,
351                output.len(),
352                plaintext.as_ptr(),
353                plaintext.len(),
354            )
355        }) {
356            return Err(Unspecified);
357        }
358
359        Ok(&mut output[..out_len])
360    }
361
362    /// Performs the key wrap padding decryption algorithm using `KeyEncryptionKey`'s configured block cipher.
363    /// It unwraps the padded `ciphertext` and writes the corresponding plaintext to `output`.
364    ///
365    /// # Sizing `output`
366    /// `output.len() >= input.len()`.
367    ///
368    /// # Errors
369    /// * [`Unspecified`]: An error occurred either due to `output` being insufficiently sized, `input` exceeding
370    ///   the allowed input size, or for other unspecified reasons.
371    fn unwrap_with_padding<'output>(
372        self,
373        ciphertext: &[u8],
374        output: &'output mut [u8],
375    ) -> Result<&'output mut [u8], Unspecified> {
376        let mut aes_key = MaybeUninit::<AES_KEY>::uninit();
377
378        if 0 != unsafe {
379            AES_set_decrypt_key(
380                self.key.as_ptr(),
381                (self.key.len() * 8).try_into().map_err(|_| Unspecified)?,
382                aes_key.as_mut_ptr(),
383            )
384        } {
385            return Err(Unspecified);
386        }
387
388        let aes_key = unsafe { aes_key.assume_init() };
389
390        let mut out_len: usize = 0;
391
392        // AWS-LC validates the following:
393        // * in_len >= AES_BLOCK_SIZE
394        // * max_out >= in_len - 8
395        if 1 != indicator_check!(unsafe {
396            AES_unwrap_key_padded(
397                &aes_key,
398                output.as_mut_ptr(),
399                &mut out_len,
400                output.len(),
401                ciphertext.as_ptr(),
402                ciphertext.len(),
403            )
404        }) {
405            return Err(Unspecified);
406        }
407
408        Ok(&mut output[..out_len])
409    }
410}
411
412impl<Cipher: BlockCipher> Drop for KeyEncryptionKey<Cipher> {
413    fn drop(&mut self) {
414        self.key.zeroize();
415    }
416}
417
418impl<Cipher: BlockCipher> Debug for KeyEncryptionKey<Cipher> {
419    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
420        f.debug_struct("KeyEncryptionKey")
421            .field("cipher", &self.cipher)
422            .finish_non_exhaustive()
423    }
424}