1use rand::{thread_rng, Rng};
3
4use anyhow::{bail, ensure, Result};
5
6use crate::blob::BlobObject;
7use crate::chat::{self, ChatId};
8use crate::config::Config;
9use crate::constants::{ASM_BODY, ASM_SUBJECT};
10use crate::contact::ContactId;
11use crate::context::Context;
12use crate::imex::set_self_key;
13use crate::key::{load_self_secret_key, DcKey};
14use crate::message::{Message, MsgId, Viewtype};
15use crate::mimeparser::SystemMessage;
16use crate::param::Param;
17use crate::pgp;
18use crate::tools::open_file_std;
19
20pub async fn initiate_key_transfer(context: &Context) -> Result<String> {
24 let setup_code = create_setup_code(context);
25 let setup_file_content = render_setup_file(context, &setup_code).await?;
27 let setup_file_blob = BlobObject::create_and_deduplicate_from_bytes(
29 context,
30 setup_file_content.as_bytes(),
31 "autocrypt-setup-message.html",
32 )?;
33
34 let chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?;
35 let mut msg = Message {
36 viewtype: Viewtype::File,
37 ..Default::default()
38 };
39 msg.param.set(Param::File, setup_file_blob.as_name());
40 msg.param
41 .set(Param::Filename, "autocrypt-setup-message.html");
42 msg.subject = ASM_SUBJECT.to_owned();
43 msg.param
44 .set(Param::MimeType, "application/autocrypt-setup");
45 msg.param.set_cmd(SystemMessage::AutocryptSetupMessage);
46 msg.force_plaintext();
47 msg.param.set_int(Param::SkipAutocrypt, 1);
48
49 context.set_config_bool(Config::BccSelf, true).await?;
52
53 chat::send_msg(context, chat_id, &mut msg).await?;
54 Ok(setup_code)
55}
56
57pub async fn continue_key_transfer(
62 context: &Context,
63 msg_id: MsgId,
64 setup_code: &str,
65) -> Result<()> {
66 ensure!(!msg_id.is_special(), "wrong id");
67
68 let msg = Message::load_from_db(context, msg_id).await?;
69 ensure!(
70 msg.is_setupmessage(),
71 "Message is no Autocrypt Setup Message."
72 );
73
74 if let Some(filename) = msg.get_file(context) {
75 let file = open_file_std(context, filename)?;
76 let sc = normalize_setup_code(setup_code);
77 let armored_key = decrypt_setup_file(&sc, file).await?;
78 set_self_key(context, &armored_key).await?;
79 context.set_config_bool(Config::BccSelf, true).await?;
80
81 Ok(())
82 } else {
83 bail!("Message is no Autocrypt Setup Message.");
84 }
85}
86
87pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result<String> {
91 let passphrase_begin = if let Some(passphrase_begin) = passphrase.get(..2) {
92 passphrase_begin
93 } else {
94 bail!("Passphrase must be at least 2 chars long.");
95 };
96 let private_key = load_self_secret_key(context).await?;
97 let ac_headers = match context.get_config_bool(Config::E2eeEnabled).await? {
98 false => None,
99 true => Some(("Autocrypt-Prefer-Encrypt", "mutual")),
100 };
101 let private_key_asc = private_key.to_asc(ac_headers);
102 let encr = pgp::symm_encrypt(passphrase, private_key_asc.as_bytes())
103 .await?
104 .replace('\n', "\r\n");
105
106 let replacement = format!(
107 concat!(
108 "-----BEGIN PGP MESSAGE-----\r\n",
109 "Passphrase-Format: numeric9x4\r\n",
110 "Passphrase-Begin: {}"
111 ),
112 passphrase_begin
113 );
114 let pgp_msg = encr.replace("-----BEGIN PGP MESSAGE-----", &replacement);
115
116 let msg_subj = ASM_SUBJECT;
117 let msg_body = ASM_BODY.to_string();
118 let msg_body_html = msg_body.replace('\r', "").replace('\n', "<br>");
119 Ok(format!(
120 concat!(
121 "<!DOCTYPE html>\r\n",
122 "<html>\r\n",
123 " <head>\r\n",
124 " <title>{}</title>\r\n",
125 " </head>\r\n",
126 " <body>\r\n",
127 " <h1>{}</h1>\r\n",
128 " <p>{}</p>\r\n",
129 " <pre>\r\n{}\r\n</pre>\r\n",
130 " </body>\r\n",
131 "</html>\r\n"
132 ),
133 msg_subj, msg_subj, msg_body_html, pgp_msg
134 ))
135}
136
137fn create_setup_code(_context: &Context) -> String {
139 let mut random_val: u16;
140 let mut rng = thread_rng();
141 let mut ret = String::new();
142
143 for i in 0..9 {
144 loop {
145 random_val = rng.gen();
146 if random_val as usize <= 60000 {
147 break;
148 }
149 }
150 random_val = (random_val as usize % 10000) as u16;
151 ret += &format!(
152 "{}{:04}",
153 if 0 != i { "-" } else { "" },
154 random_val as usize
155 );
156 }
157
158 ret
159}
160
161async fn decrypt_setup_file<T: std::io::Read + std::io::Seek>(
162 passphrase: &str,
163 file: T,
164) -> Result<String> {
165 let plain_bytes = pgp::symm_decrypt(passphrase, file).await?;
166 let plain_text = std::string::String::from_utf8(plain_bytes)?;
167
168 Ok(plain_text)
169}
170
171fn normalize_setup_code(s: &str) -> String {
172 let mut out = String::new();
173 for c in s.chars() {
174 if c.is_ascii_digit() {
175 out.push(c);
176 if let 4 | 9 | 14 | 19 | 24 | 29 | 34 | 39 = out.len() {
177 out += "-"
178 }
179 }
180 }
181 out
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
189 use crate::receive_imf::receive_imf;
190 use crate::test_utils::{TestContext, TestContextManager};
191 use ::pgp::armor::BlockType;
192
193 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
194 async fn test_render_setup_file() {
195 let t = TestContext::new_alice().await;
196 let msg = render_setup_file(&t, "hello").await.unwrap();
197 println!("{}", &msg);
198 assert!(msg.contains("<title>Autocrypt Setup Message</title"));
200 assert!(msg.contains("<h1>Autocrypt Setup Message</h1>"));
201 assert!(msg.contains("<p>This is the Autocrypt Setup Message used to"));
202 assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n"));
203 assert!(msg.contains("Passphrase-Format: numeric9x4\r\n"));
204 assert!(msg.contains("Passphrase-Begin: he\r\n"));
205 assert!(msg.contains("-----END PGP MESSAGE-----\r\n"));
206
207 for line in msg.rsplit_terminator('\n') {
208 assert!(line.ends_with('\r'));
209 }
210 }
211
212 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
213 async fn test_render_setup_file_newline_replace() {
214 let t = TestContext::new_alice().await;
215 let msg = render_setup_file(&t, "pw").await.unwrap();
216 println!("{}", &msg);
217 assert!(msg.contains("<p>This is the Autocrypt Setup Message used to transfer your end-to-end setup between clients.<br>"));
218 }
219
220 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
221 async fn test_create_setup_code() {
222 let t = TestContext::new().await;
223 let setupcode = create_setup_code(&t);
224 assert_eq!(setupcode.len(), 44);
225 assert_eq!(setupcode.chars().nth(4).unwrap(), '-');
226 assert_eq!(setupcode.chars().nth(9).unwrap(), '-');
227 assert_eq!(setupcode.chars().nth(14).unwrap(), '-');
228 assert_eq!(setupcode.chars().nth(19).unwrap(), '-');
229 assert_eq!(setupcode.chars().nth(24).unwrap(), '-');
230 assert_eq!(setupcode.chars().nth(29).unwrap(), '-');
231 assert_eq!(setupcode.chars().nth(34).unwrap(), '-');
232 assert_eq!(setupcode.chars().nth(39).unwrap(), '-');
233 }
234
235 #[test]
236 fn test_normalize_setup_code() {
237 let norm = normalize_setup_code("123422343234423452346234723482349234");
238 assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
239
240 let norm =
241 normalize_setup_code("\t1 2 3422343234- foo bar-- 423-45 2 34 6234723482349234 ");
242 assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234");
243 }
244
245 const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597";
248 const S_EM_SETUPFILE: &str = include_str!("../../test-data/message/stress.txt");
249
250 const S_PLAINTEXT_SETUPFILE: &str =
252 include_str!("../../test-data/message/plaintext-autocrypt-setup.txt");
253
254 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
255 async fn test_split_and_decrypt() {
256 let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec();
257 let (typ, headers, base64) = split_armored_data(&buf_1).unwrap();
258 assert_eq!(typ, BlockType::Message);
259 assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap()));
260 assert!(!headers.contains_key(HEADER_AUTOCRYPT));
261
262 assert!(!base64.is_empty());
263
264 let setup_file = S_EM_SETUPFILE.to_string();
265 let decrypted =
266 decrypt_setup_file(S_EM_SETUPCODE, std::io::Cursor::new(setup_file.as_bytes()))
267 .await
268 .unwrap();
269
270 let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap();
271
272 assert_eq!(typ, BlockType::PrivateKey);
273 assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string()));
274 assert!(!headers.contains_key(HEADER_SETUPCODE));
275 }
276
277 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
283 async fn test_decrypt_plaintext_autocrypt_setup_message() {
284 let setup_file = S_PLAINTEXT_SETUPFILE.to_string();
285 let incorrect_setupcode = "0000-0000-0000-0000-0000-0000-0000-0000-0000";
286 assert!(decrypt_setup_file(
287 incorrect_setupcode,
288 std::io::Cursor::new(setup_file.as_bytes()),
289 )
290 .await
291 .is_err());
292 }
293
294 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
295 async fn test_key_transfer() -> Result<()> {
296 let alice = TestContext::new_alice().await;
297
298 alice.set_config(Config::BccSelf, Some("0")).await?;
299 let setup_code = initiate_key_transfer(&alice).await?;
300
301 assert_eq!(alice.get_config_bool(Config::BccSelf).await?, true);
303
304 let sent = alice.pop_sent_msg().await;
306
307 let alice2 = TestContext::new().await;
309 alice2.set_name("alice2");
310 alice2.configure_addr("alice@example.org").await;
311 alice2.recv_msg(&sent).await;
312 let msg = alice2.get_last_msg().await;
313 assert!(msg.is_setupmessage());
314 assert_eq!(
315 crate::key::load_self_secret_keyring(&alice2).await?.len(),
316 0
317 );
318
319 alice2.set_config(Config::BccSelf, Some("0")).await?;
321 continue_key_transfer(&alice2, msg.id, &setup_code).await?;
322 assert_eq!(alice2.get_config_bool(Config::BccSelf).await?, true);
323 assert_eq!(
324 crate::key::load_self_secret_keyring(&alice2).await?.len(),
325 1
326 );
327
328 let sent = alice2.send_text(msg.chat_id, "Test").await;
330 let rcvd_msg = alice.recv_msg(&sent).await;
331 assert_eq!(rcvd_msg.get_text(), "Test");
332
333 Ok(())
334 }
335
336 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
341 async fn test_key_transfer_non_self_sent() -> Result<()> {
342 let mut tcm = TestContextManager::new();
343 let alice = tcm.alice().await;
344 let bob = tcm.bob().await;
345
346 let _setup_code = initiate_key_transfer(&alice).await?;
347
348 let sent = alice.pop_sent_msg().await;
350
351 let rcvd = bob.recv_msg(&sent).await;
352 assert!(!rcvd.is_setupmessage());
353
354 Ok(())
355 }
356
357 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
362 async fn test_key_transfer_k_9() -> Result<()> {
363 let t = &TestContext::new().await;
364 t.configure_addr("autocrypt@nine.testrun.org").await;
365
366 let raw = include_bytes!("../../test-data/message/k-9-autocrypt-setup-message.eml");
367 let received = receive_imf(t, raw, false).await?.unwrap();
368
369 let setup_code = "0655-9868-8252-5455-4232-5158-1237-5333-2638";
370 continue_key_transfer(t, *received.msg_ids.last().unwrap(), setup_code).await?;
371
372 Ok(())
373 }
374}