feat: Remove mostly-unused SignUnencrypted option (#8190)

This commit is contained in:
Hocuri
2026-04-30 13:59:30 +02:00
committed by GitHub
parent 4b528e426b
commit 4c01802982
9 changed files with 95 additions and 228 deletions

View File

@@ -407,9 +407,6 @@ pub enum Config {
#[strum(props(default = "1"))] #[strum(props(default = "1"))]
SyncMsgs, SyncMsgs,
/// Make all outgoing messages with Autocrypt header "multipart/signed".
SignUnencrypted,
/// Let the core save all events to the database. /// Let the core save all events to the database.
/// This value is used internally to remember the MsgId of the logging xdc /// This value is used internally to remember the MsgId of the logging xdc
#[strum(props(default = "0"))] #[strum(props(default = "0"))]
@@ -710,7 +707,6 @@ impl Context {
| Config::Bot | Config::Bot
| Config::NotifyAboutWrongPw | Config::NotifyAboutWrongPw
| Config::SyncMsgs | Config::SyncMsgs
| Config::SignUnencrypted
| Config::DisableIdle => { | Config::DisableIdle => {
ensure!( ensure!(
matches!(value, None | Some("0") | Some("1")), matches!(value, None | Some("0") | Some("1")),

View File

@@ -991,12 +991,6 @@ impl Context {
.await? .await?
.to_string(), .to_string(),
); );
res.insert(
"sign_unencrypted",
self.get_config_int(Config::SignUnencrypted)
.await?
.to_string(),
);
res.insert( res.insert(
"debug_logging", "debug_logging",
self.get_config_int(Config::DebugLogging).await?.to_string(), self.get_config_int(Config::DebugLogging).await?.to_string(),

View File

@@ -79,16 +79,6 @@ impl EncryptHelper {
Ok(ctext) Ok(ctext)
} }
/// Signs the passed-in `mail` using the private key from `context`.
/// Returns the payload and the signature.
pub async fn sign(self, context: &Context, mail: &MimePart<'static>) -> Result<String> {
let sign_key = load_self_secret_key(context).await?;
let mut buffer = Vec::new();
mail.clone().write_part(&mut buffer)?;
let signature = pgp::pk_calc_signature(buffer, &sign_key)?;
Ok(signature)
}
} }
/// Ensures a private key exists for the configured user. /// Ensures a private key exists for the configured user.

View File

@@ -1227,53 +1227,18 @@ impl MimeFactory {
message.header(header, value) message.header(header, value)
}); });
let message = MimePart::new("multipart/mixed", vec![message]); let message = MimePart::new("multipart/mixed", vec![message]);
let mut message = protected_headers let message = protected_headers
.iter() .iter()
.fold(message, |message, (header, value)| { .fold(message, |message, (header, value)| {
message.header(*header, value.clone()) message.header(*header, value.clone())
}); });
if skip_autocrypt || !context.get_config_bool(Config::SignUnencrypted).await? { // Deduplicate unprotected headers that also are in the protected headers:
// Deduplicate unprotected headers that also are in the protected headers: let protected: HashSet<&str> =
let protected: HashSet<&str> = HashSet::from_iter(protected_headers.iter().map(|(header, _value)| *header));
HashSet::from_iter(protected_headers.iter().map(|(header, _value)| *header)); unprotected_headers.retain(|(header, _value)| !protected.contains(header));
unprotected_headers.retain(|(header, _value)| !protected.contains(header));
message message
} else {
for (h, v) in &mut message.headers {
if h == "Content-Type"
&& let mail_builder::headers::HeaderType::ContentType(ct) = v
{
let mut ct_new = ct.clone();
ct_new = ct_new.attribute("protected-headers", "v1");
if use_std_header_protection {
ct_new = ct_new.attribute("hp", "clear");
}
*ct = ct_new;
break;
}
}
let signature = encrypt_helper.sign(context, &message).await?;
MimePart::new(
"multipart/signed; protocol=\"application/pgp-signature\"; protected",
vec![
message,
MimePart::new(
"application/pgp-signature; name=\"signature.asc\"",
signature,
)
.header(
"Content-Description",
mail_builder::headers::raw::Raw::<'static>::new(
"OpenPGP digital signature",
),
)
.attachment("signature"),
],
)
}
}; };
let MimeFactory { let MimeFactory {
@@ -2192,10 +2157,6 @@ fn group_headers_by_confidentiality(
} }
} }
} else { } else {
// Copy the header to the protected headers
// in case of signed-only message.
// If the message is not signed, this value will not be used.
protected_headers.push(header.clone());
unprotected_headers.push(header.clone()) unprotected_headers.push(header.clone())
} }
} }

