Skip to main content

ic_cipher/
keywrap.rs

1//! AES Key Wrap (SP 800-38F, RFC 3394 and RFC 5649).
2//!
3//! A cipher for encrypting keys with keys. It exists because the obvious
4//! alternative — a general AEAD — needs a nonce, and the places key wrapping is
5//! used are exactly the places where nonce management is hardest: a hardware
6//! token with no clock, a backup file written once and read years later, a JOSE
7//! header with nowhere to put one.
8//!
9//! Key Wrap solves that by being deterministic and taking no nonce at all. It
10//! buys the missing randomization with six passes over the data, so every output
11//! block depends on every input block, and integrity comes from a fixed check
12//! value recovered on unwrap rather than from a separate tag.
13//!
14//! # Two variants
15//!
16//! [`Aes256Kw`] wraps data that is a whole number of 64-bit blocks, at least two
17//! of them — which covers every symmetric key anyone actually wraps.
18//! [`Aes256Kwp`] adds RFC 5649 padding for arbitrary lengths, at the cost of
19//! revealing the length to within eight bytes.
20//!
21//! # What it does not do
22//!
23//! There is no associated data, and the integrity check is 64 bits, not 128.
24//! SP 800-38F is explicit that this is a key-wrapping mechanism and not a
25//! general-purpose AEAD; for bulk data use AES-GCM or ChaCha20-Poly1305, which
26//! this workspace also has.
27
28use ic_core::traits::{Algorithm, BlockCipher, SelfTest};
29use ic_core::{ensure, Result, Zeroize};
30
31/// The fixed check value from RFC 3394 section 2.2.3.1.
32///
33/// Recovering it on unwrap is what authenticates the ciphertext. Sixty-four
34/// bits of integrity is weaker than an AEAD tag, and deliberate: the
35/// construction predates modern AEADs and its security argument accounts for
36/// the width.
37const KW_IV: [u8; 8] = [0xa6; 8];
38
39/// The RFC 5649 alternative check value, which carries a length.
40const KWP_IV: [u8; 4] = [0xa6, 0x59, 0x59, 0xa6];
41
42/// Largest wrapped payload this handles, in 64-bit blocks.
43///
44/// Sized for a 4096-bit RSA private key with room to spare. The bound exists so
45/// the implementation can work on the stack.
46const MAX_BLOCKS: usize = 128;
47
48/// Ciphertext is one block longer than plaintext.
49pub const OVERHEAD: usize = 8;
50
51/// The core RFC 3394 wrapping loop, over `n` 64-bit blocks already in `r`.
52///
53/// Indexed rather than iterated because the index is the point: block `i` in
54/// round `j` is combined with the counter `n*j + i + 1`, and that relationship
55/// is what the six passes are built on.
56#[allow(clippy::needless_range_loop)]
57fn wrap_blocks<C: BlockCipher>(
58    cipher: &C,
59    a: &mut [u8; 8],
60    r: &mut [[u8; 8]],
61    n: usize,
62) -> Result<()> {
63    let mut block = [0u8; 16];
64    for j in 0..6u64 {
65        for i in 0..n {
66            block[..8].copy_from_slice(a);
67            block[8..].copy_from_slice(&r[i]);
68            cipher.encrypt_block(&mut block)?;
69
70            // t = n*j + i + 1, xored into the low end of A.
71            let t = (n as u64) * j + (i as u64) + 1;
72            a.copy_from_slice(&block[..8]);
73            for (k, byte) in t.to_be_bytes().iter().enumerate() {
74                a[k] ^= *byte;
75            }
76            r[i].copy_from_slice(&block[8..]);
77        }
78    }
79    block.zeroize();
80    Ok(())
81}
82
83/// The inverse loop. Runs the rounds and counters backwards.
84#[allow(clippy::needless_range_loop)]
85fn unwrap_blocks<C: BlockCipher>(
86    cipher: &C,
87    a: &mut [u8; 8],
88    r: &mut [[u8; 8]],
89    n: usize,
90) -> Result<()> {
91    let mut block = [0u8; 16];
92    for j in (0..6u64).rev() {
93        for i in (0..n).rev() {
94            let t = (n as u64) * j + (i as u64) + 1;
95            block[..8].copy_from_slice(a);
96            for (k, byte) in t.to_be_bytes().iter().enumerate() {
97                block[k] ^= *byte;
98            }
99            block[8..].copy_from_slice(&r[i]);
100            cipher.decrypt_block(&mut block)?;
101
102            a.copy_from_slice(&block[..8]);
103            r[i].copy_from_slice(&block[8..]);
104        }
105    }
106    block.zeroize();
107    Ok(())
108}
109
110/// Declare a key-wrap pair over one AES key size.
111macro_rules! key_wrap {
112    ($kw:ident, $kwp:ident, $cipher:ty, $key_len:literal, $kw_id:literal, $kwp_id:literal) => {
113        #[doc = concat!("SP 800-38F KW with AES-", stringify!($key_len), "*8.")]
114        pub struct $kw;
115
116        impl Algorithm for $kw {
117            const ID: &'static str = $kw_id;
118            const NAME: &'static str = $kw_id;
119        }
120
121        impl $kw {
122            /// Key-encryption key length.
123            pub const KEY_LEN: usize = $key_len;
124
125            /// Wrap `plaintext`, writing `plaintext.len() + 8` bytes.
126            ///
127            /// The input must be a whole number of 64-bit blocks and at least
128            /// two of them. A single block is refused: RFC 3394's loop
129            /// degenerates there, and RFC 5649 exists to cover it.
130            pub fn wrap(kek: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<()> {
131                ensure!(kek.len() == $key_len, InvalidLength, "key-wrap kek");
132                ensure!(
133                    plaintext.len() % 8 == 0,
134                    InvalidLength,
135                    "key-wrap input must be a whole number of 64-bit blocks"
136                );
137                let n = plaintext.len() / 8;
138                ensure!(
139                    n >= 2,
140                    InvalidLength,
141                    "key-wrap input must be at least 16 bytes"
142                );
143                ensure!(n <= MAX_BLOCKS, InvalidLength, "key-wrap input too large");
144                ensure!(
145                    out.len() == plaintext.len() + OVERHEAD,
146                    InvalidLength,
147                    "key-wrap output"
148                );
149
150                let cipher = <$cipher>::new(kek)?;
151                let mut a = KW_IV;
152                let mut r = [[0u8; 8]; MAX_BLOCKS];
153                for i in 0..n {
154                    r[i].copy_from_slice(&plaintext[i * 8..(i + 1) * 8]);
155                }
156
157                wrap_blocks(&cipher, &mut a, &mut r[..n], n)?;
158
159                out[..8].copy_from_slice(&a);
160                for i in 0..n {
161                    out[8 + i * 8..16 + i * 8].copy_from_slice(&r[i]);
162                }
163                for block in r.iter_mut() {
164                    block.zeroize();
165                }
166                Ok(())
167            }
168
169            /// Unwrap, writing `ciphertext.len() - 8` bytes.
170            ///
171            /// Fails if the recovered check value is wrong, which is the only
172            /// integrity signal the construction has.
173            pub fn unwrap(kek: &[u8], ciphertext: &[u8], out: &mut [u8]) -> Result<()> {
174                ensure!(kek.len() == $key_len, InvalidLength, "key-wrap kek");
175                ensure!(
176                    ciphertext.len() % 8 == 0 && ciphertext.len() >= 24,
177                    InvalidLength,
178                    "key-wrap ciphertext"
179                );
180                let n = ciphertext.len() / 8 - 1;
181                ensure!(
182                    n <= MAX_BLOCKS,
183                    InvalidLength,
184                    "key-wrap ciphertext too large"
185                );
186                ensure!(
187                    out.len() == ciphertext.len() - OVERHEAD,
188                    InvalidLength,
189                    "key-wrap output"
190                );
191
192                let cipher = <$cipher>::new(kek)?;
193                let mut a = [0u8; 8];
194                a.copy_from_slice(&ciphertext[..8]);
195                let mut r = [[0u8; 8]; MAX_BLOCKS];
196                for i in 0..n {
197                    r[i].copy_from_slice(&ciphertext[8 + i * 8..16 + i * 8]);
198                }
199
200                unwrap_blocks(&cipher, &mut a, &mut r[..n], n)?;
201
202                // Constant-time: an early return on the check value would leak
203                // nothing much here, but there is no reason to leak it.
204                let ok = ic_core::ct::verify(&a, &KW_IV);
205                if !ok {
206                    for block in r.iter_mut() {
207                        block.zeroize();
208                    }
209                    return Err(ic_core::err!(AuthenticationFailed, $kw_id));
210                }
211                for i in 0..n {
212                    out[i * 8..(i + 1) * 8].copy_from_slice(&r[i]);
213                }
214                for block in r.iter_mut() {
215                    block.zeroize();
216                }
217                Ok(())
218            }
219        }
220
221        #[doc = concat!("SP 800-38F KWP with AES-", stringify!($key_len), "*8, per RFC 5649.")]
222        pub struct $kwp;
223
224        impl Algorithm for $kwp {
225            const ID: &'static str = $kwp_id;
226            const NAME: &'static str = $kwp_id;
227        }
228
229        impl $kwp {
230            /// Key-encryption key length.
231            pub const KEY_LEN: usize = $key_len;
232
233            /// Output length for a given input length: padded up to a multiple
234            /// of eight, plus the eight-byte header.
235            pub const fn wrapped_len(plaintext_len: usize) -> usize {
236                plaintext_len.div_ceil(8) * 8 + OVERHEAD
237            }
238
239            /// Wrap data of any length from one byte upwards.
240            ///
241            /// The length is carried in the check value, so unwrapping recovers
242            /// it exactly. It is not hidden: an observer learns the length to
243            /// within eight bytes from the ciphertext size alone.
244            pub fn wrap(kek: &[u8], plaintext: &[u8], out: &mut [u8]) -> Result<()> {
245                ensure!(kek.len() == $key_len, InvalidLength, "key-wrap kek");
246                ensure!(
247                    !plaintext.is_empty(),
248                    InvalidLength,
249                    "key-wrap input is empty"
250                );
251                ensure!(
252                    plaintext.len() <= MAX_BLOCKS * 8,
253                    InvalidLength,
254                    "key-wrap input too large"
255                );
256                ensure!(
257                    out.len() == Self::wrapped_len(plaintext.len()),
258                    InvalidLength,
259                    "key-wrap output"
260                );
261
262                let cipher = <$cipher>::new(kek)?;
263                let mut a = [0u8; 8];
264                a[..4].copy_from_slice(&KWP_IV);
265                a[4..].copy_from_slice(&(plaintext.len() as u32).to_be_bytes());
266
267                let n = plaintext.len().div_ceil(8);
268                let mut r = [[0u8; 8]; MAX_BLOCKS];
269                for (i, chunk) in plaintext.chunks(8).enumerate() {
270                    r[i][..chunk.len()].copy_from_slice(chunk);
271                }
272
273                if n == 1 {
274                    // A single padded block is encrypted directly: the RFC 3394
275                    // loop needs at least two blocks to mix anything.
276                    let mut block = [0u8; 16];
277                    block[..8].copy_from_slice(&a);
278                    block[8..].copy_from_slice(&r[0]);
279                    cipher.encrypt_block(&mut block)?;
280                    out.copy_from_slice(&block);
281                    block.zeroize();
282                } else {
283                    wrap_blocks(&cipher, &mut a, &mut r[..n], n)?;
284                    out[..8].copy_from_slice(&a);
285                    for i in 0..n {
286                        out[8 + i * 8..16 + i * 8].copy_from_slice(&r[i]);
287                    }
288                }
289                for block in r.iter_mut() {
290                    block.zeroize();
291                }
292                Ok(())
293            }
294
295            /// Unwrap, returning the recovered length.
296            ///
297            /// `out` must be large enough for the padded data; the return value
298            /// says how much of it is real.
299            pub fn unwrap(kek: &[u8], ciphertext: &[u8], out: &mut [u8]) -> Result<usize> {
300                ensure!(kek.len() == $key_len, InvalidLength, "key-wrap kek");
301                ensure!(
302                    ciphertext.len() % 8 == 0 && ciphertext.len() >= 16,
303                    InvalidLength,
304                    "key-wrap ciphertext"
305                );
306                let n = ciphertext.len() / 8 - 1;
307                ensure!(
308                    n <= MAX_BLOCKS,
309                    InvalidLength,
310                    "key-wrap ciphertext too large"
311                );
312                ensure!(out.len() >= n * 8, InvalidLength, "key-wrap output");
313
314                let cipher = <$cipher>::new(kek)?;
315                let mut a = [0u8; 8];
316                let mut r = [[0u8; 8]; MAX_BLOCKS];
317
318                if n == 1 {
319                    let mut block = [0u8; 16];
320                    block.copy_from_slice(ciphertext);
321                    cipher.decrypt_block(&mut block)?;
322                    a.copy_from_slice(&block[..8]);
323                    r[0].copy_from_slice(&block[8..]);
324                    block.zeroize();
325                } else {
326                    a.copy_from_slice(&ciphertext[..8]);
327                    for i in 0..n {
328                        r[i].copy_from_slice(&ciphertext[8 + i * 8..16 + i * 8]);
329                    }
330                    unwrap_blocks(&cipher, &mut a, &mut r[..n], n)?;
331                }
332
333                // Check the fixed half, then the length, then the padding —
334                // accumulating into one decision so the failure mode does not
335                // say which part was wrong.
336                let mut ok = ic_core::ct::eq(&a[..4], &KWP_IV);
337                let declared = u32::from_be_bytes([a[4], a[5], a[6], a[7]]) as usize;
338                let padded = n * 8;
339                let plausible = declared <= padded && padded - declared < 8 && declared > 0;
340                ok = ok.and(ic_core::ct::Choice::from_u8(u8::from(plausible)));
341
342                if plausible {
343                    // Every padding byte must be zero.
344                    let mut zeros = 0u8;
345                    for i in declared..padded {
346                        zeros |= r[i / 8][i % 8];
347                    }
348                    ok = ok.and(ic_core::ct::is_zero(&[zeros]));
349                }
350
351                if !bool::from(ok) {
352                    for block in r.iter_mut() {
353                        block.zeroize();
354                    }
355                    return Err(ic_core::err!(AuthenticationFailed, $kwp_id));
356                }
357
358                for i in 0..n {
359                    out[i * 8..(i + 1) * 8].copy_from_slice(&r[i]);
360                }
361                for block in r.iter_mut() {
362                    block.zeroize();
363                }
364                Ok(declared)
365            }
366        }
367    };
368}
369
370key_wrap!(
371    Aes128Kw,
372    Aes128Kwp,
373    crate::Aes128,
374    16,
375    "aes-128-kw",
376    "aes-128-kwp"
377);
378key_wrap!(
379    Aes192Kw,
380    Aes192Kwp,
381    crate::Aes192,
382    24,
383    "aes-192-kw",
384    "aes-192-kwp"
385);
386key_wrap!(
387    Aes256Kw,
388    Aes256Kwp,
389    crate::Aes256,
390    32,
391    "aes-256-kw",
392    "aes-256-kwp"
393);
394
395impl SelfTest for Aes128Kw {
396    /// RFC 3394 section 4.1: the published vector, wrapping a 128-bit key with
397    /// a 128-bit KEK.
398    fn self_test() -> Result<()> {
399        let mut kek = [0u8; 16];
400        ic_core::codec::hex_decode(b"000102030405060708090a0b0c0d0e0f", &mut kek)?;
401        let mut key = [0u8; 16];
402        ic_core::codec::hex_decode(b"00112233445566778899aabbccddeeff", &mut key)?;
403        let mut want = [0u8; 24];
404        ic_core::codec::hex_decode(
405            b"1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5",
406            &mut want,
407        )?;
408
409        let mut got = [0u8; 24];
410        Aes128Kw::wrap(&kek, &key, &mut got)?;
411        ensure!(
412            ic_core::ct::verify(&want, &got),
413            SelfTestFailed,
414            "aes-128-kw"
415        );
416
417        let mut back = [0u8; 16];
418        Aes128Kw::unwrap(&kek, &want, &mut back)?;
419        ensure!(
420            ic_core::ct::verify(&key, &back),
421            SelfTestFailed,
422            "aes-128-kw"
423        );
424
425        let mut tampered = want;
426        tampered[0] ^= 1;
427        ensure!(
428            Aes128Kw::unwrap(&kek, &tampered, &mut back).is_err(),
429            SelfTestFailed,
430            "aes-128-kw"
431        );
432        Ok(())
433    }
434}
435
436impl SelfTest for Aes256Kw {
437    /// RFC 3394 section 4.6: a 256-bit key under a 256-bit KEK.
438    fn self_test() -> Result<()> {
439        let mut kek = [0u8; 32];
440        ic_core::codec::hex_decode(
441            b"000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
442            &mut kek,
443        )?;
444        let mut key = [0u8; 32];
445        ic_core::codec::hex_decode(
446            b"00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f",
447            &mut key,
448        )?;
449        let mut want = [0u8; 40];
450        ic_core::codec::hex_decode(
451            b"28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21",
452            &mut want,
453        )?;
454
455        let mut got = [0u8; 40];
456        Aes256Kw::wrap(&kek, &key, &mut got)?;
457        ensure!(
458            ic_core::ct::verify(&want, &got),
459            SelfTestFailed,
460            "aes-256-kw"
461        );
462
463        let mut back = [0u8; 32];
464        Aes256Kw::unwrap(&kek, &want, &mut back)?;
465        ensure!(
466            ic_core::ct::verify(&key, &back),
467            SelfTestFailed,
468            "aes-256-kw"
469        );
470        Ok(())
471    }
472}
473
474impl SelfTest for Aes192Kwp {
475    /// RFC 5649 section 6: the twenty-byte published vector, which exercises
476    /// padding across several blocks.
477    fn self_test() -> Result<()> {
478        let mut kek = [0u8; 24];
479        ic_core::codec::hex_decode(
480            b"5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8",
481            &mut kek,
482        )?;
483        let mut key = [0u8; 20];
484        ic_core::codec::hex_decode(b"c37b7e6492584340bed12207808941155068f738", &mut key)?;
485        let mut want = [0u8; 32];
486        ic_core::codec::hex_decode(
487            b"138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a",
488            &mut want,
489        )?;
490
491        let mut got = [0u8; 32];
492        Aes192Kwp::wrap(&kek, &key, &mut got)?;
493        ensure!(
494            ic_core::ct::verify(&want, &got),
495            SelfTestFailed,
496            "aes-192-kwp"
497        );
498
499        let mut back = [0u8; 24];
500        let len = Aes192Kwp::unwrap(&kek, &want, &mut back)?;
501        ensure!(len == key.len(), SelfTestFailed, "aes-192-kwp");
502        ensure!(
503            ic_core::ct::verify(&key, &back[..len]),
504            SelfTestFailed,
505            "aes-192-kwp"
506        );
507
508        let mut tampered = want;
509        tampered[3] ^= 1;
510        ensure!(
511            Aes192Kwp::unwrap(&kek, &tampered, &mut back).is_err(),
512            SelfTestFailed,
513            "aes-192-kwp"
514        );
515        Ok(())
516    }
517}
518
519impl SelfTest for Aes256Kwp {
520    /// No published RFC 5649 vector uses a 256-bit KEK, so this checks the
521    /// round trip and the rejection of tampering at that size. The padded
522    /// construction itself is vector-tested through [`Aes192Kwp`], which shares
523    /// every line of it but the cipher.
524    fn self_test() -> Result<()> {
525        let kek = [0x5au8; 32];
526        let secret = b"nineteen bytes here";
527        let mut wrapped = [0u8; 32];
528        Aes256Kwp::wrap(&kek, secret, &mut wrapped)?;
529
530        let mut out = [0u8; 24];
531        let len = Aes256Kwp::unwrap(&kek, &wrapped, &mut out)?;
532        ensure!(len == secret.len(), SelfTestFailed, "aes-256-kwp");
533        ensure!(
534            ic_core::ct::verify(secret, &out[..len]),
535            SelfTestFailed,
536            "aes-256-kwp"
537        );
538
539        let mut tampered = wrapped;
540        tampered[3] ^= 1;
541        ensure!(
542            Aes256Kwp::unwrap(&kek, &tampered, &mut out).is_err(),
543            SelfTestFailed,
544            "aes-256-kwp"
545        );
546        Ok(())
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use ic_core::codec::{hex, unhex};
554
555    /// RFC 3394's six published vectors, section 4.1 through 4.6.
556    ///
557    /// These are the anchor for everything else here. A wrong implementation
558    /// does not accidentally reproduce a published ciphertext, so matching even
559    /// one of them establishes that the construction is right; matching all six
560    /// across three key sizes and three data sizes leaves very little room.
561    #[test]
562    fn rfc_3394_vectors() {
563        struct Case {
564            kek: &'static str,
565            key: &'static str,
566            wrapped: &'static str,
567        }
568        let cases = [
569            // 4.1: 128-bit data, 128-bit KEK
570            Case {
571                kek: "000102030405060708090a0b0c0d0e0f",
572                key: "00112233445566778899aabbccddeeff",
573                wrapped: "1fa68b0a8112b447aef34bd8fb5a7b829d3e862371d2cfe5",
574            },
575            // 4.2: 128-bit data, 192-bit KEK
576            Case {
577                kek: "000102030405060708090a0b0c0d0e0f1011121314151617",
578                key: "00112233445566778899aabbccddeeff",
579                wrapped: "96778b25ae6ca435f92b5b97c050aed2468ab8a17ad84e5d",
580            },
581            // 4.3: 128-bit data, 256-bit KEK
582            Case {
583                kek: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
584                key: "00112233445566778899aabbccddeeff",
585                wrapped: "64e8c3f9ce0f5ba263e9777905818a2a93c8191e7d6e8ae7",
586            },
587            // 4.4: 192-bit data, 192-bit KEK
588            Case {
589                kek: "000102030405060708090a0b0c0d0e0f1011121314151617",
590                key: "00112233445566778899aabbccddeeff0001020304050607",
591                wrapped: "031d33264e15d33268f24ec260743edce1c6c7ddee725a936ba814915c6762d2",
592            },
593            // 4.5: 192-bit data, 256-bit KEK
594            Case {
595                kek: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
596                key: "00112233445566778899aabbccddeeff0001020304050607",
597                wrapped: "a8f9bc1612c68b3ff6e6f4fbe30e71e4769c8b80a32cb8958cd5d17d6b254da1",
598            },
599            // 4.6: 256-bit data, 256-bit KEK
600            Case {
601                kek: "000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f",
602                key: "00112233445566778899aabbccddeeff000102030405060708090a0b0c0d0e0f",
603                wrapped: "28c9f404c4b810f4cbccb35cfb87f8263f5786e2d80ed326cbc7f0e71a99f43bfb988b9b7a02dd21",
604            },
605        ];
606
607        for (index, case) in cases.iter().enumerate() {
608            let kek = unhex(case.kek).unwrap();
609            let key = unhex(case.key).unwrap();
610            let want = unhex(case.wrapped).unwrap();
611
612            let mut got = vec![0u8; key.len() + OVERHEAD];
613            match kek.len() {
614                16 => Aes128Kw::wrap(&kek, &key, &mut got).unwrap(),
615                24 => Aes192Kw::wrap(&kek, &key, &mut got).unwrap(),
616                _ => Aes256Kw::wrap(&kek, &key, &mut got).unwrap(),
617            }
618            assert_eq!(hex(&got), case.wrapped, "RFC 3394 case 4.{}", index + 1);
619
620            let mut back = vec![0u8; key.len()];
621            match kek.len() {
622                16 => Aes128Kw::unwrap(&kek, &want, &mut back).unwrap(),
623                24 => Aes192Kw::unwrap(&kek, &want, &mut back).unwrap(),
624                _ => Aes256Kw::unwrap(&kek, &want, &mut back).unwrap(),
625            }
626            assert_eq!(hex(&back), case.key, "RFC 3394 unwrap 4.{}", index + 1);
627        }
628    }
629
630    /// RFC 5649 section 6's two published vectors, both under a 192-bit KEK.
631    ///
632    /// These cover the padded construction: the first needs padding across
633    /// several blocks, the second is short enough to take the single-block
634    /// path, which is a separate branch entirely.
635    #[test]
636    fn rfc_5649_vectors() {
637        let kek = unhex("5840df6e29b02af1ab493b705bf16ea1ae8338f4dcc176a8").unwrap();
638
639        let key = unhex("c37b7e6492584340bed12207808941155068f738").unwrap();
640        let mut wrapped = vec![0u8; Aes192Kwp::wrapped_len(key.len())];
641        Aes192Kwp::wrap(&kek, &key, &mut wrapped).unwrap();
642        assert_eq!(
643            hex(&wrapped),
644            "138bdeaa9b8fa7fc61f97742e72248ee5ae6ae5360d1ae6a5f54f373fa543b6a",
645            "RFC 5649 twenty-byte vector"
646        );
647        let mut back = vec![0u8; wrapped.len() - 8];
648        let len = Aes192Kwp::unwrap(&kek, &wrapped, &mut back).unwrap();
649        assert_eq!(hex(&back[..len]), hex(&key));
650
651        let key = unhex("466f7250617369").unwrap();
652        let mut wrapped = vec![0u8; Aes192Kwp::wrapped_len(key.len())];
653        Aes192Kwp::wrap(&kek, &key, &mut wrapped).unwrap();
654        assert_eq!(
655            hex(&wrapped),
656            "afbeb0f07dfbf5419200f2ccb50bb24f",
657            "RFC 5649 seven-byte vector, the single-block path"
658        );
659        let mut back = vec![0u8; wrapped.len() - 8];
660        let len = Aes192Kwp::unwrap(&kek, &wrapped, &mut back).unwrap();
661        assert_eq!(hex(&back[..len]), hex(&key));
662    }
663
664    #[test]
665    fn wrapping_round_trips_at_every_supported_size() {
666        let kek = [0x11u8; 32];
667        for blocks in 2..=16usize {
668            let plaintext: Vec<u8> = (0..blocks * 8).map(|i| i as u8).collect();
669            let mut wrapped = vec![0u8; plaintext.len() + OVERHEAD];
670            Aes256Kw::wrap(&kek, &plaintext, &mut wrapped).unwrap();
671            assert_ne!(&wrapped[8..], &plaintext[..], "the data must be encrypted");
672
673            let mut back = vec![0u8; plaintext.len()];
674            Aes256Kw::unwrap(&kek, &wrapped, &mut back).unwrap();
675            assert_eq!(back, plaintext, "{blocks} blocks");
676        }
677    }
678
679    /// Every bit of the ciphertext is authenticated by the check value.
680    #[test]
681    fn tampering_is_rejected() {
682        let kek = [0x22u8; 32];
683        let plaintext = [0x33u8; 32];
684        let mut wrapped = [0u8; 40];
685        Aes256Kw::wrap(&kek, &plaintext, &mut wrapped).unwrap();
686
687        let mut back = [0u8; 32];
688        for byte in 0..wrapped.len() {
689            let mut bad = wrapped;
690            bad[byte] ^= 1;
691            assert!(
692                Aes256Kw::unwrap(&kek, &bad, &mut back).is_err(),
693                "a flip in byte {byte} was accepted"
694            );
695        }
696        // And the wrong KEK.
697        assert!(Aes256Kw::unwrap(&[0x23u8; 32], &wrapped, &mut back).is_err());
698    }
699
700    /// Determinism is the point: no nonce, same output every time.
701    #[test]
702    fn wrapping_is_deterministic() {
703        let kek = [0x44u8; 32];
704        let plaintext = [0x55u8; 24];
705        let mut a = [0u8; 32];
706        let mut b = [0u8; 32];
707        Aes256Kw::wrap(&kek, &plaintext, &mut a).unwrap();
708        Aes256Kw::wrap(&kek, &plaintext, &mut b).unwrap();
709        assert_eq!(a, b);
710    }
711
712    /// The six passes exist so that every output block depends on every input
713    /// block. A one-bit change anywhere must scramble the whole wrap.
714    #[test]
715    fn every_output_block_depends_on_every_input_block() {
716        let kek = [0x66u8; 32];
717        let base = [0u8; 64];
718        let mut reference = [0u8; 72];
719        Aes256Kw::wrap(&kek, &base, &mut reference).unwrap();
720
721        for index in [0usize, 8, 32, 63] {
722            let mut changed = base;
723            changed[index] ^= 1;
724            let mut wrapped = [0u8; 72];
725            Aes256Kw::wrap(&kek, &changed, &mut wrapped).unwrap();
726
727            let same = reference
728                .chunks(8)
729                .zip(wrapped.chunks(8))
730                .filter(|(a, b)| a == b)
731                .count();
732            assert_eq!(
733                same, 0,
734                "changing input byte {index} left {same} output blocks unchanged"
735            );
736        }
737    }
738
739    #[test]
740    fn padded_wrapping_round_trips_at_every_length() {
741        let kek = [0x77u8; 32];
742        for len in 1..=64usize {
743            let plaintext: Vec<u8> = (0..len).map(|i| (i * 7) as u8).collect();
744            let mut wrapped = vec![0u8; Aes256Kwp::wrapped_len(len)];
745            Aes256Kwp::wrap(&kek, &plaintext, &mut wrapped).unwrap();
746            assert_eq!(wrapped.len(), len.div_ceil(8) * 8 + 8);
747
748            let mut back = vec![0u8; wrapped.len() - 8];
749            let got = Aes256Kwp::unwrap(&kek, &wrapped, &mut back).unwrap();
750            assert_eq!(got, len, "recovered length at {len}");
751            assert_eq!(&back[..got], &plaintext[..], "round trip at {len}");
752        }
753    }
754
755    /// The single-block path is a different code path in RFC 5649, so it gets
756    /// its own check.
757    #[test]
758    fn the_single_block_padded_path_works() {
759        let kek = [0x88u8; 32];
760        for len in 1..=8usize {
761            let plaintext = vec![0xabu8; len];
762            let mut wrapped = vec![0u8; 16];
763            Aes256Kwp::wrap(&kek, &plaintext, &mut wrapped).unwrap();
764            assert_eq!(wrapped.len(), 16, "one block plus the header");
765
766            let mut back = [0u8; 8];
767            let got = Aes256Kwp::unwrap(&kek, &wrapped, &mut back).unwrap();
768            assert_eq!(got, len);
769            assert_eq!(&back[..got], &plaintext[..]);
770        }
771    }
772
773    #[test]
774    fn padded_wrapping_rejects_tampering() {
775        let kek = [0x99u8; 32];
776        let plaintext = b"a secret of awkward length";
777        let mut wrapped = vec![0u8; Aes256Kwp::wrapped_len(plaintext.len())];
778        Aes256Kwp::wrap(&kek, plaintext, &mut wrapped).unwrap();
779
780        let mut back = vec![0u8; wrapped.len() - 8];
781        for byte in 0..wrapped.len() {
782            let mut bad = wrapped.clone();
783            bad[byte] ^= 1;
784            assert!(
785                Aes256Kwp::unwrap(&kek, &bad, &mut back).is_err(),
786                "a flip in byte {byte} was accepted"
787            );
788        }
789    }
790
791    #[test]
792    fn lengths_are_validated() {
793        let kek = [0u8; 32];
794        let mut out = [0u8; 64];
795
796        // Not a whole number of blocks.
797        assert!(Aes256Kw::wrap(&kek, &[0u8; 20], &mut out[..28]).is_err());
798        // A single block: RFC 3394 needs two.
799        assert!(Aes256Kw::wrap(&kek, &[0u8; 8], &mut out[..16]).is_err());
800        // Empty.
801        assert!(Aes256Kw::wrap(&kek, &[], &mut out[..8]).is_err());
802        assert!(Aes256Kwp::wrap(&kek, &[], &mut out[..8]).is_err());
803        // Wrong KEK size.
804        assert!(Aes256Kw::wrap(&[0u8; 16], &[0u8; 16], &mut out[..24]).is_err());
805        // Wrong output size.
806        assert!(Aes256Kw::wrap(&kek, &[0u8; 16], &mut out[..23]).is_err());
807        // Ciphertext too short to contain anything.
808        assert!(Aes256Kw::unwrap(&kek, &[0u8; 16], &mut out[..8]).is_err());
809    }
810
811    #[test]
812    fn all_self_tests_pass() {
813        Aes128Kw::self_test().unwrap();
814        Aes256Kw::self_test().unwrap();
815        Aes192Kwp::self_test().unwrap();
816        Aes256Kwp::self_test().unwrap();
817    }
818}