Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 44 additions & 29 deletions src/aheader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,13 @@ pub struct Aheader {
pub addr: String,
pub public_key: SignedPublicKey,
pub prefer_encrypt: EncryptPreference,
}

impl Aheader {
/// Creates new autocrypt header
pub fn new(
addr: String,
public_key: SignedPublicKey,
prefer_encrypt: EncryptPreference,
) -> Self {
Aheader {
addr,
public_key,
prefer_encrypt,
}
}
// Whether `_verified` attribute is present.
//
// `_verified` attribute is an extension to `Autocrypt-Gossip`
// header that is used to tell that the sender
// marked this key as verified.
pub verified: bool,
}

impl fmt::Display for Aheader {
Expand All @@ -69,6 +61,9 @@ impl fmt::Display for Aheader {
if self.prefer_encrypt == EncryptPreference::Mutual {
write!(fmt, " prefer-encrypt=mutual;")?;
}
if self.verified {
write!(fmt, " _verified=1;")?;
}

// adds a whitespace every 78 characters, this allows
// email crate to wrap the lines according to RFC 5322
Expand Down Expand Up @@ -123,6 +118,8 @@ impl FromStr for Aheader {
.and_then(|raw| raw.parse().ok())
.unwrap_or_default();

let verified = attributes.remove("_verified").is_some();

// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
// Autocrypt-Level0: unknown attribute, treat the header as invalid
if attributes.keys().any(|k| !k.starts_with('_')) {
Expand All @@ -133,6 +130,7 @@ impl FromStr for Aheader {
addr,
public_key,
prefer_encrypt,
verified,
})
}
}
Expand All @@ -150,6 +148,7 @@ mod tests {

assert_eq!(h.addr, "me@mail.com");
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
assert_eq!(h.verified, false);
Ok(())
}

Expand Down Expand Up @@ -243,11 +242,12 @@ mod tests {
assert!(
format!(
"{}",
Aheader::new(
"test@example.com".to_string(),
SignedPublicKey::from_base64(RAWKEY).unwrap(),
EncryptPreference::Mutual
)
Aheader {
addr: "test@example.com".to_string(),
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
prefer_encrypt: EncryptPreference::Mutual,
verified: false
}
)
.contains("prefer-encrypt=mutual;")
);
Expand All @@ -258,11 +258,12 @@ mod tests {
assert!(
!format!(
"{}",
Aheader::new(
"test@example.com".to_string(),
SignedPublicKey::from_base64(RAWKEY).unwrap(),
EncryptPreference::NoPreference
)
Aheader {
addr: "test@example.com".to_string(),
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
prefer_encrypt: EncryptPreference::NoPreference,
verified: false
}
)
.contains("prefer-encrypt")
);
Expand All @@ -271,13 +272,27 @@ mod tests {
assert!(
format!(
"{}",
Aheader::new(
"TeSt@eXaMpLe.cOm".to_string(),
SignedPublicKey::from_base64(RAWKEY).unwrap(),
EncryptPreference::Mutual
)
Aheader {
addr: "TeSt@eXaMpLe.cOm".to_string(),
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
prefer_encrypt: EncryptPreference::Mutual,
verified: false
}
)
.contains("test@example.com")
);

assert!(
format!(
"{}",
Aheader {
addr: "test@example.com".to_string(),
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
prefer_encrypt: EncryptPreference::NoPreference,
verified: true
}
)
.contains("_verified")
);
}
}
9 changes: 6 additions & 3 deletions src/e2ee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ impl EncryptHelper {
}

pub fn get_aheader(&self) -> Aheader {
let pk = self.public_key.clone();
let addr = self.addr.to_string();
Aheader::new(addr, pk, self.prefer_encrypt)
Aheader {
addr: self.addr.clone(),
public_key: self.public_key.clone(),
prefer_encrypt: self.prefer_encrypt,
verified: false,
}
}

/// Tries to encrypt the passed in `mail`.
Expand Down
11 changes: 6 additions & 5 deletions src/mimefactory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1092,13 +1092,14 @@ impl MimeFactory {
continue;
}

let header = Aheader::new(
addr.clone(),
key.clone(),
let header = Aheader {
addr: addr.clone(),
public_key: key.clone(),
// Autocrypt 1.1.0 specification says that
// `prefer-encrypt` attribute SHOULD NOT be included.
EncryptPreference::NoPreference,
)
prefer_encrypt: EncryptPreference::NoPreference,
verified: false,
}
.to_string();

message = message.header(
Expand Down
26 changes: 21 additions & 5 deletions src/mimeparser.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! # MIME message parsing module.

use std::cmp::min;
use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::path::Path;
use std::str;
use std::str::FromStr;
Expand Down Expand Up @@ -36,6 +36,17 @@ use crate::tools::{
};
use crate::{chatlist_events, location, stock_str, tools};

/// Public key extracted from `Autocrypt-Gossip`
/// header with associated information.
#[derive(Debug)]
pub struct GossipedKey {
/// Public key extracted from `keydata` attribute.
pub public_key: SignedPublicKey,

/// True if `Autocrypt-Gossip` has a `_verified` attribute.
pub verified: bool,
}

/// A parsed MIME message.
///
/// This represents the relevant information of a parsed MIME message
Expand Down Expand Up @@ -85,7 +96,7 @@ pub(crate) struct MimeMessage {

/// The addresses for which there was a gossip header
/// and their respective gossiped keys.
pub gossiped_keys: HashMap<String, SignedPublicKey>,
pub gossiped_keys: BTreeMap<String, GossipedKey>,

/// Fingerprint of the key in the Autocrypt header.
///
Expand Down Expand Up @@ -1967,9 +1978,9 @@ async fn parse_gossip_headers(
from: &str,
recipients: &[SingleInfo],
gossip_headers: Vec<String>,
) -> Result<HashMap<String, SignedPublicKey>> {
) -> Result<BTreeMap<String, GossipedKey>> {
// XXX split the parsing from the modification part
let mut gossiped_keys: HashMap<String, SignedPublicKey> = Default::default();
let mut gossiped_keys: BTreeMap<String, GossipedKey> = Default::default();

for value in &gossip_headers {
let header = match value.parse::<Aheader>() {
Expand Down Expand Up @@ -2011,7 +2022,12 @@ async fn parse_gossip_headers(
)
.await?;

gossiped_keys.insert(header.addr.to_lowercase(), header.public_key);
let gossiped_key = GossipedKey {
public_key: header.public_key,

verified: header.verified,
};
gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key);
}

Ok(gossiped_keys)
Expand Down
34 changes: 26 additions & 8 deletions src/receive_imf.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Internet Message Format reception pipeline.

use std::collections::{HashMap, HashSet};
use std::collections::{BTreeMap, HashSet};
use std::iter;
use std::sync::LazyLock;

Expand Down Expand Up @@ -28,14 +28,14 @@ use crate::events::EventType;
use crate::headerdef::{HeaderDef, HeaderDefMap};
use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table};
use crate::key::self_fingerprint_opt;
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
use crate::key::{DcKey, Fingerprint};
use crate::log::LogExt;
use crate::log::{info, warn};
use crate::logged_debug_assert;
use crate::message::{
self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
};
use crate::mimeparser::{AvatarAction, MimeMessage, SystemMessage, parse_message_ids};
use crate::mimeparser::{AvatarAction, GossipedKey, MimeMessage, SystemMessage, parse_message_ids};
use crate::param::{Param, Params};
use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub};
use crate::reaction::{Reaction, set_msg_reaction};
Expand Down Expand Up @@ -835,7 +835,7 @@ pub(crate) async fn receive_imf_inner(
context
.sql
.transaction(move |transaction| {
let fingerprint = gossiped_key.dc_fingerprint().hex();
let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
transaction.execute(
"INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
VALUES (?, ?, ?)
Expand Down Expand Up @@ -2917,7 +2917,7 @@ async fn apply_group_changes(
// highest `add_timestamp` to disambiguate.
// The result of the error is that info message
// may contain display name of the wrong contact.
let fingerprint = key.dc_fingerprint().hex();
let fingerprint = key.public_key.dc_fingerprint().hex();
if let Some(contact_id) =
lookup_key_contact_by_fingerprint(context, &fingerprint).await?
{
Expand Down Expand Up @@ -3659,10 +3659,28 @@ async fn mark_recipients_as_verified(
to_ids: &[Option<ContactId>],
mimeparser: &MimeMessage,
) -> Result<()> {
let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
for gossiped_key in mimeparser
.gossiped_keys
.values()
.filter(|gossiped_key| gossiped_key.verified)
{
let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
continue;
};

if to_id == ContactId::SELF || to_id == from_id {
continue;
}

mark_contact_id_as_verified(context, to_id, verifier_id).await?;
ChatId::set_protection_for_contact(context, to_id, mimeparser.timestamp_sent).await?;
}

if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
return Ok(());
}
let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
for to_id in to_ids.iter().filter_map(|&x| x) {
if to_id == ContactId::SELF || to_id == from_id {
continue;
Expand Down Expand Up @@ -3755,7 +3773,7 @@ async fn add_or_lookup_contacts_by_address_list(
async fn add_or_lookup_key_contacts(
context: &Context,
address_list: &[SingleInfo],
gossiped_keys: &HashMap<String, SignedPublicKey>,
gossiped_keys: &BTreeMap<String, GossipedKey>,
fingerprints: &[Fingerprint],
origin: Origin,
) -> Result<Vec<Option<ContactId>>> {
Expand All @@ -3771,7 +3789,7 @@ async fn add_or_lookup_key_contacts(
// Iterator has not ran out of fingerprints yet.
fp.hex()
} else if let Some(key) = gossiped_keys.get(addr) {
key.dc_fingerprint().hex()
key.public_key.dc_fingerprint().hex()
} else if context.is_self_addr(addr).await? {
contact_ids.push(Some(ContactId::SELF));
continue;
Expand Down
6 changes: 4 additions & 2 deletions src/securejoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ pub(crate) async fn handle_securejoin_handshake(
let mut self_found = false;
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
for (addr, key) in &mime_message.gossiped_keys {
if key.dc_fingerprint() == self_fingerprint && context.is_self_addr(addr).await? {
if key.public_key.dc_fingerprint() == self_fingerprint
&& context.is_self_addr(addr).await?
{
self_found = true;
break;
}
Expand Down Expand Up @@ -542,7 +544,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
return Ok(HandshakeMessage::Ignore);
};

if key.dc_fingerprint() != contact_fingerprint {
if key.public_key.dc_fingerprint() != contact_fingerprint {
// Fingerprint does not match, ignore.
warn!(context, "Fingerprint does not match.");
return Ok(HandshakeMessage::Ignore);
Expand Down
6 changes: 5 additions & 1 deletion src/securejoin/securejoin_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use crate::chat::{CantSendReason, remove_contact_from_chat};
use crate::chatlist::Chatlist;
use crate::constants::Chattype;
use crate::key::self_fingerprint;
use crate::mimeparser::GossipedKey;
use crate::receive_imf::receive_imf;
use crate::stock_str::{self, messages_e2e_encrypted};
use crate::test_utils::{
Expand Down Expand Up @@ -185,7 +186,10 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
);

if case == SetupContactCase::WrongAliceGossip {
let wrong_pubkey = load_self_public_key(&bob).await.unwrap();
let wrong_pubkey = GossipedKey {
public_key: load_self_public_key(&bob).await.unwrap(),
verified: false,
};
let alice_pubkey = msg
.gossiped_keys
.insert(alice_addr.to_string(), wrong_pubkey)
Expand Down
Loading