mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 15:26:30 +03:00
feat: Remove mostly-unused SignUnencrypted option (#8190)
This commit is contained in:
@@ -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")),
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
10
src/e2ee.rs
10
src/e2ee.rs
@@ -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.
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<()> {
|
||||||
|
|||||||
@@ -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!(
|
||||||
|
|||||||
@@ -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(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
49
src/pgp.rs
49
src/pgp.rs
@@ -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
|
||||||
|
|||||||
70
test-data/message/unencrypted_signed_simple.eml
Normal file
70
test-data/message/unencrypted_signed_simple.eml
Normal 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--
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user