securedrop_protocol_minimal/
source.rs1use crate::VerifyingKey;
2use crate::api::Client;
3use crate::message::{MessagePublicKey, deterministic_keygen as kgen_deterministic_message};
4use crate::metadata::{MetadataPublicKey, deterministic_keygen as kgen_deterministic_metadata};
5use crate::primitives::x25519::DHPrivateKey;
6use crate::primitives::x25519::DHPublicKey;
7use crate::primitives::x25519::deterministic_dh_keygen;
8use alloc::vec::Vec;
9use argon2::{Algorithm, Argon2, Params, Version};
10use rand_core::{CryptoRng, RngCore};
11
12use crate::ciphertext::Plaintext;
13use crate::keys::*;
14use crate::primitives::x25519::DH_PUBLIC_KEY_LEN;
15use crate::primitives::xwing::XWING_PUBLIC_KEY_LEN;
16use crate::traits::{UserPublic, UserSecret};
17
18use crate::sealed;
20impl sealed::Sealed for Source {}
21
22const SOURCE_PBKDF_SALT: &[u8] = b"securedrop-source-v1";
26
27pub struct Source {
34 fetch_key: DhFetchKeyPair,
35 message_keys: MessageKeyBundle,
36 passphrase: Vec<u8>,
37 session: SessionStorage,
38}
39
40impl core::fmt::Debug for Source {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 f.debug_struct("Source").finish_non_exhaustive()
44 }
45}
46
47#[derive(Debug, Clone)]
49pub struct SourcePublicView {
50 fetch_pk: DHPublicKey,
51 apke_pk: MessagePublicKey,
52 message_pks: KeyBundlePublic,
53}
54
55impl UserPublic for SourcePublicView {
56 fn fetch_pk(&self) -> &DHPublicKey {
57 &self.fetch_pk
58 }
59
60 fn message_auth_pk(&self) -> &MessagePublicKey {
61 &self.apke_pk
62 }
63
64 fn message_metadata_pk(&self) -> &MetadataPublicKey {
65 &self.message_pks.metadata_pk
66 }
67
68 fn message_enc_pk(&self) -> &MessagePublicKey {
69 &self.message_pks.apke_pk
70 }
71}
72
73impl Client for Source {
74 fn newsroom_verifying_key(&self) -> Option<&VerifyingKey> {
75 self.session.nr_key.as_ref()
76 }
77
78 fn set_newsroom_verifying_key(&mut self, key: VerifyingKey) {
79 self.session.nr_key = Some(key);
80 }
81}
82
83impl UserSecret for Source {
85 fn num_bundles(&self) -> usize {
86 1
87 }
88
89 fn fetch_keypair(&self) -> (&DHPrivateKey, &DHPublicKey) {
90 (&self.fetch_key.sk, &self.fetch_key.pk)
91 }
92
93 fn message_auth_key(&self) -> &crate::message::MessagePrivateKey {
94 self.message_keys.apke.private_key()
95 }
96
97 fn message_auth_pk(&self) -> &crate::message::MessagePublicKey {
98 self.message_keys.apke.public_key()
99 }
100
101 fn build_message(&self, message: Vec<u8>) -> Plaintext {
102 let mut fetch_pk = [0u8; DH_PUBLIC_KEY_LEN];
103 fetch_pk.copy_from_slice(&self.fetch_key.pk.into_bytes());
104
105 let mut reply_key_pq_hybrid = [0u8; XWING_PUBLIC_KEY_LEN];
106 reply_key_pq_hybrid.copy_from_slice(self.message_keys.metadata_kp.public_key().as_bytes());
107
108 Plaintext {
109 sender_fetch_key: fetch_pk,
110 sender_reply_pubkey_hybrid: reply_key_pq_hybrid,
111 msg: message,
112 }
113 }
114
115 fn keybundles(&self) -> Vec<&MessageKeyBundle> {
116 alloc::vec![&self.message_keys]
117 }
118}
119
120impl Source {
121 pub fn new<R: RngCore + CryptoRng>(mut rng: R) -> Self {
126 let mut passphrase = [0u8; 32];
127 rng.fill_bytes(&mut passphrase);
128 Self::from_passphrase(&passphrase)
129 }
130
131 pub fn passphrase(&self) -> &[u8] {
138 &self.passphrase
139 }
140
141 fn derive_master_key(passphrase: &[u8]) -> [u8; 64] {
146 let params = Params::new(19456, 2, 1, Some(64)).expect("valid Argon2id params");
149 let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
150
151 let mut mk = [0u8; 64];
152 argon2
153 .hash_password_into(passphrase, SOURCE_PBKDF_SALT, &mut mk)
154 .expect("Argon2id master key derivation failed");
155 mk
156 }
157
158 pub fn from_passphrase(passphrase: &[u8]) -> Self {
163 use blake2::{Blake2b, Digest};
164
165 let mk = Self::derive_master_key(passphrase);
166
167 let mut fetch_hasher = Blake2b::<blake2::digest::typenum::U32>::new();
168 fetch_hasher.update(b"sourcefetchkey");
169 fetch_hasher.update(mk);
170 let fetch_result = fetch_hasher.finalize();
171
172 let mut dh_hasher = Blake2b::<blake2::digest::typenum::U32>::new();
176 dh_hasher.update(b"sourceAPKEkey-dh");
177 dh_hasher.update(mk);
178 let dh_result = dh_hasher.finalize();
179
180 let mut kem_hasher = Blake2b::<blake2::digest::typenum::U64>::new();
181 kem_hasher.update(b"sourceAPKEkey-mlkem");
182 kem_hasher.update(mk);
183 let kem_result = kem_hasher.finalize();
184
185 let mut pke_hasher = Blake2b::<blake2::digest::typenum::U32>::new();
186 pke_hasher.update(b"sourcePKEkey");
187 pke_hasher.update(mk);
188 let pke_result = pke_hasher.finalize();
189
190 let (fetch_sk, fetch_pk): (DHPrivateKey, DHPublicKey) =
192 deterministic_dh_keygen(fetch_result.into()).expect("Need Fetch keygen");
193
194 let message_kp = kgen_deterministic_message(dh_result.into(), kem_result.into())
195 .expect("Need SD-APKE keygen");
196
197 let metadata_kp =
198 kgen_deterministic_metadata(pke_result.into()).expect("Need X-Wing keygen");
199
200 let session = SessionStorage {
201 fpf_key: None,
202 nr_key: None,
203 fpf_signature: None,
204 };
205
206 Self {
207 fetch_key: KeyPair {
208 sk: fetch_sk,
209 pk: fetch_pk,
210 },
211 message_keys: MessageKeyBundle::new(message_kp, metadata_kp),
212 passphrase: passphrase.to_vec(),
213 session,
214 }
215 }
216
217 pub fn public(&self) -> SourcePublicView {
219 SourcePublicView {
220 fetch_pk: self.fetch_key.pk,
221 apke_pk: self.message_keys.apke.public_key().clone(),
222 message_pks: self.message_keys.public(),
223 }
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230 use crate::primitives::xwing::XWING_PRIVATE_KEY_LEN;
231 use rand_chacha::ChaCha20Rng;
232 use rand_core::SeedableRng;
233
234 #[test]
235 fn test_initialize_with_passphrase() {
236 let mut rng = ChaCha20Rng::seed_from_u64(666);
238
239 let mut passphrase_bytes: [u8; 32] = [0u8; 32];
240 let _ = &rng.fill_bytes(&mut passphrase_bytes);
241
242 let source1 = Source::from_passphrase(&passphrase_bytes.clone());
243 let source2 = Source::from_passphrase(&passphrase_bytes);
244
245 assert_eq!(
246 source1.passphrase, source2.passphrase,
247 "Expected identical passphrase"
248 );
249
250 assert_eq!(
252 source1.message_keys.apke.public_key().as_bytes(),
253 source2.message_keys.apke.public_key().as_bytes(),
254 "SD-APKE public key should be identical"
255 );
256
257 assert_eq!(
259 source1.message_keys.metadata_kp.public_key().as_bytes(),
260 source2.message_keys.metadata_kp.public_key().as_bytes(),
261 "XWING Encaps Key should be identical"
262 );
263 assert_eq!(
264 source1.message_keys.metadata_kp.private_key().as_bytes(),
265 source2.message_keys.metadata_kp.private_key().as_bytes(),
266 "XWING Decaps Key should be identical"
267 );
268 assert_ne!(
269 source1.message_keys.metadata_kp.private_key().as_bytes(),
270 &[0u8; XWING_PRIVATE_KEY_LEN]
271 );
272 }
273}