mirror of
https://github.com/chatmail/core.git
synced 2026-05-08 01:16:31 +03:00
api!: remove functions for sending and receiving Autocrypt Setup Message
This commit is contained in:
@@ -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);
|
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.
|
* Signal an ongoing process to stop.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -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]
|
#[no_mangle]
|
||||||
pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
|
pub unsafe extern "C" fn dc_stop_ongoing_process(context: *mut dc_context_t) {
|
||||||
if context.is_null() {
|
if context.is_null() {
|
||||||
|
|||||||
@@ -699,25 +699,6 @@ impl CommandApi {
|
|||||||
message::estimate_deletion_cnt(&ctx, from_server, seconds).await
|
message::estimate_deletion_cnt(&ctx, from_server, seconds).await
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------
|
|
||||||
// autocrypt
|
|
||||||
// ---------------------------------------------
|
|
||||||
|
|
||||||
async fn initiate_autocrypt_key_transfer(&self, account_id: u32) -> Result<String> {
|
|
||||||
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
|
// chat list
|
||||||
// ---------------------------------------------
|
// ---------------------------------------------
|
||||||
|
|||||||
@@ -302,9 +302,7 @@ pub async fn cmdline(context: Context, line: &str, chat_id: &mut ChatId) -> Resu
|
|||||||
// TODO: reuse commands definition in main.rs.
|
// TODO: reuse commands definition in main.rs.
|
||||||
"imex" => println!(
|
"imex" => println!(
|
||||||
"====================Import/Export commands==\n\
|
"====================Import/Export commands==\n\
|
||||||
initiate-key-transfer\n\
|
|
||||||
get-setupcodebegin <msg-id>\n\
|
get-setupcodebegin <msg-id>\n\
|
||||||
continue-key-transfer <msg-id> <setup-code>\n\
|
|
||||||
has-backup\n\
|
has-backup\n\
|
||||||
export-backup\n\
|
export-backup\n\
|
||||||
import-backup <backup-file>\n\
|
import-backup <backup-file>\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" => {
|
"get-setupcodebegin" => {
|
||||||
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
ensure!(!arg1.is_empty(), "Argument <msg-id> missing.");
|
||||||
let msg_id: MsgId = MsgId::new(arg1.parse()?);
|
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.",);
|
bail!("{msg_id} is no setup message.",);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"continue-key-transfer" => {
|
|
||||||
ensure!(
|
|
||||||
!arg1.is_empty() && !arg2.is_empty(),
|
|
||||||
"Arguments <msg-id> <setup-code> expected"
|
|
||||||
);
|
|
||||||
continue_key_transfer(&context, MsgId::new(arg1.parse()?), arg2).await?;
|
|
||||||
}
|
|
||||||
"has-backup" => {
|
"has-backup" => {
|
||||||
has_backup(&context, blobdir).await?;
|
has_backup(&context, blobdir).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -149,10 +149,8 @@ impl Completer for DcHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const IMEX_COMMANDS: [&str; 13] = [
|
const IMEX_COMMANDS: [&str; 11] = [
|
||||||
"initiate-key-transfer",
|
|
||||||
"get-setupcodebegin",
|
"get-setupcodebegin",
|
||||||
"continue-key-transfer",
|
|
||||||
"has-backup",
|
"has-backup",
|
||||||
"export-backup",
|
"export-backup",
|
||||||
"import-backup",
|
"import-backup",
|
||||||
|
|||||||
@@ -483,10 +483,6 @@ class Account:
|
|||||||
passphrase = "" # Importing passphrase-protected keys is currently not supported.
|
passphrase = "" # Importing passphrase-protected keys is currently not supported.
|
||||||
self._rpc.import_self_keys(self.id, str(path), passphrase)
|
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:
|
def ice_servers(self) -> list:
|
||||||
"""Return ICE servers for WebRTC configuration."""
|
"""Return ICE servers for WebRTC configuration."""
|
||||||
ice_servers_json = self._rpc.ice_servers(self.id)
|
ice_servers_json = self._rpc.ice_servers(self.id)
|
||||||
|
|||||||
@@ -72,14 +72,6 @@ class Message:
|
|||||||
"""Return True if the message exists."""
|
"""Return True if the message exists."""
|
||||||
return bool(self._rpc.get_existing_msg_ids(self.account.id, [self.id]))
|
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:
|
def send_webxdc_status_update(self, update: Union[dict, str], description: str) -> None:
|
||||||
"""Send a webxdc status update. This message must be a webxdc."""
|
"""Send a webxdc status update. This message must be a webxdc."""
|
||||||
if not isinstance(update, str):
|
if not isinstance(update, str):
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -2844,9 +2844,7 @@ pub(crate) async fn create_send_msg_jobs(context: &Context, msg: &mut Message) -
|
|||||||
let lowercase_from = from.to_lowercase();
|
let lowercase_from = from.to_lowercase();
|
||||||
|
|
||||||
recipients.retain(|x| x.to_lowercase() != lowercase_from);
|
recipients.retain(|x| x.to_lowercase() != lowercase_from);
|
||||||
if context.get_config_bool(Config::BccSelf).await?
|
if context.get_config_bool(Config::BccSelf).await? {
|
||||||
|| msg.param.get_cmd() == SystemMessage::AutocryptSetupMessage
|
|
||||||
{
|
|
||||||
smtp::add_self_recipients(context, &mut recipients, needs_encryption).await?;
|
smtp::add_self_recipients(context, &mut recipients, needs_encryption).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,19 +234,6 @@ pub(crate) const TIMESTAMP_SENT_TOLERANCE: i64 = 60;
|
|||||||
// Newer Delta Chats will remove the prefix as needed.
|
// Newer Delta Chats will remove the prefix as needed.
|
||||||
pub(crate) const EDITED_PREFIX: &str = "✏️";
|
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.
|
/// Period between `sql::housekeeping()` runs.
|
||||||
pub(crate) const HOUSEKEEPING_PERIOD: i64 = 24 * 60 * 60;
|
pub(crate) const HOUSEKEEPING_PERIOD: i64 = 24 * 60 * 60;
|
||||||
|
|
||||||
|
|||||||
@@ -208,10 +208,10 @@ mod tests {
|
|||||||
/// Test that headers are parsed case-insensitively
|
/// Test that headers are parsed case-insensitively
|
||||||
fn test_get_header_value_case() {
|
fn test_get_header_value_case() {
|
||||||
let (headers, _) =
|
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!(
|
assert_eq!(
|
||||||
headers.get_header_value(HeaderDef::AutocryptSetupMessage),
|
headers.get_header_value(HeaderDef::AutocryptGossip),
|
||||||
Some("v99".to_string())
|
Some("fooBaR".to_string())
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
headers.get_header_value(HeaderDef::From_),
|
headers.get_header_value(HeaderDef::From_),
|
||||||
|
|||||||
@@ -1974,15 +1974,6 @@ async fn needs_move_to_mvbox(
|
|||||||
return Ok(false);
|
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 {
|
if has_chat_version {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else if let Some(parent) = get_prefetch_parent_message(context, headers).await? {
|
} else if let Some(parent) = get_prefetch_parent_message(context, headers).await? {
|
||||||
|
|||||||
@@ -105,10 +105,9 @@ async fn check_target_folder_combination(
|
|||||||
expected_destination: &str,
|
expected_destination: &str,
|
||||||
accepted_chat: bool,
|
accepted_chat: bool,
|
||||||
outgoing: bool,
|
outgoing: bool,
|
||||||
setupmessage: bool,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
println!(
|
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;
|
let t = TestContext::new_alice().await;
|
||||||
@@ -125,9 +124,7 @@ async fn check_target_folder_combination(
|
|||||||
}
|
}
|
||||||
let temp;
|
let temp;
|
||||||
|
|
||||||
let bytes = if setupmessage {
|
let bytes = {
|
||||||
include_bytes!("../../test-data/message/AutocryptSetupMessage.eml")
|
|
||||||
} else {
|
|
||||||
temp = format!(
|
temp = format!(
|
||||||
"Received: (Postfix, from userid 1000); Mon, 4 Dec 2006 14:51:39 +0100 (CET)\n\
|
"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!(
|
assert_eq!(
|
||||||
expected,
|
expected,
|
||||||
actual.as_deref(),
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -204,7 +201,6 @@ async fn test_target_folder_incoming_accepted() -> Result<()> {
|
|||||||
expected_destination,
|
expected_destination,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -221,7 +217,6 @@ async fn test_target_folder_incoming_request() -> Result<()> {
|
|||||||
expected_destination,
|
expected_destination,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@@ -239,25 +234,6 @@ async fn test_target_folder_outgoing() -> Result<()> {
|
|||||||
expected_destination,
|
expected_destination,
|
||||||
true,
|
true,
|
||||||
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?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,11 +28,9 @@ use crate::tools::{
|
|||||||
write_file,
|
write_file,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod key_transfer;
|
|
||||||
mod transfer;
|
mod transfer;
|
||||||
|
|
||||||
use ::pgp::types::KeyDetails;
|
use ::pgp::types::KeyDetails;
|
||||||
pub use key_transfer::{continue_key_transfer, initiate_key_transfer};
|
|
||||||
pub use transfer::{BackupProvider, get_backup};
|
pub use transfer::{BackupProvider, get_backup};
|
||||||
|
|
||||||
// Name of the database file in the backup.
|
// Name of the database file in the backup.
|
||||||
|
|||||||
@@ -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<String> {
|
|
||||||
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<String> {
|
|
||||||
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', "<br>");
|
|
||||||
Ok(format!(
|
|
||||||
concat!(
|
|
||||||
"<!DOCTYPE html>\r\n",
|
|
||||||
"<html>\r\n",
|
|
||||||
" <head>\r\n",
|
|
||||||
" <title>{}</title>\r\n",
|
|
||||||
" </head>\r\n",
|
|
||||||
" <body>\r\n",
|
|
||||||
" <h1>{}</h1>\r\n",
|
|
||||||
" <p>{}</p>\r\n",
|
|
||||||
" <pre>\r\n{}\r\n</pre>\r\n",
|
|
||||||
" </body>\r\n",
|
|
||||||
"</html>\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<T: std::fmt::Debug + std::io::BufRead + Send + 'static>(
|
|
||||||
passphrase: &str,
|
|
||||||
file: T,
|
|
||||||
) -> Result<String> {
|
|
||||||
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("<title>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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,8 +17,7 @@ use crate::aheader::{Aheader, EncryptPreference};
|
|||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chat::{self, Chat, PARAM_BROADCAST_SECRET, load_broadcast_secret};
|
use crate::chat::{self, Chat, PARAM_BROADCAST_SECRET, load_broadcast_secret};
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants::{ASM_SUBJECT, BROADCAST_INCOMPATIBILITY_MSG};
|
use crate::constants::{BROADCAST_INCOMPATIBILITY_MSG, Chattype, DC_FROM_HANDSHAKE};
|
||||||
use crate::constants::{Chattype, DC_FROM_HANDSHAKE};
|
|
||||||
use crate::contact::{Contact, ContactId, Origin};
|
use crate::contact::{Contact, ContactId, Origin};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::download::PostMsgMetadata;
|
use crate::download::PostMsgMetadata;
|
||||||
@@ -1575,14 +1574,6 @@ impl MimeFactory {
|
|||||||
mail_builder::headers::raw::Raw::new("auto-generated").into(),
|
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 => {
|
SystemMessage::SecurejoinMessage => {
|
||||||
let step = msg.param.get(Param::Arg).unwrap_or_default();
|
let step = msg.param.get(Param::Arg).unwrap_or_default();
|
||||||
if !step.is_empty() {
|
if !step.is_empty() {
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ pub enum SystemMessage {
|
|||||||
MemberRemovedFromGroup = 5,
|
MemberRemovedFromGroup = 5,
|
||||||
|
|
||||||
/// Autocrypt Setup Message.
|
/// Autocrypt Setup Message.
|
||||||
|
///
|
||||||
|
/// Deprecated as of 2026-03-15, such messages should not be created
|
||||||
|
/// but may exist in the database.
|
||||||
AutocryptSetupMessage = 6,
|
AutocryptSetupMessage = 6,
|
||||||
|
|
||||||
/// Secure-join message.
|
/// Secure-join message.
|
||||||
|
|||||||
43
src/pgp.rs
43
src/pgp.rs
@@ -1,15 +1,15 @@
|
|||||||
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
//! OpenPGP helper module using [rPGP facilities](https://github.com/rpgp/rpgp).
|
||||||
|
|
||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::io::{BufRead, Cursor};
|
use std::io::Cursor;
|
||||||
|
|
||||||
use anyhow::{Context as _, Result, ensure};
|
use anyhow::{Context as _, Result, ensure};
|
||||||
use deltachat_contact_tools::{EmailAddress, may_be_valid_addr};
|
use deltachat_contact_tools::{EmailAddress, may_be_valid_addr};
|
||||||
use pgp::armor::BlockType;
|
use pgp::armor::BlockType;
|
||||||
use pgp::composed::{
|
use pgp::composed::{
|
||||||
ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType,
|
ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType,
|
||||||
Message, MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey,
|
MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey,
|
||||||
SignedPublicSubKey, SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig,
|
SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig,
|
||||||
};
|
};
|
||||||
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
||||||
use pgp::crypto::ecc_curve::ECCCurve;
|
use pgp::crypto::ecc_curve::ECCCurve;
|
||||||
@@ -344,24 +344,6 @@ pub fn pk_validate(
|
|||||||
Ok(ret)
|
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.
|
/// Symmetrically encrypt the message.
|
||||||
/// This is used for broadcast channels and for version 2 of the Securejoin protocol.
|
/// 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.
|
/// `shared secret` is the secret that will be used for symmetric encryption.
|
||||||
@@ -405,23 +387,6 @@ pub async fn symm_encrypt_message(
|
|||||||
.await?
|
.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.
|
/// Merges and minimizes OpenPGP certificates.
|
||||||
///
|
///
|
||||||
/// Keeps at most one direct key signature and
|
/// Keeps at most one direct key signature and
|
||||||
@@ -596,7 +561,7 @@ mod tests {
|
|||||||
test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair},
|
test_utils::{TestContext, TestContextManager, alice_keypair, bob_keypair},
|
||||||
token,
|
token,
|
||||||
};
|
};
|
||||||
use pgp::composed::Esk;
|
use pgp::composed::{Esk, Message};
|
||||||
use pgp::packet::PublicKeyEncryptedSessionKey;
|
use pgp::packet::PublicKeyEncryptedSessionKey;
|
||||||
|
|
||||||
async fn decrypt_bytes(
|
async fn decrypt_bytes(
|
||||||
|
|||||||
@@ -729,8 +729,7 @@ pub(crate) async fn receive_imf_inner(
|
|||||||
|
|
||||||
let allow_creation = if mime_parser.decrypting_failed {
|
let allow_creation = if mime_parser.decrypting_failed {
|
||||||
false
|
false
|
||||||
} else if mime_parser.is_system_message != SystemMessage::AutocryptSetupMessage
|
} else if is_dc_message == MessengerMessage::No
|
||||||
&& is_dc_message == MessengerMessage::No
|
|
||||||
&& !context.get_config_bool(Config::IsChatmail).await?
|
&& !context.get_config_bool(Config::IsChatmail).await?
|
||||||
{
|
{
|
||||||
// the message is a classic email in a classic profile
|
// the message is a classic email in a classic profile
|
||||||
|
|||||||
@@ -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--
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user