File size: 9,771 Bytes
0220cd3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 | //! "AEAP" means "Automatic Email Address Porting"
//! and was the predecessor of key-contacts
//! (i.e. identifying contacts via the fingerprint,
//! while allowing the email address to change).
//!
//! These tests still pass because key-contacts
//! allows messaging to continue after an email address change,
//! just as AEAP did. Some other tests had to be removed.
use anyhow::Result;
use crate::chat::{self, Chat, ChatId};
use crate::contact::{Contact, ContactId};
use crate::message::Message;
use crate::receive_imf::receive_imf;
use crate::securejoin::get_securejoin_qr;
use crate::test_utils::TestContext;
use crate::test_utils::TestContextManager;
use crate::test_utils::mark_as_verified;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_change_primary_self_addr() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
tcm.send_recv_accept(&alice, &bob, "Hi").await;
let bob_alice_chat = bob.create_chat(&alice).await;
tcm.change_addr(&alice, "alice@someotherdomain.xyz").await;
tcm.section("Bob sends a message to Alice, encrypting to her previous key");
let sent = bob.send_text(bob_alice_chat.id, "hi back").await;
// Alice set up message forwarding so that she still receives
// the message with her new address
let alice_msg = alice.recv_msg(&sent).await;
assert_eq!(alice_msg.text, "hi back".to_string());
assert_eq!(alice_msg.get_showpadlock(), true);
let alice_bob_chat = alice.create_chat(&bob).await;
assert_eq!(alice_msg.chat_id, alice_bob_chat.id);
Ok(())
}
enum ChatForTransition {
OneToOne,
GroupChat,
VerifiedGroup,
}
use ChatForTransition::*;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_aeap_transition_0() {
check_aeap_transition(OneToOne, false).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_aeap_transition_1() {
check_aeap_transition(GroupChat, false).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_aeap_transition_0_verified() {
check_aeap_transition(OneToOne, true).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_aeap_transition_1_verified() {
check_aeap_transition(GroupChat, true).await;
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_aeap_transition_2_verified() {
check_aeap_transition(VerifiedGroup, true).await;
}
/// Happy path test for AEAP.
/// - `chat_for_transition`: Which chat the transition message should be sent in
/// - `verified`: Whether Alice and Bob verified each other
async fn check_aeap_transition(chat_for_transition: ChatForTransition, verified: bool) {
const ALICE_NEW_ADDR: &str = "alice2@example.net";
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
tcm.send_recv_accept(alice, bob, "Hi").await;
tcm.send_recv(bob, alice, "Hi back").await;
if verified {
mark_as_verified(alice, bob).await;
mark_as_verified(bob, alice).await;
}
let mut groups = vec![
chat::create_group(bob, "Group 0").await.unwrap(),
chat::create_group(bob, "Group 1").await.unwrap(),
];
if verified {
groups.push(chat::create_group(bob, "Group 2").await.unwrap());
groups.push(chat::create_group(bob, "Group 3").await.unwrap());
}
let alice_contact = bob.add_or_lookup_contact_id(alice).await;
for group in &groups {
chat::add_contact_to_chat(bob, *group, alice_contact)
.await
.unwrap();
}
// groups 0 and 2 stay unpromoted (i.e. local
// on Bob's device, Alice doesn't know about them)
tcm.section("Promoting group 1");
let sent = bob.send_text(groups[1], "group created").await;
let group1_alice = alice.recv_msg(&sent).await.chat_id;
let mut group3_alice = None;
if verified {
tcm.section("Promoting group 3");
let sent = bob.send_text(groups[3], "group created").await;
group3_alice = Some(alice.recv_msg(&sent).await.chat_id);
}
tcm.change_addr(alice, ALICE_NEW_ADDR).await;
tcm.section("Alice sends another message to Bob, this time from her new addr");
// No matter which chat Alice sends to, the transition should be done in all groups
let chat_to_send = match chat_for_transition {
OneToOne => alice.create_chat(bob).await.id,
GroupChat => group1_alice,
VerifiedGroup => group3_alice.expect("No verified group"),
};
let sent = alice
.send_text(chat_to_send, "Hello from my new addr!")
.await;
let recvd = bob.recv_msg(&sent).await;
assert_eq!(recvd.text, "Hello from my new addr!");
tcm.section("Check that the AEAP transition worked");
check_that_transition_worked(bob, &groups, alice_contact, ALICE_NEW_ADDR).await;
tcm.section("Test switching back");
tcm.change_addr(alice, "alice@example.org").await;
let sent = alice
.send_text(chat_to_send, "Hello from my old addr!")
.await;
let recvd = bob.recv_msg(&sent).await;
assert_eq!(recvd.text, "Hello from my old addr!");
check_that_transition_worked(bob, &groups, alice_contact, "alice@example.org").await;
}
async fn check_that_transition_worked(
bob: &TestContext,
groups: &[ChatId],
alice_contact_id: ContactId,
alice_addr: &str,
) {
for group in groups {
let members = chat::get_chat_contacts(bob, *group).await.unwrap();
// In all the groups, exactly Bob and Alice are members.
assert_eq!(
members.len(),
2,
"Group {} has members {:?}, but should have members {:?} and {:?}",
group,
&members,
alice_contact_id,
ContactId::SELF
);
assert!(
members.contains(&alice_contact_id),
"Group {group} lacks {alice_contact_id}"
);
assert!(members.contains(&ContactId::SELF));
}
// Test that the email address of Alice is updated.
let alice_contact = Contact::get_by_id(bob, alice_contact_id).await.unwrap();
assert_eq!(alice_contact.get_addr(), alice_addr);
}
/// Test that an attacker - here Fiona - can't replay a message sent by Alice
/// to make Bob think that there was a transition to Fiona's address.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_aeap_replay_attack() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = tcm.alice().await;
let bob = tcm.bob().await;
let fiona = tcm.fiona().await;
tcm.send_recv_accept(&alice, &bob, "Hi").await;
tcm.send_recv(&bob, &alice, "Hi back").await;
let group = chat::create_group(&bob, "Group 0").await?;
let bob_alice_contact = bob.add_or_lookup_contact_id(&alice).await;
let bob_fiona_contact = bob.add_or_lookup_contact_id(&fiona).await;
chat::add_contact_to_chat(&bob, group, bob_alice_contact).await?;
// Alice sends a message which Bob doesn't receive or something
// A real attack would rather reuse a message that was sent to a group
// and replace the Message-Id or so.
let chat = alice.create_chat(&bob).await;
let sent = alice.send_text(chat.id, "whoop whoop").await;
// Fiona gets the message, replaces the From addr...
let sent = sent
.payload()
.replace("From: <alice@example.org>", "From: <fiona@example.net>");
sent.find("From: <fiona@example.net>").unwrap(); // Assert that it worked
// Autocrypt header is protected, nothing to replace outside.
// In the signed part we cannot replace it without breaking the signature.
assert!(!sent.contains("addr=alice@example.org;"));
tcm.section("Fiona replaced the From addr and forwards the message to Bob");
receive_imf(&bob, sent.as_bytes(), false).await?.unwrap();
// Check that no transition was done
assert!(chat::is_contact_in_chat(&bob, group, bob_alice_contact).await?);
assert!(!chat::is_contact_in_chat(&bob, group, bob_fiona_contact).await?);
Ok(())
}
/// Tests that writing to a contact is possible
/// after address change.
///
/// This test is redundant after introduction
/// of key-contacts, but is kept to avoid deleting the tests.
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_write_to_alice_after_aeap() -> Result<()> {
let mut tcm = TestContextManager::new();
let alice = &tcm.alice().await;
let bob = &tcm.bob().await;
let alice_grp_id = chat::create_group(alice, "Group").await?;
let qr = get_securejoin_qr(alice, Some(alice_grp_id)).await?;
tcm.exec_securejoin_qr(bob, alice, &qr).await;
let bob_alice_contact = bob.add_or_lookup_contact(alice).await;
assert!(bob_alice_contact.is_verified(bob).await?);
let bob_alice_chat = bob.create_chat(alice).await;
let bob_unprotected_grp_id = bob.create_group_with_members("Group", &[alice]).await;
tcm.change_addr(alice, "alice@someotherdomain.xyz").await;
let sent = alice.send_text(alice_grp_id, "Hello!").await;
bob.recv_msg(&sent).await;
assert!(bob_alice_contact.is_verified(bob).await?);
let bob_alice_chat = Chat::load_from_db(bob, bob_alice_chat.id).await?;
let mut msg = Message::new_text("hi".to_string());
chat::send_msg(bob, bob_alice_chat.id, &mut msg).await?;
// Encrypted communication is also possible in unprotected groups with Alice.
let sent = bob
.send_text(bob_unprotected_grp_id, "Alice, how is your address change?")
.await;
let msg = Message::load_from_db(bob, sent.sender_msg_id).await?;
assert!(msg.get_showpadlock());
Ok(())
}
|