1use rand::RngCore;
9
10use crate::aead;
11use crate::Error;
12
13const NONCE_LEN: usize = 12;
14const TAG_LEN: usize = 16;
15
16pub fn wrap(kek: &[u8; 32], plaintext: &[u8]) -> Vec<u8> {
20 let mut nonce = [0u8; NONCE_LEN];
21 rand::thread_rng().fill_bytes(&mut nonce);
22 let ct = aead::seal(kek, &nonce, &[], plaintext)
23 .expect("AES-256-GCM seal with valid key never fails");
24 let mut out = Vec::with_capacity(NONCE_LEN + ct.len());
25 out.extend_from_slice(&nonce);
26 out.extend_from_slice(&ct);
27 out
28}
29
30pub fn unwrap(kek: &[u8; 32], wrapped: &[u8]) -> Result<Vec<u8>, Error> {
33 if wrapped.len() < NONCE_LEN + TAG_LEN {
34 return Err(Error::InvalidLength {
35 expected: NONCE_LEN + TAG_LEN,
36 got: wrapped.len(),
37 });
38 }
39 let (nonce_bytes, ct) = wrapped.split_at(NONCE_LEN);
40 let nonce: [u8; NONCE_LEN] = nonce_bytes
41 .try_into()
42 .expect("split_at(NONCE_LEN) yields NONCE_LEN bytes");
43 aead::open(kek, &nonce, &[], ct)
44}
45
46#[cfg(test)]
47mod tests {
48 use super::*;
49
50 fn kek() -> [u8; 32] {
51 [11u8; 32]
52 }
53
54 #[test]
55 fn roundtrip() {
56 let k = kek();
57 let pt = b"identity seed material";
58 let w = wrap(&k, pt);
59 let unwrapped = unwrap(&k, &w).unwrap();
60 assert_eq!(unwrapped, pt);
61 }
62
63 #[test]
64 fn nonce_is_random_each_call() {
65 let k = kek();
66 let a = wrap(&k, b"same plaintext");
67 let b = wrap(&k, b"same plaintext");
68 assert_ne!(a, b, "random nonce should produce distinct ciphertexts");
69 }
70
71 #[test]
72 fn wrong_kek_rejected() {
73 let pt = b"secret";
74 let w = wrap(&[1u8; 32], pt);
75 assert!(matches!(unwrap(&[2u8; 32], &w).unwrap_err(), Error::Aead));
76 }
77
78 #[test]
79 fn truncated_wrap_rejected() {
80 let short = vec![0u8; NONCE_LEN + TAG_LEN - 1];
81 assert!(matches!(
82 unwrap(&kek(), &short).unwrap_err(),
83 Error::InvalidLength { .. }
84 ));
85 }
86
87 #[test]
88 fn tampered_ciphertext_rejected() {
89 let k = kek();
90 let mut w = wrap(&k, b"secret");
91 let last = w.len() - 1;
92 w[last] ^= 0x01;
93 assert!(matches!(unwrap(&k, &w).unwrap_err(), Error::Aead));
94 }
95
96 #[test]
97 fn empty_plaintext_roundtrips() {
98 let k = kek();
99 let w = wrap(&k, b"");
100 let pt = unwrap(&k, &w).unwrap();
101 assert!(pt.is_empty());
102 }
103}