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