securedrop_protocol_minimal/server.rs
1//! Server-side protocol implementation
2//!
3//! This module implements the server-side handling of SecureDrop protocol steps 5-10.
4
5use alloc::vec::Vec;
6use anyhow::Error;
7use rand_core::{CryptoRng, RngCore};
8use uuid::Uuid;
9
10use crate::Envelope;
11use crate::encrypt_decrypt::compute_fetch_challenges;
12use crate::keys::NewsroomKeyPair;
13use crate::primitives;
14use crate::sign::{FpfOnNewsroom, NewsroomOnJournalist, Signature, VerifyingKey};
15use crate::storage::ServerStorage;
16use crate::wire::core::{
17 JournalistEphemeralKeys, JournalistLongTermView, MessageChallengeFetchRequest,
18 MessageChallengeFetchResponse, MessageFetchRequest, WelcomeBundle,
19};
20use crate::wire::setup::{
21 JournalistEphemeralKeyRequest, JournalistSetupRequest, JournalistSetupResponse,
22 NewsroomSetupRequest,
23};
24
25/// Server session for handling source requests
26#[derive(Default)]
27pub struct Server {
28 storage: ServerStorage,
29 newsroom_keys: Option<NewsroomKeyPair>,
30 /// Signature from FPF over the newsroom keys
31 signature: Option<Signature<FpfOnNewsroom>>,
32}
33
34impl Server {
35 /// Create a new server session
36 ///
37 /// TODO: Load newsroom keys from storage if they exist.
38 pub fn new() -> Self {
39 Self::default()
40 }
41
42 /// Generate a new newsroom setup request.
43 ///
44 /// This creates a newsroom key pair, stores it in the server storage,
45 /// and returns a setup request that can be sent to FPF for signing.
46 ///
47 /// TODO: The caller should persist these keys to disk.
48 pub fn create_newsroom_setup_request<R: RngCore + CryptoRng>(
49 &mut self,
50 rng: &mut R,
51 ) -> Result<NewsroomSetupRequest, Error> {
52 let newsroom_keys = NewsroomKeyPair::new(rng)?;
53 let newsroom_vk = newsroom_keys.verifying_key();
54
55 // Store the newsroom keys in the session for later use (e.g., signing journalist keys)
56 self.newsroom_keys = Some(newsroom_keys);
57
58 Ok(NewsroomSetupRequest {
59 newsroom_verifying_key: newsroom_vk,
60 })
61 }
62
63 /// Setup a journalist. This corresponds to step 3.1 in the spec.
64 ///
65 /// The newsroom then signs the bundle of journalist public keys.
66 ///
67 /// TODO: There is a manual verification step here, so the caller should
68 /// instruct the user to stop, verify the fingerprint out of band, and
69 /// then proceed. The caller should also persist the fingerprint and signature
70 /// in its local data store.
71 ///
72 /// TODO(later): How to handle signing when offline? (Not relevant for benchmarking)
73 pub fn setup_journalist(
74 &mut self,
75 request: JournalistSetupRequest,
76 ) -> Result<JournalistSetupResponse, Error> {
77 // Get enrollment key from the request
78 let journalist_signing_key = request.enrollment.verification_key;
79
80 // Verify journalist self-signature over their own pubkeys.
81 journalist_signing_key
82 .verify(
83 &request.enrollment.bundle.bundle_bytes(),
84 &request.enrollment.bundle.selfsig,
85 )
86 .map_err(|_| anyhow::anyhow!("Invalid signature on longterm keys"))?;
87
88 // Sign the journalist's verifying key.
89 let verifying_key_bytes = request.enrollment.verification_key.into_bytes();
90 let newsroom_keys = self
91 .newsroom_keys
92 .as_ref()
93 .ok_or_else(|| anyhow::anyhow!("Newsroom keys not found in session"))?;
94 let newsroom_signature: Signature<NewsroomOnJournalist> =
95 newsroom_keys.sign(&verifying_key_bytes);
96
97 // Insert journalist keys into storage
98 let _journalist_id = self
99 .storage
100 .add_journalist(request.enrollment, newsroom_signature.clone());
101
102 Ok(JournalistSetupResponse {
103 sig: newsroom_signature,
104 })
105 }
106
107 /// Handle journalist ephemeral key replenishment. This corresponds to step 3.2 in the spec.
108 ///
109 /// The journalist sends ephemeral keys signed by their signing key, and the server
110 /// verifies the signature and stores the ephemeral keys.
111 ///
112 /// # Errors
113 ///
114 /// Returns an error if the journalist is not found in storage, or if any bundle
115 /// signature fails verification.
116 pub fn handle_ephemeral_key_request(
117 &mut self,
118 request: JournalistEphemeralKeyRequest,
119 ) -> Result<(), Error> {
120 // Look up the journalist by their verifying key
121 let journalist_id = self
122 .storage
123 .find_journalist_by_verifying_key(&request.verifying_key)
124 .ok_or_else(|| anyhow::anyhow!("Journalist not found in storage"))?;
125
126 // TODO: more efficient way than verifying each signature!
127 // Verify each ephemeral bundle signature.
128 request
129 .bundles
130 .iter()
131 .try_for_each(|k| request.verifying_key.verify(&k.0.as_bytes(), &k.1))
132 .map_err(|_| anyhow::anyhow!("Invalid signature on ephemeral keys"))?;
133
134 // Store the ephemeral keys for the journalist
135 self.storage
136 .add_ephemeral_keys(journalist_id, request.bundles);
137
138 Ok(())
139 }
140
141 /// Returns the newsroom verifying key, if one has been generated.
142 pub fn newsroom_verifying_key(&self) -> Option<VerifyingKey> {
143 self.newsroom_keys.as_ref().map(|keys| keys.verifying_key())
144 }
145
146 /// Set the FPF signature for the newsroom
147 pub fn set_fpf_signature(&mut self, signature: Signature<FpfOnNewsroom>) {
148 self.signature = Some(signature);
149 }
150
151 /// Get the ephemeral key count for a journalist
152 pub fn ephemeral_keys_count(&self, journalist_id: Uuid) -> usize {
153 self.storage.ephemeral_keys_count(journalist_id)
154 }
155
156 /// Check if a journalist has ephemeral keys available
157 pub fn has_ephemeral_keys(&self, journalist_id: Uuid) -> bool {
158 self.storage.has_ephemeral_keys(journalist_id)
159 }
160
161 /// Find journalist ID by verifying key
162 pub fn find_journalist_id(&self, verifying_key: &VerifyingKey) -> Option<Uuid> {
163 self.storage.find_journalist_by_verifying_key(verifying_key)
164 }
165
166 /// Check if a message exists with the given ID
167 pub fn has_message(&self, message_id: &Uuid) -> bool {
168 self.storage.get_messages().contains_key(message_id)
169 }
170
171 pub fn handle_welcome(&self) -> WelcomeBundle {
172 let newsroom_verifying_key = self
173 .newsroom_keys
174 .as_ref()
175 .expect("Newsroom keys not found")
176 .verifying_key();
177 let fpf_sig = self
178 .signature
179 .as_ref()
180 .expect("FPF signature not found")
181 .clone();
182
183 let mut journalists = Vec::new();
184 for (_id, entry) in self.storage.get_journalists().iter() {
185 let (vk, signed_longterm_key_bundle, nr_signature) = entry.clone();
186 journalists.push(JournalistLongTermView {
187 vk,
188 signed_longterm_key_bundle,
189 nr_signature,
190 });
191 }
192
193 WelcomeBundle {
194 newsroom_verifying_key,
195 fpf_sig,
196 journalists,
197 }
198 }
199
200 pub fn handle_journalist_ephemeral_keys<R: RngCore + CryptoRng>(
201 &mut self,
202 rng: &mut R,
203 ) -> Vec<JournalistEphemeralKeys> {
204 let mut responses = Vec::new();
205
206 let journalist_ephemeral_keys = self.storage.get_all_ephemeral_keys(rng);
207
208 for (journalist_id, ephemeral_bundle) in journalist_ephemeral_keys.iter() {
209 // TODO: Do something better than expect here
210 let entry = self
211 .storage
212 .get_journalists()
213 .get(journalist_id)
214 .expect("Journalist should exist in storage")
215 .clone();
216 let vk = entry.0;
217
218 responses.push(JournalistEphemeralKeys {
219 vk,
220 ephemeral: ephemeral_bundle.clone(),
221 });
222 }
223
224 responses
225 }
226
227 /// Handle message submission (step 6 for sources, step 9 for journalists)
228 pub fn handle_message_submit<R: RngCore + CryptoRng>(
229 &mut self,
230 message: Envelope,
231 rng: &mut R,
232 ) -> Result<Uuid, Error> {
233 // Generate a random message ID
234 let message_id = self.storage.deterministic_uuid(rng);
235
236 // Store the message with the generated ID
237 self.storage.add_message(message_id, message);
238
239 Ok(message_id)
240 }
241
242 /// Compute "hints"/challenges for message id fetch request (step 7)
243 pub fn handle_request_challenges<R: RngCore + CryptoRng>(
244 &self,
245 _request: MessageChallengeFetchRequest,
246 rng: &mut R,
247 ) -> Result<MessageChallengeFetchResponse, Error> {
248 let total_challenges: usize = primitives::MESSAGE_ID_FETCH_SIZE;
249 let entries: Vec<_> = self
250 .storage
251 .get_messages()
252 .iter()
253 .take(total_challenges)
254 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
255 .collect();
256 let chall = compute_fetch_challenges(rng, &entries, total_challenges);
257
258 Ok(MessageChallengeFetchResponse {
259 count: total_challenges,
260 messages: chall,
261 })
262 }
263
264 /// Handle message ID fetch request (step 7)
265 ///
266 /// TODO: Nothing here prevents someone from requesting messages
267 /// that aren't theirs? Should request messages have a signature?
268 // #[deprecated] // "use compute_fetch_challenges"
269 // pub fn handle_message_id_fetch<R: RngCore + CryptoRng>(
270 // &self,
271 // _request: MessageChallengeFetchRequest,
272 // rng: &mut R,
273 // ) -> Result<MessageChallengeFetchResponse, Error> {
274 // let messages = self.storage.get_messages();
275 // let message_count = messages.len();
276 // // Fixed response size to prevent traffic analysis
277
278 // let mut q_entries = Vec::new();
279 // let mut cid_entries = Vec::new();
280
281 // // Process real messages
282 // for (message_id, message) in messages.iter() {
283 // let y = generate_random_scalar(rng).expect("Failed to generate random scalar");
284
285 // // k_i = DH(Z_i, y)
286 // let z_public_key = dh_public_key_from_scalar(
287 // message.dh_share_z.clone().try_into().unwrap_or([0u8; 32]),
288 // );
289 // let k_i = dh_shared_secret(&z_public_key, y)?.into_bytes();
290
291 // // Q_i = DH(X_i, y)
292 // let x_public_key = dh_public_key_from_scalar(
293 // message.dh_share_x.clone().try_into().unwrap_or([0u8; 32]),
294 // );
295 // let q_i = dh_shared_secret(&x_public_key, y)?.into_bytes();
296
297 // // ID: cid_i = Enc(k_i, id_i)
298 // let message_id_bytes = message_id.as_bytes().to_vec();
299 // let cid_i =
300 // encrypt_message_id(&k_i, &message_id_bytes).expect("Failed to encrypt message ID");
301
302 // q_entries.push(q_i.to_vec());
303 // cid_entries.push(cid_i);
304 // }
305
306 // // Fill remaining slots with random data
307 // while q_entries.len() < MESSAGE_ID_FETCH_SIZE {
308 // // Generate random Q_i that matches the structure of real Q_i
309 // // Real Q_i = DH(X_i, y), so random Q_i should also be a DH shared secret
310 // let random_y = generate_random_scalar(rng).expect("Failed to generate random scalar");
311 // let random_x = generate_random_scalar(rng).expect("Failed to generate random scalar");
312 // let random_x_pub = dh_public_key_from_scalar(random_x);
313 // let random_q = dh_shared_secret(&random_x_pub, random_y)
314 // .map_err(|_| anyhow!("failed to construct shared secret"))?
315 // .into_bytes();
316
317 // // Generate random cid by encrypting a random UUID
318 // // This ensures indistinguishability from real cid_i
319 // let random_uuid = Uuid::new_v4();
320 // let random_key = generate_random_scalar(rng).expect("Failed to generate random key");
321 // let random_cid =
322 // crate::primitives::encrypt_message_id(&random_key, random_uuid.as_bytes())
323 // .expect("Failed to encrypt random UUID");
324
325 // q_entries.push(random_q.to_vec());
326 // cid_entries.push(random_cid);
327 // }
328
329 // // Shuffle the entries to hide which are real vs random
330 // // Zip the arrays together, shuffle, then unzip
331 // let mut pairs: Vec<_> = q_entries.into_iter().zip(cid_entries).collect();
332
333 // let shuffled = Self::shuffle_not_for_prod(&mut pairs)
334 // .expect("Need shuffled list")
335 // .to_vec();
336
337 // // Unzip back into separate arrays
338 // let (q_entries, cid_entries): (Vec<_>, Vec<_>) = shuffled.into_iter().unzip();
339
340 // Ok(MessageChallengeFetchResponse {
341 // count: MESSAGE_ID_FETCH_SIZE,
342 // messages: q_entries.into_iter().zip(cid_entries).collect(),
343 // })
344 // }
345
346 // /// Shuffle challenges so that real and decoys are interspersed.
347 // /// Note: not a true random shuffle, toybox impl only
348 // pub fn shuffle_not_for_prod<'a>(
349 // vec: &'a mut Vec<(Vec<u8>, Vec<u8>)>,
350 // ) -> Option<&'a mut [(Vec<u8>, Vec<u8>)]> {
351 // if vec.is_empty() {
352 // return None;
353 // }
354
355 // let len = vec.len();
356
357 // // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
358 // for i in (0..len - 1).rev() {
359 // // not for prod: modulo bias
360 // let new_index = getrandom::u32().unwrap() as usize % i;
361 // vec.swap(i, new_index);
362 // }
363
364 // Some(vec.as_mut_slice())
365 // }
366
367 /// Handle message fetch request (step 8/10)
368 pub fn handle_message_fetch(&self, request: MessageFetchRequest) -> Option<Envelope> {
369 self.storage
370 .get_messages()
371 .get(&request.message_id)
372 .cloned()
373 }
374}