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::constants::*;
14use crate::keys::*;
15use crate::traits::{Enrollable, JournalistPublic, RestrictedApi, UserPublic, UserSecret};
16
17use crate::sealed;
19impl sealed::Sealed for Journalist {}
20impl RestrictedApi for Journalist {}
21
22pub struct Journalist {
26 signing_key: SigningKeyPair,
27 fetch_key: DhFetchKeyPair,
28 message_keys: Vec<SignedMessageKeyBundle>,
29 reply_apke: MessageKeyPair,
31 self_signature: Signature<JournalistLongTermKey>,
32 signed_longterm_key_bytes: SignedLongtermPubKeyBytes,
33 session_storage: SessionStorage,
34}
35
36pub 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
117impl 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 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 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 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}