Skip to content

Commit 53a3e51

Browse files
committed
feat: support receiving Autocrypt-Gossip with _verified attribute
This commit is a preparation for sending Autocrypt-Gossip with `_verified` attribute instead of `Chat-Verified` header.
1 parent 4033566 commit 53a3e51

File tree

7 files changed

+91
-19
lines changed

7 files changed

+91
-19
lines changed

src/aheader.rs

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ pub struct Aheader {
4646
pub addr: String,
4747
pub public_key: SignedPublicKey,
4848
pub prefer_encrypt: EncryptPreference,
49+
50+
// Whether `_verified` attribute is present.
51+
//
52+
// `_verified` attribute is an extension to `Autocrypt-Gossip`
53+
// header that is used to tell that the sender
54+
// marked this key as verified.
55+
pub verified: bool,
4956
}
5057

5158
impl fmt::Display for Aheader {
@@ -54,6 +61,9 @@ impl fmt::Display for Aheader {
5461
if self.prefer_encrypt == EncryptPreference::Mutual {
5562
write!(fmt, " prefer-encrypt=mutual;")?;
5663
}
64+
if self.verified {
65+
write!(fmt, " _verified=1;")?;
66+
}
5767

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

121+
let verified = attributes.remove("_verified").is_some();
122+
111123
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
112124
// Autocrypt-Level0: unknown attribute, treat the header as invalid
113125
if attributes.keys().any(|k| !k.starts_with('_')) {
@@ -118,6 +130,7 @@ impl FromStr for Aheader {
118130
addr,
119131
public_key,
120132
prefer_encrypt,
133+
verified,
121134
})
122135
}
123136
}
@@ -135,6 +148,7 @@ mod tests {
135148

136149
assert_eq!(h.addr, "[email protected]");
137150
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
151+
assert_eq!(h.verified, false);
138152
Ok(())
139153
}
140154

@@ -231,7 +245,8 @@ mod tests {
231245
Aheader {
232246
addr: "[email protected]".to_string(),
233247
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
234-
prefer_encrypt: EncryptPreference::Mutual
248+
prefer_encrypt: EncryptPreference::Mutual,
249+
verified: false
235250
}
236251
)
237252
.contains("prefer-encrypt=mutual;")
@@ -246,7 +261,8 @@ mod tests {
246261
Aheader {
247262
addr: "[email protected]".to_string(),
248263
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
249-
prefer_encrypt: EncryptPreference::NoPreference
264+
prefer_encrypt: EncryptPreference::NoPreference,
265+
verified: false
250266
}
251267
)
252268
.contains("prefer-encrypt")
@@ -259,10 +275,24 @@ mod tests {
259275
Aheader {
260276
addr: "[email protected]".to_string(),
261277
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
262-
prefer_encrypt: EncryptPreference::Mutual
278+
prefer_encrypt: EncryptPreference::Mutual,
279+
verified: false
263280
}
264281
)
265282
.contains("[email protected]")
266283
);
284+
285+
assert!(
286+
format!(
287+
"{}",
288+
Aheader {
289+
addr: "[email protected]".to_string(),
290+
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
291+
prefer_encrypt: EncryptPreference::NoPreference,
292+
verified: true
293+
}
294+
)
295+
.contains("_verified")
296+
);
267297
}
268298
}

src/e2ee.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ impl EncryptHelper {
3535
addr: self.addr.clone(),
3636
public_key: self.public_key.clone(),
3737
prefer_encrypt: self.prefer_encrypt,
38+
verified: false,
3839
}
3940
}
4041

src/mimefactory.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1098,6 +1098,7 @@ impl MimeFactory {
10981098
// Autocrypt 1.1.0 specification says that
10991099
// `prefer-encrypt` attribute SHOULD NOT be included.
11001100
prefer_encrypt: EncryptPreference::NoPreference,
1101+
verified: false,
11011102
}
11021103
.to_string();
11031104

src/mimeparser.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! # MIME message parsing module.
22
33
use std::cmp::min;
4-
use std::collections::{HashMap, HashSet};
4+
use std::collections::{BTreeMap, HashMap, HashSet};
55
use std::path::Path;
66
use std::str;
77
use std::str::FromStr;
@@ -36,6 +36,17 @@ use crate::tools::{
3636
};
3737
use crate::{chatlist_events, location, stock_str, tools};
3838

