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.keys.0;
79
80 // Verify journalist self-signature over their own pubkeys.
81 journalist_signing_key
82 .verify(
83 request.enrollment.bundle.as_bytes(),
84 &request.enrollment.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.keys.0.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, fetch_pk, reply_apke_pk, selfsig, signed_longterm_key_bytes, nr_signature) =
186 entry.clone();
187 journalists.push(JournalistLongTermView {
188 vk,
189 fetch_pk,
190 reply_apke_pk,
191 signed_longterm_key_bytes,
192 selfsig,
193 nr_signature,
194 });
195 }
196
197 WelcomeBundle {
198 newsroom_verifying_key,
199 fpf_sig,
200 journalists,
201 }
202 }
203
204 pub fn handle_journalist_ephemeral_keys<R: RngCore + CryptoRng>(
205 &mut self,
206 rng: &mut R,
207 ) -> Vec<JournalistEphemeralKeys> {
208 let mut responses = Vec::new();
209
210 let journalist_ephemeral_keys = self.storage.get_all_ephemeral_keys(rng);
211
212 for (journalist_id, ephemeral_bundle) in journalist_ephemeral_keys.iter() {
213 // TODO: Do something better than expect here
214 let entry = self
215 .storage
216 .get_journalists()
217 .get(journalist_id)
218 .expect("Journalist should exist in storage")
219 .clone();
220 let vk = entry.0;
221
222 responses.push(JournalistEphemeralKeys {
223 vk,
224 ephemeral: ephemeral_bundle.clone(),
225 });
226 }
227
228 responses
229 }
230
231 /// Handle message submission (step 6 for sources, step 9 for journalists)
232 pub fn handle_message_submit<R: RngCore + CryptoRng>(
233 &mut self,
234 message: Envelope,
235 rng: &mut R,
236 ) -> Result<Uuid, Error> {
237 // Generate a random message ID
238 let message_id = self.storage.deterministic_uuid(rng);
239
240 // Store the message with the generated ID
241 self.storage.add_message(message_id, message);
242
243 Ok(message_id)
244 }
245
246 /// Compute "hints"/challenges for message id fetch request (step 7)
247 pub fn handle_request_challenges<R: RngCore + CryptoRng>(
248 &self,
249 _request: MessageChallengeFetchRequest,
250 rng: &mut R,
251 ) -> Result<MessageChallengeFetchResponse, Error> {
252 let total_challenges: usize = primitives::MESSAGE_ID_FETCH_SIZE;
253 let entries: Vec<_> = self
254 .storage
255 .get_messages()
256 .iter()
257 .take(total_challenges)
258 .map(|(uuid, envelope)| (*uuid.as_bytes(), envelope.clone()))
259 .collect();
260 let chall = compute_fetch_challenges(rng, &entries, total_challenges);
261
262 Ok(MessageChallengeFetchResponse {
263 count: total_challenges,
264 messages: chall,
265 })
266 }
267
268 /// Handle message ID fetch request (step 7)
269 ///
270 /// TODO: Nothing here prevents someone from requesting messages
271 /// that aren't theirs? Should request messages have a signature?
272 // #[deprecated] // "use compute_fetch_challenges"
273 // pub fn handle_message_id_fetch<R: RngCore + CryptoRng>(
274 // &self,
275 // _request: MessageChallengeFetchRequest,
276 // rng: &mut R,
277 // ) -> Result<MessageChallengeFetchResponse, Error> {
278 // let messages = self.storage.get_messages();
279 // let message_count = messages.len();
280 // // Fixed response size to prevent traffic analysis
281
282 // let mut q_entries = Vec::new();
283 // let mut cid_entries = Vec::new();
284
285 // // Process real messages
286 // for (message_id, message) in messages.iter() {
287 // let y = generate_random_scalar(rng).expect("Failed to generate random scalar");
288
289 // // k_i = DH(Z_i, y)
290 // let z_public_key = dh_public_key_from_scalar(
291 // message.dh_share_z.clone().try_into().unwrap_or([0u8; 32]),
292 // );
293 // let k_i = dh_shared_secret(&z_public_key, y)?.into_bytes();
294
295 // // Q_i = DH(X_i, y)
296 // let x_public_key = dh_public_key_from_scalar(
297 // message.dh_share_x.clone().try_into().unwrap_or([0u8; 32]),
298 // );
299 // let q_i = dh_shared_secret(&x_public_key, y)?.into_bytes();
300
301 // // ID: cid_i = Enc(k_i, id_i)
302 // let message_id_bytes = message_id.as_bytes().to_vec();
303 // let cid_i =
304 // encrypt_message_id(&k_i, &message_id_bytes).expect("Failed to encrypt message ID");
305
306 // q_entries.push(q_i.to_vec());
307 // cid_entries.push(cid_i);
308 // }
309
310 // // Fill remaining slots with random data
311 // while q_entries.len() < MESSAGE_ID_FETCH_SIZE {
312 // // Generate random Q_i that matches the structure of real Q_i
313 // // Real Q_i = DH(X_i, y), so random Q_i should also be a DH shared secret
314 // let random_y = generate_random_scalar(rng).expect("Failed to generate random scalar");
315 // let random_x = generate_random_scalar(rng).expect("Failed to generate random scalar");
316 // let random_x_pub = dh_public_key_from_scalar(random_x);
317 // let random_q = dh_shared_secret(&random_x_pub, random_y)
318 // .map_err(|_| anyhow!("failed to construct shared secret"))?
319 // .into_bytes();
320
321 // // Generate random cid by encrypting a random UUID
322 // // This ensures indistinguishability from real cid_i
323 // let random_uuid = Uuid::new_v4();
324 // let random_key = generate_random_scalar(rng).expect("Failed to generate random key");
325 // let random_cid =
326 // crate::primitives::encrypt_message_id(&random_key, random_uuid.as_bytes())
327 // .expect("Failed to encrypt random UUID");
328
329 // q_entries.push(random_q.to_vec());
330 // cid_entries.push(random_cid);
331 // }
332
333 // // Shuffle the entries to hide which are real vs random
334 // // Zip the arrays together, shuffle, then unzip
335 // let mut pairs: Vec<_> = q_entries.into_iter().zip(cid_entries).collect();
336
337 // let shuffled = Self::shuffle_not_for_prod(&mut pairs)
338 // .expect("Need shuffled list")
339 // .to_vec();
340
341 // // Unzip back into separate arrays
342 // let (q_entries, cid_entries): (Vec<_>, Vec<_>) = shuffled.into_iter().unzip();
343
344 // Ok(MessageChallengeFetchResponse {
345 // count: MESSAGE_ID_FETCH_SIZE,
346 // messages: q_entries.into_iter().zip(cid_entries).collect(),
347 // })
348 // }
349
350 // /// Shuffle challenges so that real and decoys are interspersed.
351 // /// Note: not a true random shuffle, toybox impl only
352 // pub fn shuffle_not_for_prod<'a>(
353 // vec: &'a mut Vec<(Vec<u8>, Vec<u8>)>,
354 // ) -> Option<&'a mut [(Vec<u8>, Vec<u8>)]> {
355 // if vec.is_empty() {
356 // return None;
357 // }
358
359 // let len = vec.len();
360
361 // // https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
362 // for i in (0..len - 1).rev() {
363 // // not for prod: modulo bias
364 // let new_index = getrandom::u32().unwrap() as usize % i;
365 // vec.swap(i, new_index);
366 // }
367
368 // Some(vec.as_mut_slice())
369 // }
370
371 /// Handle message fetch request (step 8/10)
372 pub fn handle_message_fetch(&self, request: MessageFetchRequest) -> Option<Envelope> {
373 self.storage
374 .get_messages()
375 .get(&request.message_id)
376 .cloned()
377 }
378}