mirror of
https://github.com/chatmail/core.git
synced 2026-04-17 21:46:35 +03:00
Treat attached PGP keys as peer keys with mutual encryption preference (#3778)
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Set read/write timeouts for IMAP over SOCKS5 #3833
|
- Set read/write timeouts for IMAP over SOCKS5 #3833
|
||||||
|
- Treat attached PGP keys as peer keys with mutual encryption preference #3832
|
||||||
|
|
||||||
|
|
||||||
## 1.103.0
|
## 1.103.0
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
use anyhow::{bail, Context as _, Result};
|
use anyhow::{bail, Context as _, Result};
|
||||||
use deltachat_derive::{FromSql, ToSql};
|
use deltachat_derive::{FromSql, ToSql};
|
||||||
@@ -10,17 +11,17 @@ use lettre_email::mime::{self, Mime};
|
|||||||
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
|
use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
use crate::aheader::Aheader;
|
use crate::aheader::{Aheader, EncryptPreference};
|
||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::constants::{DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN};
|
use crate::constants::{DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN};
|
||||||
use crate::contact::{addr_cmp, addr_normalize, ContactId};
|
use crate::contact::{addr_cmp, addr_normalize, ContactId};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::decrypt::{prepare_decryption, try_decrypt};
|
use crate::decrypt::{prepare_decryption, try_decrypt, DecryptionInfo};
|
||||||
use crate::dehtml::dehtml;
|
use crate::dehtml::dehtml;
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::format_flowed::unformat_flowed;
|
use crate::format_flowed::unformat_flowed;
|
||||||
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
use crate::headerdef::{HeaderDef, HeaderDefMap};
|
||||||
use crate::key::Fingerprint;
|
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
||||||
use crate::message::{self, Viewtype};
|
use crate::message::{self, Viewtype};
|
||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
use crate::peerstate::Peerstate;
|
use crate::peerstate::Peerstate;
|
||||||
@@ -52,6 +53,7 @@ pub struct MimeMessage {
|
|||||||
pub from_is_signed: bool,
|
pub from_is_signed: bool,
|
||||||
pub list_post: Option<String>,
|
pub list_post: Option<String>,
|
||||||
pub chat_disposition_notification_to: Option<SingleInfo>,
|
pub chat_disposition_notification_to: Option<SingleInfo>,
|
||||||
|
pub decryption_info: DecryptionInfo,
|
||||||
pub decrypting_failed: bool,
|
pub decrypting_failed: bool,
|
||||||
|
|
||||||
/// Set of valid signature fingerprints if a message is an
|
/// Set of valid signature fingerprints if a message is an
|
||||||
@@ -322,6 +324,7 @@ impl MimeMessage {
|
|||||||
from,
|
from,
|
||||||
from_is_signed,
|
from_is_signed,
|
||||||
chat_disposition_notification_to,
|
chat_disposition_notification_to,
|
||||||
|
decryption_info,
|
||||||
decrypting_failed: mail.is_err(),
|
decrypting_failed: mail.is_err(),
|
||||||
|
|
||||||
// only non-empty if it was a valid autocrypt message
|
// only non-empty if it was a valid autocrypt message
|
||||||
@@ -390,8 +393,8 @@ impl MimeMessage {
|
|||||||
parser.decoded_data = mail_raw;
|
parser.decoded_data = mail_raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
crate::peerstate::maybe_do_aeap_transition(context, &mut decryption_info, &parser).await?;
|
crate::peerstate::maybe_do_aeap_transition(context, &mut parser).await?;
|
||||||
if let Some(peerstate) = decryption_info.peerstate {
|
if let Some(peerstate) = &parser.decryption_info.peerstate {
|
||||||
peerstate
|
peerstate
|
||||||
.handle_fingerprint_change(context, message_time)
|
.handle_fingerprint_change(context, message_time)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -950,7 +953,7 @@ impl MimeMessage {
|
|||||||
&filename,
|
&filename,
|
||||||
is_related,
|
is_related,
|
||||||
)
|
)
|
||||||
.await;
|
.await?;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
match mime_type.type_() {
|
match mime_type.type_() {
|
||||||
@@ -1093,9 +1096,18 @@ impl MimeMessage {
|
|||||||
decoded_data: &[u8],
|
decoded_data: &[u8],
|
||||||
filename: &str,
|
filename: &str,
|
||||||
is_related: bool,
|
is_related: bool,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if decoded_data.is_empty() {
|
if decoded_data.is_empty() {
|
||||||
return;
|
return Ok(());
|
||||||
|
}
|
||||||
|
if let Some(peerstate) = &mut self.decryption_info.peerstate {
|
||||||
|
if peerstate.prefer_encrypt != EncryptPreference::Mutual
|
||||||
|
&& mime_type.type_() == mime::APPLICATION
|
||||||
|
&& mime_type.subtype().as_str() == "pgp-keys"
|
||||||
|
&& Self::try_set_peer_key_from_file_part(context, peerstate, decoded_data).await?
|
||||||
|
{
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let msg_type = if context
|
let msg_type = if context
|
||||||
.is_webxdc_file(filename, decoded_data)
|
.is_webxdc_file(filename, decoded_data)
|
||||||
@@ -1117,7 +1129,7 @@ impl MimeMessage {
|
|||||||
} else {
|
} else {
|
||||||
self.message_kml = parsed;
|
self.message_kml = parsed;
|
||||||
}
|
}
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
msg_type
|
msg_type
|
||||||
} else if filename == "multi-device-sync.json" {
|
} else if filename == "multi-device-sync.json" {
|
||||||
@@ -1130,13 +1142,13 @@ impl MimeMessage {
|
|||||||
warn!(context, "failed to parse sync data: {}", err);
|
warn!(context, "failed to parse sync data: {}", err);
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
return;
|
return Ok(());
|
||||||
} else if filename == "status-update.json" {
|
} else if filename == "status-update.json" {
|
||||||
let serialized = String::from_utf8_lossy(decoded_data)
|
let serialized = String::from_utf8_lossy(decoded_data)
|
||||||
.parse()
|
.parse()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
self.webxdc_status_update = Some(serialized);
|
self.webxdc_status_update = Some(serialized);
|
||||||
return;
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
msg_type
|
msg_type
|
||||||
};
|
};
|
||||||
@@ -1151,7 +1163,7 @@ impl MimeMessage {
|
|||||||
context,
|
context,
|
||||||
"Could not add blob for mime part {}, error {}", filename, err
|
"Could not add blob for mime part {}, error {}", filename, err
|
||||||
);
|
);
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
info!(context, "added blobfile: {:?}", blob.as_name());
|
info!(context, "added blobfile: {:?}", blob.as_name());
|
||||||
@@ -1174,6 +1186,66 @@ impl MimeMessage {
|
|||||||
part.is_related = is_related;
|
part.is_related = is_related;
|
||||||
|
|
||||||
self.do_add_single_part(part);
|
self.do_add_single_part(part);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether a key from the attachment was set as peer's pubkey.
|
||||||
|
async fn try_set_peer_key_from_file_part(
|
||||||
|
context: &Context,
|
||||||
|
peerstate: &mut Peerstate,
|
||||||
|
decoded_data: &[u8],
|
||||||
|
) -> Result<bool> {
|
||||||
|
let key = match str::from_utf8(decoded_data) {
|
||||||
|
Err(err) => {
|
||||||
|
warn!(context, "PGP key attachment is not a UTF-8 file: {}", err);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok(key) => key,
|
||||||
|
};
|
||||||
|
let key = match SignedPublicKey::from_asc(key) {
|
||||||
|
Err(err) => {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"PGP key attachment is not an ASCII-armored file: {}", err,
|
||||||
|
);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
Ok((key, _)) => key,
|
||||||
|
};
|
||||||
|
if let Err(err) = key.verify() {
|
||||||
|
warn!(context, "attached PGP key verification failed: {}", err);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if !key.details.users.iter().any(|user| {
|
||||||
|
user.id
|
||||||
|
.id()
|
||||||
|
.ends_with(&(String::from("<") + &peerstate.addr + ">"))
|
||||||
|
}) {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
if let Some(curr_key) = &peerstate.public_key {
|
||||||
|
if key != *curr_key && peerstate.prefer_encrypt != EncryptPreference::Reset {
|
||||||
|
// We don't want to break the existing Autocrypt setup. Yes, it's unlikely that a
|
||||||
|
// user have an Autocrypt-capable MUA and also attaches a key, but if that's the
|
||||||
|
// case, let 'em first disable Autocrypt and then change the key by attaching it.
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"not using attached PGP key for peer '{}' because another one is already set \
|
||||||
|
with prefer-encrypt={}",
|
||||||
|
peerstate.addr,
|
||||||
|
peerstate.prefer_encrypt,
|
||||||
|
);
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
info!(
|
||||||
|
context,
|
||||||
|
"will use attached PGP key for peer '{}' with mutual encryption", peerstate.addr,
|
||||||
|
);
|
||||||
|
peerstate.public_key = Some(key);
|
||||||
|
peerstate.prefer_encrypt = EncryptPreference::Mutual;
|
||||||
|
peerstate.save_to_db(&context.sql).await?;
|
||||||
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn do_add_single_part(&mut self, mut part: Part) {
|
fn do_add_single_part(&mut self, mut part: Part) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ use crate::chatlist::Chatlist;
|
|||||||
use crate::constants::Chattype;
|
use crate::constants::Chattype;
|
||||||
use crate::contact::{addr_cmp, Contact, Origin};
|
use crate::contact::{addr_cmp, Contact, Origin};
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::decrypt::DecryptionInfo;
|
|
||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
|
||||||
use crate::message::Message;
|
use crate::message::Message;
|
||||||
@@ -565,10 +564,10 @@ impl Peerstate {
|
|||||||
/// In `drafts/aeap_mvp.md` there is a "big picture" overview over AEAP.
|
/// In `drafts/aeap_mvp.md` there is a "big picture" overview over AEAP.
|
||||||
pub async fn maybe_do_aeap_transition(
|
pub async fn maybe_do_aeap_transition(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
info: &mut DecryptionInfo,
|
mime_parser: &mut crate::mimeparser::MimeMessage,
|
||||||
mime_parser: &crate::mimeparser::MimeMessage,
|
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if let Some(peerstate) = &mut info.peerstate {
|
let info = &mime_parser.decryption_info;
|
||||||
|
if let Some(peerstate) = &info.peerstate {
|
||||||
// If the from addr is different from the peerstate address we know,
|
// If the from addr is different from the peerstate address we know,
|
||||||
// we may want to do an AEAP transition.
|
// we may want to do an AEAP transition.
|
||||||
if !addr_cmp(&peerstate.addr, &mime_parser.from.addr)
|
if !addr_cmp(&peerstate.addr, &mime_parser.from.addr)
|
||||||
@@ -588,6 +587,8 @@ pub async fn maybe_do_aeap_transition(
|
|||||||
&& mime_parser.from_is_signed
|
&& mime_parser.from_is_signed
|
||||||
&& info.message_time > peerstate.last_seen
|
&& info.message_time > peerstate.last_seen
|
||||||
{
|
{
|
||||||
|
let info = &mut mime_parser.decryption_info;
|
||||||
|
let peerstate = info.peerstate.as_mut().context("no peerstate??")?;
|
||||||
// Add info messages to chats with this (verified) contact
|
// Add info messages to chats with this (verified) contact
|
||||||
//
|
//
|
||||||
peerstate
|
peerstate
|
||||||
|
|||||||
@@ -5350,6 +5350,22 @@ Reply from different address
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_thunderbird_autocrypt_unencrypted() -> Result<()> {
|
||||||
|
let t = TestContext::new_bob().await;
|
||||||
|
t.set_config(Config::ShowEmails, Some("2")).await?;
|
||||||
|
|
||||||
|
let raw = include_bytes!("../test-data/message/thunderbird_with_autocrypt_unencrypted.eml");
|
||||||
|
receive_imf(&t, raw, false).await?;
|
||||||
|
|
||||||
|
let peerstate = Peerstate::from_addr(&t, "alice@example.org")
|
||||||
|
.await?
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(peerstate.prefer_encrypt, EncryptPreference::Mutual);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_mua_user_adds_member() -> Result<()> {
|
async fn test_mua_user_adds_member() -> Result<()> {
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
|
|||||||
142
test-data/message/thunderbird_with_autocrypt_unencrypted.eml
Normal file
142
test-data/message/thunderbird_with_autocrypt_unencrypted.eml
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
From - Fri, 09 Dec 2022 13:16:11 GMT
|
||||||
|
X-Mozilla-Status: 0801
|
||||||
|
X-Mozilla-Status2: 00000000
|
||||||
|
Message-ID: <0c8e3ffc-99ae-eb68-15b5-15c4d85a5c12@example.org>
|
||||||
|
Date: Fri, 9 Dec 2022 10:16:11 -0300
|
||||||
|
MIME-Version: 1.0
|
||||||
|
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101
|
||||||
|
Thunderbird/102.5.1
|
||||||
|
Content-Language: en-US
|
||||||
|
To: bob@example.net
|
||||||
|
From: Alice <alice@example.org>
|
||||||
|
Subject: test message 10:15
|
||||||
|
Autocrypt: addr=alice@example.org; keydata=
|
||||||
|
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
|
||||||
|
rhEBN10sGyg93A==
|
||||||
|
X-Mozilla-Draft-Info: internal/draft; vcard=0; receipt=0; DSN=0; uuencode=0;
|
||||||
|
attachmentreminder=0; deliveryformat=0
|
||||||
|
X-Identity-Key: id3
|
||||||
|
Fcc: imap://alice%40example.org@in.example.org/Sent
|
||||||
|
Content-Type: multipart/signed; micalg=pgp-sha256;
|
||||||
|
protocol="application/pgp-signature";
|
||||||
|
boundary="------------FFBOG29BVxcOkoFV1hnc0RaY"
|
||||||
|
|
||||||
|
This is an OpenPGP/MIME signed message (RFC 4880 and 3156)
|
||||||
|
--------------FFBOG29BVxcOkoFV1hnc0RaY
|
||||||
|
Content-Type: multipart/mixed; boundary="------------4cwiD0i5NnTXNSfPNpFwrv6V";
|
||||||
|
protected-headers="v1"
|
||||||
|
From: Alice <alice@example.org>
|
||||||
|
To: bob@example.net
|
||||||
|
Message-ID: <0c8e3ffc-99ae-eb68-15b5-15c4d85a5c12@example.org>
|
||||||
|
Subject: test message 10:15
|
||||||
|
|
||||||
|
--------------4cwiD0i5NnTXNSfPNpFwrv6V
|
||||||
|
Content-Type: multipart/mixed; boundary="------------fbNEFvfS22YOKnkTd1oAl0ak"
|
||||||
|
|
||||||
|
--------------fbNEFvfS22YOKnkTd1oAl0ak
|
||||||
|
Content-Type: text/plain; charset=UTF-8; format=flowed
|
||||||
|
Content-Transfer-Encoding: base64
|
||||||
|
|
||||||
|
MTIzDQoNCg==
|
||||||
|
--------------fbNEFvfS22YOKnkTd1oAl0ak
|
||||||
|
Content-Type: application/pgp-keys; name="OpenPGP_0xE0D506661947F47C.asc"
|
||||||
|
Content-Disposition: attachment; filename="OpenPGP_0xE0D506661947F47C.asc"
|
||||||
|
Content-Description: OpenPGP public key
|
||||||
|
Content-Transfer-Encoding: quoted-printable
|
||||||
|
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
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
|
||||||
|
rhEBN10sGyg93A=3D=3D
|
||||||
|
=3DDPMe
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
--------------fbNEFvfS22YOKnkTd1oAl0ak--
|
||||||
|
|
||||||
|
--------------4cwiD0i5NnTXNSfPNpFwrv6V--
|
||||||
|
|
||||||
|
--------------FFBOG29BVxcOkoFV1hnc0RaY
|
||||||
|
Content-Type: application/pgp-signature; name="OpenPGP_signature.asc"
|
||||||
|
Content-Description: OpenPGP digital signature
|
||||||
|
Content-Disposition: attachment; filename="OpenPGP_signature"
|
||||||
|
|
||||||
|
-----BEGIN PGP SIGNATURE-----
|
||||||
|
|
||||||
|
wsD5BAABCAAjFiEEvtjufiR1TFlamixB4NUGZhlH9HwFAmOTNRsFAwAAAAAACgkQ4NUGZhlH9Hzw
|
||||||
|
Iwv/dNC7LDvRGmZ71IaivkUkSTbpGgg0gnCNOuf+B8OxUBQlWPkBmLxyrXbkxsTghFogDsVQeZQQ
|
||||||
|
DJ182KMgeC//rUN5DPJNrh95YZnav0nUpzW1mkFZjK+PdhbfdXKoXhJIqcw/7lpy/povRYZ20Igg
|
||||||
|
tIHLa1NlqPPhSx/o2dsEqWeAtXF4e8T/jQSA5+ZQtVrdcTCNQG6zbqlHZuJ7bF1bwuHPgLgDhJ5k
|
||||||
|
+T2ny80ZtkfLXJl5tQdblAomhBPfEOj+AeLCKsrJFO3WFZOvsuoKMPZpwW1wEh7+QYLABX/lRvqx
|
||||||
|
IxjH1Tc26vttlOrVH13FKGSeWJELun+b2dP1LPiBQ7DOsrrFNs3fp56Nb7Y+exH5ld0jz0kJZTUD
|
||||||
|
yPqZpJXTsWkFPE7x1tbH/7goiH8f9DbQrvmqQ2fnCjzf3UJR3ZhG/13YAUEdLVkVzwMItEd6yisg
|
||||||
|
MP8mlbwm4aDeCiGXO/xhOoBVl6bn1HVSxo7mb0chHVyD1NOfd7qsxem0L/A2
|
||||||
|
=MOQB
|
||||||
|
-----END PGP SIGNATURE-----
|
||||||
|
|
||||||
|
--------------FFBOG29BVxcOkoFV1hnc0RaY--
|
||||||
Reference in New Issue
Block a user