39+
/// Public key extracted from `Autocrypt-Gossip`
40+
/// header with associated information.
41+
#[derive(Debug)]
42+
pub struct GossipedKey {
43+
/// Public key extracted from `keydata` attribute.
44+
pub public_key: SignedPublicKey,
45+
46+
/// True if `Autocrypt-Gossip` has a `_verified` attribute.
47+
pub verified: bool,
48+
}
49+
3950
/// A parsed MIME message.
4051
///
4152
/// This represents the relevant information of a parsed MIME message
@@ -85,7 +96,7 @@ pub(crate) struct MimeMessage {
8596

8697
/// The addresses for which there was a gossip header
8798
/// and their respective gossiped keys.
88-
pub gossiped_keys: HashMap<String, SignedPublicKey>,
99+
pub gossiped_keys: BTreeMap<String, GossipedKey>,
89100

90101
/// Fingerprint of the key in the Autocrypt header.
91102
///
@@ -1963,9 +1974,9 @@ async fn parse_gossip_headers(
19631974
from: &str,
19641975
recipients: &[SingleInfo],
19651976
gossip_headers: Vec<String>,
1966-
) -> Result<HashMap<String, SignedPublicKey>> {
1977+
) -> Result<BTreeMap<String, GossipedKey>> {
19671978
// XXX split the parsing from the modification part
1968-
let mut gossiped_keys: HashMap<String, SignedPublicKey> = Default::default();
1979+
let mut gossiped_keys: BTreeMap<String, GossipedKey> = Default::default();
19691980

19701981
for value in &gossip_headers {
19711982
let header = match value.parse::<Aheader>() {
@@ -2007,7 +2018,12 @@ async fn parse_gossip_headers(
20072018
)
20082019
.await?;
20092020

2010-
gossiped_keys.insert(header.addr.to_lowercase(), header.public_key);
2021+
let gossiped_key = GossipedKey {
2022+
public_key: header.public_key,
2023+
2024+
verified: header.verified,
2025+
};
2026+
gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key);
20112027
}
20122028

20132029
Ok(gossiped_keys)

src/receive_imf.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Internet Message Format reception pipeline.
22
3-
use std::collections::{HashMap, HashSet};
3+
use std::collections::{BTreeMap, HashSet};
44
use std::iter;
55
use std::sync::LazyLock;
66

@@ -28,14 +28,14 @@ use crate::events::EventType;
2828
use crate::headerdef::{HeaderDef, HeaderDefMap};
2929
use crate::imap::{GENERATED_PREFIX, markseen_on_imap_table};
3030
use crate::key::self_fingerprint_opt;
31-
use crate::key::{DcKey, Fingerprint, SignedPublicKey};
31+
use crate::key::{DcKey, Fingerprint};
3232
use crate::log::LogExt;
3333
use crate::log::{info, warn};
3434
use crate::logged_debug_assert;
3535
use crate::message::{
3636
self, Message, MessageState, MessengerMessage, MsgId, Viewtype, rfc724_mid_exists,
3737
};
38-
use crate::mimeparser::{AvatarAction, MimeMessage, SystemMessage, parse_message_ids};
38+
use crate::mimeparser::{AvatarAction, GossipedKey, MimeMessage, SystemMessage, parse_message_ids};
3939
use crate::param::{Param, Params};
4040
use crate::peer_channels::{add_gossip_peer_from_header, insert_topic_stub};
4141
use crate::reaction::{Reaction, set_msg_reaction};
@@ -835,7 +835,7 @@ pub(crate) async fn receive_imf_inner(
835835
context
836836
.sql
837837
.transaction(move |transaction| {
838-
let fingerprint = gossiped_key.dc_fingerprint().hex();
838+
let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
839839
transaction.execute(
840840
"INSERT INTO gossip_timestamp (chat_id, fingerprint, timestamp)
841841
VALUES (?, ?, ?)
@@ -2917,7 +2917,7 @@ async fn apply_group_changes(
29172917
// highest `add_timestamp` to disambiguate.
29182918
// The result of the error is that info message
29192919
// may contain display name of the wrong contact.
2920-
let fingerprint = key.dc_fingerprint().hex();
2920+
let fingerprint = key.public_key.dc_fingerprint().hex();
29212921
if let Some(contact_id) =
29222922
lookup_key_contact_by_fingerprint(context, &fingerprint).await?
29232923
{
@@ -3659,10 +3659,28 @@ async fn mark_recipients_as_verified(
36593659
to_ids: &[Option<ContactId>],
36603660
mimeparser: &MimeMessage,
36613661
) -> Result<()> {
3662+
let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
3663+
for gossiped_key in mimeparser
3664+
.gossiped_keys
3665+
.values()
3666+
.filter(|gossiped_key| gossiped_key.verified)
3667+
{
3668+
let fingerprint = gossiped_key.public_key.dc_fingerprint().hex();
3669+
let Some(to_id) = lookup_key_contact_by_fingerprint(context, &fingerprint).await? else {
3670+
continue;
3671+
};
3672+
3673+
if to_id == ContactId::SELF || to_id == from_id {
3674+
continue;
3675+
}
3676+
3677+
mark_contact_id_as_verified(context, to_id, verifier_id).await?;
3678+
ChatId::set_protection_for_contact(context, to_id, mimeparser.timestamp_sent).await?;
3679+
}
3680+
36623681
if mimeparser.get_header(HeaderDef::ChatVerified).is_none() {
36633682
return Ok(());
36643683
}
3665-
let verifier_id = Some(from_id).filter(|&id| id != ContactId::SELF);
36663684
for to_id in to_ids.iter().filter_map(|&x| x) {
36673685
if to_id == ContactId::SELF || to_id == from_id {
36683686
continue;
@@ -3755,7 +3773,7 @@ async fn add_or_lookup_contacts_by_address_list(
37553773
async fn add_or_lookup_key_contacts(
37563774
context: &Context,
37573775
address_list: &[SingleInfo],
3758-
gossiped_keys: &HashMap<String, SignedPublicKey>,
3776+
gossiped_keys: &BTreeMap<String, GossipedKey>,
37593777
fingerprints: &[Fingerprint],
37603778
origin: Origin,
37613779
) -> Result<Vec<Option<ContactId>>> {
@@ -3771,7 +3789,7 @@ async fn add_or_lookup_key_contacts(
37713789
// Iterator has not ran out of fingerprints yet.
37723790
fp.hex()
37733791
} else if let Some(key) = gossiped_keys.get(addr) {
3774-
key.dc_fingerprint().hex()
3792+
key.public_key.dc_fingerprint().hex()
37753793
} else if context.is_self_addr(addr).await? {
37763794
contact_ids.push(Some(ContactId::SELF));
37773795
continue;

src/securejoin.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,9 @@ pub(crate) async fn handle_securejoin_handshake(
272272
let mut self_found = false;
273273
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
274274
for (addr, key) in &mime_message.gossiped_keys {
275-
if key.dc_fingerprint() == self_fingerprint && context.is_self_addr(addr).await? {
275+
if key.public_key.dc_fingerprint() == self_fingerprint
276+
&& context.is_self_addr(addr).await?
277+
{
276278
self_found = true;
277279
break;
278280
}
@@ -542,7 +544,7 @@ pub(crate) async fn observe_securejoin_on_other_device(
542544
return Ok(HandshakeMessage::Ignore);
543545
};
544546

545-
if key.dc_fingerprint() != contact_fingerprint {
547+
if key.public_key.dc_fingerprint() != contact_fingerprint {
546548
// Fingerprint does not match, ignore.
547549
warn!(context, "Fingerprint does not match.");
548550
return Ok(HandshakeMessage::Ignore);

src/securejoin/securejoin_tests.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::chat::{CantSendReason, remove_contact_from_chat};
55
use crate::chatlist::Chatlist;
66
use crate::constants::Chattype;
77
use crate::key::self_fingerprint;
8+
use crate::mimeparser::GossipedKey;
89
use crate::receive_imf::receive_imf;
910
use crate::stock_str::{self, messages_e2e_encrypted};
1011
use crate::test_utils::{
@@ -185,7 +186,10 @@ async fn test_setup_contact_ex(case: SetupContactCase) {
185186
);
186187

187188
if case == SetupContactCase::WrongAliceGossip {
188-
let wrong_pubkey = load_self_public_key(&bob).await.unwrap();
189+
let wrong_pubkey = GossipedKey {
190+
public_key: load_self_public_key(&bob).await.unwrap(),
191+
verified: false,
192+
};
189193
let alice_pubkey = msg
190194
.gossiped_keys
191195
.insert(alice_addr.to_string(), wrong_pubkey)

0 commit comments

Comments
 (0)