1use crate::message::MessagePublicKey;
2use crate::metadata;
3use crate::primitives::provider::constants::{LEN_KMID, LEN_MESSAGE_ID};
4use crate::primitives::ristretto255::{
5 DHPublicKey, dh_shared_secret, generate_dh_keypair, generate_random_scalar,
6 random_dh_public_key,
7};
8use crate::primitives::xwing::XWingPublicKey;
9use crate::primitives::{decrypt_message_id, derive_challenge_key, 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 keypair_s = sender.message_auth_keypair();
38 let pk_r = recipient.message_enc_pk();
40 let pk_r_fetch = recipient.fetch_pk();
42
43 let ct_apke = crate::message::auth_enc(
45 rng,
46 keypair_s,
47 pk_r,
48 &plaintext.to_bytes(),
49 NR_ID,
50 pk_r_fetch,
51 )
52 .expect("SD-APKE AuthEnc failed");
53
54 let (hint_esk, hint_epk) = generate_dh_keypair(rng);
57 let hint_sharedsecret: DHPublicKey = dh_shared_secret(recipient.fetch_pk(), &hint_esk);
59
60 let ct_pke = metadata::encrypt(recipient.message_metadata_pk(), &keypair_s.public_key())
64 .expect("Valid Keybundle should allow metadata seal");
65
66 Envelope {
67 ct_apke, ct_pke, mgdh_pubkey: hint_epk, mgdh: hint_sharedsecret, }
72}
73
74#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
75pub fn decrypt<U: UserSecret + ?Sized>(receiver: &U, envelope: &Envelope) -> Plaintext {
76 decrypt_with_sender(receiver, envelope).0
77}
78
79#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
82pub fn decrypt_with_sender<U: UserSecret + ?Sized>(
83 receiver: &U,
84 envelope: &Envelope,
85) -> (Plaintext, MessagePublicKey) {
86 let mut found: Option<(&MessageKeyBundle, Vec<u8>)> = None;
89 for &bundle in receiver.keybundles().iter() {
90 if let Ok(m) = metadata::decrypt(bundle.metadata_kp.private_key(), &envelope.ct_pke) {
91 found = Some((bundle, m));
92 }
93 }
94
95 let (bundle, raw_metadata) = found.expect("we should find exactly 1 result");
97
98 let sender_pk = MessagePublicKey::from_bytes(&raw_metadata)
100 .expect("Metadata must contain valid sender APKE key tuple");
101
102 let pk_r_fetch = receiver.fetch_keypair().1;
104
105 let pt = crate::message::auth_dec(
107 bundle.apke.private_key(), &sender_pk, &envelope.ct_apke, NR_ID, pk_r_fetch, )
113 .expect("SD-APKE AuthDec failed");
114
115 (Plaintext::from_bytes(&pt).unwrap(), sender_pk)
116}
117
118#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
126pub fn compute_fetch_challenges<R: RngCore + CryptoRng>(
127 rng: &mut R,
128 entries: &[([u8; LEN_MESSAGE_ID], Envelope)],
129 total_responses: usize,
130) -> Vec<FetchResponse> {
131 let mut responses = Vec::with_capacity(total_responses);
132
133 for (message_id, envelope) in entries.iter() {
134 if responses.len() < total_responses {
135 let eph_sk = generate_random_scalar(&mut *rng);
137
138 let shared_secret = dh_shared_secret(&envelope.mgdh, &eph_sk);
140 let key = derive_challenge_key(&shared_secret, NR_ID).unwrap();
141 let enc_mid = encrypt_message_id(&key, message_id).unwrap();
142
143 let mut kmid = [0u8; LEN_KMID];
147 kmid.copy_from_slice(&enc_mid);
148
149 let pmgdh = dh_shared_secret(&envelope.mgdh_pubkey, &eph_sk);
152
153 responses.push(FetchResponse {
154 enc_id: kmid,
155 pmgdh,
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 responses.push(FetchResponse {
166 enc_id: pad_kmid,
167 pmgdh: random_dh_public_key(rng),
168 });
169 }
170 responses
171}
172
173#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
176pub fn solve_fetch_challenges<S: UserSecret>(
177 recipient: &S,
178 challenges: &[FetchResponse],
179) -> Vec<Uuid> {
180 let mut message_ids: Vec<Uuid> = Vec::new();
181
182 for chall in challenges.iter() {
183 let maybe_kmid_secret = dh_shared_secret(&chall.pmgdh, recipient.fetch_keypair().0);
185 let challenge_key = derive_challenge_key(&maybe_kmid_secret, NR_ID).unwrap();
186
187 match decrypt_message_id(&challenge_key, &chall.enc_id) {
190 Ok(message_id_bytes) => {
191 let uuid = crate::primitives::provider::uuid_parse::from_slice(&message_id_bytes);
192
193 message_ids.push(uuid);
194 }
195 Err(_) => {
196 }
199 }
200 }
201 message_ids
202}
203
204#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
208pub fn build_message(sender: &impl UserPublic, message: Vec<u8>) -> Plaintext {
209 let mut reply_key_pq_hybrid = [0u8; XWingPublicKey::LEN];
210 reply_key_pq_hybrid.copy_from_slice(sender.message_metadata_pk().as_bytes());
211
212 Plaintext {
213 sender_fetch_key: *sender.fetch_pk(),
214 sender_reply_pubkey_hybrid: reply_key_pq_hybrid,
215 msg: message,
216 }
217}
218
219#[cfg(test)]
221mod tests {
222 use rand_chacha::ChaCha20Rng;
223 use rand_core::SeedableRng;
224
225 use crate::{Journalist, Source, SourcePublicView, storage::ServerStorage};
226
227 use super::*;
228
229 fn setup_rng() -> impl rand_core::CryptoRng + rand_core::RngCore {
231 let mut seed = [0u8; 32];
232 getrandom::fill(&mut seed).expect("getrandom failed- is platform supported?");
233 ChaCha20Rng::from_seed(seed)
234 }
235
236 fn assert_encrypt_decrypt<R: CryptoRng + RngCore>(
237 rng: &mut R,
238 sender_public: &impl UserPublic,
239 sender_secret: &impl UserSecret,
240 rcvr_public: &impl UserPublic,
241 rcvr_secret: &impl UserSecret,
242 msg: Vec<u8>,
243 ) {
244 let pt = build_message(sender_public, msg);
245
246 let envelope = encrypt(rng, sender_secret, &pt, rcvr_public);
247 let decrypted = decrypt(rcvr_secret, &envelope);
248
249 let pt_ref = &pt;
250
251 assert_eq!(pt_ref.msg, decrypted.msg);
252 assert_eq!(pt_ref.len(), decrypted.to_bytes().len());
253
254 assert_eq!(
255 pt_ref.sender_fetch_key.into_bytes(),
256 sender_secret.fetch_keypair().1.clone().into_bytes()
257 );
258 assert_eq!(
259 &pt_ref.sender_reply_pubkey_hybrid,
260 sender_public.message_metadata_pk().as_bytes()
261 );
262 assert_eq!(
263 pt.len(),
264 &pt_ref.msg.len() + DHPublicKey::LEN + XWingPublicKey::LEN
265 );
266 }
267
268 #[test]
269 fn test_encrypt_decrypt_roundtrip() {
270 let mut rng = setup_rng();
271
272 let sender = Source::new(&mut rng);
273 let recipient = Journalist::new(&mut rng, 2);
274
275 let msg = b"Encrypt-decrypt-test".to_vec();
276
277 assert_encrypt_decrypt(
278 &mut rng,
279 &sender.public(),
280 &sender,
281 &recipient.public(1),
282 &recipient,
283 msg,
284 );
285 }
286
287 #[test]
288 fn test_encrypt_decrypt_sourcesource() {
289 let mut rng = setup_rng();
291 let sender = Source::new(&mut rng);
292 let recipient = Source::new(&mut rng);
293
294 assert_encrypt_decrypt(
295 &mut rng,
296 &sender.public(),
297 &sender,
298 &recipient.public(),
299 &recipient,
300 b"Encrypt-decrypt-test".to_vec(),
301 );
302 }
303
304 #[test]
305 fn test_fetch_challenges_roundtrip() {
306 let mut rng = setup_rng();
307
308 let source = Source::new(&mut rng);
309 let journalist = Journalist::new(&mut rng, 2);
310
311 let journalist_public = journalist.public(0);
313
314 let msg = b"Fetch this message";
315 let plaintext = build_message(&source.public(), msg.to_vec());
316 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
317
318 let mut store: ServerStorage = ServerStorage::new();
319 let message_id = store.deterministic_uuid(&mut rng);
320
321 store.add_message(message_id, envelope);
322
323 let entries: Vec<_> = store
324 .get_messages()
325 .iter()
326 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
327 .collect();
328 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
329
330 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
331
332 assert_eq!(solved_ids.len(), 1);
333 assert_eq!(solved_ids[0], message_id);
334 }
335
336 #[test]
337 fn test_wrong_recipient_cannot_decrypt_challenge() {
338 let mut rng = setup_rng();
339
340 let source = Source::new(&mut rng);
341 let journalist = Journalist::new(&mut rng, 2);
342
343 let wrong_journalist = Journalist::new(&mut rng, 2);
344
345 let journalist_public = journalist.public(0);
347
348 let msg = b"Fetch this message";
349 let plaintext = build_message(&source.public(), msg.to_vec());
350 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
351
352 let mut store: ServerStorage = ServerStorage::new();
353 let message_id = store.deterministic_uuid(&mut rng);
354
355 store.add_message(message_id, envelope);
356
357 let entries: Vec<_> = store
358 .get_messages()
359 .iter()
360 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
361 .collect();
362 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
363
364 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
365
366 let solved_ids_miss = solve_fetch_challenges(&wrong_journalist, &challenges);
367
368 assert_eq!(solved_ids.len(), 1);
369 assert_eq!(solved_ids[0], message_id);
370 assert_eq!(solved_ids_miss.len(), 0);
371 }
372
373 #[test]
374 fn test_journalist_reply_to_source_roundtrip() {
375 let mut rng = setup_rng();
376
377 let source = Source::new(&mut rng);
379 let journalist = Journalist::new(&mut rng, 2);
380 let journalist_public = journalist.public(0);
381
382 let submission = build_message(&source.public(), b"howdy".to_vec());
383 let envelope = encrypt(&mut rng, &source, &submission, &journalist_public);
384
385 let (pt, sender_apke) = decrypt_with_sender(&journalist, &envelope);
387 let reply_recipient = SourcePublicView::from_reply_keys(
388 pt.sender_fetch_key,
389 sender_apke,
390 crate::metadata::MetadataPublicKey::from_bytes(&pt.sender_reply_pubkey_hybrid)
391 .expect("recovered metadata key is valid"),
392 );
393
394 let reply_text = b"thanks dawg".to_vec();
396 let reply_pt = journalist.build_message(reply_text.clone());
397 let reply_envelope = encrypt(&mut rng, &journalist, &reply_pt, &reply_recipient);
398
399 let decrypted = decrypt(&source, &reply_envelope);
401 assert_eq!(decrypted.msg, reply_text);
402 }
403
404 #[test]
405 fn test_encrypt_decrypt_journalist_only() {
406 let mut rng = setup_rng();
407
408 let journalist = Journalist::new(&mut rng, 2);
409 let j2 = Journalist::new(&mut rng, 2);
410
411 let msg = "Test message".as_bytes().to_vec();
412
413 assert_encrypt_decrypt(
414 &mut rng,
415 &journalist.public(0),
416 &journalist,
417 &j2.public(0),
418 &j2,
419 msg,
420 );
421 }
422}