securedrop_protocol_minimal/client/
message.rs1use crate::keys::NR_ID;
2use crate::message::MessagePublicKey;
3use crate::metadata;
4use crate::primitives::provider::constants::{LEN_KMID, LEN_MESSAGE_ID};
5use crate::primitives::ristretto255::{
6 DHPublicKey, dh_shared_secret, generate_dh_keypair, generate_random_scalar,
7 random_dh_public_key,
8};
9use crate::{Envelope, FetchResponse, MessageKeyBundle, Plaintext, UserPublic, UserSecret};
10use alloc::vec::Vec;
11use rand_core::{CryptoRng, RngCore};
12
13#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
20pub fn encrypt<R, Sender, Recipient>(
21 rng: &mut R,
22 sender: &Sender,
23 plaintext: &Plaintext,
24 recipient: &Recipient,
25) -> Envelope
26where
27 R: RngCore + CryptoRng,
28 Sender: UserSecret + ?Sized,
29 Recipient: UserPublic + ?Sized,
30{
31 let keypair_s = sender.message_auth_keypair();
33 let pk_r = recipient.message_enc_pk();
35 let pk_r_fetch = recipient.fetch_pk();
37
38 let ct_apke = crate::message::auth_enc(
40 rng,
41 keypair_s,
42 pk_r,
43 &plaintext.to_bytes(),
44 NR_ID,
45 pk_r_fetch,
46 )
47 .expect("SD-APKE AuthEnc failed");
48
49 let (hint_esk, hint_epk) = generate_dh_keypair(rng);
52 let hint_sharedsecret: DHPublicKey = dh_shared_secret(recipient.fetch_pk(), &hint_esk);
54
55 let ct_pke = metadata::encrypt(
59 rng,
60 recipient.message_metadata_pk(),
61 &keypair_s.public_key(),
62 )
63 .expect("Valid Keybundle should allow metadata seal");
64
65 Envelope {
66 ct_apke, ct_pke, mgdh_pubkey: hint_epk, mgdh: hint_sharedsecret, }
71}
72
73#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
74pub fn decrypt<U: UserSecret + ?Sized>(receiver: &U, envelope: &Envelope) -> Plaintext {
75 decrypt_with_sender(receiver, envelope).0
76}
77
78#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
81pub fn decrypt_with_sender<U: UserSecret + ?Sized>(
82 receiver: &U,
83 envelope: &Envelope,
84) -> (Plaintext, MessagePublicKey) {
85 let mut found: Option<(&MessageKeyBundle, Vec<u8>)> = None;
88 for &bundle in receiver.keybundles().iter() {
89 if let Ok(m) = metadata::decrypt(bundle.metadata_kp.private_key(), &envelope.ct_pke) {
90 found = Some((bundle, m));
91 }
92 }
93
94 let (bundle, raw_metadata) = found.expect("we should find exactly 1 result");
96
97 let sender_pk = MessagePublicKey::from_bytes(&raw_metadata)
99 .expect("Metadata must contain valid sender APKE key tuple");
100
101 let pk_r_fetch = receiver.fetch_keypair().1;
103
104 let pt = crate::message::auth_dec(
106 bundle.apke.private_key(), &sender_pk, &envelope.ct_apke, NR_ID, pk_r_fetch, )
112 .expect("SD-APKE AuthDec failed");
113
114 (Plaintext::from_bytes(&pt).unwrap(), sender_pk)
115}
116
117#[cfg_attr(hax, hax_lib::fstar::verification_status(lax))]
121pub fn build_message(sender: &impl UserPublic, message: Vec<u8>) -> Plaintext {
122 Plaintext {
123 sender_fetch_key: *sender.fetch_pk(),
124 sender_reply_pubkey_hybrid: sender.message_metadata_pk().clone(),
125 msg: message,
126 }
127}
128
129#[cfg(test)]
131mod tests {
132 use rand_chacha::ChaCha20Rng;
133 use rand_core::SeedableRng;
134
135 use crate::metadata::MetadataPublicKey;
136 use crate::{Journalist, Source, SourcePublicView};
137
138 use super::*;
139
140 fn setup_rng() -> impl rand_core::CryptoRng + rand_core::RngCore {
142 let mut seed = [0u8; 32];
143 getrandom::fill(&mut seed).expect("getrandom failed- is platform supported?");
144 ChaCha20Rng::from_seed(seed)
145 }
146
147 fn assert_encrypt_decrypt<R: CryptoRng + RngCore>(
148 rng: &mut R,
149 sender_public: &impl UserPublic,
150 sender_secret: &impl UserSecret,
151 rcvr_public: &impl UserPublic,
152 rcvr_secret: &impl UserSecret,
153 msg: Vec<u8>,
154 ) {
155 let pt = build_message(sender_public, msg);
156
157 let envelope = encrypt(rng, sender_secret, &pt, rcvr_public);
158 let decrypted = decrypt(rcvr_secret, &envelope);
159
160 let pt_ref = &pt;
161
162 assert_eq!(pt_ref.msg, decrypted.msg);
163 assert_eq!(pt_ref.len(), decrypted.to_bytes().len());
164
165 assert_eq!(
166 pt_ref.sender_fetch_key.into_bytes(),
167 sender_secret.fetch_keypair().1.clone().into_bytes()
168 );
169 assert_eq!(
170 pt_ref.sender_reply_pubkey_hybrid.as_bytes(),
171 sender_public.message_metadata_pk().as_bytes()
172 );
173 assert_eq!(
174 pt.len(),
175 &pt_ref.msg.len() + DHPublicKey::LEN + MetadataPublicKey::LEN
176 );
177 }
178
179 #[test]
180 fn test_encrypt_decrypt_roundtrip() {
181 let mut rng = setup_rng();
182
183 let sender = Source::new(&mut rng);
184 let recipient = Journalist::new(&mut rng, 2);
185
186 let msg = b"Encrypt-decrypt-test".to_vec();
187
188 assert_encrypt_decrypt(
189 &mut rng,
190 &sender.public(),
191 &sender,
192 &recipient.public(1),
193 &recipient,
194 msg,
195 );
196 }
197
198 #[test]
199 fn test_encrypt_decrypt_sourcesource() {
200 let mut rng = setup_rng();
202 let sender = Source::new(&mut rng);
203 let recipient = Source::new(&mut rng);
204
205 assert_encrypt_decrypt(
206 &mut rng,
207 &sender.public(),
208 &sender,
209 &recipient.public(),
210 &recipient,
211 b"Encrypt-decrypt-test".to_vec(),
212 );
213 }
214
215 #[test]
216 fn test_journalist_reply_to_source_roundtrip() {
217 let mut rng = setup_rng();
218
219 let source = Source::new(&mut rng);
221 let journalist = Journalist::new(&mut rng, 2);
222 let journalist_public = journalist.public(0);
223
224 let submission = build_message(&source.public(), b"howdy".to_vec());
225 let envelope = encrypt(&mut rng, &source, &submission, &journalist_public);
226
227 let (pt, sender_apke) = decrypt_with_sender(&journalist, &envelope);
229 let reply_recipient = SourcePublicView::from_reply_keys(
230 pt.sender_fetch_key,
231 sender_apke,
232 pt.sender_reply_pubkey_hybrid,
233 );
234
235 let reply_text = b"thanks dawg".to_vec();
237 let reply_pt = journalist.build_message(reply_text.clone());
238 let reply_envelope = encrypt(&mut rng, &journalist, &reply_pt, &reply_recipient);
239
240 let decrypted = decrypt(&source, &reply_envelope);
242 assert_eq!(decrypted.msg, reply_text);
243 }
244
245 #[test]
246 fn test_encrypt_decrypt_journalist_only() {
247 let mut rng = setup_rng();
248
249 let journalist = Journalist::new(&mut rng, 2);
250 let j2 = Journalist::new(&mut rng, 2);
251
252 let msg = "Test message".as_bytes().to_vec();
253
254 assert_encrypt_decrypt(
255 &mut rng,
256 &journalist.public(0),
257 &journalist,
258 &j2.public(0),
259 &j2,
260 msg,
261 );
262 }
263}