Skip to content

Commit 612aa14

Browse files
iequidoolink2xt
authored andcommitted
fix: Check that peer SecureJoin messages (except vc/vg-request) gossip our addr+pubkey
This fixes the following identity-misbinding attack: It appears that Bob’s messages in the SecureJoin protocol do not properly “bind” to Alice’s public key or fingerprint. Even though Bob’s messages carry Alice’s public key and address as a gossip in the protected payload, Alice does not reject the message if the gossiped key is different from her own key. As a result, Mallory could perform an identity-misbinding attack. If Mallory obtained Alice’s QR invite code, she could change her own QR code to contain the same tokens as in Alice’s QR code, and convince Bob to scan the modified QR code, possibly as an insider attacker. Mallory would forward messages from Bob to Alice and craft appropriate responses for Bob on his own. In the end, Bob would believe he is talking to Mallory, but Alice would believe she is talking to Bob.
1 parent 781d3ab commit 612aa14

File tree

1 file changed

+66
-16
lines changed

1 file changed

+66
-16
lines changed

src/securejoin.rs

Lines changed: 66 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,23 @@ pub(crate) async fn handle_securejoin_handshake(
295295

296296
let join_vg = step.starts_with("vg-");
297297

298+
if !matches!(step.as_str(), "vg-request" | "vc-request") {
299+
let mut self_found = false;
300+
let self_fingerprint = load_self_public_key(context).await?.fingerprint();
301+
for (addr, key) in &mime_message.gossiped_keys {
302+
if key.fingerprint() == self_fingerprint && context.is_self_addr(addr).await? {
303+
self_found = true;
304+
break;
305+
}
306+
}
307+
if !self_found {
308+
// This message isn't intended for us. Possibly the peer doesn't own the key which the
309+
// message is signed with but forwarded someone's message to us.
310+
warn!(context, "Step {step}: No self addr+pubkey gossip found.");
311+
return Ok(HandshakeMessage::Ignore);
312+
}
313+
}
314+
298315
match step.as_str() {
299316
"vg-request" | "vc-request" => {
300317
/*=======================================================
@@ -753,19 +770,32 @@ mod tests {
753770
use crate::tools::{EmailAddress, SystemTime};
754771
use std::time::Duration;
755772

773+
#[derive(PartialEq)]
774+
enum SetupContactCase {
775+
Normal,
776+
CheckProtectionTimestamp,
777+
WrongAliceGossip,
778+
}
779+
756780
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
757781
async fn test_setup_contact() {
758-
test_setup_contact_ex(false).await
782+
test_setup_contact_ex(SetupContactCase::Normal).await
759783
}
760784

761785
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
762786
async fn test_setup_contact_protection_timestamp() {
763-
test_setup_contact_ex(true).await
787+
test_setup_contact_ex(SetupContactCase::CheckProtectionTimestamp).await
764788
}
765789

766-
async fn test_setup_contact_ex(check_protection_timestamp: bool) {
790+
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
791+
async fn test_setup_contact_wrong_alice_gossip() {
792+
test_setup_contact_ex(SetupContactCase::WrongAliceGossip).await
793+
}
794+
795+
async fn test_setup_contact_ex(case: SetupContactCase) {
767796
let mut tcm = TestContextManager::new();
768797
let alice = tcm.alice().await;
798+
let alice_addr = &alice.get_config(Config::Addr).await.unwrap().unwrap();
769799
let bob = tcm.bob().await;
770800
alice
771801
.set_config(Config::VerifiedOneOnOneChats, Some("1"))
@@ -798,10 +828,7 @@ mod tests {
798828
);
799829

800830
let sent = bob.pop_sent_msg().await;
801-
assert_eq!(
802-
sent.recipient(),
803-
EmailAddress::new("alice@example.org").unwrap()
804-
);
831+
assert_eq!(sent.recipient(), EmailAddress::new(alice_addr).unwrap());
805832
let msg = alice.parse_msg(&sent).await;
806833
assert!(!msg.was_encrypted());
807834
assert_eq!(msg.get_header(HeaderDef::SecureJoin).unwrap(), "vc-request");
@@ -839,7 +866,7 @@ mod tests {
839866
progress,
840867
} => {
841868
let alice_contact_id =
842-
Contact::lookup_id_by_addr(&bob.ctx, "alice@example.org", Origin::Unknown)
869+
Contact::lookup_id_by_addr(&bob.ctx, alice_addr, Origin::Unknown)
843870
.await
844871
.expect("Error looking up contact")
845872
.expect("Contact not found");
@@ -851,7 +878,7 @@ mod tests {
851878

852879
// Check Bob sent the right message.
853880
let sent = bob.pop_sent_msg().await;
854-
let msg = alice.parse_msg(&sent).await;
881+
let mut msg = alice.parse_msg(&sent).await;
855882
let vc_request_with_auth_ts_sent = msg
856883
.get_header(HeaderDef::Date)
857884
.and_then(|value| mailparse::dateparse(value).ok())
@@ -868,6 +895,30 @@ mod tests {
868895
bob_fp.hex()
869896
);
870897

898+
if case == SetupContactCase::WrongAliceGossip {
899+
let wrong_pubkey = load_self_public_key(&bob).await.unwrap();
900+
let alice_pubkey = msg
901+
.gossiped_keys
902+
.insert(alice_addr.to_string(), wrong_pubkey)
903+
.unwrap();
904+
let contact_bob = alice.add_or_lookup_contact(&bob).await;
905+
let handshake_msg = handle_securejoin_handshake(&alice, &msg, contact_bob.id)
906+
.await
907+
.unwrap();
908+
assert_eq!(handshake_msg, HandshakeMessage::Ignore);
909+
assert_eq!(contact_bob.is_verified(&alice.ctx).await.unwrap(), false);
910+
911+
msg.gossiped_keys
912+
.insert(alice_addr.to_string(), alice_pubkey)
913+
.unwrap();
914+
let handshake_msg = handle_securejoin_handshake(&alice, &msg, contact_bob.id)
915+
.await
916+
.unwrap();
917+
assert_eq!(handshake_msg, HandshakeMessage::Ignore);
918+
assert!(contact_bob.is_verified(&alice.ctx).await.unwrap());
919+
return;
920+
}
921+
871922
// Alice should not yet have Bob verified
872923
let contact_bob_id =
873924
Contact::lookup_id_by_addr(&alice.ctx, "bob@example.net", Origin::Unknown)
@@ -879,7 +930,7 @@ mod tests {
879930
.unwrap();
880931
assert_eq!(contact_bob.is_verified(&alice.ctx).await.unwrap(), false);
881932

882-
if check_protection_timestamp {
933+
if case == SetupContactCase::CheckProtectionTimestamp {
883934
SystemTime::shift(Duration::from_secs(3600));
884935
}
885936

@@ -908,7 +959,7 @@ mod tests {
908959
assert!(msg.is_info());
909960
let expected_text = chat_protection_enabled(&alice).await;
910961
assert_eq!(msg.get_text(), expected_text);
911-
if check_protection_timestamp {
962+
if case == SetupContactCase::CheckProtectionTimestamp {
912963
assert_eq!(msg.timestamp_sort, vc_request_with_auth_ts_sent);
913964
}
914965
}
@@ -923,11 +974,10 @@ mod tests {
923974
);
924975

925976
// Bob should not yet have Alice verified
926-
let contact_alice_id =
927-
Contact::lookup_id_by_addr(&bob.ctx, "alice@example.org", Origin::Unknown)
928-
.await
929-
.expect("Error looking up contact")
930-
.expect("Contact not found");
977+
let contact_alice_id = Contact::lookup_id_by_addr(&bob.ctx, alice_addr, Origin::Unknown)
978+
.await
979+
.expect("Error looking up contact")
980+
.expect("Contact not found");
931981
let contact_alice = Contact::get_by_id(&bob.ctx, contact_alice_id)
932982
.await
933983
.unwrap();

0 commit comments

Comments
 (0)