1use crate::aead::{decrypt, encrypt};
12use crate::kdf::{derive_scoped_key, KeyNonce};
13use crate::{CryptoError, CryptoResult};
14
15pub const MAGIC_V1: &[u8; 4] = b"VD01";
17
18const ENVELOPE_HEADER_SIZE: usize = 4 + KeyNonce::SIZE;
20
21pub fn generate_key_nonce() -> KeyNonce {
23 KeyNonce::generate()
24}
25
26pub fn encrypt_with_envelope(
36 root_key: &[u8; 32],
37 nonce: &KeyNonce,
38 plaintext: &[u8],
39 aad: &[u8],
40) -> CryptoResult<Vec<u8>> {
41 let scope = format!("commit:{}", hex::encode(nonce.as_bytes()));
42 let derived_key = derive_scoped_key(root_key, &scope)?;
43
44 let encrypted = encrypt(&derived_key, plaintext, aad)?;
45
46 let mut envelope = Vec::with_capacity(ENVELOPE_HEADER_SIZE + encrypted.len());
47 envelope.extend_from_slice(MAGIC_V1);
48 envelope.extend_from_slice(nonce.as_bytes());
49 envelope.extend_from_slice(&encrypted);
50
51 Ok(envelope)
52}
53
54pub fn decrypt_envelope(
59 root_key: &[u8; 32],
60 blob: &[u8],
61 aad: &[u8],
62) -> CryptoResult<(Vec<u8>, KeyNonce)> {
63 if blob.len() <= ENVELOPE_HEADER_SIZE || !blob.starts_with(MAGIC_V1) {
64 return Err(CryptoError::Decryption(
65 "missing VD01 envelope header".into(),
66 ));
67 }
68
69 let nonce = KeyNonce::from_bytes(&blob[4..ENVELOPE_HEADER_SIZE])
70 .ok_or_else(|| CryptoError::Decryption("invalid envelope nonce length".into()))?;
71
72 let scope = format!("commit:{}", hex::encode(nonce.as_bytes()));
73 let derived_key = derive_scoped_key(root_key, &scope)?;
74
75 let plaintext = decrypt(&derived_key, &blob[ENVELOPE_HEADER_SIZE..], aad)?;
76
77 Ok((plaintext, nonce))
78}
79
80#[cfg(test)]
81mod tests {
82 use super::*;
83 use crate::aead::AAD_COMMIT;
84
85 const TEST_AAD: &[u8] = b"void:envelope:test";
86
87 #[test]
88 fn envelope_roundtrip() {
89 let root_key = [0x42u8; 32];
90 let nonce = generate_key_nonce();
91 let plaintext = b"hello, envelope!";
92
93 let envelope = encrypt_with_envelope(&root_key, &nonce, plaintext, TEST_AAD).unwrap();
94
95 assert!(envelope.starts_with(MAGIC_V1));
96 assert_eq!(&envelope[4..20], nonce.as_bytes());
97
98 let (decrypted, returned_nonce) = decrypt_envelope(&root_key, &envelope, TEST_AAD).unwrap();
99 assert_eq!(decrypted, plaintext);
100 assert_eq!(returned_nonce, nonce);
101 }
102
103 #[test]
104 fn wrong_key_fails() {
105 let root_key = [0x42u8; 32];
106 let wrong_key = [0x43u8; 32];
107 let nonce = generate_key_nonce();
108 let plaintext = b"secret data";
109
110 let envelope = encrypt_with_envelope(&root_key, &nonce, plaintext, TEST_AAD).unwrap();
111 let result = decrypt_envelope(&wrong_key, &envelope, TEST_AAD);
112 assert!(result.is_err());
113 }
114
115 #[test]
116 fn envelope_with_aad_commit() {
117 let root_key = [0x42u8; 32];
118 let nonce = generate_key_nonce();
119 let plaintext = b"commit data with proper AAD";
120
121 let envelope = encrypt_with_envelope(&root_key, &nonce, plaintext, AAD_COMMIT).unwrap();
122
123 let (decrypted, _) = decrypt_envelope(&root_key, &envelope, AAD_COMMIT).unwrap();
124 assert_eq!(decrypted, plaintext);
125
126 let result = decrypt_envelope(&root_key, &envelope, b"wrong:aad");
127 assert!(result.is_err());
128 }
129
130 #[test]
131 fn generate_key_nonce_unique() {
132 let nonce1 = generate_key_nonce();
133 let nonce2 = generate_key_nonce();
134 assert_ne!(nonce1, nonce2);
135 }
136}