1use crate::message::MessagePublicKey;
2use crate::metadata;
3use crate::primitives::provider::constants::{LEN_KMID, LEN_MESSAGE_ID};
4use crate::primitives::x25519::{
5 DH_PUBLIC_KEY_LEN, DHPublicKey, DHSharedSecret, dh_shared_secret, generate_dh_keypair,
6 generate_random_scalar,
7};
8use crate::primitives::xwing::XWING_PUBLIC_KEY_LEN;
9use crate::primitives::{decrypt_message_id, encrypt_message_id};
10use crate::{Envelope, FetchResponse, MessageKeyBundle, Plaintext, UserPublic, UserSecret};
11use alloc::vec::Vec;
12use rand_core::{CryptoRng, RngCore};
13use uuid::Uuid;
14
15const NR_ID: &[u8] = b"MOCK_NEWSROOM_ID";
17
18#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
25pub fn encrypt<R, Sender, Recipient>(
26 rng: &mut R,
27 sender: &Sender,
28 plaintext: &Plaintext,
29 recipient: &Recipient,
30) -> Envelope
31where
32 R: RngCore + CryptoRng,
33 Sender: UserSecret + ?Sized,
34 Recipient: UserPublic + ?Sized,
35{
36 let sk_s = sender.message_auth_key();
38 let pk_r = recipient.message_enc_pk();
40 let pk_r_fetch = recipient.fetch_pk().into_bytes();
42
43 let ct_apke =
45 crate::message::auth_enc(rng, sk_s, pk_r, &plaintext.to_bytes(), NR_ID, &pk_r_fetch)
46 .expect("SD-APKE AuthEnc failed");
47
48 let (hint_esk, hint_epk) = generate_dh_keypair(rng).expect("DH Keygen (hint) failed");
51 let hint_sharedsecret: DHSharedSecret =
53 dh_shared_secret(recipient.fetch_pk(), hint_esk.into_bytes())
54 .expect("Failed to generate shared secret");
55
56 let ct_pke = metadata::encrypt(
60 recipient.message_metadata_pk(),
61 &sender.own_message_auth_pk(),
62 )
63 .expect("Valid Keybundle should allow metadata seal");
64
65 Envelope {
66 ct_apke, ct_pke, mgdh_pubkey: hint_epk.into_bytes(), mgdh: hint_sharedsecret.into_bytes(), }
71}
72
73#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
74pub fn decrypt<U: UserSecret + ?Sized>(receiver: &U, envelope: &Envelope) -> Plaintext {
75 decrypt_with_sender(receiver, envelope).0
76}
77
78#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
81pub fn decrypt_with_sender<U: UserSecret + ?Sized>(
82 receiver: &U,
83 envelope: &Envelope,
84) -> (Plaintext, MessagePublicKey) {
85 let mut found: Option<(&MessageKeyBundle, Vec<u8>)> = None;
88 for &bundle in receiver.keybundles().iter() {
89 if let Ok(m) = metadata::decrypt(bundle.metadata_kp.private_key(), &envelope.ct_pke) {
90 found = Some((bundle, m));
91 }
92 }
93
94 let (bundle, raw_metadata) = found.expect("we should find exactly 1 result");
96
97 let sender_pk = MessagePublicKey::from_bytes(&raw_metadata)
99 .expect("Metadata must contain valid sender APKE key tuple");
100
101 let pk_r_fetch = receiver.fetch_keypair().1.into_bytes();
103
104 let pt = crate::message::auth_dec(
106 bundle.apke.private_key(), &sender_pk, &envelope.ct_apke, NR_ID, &pk_r_fetch, )
112 .expect("SD-APKE AuthDec failed");
113
114 (Plaintext::from_bytes(&pt).unwrap(), sender_pk)
115}
116
117#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
125pub fn compute_fetch_challenges<R: RngCore + CryptoRng>(
126 rng: &mut R,
127 entries: &[([u8; LEN_MESSAGE_ID], Envelope)],
128 total_responses: usize,
129) -> Vec<FetchResponse> {
130 let mut responses = Vec::with_capacity(total_responses);
131
132 let eph_sk = generate_random_scalar(&mut *rng).expect("Want dh scalar");
134
135 for (message_id, envelope) in entries.iter() {
136 if responses.len() < total_responses {
137 let shared_secret = dh_shared_secret(&DHPublicKey::from_bytes(envelope.mgdh), eph_sk)
139 .expect("Need 3-party dh shared secret");
140 let enc_mid = encrypt_message_id(&shared_secret.into_bytes(), message_id, rng).unwrap();
141
142 let mut kmid = [0u8; LEN_KMID];
146 kmid.copy_from_slice(&enc_mid);
147
148 let pmgdh = dh_shared_secret(&DHPublicKey::from_bytes(envelope.mgdh_pubkey), eph_sk)
151 .expect("Need pmgdh");
152
153 responses.push(FetchResponse {
154 enc_id: kmid,
155 pmgdh: pmgdh.into_bytes(),
156 });
157 }
158 }
159
160 while responses.len() < total_responses {
162 let mut pad_kmid: [u8; LEN_KMID] = [0u8; LEN_KMID];
163 rng.fill_bytes(&mut pad_kmid);
164
165 let mut pad_pmgdh: [u8; DH_PUBLIC_KEY_LEN] = [0u8; DH_PUBLIC_KEY_LEN];
166 rng.fill_bytes(&mut pad_pmgdh);
167
168 responses.push(FetchResponse {
169 enc_id: pad_kmid,
170 pmgdh: pad_pmgdh,
171 });
172 }
173 responses
174}
175
176#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
179pub fn solve_fetch_challenges<S: UserSecret>(
180 recipient: &S,
181 challenges: &[FetchResponse],
182) -> Vec<Uuid> {
183 let mut message_ids: Vec<Uuid> = Vec::new();
184
185 for chall in challenges.iter() {
186 let maybe_kmid_secret = dh_shared_secret(
188 &DHPublicKey::from_bytes(chall.pmgdh),
189 recipient.fetch_keypair().0.clone().into_bytes(),
190 )
191 .expect("Need 3-party DH (scalarmult) on pmgdh");
192
193 match decrypt_message_id(&maybe_kmid_secret.into_bytes(), &chall.enc_id) {
196 Ok(message_id_bytes) => {
197 let uuid = crate::primitives::provider::uuid_parse::from_slice(&message_id_bytes);
198
199 message_ids.push(uuid);
200 }
201 Err(_) => {
202 }
205 }
206 }
207 message_ids
208}
209
210#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
214pub fn build_message(sender: &impl UserPublic, message: Vec<u8>) -> Plaintext {
215 let mut fetch_pk = [0u8; DH_PUBLIC_KEY_LEN];
216 fetch_pk.copy_from_slice(&sender.fetch_pk().clone().into_bytes());
217
218 let mut reply_key_pq_hybrid = [0u8; XWING_PUBLIC_KEY_LEN];
219 reply_key_pq_hybrid.copy_from_slice(sender.message_metadata_pk().as_bytes());
220
221 Plaintext {
222 sender_fetch_key: fetch_pk,
223 sender_reply_pubkey_hybrid: reply_key_pq_hybrid,
224 msg: message,
225 }
226}
227
228#[cfg(test)]
230mod tests {
231 use rand_chacha::ChaCha20Rng;
232 use rand_core::SeedableRng;
233
234 use crate::{Journalist, Source, SourcePublicView, storage::ServerStorage};
235
236 use super::*;
237
238 fn setup_rng() -> impl rand_core::CryptoRng + rand_core::RngCore {
240 let mut seed = [0u8; 32];
241 getrandom::fill(&mut seed).expect("getrandom failed- is platform supported?");
242 ChaCha20Rng::from_seed(seed)
243 }
244
245 fn assert_encrypt_decrypt<R: CryptoRng + RngCore>(
246 rng: &mut R,
247 sender_public: &impl UserPublic,
248 sender_secret: &impl UserSecret,
249 rcvr_public: &impl UserPublic,
250 rcvr_secret: &impl UserSecret,
251 msg: Vec<u8>,
252 ) {
253 let pt = build_message(sender_public, msg);
254
255 let envelope = encrypt(rng, sender_secret, &pt, rcvr_public);
256 let decrypted = decrypt(rcvr_secret, &envelope);
257
258 let pt_ref = &pt;
259
260 assert_eq!(pt_ref.msg, decrypted.msg);
261 assert_eq!(pt_ref.len(), decrypted.to_bytes().len());
262
263 assert_eq!(
264 pt_ref.sender_fetch_key,
265 sender_secret.fetch_keypair().1.clone().into_bytes()
266 );
267 assert_eq!(
268 &pt_ref.sender_reply_pubkey_hybrid,
269 sender_public.message_metadata_pk().as_bytes()
270 );
271 assert_eq!(
272 pt.len(),
273 &pt_ref.msg.len() + DH_PUBLIC_KEY_LEN + XWING_PUBLIC_KEY_LEN
274 );
275 }
276
277 #[test]
278 fn test_encrypt_decrypt_roundtrip() {
279 let mut rng = setup_rng();
280
281 let sender = Source::new(&mut rng);
282 let recipient = Journalist::new(&mut rng, 2);
283
284 let msg = b"Encrypt-decrypt-test".to_vec();
285
286 assert_encrypt_decrypt(
287 &mut rng,
288 &sender.public(),
289 &sender,
290 &recipient.public(1),
291 &recipient,
292 msg,
293 );
294 }
295
296 #[test]
297 fn test_encrypt_decrypt_sourcesource() {
298 let mut rng = setup_rng();
300 let sender = Source::new(&mut rng);
301 let recipient = Source::new(&mut rng);
302
303 assert_encrypt_decrypt(
304 &mut rng,
305 &sender.public(),
306 &sender,
307 &recipient.public(),
308 &recipient,
309 b"Encrypt-decrypt-test".to_vec(),
310 );
311 }
312
313 #[test]
314 fn test_fetch_challenges_roundtrip() {
315 let mut rng = setup_rng();
316
317 let source = Source::new(&mut rng);
318 let journalist = Journalist::new(&mut rng, 2);
319
320 let journalist_public = journalist.public(0);
322
323 let msg = b"Fetch this message";
324 let plaintext = build_message(&source.public(), msg.to_vec());
325 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
326
327 let mut store: ServerStorage = ServerStorage::new();
328 let message_id = store.deterministic_uuid(&mut rng);
329
330 store.add_message(message_id, envelope);
331
332 let entries: Vec<_> = store
333 .get_messages()
334 .iter()
335 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
336 .collect();
337 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
338
339 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
340
341 assert_eq!(solved_ids.len(), 1);
342 assert_eq!(solved_ids[0], message_id);
343 }
344
345 #[test]
346 fn test_wrong_recipient_cannot_decrypt_challenge() {
347 let mut rng = setup_rng();
348
349 let source = Source::new(&mut rng);
350 let journalist = Journalist::new(&mut rng, 2);
351
352 let wrong_journalist = Journalist::new(&mut rng, 2);
353
354 let journalist_public = journalist.public(0);
356
357 let msg = b"Fetch this message";
358 let plaintext = build_message(&source.public(), msg.to_vec());
359 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
360
361 let mut store: ServerStorage = ServerStorage::new();
362 let message_id = store.deterministic_uuid(&mut rng);
363
364 store.add_message(message_id, envelope);
365
366 let entries: Vec<_> = store
367 .get_messages()
368 .iter()
369 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
370 .collect();
371 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
372
373 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
374
375 let solved_ids_miss = solve_fetch_challenges(&wrong_journalist, &challenges);
376
377 assert_eq!(solved_ids.len(), 1);
378 assert_eq!(solved_ids[0], message_id);
379 assert_eq!(solved_ids_miss.len(), 0);
380 }
381
382 #[test]
383 fn test_journalist_reply_to_source_roundtrip() {
384 let mut rng = setup_rng();
385
386 let source = Source::new(&mut rng);
388 let journalist = Journalist::new(&mut rng, 2);
389 let journalist_public = journalist.public(0);
390
391 let submission = build_message(&source.public(), b"howdy".to_vec());
392 let envelope = encrypt(&mut rng, &source, &submission, &journalist_public);
393
394 let (pt, sender_apke) = decrypt_with_sender(&journalist, &envelope);
396 let reply_recipient = SourcePublicView::from_reply_keys(
397 DHPublicKey::from_bytes(pt.sender_fetch_key),
398 sender_apke,
399 crate::metadata::MetadataPublicKey::from_bytes(&pt.sender_reply_pubkey_hybrid)
400 .expect("recovered metadata key is valid"),
401 );
402
403 let reply_text = b"thanks dawg".to_vec();
405 let reply_pt = journalist.build_message(reply_text.clone());
406 let reply_envelope = encrypt(&mut rng, &journalist, &reply_pt, &reply_recipient);
407
408 let decrypted = decrypt(&source, &reply_envelope);
410 assert_eq!(decrypted.msg, reply_text);
411 }
412
413 #[test]
414 fn test_encrypt_decrypt_journalist_only() {
415 let mut rng = setup_rng();
416
417 let journalist = Journalist::new(&mut rng, 2);
418 let j2 = Journalist::new(&mut rng, 2);
419
420 let msg = "Test message".as_bytes().to_vec();
421
422 assert_encrypt_decrypt(
423 &mut rng,
424 &journalist.public(0),
425 &journalist,
426 &j2.public(0),
427 &j2,
428 msg,
429 );
430 }
431}