Compare commits

..

10 Commits

Author SHA1 Message Date
link2xt
57d22f8406 feat: dc_accounts_set_push_device_token and dc_get_push_state APIs 2024-03-04 19:53:34 +00:00
link2xt
5fe6d0b106 build: increase MSRV to 1.74.0
This is what updated dependencies require.

Also update Rust used to build manylinux wheels
from 1.72.0 to 1.76.0.
2024-03-04 18:54:23 +00:00
link2xt
d40673af1d chore: cargo update 2024-03-04 18:30:20 +00:00
link2xt
a6d3ff6cf7 chore(deps): update mio to fix RUSTSEC-2024-0019 2024-03-04 18:26:18 +00:00
link2xt
15f3af418a chore(deps): update buffer-redux to remove unmaintained safemem 2024-03-04 18:25:37 +00:00
holger krekel
7cc2a09627 test: fix pytest compat (#5317)
seems pytest_report_header has changed with pytest incompatible but we
don't need it anyway so we can just leave it out.
2024-03-04 15:58:57 +00:00
link2xt
c96139b579 build: do not vendor OpenSSL when cross-compiling (#5316)
Compilation of vendored OpenSSL inside Nix is broken since
<https://github.com/alexcrichton/openssl-src-rs/pull/229> due to build
script changes.

There is anyway no need to compile vendored OpenSSL as nixpkgs already
contains OpenSSL package.

This fixes `nix build .#deltachat-rpc-server-x86_64-linux` and similar
commands which are used during releases.
2024-03-04 15:57:17 +00:00
Hocuri
c52998f2ff feat(Self-Reporting): Report number of protected/encrypted/unencrypted chats (#5292) 2024-03-04 11:03:37 +01:00
dependabot[bot]
12587684ca Merge pull request #5305 from deltachat/dependabot/cargo/tempfile-3.10.1 2024-03-03 20:44:06 +00:00
dependabot[bot]
0c1d578207 chore(cargo): bump tempfile from 3.10.0 to 3.10.1
Bumps [tempfile](https://github.com/Stebalien/tempfile) from 3.10.0 to 3.10.1.
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.10.0...v3.10.1)

---
updated-dependencies:
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-03 08:05:01 +00:00
34 changed files with 187 additions and 642 deletions

View File

@@ -1,64 +1,5 @@
# Changelog
## [1.136.0] - 2024-03-04
### Features / Changes
- Recognise Trash folder by name ([#5275](https://github.com/deltachat/deltachat-core-rust/pull/5275)).
- Send Chat-Group-Avatar as inline base64 ([#5253](https://github.com/deltachat/deltachat-core-rust/pull/5253)).
- Self-Reporting: Report number of protected/encrypted/unencrypted chats ([#5292](https://github.com/deltachat/deltachat-core-rust/pull/5292)).
### Fixes
- Don't send sync messages on self-{status,avatar} update from self-sent messages ([#5289](https://github.com/deltachat/deltachat-core-rust/pull/5289)).
- imap: Allow `maybe_network` to interrupt connection ratelimit.
- imap: Set connectivity to "connecting" only after ratelimit.
- Remove `Group-ID` from `Message-ID`.
- Prioritize protected `Message-ID` over `X-Microsoft-Original-Message-ID`.
### API-Changes
- Make `store_self_keypair` private.
- Add `ContextBuilder.build()` to build Context without opening.
- `dc_accounts_set_push_device_token` and `dc_get_push_state` APIs for iOS push notifications.
### Build system
- Tag armv6 wheels with tags accepted by PyPI.
- Unpin OpenSSL.
- Remove deprecated `unmaintained` field from deny.toml.
- Do not vendor OpenSSL when cross-compiling ([#5316](https://github.com/deltachat/deltachat-core-rust/pull/5316)).
- Increase MSRV to 1.74.0.
### CI
- Upgrade setup-python GitHub Action.
- Update to Rust 1.76 and fix clippy warnings.
- Build Python docs with Nix.
- Upload python docs without GH actions.
- Upload cffi docs without GH actions.
- Build c.delta.chat docs with nix.
### Other
- refactor: move more methods from Imap into Session.
- Add deltachat-time to sources.
### Refactor
- Remove Session from Imap structure.
- Merge ImapConfig into Imap.
- Get rid of ImapActionResult.
- Build contexts using ContextBuilder.
- Do not send `Secure-Join-Group` in `vg-request`.
### Tests
- Fix `test_verified_oneonone_chat_broken_by_device_change()` ([#5280](https://github.com/deltachat/deltachat-core-rust/pull/5280)).
- `get_protected_chat()`: Use FFIEventTracker instead of `dc_wait_next_msgs()` ([#5207](https://github.com/deltachat/deltachat-core-rust/pull/5207)).
- Fixup `tests/test_3_offline.py::TestOfflineAccountBasic::test_wrong_db`.
- Fix pytest compat ([#5317](https://github.com/deltachat/deltachat-core-rust/pull/5317)).
## [1.135.1] - 2024-02-20
### Features / Changes
@@ -3676,4 +3617,3 @@ https://github.com/deltachat/deltachat-core-rust/pulls?q=is%3Apr+is%3Aclosed
[1.134.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.133.2...v1.134.0
[1.135.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.134.0...v1.135.0
[1.135.1]: https://github.com/deltachat/deltachat-core-rust/compare/v1.135.0...v1.135.1
[1.136.0]: https://github.com/deltachat/deltachat-core-rust/compare/v1.135.1...v1.136.0

13
Cargo.lock generated
View File

@@ -1085,7 +1085,7 @@ dependencies = [
[[package]]
name = "deltachat"
version = "1.136.0"
version = "1.135.1"
dependencies = [
"ansi_term",
"anyhow",
@@ -1165,7 +1165,7 @@ dependencies = [
[[package]]
name = "deltachat-jsonrpc"
version = "1.136.0"
version = "1.135.1"
dependencies = [
"anyhow",
"async-channel 2.2.0",
@@ -1189,7 +1189,7 @@ dependencies = [
[[package]]
name = "deltachat-repl"
version = "1.136.0"
version = "1.135.1"
dependencies = [
"ansi_term",
"anyhow",
@@ -1204,7 +1204,7 @@ dependencies = [
[[package]]
name = "deltachat-rpc-server"
version = "1.136.0"
version = "1.135.1"
dependencies = [
"anyhow",
"deltachat",
@@ -1233,7 +1233,7 @@ dependencies = [
[[package]]
name = "deltachat_ffi"
version = "1.136.0"
version = "1.135.1"
dependencies = [
"anyhow",
"deltachat",
@@ -3244,7 +3244,8 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-src"
version = "300.2.3+3.2.1"
source = "git+https://github.com/link2xt/openssl-src-rs.git?branch=link2xt/copy-symlink#68efa9733ace4aae3372646838d7430b8f974c67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843"
dependencies = [
"cc",
]

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat"
version = "1.136.0"
version = "1.135.1"
edition = "2021"
license = "MPL-2.0"
rust-version = "1.74"
@@ -30,9 +30,6 @@ opt-level = "z"
codegen-units = 1
strip = true
[patch.crates-io]
openssl-src = { git = "https://github.com/link2xt/openssl-src-rs.git", branch = "link2xt/copy-symlink" }
[dependencies]
deltachat_derive = { path = "./deltachat_derive" }
deltachat-time = { path = "./deltachat-time" }

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat_ffi"
version = "1.136.0"
version = "1.135.1"
description = "Deltachat FFI"
edition = "2018"
readme = "README.md"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-jsonrpc"
version = "1.136.0"
version = "1.135.1"
description = "DeltaChat JSON-RPC API"
edition = "2021"
default-run = "deltachat-jsonrpc-server"

View File

@@ -53,5 +53,5 @@
},
"type": "module",
"types": "dist/deltachat.d.ts",
"version": "1.136.0"
"version": "1.135.1"
}

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-repl"
version = "1.136.0"
version = "1.135.1"
license = "MPL-2.0"
edition = "2021"

View File

@@ -1,6 +1,6 @@
[package]
name = "deltachat-rpc-server"
version = "1.136.0"
version = "1.135.1"
description = "DeltaChat JSON-RPC server"
edition = "2021"
readme = "README.md"

View File

@@ -168,9 +168,6 @@
auditable = false; # Avoid cargo-auditable failures.
doCheck = false; # Disable test as it requires network access.
nativeBuildInputs = [
pkgs.perl # Needed to build vendored OpenSSL.
];
CARGO_BUILD_TARGET = rustTarget;
TARGET_CC = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc";
CARGO_BUILD_RUSTFLAGS = [
@@ -180,6 +177,11 @@
CC = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc";
LD = "${pkgsCross.stdenv.cc}/bin/${pkgsCross.stdenv.cc.targetPrefix}cc";
OPENSSL_LIB_DIR = "${pkgsCross.pkgsStatic.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgsCross.pkgsStatic.openssl.dev}/include";
OPENSSL_STATIC = "1";
OPENSSL_NO_VENDOR = "1";
};
mk-aarch64-RustPackage = mkCrossRustPackage "aarch64-unknown-linux-musl" "aarch64-unknown-linux-musl";

View File

@@ -55,5 +55,5 @@
"test:mocha": "mocha node/test/test.mjs --growl --reporter=spec --bail --exit"
},
"types": "node/dist/index.d.ts",
"version": "1.136.0"
"version": "1.135.1"
}

View File

@@ -1 +1 @@
2024-03-04
2024-02-20

View File

@@ -119,9 +119,8 @@ All group members form the member list.
To allow different groups with the same members,
groups are identified by a group-id.
The group-id MUST be created only from the characters
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`,
MUST have a length of at least 11 characters
and no more than 32 characters.
`0`-`9`, `A`-`Z`, `a`-`z` `_` and `-`
and MUST have a length of at least 11 characters.
Groups MUST have a group-name.
The group-name is any non-zero-length UTF-8 string.

View File

@@ -1795,7 +1795,13 @@ impl Chat {
let mut location_id = 0;
let from = context.get_primary_self_addr().await?;
let new_rfc724_mid = create_outgoing_rfc724_mid(&from);
let new_rfc724_mid = {
let grpid = match self.typ {
Chattype::Group => Some(self.grpid.as_str()),
_ => None,
};
create_outgoing_rfc724_mid(grpid, &from)
};
if self.typ == Chattype::Single {
if let Some(id) = context
@@ -4145,7 +4151,7 @@ pub async fn add_device_msg_with_importance(
if let Some(msg) = msg {
chat_id = ChatId::get_for_contact(context, ContactId::DEVICE).await?;
let rfc724_mid = create_outgoing_rfc724_mid("@device");
let rfc724_mid = create_outgoing_rfc724_mid(None, "@device");
prepare_msg_blob(context, msg).await?;
let timestamp_sent = create_smeared_timestamp(context);
@@ -4285,7 +4291,7 @@ pub(crate) async fn add_info_msg_with_cmd(
parent: Option<&Message>,
from_id: Option<ContactId>,
) -> Result<MsgId> {
let rfc724_mid = create_outgoing_rfc724_mid("@device");
let rfc724_mid = create_outgoing_rfc724_mid(None, "@device");
let ephemeral_timer = chat_id.get_ephemeral_timer(context).await?;
let mut param = Params::new();
@@ -5926,11 +5932,11 @@ mod tests {
// Alice has an SMTP-server replacing the `Message-ID:`-header (as done eg. by outlook.com).
let sent_msg = alice.pop_sent_msg().await;
let msg = sent_msg.payload();
assert_eq!(msg.match_indices("Message-ID: <Mr.").count(), 2);
assert_eq!(msg.match_indices("References: <Mr.").count(), 1);
let msg = msg.replace("Message-ID: <Mr.", "Message-ID: <XXX");
assert_eq!(msg.match_indices("Message-ID: <Mr.").count(), 0);
assert_eq!(msg.match_indices("References: <Mr.").count(), 1);
assert_eq!(msg.match_indices("Message-ID: <Gr.").count(), 2);
assert_eq!(msg.match_indices("References: <Gr.").count(), 1);
let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX");
assert_eq!(msg.match_indices("Message-ID: <Gr.").count(), 0);
assert_eq!(msg.match_indices("References: <Gr.").count(), 1);
// Bob receives this message, he may detect group by `References:`- or `Chat-Group:`-header
receive_imf(&bob, msg.as_bytes(), false).await.unwrap();
@@ -5947,7 +5953,7 @@ mod tests {
send_text_msg(&bob, bob_chat.id, "ho!".to_string()).await?;
let sent_msg = bob.pop_sent_msg().await;
let msg = sent_msg.payload();
let msg = msg.replace("Message-ID: <Mr.", "Message-ID: <XXX");
let msg = msg.replace("Message-ID: <Gr.", "Message-ID: <XXX");
let msg = msg.replace("Chat-", "XXXX-");
assert_eq!(msg.match_indices("Chat-").count(), 0);

View File

@@ -1395,7 +1395,7 @@ mod tests {
\n\
hello\n",
contact.get_addr(),
create_outgoing_rfc724_mid(contact.get_addr())
create_outgoing_rfc724_mid(None, contact.get_addr())
);
println!("{msg}");
receive_imf(t, msg.as_bytes(), false).await.unwrap();

View File

@@ -12,7 +12,7 @@ use crate::authres::{self, DkimResults};
use crate::contact::addr_cmp;
use crate::context::Context;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::key::{self, DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
use crate::key::{DcKey, Fingerprint, SignedPublicKey, SignedSecretKey};
use crate::peerstate::Peerstate;
use crate::pgp;
@@ -264,22 +264,16 @@ pub(crate) fn validate_detached_signature<'a, 'b>(
}
}
/// Returns public keyring for `peerstate`.
pub(crate) async fn keyring_from_peerstate(
context: &Context,
peerstate: Option<&Peerstate>,
) -> Result<Vec<SignedPublicKey>> {
pub(crate) fn keyring_from_peerstate(peerstate: Option<&Peerstate>) -> Vec<SignedPublicKey> {
let mut public_keyring_for_validate = Vec::new();
if let Some(peerstate) = peerstate {
if let Some(key) = &peerstate.public_key {
public_keyring_for_validate.push(key.clone());
} else if let Some(key) = &peerstate.gossip_key {
public_keyring_for_validate.push(key.clone());
} else if context.is_self_addr(&peerstate.addr).await? {
public_keyring_for_validate = key::load_self_public_keyring(context).await?;
}
}
Ok(public_keyring_for_validate)
public_keyring_for_validate
}
/// Applies Autocrypt header to Autocrypt peer state and saves it into the database.
@@ -298,7 +292,6 @@ pub(crate) async fn get_autocrypt_peerstate(
message_time: i64,
allow_change: bool,
) -> Result<Option<Peerstate>> {
let allow_change = allow_change && !context.is_self_addr(from).await?;
let mut peerstate;
// Apply Autocrypt header

View File

@@ -95,7 +95,6 @@ impl EncryptHelper {
verified: bool,
mail_to_encrypt: lettre_email::PartBuilder,
peerstates: Vec<(Option<Peerstate>, String)>,
compress: bool,
) -> Result<String> {
let mut keyring: Vec<SignedPublicKey> = Vec::new();
@@ -136,7 +135,7 @@ impl EncryptHelper {
let raw_message = mail_to_encrypt.build().as_string().into_bytes();
let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key), compress).await?;
let ctext = pgp::pk_encrypt(&raw_message, keyring, Some(sign_key)).await?;
Ok(ctext)
}

View File

@@ -74,11 +74,6 @@ pub enum HeaderDef {
Autocrypt,
AutocryptSetupMessage,
SecureJoin,
/// Deprecated header containing Group-ID in `vg-request-with-auth` message.
///
/// It is not used by Alice as Alice knows the group corresponding to the AUTH token.
/// Bob still sends it for backwards compatibility.
SecureJoinGroup,
SecureJoinFingerprint,
SecureJoinInvitenumber,

View File

@@ -827,7 +827,7 @@ mod tests {
use crate::key;
use crate::pgp::{split_armored_data, HEADER_AUTOCRYPT, HEADER_SETUPCODE};
use crate::stock_str::StockMessage;
use crate::test_utils::{alice_keypair, TestContext, TestContextManager};
use crate::test_utils::{alice_keypair, TestContext};
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_render_setup_file() {
@@ -1095,10 +1095,6 @@ mod tests {
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();
@@ -1122,23 +1118,6 @@ mod tests {
assert!(headers.get(HEADER_SETUPCODE).is_none());
}
/// 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.to_string();
let incorrect_setupcode = "0000-0000-0000-0000-0000-0000-0000-0000-0000";
assert!(decrypt_setup_file(
incorrect_setupcode,
std::io::Cursor::new(setup_file.as_bytes()),
)
.await
.is_err());
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_key_transfer() -> Result<()> {
let alice = TestContext::new_alice().await;
@@ -1154,7 +1133,6 @@ mod tests {
alice2.configure_addr("alice@example.org").await;
alice2.recv_msg(&sent).await;
let msg = alice2.get_last_msg().await;
assert!(msg.is_setupmessage());
// Send a message that cannot be decrypted because the keys are
// not synchronized yet.
@@ -1172,25 +1150,4 @@ mod tests {
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(())
}
}

View File

@@ -101,25 +101,6 @@ pub(crate) async fn load_self_public_key(context: &Context) -> Result<SignedPubl
}
}
/// Returns our own public keyring.
pub(crate) async fn load_self_public_keyring(context: &Context) -> Result<Vec<SignedPublicKey>> {
let keys = context
.sql
.query_map(
r#"SELECT public_key
FROM keypairs
ORDER BY id=(SELECT value FROM config WHERE keyname='key_id') DESC"#,
(),
|row| row.get::<_, Vec<u8>>(0),
|keys| keys.collect::<Result<Vec<_>, _>>().map_err(Into::into),
)
.await?
.into_iter()
.filter_map(|bytes| SignedPublicKey::from_slice(&bytes).log_err(context).ok())
.collect();
Ok(keys)
}
pub(crate) async fn load_self_secret_key(context: &Context) -> Result<SignedSecretKey> {
let private_key = context
.sql

View File

@@ -359,25 +359,20 @@ impl<'a> MimeFactory<'a> {
}
}
async fn should_do_gossip(&self, context: &Context, multiple_recipients: bool) -> Result<bool> {
async fn should_do_gossip(&self, context: &Context) -> Result<bool> {
match &self.loaded {
Loaded::Message { chat } => {
let cmd = self.msg.param.get_cmd();
if cmd == SystemMessage::MemberAddedToGroup
|| cmd == SystemMessage::SecurejoinMessage
{
// beside key- and member-changes, force a periodic re-gossip.
let gossiped_timestamp = chat.id.get_gossiped_timestamp(context).await?;
let gossip_period = context.get_config_i64(Config::GossipPeriod).await?;
if time() >= gossiped_timestamp + gossip_period {
Ok(true)
} else if multiple_recipients {
// beside key- and member-changes, force a periodic re-gossip.
let gossiped_timestamp = chat.id.get_gossiped_timestamp(context).await?;
let gossip_period = context.get_config_i64(Config::GossipPeriod).await?;
if time() >= gossiped_timestamp + gossip_period {
Ok(true)
} else {
Ok(false)
}
} else {
Ok(false)
let cmd = self.msg.param.get_cmd();
// Do gossip in all Securejoin messages not to complicate the code. There's no
// need in gossips in "vg-auth-required" messages f.e., but let them be.
Ok(cmd == SystemMessage::MemberAddedToGroup
|| cmd == SystemMessage::SecurejoinMessage)
}
}
Loaded::Mdn { .. } => Ok(false),
@@ -556,7 +551,7 @@ impl<'a> MimeFactory<'a> {
let rfc724_mid = match self.loaded {
Loaded::Message { .. } => self.msg.rfc724_mid.clone(),
Loaded::Mdn { .. } => create_outgoing_rfc724_mid(&self.from_addr),
Loaded::Mdn { .. } => create_outgoing_rfc724_mid(None, &self.from_addr),
};
let rfc724_mid_headervalue = render_rfc724_mid(&rfc724_mid);
let rfc724_mid_header = Header::new("Message-ID".into(), rfc724_mid_headervalue);
@@ -703,9 +698,9 @@ impl<'a> MimeFactory<'a> {
.fold(message, |message, header| message.header(header));
// Add gossip headers in chats with multiple recipients
let multiple_recipients =
peerstates.len() > 1 || context.get_config_bool(Config::BccSelf).await?;
if self.should_do_gossip(context, multiple_recipients).await? {
if (peerstates.len() > 1 || context.get_config_bool(Config::BccSelf).await?)
&& self.should_do_gossip(context).await?
{
for peerstate in peerstates.iter().filter_map(|(state, _)| state.as_ref()) {
if let Some(header) = peerstate.render_gossip_header(verified) {
message = message.header(Header::new("Autocrypt-Gossip".into(), header));
@@ -739,12 +734,8 @@ impl<'a> MimeFactory<'a> {
);
}
// Disable compression for SecureJoin to ensure
// there are no compression side channels
// leaking information about the tokens.
let compress = self.msg.param.get_cmd() != SystemMessage::SecurejoinMessage;
let encrypted = encrypt_helper
.encrypt(context, verified, message, peerstates, compress)
.encrypt(context, verified, message, peerstates)
.await?;
outer_message

View File

@@ -275,18 +275,6 @@ impl MimeMessage {
}
}
// Overwrite Message-ID with X-Microsoft-Original-Message-ID.
// However if we later find Message-ID in the protected part,
// it will overwrite both.
if let Some(microsoft_message_id) =
headers.remove(HeaderDef::XMicrosoftOriginalMessageId.get_headername())
{
headers.insert(
HeaderDef::MessageId.get_headername().to_string(),
microsoft_message_id,
);
}
// Remove headers that are allowed _only_ in the encrypted+signed part. It's ok to leave
// them in signed-only emails, but has no value currently.
Self::remove_secured_headers(&mut headers);
@@ -304,8 +292,7 @@ impl MimeMessage {
hop_info += "\n\n";
hop_info += &decryption_info.dkim_results.to_string();
let public_keyring =
keyring_from_peerstate(context, decryption_info.peerstate.as_ref()).await?;
let public_keyring = keyring_from_peerstate(decryption_info.peerstate.as_ref());
let (mail, mut signatures, encrypted) = match tokio::task::block_in_place(|| {
try_decrypt(&mail, &private_keyring, &public_keyring)
}) {
@@ -348,20 +335,9 @@ impl MimeMessage {
gossip_headers,
)
.await?;
// Remove unsigned opportunistically protected headers from messages considered
// Autocrypt-encrypted / displayed with padlock.
// For "Subject" see <https://github.com/deltachat/deltachat-core-rust/issues/1790>.
for h in [
HeaderDef::Subject,
HeaderDef::ChatGroupId,
HeaderDef::ChatGroupName,
HeaderDef::ChatGroupNameChanged,
HeaderDef::ChatGroupAvatar,
HeaderDef::ChatGroupMemberRemoved,
HeaderDef::ChatGroupMemberAdded,
] {
headers.remove(h.get_headername());
}
// Remove unsigned subject from messages displayed with padlock.
// See <https://github.com/deltachat/deltachat-core-rust/issues/1790>.
headers.remove("subject");
}
// let known protected headers from the decrypted
@@ -389,20 +365,13 @@ impl MimeMessage {
// signed part, but it doesn't match the outer one.
// This _might_ be because the sender's mail server
// replaced the sending address, e.g. in a mailing list.
// Or it's because someone is doing some replay attack.
// Resending encrypted messages via mailing lists
// without reencrypting is not useful anyway,
// so we return an error below.
// Or it's because someone is doing some replay attack
// - OTOH, I can't come up with an attack scenario
// where this would be useful.
warn!(
context,
"From header in signed part doesn't match the outer one",
);
// Return an error from the parser.
// This will result in creating a tombstone
// and no further message processing
// as if the MIME structure is broken.
bail!("From header is forged");
}
}
}
@@ -521,7 +490,7 @@ impl MimeMessage {
/// Parses system messages.
fn parse_system_message_headers(&mut self, context: &Context) {
if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() && !self.incoming {
if self.get_header(HeaderDef::AutocryptSetupMessage).is_some() {
self.parts.retain(|part| {
part.mimetype.is_none()
|| part.mimetype.as_ref().unwrap().as_ref() == MIME_AC_SETUP_FILE
@@ -1410,22 +1379,14 @@ impl MimeMessage {
}
pub(crate) fn get_rfc724_mid(&self) -> Option<String> {
self.get_header(HeaderDef::MessageId)
self.get_header(HeaderDef::XMicrosoftOriginalMessageId)
.or_else(|| self.get_header(HeaderDef::MessageId))
.and_then(|msgid| parse_message_id(msgid).ok())
}
fn remove_secured_headers(headers: &mut HashMap<String, String>) {
headers.remove("secure-join-fingerprint");
headers.remove("secure-join-auth");
headers.remove("chat-verified");
headers.remove("autocrypt-gossip");
// Secure-Join is secured unless it is an initial "vc-request"/"vg-request".
if let Some(secure_join) = headers.remove("secure-join") {
if secure_join == "vc-request" || secure_join == "vg-request" {
headers.insert("secure-join".to_string(), secure_join);
}
}
}
fn merge_headers(
@@ -1850,8 +1811,6 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
}
}
/// Returns true if the header overwrites outer header
/// when it comes from protected headers.
fn is_known(key: &str) -> bool {
matches!(
key,
@@ -1867,7 +1826,6 @@ fn is_known(key: &str) -> bool {
| "in-reply-to"
| "references"
| "subject"
| "secure-join"
)
}
@@ -2257,12 +2215,11 @@ mod tests {
use super::*;
use crate::{
chat,
chatlist::Chatlist,
constants::{Blocked, DC_DESIRED_TEXT_LEN, DC_ELLIPSIS},
message::{Message, MessageState, MessengerMessage},
receive_imf::receive_imf,
test_utils::{TestContext, TestContextManager},
test_utils::TestContext,
tools::time,
};
@@ -3581,29 +3538,6 @@ On 2020-10-25, Bob wrote:
);
}
/// Tests that X-Microsoft-Original-Message-ID does not overwrite encrypted Message-ID.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_x_microsoft_original_message_id_precedence() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let bob_chat_id = tcm.send_recv_accept(&alice, &bob, "hi").await.chat_id;
chat::send_text_msg(&bob, bob_chat_id, "hi!".to_string()).await?;
let mut sent_msg = bob.pop_sent_msg().await;
// Insert X-Microsoft-Original-Message-ID.
// It should be ignored because there is a Message-ID in the encrypted part.
sent_msg.payload = sent_msg.payload.replace(
"Message-ID:",
"X-Microsoft-Original-Message-ID: <fake-message-id@example.net>\r\nMessage-ID:",
);
let msg = alice.recv_msg(&sent_msg).await;
assert!(!msg.rfc724_mid.contains("fake-message-id"));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_long_in_reply_to() -> Result<()> {
let t = TestContext::new_alice().await;

View File

@@ -87,7 +87,7 @@ pub enum Param {
/// `Secure-Join-Fingerprint` header for `{vc,vg}-request-with-auth` messages.
Arg3 = b'G',
/// Deprecated `Secure-Join-Group` header for messages.
/// For Messages
Arg4 = b'H',
/// For Messages

View File

@@ -16,7 +16,6 @@ use crate::message::Message;
use crate::mimeparser::SystemMessage;
use crate::sql::Sql;
use crate::stock_str;
use crate::tools;
/// Type of the public key stored inside the peerstate.
#[derive(Debug)]
@@ -166,9 +165,6 @@ impl Peerstate {
/// Loads peerstate corresponding to the given address from the database.
pub async fn from_addr(context: &Context, addr: &str) -> Result<Option<Peerstate>> {
if context.is_self_addr(addr).await? {
return Ok(Some(Peerstate::get_self_stub(addr)));
}
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint, \
@@ -186,7 +182,6 @@ impl Peerstate {
context: &Context,
fingerprint: &Fingerprint,
) -> Result<Option<Peerstate>> {
// NOTE: If it's our key fingerprint, this returns None currently.
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint, \
@@ -211,9 +206,6 @@ impl Peerstate {
fingerprint: &Fingerprint,
addr: &str,
) -> Result<Option<Peerstate>> {
if context.is_self_addr(addr).await? {
return Ok(Some(Peerstate::get_self_stub(addr)));
}
let query = "SELECT addr, last_seen, last_seen_autocrypt, prefer_encrypted, public_key, \
gossip_timestamp, gossip_key, public_key_fingerprint, gossip_key_fingerprint, \
verified_key, verified_key_fingerprint, \
@@ -229,34 +221,6 @@ impl Peerstate {
Self::from_stmt(context, query, (&fp, &addr, &fp)).await
}
/// Returns peerstate stub for self `addr`.
///
/// Needed for [`crate::decrypt::keyring_from_peerstate()`] which returns a keyring of all our
/// pubkeys for such a stub so that we can check if a message is signed by us.
fn get_self_stub(addr: &str) -> Self {
let now = tools::time();
// We can have multiple pubkeys, just make the corresponding fields None.
Self {
addr: addr.to_string(),
last_seen: now,
last_seen_autocrypt: now,
prefer_encrypt: EncryptPreference::Mutual,
public_key: None,
public_key_fingerprint: None,
gossip_key: None,
gossip_key_fingerprint: None,
gossip_timestamp: 0,
verified_key: None,
verified_key_fingerprint: None,
verifier: None,
secondary_verified_key: None,
secondary_verified_key_fingerprint: None,
secondary_verifier: None,
backward_verified_key_id: None,
fingerprint_changed: false,
}
}
async fn from_stmt(
context: &Context,
query: &str,

View File

@@ -236,7 +236,6 @@ pub async fn pk_encrypt(
plain: &[u8],
public_keys_for_encryption: Vec<SignedPublicKey>,
private_key_for_signing: Option<SignedSecretKey>,
compress: bool,
) -> Result<String> {
let lit_msg = Message::new_literal_bytes("", plain);
@@ -250,19 +249,20 @@ pub async fn pk_encrypt(
let mut rng = thread_rng();
// TODO: measure time
let encrypted_msg = if let Some(ref skey) = private_key_for_signing {
let signed_msg = lit_msg.sign(skey, || "".into(), HASH_ALGORITHM)?;
let compressed_msg = if compress {
signed_msg.compress(CompressionAlgorithm::ZLIB)?
} else {
signed_msg
};
compressed_msg.encrypt_to_keys(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)?
lit_msg
.sign(skey, || "".into(), HASH_ALGORITHM)
.and_then(|msg| msg.compress(CompressionAlgorithm::ZLIB))
.and_then(|msg| {
msg.encrypt_to_keys(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)
})
} else {
lit_msg.encrypt_to_keys(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)?
lit_msg.encrypt_to_keys(&mut rng, SYMMETRIC_KEY_ALGORITHM, &pkeys_refs)
};
let encoded_msg = encrypted_msg.to_armored_string(None)?;
let msg = encrypted_msg?;
let encoded_msg = msg.to_armored_string(None)?;
Ok(encoded_msg)
})
@@ -484,16 +484,10 @@ mod tests {
CTEXT_SIGNED
.get_or_init(|| async {
let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
let compress = true;
pk_encrypt(
CLEARTEXT,
keyring,
Some(KEYS.alice_secret.clone()),
compress,
)
.await
.unwrap()
pk_encrypt(CLEARTEXT, keyring, Some(KEYS.alice_secret.clone()))
.await
.unwrap()
})
.await
}
@@ -503,11 +497,7 @@ mod tests {
CTEXT_UNSIGNED
.get_or_init(|| async {
let keyring = vec![KEYS.alice_public.clone(), KEYS.bob_public.clone()];
let compress = true;
pk_encrypt(CLEARTEXT, keyring, None, compress)
.await
.unwrap()
pk_encrypt(CLEARTEXT, keyring, None).await.unwrap()
})
.await
}

View File

@@ -23,7 +23,6 @@ use crate::message::Message;
use crate::peerstate::Peerstate;
use crate::socks::Socks5Config;
use crate::token;
use crate::tools::validate_id;
const OPENPGP4FPR_SCHEME: &str = "OPENPGP4FPR:"; // yes: uppercase
const IDELTACHAT_SCHEME: &str = "https://i.delta.chat/#";
@@ -250,6 +249,8 @@ fn starts_with_ignore_case(string: &str, pattern: &str) -> bool {
/// The function should be called after a QR code is scanned.
/// The function takes the raw text scanned and checks what can be done with it.
pub async fn check_qr(context: &Context, qr: &str) -> Result<Qr> {
info!(context, "Scanned QR code: {}", qr);
let qrcode = if starts_with_ignore_case(qr, OPENPGP4FPR_SCHEME) {
decode_openpgp(context, qr)
.await
@@ -346,18 +347,9 @@ async fn decode_openpgp(context: &Context, qr: &str) -> Result<Qr> {
"".to_string()
};
let invitenumber = param
.get("i")
.filter(|&s| validate_id(s))
.map(|s| s.to_string());
let authcode = param
.get("s")
.filter(|&s| validate_id(s))
.map(|s| s.to_string());
let grpid = param
.get("x")
.filter(|&s| validate_id(s))
.map(|s| s.to_string());
let invitenumber = param.get("i").map(|s| s.to_string());
let authcode = param.get("s").map(|s| s.to_string());
let grpid = param.get("x").map(|s| s.to_string());
let grpname = if grpid.is_some() {
if let Some(encoded_name) = param.get("g") {
@@ -482,7 +474,8 @@ fn decode_account(qr: &str) -> Result<Qr> {
let payload = qr
.get(DCACCOUNT_SCHEME.len()..)
.context("invalid DCACCOUNT payload")?;
let url = url::Url::parse(payload).context("Invalid account URL")?;
let url =
url::Url::parse(payload).with_context(|| format!("Invalid account URL: {payload:?}"))?;
if url.scheme() == "http" || url.scheme() == "https" {
Ok(Qr::Account {
domain: url
@@ -491,7 +484,7 @@ fn decode_account(qr: &str) -> Result<Qr> {
.to_string(),
})
} else {
bail!("Bad scheme for account URL: {:?}.", url.scheme());
bail!("Bad scheme for account URL: {:?}.", payload);
}
}
@@ -502,7 +495,8 @@ fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
.context("invalid DCWEBRTC payload")?;
let (_type, url) = Message::parse_webrtc_instance(payload);
let url = url::Url::parse(&url).context("Invalid WebRTC instance")?;
let url =
url::Url::parse(&url).with_context(|| format!("Invalid WebRTC instance: {payload:?}"))?;
if url.scheme() == "http" || url.scheme() == "https" {
Ok(Qr::WebrtcInstance {
@@ -513,7 +507,7 @@ fn decode_webrtc_instance(_context: &Context, qr: &str) -> Result<Qr> {
instance_pattern: payload.to_string(),
})
} else {
bail!("Bad URL scheme for WebRTC instance: {:?}", url.scheme());
bail!("Bad URL scheme for WebRTC instance: {:?}", payload);
}
}
@@ -555,15 +549,16 @@ async fn set_account_from_qr(context: &Context, qr: &str) -> Result<()> {
.send()
.await?;
let response_status = response.status();
let response_text = response
.text()
.await
.context("Cannot create account, request failed: empty response")?;
let response_text = response.text().await.with_context(|| {
format!("Cannot create account, request to {url_str:?} failed: empty response")
})?;
if response_status.is_success() {
let CreateAccountSuccessResponse { password, email } = serde_json::from_str(&response_text)
.with_context(|| {
format!("Cannot create account, response is malformed:\n{response_text:?}")
format!(
"Cannot create account, response from {url_str:?} is malformed:\n{response_text:?}"
)
})?;
context
.set_config_internal(Config::Addr, Some(&email))
@@ -658,7 +653,7 @@ pub async fn set_config_from_qr(context: &Context, qr: &str) -> Result<()> {
Qr::Login { address, options } => {
configure_from_login_qr(context, &address, options).await?
}
_ => bail!("QR code does not contain config"),
_ => bail!("qr code {:?} does not contain config", qr),
}
Ok(())
@@ -1045,21 +1040,6 @@ mod tests {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decode_openpgp_invalid_token() -> Result<()> {
let ctx = TestContext::new().await;
// Token cannot contain "/"
let qr = check_qr(
&ctx.ctx,
"OPENPGP4FPR:79252762C34C5096AF57958F4FC3D21A81B0F0A7#a=cli%40deltachat.de&g=test%20%3F+test%20%21&x=h-0oKQf2CDK&i=9JEXlxAqGM0&s=0V7LzL/cxRL"
).await?;
assert!(matches!(qr, Qr::FprMismatch { .. }));
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_decode_openpgp_secure_join() -> Result<()> {
let ctx = TestContext::new().await;

View File

@@ -38,9 +38,7 @@ use crate::simplify;
use crate::sql;
use crate::stock_str;
use crate::sync::Sync::*;
use crate::tools::{
self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters, validate_id,
};
use crate::tools::{self, buf_compress, extract_grpid_from_rfc724_mid, strip_rtlo_characters};
use crate::{contact, imap};
/// This is the struct that is returned after receiving one email (aka MIME message).
@@ -2358,10 +2356,7 @@ async fn apply_mailinglist_changes(
}
fn try_getting_grpid(mime_parser: &MimeMessage) -> Option<String> {
if let Some(optional_field) = mime_parser
.get_header(HeaderDef::ChatGroupId)
.filter(|s| validate_id(s))
{
if let Some(optional_field) = mime_parser.get_header(HeaderDef::ChatGroupId) {
return Some(optional_field.clone());
}

View File

@@ -12,7 +12,6 @@ use crate::config::Config;
use crate::constants::{DC_GCL_FOR_FORWARDING, DC_GCL_NO_SPECIALS};
use crate::download::{DownloadState, MIN_DOWNLOAD_LIMIT};
use crate::imap::prefetch_should_download;
use crate::imex::{imex, ImexMode};
use crate::message::{self, Message};
use crate::test_utils::{get_chat_msg, TestContext, TestContextManager};
@@ -1693,7 +1692,7 @@ async fn test_in_reply_to_two_member_group() {
Subject: foo\n\
Message-ID: <message@example.org>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: foobarbaz12\n\
Chat-Group-ID: foo\n\
Chat-Group-Name: foo\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
\n\
@@ -1738,7 +1737,7 @@ async fn test_in_reply_to_two_member_group() {
Message-ID: <chatreply@example.org>\n\
In-Reply-To: <message@example.org>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: foobarbaz12\n\
Chat-Group-ID: foo\n\
Date: Sun, 22 Mar 2020 22:37:57 +0000\n\
\n\
chat reply\n",
@@ -3547,32 +3546,6 @@ async fn test_mua_user_adds_recipient_to_single_chat() -> Result<()> {
Ok(())
}
/// If a message is Autocrypt-encrypted, unsigned Chat-Group-* headers have no effect.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_unsigned_chat_group_hdr() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let bob_addr = bob.get_config(Config::Addr).await?.unwrap();
let bob_id = Contact::create(alice, "Bob", &bob_addr).await?;
let alice_chat_id = create_group_chat(alice, ProtectionStatus::Unprotected, "foos").await?;
add_contact_to_chat(alice, alice_chat_id, bob_id).await?;
send_text_msg(alice, alice_chat_id, "populate".to_string()).await?;
let sent_msg = alice.pop_sent_msg().await;
let bob_chat_id = bob.recv_msg(&sent_msg).await.chat_id;
bob_chat_id.accept(bob).await?;
send_text_msg(bob, bob_chat_id, "hi all!".to_string()).await?;
let mut sent_msg = bob.pop_sent_msg().await;
sent_msg.payload = sent_msg.payload.replace(
"Chat-Version:",
&format!("Chat-Group-Member-Removed: {bob_addr}\r\nChat-Version:"),
);
let chat_id = alice.recv_msg(&sent_msg).await.chat_id;
assert_eq!(chat_id, alice_chat_id);
assert_eq!(get_chat_contacts(alice, alice_chat_id).await?.len(), 2);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_sync_member_list_on_rejoin() -> Result<()> {
let mut tcm = TestContextManager::new();
@@ -4043,37 +4016,6 @@ async fn test_download_later() -> Result<()> {
Ok(())
}
/// Malice can pretend they have the same address as Alice and sends a message encrypted to Alice's
/// key but signed with another one. Alice must detect that this message is wrongly signed and not
/// treat it as Autocrypt-encrypted.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_outgoing_msg_forgery() -> Result<()> {
let mut tcm = TestContextManager::new();
let export_dir = tempfile::tempdir().unwrap();
let alice = &tcm.alice().await;
let alice_addr = &alice.get_config(Config::Addr).await?.unwrap();
imex(alice, ImexMode::ExportSelfKeys, export_dir.path(), None).await?;
// We need Bob only to encrypt the forged message to Alice's key, actually Bob doesn't
// participate in the scenario.
let bob = &TestContext::new().await;
bob.configure_addr("bob@example.net").await;
imex(bob, ImexMode::ImportSelfKeys, export_dir.path(), None).await?;
let malice = &TestContext::new().await;
malice.configure_addr(alice_addr).await;
let malice_chat_id = tcm
.send_recv_accept(bob, malice, "hi from bob")
.await
.chat_id;
let sent_msg = malice.send_text(malice_chat_id, "hi from malice").await;
let msg = alice.recv_msg(&sent_msg).await;
assert_eq!(msg.state, MessageState::OutDelivered);
assert!(!msg.get_showpadlock());
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_create_group_with_big_msg() -> Result<()> {
let mut tcm = TestContextManager::new();
@@ -4267,22 +4209,3 @@ Chat-Group-Member-Added: charlie@example.com",
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_forged_from() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let bob_chat_id = tcm.send_recv_accept(&alice, &bob, "hi").await.chat_id;
chat::send_text_msg(&bob, bob_chat_id, "hi!".to_string()).await?;
let mut sent_msg = bob.pop_sent_msg().await;
sent_msg.payload = sent_msg
.payload
.replace("bob@example.net", "notbob@example.net");
let msg = alice.recv_msg(&sent_msg).await;
assert!(msg.chat_id.is_trash());
Ok(())
}

View File

@@ -124,7 +124,8 @@ pub async fn get_securejoin_qr(context: &Context, group: Option<ChatId>) -> Resu
)
};
info!(context, "Generated QR code.");
info!(context, "Generated QR code: {}", qr);
Ok(qr)
}
@@ -295,23 +296,6 @@ pub(crate) async fn handle_securejoin_handshake(
let join_vg = step.starts_with("vg-");
if !matches!(step.as_str(), "vg-request" | "vc-request") {
let mut self_found = false;
let self_fingerprint = load_self_public_key(context).await?.fingerprint();
for (addr, key) in &mime_message.gossiped_keys {
if key.fingerprint() == self_fingerprint && context.is_self_addr(addr).await? {
self_found = true;
break;
}
}
if !self_found {
// This message isn't intended for us. Possibly the peer doesn't own the key which the
// message is signed with but forwarded someone's message to us.
warn!(context, "Step {step}: No self addr+pubkey gossip found.");
return Ok(HandshakeMessage::Ignore);
}
}
match step.as_str() {
"vg-request" | "vc-request" => {
/*=======================================================
@@ -414,7 +398,7 @@ pub(crate) async fn handle_securejoin_handshake(
.await?;
return Ok(HandshakeMessage::Ignore);
};
let Some(group_chat_id) = token::auth_chat_id(context, auth).await? else {
if !token::exists(context, token::Namespace::Auth, auth).await? {
could_not_establish_secure_connection(
context,
contact_id,
@@ -423,8 +407,7 @@ pub(crate) async fn handle_securejoin_handshake(
)
.await?;
return Ok(HandshakeMessage::Ignore);
};
}
let contact_addr = Contact::get_by_id(context, contact_id)
.await?
.get_addr()
@@ -452,8 +435,41 @@ pub(crate) async fn handle_securejoin_handshake(
info!(context, "Auth verified.",);
context.emit_event(EventType::ContactsChanged(Some(contact_id)));
inviter_progress(context, contact_id, 600);
if group_chat_id.is_unset() {
// Setup verified contact.
if join_vg {
// the vg-member-added message is special:
// this is a normal Chat-Group-Member-Added message
// with an additional Secure-Join header
let field_grpid = match mime_message.get_header(HeaderDef::SecureJoinGroup) {
Some(s) => s.as_str(),
None => {
warn!(context, "Missing Secure-Join-Group header");
return Ok(HandshakeMessage::Ignore);
}
};
match chat::get_chat_id_by_grpid(context, field_grpid).await? {
Some((group_chat_id, _, _)) => {
secure_connection_established(
context,
contact_id,
group_chat_id,
mime_message.timestamp_sent,
)
.await?;
chat::add_contact_to_chat_ex(
context,
Nosync,
group_chat_id,
contact_id,
true,
)
.await?;
}
None => bail!("Chat {} not found", &field_grpid),
}
inviter_progress(context, contact_id, 800);
inviter_progress(context, contact_id, 1000);
} else {
// Alice -> Bob
secure_connection_established(
context,
contact_id,
@@ -466,19 +482,6 @@ pub(crate) async fn handle_securejoin_handshake(
.context("failed sending vc-contact-confirm message")?;
inviter_progress(context, contact_id, 1000);
} else {
// Join group.
secure_connection_established(
context,
contact_id,
group_chat_id,
mime_message.timestamp_sent,
)
.await?;
chat::add_contact_to_chat_ex(context, Nosync, group_chat_id, contact_id, true)
.await?;
inviter_progress(context, contact_id, 800);
inviter_progress(context, contact_id, 1000);
}
Ok(HandshakeMessage::Ignore) // "Done" would delete the message and break multi-device (the key from Autocrypt-header is needed)
}
@@ -770,32 +773,19 @@ mod tests {
use crate::tools::{EmailAddress, SystemTime};
use std::time::Duration;
#[derive(PartialEq)]
enum SetupContactCase {
Normal,
CheckProtectionTimestamp,
WrongAliceGossip,
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_setup_contact() {
test_setup_contact_ex(SetupContactCase::Normal).await
test_setup_contact_ex(false).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_setup_contact_protection_timestamp() {
test_setup_contact_ex(SetupContactCase::CheckProtectionTimestamp).await
test_setup_contact_ex(true).await
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_setup_contact_wrong_alice_gossip() {
test_setup_contact_ex(SetupContactCase::WrongAliceGossip).await
}
async fn test_setup_contact_ex(case: SetupContactCase) {
async fn test_setup_contact_ex(check_protection_timestamp: bool) {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let alice_addr = &alice.get_config(Config::Addr).await.unwrap().unwrap();
let bob = tcm.bob().await;
alice
.set_config(Config::VerifiedOneOnOneChats, Some("1"))
@@ -828,7 +818,10 @@ mod tests {
);
let sent = bob.pop_sent_msg().await;
assert_eq!(sent.recipient(), EmailAddress::new(alice_addr).unwrap());
assert_eq!(
sent.recipient(),
EmailAddress::new("alice@example.org").unwrap()
);
let msg = alice.parse_msg(&sent).await;
assert!(!msg.was_encrypted());
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vc-request");
@@ -866,7 +859,7 @@ mod tests {
progress,
} => {
let alice_contact_id =
Contact::lookup_id_by_addr(&bob.ctx, alice_addr, Origin::Unknown)
Contact::lookup_id_by_addr(&bob.ctx, "alice@example.org", Origin::Unknown)
.await
.expect("Error looking up contact")
.expect("Contact not found");
@@ -878,7 +871,7 @@ mod tests {
// Check Bob sent the right message.
let sent = bob.pop_sent_msg().await;
let mut msg = alice.parse_msg(&sent).await;
let msg = alice.parse_msg(&sent).await;
let vc_request_with_auth_ts_sent = msg
.get_header(HeaderDef::Date)
.and_then(|value| mailparse::dateparse(value).ok())
@@ -895,30 +888,6 @@ mod tests {
bob_fp.hex()
);
if case == SetupContactCase::WrongAliceGossip {
let wrong_pubkey = load_self_public_key(&bob).await.unwrap();
let alice_pubkey = msg
.gossiped_keys
.insert(alice_addr.to_string(), wrong_pubkey)
.unwrap();
let contact_bob = alice.add_or_lookup_contact(&bob).await;
let handshake_msg = handle_securejoin_handshake(&alice, &msg, contact_bob.id)
.await
.unwrap();
assert_eq!(handshake_msg, HandshakeMessage::Ignore);
assert_eq!(contact_bob.is_verified(&alice.ctx).await.unwrap(), false);
msg.gossiped_keys
.insert(alice_addr.to_string(), alice_pubkey)
.unwrap();
let handshake_msg = handle_securejoin_handshake(&alice, &msg, contact_bob.id)
.await
.unwrap();
assert_eq!(handshake_msg, HandshakeMessage::Ignore);
assert!(contact_bob.is_verified(&alice.ctx).await.unwrap());
return;
}
// Alice should not yet have Bob verified
let contact_bob_id =
Contact::lookup_id_by_addr(&alice.ctx, "bob@example.net", Origin::Unknown)
@@ -930,7 +899,7 @@ mod tests {
.unwrap();
assert_eq!(contact_bob.is_verified(&alice.ctx).await.unwrap(), false);
if case == SetupContactCase::CheckProtectionTimestamp {
if check_protection_timestamp {
SystemTime::shift(Duration::from_secs(3600));
}
@@ -959,7 +928,7 @@ mod tests {
assert!(msg.is_info());
let expected_text = chat_protection_enabled(&alice).await;
assert_eq!(msg.get_text(), expected_text);
if case == SetupContactCase::CheckProtectionTimestamp {
if check_protection_timestamp {
assert_eq!(msg.timestamp_sort, vc_request_with_auth_ts_sent);
}
}
@@ -974,10 +943,11 @@ mod tests {
);
// Bob should not yet have Alice verified
let contact_alice_id = Contact::lookup_id_by_addr(&bob.ctx, alice_addr, Origin::Unknown)
.await
.expect("Error looking up contact")
.expect("Contact not found");
let contact_alice_id =
Contact::lookup_id_by_addr(&bob.ctx, "alice@example.org", Origin::Unknown)
.await
.expect("Error looking up contact")
.expect("Contact not found");
let contact_alice = Contact::get_by_id(&bob.ctx, contact_alice_id)
.await
.unwrap();
@@ -1175,14 +1145,6 @@ mod tests {
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vg-request");
assert!(msg.get_header(HeaderDef::SecureJoinInvitenumber).is_some());
// Old Delta Chat core sent `Secure-Join-Group` header in `vg-request`,
// but it was only used by Alice in `vg-request-with-auth`.
// New Delta Chat versions do not use `Secure-Join-Group` header at all
// and it is deprecated.
// Now `Secure-Join-Group` header
// is only sent in `vg-request-with-auth` for compatibility.
assert!(msg.get_header(HeaderDef::SecureJoinGroup).is_none());
// Step 3: Alice receives vg-request, sends vg-auth-required
alice.recv_msg(&sent).await;

View File

@@ -378,21 +378,14 @@ async fn send_handshake_message(
// Sends our own fingerprint in the Secure-Join-Fingerprint header.
let bob_fp = load_self_public_key(context).await?.fingerprint();
msg.param.set(Param::Arg3, bob_fp.hex());
// Sends the grpid in the Secure-Join-Group header.
//
// `Secure-Join-Group` header is deprecated,
// but old Delta Chat core requires that Alice receives it.
//
// Previous Delta Chat core also sent `Secure-Join-Group` header
// in `vg-request` messages,
// but it was not used on the receiver.
if let QrInvite::Group { ref grpid, .. } = invite {
msg.param.set(Param::Arg4, grpid);
}
}
};
// Sends the grpid in the Secure-Join-Group header.
if let QrInvite::Group { ref grpid, .. } = invite {
msg.param.set(Param::Arg4, grpid);
}
chat::send_msg(context, chat_id, &mut msg).await?;
Ok(())
}

View File

@@ -97,7 +97,7 @@ impl TryFrom<Qr> for QrInvite {
invitenumber,
authcode,
}),
_ => bail!("Unsupported QR type"),
_ => bail!("Unsupported QR type {:?}", qr),
}
}
}

View File

@@ -114,25 +114,6 @@ pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> Res
Ok(exists)
}
/// Looks up ChatId by auth token.
///
/// Returns None if auth token is not valid.
/// Returns zero/unset ChatId if the token corresponds to "setup contact" rather than group join.
pub async fn auth_chat_id(context: &Context, token: &str) -> Result<Option<ChatId>> {
let chat_id: Option<ChatId> = context
.sql
.query_row_optional(
"SELECT foreign_id FROM tokens WHERE namespc=? AND token=?",
(Namespace::Auth, token),
|row| {
let chat_id: ChatId = row.get(0)?;
Ok(chat_id)
},
)
.await?;
Ok(chat_id)
}
pub async fn delete(context: &Context, namespace: Namespace, token: &str) -> Result<()> {
context
.sql

View File

@@ -277,24 +277,19 @@ pub(crate) fn create_id() -> String {
.collect()
}
/// Returns true if given string is a valid ID.
///
/// All IDs generated with `create_id()` should be considered valid.
pub(crate) fn validate_id(s: &str) -> bool {
let alphabet = base64::alphabet::URL_SAFE.as_str();
s.chars().all(|c| alphabet.contains(c)) && s.len() > 10 && s.len() <= 32
}
/// Function generates a Message-ID that can be used for a new outgoing message.
/// - this function is called for all outgoing messages.
/// - the message ID should be globally unique
/// - do not add a counter or any private data as this leaks information unnecessarily
pub(crate) fn create_outgoing_rfc724_mid(from_addr: &str) -> String {
pub(crate) fn create_outgoing_rfc724_mid(grpid: Option<&str>, from_addr: &str) -> String {
let hostname = from_addr
.find('@')
.and_then(|k| from_addr.get(k..))
.unwrap_or("@nohost");
format!("Mr.{}.{}{}", create_id(), create_id(), hostname)
match grpid {
Some(grpid) => format!("Gr.{}.{}{}", grpid, create_id(), hostname),
None => format!("Mr.{}.{}{}", create_id(), create_id(), hostname),
}
}
/// Extract the group id (grpid) from a message id (mid)
@@ -974,27 +969,6 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
assert_eq!(buf.len(), 11);
}
#[test]
fn test_validate_id() {
for _ in 0..10 {
assert!(validate_id(&create_id()));
}
assert_eq!(validate_id("aaaaaaaaaaaa"), true);
assert_eq!(validate_id("aa-aa_aaaXaa"), true);
// ID cannot contain whitespace.
assert_eq!(validate_id("aaaaa aaaaaa"), false);
assert_eq!(validate_id("aaaaa\naaaaaa"), false);
// ID cannot contain "/", "+".
assert_eq!(validate_id("aaaaa/aaaaaa"), false);
assert_eq!(validate_id("aaaaaaaa+aaa"), false);
// Too long ID.
assert_eq!(validate_id("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"), false);
}
#[test]
fn test_create_id_invalid_chars() {
for _ in 1..1000 {
@@ -1039,10 +1013,21 @@ DKIM Results: Passed=true, Works=true, Allow_Keychange=true";
#[test]
fn test_create_outgoing_rfc724_mid() {
let mid = create_outgoing_rfc724_mid("foo@bar.de");
// create a normal message-id
let mid = create_outgoing_rfc724_mid(None, "foo@bar.de");
assert!(mid.starts_with("Mr."));
assert!(mid.ends_with("bar.de"));
assert!(extract_grpid_from_rfc724_mid(mid.as_str()).is_none());
// create a message-id containing a group-id
let grpid = create_id();
let mid = create_outgoing_rfc724_mid(Some(&grpid), "foo@bar.de");
assert!(mid.starts_with("Gr."));
assert!(mid.ends_with("bar.de"));
assert_eq!(
extract_grpid_from_rfc724_mid(mid.as_str()),
Some(grpid.as_str())
);
}
#[test]

View File

@@ -164,7 +164,7 @@ mod tests {
To: alice@example.org\n\
Message-ID: <msg1@example.org>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: abcde123456\n\
Chat-Group-ID: abcde\n\
Chat-Group-Name: initial name\n\
Date: Sun, 22 Mar 2021 01:00:00 +0000\n\
\n\
@@ -182,7 +182,7 @@ mod tests {
To: alice@example.org\n\
Message-ID: <msg3@example.org>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: abcde123456\n\
Chat-Group-ID: abcde\n\
Chat-Group-Name: another name update\n\
Chat-Group-Name-Changed: a name update\n\
Date: Sun, 22 Mar 2021 03:00:00 +0000\n\
@@ -197,7 +197,7 @@ mod tests {
To: alice@example.org\n\
Message-ID: <msg2@example.org>\n\
Chat-Version: 1.0\n\
Chat-Group-ID: abcde123456\n\
Chat-Group-ID: abcde\n\
Chat-Group-Name: a name update\n\
Chat-Group-Name-Changed: initial name\n\
Date: Sun, 22 Mar 2021 02:00:00 +0000\n\

View File

@@ -1,23 +0,0 @@
-----BEGIN PGP MESSAGE-----
Passphrase-Format: numeric9x4
Passphrase-Begin: .
ww0EAAMIn48zz4/N5VLg0sJOAX7Qy8IyYgBlyt1KLS0tLS1CRUdJTiBQR1AgUFJJ
VkFURSBLRVkgQkxPQ0stLS0tLQpBdXRvY3J5cHQtUHJlZmVyLUVuY3J5cHQ6IG11
dHVhbAoKeFZnRVpjcmRTaFlKS3dZQkJBSGFSdzhCQVFkQUtNSUJtZTVLV2tCak5U
ajBYTmZURUdTcEttclBzTEFJcUhFYQppQ01tVHgwQUFRRE53Z3R6T05Ed2MzVkF4
M2wrcW0wRFVuMEpVZzVMYlVFWHNmY3NFMXdoYmcwZ3pSbEJiR2xqClpTQThZV3hw
WTJWQVpYaGhiWEJzWlM1amIyMCt3b1FFRXhZSUFDd0ZBbVhLM1VvQ0d3SUNDd2ND
RlFnQ0ZnSUMKSGdFV0lRUVhldDlUeXF4VndvY3hid05NaFdYVGRhaE40QUFLQ1JC
TWhXWFRkYWhONElla0FRQ3FKUUlJelVxcworQmN3cW4zY2cvbm42b1Mvd2tNY3RF
c3NNTytjN1VsQk13RUFyc09XbFNwbzVJWDZYbnl2ZmpoNldHb0hLSVArCjR5dXpj
QlRkZTgyNEJnWEhYUVJseXQxS0Vnb3JCZ0VFQVpkVkFRVUJBUWRBYkplbkVUY3NM
Q0o0b2dLa2Qxc28KeUg5Q0FFZ25qMmVEQVBQY2tyWnRsUnNEQVFnSEFBRC9iZUd0
MFZEQ3laRkFEUUZPNXlEUFF3S1B3M3VGL1NSdApsd2o3WEZkMi9hQVJMc0o0QkJn
V0NBQWdCUUpseXQxS0Foc0VGaUVFRjNyZlU4cXNWY0tITVc4RFRJVmwwM1dvClRl
QUFDZ2tRVElWbDAzV29UZUNxelFFQWwzNVhjaENqSkV0dkI0bDVxdXVUMXZ5d1Bn
Q0dyZVJnV01NbHVOaWEKTHY0QkFNRFVuSDZqMmJEWXA0cWc1V2V0R29WcW00UUha
aUgyTlRYOUFmZk50clVPCj0wN2xkCi0tLS0tRU5EIFBHUCBQUklWQVRFIEtFWSBC
TE9DSy0tLS0tCtMUxf1xDQmBgDtEozOxjZG4+GiJc+w=
=jkSD
-----END PGP MESSAGE-----