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 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);
51 let hint_sharedsecret: DHPublicKey = dh_shared_secret(recipient.fetch_pk(), &hint_esk);
53
54 let ct_pke = metadata::encrypt(
58 recipient.message_metadata_pk(),
59 &sender.own_message_auth_pk(),
60 )
61 .expect("Valid Keybundle should allow metadata seal");
62
63 Envelope {
64 ct_apke, ct_pke, mgdh_pubkey: hint_epk, mgdh: hint_sharedsecret, }
69}
70
71#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
72pub fn decrypt<U: UserSecret + ?Sized>(receiver: &U, envelope: &Envelope) -> Plaintext {
73 decrypt_with_sender(receiver, envelope).0
74}
75
76#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
79pub fn decrypt_with_sender<U: UserSecret + ?Sized>(
80 receiver: &U,
81 envelope: &Envelope,
82) -> (Plaintext, MessagePublicKey) {
83 let mut found: Option<(&MessageKeyBundle, Vec<u8>)> = None;
86 for &bundle in receiver.keybundles().iter() {
87 if let Ok(m) = metadata::decrypt(bundle.metadata_kp.private_key(), &envelope.ct_pke) {
88 found = Some((bundle, m));
89 }
90 }
91
92 let (bundle, raw_metadata) = found.expect("we should find exactly 1 result");
94
95 let sender_pk = MessagePublicKey::from_bytes(&raw_metadata)
97 .expect("Metadata must contain valid sender APKE key tuple");
98
99 let pk_r_fetch = receiver.fetch_keypair().1.into_bytes();
101
102 let pt = crate::message::auth_dec(
104 bundle.apke.private_key(), &sender_pk, &envelope.ct_apke, NR_ID, &pk_r_fetch, )
110 .expect("SD-APKE AuthDec failed");
111
112 (Plaintext::from_bytes(&pt).unwrap(), sender_pk)
113}
114
115#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
123pub fn compute_fetch_challenges<R: RngCore + CryptoRng>(
124 rng: &mut R,
125 entries: &[([u8; LEN_MESSAGE_ID], Envelope)],
126 total_responses: usize,
127) -> Vec<FetchResponse> {
128 let mut responses = Vec::with_capacity(total_responses);
129
130 for (message_id, envelope) in entries.iter() {
131 if responses.len() < total_responses {
132 let eph_sk = generate_random_scalar(&mut *rng);
134
135 let shared_secret = dh_shared_secret(&envelope.mgdh, &eph_sk);
137 let enc_mid = encrypt_message_id(&shared_secret.into_bytes(), message_id, rng).unwrap();
138
139 let mut kmid = [0u8; LEN_KMID];
143 kmid.copy_from_slice(&enc_mid);
144
145 let pmgdh = dh_shared_secret(&envelope.mgdh_pubkey, &eph_sk);
148
149 responses.push(FetchResponse {
150 enc_id: kmid,
151 pmgdh,
152 });
153 }
154 }
155
156 while responses.len() < total_responses {
158 let mut pad_kmid: [u8; LEN_KMID] = [0u8; LEN_KMID];
159 rng.fill_bytes(&mut pad_kmid);
160
161 responses.push(FetchResponse {
162 enc_id: pad_kmid,
163 pmgdh: random_dh_public_key(rng),
164 });
165 }
166 responses
167}
168
169#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
172pub fn solve_fetch_challenges<S: UserSecret>(
173 recipient: &S,
174 challenges: &[FetchResponse],
175) -> Vec<Uuid> {
176 let mut message_ids: Vec<Uuid> = Vec::new();
177
178 for chall in challenges.iter() {
179 let maybe_kmid_secret = dh_shared_secret(&chall.pmgdh, recipient.fetch_keypair().0);
181
182 match decrypt_message_id(&maybe_kmid_secret.into_bytes(), &chall.enc_id) {
185 Ok(message_id_bytes) => {
186 let uuid = crate::primitives::provider::uuid_parse::from_slice(&message_id_bytes);
187
188 message_ids.push(uuid);
189 }
190 Err(_) => {
191 }
194 }
195 }
196 message_ids
197}
198
199#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
203pub fn build_message(sender: &impl UserPublic, message: Vec<u8>) -> Plaintext {
204 let mut reply_key_pq_hybrid = [0u8; XWING_PUBLIC_KEY_LEN];
205 reply_key_pq_hybrid.copy_from_slice(sender.message_metadata_pk().as_bytes());
206
207 Plaintext {
208 sender_fetch_key: *sender.fetch_pk(),
209 sender_reply_pubkey_hybrid: reply_key_pq_hybrid,
210 msg: message,
211 }
212}
213
214#[cfg(test)]
216mod tests {
217 use rand_chacha::ChaCha20Rng;
218 use rand_core::SeedableRng;
219
220 use crate::primitives::ristretto255::DH_PUBLIC_KEY_LEN;
221 use crate::{Journalist, Source, SourcePublicView, storage::ServerStorage};
222
223 use super::*;
224
225 fn setup_rng() -> impl rand_core::CryptoRng + rand_core::RngCore {
227 let mut seed = [0u8; 32];
228 getrandom::fill(&mut seed).expect("getrandom failed- is platform supported?");
229 ChaCha20Rng::from_seed(seed)
230 }
231
232 fn assert_encrypt_decrypt<R: CryptoRng + RngCore>(
233 rng: &mut R,
234 sender_public: &impl UserPublic,
235 sender_secret: &impl UserSecret,
236 rcvr_public: &impl UserPublic,
237 rcvr_secret: &impl UserSecret,
238 msg: Vec<u8>,
239 ) {
240 let pt = build_message(sender_public, msg);
241
242 let envelope = encrypt(rng, sender_secret, &pt, rcvr_public);
243 let decrypted = decrypt(rcvr_secret, &envelope);
244
245 let pt_ref = &pt;
246
247 assert_eq!(pt_ref.msg, decrypted.msg);
248 assert_eq!(pt_ref.len(), decrypted.to_bytes().len());
249
250 assert_eq!(
251 pt_ref.sender_fetch_key.into_bytes(),
252 sender_secret.fetch_keypair().1.clone().into_bytes()
253 );
254 assert_eq!(
255 &pt_ref.sender_reply_pubkey_hybrid,
256 sender_public.message_metadata_pk().as_bytes()
257 );
258 assert_eq!(
259 pt.len(),
260 &pt_ref.msg.len() + DH_PUBLIC_KEY_LEN + XWING_PUBLIC_KEY_LEN
261 );
262 }
263
264 #[test]
265 fn test_encrypt_decrypt_roundtrip() {
266 let mut rng = setup_rng();
267
268 let sender = Source::new(&mut rng);
269 let recipient = Journalist::new(&mut rng, 2);
270
271 let msg = b"Encrypt-decrypt-test".to_vec();
272
273 assert_encrypt_decrypt(
274 &mut rng,
275 &sender.public(),
276 &sender,
277 &recipient.public(1),
278 &recipient,
279 msg,
280 );
281 }
282
283 #[test]
284 fn test_encrypt_decrypt_sourcesource() {
285 let mut rng = setup_rng();
287 let sender = Source::new(&mut rng);
288 let recipient = Source::new(&mut rng);
289
290 assert_encrypt_decrypt(
291 &mut rng,
292 &sender.public(),
293 &sender,
294 &recipient.public(),
295 &recipient,
296 b"Encrypt-decrypt-test".to_vec(),
297 );
298 }
299
300 #[test]
301 fn test_fetch_challenges_roundtrip() {
302 let mut rng = setup_rng();
303
304 let source = Source::new(&mut rng);
305 let journalist = Journalist::new(&mut rng, 2);
306
307 let journalist_public = journalist.public(0);
309
310 let msg = b"Fetch this message";
311 let plaintext = build_message(&source.public(), msg.to_vec());
312 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
313
314 let mut store: ServerStorage = ServerStorage::new();
315 let message_id = store.deterministic_uuid(&mut rng);
316
317 store.add_message(message_id, envelope);
318
319 let entries: Vec<_> = store
320 .get_messages()
321 .iter()
322 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
323 .collect();
324 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
325
326 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
327
328 assert_eq!(solved_ids.len(), 1);
329 assert_eq!(solved_ids[0], message_id);
330 }
331
332 #[test]
333 fn test_wrong_recipient_cannot_decrypt_challenge() {
334 let mut rng = setup_rng();
335
336 let source = Source::new(&mut rng);
337 let journalist = Journalist::new(&mut rng, 2);
338
339 let wrong_journalist = Journalist::new(&mut rng, 2);
340
341 let journalist_public = journalist.public(0);
343
344 let msg = b"Fetch this message";
345 let plaintext = build_message(&source.public(), msg.to_vec());
346 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
347
348 let mut store: ServerStorage = ServerStorage::new();
349 let message_id = store.deterministic_uuid(&mut rng);
350
351 store.add_message(message_id, envelope);
352
353 let entries: Vec<_> = store
354 .get_messages()
355 .iter()
356 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
357 .collect();
358 let challenges = compute_fetch_challenges(&mut rng, &entries, 2);
359
360 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
361
362 let solved_ids_miss = solve_fetch_challenges(&wrong_journalist, &challenges);
363
364 assert_eq!(solved_ids.len(), 1);
365 assert_eq!(solved_ids[0], message_id);
366 assert_eq!(solved_ids_miss.len(), 0);
367 }
368
369 #[test]
370 fn test_journalist_reply_to_source_roundtrip() {
371 let mut rng = setup_rng();
372
373 let source = Source::new(&mut rng);
375 let journalist = Journalist::new(&mut rng, 2);
376 let journalist_public = journalist.public(0);
377
378 let submission = build_message(&source.public(), b"howdy".to_vec());
379 let envelope = encrypt(&mut rng, &source, &submission, &journalist_public);
380
381 let (pt, sender_apke) = decrypt_with_sender(&journalist, &envelope);
383 let reply_recipient = SourcePublicView::from_reply_keys(
384 pt.sender_fetch_key,
385 sender_apke,
386 crate::metadata::MetadataPublicKey::from_bytes(&pt.sender_reply_pubkey_hybrid)
387 .expect("recovered metadata key is valid"),
388 );
389
390 let reply_text = b"thanks dawg".to_vec();
392 let reply_pt = journalist.build_message(reply_text.clone());
393 let reply_envelope = encrypt(&mut rng, &journalist, &reply_pt, &reply_recipient);
394
395 let decrypted = decrypt(&source, &reply_envelope);
397 assert_eq!(decrypted.msg, reply_text);
398 }
399
400 #[test]
401 fn test_encrypt_decrypt_journalist_only() {
402 let mut rng = setup_rng();
403
404 let journalist = Journalist::new(&mut rng, 2);
405 let j2 = Journalist::new(&mut rng, 2);
406
407 let msg = "Test message".as_bytes().to_vec();
408
409 assert_encrypt_decrypt(
410 &mut rng,
411 &journalist.public(0),
412 &journalist,
413 &j2.public(0),
414 &j2,
415 msg,
416 );
417 }
418}