Skip to main content

aes_kw/
kwp.rs

1use core::ops::{Add, Div, Mul};
2
3use crate::{Error, IV_LEN, IntegrityCheckFailed, IvLen, ctx::Ctx};
4use aes::cipher::{
5    Array, Block, BlockCipherDecrypt, BlockCipherEncrypt,
6    array::ArraySize,
7    common::{InnerInit, InnerUser},
8    consts::{B1, U7, U4294967296},
9    typenum::{Add1, IsLess, Le, NonZero, Prod, Quot, Sum, U16},
10};
11
12/// Maximum length of the AES-KWP input data (2^32 bytes) represented as a `typenum` type.
13type KwpMaxLen = U4294967296;
14/// Maximum length of the AES-KWP input data (2^32 - 1 bytes).
15const KWP_MAX_LEN: usize = u32::MAX as usize;
16
17/// Alternative Initial Value constant prefix for AES-KWP as defined in RFC 3394 § 3.
18///
19/// <https://datatracker.ietf.org/doc/html/rfc5649#section-3>
20///
21/// ```text
22/// The Alternative Initial Value (AIV) required by this specification is
23//  a 32-bit constant concatenated to a 32-bit MLI.  The constant is (in
24//  hexadecimal) A65959A6 and occupies the high-order half of the AIV.
25/// ```
26const KWP_IV_PREFIX: [u8; IV_LEN / 2] = [0xA6, 0x59, 0x59, 0xA6];
27
28/// [`IvLen`] (`U8`) minus one
29type IvLenM1 = U7;
30
31/// Type alias representing wrapped key roughly equivalent to
32/// `[u8; IV_LEN * (N.div_ceil(IV_LEN) + 1)]`.
33pub type KwpWrappedKey<N> = Array<u8, Prod<Add1<Quot<Sum<N, IvLenM1>, IvLen>>, IvLen>>;
34
35/// AES Key Wrapper with Padding (KWP), as defined in [RFC 5649].
36///
37/// [RFC 5649]: https://www.rfc-editor.org/rfc/rfc5649.txt
38#[derive(Debug, Clone, Copy, PartialEq)]
39pub struct AesKwp<C> {
40    cipher: C,
41}
42
43impl<C> InnerUser for AesKwp<C> {
44    type Inner = C;
45}
46
47impl<C> InnerInit for AesKwp<C> {
48    #[inline]
49    fn inner_init(cipher: Self::Inner) -> Self {
50        AesKwp { cipher }
51    }
52}
53
54impl<C: BlockCipherEncrypt<BlockSize = U16>> AesKwp<C> {
55    /// Wrap key into `buf` assuming that it has correct length.
56    fn wrap_key_trusted(&self, key: &[u8], buf: &mut [u8]) {
57        let semiblocks_len = key.len().div_ceil(IV_LEN);
58
59        // 2) Wrapping
60
61        // 2.1) Initialize variables
62
63        // Set A to the AIV
64        let block = &mut Block::<C>::default();
65        let (prefix, mli) = block[..IV_LEN].split_at_mut(IV_LEN / 2);
66        prefix.copy_from_slice(&KWP_IV_PREFIX);
67        // 32-bit MLI equal to the number of bytes in the input data, big endian
68        mli.copy_from_slice(&(key.len() as u32).to_be_bytes());
69
70        // If semiblocks_len is 1, the plaintext is encrypted as a single AES block
71        if semiblocks_len == 1 {
72            // 1) Append padding
73
74            block[IV_LEN..][..key.len()].copy_from_slice(key);
75            self.cipher
76                .encrypt_block_b2b(block, buf.try_into().unwrap());
77        } else {
78            // 1) Append padding
79
80            // 2.2) Calculate intermediate values
81            buf[IV_LEN..][..key.len()].copy_from_slice(key);
82
83            self.cipher.encrypt_with_backend(Ctx {
84                blocks_len: semiblocks_len,
85                block,
86                buf,
87            });
88
89            // 2.3) Output the results
90            buf[..IV_LEN].copy_from_slice(&block[..IV_LEN]);
91        }
92    }
93
94    /// AES Key Wrap with Padding, as defined in RFC 5649.
95    ///
96    /// The `buf` buffer will be overwritten, and must be the smallest
97    /// multiple of [`IV_LEN`] (i.e. 8) which is at least [`IV_LEN`]
98    /// bytes (i.e. 8 bytes) longer than the length of `data`.
99    #[inline]
100    pub fn wrap_key<'a>(&self, key: &[u8], buf: &'a mut [u8]) -> Result<&'a [u8], Error> {
101        if key.len() > KWP_MAX_LEN {
102            return Err(Error::InvalidDataSize);
103        }
104
105        // 0) Prepare inputs
106
107        // number of 64 bit blocks in the input data (padded)
108        let semiblocks_len = key.len().div_ceil(IV_LEN);
109
110        let expected_len = semiblocks_len * IV_LEN + IV_LEN;
111        let buf = buf
112            .get_mut(..expected_len)
113            .ok_or(Error::InvalidOutputSize { expected_len })?;
114
115        self.wrap_key_trusted(key, buf);
116
117        Ok(buf)
118    }
119
120    /// Wrap fixed-size key `key` and return wrapped key.
121    ///
122    /// This method is roughly equivalent to:
123    /// ```ignore
124    /// pub fn wrap_fixed_key<const N: usize>(
125    ///     &self,
126    ///     key: &[u8; N],
127    /// ) -> [u8; IV_LEN * (N.div_ceil(IV_LEN) + 1)]
128    /// { ... }
129    /// ```
130    /// but uses [`hybrid_array::Array`][Array] instead of built-in arrays
131    /// to work around current limitations of the const generics system.
132    #[inline]
133    pub fn wrap_fixed_key<N>(&self, key: &Array<u8, N>) -> KwpWrappedKey<N>
134    where
135        N: ArraySize + Add<IvLenM1> + IsLess<KwpMaxLen>,
136        Le<N, KwpMaxLen>: NonZero,
137        Sum<N, IvLenM1>: Div<IvLen>,
138        Quot<Sum<N, IvLenM1>, IvLen>: Add<B1>,
139        Add1<Quot<Sum<N, IvLenM1>, IvLen>>: Mul<IvLen>,
140        Prod<Add1<Quot<Sum<N, IvLenM1>, IvLen>>, IvLen>: ArraySize,
141    {
142        // 0) Prepare inputs
143
144        // number of 64 bit blocks in the input data (padded)
145
146        let semiblocks_len = key.len().div_ceil(IV_LEN);
147        let mut buf = KwpWrappedKey::<N>::default();
148        assert_eq!(semiblocks_len * IV_LEN + IV_LEN, buf.len());
149
150        self.wrap_key_trusted(key, &mut buf);
151
152        buf
153    }
154}
155
156impl<C: BlockCipherDecrypt<BlockSize = U16>> AesKwp<C> {
157    /// Unwrap key into `buf` assuming that it has correct length.
158    fn unwrap_key_trusted<'a>(
159        &self,
160        wkey: &[u8],
161        buf: &'a mut [u8],
162    ) -> Result<&'a [u8], IntegrityCheckFailed> {
163        let blocks_len = buf.len() / IV_LEN;
164
165        // 1) Key unwrapping
166
167        // 1.1) Initialize variables
168
169        let block = &mut Block::<C>::default();
170
171        // If n is 1, the plaintext is encrypted as a single AES block
172        if blocks_len == 1 {
173            block.copy_from_slice(wkey);
174            self.cipher.decrypt_block(block);
175            buf.copy_from_slice(&block[IV_LEN..]);
176        } else {
177            block[..IV_LEN].copy_from_slice(&wkey[..IV_LEN]);
178
179            //   for i = 1 to n: R[i] = C[i]
180            buf.copy_from_slice(&wkey[IV_LEN..]);
181
182            // 1.2) Calculate intermediate values
183
184            self.cipher.decrypt_with_backend(Ctx {
185                blocks_len,
186                block,
187                buf,
188            });
189        }
190
191        // 2) AIV verification
192
193        // Checks as defined in RFC5649 § 3
194
195        let prefix_calc = u32::from_ne_bytes(block[..IV_LEN / 2].try_into().unwrap());
196        let prefix_exp = u32::from_ne_bytes(KWP_IV_PREFIX);
197        if prefix_calc != prefix_exp {
198            buf.fill(0);
199            return Err(IntegrityCheckFailed);
200        }
201
202        let mli_bytes = block[IV_LEN / 2..IV_LEN].try_into().unwrap();
203        let mli: usize = usize::try_from(u32::from_be_bytes(mli_bytes)).map_err(|_| {
204            buf.fill(0);
205            IntegrityCheckFailed
206        })?;
207        if mli.div_ceil(IV_LEN) != blocks_len {
208            buf.fill(0);
209            return Err(IntegrityCheckFailed);
210        }
211
212        let (res, pad) = buf.split_at_mut(mli);
213        if !pad.iter().all(|&b| b == 0) {
214            res.fill(0);
215            pad.fill(0);
216            return Err(IntegrityCheckFailed);
217        }
218
219        Ok(res)
220    }
221
222    /// AES Key Wrap with Padding, as defined in RFC 5649.
223    ///
224    /// The `buf` buffer will be overwritten, and must be exactly [`IV_LEN`]
225    /// bytes (i.e. 8 bytes) shorter than the length of `data`.
226    /// This method returns a slice of `out`, truncated to the appropriate
227    /// length by removing the padding.
228    #[inline]
229    pub fn unwrap_key<'a>(&self, data: &[u8], buf: &'a mut [u8]) -> Result<&'a [u8], Error> {
230        let blocks_len = data.len() / IV_LEN;
231        let blocks_rem = data.len() % IV_LEN;
232        if blocks_rem != 0 || blocks_len < 1 || data.len() > KWP_MAX_LEN {
233            return Err(Error::InvalidDataSize);
234        }
235
236        let blocks_len = blocks_len - 1;
237        let expected_len = blocks_len * IV_LEN;
238        let buf = buf
239            .get_mut(..expected_len)
240            .ok_or(Error::InvalidOutputSize { expected_len })?;
241
242        self.unwrap_key_trusted(data, buf)
243            .map_err(|_| Error::IntegrityCheckFailed)
244    }
245
246    /// Unwrap fixed-size wrapped key `wkey` and return resulting key.
247    ///
248    /// This method is roughly equivalent to:
249    /// ```ignore
250    /// pub fn unwrap_fixed_key<const N: usize>(
251    ///     &self,
252    ///     wkey: &[u8; IV_LEN * (N.div_ceil(IV_LEN) + 1)],
253    /// ) -> [u8; N]
254    /// { ... }
255    /// ```
256    /// but uses [`hybrid_array::Array`][Array] instead of built-in arrays
257    /// to work around current limitations of the const generics system.
258    #[inline]
259    pub fn unwrap_fixed_key<N>(
260        &self,
261        wkey: &KwpWrappedKey<N>,
262    ) -> Result<Array<u8, N>, IntegrityCheckFailed>
263    where
264        N: ArraySize + Add<IvLenM1> + IsLess<KwpMaxLen>,
265        Le<N, KwpMaxLen>: NonZero,
266        Sum<N, IvLenM1>: Div<IvLen>,
267        Quot<Sum<N, IvLenM1>, IvLen>: Add<B1> + Mul<IvLen>,
268        Add1<Quot<Sum<N, IvLenM1>, IvLen>>: Mul<IvLen>,
269        Prod<Add1<Quot<Sum<N, IvLenM1>, IvLen>>, IvLen>: ArraySize,
270        Prod<Quot<Sum<N, IvLenM1>, IvLen>, IvLen>: ArraySize,
271    {
272        let mut buf = Array::<u8, Prod<Quot<Sum<N, IvLenM1>, IvLen>, IvLen>>::default();
273        self.unwrap_key_trusted(wkey, &mut buf)
274            .map(|res| res.try_into().unwrap())
275    }
276}
277
278#[cfg(feature = "zeroize")]
279impl<C: zeroize::ZeroizeOnDrop> zeroize::ZeroizeOnDrop for AesKwp<C> {}