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