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