securedrop_protocol_minimal/
encrypt_decrypt.rs1use 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::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 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 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(&envelope.mgdh_pubkey, &eph_sk);
151
152 responses.push(FetchResponse {
153 enc_id: kmid,
154 pmgdh,
155 });
156 }
157 }
158
159 while responses.len() < total_responses {
161 let mut pad_kmid: [u8; LEN_KMID] = [0u8; LEN_KMID];
162 rng.fill_bytes(&mut pad_kmid);
163
164 responses.push(FetchResponse {
165 enc_id: pad_kmid,
166 pmgdh: random_dh_public_key(rng),
167 });
168 }
169 responses
170}
171
172#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
175pub fn solve_fetch_challenges<S: UserSecret>(
176 recipient: &S,
177 challenges: &[FetchResponse],
178) -> Vec<Uuid> {
179 let mut message_ids: Vec<Uuid> = Vec::new();
180
181 for chall in challenges.iter() {
182 let maybe_kmid_secret = dh_shared_secret(&chall.pmgdh, recipient.fetch_keypair().0);
184
185 match decrypt_message_id(&maybe_kmid_secret.into_bytes(), &chall.enc_id) {
188 Ok(message_id_bytes) => {
189 let uuid = crate::primitives::provider::uuid_parse::from_slice(&message_id_bytes);
190
191 message_ids.push(uuid);
192 }
193 Err(_) => {
194 }
197 }
198 }
199 message_ids
200}
201
202#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
206pub fn build_message(sender: &impl UserPublic, message: Vec<u8>) -> Plaintext {
207 let mut reply_key_pq_hybrid = [0u8; XWING_PUBLIC_KEY_LEN];
208 reply_key_pq_hybrid.copy_from_slice(sender.message_metadata_pk().as_bytes());
209
210 Plaintext {
211 sender_fetch_key: *sender.fetch_pk(),
212 sender_reply_pubkey_hybrid: reply_key_pq_hybrid,
213 msg: message,
214 }
215}
216
217#[cfg(test)]
219mod tests {
220 use rand_chacha::ChaCha20Rng;
221 use rand_core::SeedableRng;
222
223 use crate::primitives::ristretto255::DH_PUBLIC_KEY_LEN;
224 use crate::{Journalist, Source, SourcePublicView, storage::ServerStorage};
225
226 use super::*;
227
228 fn setup_rng() -> impl rand_core::CryptoRng + rand_core::RngCore {
230 let mut seed = [0u8; 32];
231 getrandom::fill(&mut seed).expect("getrandom failed- is platform supported?");
232 ChaCha20Rng::from_seed(seed)
233 }
234
235 fn assert_encrypt_decrypt<R: CryptoRng + RngCore>(
236 rng: &mut R,
237 sender_public: &impl UserPublic,
238 sender_secret: &impl UserSecret,
239 rcvr_public: &impl UserPublic,
240 rcvr_secret: &impl UserSecret,
241 msg: Vec<u8>,
242 ) {
243 let pt = build_message(sender_public, msg);
244
245 let envelope = encrypt(rng, sender_secret, &pt, rcvr_public);
246 let decrypted = decrypt(rcvr_secret, &envelope);
247
248 let pt_ref = &pt;
249
250 assert_eq!(pt_ref.msg, decrypted.msg);
251 assert_eq!(pt_ref.len(), decrypted.to_bytes().len());
252
253 assert_eq!(
254 pt_ref.sender_fetch_key.into_bytes(),
255 sender_secret.fetch_keypair().1.clone().into_bytes()
256 );
257 assert_eq!(
258 &pt_ref.sender_reply_pubkey_hybrid,
259 sender_public.message_metadata_pk().as_bytes()
260 );
261 assert_eq!(
262 pt.len(),
263 &pt_ref.msg.len() + DH_PUBLIC_KEY_LEN + XWING_PUBLIC_KEY_LEN
264 );
265 }
266
267 #[test]
268 fn test_encrypt_decrypt_roundtrip() {
269 let mut rng = setup_rng();
270
271 let sender = Source::new(&mut rng);
272 let recipient = Journalist::new(&mut rng, 2);
273
274 let msg = b"Encrypt-decrypt-test".to_vec();
275
276 assert_encrypt_decrypt(
277 &mut rng,
278 &sender.public(),
279 &sender,
280 &recipient.public(1),
281 &recipient,
282 msg,
283 );
284 }
285
286 #[test]
287 fn test_encrypt_decrypt_sourcesource() {
288 let mut rng = setup_rng();
290 let sender = Source::new(&mut rng);
291 let recipient = Source::new(&mut rng);
292
293 assert_encrypt_decrypt(
294 &mut rng,
295 &sender.public(),
296 &sender,
297 &recipient.public(),
298 &recipient,
299 b"Encrypt-decrypt-test".to_vec(),
300 );
301 }
302
303 #[test]
304 fn test_fetch_challenges_roundtrip() {
305 let mut rng = setup_rng();
306
307 let source = Source::new(&mut rng);
308 let journalist = Journalist::new(&mut rng, 2);
309
310 let journalist_public = journalist.public(0);
312
313 let msg = b"Fetch this message";
314 let plaintext = build_message(&source.public(), msg.to_vec());
315 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
316
317 let mut store: ServerStorage = ServerStorage::new();
318 let message_id = store.deterministic_uuid(&mut rng);
319
320 store.add_message(message_id, envelope);
321
322 let entries: Vec<_> = store
323 .get_messages()
324 .iter()
325 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
326 .collect();
327 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
328
329 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
330
331 assert_eq!(solved_ids.len(), 1);
332 assert_eq!(solved_ids[0], message_id);
333 }
334
335 #[test]
336 fn test_wrong_recipient_cannot_decrypt_challenge() {
337 let mut rng = setup_rng();
338
339 let source = Source::new(&mut rng);
340 let journalist = Journalist::new(&mut rng, 2);
341
342 let wrong_journalist = Journalist::new(&mut rng, 2);
343
344 let journalist_public = journalist.public(0);
346
347 let msg = b"Fetch this message";
348 let plaintext = build_message(&source.public(), msg.to_vec());
349 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
350
351 let mut store: ServerStorage = ServerStorage::new();
352 let message_id = store.deterministic_uuid(&mut rng);
353
354 store.add_message(message_id, envelope);
355
356 let entries: Vec<_> = store
357 .get_messages()
358 .iter()
359 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
360 .collect();
361 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
362
363 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
364
365 let solved_ids_miss = solve_fetch_challenges(&wrong_journalist, &challenges);
366
367 assert_eq!(solved_ids.len(), 1);
368 assert_eq!(solved_ids[0], message_id);
369 assert_eq!(solved_ids_miss.len(), 0);
370 }
371
372 #[test]
373 fn test_journalist_reply_to_source_roundtrip() {
374 let mut rng = setup_rng();
375
376 let source = Source::new(&mut rng);
378 let journalist = Journalist::new(&mut rng, 2);
379 let journalist_public = journalist.public(0);
380
381 let submission = build_message(&source.public(), b"howdy".to_vec());
382 let envelope = encrypt(&mut rng, &source, &submission, &journalist_public);
383
384 let (pt, sender_apke) = decrypt_with_sender(&journalist, &envelope);
386 let reply_recipient = SourcePublicView::from_reply_keys(
387 pt.sender_fetch_key,
388 sender_apke,
389 crate::metadata::MetadataPublicKey::from_bytes(&pt.sender_reply_pubkey_hybrid)
390 .expect("recovered metadata key is valid"),
391 );
392
393 let reply_text = b"thanks dawg".to_vec();
395 let reply_pt = journalist.build_message(reply_text.clone());
396 let reply_envelope = encrypt(&mut rng, &journalist, &reply_pt, &reply_recipient);
397
398 let decrypted = decrypt(&source, &reply_envelope);
400 assert_eq!(decrypted.msg, reply_text);
401 }
402
403 #[test]
404 fn test_encrypt_decrypt_journalist_only() {
405 let mut rng = setup_rng();
406
407 let journalist = Journalist::new(&mut rng, 2);
408 let j2 = Journalist::new(&mut rng, 2);
409
410 let msg = "Test message".as_bytes().to_vec();
411
412 assert_encrypt_decrypt(
413 &mut rng,
414 &journalist.public(0),
415 &journalist,
416 &j2.public(0),
417 &j2,
418 msg,
419 );
420 }
421}