mirror of
https://github.com/chatmail/core.git
synced 2026-05-07 08:56:30 +03:00
feat: Send Intended Recipient Fingerprint subpackets
Implement "5.2.3.36. Intended Recipient Fingerprint" from RFC 9580.
This commit is contained in:
@@ -1,11 +1,13 @@
|
|||||||
use mailparse::ParsedMail;
|
use mailparse::ParsedMail;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
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},
|
||||||
|
key,
|
||||||
message::{MessageState, MessengerMessage},
|
message::{MessageState, MessengerMessage},
|
||||||
receive_imf::receive_imf,
|
receive_imf::receive_imf,
|
||||||
test_utils::{TestContext, TestContextManager},
|
test_utils::{TestContext, TestContextManager},
|
||||||
@@ -1420,6 +1422,40 @@ async fn test_extra_imf_headers() -> Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
|
async fn test_intended_recipient_fingerprint() -> Result<()> {
|
||||||
|
let mut tcm = TestContextManager::new();
|
||||||
|
let t = &tcm.alice().await;
|
||||||
|
let t_fp = key::load_self_public_key(t).await?.dc_fingerprint();
|
||||||
|
t.set_config_bool(Config::BccSelf, false).await.unwrap();
|
||||||
|
let members = [tcm.bob().await, tcm.fiona().await];
|
||||||
|
let chat_id = chat::create_group(t, "").await?;
|
||||||
|
|
||||||
|
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||||
|
assert!(t.pop_sent_msg_opt(Duration::ZERO).await.is_none());
|
||||||
|
|
||||||
|
for (i, member) in members.iter().enumerate() {
|
||||||
|
let contact = t.add_or_lookup_contact(member).await;
|
||||||
|
chat::add_contact_to_chat(t, chat_id, contact.id).await?;
|
||||||
|
let sent_msg = t.pop_sent_msg().await;
|
||||||
|
let (fp, recipient_fps) = t.parse_msg(&sent_msg).await.signature.unwrap();
|
||||||
|
assert_eq!(fp, t_fp);
|
||||||
|
// `mimefactory` encrypts to self unconditionally.
|
||||||
|
assert_eq!(recipient_fps.len(), 1 + i + 1);
|
||||||
|
assert!(recipient_fps.contains(&t_fp));
|
||||||
|
assert!(recipient_fps.contains(&contact.fingerprint().unwrap()));
|
||||||
|
}
|
||||||
|
|
||||||
|
t.set_config_bool(Config::BccSelf, true).await.unwrap();
|
||||||
|
chat::send_text_msg(t, chat_id, "hi!".to_string()).await?;
|
||||||
|
let sent_msg = t.pop_sent_msg().await;
|
||||||
|
let (fp, recipient_fps) = t.parse_msg(&sent_msg).await.signature.unwrap();
|
||||||
|
assert_eq!(fp, t_fp);
|
||||||
|
assert_eq!(recipient_fps.len(), 1 + members.len());
|
||||||
|
assert!(recipient_fps.contains(&t_fp));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||||
async fn test_long_in_reply_to() -> Result<()> {
|
async fn test_long_in_reply_to() -> Result<()> {
|
||||||
let t = TestContext::new_alice().await;
|
let t = TestContext::new_alice().await;
|
||||||
|
|||||||
59
src/pgp.rs
59
src/pgp.rs
@@ -10,7 +10,7 @@ use pgp::armor::BlockType;
|
|||||||
use pgp::composed::{
|
use pgp::composed::{
|
||||||
ArmorOptions, DecryptionOptions, Deserializable, DetachedSignature, KeyType as PgpKeyType,
|
ArmorOptions, DecryptionOptions, Deserializable, DetachedSignature, KeyType as PgpKeyType,
|
||||||
Message, MessageBuilder, SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey,
|
Message, MessageBuilder, SecretKeyParamsBuilder, SignedPublicKey, SignedPublicSubKey,
|
||||||
SignedSecretKey, SubkeyParamsBuilder, TheRing,
|
SignedSecretKey, SubkeyParamsBuilder, SubpacketConfig, TheRing,
|
||||||
};
|
};
|
||||||
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
use pgp::crypto::aead::{AeadAlgorithm, ChunkSize};
|
||||||
use pgp::crypto::ecc_curve::ECCCurve;
|
use pgp::crypto::ecc_curve::ECCCurve;
|
||||||
@@ -18,7 +18,8 @@ use pgp::crypto::hash::HashAlgorithm;
|
|||||||
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
use pgp::crypto::sym::SymmetricKeyAlgorithm;
|
||||||
use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
|
use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData};
|
||||||
use pgp::types::{
|
use pgp::types::{
|
||||||
CompressionAlgorithm, KeyDetails, Password, PublicKeyTrait, SecretKeyTrait as _, StringToKey,
|
CompressionAlgorithm, KeyDetails, KeyVersion, Password, PublicKeyTrait, SecretKeyTrait as _,
|
||||||
|
StringToKey,
|
||||||
};
|
};
|
||||||
use rand_old::{Rng as _, thread_rng};
|
use rand_old::{Rng as _, thread_rng};
|
||||||
use tokio::runtime::Handle;
|
use tokio::runtime::Handle;
|
||||||
@@ -190,6 +191,30 @@ pub async fn pk_encrypt(
|
|||||||
let pkeys = public_keys_for_encryption
|
let pkeys = public_keys_for_encryption
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(select_pk_for_encryption);
|
.filter_map(select_pk_for_encryption);
|
||||||
|
let subpkts = {
|
||||||
|
let mut hashed = Vec::with_capacity(1 + public_keys_for_encryption.len() + 1);
|
||||||
|
hashed.push(Subpacket::critical(SubpacketData::SignatureCreationTime(
|
||||||
|
chrono::Utc::now().trunc_subsecs(0),
|
||||||
|
))?);
|
||||||
|
for key in &public_keys_for_encryption {
|
||||||
|
let data = SubpacketData::IntendedRecipientFingerprint(key.fingerprint());
|
||||||
|
let subpkt = match private_key_for_signing.version() < KeyVersion::V6 {
|
||||||
|
true => Subpacket::regular(data)?,
|
||||||
|
false => Subpacket::critical(data)?,
|
||||||
|
};
|
||||||
|
hashed.push(subpkt);
|
||||||
|
}
|
||||||
|
hashed.push(Subpacket::regular(SubpacketData::IssuerFingerprint(
|
||||||
|
private_key_for_signing.fingerprint(),
|
||||||
|
))?);
|
||||||
|
let mut unhashed = vec![];
|
||||||
|
if private_key_for_signing.version() <= KeyVersion::V4 {
|
||||||
|
unhashed.push(Subpacket::regular(SubpacketData::Issuer(
|
||||||
|
private_key_for_signing.key_id(),
|
||||||
|
))?);
|
||||||
|
}
|
||||||
|
SubpacketConfig::UserDefined { hashed, unhashed }
|
||||||
|
};
|
||||||
|
|
||||||
let msg = MessageBuilder::from_bytes("", plain);
|
let msg = MessageBuilder::from_bytes("", plain);
|
||||||
let encoded_msg = match seipd_version {
|
let encoded_msg = match seipd_version {
|
||||||
@@ -205,7 +230,12 @@ pub async fn pk_encrypt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hash_algorithm = private_key_for_signing.hash_alg();
|
let hash_algorithm = private_key_for_signing.hash_alg();
|
||||||
msg.sign(&*private_key_for_signing, Password::empty(), hash_algorithm);
|
msg.sign_with_subpackets(
|
||||||
|
&*private_key_for_signing,
|
||||||
|
Password::empty(),
|
||||||
|
hash_algorithm,
|
||||||
|
subpkts,
|
||||||
|
);
|
||||||
if compress {
|
if compress {
|
||||||
msg.compression(CompressionAlgorithm::ZLIB);
|
msg.compression(CompressionAlgorithm::ZLIB);
|
||||||
}
|
}
|
||||||
@@ -229,7 +259,12 @@ pub async fn pk_encrypt(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let hash_algorithm = private_key_for_signing.hash_alg();
|
let hash_algorithm = private_key_for_signing.hash_alg();
|
||||||
msg.sign(&*private_key_for_signing, Password::empty(), hash_algorithm);
|
msg.sign_with_subpackets(
|
||||||
|
&*private_key_for_signing,
|
||||||
|
Password::empty(),
|
||||||
|
hash_algorithm,
|
||||||
|
subpkts,
|
||||||
|
);
|
||||||
if compress {
|
if compress {
|
||||||
msg.compression(CompressionAlgorithm::ZLIB);
|
msg.compression(CompressionAlgorithm::ZLIB);
|
||||||
}
|
}
|
||||||
@@ -264,9 +299,14 @@ pub fn pk_calc_signature(
|
|||||||
chrono::Utc::now().trunc_subsecs(0),
|
chrono::Utc::now().trunc_subsecs(0),
|
||||||
))?,
|
))?,
|
||||||
];
|
];
|
||||||
config.unhashed_subpackets = vec![Subpacket::regular(SubpacketData::Issuer(
|
config.unhashed_subpackets = vec![];
|
||||||
private_key_for_signing.key_id(),
|
if private_key_for_signing.version() <= KeyVersion::V4 {
|
||||||
))?];
|
config
|
||||||
|
.unhashed_subpackets
|
||||||
|
.push(Subpacket::regular(SubpacketData::Issuer(
|
||||||
|
private_key_for_signing.key_id(),
|
||||||
|
))?);
|
||||||
|
}
|
||||||
|
|
||||||
let signature = config.sign(
|
let signature = config.sign(
|
||||||
&private_key_for_signing.primary_key,
|
&private_key_for_signing.primary_key,
|
||||||
@@ -634,8 +674,7 @@ mod tests {
|
|||||||
assert_eq!(content, CLEARTEXT);
|
assert_eq!(content, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 1);
|
assert_eq!(valid_signatures.len(), 1);
|
||||||
for recipient_fps in valid_signatures.values() {
|
for recipient_fps in valid_signatures.values() {
|
||||||
// Intended Recipient Fingerprint subpackets aren't added currently.
|
assert_eq!(recipient_fps.len(), 2);
|
||||||
assert_eq!(recipient_fps.len(), 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check decrypting as Bob
|
// Check decrypting as Bob
|
||||||
@@ -650,7 +689,7 @@ mod tests {
|
|||||||
assert_eq!(content, CLEARTEXT);
|
assert_eq!(content, CLEARTEXT);
|
||||||
assert_eq!(valid_signatures.len(), 1);
|
assert_eq!(valid_signatures.len(), 1);
|
||||||
for recipient_fps in valid_signatures.values() {
|
for recipient_fps in valid_signatures.values() {
|
||||||
assert_eq!(recipient_fps.len(), 0);
|
assert_eq!(recipient_fps.len(), 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user