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(())
}