Skip to main content

securedrop_protocol_minimal/
journalist.rs

1use crate::VerifyingKey;
2use crate::api::Client;
3use crate::message::{MessageKeyPair, MessagePublicKey, keygen as message_keygen};
4use crate::metadata::{MetadataPublicKey, keygen as metadata_keygen};
5use crate::primitives::x25519::DHPrivateKey;
6use crate::primitives::x25519::DHPublicKey;
7use crate::primitives::x25519::generate_dh_keypair;
8use crate::sign::{JournalistEphemeralKey, JournalistLongTermKey, Signature, SigningKey};
9use alloc::vec::Vec;
10use rand_core::{CryptoRng, RngCore};
11
12use crate::ciphertext::Plaintext;
13use crate::constants::*;
14use crate::keys::*;
15use crate::traits::{Enrollable, JournalistPublic, RestrictedApi, UserPublic, UserSecret};
16
17// caution: do not re-export!
18use crate::sealed;
19impl sealed::Sealed for Journalist {}
20impl RestrictedApi for Journalist {}
21
22/// Journalists: ingredients.
23/// Journalists have a signing/verifying key, a reply key,
24/// a fetch key, and a collection of one-time signed key bundles
25pub struct Journalist {
26    signing_key: SigningKeyPair,
27    fetch_key: DhFetchKeyPair,
28    message_keys: Vec<SignedMessageKeyBundle>,
29    /// Long-term SD-APKE key tuple `(sk_J^APKE, pk_J^APKE)`
30    reply_apke: MessageKeyPair,
31    self_signature: Signature<JournalistLongTermKey>,
32    signed_longterm_key_bytes: SignedLongtermPubKeyBytes,
33    session_storage: SessionStorage,
34}
35
36// Public-facing representation of a journalist
37// used to send them a message
38pub struct JournalistPublicView {
39    vk: VerifyingKey,
40    fetch_pk: DHPublicKey,
41    reply_apke_pk: MessagePublicKey,
42    signed_longterm_key_bytes: SignedLongtermPubKeyBytes,
43    selfsig: Signature<JournalistLongTermKey>,
44    kb: SignedKeyBundlePublic,
45}
46
47impl JournalistPublicView {
48    pub fn new(
49        vk: VerifyingKey,
50        fetch: DHPublicKey,
51        reply_apke: MessagePublicKey,
52        selfsig: Signature<JournalistLongTermKey>,
53        signed_longterm_key_bytes: SignedLongtermPubKeyBytes,
54        kb: SignedKeyBundlePublic,
55    ) -> Self {
56        Self {
57            vk,
58            fetch_pk: fetch,
59            reply_apke_pk: reply_apke,
60            selfsig,
61            signed_longterm_key_bytes,
62            kb,
63        }
64    }
65}
66
67impl UserPublic for JournalistPublicView {
68    fn fetch_pk(&self) -> &DHPublicKey {
69        &self.fetch_pk
70    }
71
72    fn message_auth_pk(&self) -> &MessagePublicKey {
73        &self.reply_apke_pk
74    }
75
76    fn message_metadata_pk(&self) -> &MetadataPublicKey {
77        &self.kb.0.metadata_pk
78    }
79
80    fn message_enc_pk(&self) -> &MessagePublicKey {
81        &self.kb.0.apke_pk
82    }
83}
84
85impl JournalistPublic for JournalistPublicView {
86    fn verifying_key(&self) -> &VerifyingKey {
87        &self.vk
88    }
89
90    fn self_signature(&self) -> &Signature<JournalistLongTermKey> {
91        &self.selfsig
92    }
93
94    fn signed_keybytes(&self) -> &SignedLongtermPubKeyBytes {
95        &self.signed_longterm_key_bytes
96    }
97
98    fn ephemeral_bundle(&self) -> &KeyBundlePublic {
99        &self.kb.0
100    }
101
102    fn ephemeral_signature(&self) -> &Signature<JournalistEphemeralKey> {
103        &self.kb.1
104    }
105}
106
107impl Client for Journalist {
108    fn newsroom_verifying_key(&self) -> Option<&VerifyingKey> {
109        self.session_storage.nr_key.as_ref()
110    }
111
112    fn set_newsroom_verifying_key(&mut self, key: VerifyingKey) {
113        self.session_storage.nr_key = Some(key);
114    }
115}
116
117/// Private, common to all users, implemented for Journalists
118impl UserSecret for Journalist {
119    fn num_bundles(&self) -> usize {
120        self.message_keys.len()
121    }
122
123    fn fetch_keypair(&self) -> (&DHPrivateKey, &DHPublicKey) {
124        (&self.fetch_key.sk, &self.fetch_key.pk)
125    }
126
127    fn message_auth_key(&self) -> &crate::message::MessagePrivateKey {
128        self.reply_apke.private_key()
129    }
130
131    fn message_auth_pk(&self) -> &MessagePublicKey {
132        self.reply_apke.public_key()
133    }
134
135    fn build_message(&self, message: Vec<u8>) -> Plaintext {
136        // TODO: the journalist doesn't attach their own keys,
137        // because the source pulls a fresh set of keys and verifies them
138        // in order to reply. either fill with random bytes or use
139        // another scheme (fixme)
140        Plaintext {
141            sender_fetch_key: [0u8; LEN_DH_ITEM],
142            sender_reply_pubkey_hybrid: [0u8; LEN_XWING_ENCAPS_KEY],
143            msg: message,
144        }
145    }
146
147    fn keybundles(&self) -> Vec<&MessageKeyBundle> {
148        self.message_keys
149            .iter()
150            .map(|signed| &signed.bundle)
151            .collect()
152    }
153}
154
155impl Enrollable for Journalist {
156    fn enroll(&self) -> Enrollment {
157        Enrollment {
158            bundle: self.signed_longterm_key_bytes.clone(),
159            selfsig: self.self_signature,
160            keys: (
161                self.signing_key.pk,
162                self.fetch_key.pk.clone(),
163                self.reply_apke.public_key().clone(),
164            ),
165        }
166    }
167
168    fn signed_keybundles(&self) -> Vec<SignedKeyBundlePublic> {
169        fn extract_public_bundle(signed: &SignedMessageKeyBundle) -> SignedKeyBundlePublic {
170            (signed.bundle.public(), signed.selfsig)
171        }
172
173        self.message_keys
174            .iter()
175            .map(extract_public_bundle)
176            .collect()
177    }
178
179    fn signing_key(&self) -> &VerifyingKey {
180        &self.signing_key.pk
181    }
182}
183
184impl Journalist {
185    pub fn new<R: RngCore + CryptoRng>(rng: &mut R, num_keybundles: usize) -> Self {
186        let mut key_bundles: Vec<SignedMessageKeyBundle> = Vec::with_capacity(num_keybundles);
187
188        let signing_key = SigningKey::new(&mut *rng).expect("Signing keygen failed");
189        let verifying_key = signing_key.vk;
190
191        let (sk_fetch, pk_fetch) =
192            generate_dh_keypair(&mut *rng).expect("DH Keygen (Fetch) failed");
193
194        let reply_apke = message_keygen(&mut *rng).expect("SD-APKE Keygen (Reply) failed");
195
196        // Self-sign long-term pubkeys (for enrollment).
197        // Covers pk_J^APKE = (pk_J^AKEM, pk_J^PQ) and pk_J^fetch
198        let selfsigned_pubkeys =
199            SignedLongtermPubKeyBytes::from_keys(reply_apke.public_key(), &pk_fetch);
200        let self_signature: Signature<JournalistLongTermKey> =
201            signing_key.sign(selfsigned_pubkeys.as_bytes());
202
203        // Generate one-time/short-lived keybundles.
204        for _ in 0..num_keybundles {
205            let apke_kp = message_keygen(rng).expect("SD-APKE keygen (ephemeral) failed");
206            let metadata_kp = metadata_keygen(rng).expect("Failed to generate metadata keys");
207
208            let bundle = MessageKeyBundle::new(apke_kp, metadata_kp);
209
210            let pubkey_bytes = bundle.public().as_bytes();
211            let selfsig: Signature<JournalistEphemeralKey> = signing_key.sign(&pubkey_bytes);
212
213            key_bundles.push(SignedMessageKeyBundle { bundle, selfsig });
214        }
215        assert_eq!(key_bundles.len(), num_keybundles);
216
217        let session_storage = SessionStorage {
218            fpf_key: None,
219            nr_key: None,
220            fpf_signature: None,
221        };
222
223        Self {
224            signing_key: KeyPair {
225                sk: signing_key,
226                pk: verifying_key,
227            },
228            fetch_key: KeyPair {
229                sk: sk_fetch,
230                pk: pk_fetch,
231            },
232            reply_apke,
233            message_keys: key_bundles,
234            self_signature,
235            signed_longterm_key_bytes: selfsigned_pubkeys,
236            session_storage,
237        }
238    }
239
240    pub fn public(&self, idx: usize) -> JournalistPublicView {
241        let kb = self.message_keys.get(idx).expect("Bad index");
242        JournalistPublicView::new(
243            self.signing_key.pk,
244            self.fetch_key.pk.clone(),
245            self.reply_apke.public_key().clone(),
246            self.self_signature,
247            self.signed_longterm_key_bytes.clone(),
248            (kb.bundle.public(), kb.selfsig),
249        )
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256    use crate::Enrollable;
257    use rand_chacha::ChaCha20Rng;
258    use rand_core::SeedableRng;
259
260    #[test]
261    fn test_journalist_setup() {
262        let mut rng = ChaCha20Rng::seed_from_u64(666);
263
264        let journalist = Journalist::new(&mut rng, 5);
265        assert_eq!(journalist.message_keys.len(), 5);
266        let skb: Vec<SignedKeyBundlePublic> = journalist.signed_keybundles();
267        assert_eq!(journalist.message_keys.len(), skb.len());
268
269        let kbs: Vec<&MessageKeyBundle> = journalist.keybundles();
270        assert_eq!(kbs.len(), journalist.message_keys.len());
271
272        for i in 0..kbs.len() {
273            assert_eq!(
274                journalist.message_keys[i]
275                    .bundle
276                    .apke
277                    .public_key()
278                    .as_bytes(),
279                kbs[i].apke.public_key().as_bytes()
280            );
281            assert_eq!(
282                journalist.message_keys[i]
283                    .bundle
284                    .metadata_kp
285                    .private_key()
286                    .as_bytes(),
287                kbs[i].metadata_kp.private_key().as_bytes()
288            );
289            assert_eq!(
290                journalist.message_keys[i]
291                    .bundle
292                    .metadata_kp
293                    .public_key()
294                    .as_bytes(),
295                kbs[i].metadata_kp.public_key().as_bytes()
296            );
297        }
298    }
299
300    #[test]
301    fn test_journalist_enroll_selfsig() {
302        let mut rng = ChaCha20Rng::seed_from_u64(666);
303
304        let journalist = Journalist::new(&mut rng, 5);
305        let e = journalist.enroll();
306
307        journalist
308            .signing_key()
309            .verify(e.bundle.as_bytes(), &e.selfsig)
310            .expect("Need correct enrollment sig");
311    }
312}