View File

@@ -601,70 +601,6 @@ async fn test_selfavatar_unencrypted() -> anyhow::Result<()> {
Ok(()) Ok(())
} }
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_selfavatar_unencrypted_signed() {
// create chat with bob, set selfavatar
let t = TestContext::new_alice().await;
t.set_config(Config::SignUnencrypted, Some("1"))
.await
.unwrap();
let chat = t.create_chat_with_contact("bob", "bob@example.org").await;
let file = t.dir.path().join("avatar.png");
let bytes = include_bytes!("../../test-data/image/avatar64x64.png");
tokio::fs::write(&file, bytes).await.unwrap();
t.set_config(Config::Selfavatar, Some(file.to_str().unwrap()))
.await
.unwrap();
// send message to bob: that should get multipart/signed.
// `Subject:` is protected by copying it.
// make sure, `Subject:` stays in the outer header (imf header)
let mut msg = Message::new_text("this is the text!".to_string());
let sent_msg = t.send_msg(chat.id, &mut msg).await;
let mut payload = sent_msg.payload().splitn(4, "\r\n\r\n");
let part = payload.next().unwrap();
assert_eq!(part.match_indices("multipart/signed").count(), 1);
assert_eq!(part.match_indices("From:").count(), 1);
assert_eq!(part.match_indices("Message-ID:").count(), 1);
assert_eq!(part.match_indices("Subject:").count(), 1);
assert_eq!(part.match_indices("Autocrypt:").count(), 1);
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
let part = payload.next().unwrap();
assert_eq!(
part.match_indices("multipart/mixed; protected-headers=\"v1\"")
.count(),
1
);
assert_eq!(part.match_indices("From:").count(), 1);
assert_eq!(part.match_indices("Message-ID:").count(), 0);
assert_eq!(part.match_indices("Subject:").count(), 1);
assert_eq!(part.match_indices("Autocrypt:").count(), 1);
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
let part = payload.next().unwrap();
assert_eq!(part.match_indices("text/plain").count(), 1);
assert_eq!(part.match_indices("From:").count(), 0);
assert_eq!(part.match_indices("Message-ID:").count(), 1);
assert_eq!(part.match_indices("Chat-User-Avatar:").count(), 0);
assert_eq!(part.match_indices("Subject:").count(), 0);
let body = payload.next().unwrap();
assert_eq!(body.match_indices("this is the text!").count(), 1);
let bob = TestContext::new_bob().await;
bob.recv_msg(&sent_msg).await;
let alice_id = Contact::lookup_id_by_addr(&bob.ctx, "alice@example.org", Origin::Unknown)
.await
.unwrap()
.unwrap();
let alice_contact = Contact::get_by_id(&bob.ctx, alice_id).await.unwrap();
assert_eq!(alice_contact.is_key_contact(), false);
}
/// Test that removed member address does not go into the `To:` field. /// Test that removed member address does not go into the `To:` field.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_remove_member_bcc() -> Result<()> { async fn test_remove_member_bcc() -> Result<()> {

View File

