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

View File

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

View File

@@ -79,16 +79,6 @@ impl EncryptHelper {
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.

View File

@@ -1227,53 +1227,18 @@ impl MimeFactory {
message.header(header, value)
});
let message = MimePart::new("multipart/mixed", vec![message]);
let mut message = protected_headers
let message = protected_headers
.iter()
.fold(message, |message, (header, value)| {
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:
let protected: HashSet<&str> =
HashSet::from_iter(protected_headers.iter().map(|(header, _value)| *header));
unprotected_headers.retain(|(header, _value)| !protected.contains(header));
// Deduplicate unprotected headers that also are in the protected headers:
let protected: HashSet<&str> =
HashSet::from_iter(protected_headers.iter().map(|(header, _value)| *header));
unprotected_headers.retain(|(header, _value)| !protected.contains(header));
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"),
],
)
}
message
};
let MimeFactory {
@@ -2192,10 +2157,6 @@ fn group_headers_by_confidentiality(
}
}
} 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())
}
}

View File

@@ -601,70 +601,6 @@ async fn test_selfavatar_unencrypted() -> anyhow::Result<()> {
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.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_remove_member_bcc() -> Result<()> {

View File

@@ -304,37 +304,9 @@ impl MimeMessage {
// Parse hidden headers.
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
&& mimetype.subtype().as_str() == "mixed"
&& let Some(part) = part.subparts.first()
&& let Some(part) = mail.subparts.first()
{
for field in &part.headers {
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
// them in signed-only emails, but has no value currently.
// Remove headers that are allowed _only_ in the encrypted+signed part
let encrypted = false;
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
/// optionally encrypted) part. This is independent from the modern Header Protection defined in
/// <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 {
key.starts_with("chat-")
|| matches!(

View File

@@ -7,6 +7,7 @@ use crate::{
chat,
chatlist::Chatlist,
constants::{self, Blocked, DC_DESIRED_TEXT_LEN, DC_ELLIPSIS},
contact::Contact,
key,
message::{MessageState, MessengerMessage},
receive_imf::receive_imf,
@@ -2041,32 +2042,24 @@ async fn test_multiple_autocrypt_hdrs() -> Result<()> {
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)]
async fn test_protected_date() -> Result<()> {
async fn test_receive_signed_only() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().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_msg_id = chat::send_text_msg(alice, alice_chat.id, "Hello!".to_string()).await?;
let alice_msg = Message::load_from_db(alice, alice_msg_id).await?;
assert_eq!(alice_msg.get_showpadlock(), false);
let alice_contact = Contact::get_by_id(bob, msg.from_id).await.unwrap();
assert_eq!(alice_contact.is_key_contact(), 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(())
}

View File

@@ -6,15 +6,15 @@ use std::io::Cursor;
use anyhow::{Context as _, Result, ensure};
use deltachat_contact_tools::{EmailAddress, may_be_valid_addr};
use pgp::composed::{
ArmorOptions, Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType,
MessageBuilder, SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey,
SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig,
Deserializable, DetachedSignature, EncryptionCaps, KeyType as PgpKeyType, MessageBuilder,
SecretKeyParamsBuilder, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey, SignedSecretKey,
SubkeyParamsBuilder, SubpacketConfig,
};
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
use pgp::crypto::ecc_curve::ECCCurve;
use pgp::crypto::hash::HashAlgorithm;
use pgp::crypto::sym::SymmetricKeyAlgorithm;
use pgp::packet::{Signature, SignatureConfig, SignatureType, Subpacket, SubpacketData};
use pgp::packet::{Signature, Subpacket, SubpacketData};
use pgp::types::{
CompressionAlgorithm, Imprint, KeyDetails, KeyVersion, Password, SignedUser, SigningKey as _,
StringToKey,
@@ -202,47 +202,6 @@ pub async fn pk_encrypt(
.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
/// of all keys from the `public_keys_for_validation` keyring that
/// 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--