1use crate::constants::*;
4use crate::error::{Error, Result};
5use crate::hybrid_kem::{self, KemCiphertext};
6use crate::keys::{KeyPair, PublicKeyBundle};
7use crate::types::Envelope;
8use aes_gcm::aead::{Aead, Payload};
9use aes_gcm::{Aes256Gcm, KeyInit};
10use alloc::vec::Vec;
11
12pub fn seal(plaintext: &[u8], recipient: &PublicKeyBundle) -> Result<Envelope> {
25 if plaintext.len() > MAX_PLAINTEXT_LEN {
26 return Err(Error::MessageTooLarge {
27 len: plaintext.len(),
28 max: MAX_PLAINTEXT_LEN,
29 });
30 }
31
32 let (kem_ct, ss) = hybrid_kem::encapsulate(recipient)?;
33
34 let mut nonce = [0u8; NONCE_LEN];
35 getrandom::fill(&mut nonce).map_err(|_| Error::RandomnessUnavailable)?;
36
37 let mut envelope = Envelope {
39 epk_x25519: kem_ct.epk_x25519,
40 ct_mlkem: kem_ct.ct_mlkem,
41 nonce,
42 ciphertext: Vec::new(),
43 };
44 let mut aad = Vec::with_capacity(ENVELOPE_AAD_LEN);
45 envelope.write_aad(&mut aad);
46
47 let cipher = Aes256Gcm::new((&*ss).into());
48 envelope.ciphertext = cipher
49 .encrypt(
50 (&nonce).into(),
51 Payload {
52 msg: plaintext,
53 aad: &aad,
54 },
55 )
56 .map_err(|_| Error::MessageTooLarge {
57 len: plaintext.len(),
58 max: MAX_PLAINTEXT_LEN,
59 })?;
60
61 Ok(envelope)
62}
63
64pub fn open(keypair: &KeyPair, envelope: &Envelope) -> Result<Vec<u8>> {
72 let kem_ct = KemCiphertext {
73 epk_x25519: envelope.epk_x25519,
74 ct_mlkem: envelope.ct_mlkem.clone(),
75 };
76 let ss = hybrid_kem::decapsulate(keypair, &kem_ct);
77
78 let mut aad = Vec::with_capacity(ENVELOPE_AAD_LEN);
79 envelope.write_aad(&mut aad);
80
81 let cipher = Aes256Gcm::new((&*ss).into());
82 cipher
83 .decrypt(
84 (&envelope.nonce).into(),
85 Payload {
86 msg: &envelope.ciphertext,
87 aad: &aad,
88 },
89 )
90 .map_err(|_| Error::DecryptionFailed)
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn roundtrip() {
99 let kp = KeyPair::generate().unwrap();
100 let msg = b"attack at dawn";
101 let env = seal(msg, kp.public_keys()).unwrap();
102 assert_eq!(open(&kp, &env).unwrap(), msg);
103 }
104
105 #[test]
106 fn empty_plaintext_roundtrip() {
107 let kp = KeyPair::generate().unwrap();
108 let env = seal(b"", kp.public_keys()).unwrap();
109 assert_eq!(open(&kp, &env).unwrap(), b"");
110 }
111
112 #[test]
113 fn wrong_recipient_fails() {
114 let alice = KeyPair::generate().unwrap();
115 let mallory = KeyPair::generate().unwrap();
116 let env = seal(b"secret", alice.public_keys()).unwrap();
117 assert_eq!(open(&mallory, &env).unwrap_err(), Error::DecryptionFailed);
118 }
119
120 #[test]
121 fn oversized_plaintext_rejected() {
122 let kp = KeyPair::generate().unwrap();
123 let big = vec![0u8; MAX_PLAINTEXT_LEN + 1];
124 assert!(matches!(
125 seal(&big, kp.public_keys()).unwrap_err(),
126 Error::MessageTooLarge { .. }
127 ));
128 }
129}