securedrop_protocol_minimal/
message.rs1use crate::primitives::provider::hpke_rs::{
24 ChaCha20Poly1305, DhKem25519, HkdfSha256, Hpke, HpkeLibcrux, HpkePrivateKey, HpkePublicKey,
25 Mode,
26};
27use crate::primitives::provider::kem::MlKem768;
28use crate::primitives::provider::traits::OwnedKem as Kem;
29use alloc::string::String;
30use alloc::vec::Vec;
31use anyhow::Error;
32use rand_core::{CryptoRng, RngCore};
33use serde::de::Error as _;
34
35use crate::primitives::dh_akem::{
36 DH_AKEM_ENCAPS_SECRET_LEN, deterministic_keygen as dhakem_derand,
37};
38use crate::primitives::dh_akem::{DhAkemPrivateKey, DhAkemPublicKey, generate_dh_akem_keypair};
39use crate::primitives::mlkem::{
40 MLKEM768PrivateKey, MLKEM768PublicKey, deterministic_keygen as mlkem_derand,
41 generate_mlkem768_keypair,
42};
43
44use crate::primitives::mlkem::LEN_MLKEM_SHAREDSECRET_ENCAPS;
45
46const PSK_ID: &[u8] = b"SD-pskAPKE";
49
50const LEN_MLKEM_ENCAPS_RAND: usize = 32;
52
53#[derive(Debug, Clone)]
58pub struct MessagePublicKey {
59 pub(crate) dhakem: DhAkemPublicKey, pub(crate) mlkem: MLKEM768PublicKey, }
62
63pub struct MessagePrivateKey {
68 pub(crate) dhakem: DhAkemPrivateKey, pub(crate) mlkem: MLKEM768PrivateKey, }
71
72pub struct MessageKeyPair {
74 sk: MessagePrivateKey,
75 pk: MessagePublicKey,
76}
77
78impl MessageKeyPair {
79 pub(crate) fn new(sk: MessagePrivateKey, pk: MessagePublicKey) -> Self {
80 Self { sk, pk }
81 }
82
83 pub fn public_key(&self) -> &MessagePublicKey {
85 &self.pk
86 }
87
88 pub fn private_key(&self) -> &MessagePrivateKey {
90 &self.sk
91 }
92}
93
94impl MessagePublicKey {
95 pub fn as_bytes(&self) -> Vec<u8> {
97 let mut out = Vec::new();
98 out.extend_from_slice(self.dhakem.as_bytes());
99 out.extend_from_slice(self.mlkem.as_bytes());
100 out
101 }
102
103 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
109 use crate::primitives::dh_akem::DH_AKEM_PUBLIC_KEY_LEN;
110 use crate::primitives::mlkem::MLKEM768_PUBLIC_KEY_LEN;
111
112 if bytes.len() != DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN {
113 return Err(anyhow::anyhow!(
114 "Invalid MessagePublicKey length: expected {}, got {}",
115 DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN,
116 bytes.len()
117 ));
118 }
119
120 let dhakem_bytes: [u8; DH_AKEM_PUBLIC_KEY_LEN] = bytes[..DH_AKEM_PUBLIC_KEY_LEN]
121 .try_into()
122 .expect("checked length");
123 let mlkem_bytes: [u8; MLKEM768_PUBLIC_KEY_LEN] = bytes[DH_AKEM_PUBLIC_KEY_LEN..]
124 .try_into()
125 .expect("checked length");
126
127 Ok(Self {
128 dhakem: DhAkemPublicKey::from_bytes(dhakem_bytes),
129 mlkem: MLKEM768PublicKey::from_bytes(mlkem_bytes),
130 })
131 }
132}
133
134#[cfg_attr(hax, hax_lib::exclude)]
135impl serde::Serialize for MessagePublicKey {
136 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
137 ser.serialize_str(&hex::encode(self.as_bytes()))
138 }
139}
140
141#[cfg_attr(hax, hax_lib::exclude)]
142impl<'de> serde::Deserialize<'de> for MessagePublicKey {
143 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
144 let s = String::deserialize(de)?;
145 let bytes = hex::decode(s.trim()).map_err(D::Error::custom)?;
146 Self::from_bytes(&bytes).map_err(D::Error::custom)
147 }
148}
149
150#[derive(Debug, Clone)]
152pub struct MessageCiphertext {
153 pub(crate) c1: [u8; DH_AKEM_ENCAPS_SECRET_LEN],
155 pub(crate) cp: Vec<u8>,
157 pub(crate) c2: [u8; LEN_MLKEM_SHAREDSECRET_ENCAPS],
159}
160
161impl MessageCiphertext {
162 pub fn len(&self) -> usize {
164 self.c1.len() + self.cp.len() + self.c2.len()
165 }
166
167 pub fn as_bytes(&self) -> Vec<u8> {
169 let mut out = Vec::with_capacity(self.len());
170 out.extend_from_slice(&self.c1);
171 out.extend_from_slice(&self.c2);
172 out.extend_from_slice(&self.cp);
173 out
174 }
175
176 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
182 const FIXED: usize = DH_AKEM_ENCAPS_SECRET_LEN + LEN_MLKEM_SHAREDSECRET_ENCAPS;
183 if bytes.len() < FIXED {
184 return Err(anyhow::anyhow!(
185 "MessageCiphertext too short: expected at least {}, got {}",
186 FIXED,
187 bytes.len()
188 ));
189 }
190
191 let (c1, rest) = bytes.split_at(DH_AKEM_ENCAPS_SECRET_LEN);
192 let (c2, cp) = rest.split_at(LEN_MLKEM_SHAREDSECRET_ENCAPS);
193 Ok(Self {
194 c1: c1.try_into().expect("checked length"),
195 cp: cp.to_vec(),
196 c2: c2.try_into().expect("checked length"),
197 })
198 }
199}
200
201#[cfg_attr(hax, hax_lib::exclude)]
202impl serde::Serialize for MessageCiphertext {
203 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
204 ser.serialize_str(&hex::encode(self.as_bytes()))
205 }
206}
207
208#[cfg_attr(hax, hax_lib::exclude)]
209impl<'de> serde::Deserialize<'de> for MessageCiphertext {
210 fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
211 let s = String::deserialize(de)?;
212 let bytes = hex::decode(s.trim()).map_err(D::Error::custom)?;
213 Self::from_bytes(&bytes).map_err(D::Error::custom)
214 }
215}
216
217pub fn keygen<R: RngCore + CryptoRng>(rng: &mut R) -> Result<MessageKeyPair, Error> {
223 let (sk1, pk1) = generate_dh_akem_keypair(rng)?; let (sk2, pk2) = generate_mlkem768_keypair(rng)?; Ok(MessageKeyPair {
226 sk: MessagePrivateKey {
227 dhakem: sk1,
228 mlkem: sk2,
229 },
230 pk: MessagePublicKey {
231 dhakem: pk1,
232 mlkem: pk2,
233 },
234 })
235}
236
237pub(crate) fn deterministic_keygen(
241 dh_seed: [u8; 32],
242 mlkem_seed: [u8; 64],
243) -> Result<MessageKeyPair, Error> {
244 let (sk1, pk1) = dhakem_derand(dh_seed)?;
245 let (sk2, pk2) = mlkem_derand(mlkem_seed)?;
246 Ok(MessageKeyPair {
247 sk: MessagePrivateKey {
248 dhakem: sk1,
249 mlkem: sk2,
250 },
251 pk: MessagePublicKey {
252 dhakem: pk1,
253 mlkem: pk2,
254 },
255 })
256}
257
258pub fn auth_enc<R: RngCore + CryptoRng>(
269 rng: &mut R,
270 sk: &MessagePrivateKey, pk: &MessagePublicKey, m: &[u8],
273 ad: &[u8],
274 info: &[u8],
275) -> Result<MessageCiphertext, Error> {
276 let mut hpke =
277 Hpke::<HpkeLibcrux>::new(Mode::AuthPsk, DhKem25519, HkdfSha256, ChaCha20Poly1305);
278
279 let mut randomness = [0u8; LEN_MLKEM_ENCAPS_RAND];
280 rng.fill_bytes(&mut randomness);
281
282 let (k2, c2) = MlKem768::encaps(pk.mlkem.as_bytes(), &randomness)
284 .map_err(|e| anyhow::anyhow!("ML-KEM encapsulation failed: {:?}", e))?;
285
286 let pkr1: HpkePublicKey = pk.dhakem.clone().into();
288 let sks1: HpkePrivateKey = sk.dhakem.clone().into();
289
290 let mut full_info = Vec::new();
291 full_info.extend_from_slice(&c2);
292 full_info.extend_from_slice(info);
293
294 let (c1_vec, cp) = hpke
295 .seal(
296 &pkr1,
297 &full_info,
298 ad,
299 m,
300 Some(&k2),
301 Some(PSK_ID),
302 Some(&sks1),
303 )
304 .map_err(|e| anyhow::anyhow!("SD-APKE AuthEnc failed: {:?}", e))?;
305
306 let c1: [u8; DH_AKEM_ENCAPS_SECRET_LEN] = c1_vec
308 .as_slice()
309 .try_into()
310 .expect("DHKEM(X25519) encapsulation output has unexpected length");
311
312 Ok(MessageCiphertext { c1, cp, c2 })
313}
314
315pub fn auth_dec(
326 sk: &MessagePrivateKey, pk: &MessagePublicKey, ct: &MessageCiphertext,
329 ad: &[u8],
330 info: &[u8],
331) -> Result<Vec<u8>, Error> {
332 let hpke = Hpke::<HpkeLibcrux>::new(Mode::AuthPsk, DhKem25519, HkdfSha256, ChaCha20Poly1305);
333
334 let k2 = MlKem768::decaps(&ct.c2, sk.mlkem.as_bytes())
336 .map_err(|e| anyhow::anyhow!("ML-KEM decapsulation failed: {:?}", e))?;
337
338 let skr1: HpkePrivateKey = sk.dhakem.clone().into();
340 let pks1: HpkePublicKey = pk.dhakem.clone().into();
341
342 let mut full_info = Vec::new();
343 full_info.extend_from_slice(&ct.c2);
344 full_info.extend_from_slice(info);
345
346 hpke.open(
347 &ct.c1,
348 &skr1,
349 &full_info,
350 ad,
351 &ct.cp,
352 Some(&k2),
353 Some(PSK_ID),
354 Some(&pks1),
355 )
356 .map_err(|e| anyhow::anyhow!("SD-APKE AuthDec failed: {:?}", e))
357}
358
359#[cfg(test)]
360mod tests {
361 use super::*;
362 use proptest::prelude::*;
363 use rand_chacha::ChaCha20Rng;
364 use rand_core::SeedableRng;
365
366 fn get_rng() -> ChaCha20Rng {
367 let mut seed = [0u8; 32];
368 getrandom::fill(&mut seed).expect("OS random source failed");
369 ChaCha20Rng::from_seed(seed)
370 }
371
372 proptest! {
373 #[test]
374 fn test_auth_enc_dec_roundtrip(
375 m in proptest::collection::vec(any::<u8>(), 0..200),
376 ad in proptest::collection::vec(any::<u8>(), 0..64),
377 info in proptest::collection::vec(any::<u8>(), 0..64),
378 ) {
379 let mut rng = get_rng();
380 let sender_kp = keygen(&mut rng).expect("KGen failed");
381 let recipient_kp = keygen(&mut rng).expect("KGen failed");
382
383 let ct = auth_enc(
384 &mut rng,
385 sender_kp.private_key(),
386 recipient_kp.public_key(),
387 &m, &ad, &info,
388 ).expect("AuthEnc failed");
389
390 let decrypted = auth_dec(
391 recipient_kp.private_key(),
392 sender_kp.public_key(),
393 &ct, &ad, &info,
394 ).expect("AuthDec failed");
395
396 prop_assert_eq!(m, decrypted);
397 }
398 }
399
400 #[test]
401 fn test_auth_dec_wrong_recipient_fails() {
402 let mut rng = get_rng();
403 let sender_kp = keygen(&mut rng).expect("KGen failed");
404 let recipient_kp = keygen(&mut rng).expect("KGen failed");
405 let wrong_kp = keygen(&mut rng).expect("KGen failed");
406
407 let ct = auth_enc(
408 &mut rng,
409 sender_kp.private_key(),
410 recipient_kp.public_key(),
411 b"secret",
412 b"ad",
413 b"info",
414 )
415 .expect("AuthEnc failed");
416
417 assert!(
418 auth_dec(
419 wrong_kp.private_key(),
420 sender_kp.public_key(),
421 &ct,
422 b"ad",
423 b"info",
424 )
425 .is_err()
426 );
427 }
428}