1use crate::constants::*;
33use crate::error::{Error, Result};
34use crate::hybrid_kem::{self, KemCiphertext};
35use crate::keys::{KeyPair, PublicKeyBundle};
36use crate::wire::{read_header, take, write_header};
37use aes_gcm::aead::{Aead, Payload};
38use aes_gcm::{Aes256Gcm, KeyInit};
39use alloc::boxed::Box;
40use alloc::vec::Vec;
41use sha3::{Digest, Sha3_256};
42use subtle::ConstantTimeEq;
43use zeroize::Zeroizing;
44
45fn cek_commitment(cek: &[u8; CEK_LEN]) -> [u8; CEK_COMMIT_LEN] {
50 let mut hasher = Sha3_256::new();
51 hasher.update(MULTI_CEK_COMMIT_LABEL);
52 hasher.update(cek);
53 hasher.finalize().into()
54}
55
56#[derive(Clone, Debug, PartialEq, Eq)]
58struct Wrap {
59 epk_x25519: [u8; X25519_PK_LEN],
60 ct_mlkem: Box<[u8; MLKEM1024_CT_LEN]>,
61 wrap_nonce: [u8; NONCE_LEN],
62 wrapped_cek: [u8; CEK_LEN + TAG_LEN],
63}
64
65#[derive(Clone, Debug, PartialEq, Eq)]
74pub struct MultiRecipientEnvelope {
75 cek_commitment: [u8; CEK_COMMIT_LEN],
78 wraps: Vec<Wrap>,
79 payload_nonce: [u8; NONCE_LEN],
80 payload_ct: Vec<u8>,
81}
82
83impl MultiRecipientEnvelope {
84 pub fn recipient_count(&self) -> usize {
86 self.wraps.len()
87 }
88
89 fn write_prefix(&self, out: &mut Vec<u8>) {
92 debug_assert!(self.wraps.len() <= MAX_RECIPIENTS);
93 write_header(out, MAGIC_MULTI);
94 out.extend_from_slice(&(self.wraps.len() as u16).to_be_bytes());
95 out.extend_from_slice(&self.cek_commitment);
96 for wrap in &self.wraps {
97 out.extend_from_slice(&wrap.epk_x25519);
98 out.extend_from_slice(wrap.ct_mlkem.as_ref());
99 out.extend_from_slice(&wrap.wrap_nonce);
100 out.extend_from_slice(&wrap.wrapped_cek);
101 }
102 out.extend_from_slice(&self.payload_nonce);
103 }
104
105 pub fn to_bytes(&self) -> Vec<u8> {
107 let mut out = Vec::with_capacity(
108 HEADER_LEN
109 + 2
110 + CEK_COMMIT_LEN
111 + self.wraps.len() * WRAP_LEN
112 + NONCE_LEN
113 + self.payload_ct.len(),
114 );
115 self.write_prefix(&mut out);
116 out.extend_from_slice(&self.payload_ct);
117 out
118 }
119
120 pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
128 let mut rest = read_header(bytes, MAGIC_MULTI, Error::InvalidEnvelope)?;
129
130 let count_bytes: [u8; 2] = take(&mut rest, Error::InvalidEnvelope)?;
131 let count = u16::from_be_bytes(count_bytes) as usize;
132 if count == 0 {
133 return Err(Error::NoRecipients);
134 }
135 if count > MAX_RECIPIENTS {
136 return Err(Error::TooManyRecipients {
137 count,
138 max: MAX_RECIPIENTS,
139 });
140 }
141
142 let cek_commitment = take(&mut rest, Error::InvalidEnvelope)?;
143
144 let mut wraps = Vec::with_capacity(count);
145 for _ in 0..count {
146 let epk_x25519 = take(&mut rest, Error::InvalidEnvelope)?;
147 let ct_mlkem: [u8; MLKEM1024_CT_LEN] = take(&mut rest, Error::InvalidEnvelope)?;
148 let wrap_nonce = take(&mut rest, Error::InvalidEnvelope)?;
149 let wrapped_cek = take(&mut rest, Error::InvalidEnvelope)?;
150 wraps.push(Wrap {
151 epk_x25519,
152 ct_mlkem: Box::new(ct_mlkem),
153 wrap_nonce,
154 wrapped_cek,
155 });
156 }
157
158 let payload_nonce = take(&mut rest, Error::InvalidEnvelope)?;
159 if rest.len() < TAG_LEN {
160 return Err(Error::InvalidEnvelope);
161 }
162 Ok(Self {
163 cek_commitment,
164 wraps,
165 payload_nonce,
166 payload_ct: rest.to_vec(),
167 })
168 }
169}
170
171fn wrap_aad(count: usize) -> Vec<u8> {
173 let mut aad = Vec::with_capacity(HEADER_LEN + 2);
174 write_header(&mut aad, MAGIC_MULTI);
175 aad.extend_from_slice(&(count as u16).to_be_bytes());
176 aad
177}
178
179pub fn seal_multi(
187 plaintext: &[u8],
188 recipients: &[&PublicKeyBundle],
189) -> Result<MultiRecipientEnvelope> {
190 if recipients.is_empty() {
191 return Err(Error::NoRecipients);
192 }
193 if recipients.len() > MAX_RECIPIENTS {
194 return Err(Error::TooManyRecipients {
195 count: recipients.len(),
196 max: MAX_RECIPIENTS,
197 });
198 }
199 if plaintext.len() > MAX_PLAINTEXT_LEN {
200 return Err(Error::MessageTooLarge {
201 len: plaintext.len(),
202 max: MAX_PLAINTEXT_LEN,
203 });
204 }
205
206 let mut cek = Zeroizing::new([0u8; CEK_LEN]);
207 getrandom::fill(cek.as_mut()).map_err(|_| Error::RandomnessUnavailable)?;
208
209 let aad = wrap_aad(recipients.len());
210 let mut wraps = Vec::with_capacity(recipients.len());
211 for recipient in recipients {
212 let (kem_ct, ss) = hybrid_kem::encapsulate(recipient)?;
213 let cipher = Aes256Gcm::new((&*ss).into());
214 let mut wrap_nonce = [0u8; NONCE_LEN];
215 getrandom::fill(&mut wrap_nonce).map_err(|_| Error::RandomnessUnavailable)?;
216 let wrapped = cipher
219 .encrypt(
220 (&wrap_nonce).into(),
221 Payload {
222 msg: &*cek,
223 aad: &aad,
224 },
225 )
226 .expect("AES-GCM wrap of a 32-byte CEK is infallible");
227 let wrapped_cek: [u8; CEK_LEN + TAG_LEN] = wrapped
228 .try_into()
229 .expect("AES-256-GCM output is plaintext length + 16-byte tag");
230 wraps.push(Wrap {
231 epk_x25519: kem_ct.epk_x25519,
232 ct_mlkem: kem_ct.ct_mlkem,
233 wrap_nonce,
234 wrapped_cek,
235 });
236 }
237
238 let mut payload_nonce = [0u8; NONCE_LEN];
239 getrandom::fill(&mut payload_nonce).map_err(|_| Error::RandomnessUnavailable)?;
240
241 let mut envelope = MultiRecipientEnvelope {
242 cek_commitment: cek_commitment(&cek),
243 wraps,
244 payload_nonce,
245 payload_ct: Vec::new(),
246 };
247 let mut payload_aad = Vec::new();
248 envelope.write_prefix(&mut payload_aad);
249
250 let cipher = Aes256Gcm::new((&*cek).into());
251 envelope.payload_ct = cipher
252 .encrypt(
253 (&payload_nonce).into(),
254 Payload {
255 msg: plaintext,
256 aad: &payload_aad,
257 },
258 )
259 .map_err(|_| Error::MessageTooLarge {
260 len: plaintext.len(),
261 max: MAX_PLAINTEXT_LEN,
262 })?;
263 Ok(envelope)
264}
265
266pub fn open_multi(keypair: &KeyPair, envelope: &MultiRecipientEnvelope) -> Result<Vec<u8>> {
272 let aad = wrap_aad(envelope.wraps.len());
273 let mut payload_aad = Vec::new();
274 envelope.write_prefix(&mut payload_aad);
275
276 for wrap in &envelope.wraps {
277 let kem_ct = KemCiphertext {
278 epk_x25519: wrap.epk_x25519,
279 ct_mlkem: wrap.ct_mlkem.clone(),
280 };
281 let ss = hybrid_kem::decapsulate(keypair, &kem_ct);
282 let cipher = Aes256Gcm::new((&*ss).into());
283 let Ok(cek_vec) = cipher.decrypt(
284 (&wrap.wrap_nonce).into(),
285 Payload {
286 msg: &wrap.wrapped_cek,
287 aad: &aad,
288 },
289 ) else {
290 continue;
291 };
292
293 let cek_vec = Zeroizing::new(cek_vec);
294 let cek: Zeroizing<[u8; CEK_LEN]> = Zeroizing::new(
295 cek_vec
296 .as_slice()
297 .try_into()
298 .map_err(|_| Error::DecryptionFailed)?,
299 );
300
301 let commit_ok: bool = cek_commitment(&cek).ct_eq(&envelope.cek_commitment).into();
304 if !commit_ok {
305 return Err(Error::DecryptionFailed);
306 }
307
308 let payload_cipher = Aes256Gcm::new((&*cek).into());
309 return payload_cipher
310 .decrypt(
311 (&envelope.payload_nonce).into(),
312 Payload {
313 msg: &envelope.payload_ct,
314 aad: &payload_aad,
315 },
316 )
317 .map_err(|_| Error::DecryptionFailed);
318 }
319 Err(Error::DecryptionFailed)
320}