From fe54cb43ac1d3cad72ae9808f2be531f9f24cb76 Mon Sep 17 00:00:00 2001 From: link2xt Date: Sun, 15 Mar 2026 15:02:10 +0000 Subject: [PATCH] api!: remove functions for sending and receiving Autocrypt Setup Message --- deltachat-ffi/deltachat.h | 70 ---- deltachat-ffi/src/lib.rs | 39 -- deltachat-jsonrpc/src/api.rs | 19 - deltachat-repl/src/cmdline.rs | 15 - deltachat-repl/src/main.rs | 4 +- .../src/deltachat_rpc_client/account.py | 4 - .../src/deltachat_rpc_client/message.py | 8 - .../tests/test_key_transfer.py | 49 --- src/chat.rs | 4 +- src/constants.rs | 13 - src/headerdef.rs | 6 +- src/imap.rs | 9 - src/imap/imap_tests.rs | 30 +- src/imex.rs | 2 - src/imex/key_transfer.rs | 363 ------------------ src/mimefactory.rs | 11 +- src/mimeparser.rs | 3 + src/pgp.rs | 43 +-- src/receive_imf.rs | 3 +- test-data/message/AutocryptSetupMessage.eml | 77 ---- 20 files changed, 17 insertions(+), 755 deletions(-) delete mode 100644 deltachat-rpc-client/tests/test_key_transfer.py delete mode 100644 src/imex/key_transfer.rs delete mode 100644 test-data/message/AutocryptSetupMessage.eml diff --git a/deltachat-ffi/deltachat.h b/deltachat-ffi/deltachat.h index abc3f0ef0..8a10b0ac7 100644 --- a/deltachat-ffi/deltachat.h +++ b/deltachat-ffi/deltachat.h @@ -2471,76 +2471,6 @@ void dc_imex (dc_context_t* context, int what, c char* dc_imex_has_backup (dc_context_t* context, const char* dir); -/** - * Initiate Autocrypt Setup Transfer. - * Before starting the setup transfer with this function, the user should be asked: - * - * ~~~ - * "An 'Autocrypt Setup Message' securely shares your end-to-end setup with other Autocrypt-compliant apps. - * The setup will be encrypted by a setup code which is displayed here and must be typed on the other device. - * ~~~ - * - * After that, this function should be called to send the Autocrypt Setup Message. - * The function creates the setup message and adds it to outgoing message queue. - * The message is sent asynchronously. - * - * The required setup code is returned in the following format: - * - * ~~~ - * 1234-1234-1234-1234-1234-1234-1234-1234-1234 - * ~~~ - * - * The setup code should be shown to the user then: - * - * ~~~ - * "Your key has been sent to yourself. Switch to the other device and - * open the setup message. You should be prompted for a setup code. Type - * the following digits into the prompt: - * - * 1234 - 1234 - 1234 - - * 1234 - 1234 - 1234 - - * 1234 - 1234 - 1234 - * - * Once you're done, your other device will be ready to use Autocrypt." - * ~~~ - * - * On the _other device_ you will call dc_continue_key_transfer() then - * for setup messages identified by dc_msg_is_setupmessage(). - * - * For more details about the Autocrypt setup process, please refer to - * https://autocrypt.org/en/latest/level1.html#autocrypt-setup-message - * - * @memberof dc_context_t - * @param context The context object. - * @return The setup code. Must be released using dc_str_unref() after usage. - * On errors, e.g. if the message could not be sent, NULL is returned. - */ -char* dc_initiate_key_transfer (dc_context_t* context); - - -/** - * Continue the Autocrypt Key Transfer on another device. - * - * If you have started the key transfer on another device using dc_initiate_key_transfer() - * and you've detected a setup message with dc_msg_is_setupmessage(), you should prompt the - * user for the setup code and call this function then. - * - * You can use dc_msg_get_setupcodebegin() to give the user a hint about the code (useful if the user - * has created several messages and should not enter the wrong code). - * - * @memberof dc_context_t - * @param context The context object. - * @param msg_id The ID of the setup message to decrypt. - * @param setup_code The setup code entered by the user. This is the same setup code as returned from - * dc_initiate_key_transfer() on the other device. - * There is no need to format the string correctly, the function will remove all spaces and other characters and - * insert the `-` characters at the correct places. - * @return 1=key successfully decrypted and imported; both devices will use the same key now; - * 0=key transfer failed e.g. due to a bad setup code. - */ -int dc_continue_key_transfer (dc_context_t* context, uint32_t msg_id, const char* setup_code); - - /** * Signal an ongoing process to stop. * diff --git a/deltachat-ffi/src/lib.rs b/deltachat-ffi/src/lib.rs index c0b128dae..ffc444891 100644 --- a/deltachat-ffi/src/lib.rs +++ b/deltachat-ffi/src/lib.rs @@ -2429,45 +2429,6 @@ pub unsafe extern "C" fn dc_imex_has_backup( } } -#[no_mangle] -pub unsafe extern "C" fn dc_initiate_key_transfer(context: *mut dc_context_t) -> *mut libc::c_char { - if context.is_null() { - eprintln!("ignoring careless call to dc_initiate_key_transfer()"); - return ptr::null_mut(); // NULL explicitly defined as "error" - } - let ctx = &*context; - - match block_on(imex::initiate_key_transfer(ctx)) - .context("dc_initiate_key_transfer()") - .log_err(ctx) - { - Ok(res) => res.strdup(), - Err(_) => ptr::null_mut(), - } -} - -#[no_mangle] -pub unsafe extern "C" fn dc_continue_key_transfer( - context: *mut dc_context_t, - msg_id: u32, - setup_code: *const libc::c_char, -) -> libc::c_int { - if context.is_null() || msg_id <= constants::DC_MSG_ID_LAST_SPECIAL || setup_code.is_null() { - eprintln!("ignoring careless call to dc_continue_key_transfer()"); - return 0; - } - let ctx = &*context; - - block_on(imex::continue_key_transfer( - ctx, - MsgId::new(msg_id), - &to_string_lossy(setup_code), - )) - .context("dc_continue_key_transfer") - .log_err(ctx) - .is_ok() as libc::c_int -} - #[no_mangle] pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) { if context.is_null() { diff --git a/deltachat-jsonrpc/src/api.rs b/deltachat-jsonrpc/src/api.rs index 3bf828850..8501496ca 100644 --- a/deltachat-jsonrpc/src/api.rs +++ b/deltachat-jsonrpc/src/api.rs @@ -699,25 +699,6 @@ impl CommandApi { message::estimate_deletion_cnt(&ctx, from_server, seconds).await } - // --------------------------------------------- - // autocrypt - // --------------------------------------------- - - async fn initiate_autocrypt_key_transfer(&self, account_id: u32) -> Result { - let ctx = self.get_context(account_id).await?; - deltachat::imex::initiate_key_transfer(&ctx).await - } - - async fn continue_autocrypt_key_transfer( - &self, - account_id: u32, - message_id: u32, - setup_code: String, - ) -> Result<()> { - let ctx = self.get_context(account_id).await?; - deltachat::imex::continue_key_transfer(&ctx, MsgId::new(message_id), &setup_code).await - } - // --------------------------------------------- // chat list // --------------------------------------------- diff --git a/deltachat-repl/src/cmdline.rs b/deltachat-repl/src/cmdline.rs index 4dd13f94d..6085ddac1 100644 --- a/deltachat-repl/src/cmdline.rs +++ b/deltachat-repl/src/cmdline.rs @@ -302,9 +302,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu // TODO: reuse commands definition in main.rs. "imex" => println!( "====================Import/Export commands==\n\ - initiate-key-transfer\n\ get-setupcodebegin \n\ - continue-key-transfer \n\ has-backup\n\ export-backup\n\ import-backup \n\ @@ -408,12 +406,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu =============================================" ), }, - "initiate-key-transfer" => match initiate_key_transfer(&context).await { - Ok(setup_code) => { - println!("Setup code for the transferred setup message: {setup_code}",) - } - Err(err) => bail!("Failed to generate setup code: {err}"), - }, "get-setupcodebegin" => { ensure!(!arg1.is_empty(), "Argument missing."); let msg_id: MsgId = MsgId::new(arg1.parse()?); @@ -429,13 +421,6 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu bail!("{msg_id} is no setup message.",); } } - "continue-key-transfer" => { - ensure!( - !arg1.is_empty() && !arg2.is_empty(), - "Arguments expected" - ); - continue_key_transfer(&context, MsgId::new(arg1.parse()?), arg2).await?; - } "has-backup" => { has_backup(&context, blobdir).await?; } diff --git a/deltachat-repl/src/main.rs b/deltachat-repl/src/main.rs index 8740678d6..f4dbc52ae 100644 --- a/deltachat-repl/src/main.rs +++ b/deltachat-repl/src/main.rs @@ -149,10 +149,8 @@ impl Completer for DcHelper { } } -const IMEX_COMMANDS: [&str; 13] = [ - "initiate-key-transfer", +const IMEX_COMMANDS: [&str; 11] = [ "get-setupcodebegin", - "continue-key-transfer", "has-backup", "export-backup", "import-backup", diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/account.py b/deltachat-rpc-client/src/deltachat_rpc_client/account.py index 522e6e5eb..e7acebd18 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/account.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/account.py @@ -483,10 +483,6 @@ class Account: passphrase = "" # Importing passphrase-protected keys is currently not supported. self._rpc.import_self_keys(self.id, str(path), passphrase) - def initiate_autocrypt_key_transfer(self) -> None: - """Send Autocrypt Setup Message.""" - return self._rpc.initiate_autocrypt_key_transfer(self.id) - def ice_servers(self) -> list: """Return ICE servers for WebRTC configuration.""" ice_servers_json = self._rpc.ice_servers(self.id) diff --git a/deltachat-rpc-client/src/deltachat_rpc_client/message.py b/deltachat-rpc-client/src/deltachat_rpc_client/message.py index 9ba1c7cff..ff2f06e39 100644 --- a/deltachat-rpc-client/src/deltachat_rpc_client/message.py +++ b/deltachat-rpc-client/src/deltachat_rpc_client/message.py @@ -72,14 +72,6 @@ class Message: """Return True if the message exists.""" return bool(self._rpc.get_existing_msg_ids(self.account.id, [self.id])) - def continue_autocrypt_key_transfer(self, setup_code: str) -> None: - """Continue the Autocrypt Setup Message key transfer. - - This function can be called on received Autocrypt Setup Message - to import the key encrypted with the provided setup code. - """ - self._rpc.continue_autocrypt_key_transfer(self.account.id, self.id, setup_code) - def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None: """Send a webxdc status update. This message must be a webxdc.""" if not isinstance(update, str): diff --git a/deltachat-rpc-client/tests/test_key_transfer.py b/deltachat-rpc-client/tests/test_key_transfer.py deleted file mode 100644 index 44229f57f..000000000 --- a/deltachat-rpc-client/tests/test_key_transfer.py +++ /dev/null @@ -1,49 +0,0 @@ -import pytest - -from deltachat_rpc_client import EventType -from deltachat_rpc_client.rpc import JsonRpcError - - -def wait_for_autocrypt_setup_message(account): - while True: - event = account.wait_for_event() - if event.kind == EventType.MSGS_CHANGED and event.msg_id != 0: - msg_id = event.msg_id - msg = account.get_message_by_id(msg_id) - if msg.get_snapshot().is_setupmessage: - return msg - - -def test_autocrypt_setup_message_key_transfer(acfactory): - alice1 = acfactory.get_online_account() - - alice2 = acfactory.get_unconfigured_account() - alice2.add_or_update_transport({"addr": alice1.get_config("addr"), "password": alice1.get_config("mail_pw")}) - alice2.bring_online() - - setup_code = alice1.initiate_autocrypt_key_transfer() - msg = wait_for_autocrypt_setup_message(alice2) - - # Test that entering wrong code returns an error. - with pytest.raises(JsonRpcError): - msg.continue_autocrypt_key_transfer("7037-0673-6287-3013-4095-7956-5617-6806-6756") - - msg.continue_autocrypt_key_transfer(setup_code) - - -def test_ac_setup_message_twice(acfactory): - alice1 = acfactory.get_online_account() - - alice2 = acfactory.get_unconfigured_account() - alice2.add_or_update_transport({"addr": alice1.get_config("addr"), "password": alice1.get_config("mail_pw")}) - alice2.bring_online() - - # Send the first Autocrypt Setup Message and ignore it. - _setup_code = alice1.initiate_autocrypt_key_transfer() - wait_for_autocrypt_setup_message(alice2) - - # Send the second Autocrypt Setup Message and import it. - setup_code = alice1.initiate_autocrypt_key_transfer() - msg = wait_for_autocrypt_setup_message(alice2) - - msg.continue_autocrypt_key_transfer(setup_code) diff --git a/src/chat.rs b/src/chat.rs index 2dd153570..a21622c75 100644 --- a/src/chat.rs +++ b/src/chat.rs @@ -2844,9 +2844,7 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) - let lowercase_from = from.to_lowercase(); recipients.retain(|x| x.to_lowercase() != lowercase_from); - if context.get_config_bool(Config::BccSelf).await? - || msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage - { + if context.get_config_bool(Config::BccSelf).await? { smtp::add_self_recipients(context, &mut recipients, needs_encryption).await?; } diff --git a/src/constants.rs b/src/constants.rs index 28eb4c0fe..1eb852b34 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -234,19 +234,6 @@ pub(crate) const TIMESTAMP_SENT_TOLERANCE: i64 = 60; // Newer Delta Chats will remove the prefix as needed. pub(crate) const EDITED_PREFIX: &str = "✏️"; -// Strings needed to render the Autocrypt Setup Message. -// Left untranslated as not being supported/recommended workflow and as translations would require deep knowledge. -pub(crate) const ASM_SUBJECT: &str = "Autocrypt Setup Message"; -pub(crate) const ASM_BODY: &str = "This is the Autocrypt Setup Message \ - used to transfer your end-to-end setup between clients. - - To decrypt and use your setup, \ - open the message in an Autocrypt-compliant client \ - and enter the setup code presented on the generating device. - - If you see this message in a chatmail client (Delta Chat, Arcane Chat, Delta Touch ...), \ - use \"Settings / Add Second Device\" instead."; - /// Period between `sql::housekeeping()` runs. pub(crate) const HOUSEKEEPING_PERIOD: i64 = 24 * 60 * 60; diff --git a/src/headerdef.rs b/src/headerdef.rs index 166815f0f..629d52f36 100644 --- a/src/headerdef.rs +++ b/src/headerdef.rs @@ -208,10 +208,10 @@ mod tests { /// Test that headers are parsed case-insensitively fn test_get_header_value_case() { let (headers, _) = - mailparse::parse_headers(b"fRoM: Bob\naUtoCryPt-SeTup-MessAge: v99").unwrap(); + mailparse::parse_headers(b"fRoM: Bob\naUtoCryPt-GoSsIp: fooBaR").unwrap(); assert_eq!( - headers.get_header_value(HeaderDef::AutocryptSetupMessage), - Some("v99".to_string()) + headers.get_header_value(HeaderDef::AutocryptGossip), + Some("fooBaR".to_string()) ); assert_eq!( headers.get_header_value(HeaderDef::From_), diff --git a/src/imap.rs b/src/imap.rs index 96d9db50c..6492cae56 100644 --- a/src/imap.rs +++ b/src/imap.rs @@ -1974,15 +1974,6 @@ async fn needs_move_to_mvbox( return Ok(false); } - if headers - .get_header_value(HeaderDef::AutocryptSetupMessage) - .is_some() - { - // do not move setup messages; - // there may be a non-delta device that wants to handle it - return Ok(false); - } - if has_chat_version { Ok(true) } else if let Some(parent) = get_prefetch_parent_message(context, headers).await? { diff --git a/src/imap/imap_tests.rs b/src/imap/imap_tests.rs index 4ba314213..28fd54a57 100644 --- a/src/imap/imap_tests.rs +++ b/src/imap/imap_tests.rs @@ -105,10 +105,9 @@ async fn check_target_folder_combination( expected_destination: &str, accepted_chat: bool, outgoing: bool, - setupmessage: bool, ) -> Result<()> { println!( - "Testing: For folder {folder}, mvbox_move {mvbox_move}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}" + "Testing: For folder {folder}, mvbox_move {mvbox_move}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}" ); let t = TestContext::new_alice().await; @@ -125,9 +124,7 @@ async fn check_target_folder_combination( } let temp; - let bytes = if setupmessage { - include_bytes!("../../test-data/message/AutocryptSetupMessage.eml") - } else { + let bytes = { temp = format!( "Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\ {}\ @@ -164,7 +161,7 @@ async fn check_target_folder_combination( assert_eq!( expected, actual.as_deref(), - "For folder {folder}, mvbox_move {mvbox_move}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}, setupmessage {setupmessage}: expected {expected:?}, got {actual:?}" + "For folder {folder}, mvbox_move {mvbox_move}, chat_msg {chat_msg}, accepted {accepted_chat}, outgoing {outgoing}: expected {expected:?}, got {actual:?}" ); Ok(()) } @@ -204,7 +201,6 @@ async fn test_target_folder_incoming_accepted() -> Result<()> { expected_destination, true, false, - false, ) .await?; } @@ -221,7 +217,6 @@ async fn test_target_folder_incoming_request() -> Result<()> { expected_destination, false, false, - false, ) .await?; } @@ -239,25 +234,6 @@ async fn test_target_folder_outgoing() -> Result<()> { expected_destination, true, true, - false, - ) - .await?; - } - Ok(()) -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_target_folder_setupmsg() -> Result<()> { - // Test setupmessages - for (folder, mvbox_move, chat_msg, _expected_destination) in COMBINATIONS_ACCEPTED_CHAT { - check_target_folder_combination( - folder, - *mvbox_move, - *chat_msg, - if folder == &"Spam" { "INBOX" } else { folder }, // Never move setup messages, except if they are in "Spam" - false, - true, - true, ) .await?; } diff --git a/src/imex.rs b/src/imex.rs index a3fe1599a..681d92fd3 100644 --- a/src/imex.rs +++ b/src/imex.rs @@ -28,11 +28,9 @@ use crate::tools::{ write_file, }; -mod key_transfer; mod transfer; use ::pgp::types::KeyDetails; -pub use key_transfer::{continue_key_transfer, initiate_key_transfer}; pub use transfer::{BackupProvider, get_backup}; // Name of the database file in the backup. diff --git a/src/imex/key_transfer.rs b/src/imex/key_transfer.rs deleted file mode 100644 index 28019365d..000000000 --- a/src/imex/key_transfer.rs +++ /dev/null @@ -1,363 +0,0 @@ -//! # Key transfer via Autocrypt Setup Message. -use std::io::BufReader; - -use anyhow::{Result, bail, ensure}; - -use crate::blob::BlobObject; -use crate::chat::{self, ChatId}; -use crate::config::Config; -use crate::constants::{ASM_BODY, ASM_SUBJECT}; -use crate::contact::ContactId; -use crate::context::Context; -use crate::imex::set_self_key; -use crate::key::{DcKey, load_self_secret_key}; -use crate::message::{Message, MsgId, Viewtype}; -use crate::mimeparser::SystemMessage; -use crate::param::Param; -use crate::pgp; -use crate::tools::open_file_std; - -/// Initiates key transfer via Autocrypt Setup Message. -/// -/// Returns setup code. -pub async fn initiate_key_transfer(context: &Context) -> Result { - let setup_code = create_setup_code(context); - /* this may require a keypair to be created. this may take a second ... */ - let setup_file_content = render_setup_file(context, &setup_code).await?; - /* encrypting may also take a while ... */ - let setup_file_blob = BlobObject::create_and_deduplicate_from_bytes( - context, - setup_file_content.as_bytes(), - "autocrypt-setup-message.html", - )?; - - let chat_id = ChatId::create_for_contact(context, ContactId::SELF).await?; - let mut msg = Message::new(Viewtype::File); - msg.param.set(Param::File, setup_file_blob.as_name()); - msg.param - .set(Param::Filename, "autocrypt-setup-message.html"); - msg.subject = ASM_SUBJECT.to_owned(); - msg.param - .set(Param::MimeType, "application/autocrypt-setup"); - msg.param.set_cmd(SystemMessage::AutocryptSetupMessage); - msg.force_plaintext(); - msg.param.set_int(Param::SkipAutocrypt, 1); - - // Enable BCC-self, because transferring a key - // means we have a multi-device setup. - context.set_config_bool(Config::BccSelf, true).await?; - - chat::send_msg(context, chat_id, &mut msg).await?; - Ok(setup_code) -} - -/// Continue key transfer via Autocrypt Setup Message. -/// -/// `msg_id` is the ID of the received Autocrypt Setup Message. -/// `setup_code` is the code entered by the user. -pub async fn continue_key_transfer( - context: &Context, - msg_id: MsgId, - setup_code: &str, -) -> Result<()> { - ensure!(!msg_id.is_special(), "wrong id"); - - let msg = Message::load_from_db(context, msg_id).await?; - ensure!( - msg.is_setupmessage(), - "Message is no Autocrypt Setup Message." - ); - - if let Some(filename) = msg.get_file(context) { - let file = open_file_std(context, filename)?; - let sc = normalize_setup_code(setup_code); - let armored_key = decrypt_setup_file(&sc, BufReader::new(file)).await?; - set_self_key(context, &armored_key).await?; - context.set_config_bool(Config::BccSelf, true).await?; - - Ok(()) - } else { - bail!("Message is no Autocrypt Setup Message."); - } -} - -/// Renders HTML body of a setup file message. -/// -/// The `passphrase` must be at least 2 characters long. -pub async fn render_setup_file(context: &Context, passphrase: &str) -> Result { - let passphrase_begin = if let Some(passphrase_begin) = passphrase.get(..2) { - passphrase_begin - } else { - bail!("Passphrase must be at least 2 chars long."); - }; - let private_key = load_self_secret_key(context).await?; - let ac_headers = Some(("Autocrypt-Prefer-Encrypt", "mutual")); - let private_key_asc = private_key.to_asc(ac_headers); - let encr = pgp::symm_encrypt_autocrypt_setup(passphrase, private_key_asc.into_bytes()) - .await? - .replace('\n', "\r\n"); - - let replacement = format!( - concat!( - "-----BEGIN PGP MESSAGE-----\r\n", - "Passphrase-Format: numeric9x4\r\n", - "Passphrase-Begin: {}" - ), - passphrase_begin - ); - let pgp_msg = encr.replace("-----BEGIN PGP MESSAGE-----", &replacement); - - let msg_subj = ASM_SUBJECT; - let msg_body = ASM_BODY.to_string(); - let msg_body_html = msg_body.replace('\r', "").replace('\n', "
"); - Ok(format!( - concat!( - "\r\n", - "\r\n", - " \r\n", - " {}\r\n", - " \r\n", - " \r\n", - "

{}

\r\n", - "

{}

\r\n", - "
\r\n{}\r\n
\r\n", - " \r\n", - "\r\n" - ), - msg_subj, msg_subj, msg_body_html, pgp_msg - )) -} - -/// Creates a new setup code for Autocrypt Setup Message. -#[expect(clippy::arithmetic_side_effects)] -fn create_setup_code(_context: &Context) -> String { - let mut random_val: u16; - let mut ret = String::new(); - - for i in 0..9 { - loop { - random_val = rand::random(); - if random_val as usize <= 60000 { - break; - } - } - random_val = (random_val as usize % 10000) as u16; - ret += &format!( - "{}{:04}", - if 0 != i { "-" } else { "" }, - random_val as usize - ); - } - - ret -} - -async fn decrypt_setup_file( - passphrase: &str, - file: T, -) -> Result { - let plain_bytes = pgp::symm_decrypt(passphrase, file).await?; - let plain_text = std::string::String::from_utf8(plain_bytes)?; - - Ok(plain_text) -} - -fn normalize_setup_code(s: &str) -> String { - let mut out = String::new(); - for c in s.chars() { - if c.is_ascii_digit() { - out.push(c); - if let 4 | 9 | 14 | 19 | 24 | 29 | 34 | 39 = out.len() { - out += "-" - } - } - } - out -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::pgp::{HEADER_AUTOCRYPT, HEADER_SETUPCODE, split_armored_data}; - use crate::receive_imf::receive_imf; - use crate::test_utils::{TestContext, TestContextManager}; - use ::pgp::armor::BlockType; - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_render_setup_file() { - let t = TestContext::new_alice().await; - let msg = render_setup_file(&t, "hello").await.unwrap(); - println!("{}", &msg); - // Check some substrings, indicating things got substituted. - assert!(msg.contains("Autocrypt Setup Message</title")); - assert!(msg.contains("<h1>Autocrypt Setup Message</h1>")); - assert!(msg.contains("<p>This is the Autocrypt Setup Message used to")); - assert!(msg.contains("-----BEGIN PGP MESSAGE-----\r\n")); - assert!(msg.contains("Passphrase-Format: numeric9x4\r\n")); - assert!(msg.contains("Passphrase-Begin: he\r\n")); - assert!(msg.contains("-----END PGP MESSAGE-----\r\n")); - - for line in msg.rsplit_terminator('\n') { - assert!(line.ends_with('\r')); - } - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_render_setup_file_newline_replace() { - let t = TestContext::new_alice().await; - let msg = render_setup_file(&t, "pw").await.unwrap(); - println!("{}", &msg); - assert!(msg.contains("<p>This is the Autocrypt Setup Message used to transfer your end-to-end setup between clients.<br>")); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_create_setup_code() { - let t = TestContext::new().await; - let setupcode = create_setup_code(&t); - assert_eq!(setupcode.len(), 44); - assert_eq!(setupcode.chars().nth(4).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(9).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(14).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(19).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(24).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(29).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(34).unwrap(), '-'); - assert_eq!(setupcode.chars().nth(39).unwrap(), '-'); - } - - #[test] - fn test_normalize_setup_code() { - let norm = normalize_setup_code("123422343234423452346234723482349234"); - assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234"); - - let norm = - normalize_setup_code("\t1 2 3422343234- foo bar-- 423-45 2 34 6234723482349234 "); - assert_eq!(norm, "1234-2234-3234-4234-5234-6234-7234-8234-9234"); - } - - /* S_EM_SETUPFILE is a AES-256 symm. encrypted setup message created by Enigmail - with an "encrypted session key", see RFC 4880. The code is in S_EM_SETUPCODE */ - const S_EM_SETUPCODE: &str = "1742-0185-6197-1303-7016-8412-3581-4441-0597"; - const S_EM_SETUPFILE: &str = include_str!("../../test-data/message/stress.txt"); - - // Autocrypt Setup Message payload "encrypted" with plaintext algorithm. - const S_PLAINTEXT_SETUPFILE: &str = - include_str!("../../test-data/message/plaintext-autocrypt-setup.txt"); - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_split_and_decrypt() { - let buf_1 = S_EM_SETUPFILE.as_bytes().to_vec(); - let (typ, headers, base64) = split_armored_data(&buf_1).unwrap(); - assert_eq!(typ, BlockType::Message); - assert!(S_EM_SETUPCODE.starts_with(headers.get(HEADER_SETUPCODE).unwrap())); - assert!(!headers.contains_key(HEADER_AUTOCRYPT)); - - assert!(!base64.is_empty()); - - let setup_file = S_EM_SETUPFILE; - let decrypted = decrypt_setup_file(S_EM_SETUPCODE, setup_file.as_bytes()) - .await - .unwrap(); - - let (typ, headers, _base64) = split_armored_data(decrypted.as_bytes()).unwrap(); - - assert_eq!(typ, BlockType::PrivateKey); - assert_eq!(headers.get(HEADER_AUTOCRYPT), Some(&"mutual".to_string())); - assert!(!headers.contains_key(HEADER_SETUPCODE)); - } - - /// Tests that Autocrypt Setup Message encrypted with "plaintext" algorithm cannot be - /// decrypted. - /// - /// According to <https://datatracker.ietf.org/doc/html/rfc4880#section-13.4> - /// "Implementations MUST NOT use plaintext in Symmetrically Encrypted Data packets". - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_decrypt_plaintext_autocrypt_setup_message() { - let setup_file = S_PLAINTEXT_SETUPFILE; - let incorrect_setupcode = "0000-0000-0000-0000-0000-0000-0000-0000-0000"; - assert!( - decrypt_setup_file(incorrect_setupcode, setup_file.as_bytes(),) - .await - .is_err() - ); - } - - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_key_transfer() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = &tcm.alice().await; - - tcm.section("Alice sends Autocrypt setup message"); - alice.set_config(Config::BccSelf, Some("0")).await?; - let setup_code = initiate_key_transfer(alice).await?; - - // Test that sending Autocrypt Setup Message enables `bcc_self`. - assert_eq!(alice.get_config_bool(Config::BccSelf).await?, true); - - // Get Autocrypt Setup Message. - let sent = alice.pop_sent_msg().await; - - tcm.section("Alice sets up a second device"); - let alice2 = &tcm.unconfigured().await; - alice2.set_name("alice2"); - alice2.configure_addr("alice@example.org").await; - alice2.recv_msg(&sent).await; - let msg = alice2.get_last_msg().await; - assert!(msg.is_setupmessage()); - assert_eq!(crate::key::load_self_secret_keyring(alice2).await?.len(), 0); - - // Transfer the key. - tcm.section("Alice imports a key from Autocrypt Setup Message"); - alice2.set_config(Config::BccSelf, Some("0")).await?; - continue_key_transfer(alice2, msg.id, &setup_code).await?; - assert_eq!(alice2.get_config_bool(Config::BccSelf).await?, true); - assert_eq!(crate::key::load_self_secret_keyring(alice2).await?.len(), 1); - - // Alice sends a message to self from the new device. - let sent = alice2.send_text(msg.chat_id, "Test").await; - let rcvd_msg = alice.recv_msg(&sent).await; - assert_eq!(rcvd_msg.get_text(), "Test"); - - Ok(()) - } - - /// Tests that Autocrypt Setup Messages is only clickable if it is self-sent. - /// This prevents Bob from tricking Alice into changing the key - /// by sending her an Autocrypt Setup Message as long as Alice's server - /// does not allow to forge the `From:` header. - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_key_transfer_non_self_sent() -> Result<()> { - let mut tcm = TestContextManager::new(); - let alice = tcm.alice().await; - let bob = tcm.bob().await; - - let _setup_code = initiate_key_transfer(&alice).await?; - - // Get Autocrypt Setup Message. - let sent = alice.pop_sent_msg().await; - - let rcvd = bob.recv_msg(&sent).await; - assert!(!rcvd.is_setupmessage()); - - Ok(()) - } - - /// Tests reception of Autocrypt Setup Message from K-9 6.802. - /// - /// Unlike Autocrypt Setup Message sent by Delta Chat, - /// this message does not contain `Autocrypt-Prefer-Encrypt` header. - #[tokio::test(flavor = "multi_thread", worker_threads = 2)] - async fn test_key_transfer_k_9() -> Result<()> { - let t = &TestContext::new().await; - t.configure_addr("autocrypt@nine.testrun.org").await; - - let raw = include_bytes!("../../test-data/message/k-9-autocrypt-setup-message.eml"); - let received = receive_imf(t, raw, false).await?.unwrap(); - - let setup_code = "0655-9868-8252-5455-4232-5158-1237-5333-2638"; - continue_key_transfer(t, *received.msg_ids.last().unwrap(), setup_code).await?; - - Ok(()) - } -} diff --git a/src/mimefactory.rs b/src/mimefactory.rs index 32929d9d1..5dbfa1651 100644 --- a/src/mimefactory.rs +++ b/src/mimefactory.rs @@ -17,8 +17,7 @@ use crate::aheader::{Aheader, EncryptPreference}; use crate::blob::BlobObject; use crate::chat::{self, Chat, PARAM_BROADCAST_SECRET, load_broadcast_secret}; use crate::config::Config; -use crate::constants::{ASM_SUBJECT, BROADCAST_INCOMPATIBILITY_MSG}; -use crate::constants::{Chattype, DC_FROM_HANDSHAKE}; +use crate::constants::{BROADCAST_INCOMPATIBILITY_MSG, Chattype, DC_FROM_HANDSHAKE}; use crate::contact::{Contact, ContactId, Origin}; use crate::context::Context; use crate::download::PostMsgMetadata; @@ -1575,14 +1574,6 @@ impl MimeFactory { mail_builder::headers::raw::Raw::new("auto-generated").into(), )); } - SystemMessage::AutocryptSetupMessage => { - headers.push(( - "Autocrypt-Setup-Message", - mail_builder::headers::raw::Raw::new("v1").into(), - )); - - placeholdertext = Some(ASM_SUBJECT.to_string()); - } SystemMessage::SecurejoinMessage => { let step = msg.param.get(Param::Arg).unwrap_or_default(); if !step.is_empty() { diff --git a/src/mimeparser.rs b/src/mimeparser.rs index e38e005f9..942facaa4 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -199,6 +199,9 @@ pub enum SystemMessage { MemberRemovedFromGroup = 5, /// Autocrypt Setup Message. + /// + /// Deprecated as of 2026-03-15, such messages should not be created + /// but may exist in the database. AutocryptSetupMessage = 6, /// Secure-join message. diff --git a/src/pgp.rs b/src/pgp.rs index 5a9471e3b..b85f6cf3c 100644 --- a/src/pgp.rs +++ b/src/pgp.rs @@ -1,15 +1,15 @@ //! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp). use std::collections::{BTreeMap, HashMap, HashSet}; -use std::io::{BufRead, Cursor}; +use std::io::Cursor; use anyhow::{Context as _, Result, ensure}; use deltachat_contact_tools::{EmailAddress, may_be_valid_addr}; use pgp::armor::BlockType; use pgp::composed::{ ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType, - Message, MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, - SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig, + MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey, + SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig, }; use pgp::crypto::aead::{AeadAlgorithm, ChunkSize}; use pgp::crypto::ecc_curve::ECCCurve; @@ -344,24 +344,6 @@ pub fn pk_validate( Ok(ret) } -/// Symmetric encryption for the autocrypt setup message (ASM). -pub async fn symm_encrypt_autocrypt_setup(passphrase: &str, plain: Vec<u8>) -> Result<String> { - let passphrase = Password::from(passphrase.to_string()); - - tokio::task::spawn_blocking(move || { - let mut rng = thread_rng(); - let s2k = StringToKey::new_default(&mut rng); - let builder = MessageBuilder::from_bytes("", plain); - let mut builder = builder.seipd_v1(&mut rng, SYMMETRIC_KEY_ALGORITHM); - builder.encrypt_with_password(s2k, &passphrase)?; - - let encoded_msg = builder.to_armored_string(&mut rng, Default::default())?; - - Ok(encoded_msg) - }) - .await? -} - /// Symmetrically encrypt the message. /// This is used for broadcast channels and for version 2 of the Securejoin protocol. /// `shared secret` is the secret that will be used for symmetric encryption. @@ -405,23 +387,6 @@ pub async fn symm_encrypt_message( .await? } -/// Symmetric decryption. -pub async fn symm_decrypt<T: BufRead + std::fmt::Debug + 'static + Send>( - passphrase: &str, - ctext: T, -) -> Result<Vec<u8>> { - let passphrase = passphrase.to_string(); - tokio::task::spawn_blocking(move || { - let (enc_msg, _) = Message::from_armor(ctext)?; - let password = Password::from(passphrase); - - let msg = enc_msg.decrypt_with_password(&password)?; - let res = msg.decompress()?.as_data_vec()?; - Ok(res) - }) - .await? -} - /// Merges and minimizes OpenPGP certificates. /// /// Keeps at most one direct key signature and @@ -596,7 +561,7 @@ mod tests { test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair}, token, }; - use pgp::composed::Esk; + use pgp::composed::{Esk, Message}; use pgp::packet::PublicKeyEncryptedSessionKey; async fn decrypt_bytes( diff --git a/src/receive_imf.rs b/src/receive_imf.rs index f11526257..b7332c945 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -729,8 +729,7 @@ pub(crate) async fn receive_imf_inner( let allow_creation = if mime_parser.decrypting_failed { false - } else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage - && is_dc_message == MessengerMessage::No + } else if is_dc_message == MessengerMessage::No && !context.get_config_bool(Config::IsChatmail).await? { // the message is a classic email in a classic profile diff --git a/test-data/message/AutocryptSetupMessage.eml b/test-data/message/AutocryptSetupMessage.eml deleted file mode 100644 index 9e147f5c6..000000000 --- a/test-data/message/AutocryptSetupMessage.eml +++ /dev/null @@ -1,77 +0,0 @@ -Return-Path: <alice@example.org> -Delivered-To: alice@example.org -Received: from hq5.merlinux.eu - by hq5.merlinux.eu with LMTP - id gNKpOrrTvF+tVAAAPzvFDg - (envelope-from <alice@example.org>) - for <alice@example.org>; Tue, 24 Nov 2020 10:34:50 +0100 -Subject: Autocrypt Setup Message -DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=testrun.org; - s=testrun; t=1606210490; - bh=MXqLqHFK1xC48pxx2TS1GUdxKSi4tdejRRSV4EAN5Tc=; - h=Subject:Date:To:From:From; - b=DRajftyu+Ycfhaxy0jXAIKCihQRMI0rxbo9+EBu6y5jhtZx13emW3odgZnvyhU6uD - IKfMXaqlmc/2HNV1/mloJVIRsIp5ORncSPX9tLykNApJVyPHg3NKdMo3Ib4NGIJ1Qo - binmLtL5qqL3bYCL68WUgieH1rcgCaf9cwck9GvwZ79pexGuWz4ItgtNWqYfapG8Zc - 9eD5maiTMNkV7UwgtOzhbBd39uKgKCoGdLAq63hoJF6dhdBBRVRyRMusAooGUZMgwm - QVuTZ76z9G8w3rDgZuHmoiICWsLsar4CDl4zAgicE6bHwtw3a7YuMiHoCtceq0RjQP - BHVaXT7B75BoA== -MIME-Version: 1.0 -Date: Tue, 24 Nov 2020 09:34:48 +0000 -Chat-Version: 1.0 -Autocrypt-Setup-Message: v1 -Message-ID: <abc@example.com> -To: <alice@example.org> -From: <alice@example.org> -Content-Type: multipart/mixed; boundary="dKhu3bbmBniQsT8W8w58YRCCiBK2YY" - - ---dKhu3bbmBniQsT8W8w58YRCCiBK2YY -Content-Type: text/plain; charset=utf-8; format=flowed; delsp=no - -This is the Autocrypt Setup Message used to transfer your end-to-end setup -between clients. - -To decrypt and use your setup, open the message in an Autocrypt-compliant -client and enter the setup code presented on the generating device. - --- -Sent with my Delta Chat Messenger: https://delta.chat - ---dKhu3bbmBniQsT8W8w58YRCCiBK2YY -Content-Type: application/autocrypt-setup -Content-Disposition: attachment; filename="autocrypt-setup-message.html" -Content-Transfer-Encoding: base64 - -PCFET0NUWVBFIGh0bWw+DQo8aHRtbD4NCiAgPGhlYWQ+DQogICAgPHRpdGxlPkF1dG9jcnlwdCBTZX -R1cCBNZXNzYWdlPC90aXRsZT4NCiAgPC9oZWFkPg0KICA8Ym9keT4NCiAgICA8aDE+QXV0b2NyeXB0 -IFNldHVwIE1lc3NhZ2U8L2gxPg0KICAgIDxwPlRoaXMgaXMgdGhlIEF1dG9jcnlwdCBTZXR1cCBNZX -NzYWdlIHVzZWQgdG8gdHJhbnNmZXIgeW91ciBlbmQtdG8tZW5kIHNldHVwIGJldHdlZW4gY2xpZW50 -cy48YnI+PGJyPlRvIGRlY3J5cHQgYW5kIHVzZSB5b3VyIHNldHVwLCBvcGVuIHRoZSBtZXNzYWdlIG -luIGFuIEF1dG9jcnlwdC1jb21wbGlhbnQgY2xpZW50IGFuZCBlbnRlciB0aGUgc2V0dXAgY29kZSBw -cmVzZW50ZWQgb24gdGhlIGdlbmVyYXRpbmcgZGV2aWNlLjwvcD4NCiAgICA8cHJlPg0KLS0tLS1CRU -dJTiBQR1AgTUVTU0FHRS0tLS0tDQpQYXNzcGhyYXNlLUZvcm1hdDogbnVtZXJpYzl4NA0KUGFzc3Bo -cmFzZS1CZWdpbjogNjIKCnd4NEVCd01JWEUzNCs4RGhtSC9nRDNNY21JTjhCSUorbmhpbDMrOFE3bF -hTd21JQnhDSnhBU2VhQUJlTGdFOTIKTi9WaER5MHlrUHFBQkp0S0xvSG9pQmxTQWZJajFRemdPeVlV -Wjl3czRtSng5OVREUE1lSnNmNHJaemJhUHZFSApQcEIrTTgyTjVhUitvV0dTcWRtUUZNQUplNWNtWX -hwM3p4eE5aTEc2cXVnRzUzOFNxNUV1TzBDSGduaXlFeEwyCkJya2hFOWVFVE1oSkNRQ3dCZDc5alhN -U2Mwcm5xYjFHbS9Kd21jbXFqVFNHMlBLTWNNcFlaV1QwQkNMaDE2TmwKTkNNbmRQWGt2cTlHd1crNX -pEMHc4cElyOERNRHk1SWVBcG83amNZR1U5UWNUR1lMWmltR2QxK1RYYlgvdGxqRQplMnNZd0hZeU5D -R1N5bHVsYi9XYnNkS2FrYXVodHJ6cUVHOXNYSkJkMnF5ZjNJajRULzdCd1pJVk42OXF1T21sCnlmMm -9PTmtYY1pCcFBJUE9ZQzdhMnJ5aFh0Q0NhbWhIVEw0czdzclg2NzJXMTVXS3VqNGVBK25URlNocFBC -cXoKb05EY3QzbG95V0hNSUluSzRha1VJeTFZak42TDFSbGwwRVhudlVQS0lkT0FpY0swbFBPaDVUZU -t6ZFMvTklyMQpQc2x6c2RyWTRZd0diMWNTdk95OXJQRFpaS3Y4d0dzbFczcFpFOCs3NnJWckllbkNY -dTdvOUZ6OFhQcVlxTGRrCkpCZGRHUGZnY0l6Um5nZjZqb0lmT0RsU2NiajR0VlgyK3htVVN5RlVhSD -RQcDFzZDgwVjhDN2xhREJ2WTc0TlAKQW9ydEVhL2xGbzQzcHNOdlhrc0JUUEVRNHFoTVZneVdQWW9V -ZGV2aUFZOGVDMmJjT0dMSFVURk5zaHZCaDFGRgozVGpIZEVRVk5zZVlqaWtZRWtkUU9Mb3B5VWdqbj -lSTUJnV2xIZTNKL1VRcmtFUkNYWi9BSVRXeGdYdmE0NHBPCkkzUHllcnF2T1lpVlJLam9JSTVIZGU4 -UFdkTnZwb2J5ZCsrTHlqN3Jxd0kyNFRwbVRwYWtIZ1RJNEJvYWtLSUcKWm1JWDhsQm4xMnQ5dlcvcD -lrbDluYWluS3Z1VFBoTk4xZmkrTE1YYTRDK1hqRXVPUnQwMFMzc01MdVo3RnBPaQprcXdGWk12RUtw -bHA3dmRLSnJNbmVzZ2dKLzBLeWc1RTJ4dVd2VFdkZUFBOE1saEJqSGlsK3JVK0dSZzdaTmxsCkxUej -RKeGpWUVl5TGpFbkhqdGU4bUVnZlNIZEE3ZDErVnV1RTZSZjlYMzRPeXhkL3NocllJSU8xY3FVdnQw -V3MKNGIwQURIN0lkbjkveTdDRjVrbWFONkMyQURBRkhFRzNIRWFZaDVNNmIwVzVJSW55WkhUQ0QxdC -tmUFdQYndxUQo0TzFRMEROZ01QT1FCRVJ0ODNXR3g5YW5GQU9YCj05dTUrCi0tLS0tRU5EIFBHUCBN -RVNTQUdFLS0tLS0KDQo8L3ByZT4NCiAgPC9ib2R5Pg0KPC9odG1sPg0K - ---dKhu3bbmBniQsT8W8w58YRCCiBK2YY-- -