@@ -304,37 +304,9 @@ impl MimeMessage {
// Parse hidden headers. // Parse hidden headers.
let mimetype = mail.ctype.mimetype.parse::<Mime>()?; let mimetype = mail.ctype.mimetype.parse::<Mime>()?;
let (part, mimetype) =
if mimetype.type_() == mime::MULTIPART && mimetype.subtype().as_str() == "signed" {
if let Some(part) = mail.subparts.first() {
// We don't remove "subject" from `headers` because currently just signed
// messages are shown as unencrypted anyway.
timestamp_sent =
Self::get_timestamp_sent(&part.headers, timestamp_sent, timestamp_rcvd);
MimeMessage::merge_headers(
context,
&mut headers,
&mut headers_removed,
&mut recipients,
&mut past_members,
&mut from,
&mut list_post,
&mut chat_disposition_notification_to,
part,
);
(part, part.ctype.mimetype.parse::<Mime>()?)
} else {
// Not a valid signed message, handle it as plaintext.
(&mail, mimetype)
}
} else {
// Currently we do not sign unencrypted messages by default.
(&mail, mimetype)
};
if mimetype.type_() == mime::MULTIPART if mimetype.type_() == mime::MULTIPART
&& mimetype.subtype().as_str() == "mixed" && mimetype.subtype().as_str() == "mixed"
&& let Some(part) = part.subparts.first() && let Some(part) = mail.subparts.first()
{ {
for field in &part.headers { for field in &part.headers {
let key = field.get_key().to_lowercase(); let key = field.get_key().to_lowercase();
@@ -358,8 +330,7 @@ impl MimeMessage {
); );
} }
// Remove headers that are allowed _only_ in the encrypted+signed part. It's ok to leave // Remove headers that are allowed _only_ in the encrypted+signed part
// them in signed-only emails, but has no value currently.
let encrypted = false; let encrypted = false;
Self::remove_secured_headers(&mut headers, &mut headers_removed, encrypted); Self::remove_secured_headers(&mut headers, &mut headers_removed, encrypted);
@@ -2217,9 +2188,6 @@ pub(crate) fn parse_message_id(ids: &str) -> Result<String> {
/// Returns whether the outer header value must be ignored if the message contains a signed (and /// Returns whether the outer header value must be ignored if the message contains a signed (and
/// optionally encrypted) part. This is independent from the modern Header Protection defined in /// optionally encrypted) part. This is independent from the modern Header Protection defined in
/// <https://www.rfc-editor.org/rfc/rfc9788.html>. /// <https://www.rfc-editor.org/rfc/rfc9788.html>.
///
/// NB: There are known cases when Subject and List-ID only appear in the outer headers of
/// signed-only messages. Such messages are shown as unencrypted anyway.
fn is_protected(key: &str) -> bool { fn is_protected(key: &str) -> bool {
key.starts_with("chat-") key.starts_with("chat-")
|| matches!( || matches!(

View File

@@ -7,6 +7,7 @@ use crate::{
chat, chat,
chatlist::Chatlist, chatlist::Chatlist,
constants::{self, Blocked, DC_DESIRED_TEXT_LEN, DC_ELLIPSIS}, constants::{self, Blocked, DC_DESIRED_TEXT_LEN, DC_ELLIPSIS},
contact::Contact,
key, key,
message::{MessageState, MessengerMessage}, message::{MessageState, MessengerMessage},
receive_imf::receive_imf, receive_imf::receive_imf,
@@ -2041,32 +2042,24 @@ async fn test_multiple_autocrypt_hdrs() -> Result<()> {
Ok(()) Ok(())
} }
/// Tests that timestamp of signed but not encrypted message is protected. /// Tests receiving a simple signed-unencrypted message
/// that was generated by an old version of Core that supported sending such messages.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)] #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_protected_date() -> Result<()> { async fn test_receive_signed_only() -> Result<()> {
let mut tcm = TestContextManager::new(); let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await; let bob = &tcm.bob().await;
alice.set_config(Config::SignUnencrypted, Some("1")).await?; let imf_raw = include_bytes!("../../test-data/message/unencrypted_signed_simple.eml");
let msg = receive_imf(bob, imf_raw, false).await?.unwrap();
assert_eq!(msg.msg_ids.len(), 1);
let msg = Message::load_from_db(bob, msg.msg_ids[0]).await?;
assert_eq!(msg.get_text(), "Hello!");
assert_eq!(msg.viewtype, Viewtype::Text);
assert_eq!(msg.get_timestamp(), 1615987853);
let alice_chat = alice.create_email_chat(bob).await; let alice_contact = Contact::get_by_id(bob, msg.from_id).await.unwrap();
let alice_msg_id = chat::send_text_msg(alice, alice_chat.id, "Hello!".to_string()).await?; assert_eq!(alice_contact.is_key_contact(), false);
let alice_msg = Message::load_from_db(alice, alice_msg_id).await?;
assert_eq!(alice_msg.get_showpadlock(), false);
let mut sent_msg = alice.pop_sent_msg().await;
sent_msg.payload = sent_msg.payload.replacen(
"Date:",
"Date: Wed, 17 Mar 2021 14:30:53 +0100 (CET)\r\nX-Not-Date:",
1,
);
let bob_msg = bob.recv_msg(&sent_msg).await;
assert_eq!(alice_msg.get_text(), bob_msg.get_text());
// Timestamp that the sender has put into the message
// should always be displayed as is on the receiver.
assert_eq!(alice_msg.get_timestamp(), bob_msg.get_timestamp());
Ok(()) Ok(())
} }

View File

@@ -6,15 +6,15 @@ use std::io::Cursor;
use anyhow::{Context as _, Result, ensure}; use anyhow::{Context as _, Result, ensure};
use deltachat_contact_tools::{EmailAddress, may_be_valid_addr}; use deltachat_contact_tools::{EmailAddress, may_be_valid_addr};
use pgp::composed::{ use pgp::composed::{
ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType, MessageBuilder,
MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig, SubkeyParamsBuilder, SubpacketConfig,
}; };
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize}; use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
use pgp::crypto::ecc_curve::ECCCurve; use pgp::crypto::ecc_curve::ECCCurve;
use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::packet::{Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData}; use pgp::packet::{Signature, Subpacket, SubpacketData};
use pgp::types::{ use pgp::types::{
CompressionAlgorithm, Imprint, KeyDetails, KeyVersion, Password, SignedUser, SigningKey as _, CompressionAlgorithm, Imprint, KeyDetails, KeyVersion, Password, SignedUser, SigningKey as _,
StringToKey, StringToKey,
@@ -202,47 +202,6 @@ pub async fn pk_encrypt(
.await? .await?
} }
/// Produces a detached signature for `plain` text using `private_key_for_signing`.
pub fn pk_calc_signature(
plain: Vec<u8>,
private_key_for_signing: &SignedSecretKey,
) -> Result<String> {
let rng = thread_rng();
let mut config = SignatureConfig::from_key(
rng,
&private_key_for_signing.primary_key,
SignatureType::Binary,
)?;
config.hashed_subpackets = vec![
Subpacket::regular(SubpacketData::IssuerFingerprint(
private_key_for_signing.fingerprint(),
))?,
Subpacket::critical(SubpacketData::SignatureCreationTime(
pgp::types::Timestamp::now(),
))?,
];
config.unhashed_subpackets = vec![];
if private_key_for_signing.version() <= KeyVersion::V4 {
config
.unhashed_subpackets
.push(Subpacket::regular(SubpacketData::IssuerKeyId(
private_key_for_signing.legacy_key_id(),
))?);
}
let signature = config.sign(
&private_key_for_signing.primary_key,
&Password::empty(),
plain.as_slice(),
)?;
let sig = DetachedSignature::new(signature);
Ok(sig.to_armored_string(ArmorOptions::default())?)
}
/// Returns fingerprints /// Returns fingerprints
/// of all keys from the `public_keys_for_validation` keyring that /// of all keys from the `public_keys_for_validation` keyring that
/// have valid signatures in `msg` and corresponding intended recipient fingerprints /// have valid signatures in `msg` and corresponding intended recipient fingerprints

View File

@@ -0,0 +1,70 @@
Content-Type: multipart/signed; protocol="application/pgp-signature"; protected;
boundary="18aa9ed356ff9321_81d052095421b935_6b26de88a99ef0a0"
MIME-Version: 1.0
From: <alice@example.org>
To: <bob@example.net>
Subject: Message from alice@example.org
Date: Wed, 17 Mar 2021 14:30:53 +0100 (CET)
X-Not-Date: Tue, 28 Apr 2026 20:20:34 +0000
Message-ID: <13140637-3c00-4553-8b76-fdbbbe3cc117@localhost>
References: <13140637-3c00-4553-8b76-fdbbbe3cc117@localhost>
Chat-Version: 1.0
Chat-Disposition-Notification-To: alice@example.org
Autocrypt: addr=alice@example.org; prefer-encrypt=mutual; keydata=mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5
DCtAQfFggAZgUCAAAAABYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDAhsDAh4JBAsJCAcFFQgJCgsDFgIB
AycJAgIZASwUgAAAAAASABFyZWxheXNAY2hhdG1haWwuYXRhbGljZUBleGFtcGxlLm9yZwAA57ABAL
DeNEB8l86SrqNKbUhDl5e7Q46VN+k/jxPEbIAs506MAQDXxgFEO2xAE19ykJI4JqU8+Zj+dwld9rXM
Bh98UTnEBs0TPGFsaWNlQGV4YW1wbGUub3JnPsKRBBMWCAA5BQIAAAAAFiEELm+iyyO1MtcoY0tYZL
CPYantlEMCGwMCHgkECwkIBwUVCAkKCwMWAgEDJwkCAhkBAAoJEGSwj2Gp7ZRD4e8BAKrOvjAu/Zd+
+XeYCfN00mA7Vb6FtLlvVb0gT0hzv/rBAP0dYE736fa81MseX1PdUeN2Lf9SyNOVw3eW8W0nKXEbDr
g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeI
eAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8G
nsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp
3jsMAg==
--18aa9ed356ff9321_81d052095421b935_6b26de88a99ef0a0
Content-Type: multipart/mixed; protected-headers="v1"; hp="clear";
boundary="18aa9ed357004185_2007cbc2d36c354a_6b26de88a99ef0a0"
From: <alice@example.org>
To: <bob@example.net>
Subject: Message from alice@example.org
Date: Tue, 28 Apr 2026 20:20:34 +0000
References: <13140637-3c00-4553-8b76-fdbbbe3cc117@localhost>
Chat-Version: 1.0
Chat-Disposition-Notification-To: alice@example.org
Autocrypt: addr=alice@example.org; prefer-encrypt=mutual; keydata=mDMEXlh13RYJKwYBBAHaRw8BAQdAzfVIAleCXMJrq8VeLlEVof6ITCviMktKjmcBKAu4m5
DCtAQfFggAZgUCAAAAABYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDAhsDAh4JBAsJCAcFFQgJCgsDFgIB
AycJAgIZASwUgAAAAAASABFyZWxheXNAY2hhdG1haWwuYXRhbGljZUBleGFtcGxlLm9yZwAA57ABAL
DeNEB8l86SrqNKbUhDl5e7Q46VN+k/jxPEbIAs506MAQDXxgFEO2xAE19ykJI4JqU8+Zj+dwld9rXM
Bh98UTnEBs0TPGFsaWNlQGV4YW1wbGUub3JnPsKRBBMWCAA5BQIAAAAAFiEELm+iyyO1MtcoY0tYZL
CPYantlEMCGwMCHgkECwkIBwUVCAkKCwMWAgEDJwkCAhkBAAoJEGSwj2Gp7ZRD4e8BAKrOvjAu/Zd+
+XeYCfN00mA7Vb6FtLlvVb0gT0hzv/rBAP0dYE736fa81MseX1PdUeN2Lf9SyNOVw3eW8W0nKXEbDr
g4BF5Ydd0SCisGAQQBl1UBBQEBB0AG7cjWy2SFAU8KnltlubVW67rFiyfp01JrRe6Xqy22HQMBCAeI
eAQYFggAIBYhBC5vossjtTLXKGNLWGSwj2Gp7ZRDBQJeWHXdAhsMAAoJEGSwj2Gp7ZRDLo8BAObE8G
nsGVwKzNqCvHeWgJsqhjS3C6gvSlV3tEm9XmF6AQDXucIyVfoBwoyMh2h6cSn/ATn5QJb35pgo+ivp
3jsMAg==
--18aa9ed357004185_2007cbc2d36c354a_6b26de88a99ef0a0
Content-Type: text/plain; charset="utf-8"
Message-ID: <13140637-3c00-4553-8b76-fdbbbe3cc117@localhost>
Content-Transfer-Encoding: 7bit
Hello!
--18aa9ed357004185_2007cbc2d36c354a_6b26de88a99ef0a0--
--18aa9ed356ff9321_81d052095421b935_6b26de88a99ef0a0
Content-Type: application/pgp-signature; name="signature.asc";
charset="utf-8"
Content-Description: OpenPGP digital signature
Content-Disposition: attachment; filename="signature"
Content-Transfer-Encoding: quoted-printable
-----BEGIN PGP SIGNATURE-----=0A=0AwnUEABYIAB0WIQQub6LLI7Uy1yhjS1hksI9hqe2UQ=
wWCafEWkQAKCRBksI9hqe2U=0AQ4qaAQCFSLVDANIjaXswP8V5zIwUSvGnUwsMD+ruozO0mG2AqA=
D9EqpWeD6cc+is=0Av9/nvp6uHi35pUmDX0s1XKu3xbSTWg8=3D=0A=3Dr9hO=0A-----END PGP=
SIGNATURE-----=0A
--18aa9ed356ff9321_81d052095421b935_6b26de88a99ef0a0--