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