mirror of
https://github.com/chatmail/core.git
synced 2026-04-05 23:22:11 +03:00
Compare commits
10 Commits
link2xt/co
...
link2xt/se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57d22f8406 | ||
|
|
5fe6d0b106 | ||
|
|
d40673af1d | ||
|
|
a6d3ff6cf7 | ||
|
|
15f3af418a | ||
|
|
7cc2a09627 | ||
|
|
c96139b579 | ||
|
|
c52998f2ff | ||
|
|
12587684ca | ||
|
|
0c1d578207 |
60
CHANGELOG.md
60
CHANGELOG.md
@@ -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
13
Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat_ffi"
|
||||
version = "1.136.0"
|
||||
version = "1.135.1"
|
||||
description = "Deltachat FFI"
|
||||
edition = "2018"
|
||||
readme = "README.md"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -53,5 +53,5 @@
|
||||
},
|
||||
"type": "module",
|
||||
"types": "dist/deltachat.d.ts",
|
||||
"version": "1.136.0"
|
||||
"version": "1.135.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "deltachat-repl"
|
||||
version = "1.136.0"
|
||||
version = "1.135.1"
|
||||
license = "MPL-2.0"
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
2024-03-04
|
||||
2024-02-20
|
||||
5
spec.md
5
spec.md
@@ -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.
|
||||
|
||||
24
src/chat.rs
24
src/chat.rs
@@ -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);
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
45
src/imex.rs
45
src/imex.rs
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
19
src/key.rs
19
src/key.rs
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
38
src/pgp.rs
38
src/pgp.rs
@@ -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
|
||||
}
|
||||
|
||||
56
src/qr.rs
56
src/qr.rs
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ impl TryFrom<Qr> for QrInvite {
|
||||
invitenumber,
|
||||
authcode,
|
||||
}),
|
||||
_ => bail!("Unsupported QR type"),
|
||||
_ => bail!("Unsupported QR type {:?}", qr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
19
src/token.rs
19
src/token.rs
@@ -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
|
||||
|
||||
49
src/tools.rs
49
src/tools.rs
@@ -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]
|
||||
|
||||
@@ -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\
|
||||
|
||||
@@ -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-----
|
||||
Reference in New Issue
Block a user