Skip to content

Commit 39a3111

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 2c0b3ec commit 39a3111

File tree

7 files changed

+91
-20
lines changed

7 files changed

+91
-20
lines changed

src/aheader.rs

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

5360
impl fmt::Display for Aheader {
@@ -56,6 +63,9 @@ impl fmt::Display for Aheader {
5663
if self.prefer_encrypt == EncryptPreference::Mutual {
5764
write!(fmt, " prefer-encrypt=mutual;")?;
5865
}
66+
if self.verified {
67+
write!(fmt, " _verified=1;")?;
68+
}
5969

6070
// adds a whitespace every 78 characters, this allows
6171
// email crate to wrap the lines according to RFC 5322
@@ -110,6 +120,8 @@ impl FromStr for Aheader {
110120
.and_then(|raw| raw.parse().ok())
111121
.unwrap_or_default();
112122

123+
let verified = attributes.remove("_verified").is_some();
124+
113125
// Autocrypt-Level0: unknown attributes starting with an underscore can be safely ignored
114126
// Autocrypt-Level0: unknown attribute, treat the header as invalid
115127
if attributes.keys().any(|k| !k.starts_with('_')) {
@@ -120,6 +132,7 @@ impl FromStr for Aheader {
120132
addr,
121133
public_key,
122134
prefer_encrypt,
135+
verified,
123136
})
124137
}
125138
}
@@ -137,6 +150,7 @@ mod tests {
137150

138151
assert_eq!(h.addr, "[email protected]");
139152
assert_eq!(h.prefer_encrypt, EncryptPreference::Mutual);
153+
assert_eq!(h.verified, false);
140154
Ok(())
141155
}
142156

@@ -233,7 +247,8 @@ mod tests {
233247
Aheader {
234248
addr: "[email protected]".to_string(),
235249
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
236-
prefer_encrypt: EncryptPreference::Mutual
250+
prefer_encrypt: EncryptPreference::Mutual,
251+
verified: false
237252
}
238253
)
239254
.contains("prefer-encrypt=mutual;")
@@ -248,7 +263,8 @@ mod tests {
248263
Aheader {
249264
addr: "[email protected]".to_string(),
250265
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
251-
prefer_encrypt: EncryptPreference::NoPreference
266+
prefer_encrypt: EncryptPreference::NoPreference,
267+
verified: false
252268
}
253269
)
254270
.contains("prefer-encrypt")
@@ -261,10 +277,24 @@ mod tests {
261277
Aheader {
262278
addr: "[email protected]".to_string(),
263279
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
264-
prefer_encrypt: EncryptPreference::Mutual
280+
prefer_encrypt: EncryptPreference::Mutual,
281+
verified: false
265282
}
266283
)
267284
.contains("[email protected]")
268285
);
286+
287+
assert!(
288+
format!(
289+
"{}",
290+
Aheader {
291+
addr: "[email protected]".to_string(),
292+
public_key: SignedPublicKey::from_base64(RAWKEY).unwrap(),
293+
prefer_encrypt: EncryptPreference::NoPreference,
294+
verified: true
295+
}
296+
)
297+
.contains("_verified")
298+
);
269299
}
270300
}

src/e2ee.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ impl EncryptHelper {
3939
addr: self.addr.clone(),
4040
public_key: self.public_key.clone(),
4141
prefer_encrypt: self.prefer_encrypt,
42+
verified: false,
4243
}
4344
}
4445

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
///
@@ -1967,9 +1978,9 @@ async fn parse_gossip_headers(
19671978
from: &str,
19681979
recipients: &[SingleInfo],
19691980
gossip_headers: Vec<String>,
1970-
) -> Result<HashMap<String, SignedPublicKey>> {
1981+
) -> Result<BTreeMap<String, GossipedKey>> {
19711982
// XXX split the parsing from the modification part
1972-
let mut gossiped_keys: HashMap<String, SignedPublicKey> = Default::default();
1983+
let mut gossiped_keys: BTreeMap<String, GossipedKey> = Default::default();
19731984

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

2014-
gossiped_keys.insert(header.addr.to_lowercase(), header.public_key);
2025+
let gossiped_key = GossipedKey {
2026+
public_key: header.public_key,
2027+
2028+
verified: header.verified,
2029+
};
2030+
gossiped_keys.insert(header.addr.to_lowercase(), gossiped_key);
20152031
}
20162032

20172033
Ok(gossiped_keys)

src/receive_imf.rs

Lines changed: 26 additions & 9 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,15 +3659,32 @@ 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 {
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;
36693687
}
3670-
36713688
mark_contact_id_as_verified(context, to_id, verifier_id).await?;
36723689
ChatId::set_protection_for_contact(context, to_id, mimeparser.timestamp_sent).await?;
36733690
}
@@ -3755,7 +3772,7 @@ async fn add_or_lookup_contacts_by_address_list(
37553772
async fn add_or_lookup_key_contacts(
37563773
context: &Context,
37573774
address_list: &[SingleInfo],
3758-
gossiped_keys: &HashMap<String, SignedPublicKey>,
3775+
gossiped_keys: &BTreeMap<String, GossipedKey>,
37593776
fingerprints: &[Fingerprint],
37603777
origin: Origin,
37613778
) -> Result<Vec<Option<ContactId>>> {
@@ -3771,7 +3788,7 @@ async fn add_or_lookup_key_contacts(
37713788
// Iterator has not ran out of fingerprints yet.
37723789
fp.hex()
37733790
} else if let Some(key) = gossiped_keys.get(addr) {
3774-
key.dc_fingerprint().hex()
3791+
key.public_key.dc_fingerprint().hex()
37753792
} else if context.is_self_addr(addr).await? {
37763793
contact_ids.push(Some(ContactId::SELF));
37773794
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+
is_verified: false,
192+
};
189193
let alice_pubkey = msg
190194
.gossiped_keys
191195
.insert(alice_addr.to_string(), wrong_pubkey)

0 commit comments

Comments
 (0)