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::vec::Vec;
29use anyhow::Error;
30use rand_core::{CryptoRng, RngCore};
31
32use crate::primitives::dh_akem::{
33 DH_AKEM_ENCAPS_SECRET_LEN, deterministic_keygen as dhakem_derand,
34};
35use crate::primitives::dh_akem::{DhAkemPrivateKey, DhAkemPublicKey, generate_dh_akem_keypair};
36use crate::primitives::mlkem::{
37 MLKEM768PrivateKey, MLKEM768PublicKey, deterministic_keygen as mlkem_derand,
38 generate_mlkem768_keypair,
39};
40
41use crate::primitives::mlkem::LEN_MLKEM_SHAREDSECRET_ENCAPS;
42
43const PSK_ID: &[u8] = b"SD-pskAPKE";
46
47const LEN_MLKEM_ENCAPS_RAND: usize = 32;
49
50#[derive(Debug, Clone)]
55pub struct MessagePublicKey {
56 pub(crate) dhakem: DhAkemPublicKey, pub(crate) mlkem: MLKEM768PublicKey, }
59
60pub struct MessagePrivateKey {
65 pub(crate) dhakem: DhAkemPrivateKey, pub(crate) mlkem: MLKEM768PrivateKey, }
68
69pub struct MessageKeyPair {
71 sk: MessagePrivateKey,
72 pk: MessagePublicKey,
73}
74
75impl MessageKeyPair {
76 pub fn public_key(&self) -> &MessagePublicKey {
78 &self.pk
79 }
80
81 pub fn private_key(&self) -> &MessagePrivateKey {
83 &self.sk
84 }
85}
86
87impl MessagePublicKey {
88 pub fn as_bytes(&self) -> Vec<u8> {
90 let mut out = Vec::new();
91 out.extend_from_slice(self.dhakem.as_bytes());
92 out.extend_from_slice(self.mlkem.as_bytes());
93 out
94 }
95
96 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
102 use crate::primitives::dh_akem::DH_AKEM_PUBLIC_KEY_LEN;
103 use crate::primitives::mlkem::MLKEM768_PUBLIC_KEY_LEN;
104
105 if bytes.len() != DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN {
106 return Err(anyhow::anyhow!(
107 "Invalid MessagePublicKey length: expected {}, got {}",
108 DH_AKEM_PUBLIC_KEY_LEN + MLKEM768_PUBLIC_KEY_LEN,
109 bytes.len()
110 ));
111 }
112
113 let dhakem_bytes: [u8; DH_AKEM_PUBLIC_KEY_LEN] = bytes[..DH_AKEM_PUBLIC_KEY_LEN]
114 .try_into()
115 .expect("checked length");
116 let mlkem_bytes: [u8; MLKEM768_PUBLIC_KEY_LEN] = bytes[DH_AKEM_PUBLIC_KEY_LEN..]
117 .try_into()
118 .expect("checked length");
119
120 Ok(Self {
121 dhakem: DhAkemPublicKey::from_bytes(dhakem_bytes),
122 mlkem: MLKEM768PublicKey::from_bytes(mlkem_bytes),
123 })
124 }
125}
126
127#[derive(Debug, Clone)]
129pub struct MessageCiphertext {
130 pub(crate) c1: [u8; DH_AKEM_ENCAPS_SECRET_LEN],
132 pub(crate) cp: Vec<u8>,
134 pub(crate) c2: [u8; LEN_MLKEM_SHAREDSECRET_ENCAPS],
136}
137
138impl MessageCiphertext {
139 pub fn len(&self) -> usize {
141 self.c1.len() + self.cp.len() + self.c2.len()
142 }
143}
144
145pub fn keygen<R: RngCore + CryptoRng>(rng: &mut R) -> Result<MessageKeyPair, Error> {
151 let (sk1, pk1) = generate_dh_akem_keypair(rng)?; let (sk2, pk2) = generate_mlkem768_keypair(rng)?; Ok(MessageKeyPair {
154 sk: MessagePrivateKey {
155 dhakem: sk1,
156 mlkem: sk2,
157 },
158 pk: MessagePublicKey {
159 dhakem: pk1,
160 mlkem: pk2,
161 },
162 })
163}
164
165pub(crate) fn deterministic_keygen(
169 dh_seed: [u8; 32],
170 mlkem_seed: [u8; 64],
171) -> Result<MessageKeyPair, Error> {
172 let (sk1, pk1) = dhakem_derand(dh_seed)?;
173 let (sk2, pk2) = mlkem_derand(mlkem_seed)?;
174 Ok(MessageKeyPair {
175 sk: MessagePrivateKey {
176 dhakem: sk1,
177 mlkem: sk2,
178 },
179 pk: MessagePublicKey {
180 dhakem: pk1,
181 mlkem: pk2,
182 },
183 })
184}
185
186pub fn auth_enc<R: RngCore + CryptoRng>(
197 rng: &mut R,
198 sk: &MessagePrivateKey, pk: &MessagePublicKey, m: &[u8],
201 ad: &[u8],
202 info: &[u8],
203) -> Result<MessageCiphertext, Error> {
204 let mut hpke = Hpke::<HpkeLibcrux>::new(Mode::AuthPsk, DhKem25519, HkdfSha256, Aes256Gcm);
205
206 let mut randomness = [0u8; LEN_MLKEM_ENCAPS_RAND];
207 rng.fill_bytes(&mut randomness);
208
209 let (k2, c2) = MlKem768::encaps(pk.mlkem.as_bytes(), &randomness)
211 .map_err(|e| anyhow::anyhow!("ML-KEM encapsulation failed: {:?}", e))?;
212
213 let pkr1: HpkePublicKey = pk.dhakem.clone().into();
215 let sks1: HpkePrivateKey = sk.dhakem.clone().into();
216
217 let mut full_info = Vec::new();
218 full_info.extend_from_slice(&c2);
219 full_info.extend_from_slice(info);
220
221 let (c1_vec, cp) = hpke
222 .seal(
223 &pkr1,
224 &full_info,
225 ad,
226 m,
227 Some(&k2),
228 Some(PSK_ID),
229 Some(&sks1),
230 )
231 .map_err(|e| anyhow::anyhow!("SD-APKE AuthEnc failed: {:?}", e))?;
232
233 let c1: [u8; DH_AKEM_ENCAPS_SECRET_LEN] = c1_vec
235 .try_into()
236 .expect("DHKEM(X25519) encapsulation output has unexpected length");
237
238 Ok(MessageCiphertext { c1, cp, c2 })
239}
240
241pub fn auth_dec(
252 sk: &MessagePrivateKey, pk: &MessagePublicKey, ct: &MessageCiphertext,
255 ad: &[u8],
256 info: &[u8],
257) -> Result<Vec<u8>, Error> {
258 let hpke = Hpke::<HpkeLibcrux>::new(Mode::AuthPsk, DhKem25519, HkdfSha256, Aes256Gcm);
259
260 let k2 = MlKem768::decaps(&ct.c2, sk.mlkem.as_bytes())
262 .map_err(|e| anyhow::anyhow!("ML-KEM decapsulation failed: {:?}", e))?;
263
264 let skr1: HpkePrivateKey = sk.dhakem.clone().into();
266 let pks1: HpkePublicKey = pk.dhakem.clone().into();
267
268 let mut full_info = Vec::new();
269 full_info.extend_from_slice(&ct.c2);
270 full_info.extend_from_slice(info);
271
272 hpke.open(
273 &ct.c1,
274 &skr1,
275 &full_info,
276 ad,
277 &ct.cp,
278 Some(&k2),
279 Some(PSK_ID),
280 Some(&pks1),
281 )
282 .map_err(|e| anyhow::anyhow!("SD-APKE AuthDec failed: {:?}", e))
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use proptest::prelude::*;
289 use rand_chacha::ChaCha20Rng;
290 use rand_core::SeedableRng;
291
292 fn get_rng() -> ChaCha20Rng {
293 let mut seed = [0u8; 32];
294 getrandom::fill(&mut seed).expect("OS random source failed");
295 ChaCha20Rng::from_seed(seed)
296 }
297
298 proptest! {
299 #[test]
300 fn test_auth_enc_dec_roundtrip(
301 m in proptest::collection::vec(any::<u8>(), 0..200),
302 ad in proptest::collection::vec(any::<u8>(), 0..64),
303 info in proptest::collection::vec(any::<u8>(), 0..64),
304 ) {
305 let mut rng = get_rng();
306 let sender_kp = keygen(&mut rng).expect("KGen failed");
307 let recipient_kp = keygen(&mut rng).expect("KGen failed");
308
309 let ct = auth_enc(
310 &mut rng,
311 sender_kp.private_key(),
312 recipient_kp.public_key(),
313 &m, &ad, &info,
314 ).expect("AuthEnc failed");
315
316 let decrypted = auth_dec(
317 recipient_kp.private_key(),
318 sender_kp.public_key(),
319 &ct, &ad, &info,
320 ).expect("AuthDec failed");
321
322 prop_assert_eq!(m, decrypted);
323 }
324 }
325
326 #[test]
327 fn test_auth_dec_wrong_recipient_fails() {
328 let mut rng = get_rng();
329 let sender_kp = keygen(&mut rng).expect("KGen failed");
330 let recipient_kp = keygen(&mut rng).expect("KGen failed");
331 let wrong_kp = keygen(&mut rng).expect("KGen failed");
332
333 let ct = auth_enc(
334 &mut rng,
335 sender_kp.private_key(),
336 recipient_kp.public_key(),
337 b"secret",
338 b"ad",
339 b"info",
340 )
341 .expect("AuthEnc failed");
342
343 assert!(
344 auth_dec(
345 wrong_kp.private_key(),
346 sender_kp.public_key(),
347 &ct,
348 b"ad",
349 b"info",
350 )
351 .is_err()
352 );
353 }
354}