diff --git a/CHANGELOG.md b/CHANGELOG.md index cf38ef76d..ae8135008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### Fixes - Set read/write timeouts for IMAP over SOCKS5 #3833 +- Treat attached PGP keys as peer keys with mutual encryption preference #3832 ## 1.103.0 diff --git a/src/mimeparser.rs b/src/mimeparser.rs index 1b68b29de..8c4826633 100644 --- a/src/mimeparser.rs +++ b/src/mimeparser.rs @@ -3,6 +3,7 @@ use std::collections::{HashMap, HashSet}; use std::future::Future; use std::pin::Pin; +use std::str; use anyhow::{bail, Context as _, Result}; use deltachat_derive::{FromSql, ToSql}; @@ -10,17 +11,17 @@ use lettre_email::mime::{self, Mime}; use mailparse::{addrparse_header, DispositionType, MailHeader, MailHeaderMap, SingleInfo}; use once_cell::sync::Lazy; -use crate::aheader::Aheader; +use crate::aheader::{Aheader, EncryptPreference}; use crate::blob::BlobObject; use crate::constants::{DC_DESIRED_TEXT_LINES, DC_DESIRED_TEXT_LINE_LEN}; use crate::contact::{addr_cmp, addr_normalize, ContactId}; 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::events::EventType; use crate::format_flowed::unformat_flowed; use crate::headerdef::{HeaderDef, HeaderDefMap}; -use crate::key::Fingerprint; +use crate::key::{DcKey, Fingerprint, SignedPublicKey}; use crate::message::{self, Viewtype}; use crate::param::{Param, Params}; use crate::peerstate::Peerstate; @@ -52,6 +53,7 @@ pub struct MimeMessage { pub from_is_signed: bool, pub list_post: Option, pub chat_disposition_notification_to: Option, + pub decryption_info: DecryptionInfo, pub decrypting_failed: bool, /// Set of valid signature fingerprints if a message is an @@ -322,6 +324,7 @@ impl MimeMessage { from, from_is_signed, chat_disposition_notification_to, + decryption_info, decrypting_failed: mail.is_err(), // only non-empty if it was a valid autocrypt message @@ -390,8 +393,8 @@ impl MimeMessage { parser.decoded_data = mail_raw; } - crate::peerstate::maybe_do_aeap_transition(context, &mut decryption_info, &parser).await?; - if let Some(peerstate) = decryption_info.peerstate { + crate::peerstate::maybe_do_aeap_transition(context, &mut parser).await?; + if let Some(peerstate) = &parser.decryption_info.peerstate { peerstate .handle_fingerprint_change(context, message_time) .await?; @@ -950,7 +953,7 @@ impl MimeMessage { &filename, is_related, ) - .await; + .await?; } None => { match mime_type.type_() { @@ -1093,9 +1096,18 @@ impl MimeMessage { decoded_data: &[u8], filename: &str, is_related: bool, - ) { + ) -> Result<()> { 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 .is_webxdc_file(filename, decoded_data) @@ -1117,7 +1129,7 @@ impl MimeMessage { } else { self.message_kml = parsed; } - return; + return Ok(()); } msg_type } else if filename == "multi-device-sync.json" { @@ -1130,13 +1142,13 @@ impl MimeMessage { warn!(context, "failed to parse sync data: {}", err); }) .ok(); - return; + return Ok(()); } else if filename == "status-update.json" { let serialized = String::from_utf8_lossy(decoded_data) .parse() .unwrap_or_default(); self.webxdc_status_update = Some(serialized); - return; + return Ok(()); } else { msg_type }; @@ -1151,7 +1163,7 @@ impl MimeMessage { context, "Could not add blob for mime part {}, error {}", filename, err ); - return; + return Ok(()); } }; info!(context, "added blobfile: {:?}", blob.as_name()); @@ -1174,6 +1186,66 @@ impl MimeMessage { part.is_related = is_related; 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 { + 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) { diff --git a/src/peerstate.rs b/src/peerstate.rs index e9591c789..22e4c8f50 100644 --- a/src/peerstate.rs +++ b/src/peerstate.rs @@ -9,7 +9,6 @@ use crate::chatlist::Chatlist; use crate::constants::Chattype; use crate::contact::{addr_cmp, Contact, Origin}; use crate::context::Context; -use crate::decrypt::DecryptionInfo; use crate::events::EventType; use crate::key::{DcKey, Fingerprint, SignedPublicKey}; use crate::message::Message; @@ -565,10 +564,10 @@ impl Peerstate { /// In `drafts/aeap_mvp.md` there is a "big picture" overview over AEAP. pub async fn maybe_do_aeap_transition( context: &Context, - info: &mut DecryptionInfo, - mime_parser: &crate::mimeparser::MimeMessage, + mime_parser: &mut crate::mimeparser::MimeMessage, ) -> 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, // we may want to do an AEAP transition. 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 && 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 // peerstate diff --git a/src/receive_imf.rs b/src/receive_imf.rs index b1398c5a9..d10d4bad1 100644 --- a/src/receive_imf.rs +++ b/src/receive_imf.rs @@ -5350,6 +5350,22 @@ Reply from different address 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)] async fn test_mua_user_adds_member() -> Result<()> { let t = TestContext::new_alice().await; diff --git a/test-data/message/thunderbird_with_autocrypt_unencrypted.eml b/test-data/message/thunderbird_with_autocrypt_unencrypted.eml new file mode 100644 index 000000000..a8654024a --- /dev/null +++ b/test-data/message/thunderbird_with_autocrypt_unencrypted.eml @@ -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 +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 +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----- + +xsDNBGOTM3UBDADZ819boOPXK/ZPO1EepYUBve2psYO3rZkPu3uhyn7qpI8c0U5I +bR+mAXPHFkKfvSwTtGiPpXaP6/vx0OjTs1aR7We9MrP+1EckbsyQnnDmDGsGxxyn +3+a3ar0FcgOBi/kSj0fPB1tX92/z3MWtOSXYtYOlMotRdIxt/L8CYQSBe8wWpoOK +QPNmtvnEuDlJwSlrhRPx6PDmBgoKv1qi5UOrAoyUPbdnINnSgj14KBNMgiuJQz6+ +AwVaYitVJ37N6lrCfhWRPZAVDRW5ajLxW+DuuYUW675xzi2bLlb4jGeFePvS9Rhw +2CpkG608cFVFrUCBH91mfb0UnmxIDMcc6JSn0UqfPESC+0wK9xokzi07/FZtXyf9 +25oiMpA7ZQ7aSNW6J7kk618xNQRivLhEV1+QofynAzfwAB+CvqY+VjNZbGKGW7ab +a84Nx9Wa7g8rbZ5ZvsQmrn38fpWu+2GcUvnGOxn8lYEljnfCthSigjCgq3T90aSU +wDQfedJej9nzM98AEQEAAc0ZQWxpY2UgPGFsaWNlQGV4YW1wbGUub3JnPsLBDQQT +AQgANxYhBL7Y7n4kdUxZWposQeDVBmYZR/R8BQJjkzN2BQm7+B4AAhsDBAsJCAcF +FQgJCgsFFgIDAQAACgkQ4NUGZhlH9HzsWwv+JucjIbwsHfRWDB81R9d0WIQGvYM8 +sjUETQWmlwEcts/yyHVLNnyvxn9EUboo9tLg3VmukPYNLyuVJ6WlWRuskZHXy3Td +W+1TcAIzO97vReBOXOunDmoTPoA9IRUFVVwC3ejyFj9timcKVKX6WUyNY7l0x1Vo +y5gHqswnlVQ0SXsdBQqDwMJqUuRmWE1zrk15EdF2OvWADlZ0j9TeGHFYcr6lLXZ9 +2sOQbjsm4vmwPGFC5oiolKmoZXNfNa9Ef3HPv9Q0XF576hfu7CyIhWXXxCNGzssu +TA42Kdxhcpppi+HtzEr1F3jApDG2T5bfMnIN9udu6UgNTdQm/Qyuamn2vo11fXsd +A41Kajrnj2Vtcf6qd4qv4HSgeyGxZw3btjbmwuVAao0x49jXYZhpx00riddTfjBh +hE1MCPNHK9ypmodWMiF99dZNhAHB434agfkNWHl8z3QwxDLjWhkzNdnHeO1Xg2zq +3/mKi2mNyb2iGImDp4GAxOQVLYGwXPRe0NeqzsDNBGOTM3YBDADFQ11NReZAL2vd +u5avkfs7iw7MNI2DANGvouIcQOP0gqSkF0UY/bMmvWXmDV6iTaxe2/+r/t51zZZR +nr1KYF/XayoQmxLuMAKWAUJvltzcYlJwSphCCbh2OpxHBZqrbhHKGZIkj1Is3uVB +SFt6gkr9lYDFk+ehhBBNoE50nSamJXNpur2A4aZYmIwKWNeU+skzYu4VDUKXet69 +fmK4bZlF1ydYturcSQtE6fLb8ob7b/52C2FJxRNFJQ7el8bozPKX0ZitKCSh9HXK +w4TvD+nD8v4tDAmzno9Z66T4o8WYRA5mCYWVpD+WQadcikcqx5G7RIiKgRxvcGAx +9kMUjMptjErc+1rKcNw7QdpFu6uiSj1602jBM/JvQRvUVa85vkQn0u07PjIzH+ZQ +eKsijdmDaeOZWjE1/XkOVi3btzoOaQQRh14spC+ztl8hV6/9+bDIWXEKiiQQUi1K +vw7TfaRQprmD1IUyfb69LpwD8MTnBoDyA/PxY1DurQPJMN5yAvsAEQEAAcLA/AQY +AQgAJhYhBL7Y7n4kdUxZWposQeDVBmYZR/R8BQJjkzN3BQm7+B4AAhsMAAoJEODV +BmYZR/R890sL/0+cJmENgLGI+Ji5rMlZe63hDk4w1p+7THf4vmX/Pg27hUTznTeR +Ls3dhGVYrSPvxgl7L4KlTwe1euSBgfWqCpNjh0g5Hvz3X5uSoLerEsGa7PoGTvpn +TuWoRzYJLYRkWtuwfQ3SvpeQOglT7vgvsSoC1h6MOnWJgTo8yYyYP92Wq7fv867b +SpWjjykHcK5DIjEM71+6IJTn5pnhkG2ddibfHyDZoBj0P8VrJFEkkCzkycANtmhU +BDr/vFhYKWy76ZZNgGHg71iFGwXK/kz5dKA6mIUNAaeyyarAzoaJh0y3UkAPW/ev +wD/PP9M4y2mP6TDPeFYBZI7o5gCD6q+t1zCMc1M4V+hOJXfsISJPE3J/Rq53QnPO +msz9sdyfOxxfePV64gtv3xHBFUafucFiipeHgx4eXmdMNRnzlGeHlhDndpFGkJJe +A8TCJqfP0DFY/CCW4mT0FvaVcFtJ/CXvmD6qORTlbJg9XZ2FNCA7x0+WJ2mjn/m1 +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--