Skip to main content

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}