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