securedrop_protocol_minimal/
encrypt_decrypt.rs1use crate::constants::{LEN_DH_ITEM, LEN_KMID, LEN_XWING_ENCAPS_KEY};
2use crate::message::MessagePublicKey;
3use crate::metadata;
4use crate::primitives::x25519::DHPublicKey;
5use crate::primitives::x25519::DHSharedSecret;
6use crate::primitives::x25519::dh_shared_secret;
7use crate::primitives::x25519::generate_dh_keypair;
8use crate::primitives::x25519::generate_random_scalar;
9use crate::primitives::{decrypt_message_id, encrypt_message_id};
10use crate::storage::ServerMessageStore;
11use crate::{Envelope, FetchResponse, MessageKeyBundle, Plaintext, UserPublic, UserSecret};
12use alloc::format;
13use alloc::vec::Vec;
14use rand_core::{CryptoRng, RngCore};
15use uuid::Uuid;
16
17const NR_ID: &[u8] = b"MOCK_NEWSROOM_ID";
19
20pub fn encrypt<R, Sender, Recipient>(
27 rng: &mut R,
28 sender: &Sender,
29 plaintext: &Plaintext,
30 recipient: &Recipient,
31) -> Envelope
32where
33 R: RngCore + CryptoRng,
34 Sender: UserSecret + ?Sized,
35 Recipient: UserPublic + ?Sized,
36{
37 let sk_s = sender.message_auth_key();
39 let pk_r = recipient.message_enc_pk();
41 let pk_r_fetch = recipient.fetch_pk().into_bytes();
43
44 let ct_apke =
46 crate::message::auth_enc(rng, sk_s, pk_r, &plaintext.to_bytes(), NR_ID, &pk_r_fetch)
47 .expect("SD-APKE AuthEnc failed");
48
49 let (hint_esk, hint_epk) = generate_dh_keypair(rng).expect("DH Keygen (hint) failed");
52 let hint_sharedsecret: DHSharedSecret =
54 dh_shared_secret(recipient.fetch_pk(), hint_esk.into_bytes())
55 .expect("Failed to generate shared secret");
56
57 let sender_apke_bytes = sender.message_auth_pk().as_bytes();
59
60 let ct_pke = metadata::encrypt(recipient.message_metadata_pk(), &sender_apke_bytes);
62
63 Envelope {
64 ct_apke, ct_pke, mgdh_pubkey: hint_epk.into_bytes(), mgdh: hint_sharedsecret.into_bytes(), }
69}
70
71pub fn decrypt<U: UserSecret + ?Sized>(receiver: &U, envelope: &Envelope) -> Plaintext {
72 let mut results: Vec<(&MessageKeyBundle, Vec<u8>)> = Vec::new();
75
76 for bundle in receiver.keybundles() {
78 if let Ok(m) = metadata::decrypt(bundle.metadata_kp.private_key(), &envelope.ct_pke) {
79 results.push((bundle, m));
80 }
81 }
82
83 let (bundle, raw_metadata) = results.first().expect("we should find exactly 1 result");
85
86 let sender_pk = MessagePublicKey::from_bytes(raw_metadata)
88 .expect("Metadata must contain valid sender APKE key tuple");
89
90 let pk_r_fetch = receiver.fetch_keypair().1.into_bytes();
92
93 let pt = crate::message::auth_dec(
95 bundle.apke.private_key(), &sender_pk, &envelope.ct_apke, NR_ID, &pk_r_fetch, )
101 .expect("SD-APKE AuthDec failed");
102
103 Plaintext::from_bytes(&pt).unwrap()
104}
105
106pub fn compute_fetch_challenges<R: RngCore + CryptoRng>(
112 rng: &mut R,
113 store: &ServerMessageStore,
114 total_responses: usize,
115) -> Vec<FetchResponse> {
116 let mut responses = Vec::with_capacity(total_responses);
117
118 let eph_sk = generate_random_scalar(&mut *rng).expect("Want dh scalar");
120
121 for entry in store.keys() {
122 let message_id = entry.as_bytes();
123 let envelope = store.get(entry).expect("missing message for this uuid");
124
125 let shared_secret = dh_shared_secret(&DHPublicKey::from_bytes(envelope.mgdh), eph_sk)
127 .expect("Need 3-party dh shared secret");
128 let enc_mid = encrypt_message_id(&shared_secret.into_bytes(), message_id, rng).unwrap();
129
130 let kmid = enc_mid
131 .try_into()
132 .expect(&format!("Need {} bytes", LEN_KMID));
133
134 let pmgdh = dh_shared_secret(&DHPublicKey::from_bytes(envelope.mgdh_pubkey), eph_sk)
137 .expect("Need pmgdh");
138
139 responses.push(FetchResponse {
140 enc_id: kmid,
141 pmgdh: pmgdh.into_bytes(),
142 });
143
144 if responses.len() == total_responses {
146 break;
147 }
148 }
149
150 while responses.len() < total_responses {
152 let mut pad_kmid: [u8; LEN_KMID] = [0u8; LEN_KMID];
153 rng.fill_bytes(&mut pad_kmid);
154
155 let mut pad_pmgdh: [u8; LEN_DH_ITEM] = [0u8; LEN_DH_ITEM];
156 rng.fill_bytes(&mut pad_pmgdh);
157
158 responses.push(FetchResponse {
159 enc_id: pad_kmid,
160 pmgdh: pad_pmgdh,
161 });
162 }
163 responses
164}
165
166pub fn solve_fetch_challenges<S: UserSecret>(
169 recipient: &S,
170 challenges: &[FetchResponse],
171) -> Vec<Uuid> {
172 let mut message_ids: Vec<Uuid> = Vec::new();
173
174 for chall in challenges.iter() {
175 let maybe_kmid_secret = dh_shared_secret(
177 &DHPublicKey::from_bytes(chall.pmgdh),
178 recipient.fetch_keypair().0.clone().into_bytes(),
179 )
180 .expect("Need 3-party DH (scalarmult) on pmgdh");
181
182 match decrypt_message_id(&maybe_kmid_secret.into_bytes(), &chall.enc_id) {
185 Ok(message_id_bytes) => {
186 let uuid = Uuid::from_slice(&message_id_bytes)
187 .expect("Need uuid from decrypted message_id_bytes");
190
191 message_ids.push(uuid);
192 }
193 Err(_) => {
194 }
197 }
198 }
199 message_ids
200}
201
202pub fn build_message(sender: &impl UserPublic, message: Vec<u8>) -> Plaintext {
206 let mut fetch_pk = [0u8; LEN_DH_ITEM];
207 fetch_pk.copy_from_slice(&sender.fetch_pk().clone().into_bytes());
208
209 let mut reply_key_pq_hybrid = [0u8; LEN_XWING_ENCAPS_KEY];
210 reply_key_pq_hybrid.copy_from_slice(sender.message_metadata_pk().as_bytes());
211
212 Plaintext {
213 sender_fetch_key: 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, 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 mut rng: 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(&mut 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,
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() + LEN_DH_ITEM + LEN_XWING_ENCAPS_KEY
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 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 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 challenges = compute_fetch_challenges(&mut rng, &store.get_messages(), 2);
324
325 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
326
327 assert_eq!(solved_ids.len(), 1);
328 assert_eq!(solved_ids[0], message_id);
329 }
330
331 #[test]
332 fn test_wrong_recipient_cannot_decrypt_challenge() {
333 let mut rng = setup_rng();
334
335 let source = Source::new(&mut rng);
336 let journalist = Journalist::new(&mut rng, 2);
337
338 let wrong_journalist = Journalist::new(&mut rng, 2);
339
340 let journalist_public = journalist.public(0);
342
343 let msg = b"Fetch this message";
344 let plaintext = build_message(&source.public(), msg.to_vec());
345 let envelope = encrypt(&mut rng, &source, &plaintext, &journalist_public);
346
347 let mut store: ServerStorage = ServerStorage::new();
348 let message_id = store.deterministic_uuid(&mut rng);
349
350 store.add_message(message_id, envelope);
351
352 let challenges = compute_fetch_challenges(&mut rng, &store.get_messages(), 2);
353
354 let solved_ids = solve_fetch_challenges(&journalist, &challenges);
355
356 let solved_ids_miss = solve_fetch_challenges(&wrong_journalist, &challenges);
357
358 assert_eq!(solved_ids.len(), 1);
359 assert_eq!(solved_ids[0], message_id);
360 assert_eq!(solved_ids_miss.len(), 0);
361 }
362
363 #[test]
364 fn test_encrypt_decrypt_journalist_only() {
365 let mut rng = setup_rng();
366
367 let journalist = Journalist::new(&mut rng, 2);
368 let j2 = Journalist::new(&mut rng, 2);
369
370 let msg = "Test message".as_bytes().to_vec();
371
372 assert_encrypt_decrypt(
373 rng,
374 &journalist.public(0),
375 &journalist,
376 &j2.public(0),
377 &j2,
378 msg,
379 );
380 }
381}