/* * Copyright 2016 OpenMarket Ltd * Copyright 2017 Vector Creations Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.matrix.androidsdk; import android.content.Context; import android.net.Uri; import android.os.SystemClock; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.text.TextUtils; import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.MethodSorters; import org.matrix.androidsdk.crypto.MXCrypto; import org.matrix.androidsdk.crypto.MXCryptoAlgorithms; import org.matrix.androidsdk.crypto.MXCryptoError; import org.matrix.androidsdk.crypto.data.MXDeviceInfo; import org.matrix.androidsdk.crypto.data.MXOlmSessionResult; import org.matrix.androidsdk.crypto.data.MXUsersDevicesMap; import org.matrix.androidsdk.data.EventTimeline; import org.matrix.androidsdk.data.Room; import org.matrix.androidsdk.data.RoomState; import org.matrix.androidsdk.data.store.IMXStore; import org.matrix.androidsdk.data.store.MXFileStore; import org.matrix.androidsdk.data.store.MXStoreListener; import org.matrix.androidsdk.listeners.MXEventListener; import org.matrix.androidsdk.rest.callback.ApiCallback; import org.matrix.androidsdk.rest.model.Event; import org.matrix.androidsdk.rest.model.MatrixError; import org.matrix.androidsdk.rest.model.Message; import org.matrix.androidsdk.rest.model.RoomMember; import org.matrix.androidsdk.rest.model.login.Credentials; import org.matrix.androidsdk.util.JsonUtils; import org.matrix.androidsdk.util.Log; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @RunWith(AndroidJUnit4.class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) public class CryptoTest { private static final String LOG_TAG = "CryptoTest"; private static final List<String> messagesFromAlice = Arrays.asList("0 - Hello I'm Alice!", "4 - Go!"); private static final List<String> messagesFromBob = Arrays.asList("1 - Hello I'm Bob!", "2 - Isn't life grand?", "3 - Let's go to the opera."); private static final String MXTESTS_BOB = "mxBob"; private static final String MXTESTS_BOB_PWD = "bobbob"; private static final String MXTESTS_ALICE = "mxAlice"; private static final String MXTESTS_ALICE_PWD = "alicealice"; private static final String MXTESTS_SAM = "mxSam"; private static final String MXTESTS_SAM_PWD = "samsam"; @Test public void test01_testCryptoNoDeviceId() throws Exception { Log.e(LOG_TAG, "test01_testCryptoNoDeviceId"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); createBobAccount(); assertTrue(null == mBobSession.getCrypto()); mBobSession.getCredentials().deviceId = null; final CountDownLatch lock1 = new CountDownLatch(1); mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto", "enableCrypto"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCrypto")); assertTrue (null != mBobSession.getCrypto()); assertTrue (null != mBobSession.getCredentials().deviceId); mBobSession.clear(context); } @Test public void test02_testCryptoPersistenceInStore() throws Exception { Log.e(LOG_TAG, "test02_testCryptoPersistenceInStore"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); createBobAccount(); mBobSession.getCredentials().deviceId = "BobDevice"; assertTrue (null == mBobSession.getCrypto()); final CountDownLatch lock0 = new CountDownLatch(1); mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto", "enableCrypto"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCrypto")); assertTrue (null != mBobSession.getCrypto()); SystemClock.sleep(1000); final String deviceCurve25519Key = mBobSession.getCrypto().getOlmDevice().getDeviceCurve25519Key(); final String deviceEd25519Key = mBobSession.getCrypto().getOlmDevice().getDeviceEd25519Key(); final List<MXDeviceInfo> myUserDevices = mBobSession.getCrypto().getUserDevices(mBobSession.getMyUserId()); assertTrue (null != myUserDevices); assertTrue (1 == myUserDevices.size()); final Credentials bobCredentials = mBobSession.getCredentials(); Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(bobCredentials); IMXStore store = new MXFileStore(hs, context); MXSession bobSession2 = new MXSession(hs, new MXDataHandler(store, bobCredentials, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); final CountDownLatch lock1 = new CountDownLatch(1); MXStoreListener listener = new MXStoreListener() { @Override public void postProcess(String accountId) { } @Override public void onStoreReady(String accountId) { results.put("onStoreReady", "onStoreReady"); lock1.countDown(); } @Override public void onStoreCorrupted(String accountId, String description) { lock1.countDown(); } @Override public void onStoreOOM(String accountId, String description) { lock1.countDown(); } }; bobSession2.getDataHandler().getStore().addMXStoreListener(listener); bobSession2.getDataHandler().getStore().open(); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onStoreReady")); assertTrue (bobSession2.isCryptoEnabled()); final CountDownLatch lock2 = new CountDownLatch(2); MXEventListener eventsListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock2.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock2.countDown(); } }; bobSession2.getDataHandler().addListener(eventsListener); bobSession2.startEventStream(null); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onInitialSyncComplete")); assertTrue (results.containsKey("onCryptoSyncComplete")); MXCrypto crypto = bobSession2.getCrypto(); assertNotNull(crypto); assertTrue (TextUtils.equals(deviceCurve25519Key, crypto.getOlmDevice().getDeviceCurve25519Key())); assertTrue (TextUtils.equals(deviceEd25519Key, crypto.getOlmDevice().getDeviceEd25519Key())); List<MXDeviceInfo> myUserDevices2 = bobSession2.getCrypto().getUserDevices(bobSession2.getMyUserId()); assertTrue (1 == myUserDevices2.size()); assertTrue (TextUtils.equals(myUserDevices2.get(0).deviceId, myUserDevices.get(0).deviceId)); mBobSession.clear(context); bobSession2.clear(context); } @Test public void test03_testKeysUploadAndDownload() throws Exception { Log.e(LOG_TAG, "test03_testKeysUploadAndDownload"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); createAliceAccount(); mAliceSession.getCredentials().deviceId = "AliceDevice"; final CountDownLatch lock0 = new CountDownLatch(1); mAliceSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto", "enableCrypto"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCrypto")); createBobAccount(); final CountDownLatch lock2 = new CountDownLatch(1); mBobSession.getCredentials().deviceId = "BobDevice"; mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto2", "enableCrypto2"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCrypto2")); final CountDownLatch lock3 = new CountDownLatch(1); mBobSession.getCrypto().getDeviceList().downloadKeys(Arrays.asList(mBobSession.getMyUserId(), mAliceSession.getMyUserId()), false, new ApiCallback<MXUsersDevicesMap<MXDeviceInfo>>() { @Override public void onSuccess(MXUsersDevicesMap<MXDeviceInfo> info) { results.put("downloadKeys", info); lock3.countDown(); } @Override public void onNetworkError(Exception e) { lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3.countDown(); } }); lock3.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("downloadKeys")); MXUsersDevicesMap<MXDeviceInfo> usersDevicesInfoMap = (MXUsersDevicesMap<MXDeviceInfo> )results.get("downloadKeys"); assertTrue (2 == usersDevicesInfoMap.getUserIds().size()); assertTrue (1 == usersDevicesInfoMap.getUserDeviceIds(mAliceSession.getMyUserId()).size()); MXDeviceInfo aliceDeviceFromBobPOV = usersDevicesInfoMap.getObject("AliceDevice", mAliceSession.getMyUserId()); assertTrue (null != aliceDeviceFromBobPOV); assertTrue (TextUtils.equals(aliceDeviceFromBobPOV.fingerprint(), mAliceSession.getCrypto().getOlmDevice().getDeviceEd25519Key())); // Continue testing other methods assertTrue (null != mBobSession.getCrypto().deviceWithIdentityKey(mAliceSession.getCrypto().getOlmDevice().getDeviceCurve25519Key(), mAliceSession.getMyUserId(), MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_OLM)); assertTrue (aliceDeviceFromBobPOV.isUnknown()); final CountDownLatch lock3a = new CountDownLatch(1); mBobSession.getCrypto().setDevicesKnown(Arrays.asList(aliceDeviceFromBobPOV), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDevicesKnown", info); lock3a.countDown(); } @Override public void onNetworkError(Exception e) { lock3a.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3a.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3a.countDown(); } } ); lock3a.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDevicesKnown")); assertTrue (aliceDeviceFromBobPOV.isUnverified()); final CountDownLatch lock3b = new CountDownLatch(1); mBobSession.getCrypto().setDeviceVerification( MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED, aliceDeviceFromBobPOV.deviceId, mAliceSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDeviceVerification1", info); lock3b.countDown(); } @Override public void onNetworkError(Exception e) { lock3b.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3b.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3b.countDown(); } } ); lock3b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDeviceVerification1")); assertTrue (aliceDeviceFromBobPOV.isBlocked()); Credentials bobCredentials = mBobSession.getCredentials(); Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(bobCredentials); IMXStore store = new MXFileStore(hs, context); MXSession bobSession2 = new MXSession(hs, new MXDataHandler(store, bobCredentials, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); final CountDownLatch lock4 = new CountDownLatch(1); MXStoreListener listener = new MXStoreListener() { @Override public void postProcess(String accountId) { } @Override public void onStoreReady(String accountId) { results.put("onStoreReady", "onStoreReady"); lock4.countDown(); } @Override public void onStoreCorrupted(String accountId, String description) { lock4.countDown(); } @Override public void onStoreOOM(String accountId, String description) { lock4.countDown(); } }; bobSession2.getDataHandler().getStore().addMXStoreListener(listener); bobSession2.getDataHandler().getStore().open(); lock4.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onStoreReady")); final CountDownLatch lock4b = new CountDownLatch(2); MXEventListener eventListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock4b.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock4b.countDown(); } }; bobSession2.getDataHandler().addListener(eventListener); bobSession2.startEventStream(null); lock4b.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onInitialSyncComplete")); assertTrue(results.containsKey("onCryptoSyncComplete")); MXDeviceInfo aliceDeviceFromBobPOV2 = bobSession2.getCrypto().deviceWithIdentityKey(mAliceSession.getCrypto().getOlmDevice().getDeviceCurve25519Key(), mAliceSession.getMyUserId(), MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_OLM); assertTrue (null != aliceDeviceFromBobPOV2); assertTrue (TextUtils.equals(aliceDeviceFromBobPOV2.fingerprint(), mAliceSession.getCrypto().getOlmDevice().getDeviceEd25519Key())); assertTrue (aliceDeviceFromBobPOV2.mVerified + " instead of " + MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED, aliceDeviceFromBobPOV2.mVerified == MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED); // Download again alice device final CountDownLatch lock5 = new CountDownLatch(1); bobSession2.getCrypto().getDeviceList().downloadKeys(Arrays.asList(mAliceSession.getMyUserId()), true, new ApiCallback<MXUsersDevicesMap<MXDeviceInfo>>() { @Override public void onSuccess(MXUsersDevicesMap<MXDeviceInfo> info) { results.put("downloadKeys2", info); lock5.countDown(); } @Override public void onNetworkError(Exception e) { lock5.countDown(); } @Override public void onMatrixError(MatrixError e) { lock5.countDown(); } @Override public void onUnexpectedError(Exception e) { lock5.countDown(); } }); lock5.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("downloadKeys2")); MXDeviceInfo aliceDeviceFromBobPOV3 = bobSession2.getCrypto().deviceWithIdentityKey(mAliceSession.getCrypto().getOlmDevice().getDeviceCurve25519Key(), mAliceSession.getMyUserId(), MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_OLM); assertTrue (null != aliceDeviceFromBobPOV3); assertTrue (TextUtils.equals(aliceDeviceFromBobPOV3.fingerprint(), mAliceSession.getCrypto().getOlmDevice().getDeviceEd25519Key())); assertTrue(aliceDeviceFromBobPOV3.isBlocked()); mAliceSession.clear(context); mBobSession.clear(context); bobSession2.clear(context); } @Test public void test04_testEnsureOlmSessionsForUsers() throws Exception { Log.e(LOG_TAG, "test04_testEnsureOlmSessionsForUsers"); Context context = InstrumentationRegistry.getContext(); createAliceAccount(); final HashMap<String, Object> results = new HashMap<>(); mAliceSession.getCredentials().deviceId = "AliceDevice"; final CountDownLatch lock0 = new CountDownLatch(1); mAliceSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCryptoAlice", "enableCryptoAlice"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCryptoAlice")); createBobAccount(); final CountDownLatch lock2 = new CountDownLatch(1); mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCryptoBob", "enableCryptoAlice"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCryptoBob")); final CountDownLatch lock3 = new CountDownLatch(1); mBobSession.getCrypto().getDeviceList().downloadKeys(Arrays.asList(mBobSession.getMyUserId(), mAliceSession.getMyUserId()), false, new ApiCallback<MXUsersDevicesMap<MXDeviceInfo>>() { @Override public void onSuccess(MXUsersDevicesMap<MXDeviceInfo> map) { results.put("downloadKeys", map); lock3.countDown(); } @Override public void onNetworkError(Exception e) { lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3.countDown(); } }); lock3.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("downloadKeys")); final CountDownLatch lock4 = new CountDownLatch(1); mBobSession.getCrypto().ensureOlmSessionsForUsers(Arrays.asList(mBobSession.getMyUserId(), mAliceSession.getMyUserId()), new ApiCallback<MXUsersDevicesMap<MXOlmSessionResult>>() { @Override public void onSuccess(MXUsersDevicesMap<MXOlmSessionResult> info) { results.put("ensureOlmSessionsForUsers", info); lock4.countDown(); } @Override public void onNetworkError(Exception e) { lock4.countDown(); } @Override public void onMatrixError(MatrixError e) { lock4.countDown(); } @Override public void onUnexpectedError(Exception e) { lock4.countDown(); } }); lock4.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("ensureOlmSessionsForUsers")); MXUsersDevicesMap<MXOlmSessionResult> result = (MXUsersDevicesMap<MXOlmSessionResult>)results.get("ensureOlmSessionsForUsers"); assertTrue (result.getUserIds().size() == 1); MXOlmSessionResult sessionWithAliceDevice = result.getObject("AliceDevice", mAliceSession.getMyUserId()); assertTrue (null != sessionWithAliceDevice); assertTrue (null != sessionWithAliceDevice.mSessionId); assertTrue (TextUtils.equals(sessionWithAliceDevice.mDevice.deviceId, "AliceDevice")); Credentials bobCredentials = mBobSession.getCredentials(); Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(bobCredentials); IMXStore store = new MXFileStore(hs, context); MXSession bobSession2 = new MXSession(hs, new MXDataHandler(store, bobCredentials, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); final CountDownLatch lock5 = new CountDownLatch(1); MXStoreListener listener = new MXStoreListener() { @Override public void postProcess(String accountId) { } @Override public void onStoreReady(String accountId) { results.put("onStoreReady", "onStoreReady"); lock5.countDown(); } @Override public void onStoreCorrupted(String accountId, String description) { lock5.countDown(); } @Override public void onStoreOOM(String accountId, String description) { lock5.countDown(); } }; bobSession2.getDataHandler().getStore().addMXStoreListener(listener); bobSession2.getDataHandler().getStore().open(); bobSession2.getDataHandler().addListener(new MXEventListener() { @Override public void onStoreReady() { lock5.countDown(); } }); lock5.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onStoreReady")); final CountDownLatch lock5b = new CountDownLatch(2); MXEventListener eventListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock5b.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock5b.countDown(); } }; bobSession2.getDataHandler().addListener(eventListener); bobSession2.startEventStream(null); lock5b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onInitialSyncComplete")); assertTrue(results.containsKey("onCryptoSyncComplete")); final CountDownLatch lock6 = new CountDownLatch(1); bobSession2.getCrypto().ensureOlmSessionsForUsers(Arrays.asList(bobSession2.getMyUserId(), mAliceSession.getMyUserId()), new ApiCallback<MXUsersDevicesMap<MXOlmSessionResult>>() { @Override public void onSuccess(MXUsersDevicesMap<MXOlmSessionResult> info) { results.put("ensureOlmSessionsForUsers2", info); lock6.countDown(); } @Override public void onNetworkError(Exception e) { lock6.countDown(); } @Override public void onMatrixError(MatrixError e) { lock6.countDown(); } @Override public void onUnexpectedError(Exception e) { lock6.countDown(); } }); lock6.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("ensureOlmSessionsForUsers2")); MXUsersDevicesMap<MXOlmSessionResult> result2 = (MXUsersDevicesMap<MXOlmSessionResult>)results.get("ensureOlmSessionsForUsers2"); MXOlmSessionResult sessionWithAliceDevice2 = result2.getObject("AliceDevice", mAliceSession.getMyUserId()); assertTrue (null != sessionWithAliceDevice2); assertTrue (null != sessionWithAliceDevice2.mSessionId); assertTrue (TextUtils.equals(sessionWithAliceDevice2.mDevice.deviceId, "AliceDevice")); mBobSession.clear(context); mAliceSession.clear(context); bobSession2.clear(context); } @Test public void test05_testRoomIsEncrypted() throws Exception { Log.e(LOG_TAG, "test05_testRoomIsEncrypted"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); createBobAccount(); final CountDownLatch lock0 = new CountDownLatch(1); mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto", "enableCrypto"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("enableCrypto")); mRoomId = null; final CountDownLatch lock1 = new CountDownLatch(1); mBobSession.createRoom(new ApiCallback<String>() { @Override public void onSuccess(String info) { mRoomId = info; lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (null != mRoomId); Room room = mBobSession.getDataHandler().getRoom(mRoomId); assertTrue (!room.isEncrypted()); final CountDownLatch lock2 = new CountDownLatch(1); room.enableEncryptionWithAlgorithm(MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_MEGOLM, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableEncryptionWithAlgorithm", "enableEncryptionWithAlgorithm"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("enableEncryptionWithAlgorithm")); assertTrue (room.isEncrypted()); mBobSession.clear(context); } @Test public void test06_testAliceInACryptedRoom() throws Exception { Log.e(LOG_TAG, "test06_testAliceInACryptedRoom"); Context context = InstrumentationRegistry.getContext(); doE2ETestWithAliceInARoom(); final String message = "Hello myself!"; Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue (roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(1); // the IOS client echoes the message // the android client does not roomFromAlicePOV.sendEvent(buildTextEvent(message, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); mAliceSession.clear(context); } @Test public void test07_testAliceAndBobInACryptedRoom() throws Exception { Log.e(LOG_TAG, "test07_testAliceAndBobInACryptedRoom"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); final String messageFromAlice = "Hello I'm Alice!"; Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(1); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { results.put("sendEventError", e); lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEventError")); MXCryptoError error = (MXCryptoError)results.get("sendEventError"); assertTrue(TextUtils.equals(error.errcode, MXCryptoError.UNKNOWN_DEVICES_CODE)); MXUsersDevicesMap<MXDeviceInfo> unknownDevices = (MXUsersDevicesMap<MXDeviceInfo> )error.mExceptionData; List<String> deviceInfos = unknownDevices.getUserDeviceIds(mBobSession.getMyUserId()); assertTrue(1 == deviceInfos.size()); assertTrue(TextUtils.equals(deviceInfos.get(0), mBobSession.getCrypto().getMyDevice().deviceId)); final CountDownLatch lock2 = new CountDownLatch(1); mAliceSession.getCrypto().setDevicesKnown(Arrays.asList(mBobSession.getCrypto().getMyDevice()), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDevicesKnown", "setDevicesKnown"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDevicesKnown")); final CountDownLatch lock3 = new CountDownLatch(3); MXEventListener eventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { try { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { if (checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)) { results.put("onLiveEvent", "onLiveEvent"); lock3.countDown(); } } } catch (Exception e) { } } }; mBobSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEvent", event); lock3.countDown(); } }); roomFromBobPOV.addEventListener(eventListener); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock3.countDown(); } @Override public void onNetworkError(Exception e) { lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3.countDown(); } }); lock3.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(results.containsKey("onLiveEvent")); mBobSession.clear(context); } @Test public void test08_testAliceAndBobInACryptedRoom2() throws Exception { Log.e(LOG_TAG, "test08_testAliceAndBobInACryptedRoom2"); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); mReceivedMessagesFromAlice = 0; mReceivedMessagesFromBob = 0; final ArrayList<CountDownLatch> list = new ArrayList<>(); MXEventListener bobEventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE) && !TextUtils.equals(event.getSender(), mBobSession.getMyUserId())) { try { if (checkEncryptedEvent(event, mRoomId, messagesFromAlice.get(mReceivedMessagesFromAlice), mAliceSession)) { mReceivedMessagesFromAlice++; list.get(list.size()-1).countDown(); } } catch (Exception e) { } } } }; MXEventListener aliceEventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE) && !TextUtils.equals(event.getSender(), mAliceSession.getMyUserId())) { try { if (checkEncryptedEvent(event, mRoomId, messagesFromBob.get(mReceivedMessagesFromBob), mBobSession)) { mReceivedMessagesFromBob++; } list.get(list.size()-1).countDown(); } catch (Exception e) { } } } }; ApiCallback<Void> callback = new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }; roomFromBobPOV.addEventListener(bobEventListener); roomFromAlicePOV.addEventListener(aliceEventListener); list.add(new CountDownLatch(2)); final HashMap<String, Object> results = new HashMap<>(); mBobSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEvent", event); list.get(0).countDown(); } }); roomFromAlicePOV.sendEvent(buildTextEvent(messagesFromAlice.get(mReceivedMessagesFromAlice), mAliceSession), callback); list.get(list.size()-1).await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(1 == mReceivedMessagesFromAlice); list.add(new CountDownLatch(1)); roomFromBobPOV.sendEvent(buildTextEvent(messagesFromBob.get(mReceivedMessagesFromBob), mBobSession), callback); list.get(list.size()-1).await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == mReceivedMessagesFromBob); list.add(new CountDownLatch(1)); roomFromBobPOV.sendEvent(buildTextEvent(messagesFromBob.get(mReceivedMessagesFromBob), mBobSession), callback); list.get(list.size()-1).await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(2 == mReceivedMessagesFromBob); list.add(new CountDownLatch(1)); roomFromBobPOV.sendEvent(buildTextEvent(messagesFromBob.get(mReceivedMessagesFromBob), mBobSession), callback); list.get(list.size()-1).await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(3 == mReceivedMessagesFromBob); list.add(new CountDownLatch(1)); roomFromAlicePOV.sendEvent(buildTextEvent(messagesFromAlice.get(mReceivedMessagesFromAlice), mAliceSession), callback); list.get(list.size()-1).await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(2 == mReceivedMessagesFromAlice); } @Test public void test09_testAliceInACryptedRoomAfterInitialSync() throws Exception { Log.e(LOG_TAG, "test09_testAliceInACryptedRoomAfterInitialSync"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceInARoom(); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final String message = "Hello myself!"; Credentials aliceCredentials = mAliceSession.getCredentials(); mAliceSession.clear(context); Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(aliceCredentials); IMXStore store = new MXFileStore(hs, context); final CountDownLatch lock1 = new CountDownLatch(1); final MXSession aliceSession2 = new MXSession(hs, new MXDataHandler(store, aliceCredentials, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); MXStoreListener listener = new MXStoreListener() { @Override public void postProcess(String accountId) { } @Override public void onStoreReady(String accountId) { results.put("onStoreReady", "onStoreReady"); lock1.countDown(); } @Override public void onStoreCorrupted(String accountId, String description) { lock1.countDown(); } @Override public void onStoreOOM(String accountId, String description) { lock1.countDown(); } }; aliceSession2.getDataHandler().getStore().addMXStoreListener(listener); aliceSession2.getDataHandler().getStore().open(); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onStoreReady")); final CountDownLatch lock1b = new CountDownLatch(2); MXEventListener eventListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock1b.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock1b.countDown(); } }; aliceSession2.getDataHandler().addListener(eventListener); aliceSession2.startEventStream(null); lock1b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onInitialSyncComplete")); assertTrue (results.containsKey("onCryptoSyncComplete")); Room roomFromAlicePOV2 = aliceSession2.getDataHandler().getRoom(mRoomId); assertTrue(roomFromAlicePOV2.isEncrypted()); final CountDownLatch lock2 = new CountDownLatch(1); if (false) { // The android client does not echo its own message MXEventListener aliceEventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { try { if (checkEncryptedEvent(event, mRoomId, message, aliceSession2)) { lock2.countDown(); } } catch (Exception e) { } } } }; roomFromAlicePOV2.addEventListener(aliceEventListener); } // the IOS client echoes the message // the android client does not roomFromAlicePOV2.sendEvent(buildTextEvent(message, aliceSession2), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("sendEvent", "sendEvent"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEvent")); aliceSession2.clear(context); } @Test public void test10_testAliceDecryptOldMessageWithANewDeviceInACryptedRoom() throws Exception { Log.e(LOG_TAG, "test10_testAliceDecryptOldMessageWithANewDeviceInACryptedRoom"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceInARoom(); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); String message = "Hello myself!"; Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); final CountDownLatch lock1 = new CountDownLatch(1); roomFromAlicePOV.sendEvent(buildTextEvent(message, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("sendEvent", "sendEvent"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEvent")); Credentials aliceCredentials = mAliceSession.getCredentials(); Credentials aliceCredentials2 = new Credentials(); // close the session and clear the data mAliceSession.clear(context); aliceCredentials2.userId = aliceCredentials.userId; aliceCredentials2.homeServer = aliceCredentials.homeServer; aliceCredentials2.accessToken = aliceCredentials.accessToken; aliceCredentials2.refreshToken = aliceCredentials.refreshToken; aliceCredentials2.deviceId = "AliceNewDevice"; Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(aliceCredentials2); IMXStore store = new MXFileStore(hs, context); MXSession aliceSession2 = new MXSession(hs, new MXDataHandler(store, aliceCredentials2, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); aliceSession2.enableCryptoWhenStarting(); final CountDownLatch lock1b = new CountDownLatch(1); MXStoreListener listener = new MXStoreListener() { @Override public void postProcess(String accountId) { } @Override public void onStoreReady(String accountId) { results.put("onStoreReady", "onStoreReady"); lock1b.countDown(); } @Override public void onStoreCorrupted(String accountId, String description) { lock1b.countDown(); } @Override public void onStoreOOM(String accountId, String description) { lock1b.countDown(); } }; aliceSession2.getDataHandler().getStore().addMXStoreListener(listener); aliceSession2.getDataHandler().getStore().open(); lock1b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onStoreReady")); final CountDownLatch lock2 = new CountDownLatch(2); MXEventListener eventListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock2.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock2.countDown(); } }; aliceSession2.getDataHandler().addListener(eventListener); aliceSession2.startEventStream(null); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onInitialSyncComplete")); assertTrue (results.containsKey("onCryptoSyncComplete")); Room roomFromAlicePOV2 = aliceSession2.getDataHandler().getRoom(mRoomId); assertTrue (null != roomFromAlicePOV2); assertTrue(roomFromAlicePOV2.getLiveState().isEncrypted()); Event event = roomFromAlicePOV2.getDataHandler().getStore().getLatestEvent(mRoomId); assertTrue(null != event); assertTrue(event.isEncrypted()); assertTrue(null == event.getClearEvent()); assertTrue(null != event.getCryptoError()); assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)); aliceSession2.clear(context); } @Test public void test11_testAliceAndBobInACryptedRoomBackPaginationFromMemoryStore() throws Exception { Log.e(LOG_TAG, "test11_testAliceAndBobInACryptedRoomBackPaginationFromMemoryStore"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap(); doE2ETestWithAliceAndBobInARoomWithCryptedMessages(true); Credentials bobCredentials = mBobSession.getCredentials(); mBobSession.clear(context); Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(bobCredentials); IMXStore store = new MXFileStore(hs, context); final CountDownLatch lock1 = new CountDownLatch(2); MXSession bobSession2 = new MXSession(hs, new MXDataHandler(store, bobCredentials, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); MXEventListener eventListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock1.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock1.countDown(); } }; bobSession2.getDataHandler().addListener(eventListener); bobSession2.getDataHandler().getStore().open(); bobSession2.startEventStream(null); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onInitialSyncComplete")); assertTrue (results.containsKey("onCryptoSyncComplete")); assertTrue (null != bobSession2.getCrypto()); Room roomFromBobPOV = bobSession2.getDataHandler().getRoom(mRoomId); final CountDownLatch lock2 = new CountDownLatch(6); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock2.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); roomFromBobPOV.getLiveTimeLine().backPaginate(new ApiCallback<Integer>() { @Override public void onSuccess(Integer info) { results.put("backPaginate", "backPaginate"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("backPaginate")); assertTrue(receivedEvents.size() + " instead of 5",5 == receivedEvents.size()); checkEncryptedEvent(receivedEvents.get(0), mRoomId, messagesFromAlice.get(1), mAliceSession); checkEncryptedEvent(receivedEvents.get(1), mRoomId, messagesFromBob.get(2), mBobSession); checkEncryptedEvent(receivedEvents.get(2), mRoomId, messagesFromBob.get(1), mBobSession); checkEncryptedEvent(receivedEvents.get(3), mRoomId, messagesFromBob.get(0), mBobSession); checkEncryptedEvent(receivedEvents.get(4), mRoomId, messagesFromAlice.get(0), mAliceSession); bobSession2.clear(context); mAliceSession.clear(context); } @Test public void test12_testAliceAndBobInACryptedRoomBackPaginationFromHomeServer() throws Exception { Log.e(LOG_TAG, "test12_testAliceAndBobInACryptedRoomBackPaginationFromHomeServer"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap(); doE2ETestWithAliceAndBobInARoomWithCryptedMessages(true); String eventId = mBobSession.getDataHandler().getStore().getLatestEvent(mRoomId).eventId; EventTimeline timeline = new EventTimeline(mBobSession.getDataHandler(), mRoomId, eventId); final CountDownLatch lock2 = new CountDownLatch(6); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock2.countDown(); } } }; timeline.addEventTimelineListener(eventTimelineListener); timeline.backPaginate(new ApiCallback<Integer>() { @Override public void onSuccess(Integer info) { results.put("backPaginate", "backPaginate"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("backPaginate")); assertTrue(5 == receivedEvents.size()); checkEncryptedEvent(receivedEvents.get(0), mRoomId, messagesFromAlice.get(1), mAliceSession); checkEncryptedEvent(receivedEvents.get(1), mRoomId, messagesFromBob.get(2), mBobSession); checkEncryptedEvent(receivedEvents.get(2), mRoomId, messagesFromBob.get(1), mBobSession); checkEncryptedEvent(receivedEvents.get(3), mRoomId, messagesFromBob.get(0), mBobSession); checkEncryptedEvent(receivedEvents.get(4), mRoomId, messagesFromAlice.get(0), mAliceSession); mBobSession.clear(context); mAliceSession.clear(context); } @Test public void test13_testAliceAndNotCryptedBobInACryptedRoom() throws Exception { Log.e(LOG_TAG, "test13_testAliceAndNotCryptedBobInACryptedRoom"); final HashMap<String, Object> results = new HashMap(); doE2ETestWithAliceAndBobInARoom(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final String messageFromAlice = "Hello I'm Alice!"; final CountDownLatch lock1 = new CountDownLatch(1); MXEventListener bobEventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED) && !TextUtils.equals(event.getSender(), mBobSession.getMyUserId())) { results.put("bobEcho", event); lock1.countDown(); } } }; roomFromBobPOV.addEventListener(bobEventListener); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("bobEcho")); Event event = (Event)results.get("bobEcho"); assertTrue(event.isEncrypted()); assertTrue(TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)); assertTrue(null != event.getContentAsJsonObject()); assertTrue(!event.getContentAsJsonObject().has("body")); assertTrue(null != event.getCryptoError()); assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.ENCRYPTING_NOT_ENABLED_ERROR_CODE)); final CountDownLatch lock2 = new CountDownLatch(1); MXEventListener aliceEventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE) && !TextUtils.equals(event.getSender(), mAliceSession.getMyUserId())) { results.put("aliceEcho", event); lock2.countDown(); } } }; roomFromAlicePOV.addEventListener(aliceEventListener); roomFromBobPOV.sendEvent(buildTextEvent("Hello I'm Bob!", mBobSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("aliceEcho")); event = (Event)results.get("aliceEcho"); assertTrue(!event.isEncrypted()); } @Test public void test14_testCryptoDeviceBlockAndLeave() throws Exception { Log.e(LOG_TAG, "test14_testCryptoDeviceBlockAndLeave"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobAndSamInARoom(); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); mSamSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); final Room roomFromSamPOV = mSamSession.getDataHandler().getRoom(mRoomId); assertTrue (null != roomFromBobPOV); assertTrue (null != roomFromAlicePOV); assertTrue (null != roomFromSamPOV); final CountDownLatch lock0 = new CountDownLatch(3); MXEventListener aliceEventsListener0 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { results.put("alice0", "alice0"); lock0.countDown(); } } }; roomFromAlicePOV.addEventListener(aliceEventsListener0); MXEventListener samEventsListener0 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { results.put("sam0", "sam0"); lock0.countDown(); } } }; roomFromSamPOV.addEventListener(samEventsListener0); // even if the device blocked, the message must be decrypted until there is a session id rolling roomFromBobPOV.sendEvent(buildTextEvent("msg1", mBobSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("send0", "send0"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results + "", results.containsKey("send0") && results.containsKey("alice0") && results.containsKey("sam0")); roomFromAlicePOV.removeEventListener(aliceEventsListener0); roomFromSamPOV.removeEventListener(samEventsListener0); final CountDownLatch lock1 = new CountDownLatch(3); MXEventListener bobEventsListener1 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { results.put("bob1", "bob1"); lock1.countDown(); } } }; roomFromBobPOV.addEventListener(bobEventsListener1); MXEventListener samEventsListener1 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { results.put("sam1", "sam1"); lock1.countDown(); } } }; roomFromSamPOV.addEventListener(samEventsListener1); roomFromAlicePOV.sendEvent(buildTextEvent("msg1", mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("send1", "send1"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("send1") && results.containsKey("bob1") && results.containsKey("sam1")); List<MXDeviceInfo> list = mBobSession.getCrypto().getUserDevices(mAliceSession.getMyUserId()); assertTrue(null != list); assertTrue(list.size() > 0); final CountDownLatch lock1b = new CountDownLatch(1); mBobSession.getCrypto().setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED, list.get(0).deviceId, mAliceSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDeviceVerification10", "setDeviceVerification10"); lock1b.countDown(); } @Override public void onNetworkError(Exception e) { lock1b.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1b.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1b.countDown(); } }); lock1b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDeviceVerification10")); final CountDownLatch lock2 = new CountDownLatch(3); MXEventListener aliceEventsListener2 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)) { results.put("alice2", "alice2"); lock2.countDown(); } } }; roomFromAlicePOV.addEventListener(aliceEventsListener2); MXEventListener samEventsListener2 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { results.put("sam2", "sam2"); lock2.countDown(); } } }; roomFromSamPOV.addEventListener(samEventsListener2); // even if the device blocked, the message must be decrypted until there is a session id rolling roomFromBobPOV.sendEvent(buildTextEvent("msg2", mBobSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("send2", "send2"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("send2") && results.containsKey("alice2") && results.containsKey("sam2")); roomFromAlicePOV.removeEventListener(aliceEventsListener2); final CountDownLatch lock3 = new CountDownLatch(2); MXEventListener bobLeaveEventsListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) { results.put("bobleave", "bobleave"); lock3.countDown(); } } }; roomFromBobPOV.addEventListener(bobLeaveEventsListener); roomFromSamPOV.leave(new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("leave", "leave"); lock3.countDown(); } @Override public void onNetworkError(Exception e) { lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3.countDown(); } }); lock3.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("leave") && results.containsKey("bobleave")); final CountDownLatch lock4 = new CountDownLatch(2); MXEventListener aliceEventsListener3 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)) { results.put("alice3", "alice3"); lock4.countDown(); } } }; roomFromAlicePOV.addEventListener(aliceEventsListener3); // even if the device blocked, the message must be decrypted until there is a session id rolling roomFromBobPOV.sendEvent(buildTextEvent("msg3", mBobSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("send3", "send3"); lock4.countDown(); } @Override public void onNetworkError(Exception e) { lock4.countDown(); } @Override public void onMatrixError(MatrixError e) { lock4.countDown(); } @Override public void onUnexpectedError(Exception e) { lock4.countDown(); } }); lock4.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("send3") && results.containsKey("alice3")); mBobSession.clear(context); mAliceSession.clear(context); mSamSession.clear(context); } @Test public void test15_testReplayAttack() throws Exception { Log.e(LOG_TAG, "test15_testReplayAttack"); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); String messageFromAlice = "Hello I'm Alice!"; final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener bobEventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE) && !TextUtils.equals(event.getSender(), mBobSession.getMyUserId())) { results.put("bobEcho", event); event.setClearEvent(null); mBobSession.getDataHandler().decryptEvent(event, roomFromBobPOV.getLiveTimeLine().getTimelineId()); results.put("decrypted", event); lock1.countDown(); } } }; roomFromBobPOV.addEventListener(bobEventListener); mBobSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEvent", event); lock1.countDown(); } }); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(results.containsKey("bobEcho")); assertTrue(results.containsKey("decrypted")); Event decryptedEvent = (Event)results.get("decrypted"); assertTrue(null == decryptedEvent.getClearEvent()); assertTrue(TextUtils.equals(decryptedEvent.getCryptoError().errcode, MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE)); // Decrypting it with no replay attack mitigation must still work mBobSession.getDataHandler().decryptEvent(decryptedEvent, null); assertTrue(checkEncryptedEvent(decryptedEvent, mRoomId, messageFromAlice, mAliceSession)); } @Test public void test16_testRoomKeyReshare() throws Exception { Log.e(LOG_TAG, "test16_testRoomKeyReshare"); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); String messageFromAlice = "Hello I'm Alice!"; final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener bobEventListener = new MXEventListener() { @Override public void onToDeviceEvent(Event event) { if (!results.containsKey("onToDeviceEvent")) { results.put("onToDeviceEvent", event); lock1.countDown(); } } }; mBobSession.getDataHandler().addListener(bobEventListener); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock1.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(1 == receivedEvents.size()); Event event = receivedEvents.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)); // Reinject a modified version of the received room_key event from Alice. // From Bob pov, that mimics Alice resharing her keys but with an advanced outbound group session. Event toDeviceEvent = (Event)results.get("onToDeviceEvent"); String sessionId = toDeviceEvent.getContentAsJsonObject().get("session_id").getAsString(); String newSessionKey = mAliceSession.getCrypto().getOlmDevice().getSessionKey(sessionId); JsonObject content = toDeviceEvent.getClearEvent().getContentAsJsonObject(); content.add("session_key", new JsonPrimitive(newSessionKey)); mBobSession.getDataHandler().onToDeviceEvent(toDeviceEvent); // We still must be able to decrypt the event // ie, the implementation must have ignored the new room key with the advanced outbound group // session key event.setClearEvent(null); event.setKeysClaimed(null); event.setKeysProved(null); mBobSession.getDataHandler().decryptEvent(event, null); assertTrue(checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)); } @Test public void test17_testLateRoomKey() throws Exception { Log.e(LOG_TAG, "test17_testLateRoomKey"); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); String messageFromAlice = "Hello I'm Alice!"; final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener bobEventListener = new MXEventListener() { @Override public void onToDeviceEvent(Event event) { if (!results.containsKey("onToDeviceEvent")) { results.put("onToDeviceEvent", event); lock1.countDown(); } } }; mBobSession.getDataHandler().addListener(bobEventListener); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock1.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(1 == receivedEvents.size()); Event event = receivedEvents.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)); // Reinject a modified version of the received room_key event from Alice. // From Bob pov, that mimics Alice resharing her keys but with an advanced outbound group session. Event toDeviceEvent = (Event)results.get("onToDeviceEvent"); String sessionId = toDeviceEvent.getContentAsJsonObject().get("session_id").getAsString(); String senderKey = toDeviceEvent.senderKey(); // remove the session mBobSession.getCrypto().getOlmDevice().removeInboundGroupSession(sessionId, senderKey); event.setClearEvent(null); event.setKeysClaimed(null); event.setKeysProved(null); // check that the message cannot be decrypted assertTrue(!mBobSession.getDataHandler().decryptEvent(event, null)); // check the error code assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)); receivedEvents.clear(); final CountDownLatch lock2 = new CountDownLatch(1); roomFromBobPOV.addEventListener(new MXEventListener() { @Override public void onEventDecrypted(Event event) { results.put("onEventDecrypted", "onEventDecrypted"); receivedEvents.add(event); lock2.countDown(); } }); event.setClearEvent(null); event.setKeysClaimed(null); event.setKeysProved(null); // reinject the session key mBobSession.getDataHandler().onToDeviceEvent(toDeviceEvent); // the message should be decrypted later lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onEventDecrypted")); assertTrue(1 == receivedEvents.size()); assertTrue(checkEncryptedEvent(receivedEvents.get(0), mRoomId, messageFromAlice, mAliceSession)); assertTrue(null == receivedEvents.get(0).getCryptoError()); } @Test public void test18_testAliceAndBobWithNewDevice() throws Exception { Log.e(LOG_TAG, "test18_testAliceAndBobWithNewDevice"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); String bobDeviceId1 = mBobSession.getCredentials().deviceId; assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener bobEventListener = new MXEventListener() { @Override public void onToDeviceEvent(Event event) { if (!results.containsKey("onToDeviceEvent")) { results.put("onToDeviceEvent", event); lock1.countDown(); } } }; mBobSession.getDataHandler().addListener(bobEventListener); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock1.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); String aliceMessage1 = "Hello I'm Alice!"; roomFromAlicePOV.sendEvent(buildTextEvent(aliceMessage1, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(1 == receivedEvents.size()); Event event = receivedEvents.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, aliceMessage1, mAliceSession)); // logout final CountDownLatch lock2 = new CountDownLatch(1); String bobId = mBobSession.getCredentials().userId; mBobSession.logout(context, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("logout", "logout"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("logout")); final CountDownLatch lock3 = new CountDownLatch(1); MXEventListener aliceEventListener = new MXEventListener() { @Override public void onToDeviceEvent(Event event) { if (!results.containsKey("onToDeviceEvent2")) { results.put("onToDeviceEvent2", event); lock3.countDown(); } } }; mAliceSession.getDataHandler().addListener(aliceEventListener); // login with a new device id MXSession bobSession2 = CryptoTestHelper.logAccountAndSync(context, bobId, MXTESTS_BOB_PWD); String bobDeviceId2 = bobSession2.getCredentials().deviceId; assertTrue(!TextUtils.equals(bobDeviceId2, bobDeviceId1)); // before sending a message, wait that the device event is received. lock3.await(10000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent2")); SystemClock.sleep(1000); final Room roomFromBobPOV2 = bobSession2.getDataHandler().getRoom(mRoomId); assertTrue(null != roomFromBobPOV2); final ArrayList<Event> receivedEvents4 = new ArrayList<>(); final CountDownLatch lock4 = new CountDownLatch(1); EventTimeline.EventTimelineListener eventTimelineListener4 = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents4.add(event); lock4.countDown(); } else { results.put("event4", event.getType() + ""); lock4.countDown(); } } }; roomFromBobPOV2.getLiveTimeLine().addEventTimelineListener(eventTimelineListener4); String aliceMessage2 = "Hello I'm still Alice!"; roomFromAlicePOV.sendEvent(buildTextEvent(aliceMessage2, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock4.await(5000, TimeUnit.DAYS.MILLISECONDS); assertTrue("received event of type " + results.get("event4"), 1 == receivedEvents4.size()); event = receivedEvents4.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, aliceMessage2, mAliceSession)); } @Test public void test19_testAliceWithNewDeviceAndBobWithNewDevice() throws Exception { Log.e(LOG_TAG, "test19_testAliceWithNewDeviceAndBobWithNewDevice"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); String bobUserId1 = mBobSession.getMyUserId(); String aliceUserId1 = mAliceSession.getMyUserId(); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener bobEventListener = new MXEventListener() { @Override public void onToDeviceEvent(Event event) { if (!results.containsKey("onToDeviceEvent")) { results.put("onToDeviceEvent", event); lock1.countDown(); } } }; mBobSession.getDataHandler().addListener(bobEventListener); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock1.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); String aliceMessage1 = "Hello I'm Alice!"; roomFromAlicePOV.sendEvent(buildTextEvent(aliceMessage1, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(1 == receivedEvents.size()); Event event = receivedEvents.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, aliceMessage1, mAliceSession)); // logout final CountDownLatch lock2 = new CountDownLatch(1); mBobSession.logout(context, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("boblogout", "boblogout"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("boblogout")); final CountDownLatch lock3 = new CountDownLatch(1); mAliceSession.logout(context, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("alicelogout", "alicelogout"); lock3.countDown(); } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock3.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("alicelogout")); MXSession bobSession2 = CryptoTestHelper.logAccountAndSync(context, bobUserId1, MXTESTS_BOB_PWD); assertTrue(null != bobSession2); bobSession2.getCrypto().setWarnOnUnknownDevices(false); MXSession aliceSession2 = CryptoTestHelper.logAccountAndSync(context, aliceUserId1, MXTESTS_ALICE_PWD); assertTrue(null != aliceSession2); aliceSession2.getCrypto().setWarnOnUnknownDevices(false); Room roomFromBob2POV = bobSession2.getDataHandler().getRoom(mRoomId); Room roomFromAlice2POV = aliceSession2.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBob2POV.isEncrypted()); event = bobSession2.getDataHandler().getStore().getLatestEvent(mRoomId); assertTrue(null != event); assertTrue(event.isEncrypted()); assertTrue(null == event.getClearEvent()); assertTrue(null != event.getCryptoError()); assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)); final CountDownLatch lock4 = new CountDownLatch(1); final ArrayList<Event> receivedEvents2 = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener2 = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents2.add(event); lock4.countDown(); } } }; roomFromBob2POV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener2); String messageFromAlice2 = "Hello I'm still Alice!"; roomFromAlice2POV.sendEvent(buildTextEvent(messageFromAlice2, aliceSession2), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock4.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents2.size()); event = receivedEvents2.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, messageFromAlice2, aliceSession2)); } @Test public void test20_testAliceAndBlockedBob() throws Exception { Log.e(LOG_TAG, "test20_testAliceAndBlockedBob"); final HashMap<String, String> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(1); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock1.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); String aliceMessage1 = "Hello I'm Alice!"; roomFromAlicePOV.sendEvent(buildTextEvent(aliceMessage1, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents.size()); Event event = receivedEvents.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, aliceMessage1, mAliceSession)); // block the bob's device final CountDownLatch lock1b = new CountDownLatch(1); mAliceSession.getCrypto().setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_BLOCKED, mBobSession.getCredentials().deviceId, mBobSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDeviceVerification20", "setDeviceVerification20"); lock1b.countDown(); } @Override public void onNetworkError(Exception e) { lock1b.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1b.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1b.countDown(); } }); lock1b.await(); assertTrue(results.containsKey("setDeviceVerification20")); /// final CountDownLatch lock2 = new CountDownLatch(1); final ArrayList<Event> receivedEvents2 = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener2 = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)) { receivedEvents2.add(event); lock2.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener2); String aliceMessage2 = "Hello I'm still Alice!"; roomFromAlicePOV.sendEvent(buildTextEvent(aliceMessage2, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents2.size()); event = receivedEvents2.get(0); assertTrue(null == event.getClearEvent()); assertTrue(null != event.getCryptoError()); assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)); // unblock the bob's device final CountDownLatch lock2b = new CountDownLatch(1); mAliceSession.getCrypto().setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_UNVERIFIED, mBobSession.getCredentials().deviceId, mBobSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDeviceVerification40", "setDeviceVerification40"); lock2b.countDown(); } @Override public void onNetworkError(Exception e) { lock2b.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2b.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2b.countDown(); } }); lock2b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDeviceVerification40")); /// final CountDownLatch lock3 = new CountDownLatch(1); final ArrayList<Event> receivedEvents3 = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener3 = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents3.add(event); lock3.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener3); String aliceMessage3 = "Hello I'm still Alice and you can read this!"; roomFromAlicePOV.sendEvent(buildTextEvent(aliceMessage3, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock3.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents3.size()); event = receivedEvents3.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, aliceMessage3, mAliceSession)); } @Test public void test21_testDownloadKeysWithUnreachableHS() throws Exception { Log.e(LOG_TAG, "test21_testDownloadKeysWithUnreachableHS"); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(1); mAliceSession.getCrypto().getDeviceList().downloadKeys(Arrays.asList(mBobSession.getMyUserId(), "@pppppppppppp:matrix.org"), false, new ApiCallback<MXUsersDevicesMap<MXDeviceInfo>>() { @Override public void onSuccess(MXUsersDevicesMap<MXDeviceInfo> info) { results.put("downloadKeys", info); lock1.countDown(); } @Override public void onNetworkError(Exception e) { results.put("downloadKeysError", e); lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { results.put("downloadKeysError", e); lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { results.put("downloadKeysError", e); lock1.countDown(); } }); lock1.await(40000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results + "", results.containsKey("downloadKeys")); MXUsersDevicesMap<MXDeviceInfo> usersDevicesInfoMap = (MXUsersDevicesMap<MXDeviceInfo>)results.get("downloadKeys"); // We can get info only get for Bob assertTrue(usersDevicesInfoMap.getMap().size() == 1); List<String> bobDevices = usersDevicesInfoMap.getUserDeviceIds(mBobSession.getMyUserId()); assertTrue(null != bobDevices); } @Test public void test22_testDownloadKeysForUserWithNoDevice() throws Exception { Log.e(LOG_TAG, "test22_testDownloadKeysForUserWithNoDevice"); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final CountDownLatch lock1 = new CountDownLatch(1); mAliceSession.getCrypto().getDeviceList().downloadKeys(Arrays.asList(mBobSession.getMyUserId()), false, new ApiCallback<MXUsersDevicesMap<MXDeviceInfo>>() { @Override public void onSuccess(MXUsersDevicesMap<MXDeviceInfo> info) { results.put("downloadKeys", info); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("downloadKeys")); MXUsersDevicesMap<MXDeviceInfo> usersDevicesInfoMap = (MXUsersDevicesMap<MXDeviceInfo>)results.get("downloadKeys"); // MXCrypto.downloadKeys should return @[] for Bob to distinguish him from an unknown user List<String> bobDevices = usersDevicesInfoMap.getUserDeviceIds(mBobSession.getMyUserId()); assertTrue(null != bobDevices); assertTrue(0 == bobDevices.size()); // try again // it should not failed final CountDownLatch lock2 = new CountDownLatch(1); mAliceSession.getCrypto().getDeviceList().downloadKeys(Arrays.asList(mBobSession.getMyUserId()), false, new ApiCallback<MXUsersDevicesMap<MXDeviceInfo>>() { @Override public void onSuccess(MXUsersDevicesMap<MXDeviceInfo> info) { results.put("downloadKeys2", info); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("downloadKeys2")); } @Test public void test23_testFirstMessageSentWhileSessionWasPaused() throws Exception { Log.e(LOG_TAG, "test23_testFirstMessageSentWhileSessionWasPaused"); Context context = InstrumentationRegistry.getContext(); final String messageFromAlice = "Hello I'm Alice!"; final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); mBobSession.getCrypto().setWarnOnUnknownDevices(false); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); mBobSession.pauseEventStream(); // wait that the bob session is really suspended SystemClock.sleep(30000); final CountDownLatch lock0 = new CountDownLatch(1); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("sendEvent", "sendEvent"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEvent")); final CountDownLatch lock2 = new CountDownLatch(2); MXEventListener eventListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { try { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { if (checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)) { results.put("onLiveEvent", "onLiveEvent"); lock2.countDown(); } } } catch (Exception e) { } } }; roomFromBobPOV.addEventListener(eventListener); mBobSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEvent", event); lock2.countDown(); } }); mBobSession.resumeEventStream(); lock2.await(10000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(results.containsKey("onLiveEvent")); mBobSession.clear(context); mAliceSession.clear(context); } @Test public void test24_testExportImport() throws Exception { Log.e(LOG_TAG, "test24_testExportImport"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceInARoom(); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); String message = "Hello myself!"; String password = "hello"; Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); final CountDownLatch lock1 = new CountDownLatch(1); roomFromAlicePOV.sendEvent(buildTextEvent(message, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("sendEvent", "sendEvent"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEvent")); Credentials aliceCredentials = mAliceSession.getCredentials(); Credentials aliceCredentials2 = new Credentials(); final CountDownLatch lock1a = new CountDownLatch(1); mAliceSession.getCrypto().exportRoomKeys(password, new ApiCallback<byte[]>() { @Override public void onSuccess(byte[] info) { results.put("exportRoomKeys", info); lock1a.countDown(); } @Override public void onNetworkError(Exception e) { lock1a.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1a.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1a.countDown(); } }); lock1a.await(10000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("exportRoomKeys")); // close the session and clear the data mAliceSession.clear(context); aliceCredentials2.userId = aliceCredentials.userId; aliceCredentials2.homeServer = aliceCredentials.homeServer; aliceCredentials2.accessToken = aliceCredentials.accessToken; aliceCredentials2.refreshToken = aliceCredentials.refreshToken; aliceCredentials2.deviceId = "AliceNewDevice"; Uri uri = Uri.parse(CryptoTestHelper.TESTS_HOME_SERVER_URL); HomeserverConnectionConfig hs = new HomeserverConnectionConfig(uri); hs.setCredentials(aliceCredentials2); IMXStore store = new MXFileStore(hs, context); MXSession aliceSession2 = new MXSession(hs, new MXDataHandler(store, aliceCredentials2, new MXDataHandler.InvalidTokenListener() { @Override public void onTokenCorrupted() { } }), context); aliceSession2.enableCryptoWhenStarting(); final CountDownLatch lock1b = new CountDownLatch(1); MXStoreListener listener = new MXStoreListener() { @Override public void postProcess(String accountId) { } @Override public void onStoreReady(String accountId) { results.put("onStoreReady", "onStoreReady"); lock1b.countDown(); } @Override public void onStoreCorrupted(String accountId, String description) { lock1b.countDown(); } @Override public void onStoreOOM(String accountId, String description) { lock1b.countDown(); } }; aliceSession2.getDataHandler().getStore().addMXStoreListener(listener); aliceSession2.getDataHandler().getStore().open(); lock1b.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onStoreReady")); final CountDownLatch lock2 = new CountDownLatch(2); MXEventListener eventListener = new MXEventListener() { @Override public void onInitialSyncComplete(String toToken) { results.put("onInitialSyncComplete", "onInitialSyncComplete"); lock2.countDown(); } @Override public void onCryptoSyncComplete() { results.put("onCryptoSyncComplete", "onCryptoSyncComplete"); lock2.countDown(); } }; aliceSession2.getDataHandler().addListener(eventListener); aliceSession2.startEventStream(null); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue (results.containsKey("onInitialSyncComplete")); assertTrue (results.containsKey("onCryptoSyncComplete")); Room roomFromAlicePOV2 = aliceSession2.getDataHandler().getRoom(mRoomId); assertTrue (null != roomFromAlicePOV2); assertTrue(roomFromAlicePOV2.getLiveState().isEncrypted()); Event event = roomFromAlicePOV2.getDataHandler().getStore().getLatestEvent(mRoomId); assertTrue(null != event); assertTrue(event.isEncrypted()); assertTrue(null == event.getClearEvent()); assertTrue(null != event.getCryptoError()); assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)); // import the e2e keys // test with a wrong password final CountDownLatch lock3 = new CountDownLatch(1); aliceSession2.getCrypto().importRoomKeys((byte[]) results.get("exportRoomKeys"), "wrong password", new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("importRoomKeys", "importRoomKeys"); lock3.countDown(); } @Override public void onNetworkError(Exception e) { results.put("importRoomKeys_failed", "importRoomKeys_failed"); lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { results.put("importRoomKeys_failed", "importRoomKeys_failed"); lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { results.put("importRoomKeys_failed", "importRoomKeys_failed"); lock3.countDown(); } }); lock3.await(10000, TimeUnit.DAYS.MILLISECONDS); assertTrue(!results.containsKey("importRoomKeys")); assertTrue(results.containsKey("importRoomKeys_failed")); // check that the message cannot be decrypted event = roomFromAlicePOV2.getDataHandler().getStore().getLatestEvent(mRoomId); assertTrue(null != event); assertTrue(event.isEncrypted()); assertTrue(null == event.getClearEvent()); assertTrue(null != event.getCryptoError()); assertTrue(TextUtils.equals(event.getCryptoError().errcode, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)); final CountDownLatch lock4 = new CountDownLatch(1); aliceSession2.getCrypto().importRoomKeys((byte[]) results.get("exportRoomKeys"), password, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("importRoomKeys", "importRoomKeys"); lock4.countDown(); } @Override public void onNetworkError(Exception e) { lock4.countDown(); } @Override public void onMatrixError(MatrixError e) { lock4.countDown(); } @Override public void onUnexpectedError(Exception e) { lock4.countDown(); } }); lock4.await(10000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("importRoomKeys")); // check that the message CAN be decrypted event = roomFromAlicePOV2.getDataHandler().getStore().getLatestEvent(mRoomId); assertTrue(null != event); assertTrue(event.isEncrypted()); assertTrue(null != event.getClearEvent()); assertTrue(null == event.getCryptoError()); assertTrue(checkEncryptedEvent(event, mRoomId, message, mAliceSession)); aliceSession2.clear(context); } @Test // issue https://github.com/vector-im/riot-web/issues/2305 public void test25_testLeftAndJoinedBob() throws Exception { Log.e(LOG_TAG, "test25_testLeftAndJoinedBob"); Context context = InstrumentationRegistry.getContext(); final String messageFromAlice = "Hello I'm Alice!"; final String message2FromAlice = "I'm still Alice!"; final HashMap<String, Object> results = new HashMap<>(); createAliceAccount(); createBobAccount(); final CountDownLatch lock_1 = new CountDownLatch(2); mAliceSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock_1.countDown(); } @Override public void onNetworkError(Exception e) { lock_1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock_1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock_1.countDown(); } }); mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock_1.countDown(); } @Override public void onNetworkError(Exception e) { lock_1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock_1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock_1.countDown(); } }); lock_1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(null != mAliceSession.getCrypto()); assertTrue(null != mBobSession.getCrypto()); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); mBobSession.getCrypto().setWarnOnUnknownDevices(false); final CountDownLatch lock0 = new CountDownLatch(1); mAliceSession.createRoom(null, null, RoomState.DIRECTORY_VISIBILITY_PUBLIC, null, RoomState.GUEST_ACCESS_CAN_JOIN, RoomState.HISTORY_VISIBILITY_SHARED, new ApiCallback<String>() { @Override public void onSuccess(String roomId) { results.put("roomId", roomId); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("roomId")); mRoomId = (String) results.get("roomId"); Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); final CountDownLatch lock1 = new CountDownLatch(1); roomFromAlicePOV.enableEncryptionWithAlgorithm(MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_MEGOLM, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableEncryptionWithAlgorithm", "enableEncryptionWithAlgorithm"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableEncryptionWithAlgorithm")); final CountDownLatch lock2 = new CountDownLatch(1); mBobSession.joinRoom(mRoomId, new ApiCallback<String>() { @Override public void onSuccess(String info) { results.put("joinRoom", "joinRoom"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("joinRoom")); Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final CountDownLatch lock3 = new CountDownLatch(1); final ArrayList<Event> receivedEvents = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents.add(event); lock3.countDown(); } } }; roomFromBobPOV.getLiveTimeLine().addEventTimelineListener(eventTimelineListener); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock3.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents.size()); Event event = receivedEvents.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)); final CountDownLatch lock4 = new CountDownLatch(1); roomFromBobPOV.leave(new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("leave", "leave"); lock4.countDown(); } @Override public void onNetworkError(Exception e) { lock4.countDown(); } @Override public void onMatrixError(MatrixError e) { lock4.countDown(); } @Override public void onUnexpectedError(Exception e) { lock4.countDown(); } }); lock4.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("leave")); // Make Bob come back to the room with a new device Credentials bobCredentials = mBobSession.getCredentials(); mBobSession.clear(context); MXSession bobSession2 = CryptoTestHelper.logAccountAndSync(context, bobCredentials.userId, MXTESTS_BOB_PWD); assertTrue(null != bobSession2); assertTrue(bobSession2.isCryptoEnabled()); assertTrue(!TextUtils.equals(bobSession2.getCrypto().getMyDevice().deviceId, bobCredentials.deviceId)); bobSession2.getCrypto().setWarnOnUnknownDevices(false); final CountDownLatch lock5 = new CountDownLatch(1); bobSession2.joinRoom(mRoomId, new ApiCallback<String>() { @Override public void onSuccess(String info) { results.put("joinRoom2", "joinRoom2"); lock5.countDown(); } @Override public void onNetworkError(Exception e) { lock5.countDown(); } @Override public void onMatrixError(MatrixError e) { lock5.countDown(); } @Override public void onUnexpectedError(Exception e) { lock5.countDown(); } }); lock5.await(5000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("joinRoom2")); Room roomFromBobPOV2 = bobSession2.getDataHandler().getRoom(mRoomId); final CountDownLatch lock6 = new CountDownLatch(1); final ArrayList<Event> receivedEvents2 = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener2 = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents2.add(event); lock6.countDown(); } } }; roomFromBobPOV2.getLiveTimeLine().addEventTimelineListener(eventTimelineListener2); roomFromAlicePOV.sendEvent(buildTextEvent(message2FromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock6.await(5000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents2.size()); event = receivedEvents2.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, message2FromAlice, mAliceSession)); bobSession2.clear(context); mAliceSession.clear(context); } @Test // Bob, Alice and Sam are in an enctypted room // Alice sends a message // The message sending fails because of unknown devices (Bob and Sam ones) // Alice marks the Bob and Sam devices as known (UNVERIFIED) // Alice sends another message // Checks that the Bob and Sam devices receive the message and can decrypt it. // Alice black lists the unverified devices // Alice sends a message // checks that the Sam and the Bob devices receive the message but it cannot be decrypted // Alice unblack-lists the unverified devices // Alice sends a message // checks that the Sam and the Bob devices receive the message and it can be decrypted on the both devices // Alice verifies the Bob device and black lists the unverified devices in the current room. // Alice sends a message // Check that the message can be decrypted by Bob's device but not by Sam's device // Alice unblack-lists the unverified devices in the current room // Alice sends a message // Check that the message can be decrypted by the Bob's device and the Sam's device public void test26_testBlackListUnverifiedDevices() throws Exception { Log.e(LOG_TAG, "test26_testBlackListUnverifiedDevices"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); doE2ETestWithAliceAndBobAndSamInARoom(); final String messageFromAlice = "Hello I'm Alice!"; Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); Room roomFromSamPOV = mSamSession.getDataHandler().getRoom(mRoomId); assertTrue(roomFromBobPOV.isEncrypted()); assertTrue(roomFromAlicePOV.isEncrypted()); assertTrue(roomFromSamPOV.isEncrypted()); final CountDownLatch lock1 = new CountDownLatch(1); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { results.put("sendEventError", e); lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEventError")); MXCryptoError error = (MXCryptoError)results.get("sendEventError"); assertTrue(TextUtils.equals(error.errcode, MXCryptoError.UNKNOWN_DEVICES_CODE)); MXUsersDevicesMap<MXDeviceInfo> unknownDevices = (MXUsersDevicesMap<MXDeviceInfo> )error.mExceptionData; // only one bob device List<String> deviceInfos = unknownDevices.getUserDeviceIds(mBobSession.getMyUserId()); assertTrue(1 == deviceInfos.size()); assertTrue(deviceInfos.contains(mBobSession.getCrypto().getMyDevice().deviceId)); // only one Sam device deviceInfos = unknownDevices.getUserDeviceIds(mSamSession.getMyUserId()); assertTrue(1 == deviceInfos.size()); assertTrue(deviceInfos.contains(mSamSession.getCrypto().getMyDevice().deviceId)); final CountDownLatch lock2 = new CountDownLatch(1); mAliceSession.getCrypto().setDevicesKnown(Arrays.asList(mBobSession.getCrypto().getMyDevice(), mSamSession.getCrypto().getMyDevice()), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDevicesKnown", "setDevicesKnown"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDevicesKnown")); final CountDownLatch lock3 = new CountDownLatch(5); MXEventListener eventListenerBob1 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { try { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { if (checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)) { results.put("onLiveEventBob1", "onLiveEvent"); lock3.countDown(); } } } catch (Exception e) { } } }; MXEventListener eventListenerSam1 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { try { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { if (checkEncryptedEvent(event, mRoomId, messageFromAlice, mAliceSession)) { results.put("onLiveEventSam1", "onLiveEvent"); lock3.countDown(); } } } catch (Exception e) { } } }; mBobSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEventBob", event); lock3.countDown(); } }); mSamSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEventSam", event); lock3.countDown(); } }); roomFromBobPOV.addEventListener(eventListenerBob1); roomFromSamPOV.addEventListener(eventListenerSam1); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock3.countDown(); } @Override public void onNetworkError(Exception e) { lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3.countDown(); } }); lock3.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEventBob")); assertTrue(results.containsKey("onToDeviceEventSam")); assertTrue(results.containsKey("onLiveEventBob1")); assertTrue(results.containsKey("onLiveEventSam1")); roomFromBobPOV.removeEventListener(eventListenerBob1); roomFromSamPOV.removeEventListener(eventListenerSam1); // play with the device black listing final List<CountDownLatch> activeLock = new ArrayList<>(); final List<String> activeMessage = new ArrayList<>(); MXEventListener eventListenerBob2 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { try { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { if (checkEncryptedEvent(event, mRoomId, activeMessage.get(0), mAliceSession)) { results.put("eventListenerBob2", "onLiveEvent"); activeLock.get(0).countDown(); } } else if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)) { results.put("eventListenerEncyptedBob2", "onLiveEvent"); activeLock.get(0).countDown(); } } catch (Exception e) { } } }; MXEventListener eventListenerSam2 = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { try { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { if (checkEncryptedEvent(event, mRoomId, activeMessage.get(0), mAliceSession)) { results.put("eventListenerSam2", "onLiveEvent"); activeLock.get(0).countDown(); } } else if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)) { results.put("eventListenerEncyptedSam2", "onLiveEvent"); activeLock.get(0).countDown(); } } catch (Exception e) { } } }; roomFromBobPOV.addEventListener(eventListenerBob2); roomFromSamPOV.addEventListener(eventListenerSam2); final CountDownLatch lock4 = new CountDownLatch(1); mAliceSession.getCrypto().setGlobalBlacklistUnverifiedDevices(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setGlobalBlacklistUnverifiedDevicesTrue", "setGlobalBlacklistUnverifiedDevices"); lock4.countDown(); } @Override public void onNetworkError(Exception e) { lock4.countDown(); } @Override public void onMatrixError(MatrixError e) { lock4.countDown(); } @Override public void onUnexpectedError(Exception e) { lock4.countDown(); } }); lock4.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setGlobalBlacklistUnverifiedDevicesTrue")); // ensure that there is no received message results.clear(); final CountDownLatch lock5 = new CountDownLatch(3); activeLock.clear(); activeLock.add(lock5); activeMessage.clear(); activeMessage.add("message 1"); roomFromAlicePOV.sendEvent(buildTextEvent(activeMessage.get(0), mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock5.countDown(); } @Override public void onNetworkError(Exception e) { lock5.countDown(); } @Override public void onMatrixError(MatrixError e) { lock5.countDown(); } @Override public void onUnexpectedError(Exception e) { lock5.countDown(); } }); lock5.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(!results.containsKey("eventListenerBob2")); assertTrue(!results.containsKey("eventListenerSam2")); assertTrue(results.containsKey("eventListenerEncyptedBob2")); assertTrue(results.containsKey("eventListenerEncyptedSam2")); final CountDownLatch lock6 = new CountDownLatch(1); mAliceSession.getCrypto().setGlobalBlacklistUnverifiedDevices(false, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setGlobalBlacklistUnverifiedDevicesfalse", "setGlobalBlacklistUnverifiedDevices"); lock6.countDown(); } @Override public void onNetworkError(Exception e) { lock6.countDown(); } @Override public void onMatrixError(MatrixError e) { lock6.countDown(); } @Override public void onUnexpectedError(Exception e) { lock6.countDown(); } }); lock6.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setGlobalBlacklistUnverifiedDevicesfalse")); // ensure that the messages are received results.clear(); final CountDownLatch lock7 = new CountDownLatch(3); activeLock.clear(); activeLock.add(lock7); activeMessage.clear(); activeMessage.add("message 2"); roomFromAlicePOV.sendEvent(buildTextEvent(activeMessage.get(0), mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock7.countDown(); } @Override public void onNetworkError(Exception e) { lock7.countDown(); } @Override public void onMatrixError(MatrixError e) { lock7.countDown(); } @Override public void onUnexpectedError(Exception e) { lock7.countDown(); } }); lock7.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("eventListenerBob2")); assertTrue(results.containsKey("eventListenerSam2")); assertTrue(!results.containsKey("eventListenerEncyptedBob2")); assertTrue(!results.containsKey("eventListenerEncyptedSam2")); // verify the bob device final CountDownLatch lock8 = new CountDownLatch(3); mAliceSession.getCrypto().setDeviceVerification(MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED, mBobSession.getCrypto().getMyDevice().deviceId, mBobSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setDeviceVerificationBob", "setDeviceVerificationBob"); lock8.countDown(); } @Override public void onNetworkError(Exception e) { lock8.countDown(); } @Override public void onMatrixError(MatrixError e) { lock8.countDown(); } @Override public void onUnexpectedError(Exception e) { lock8.countDown(); } } ); lock8.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setDeviceVerificationBob")); final CountDownLatch lock9 = new CountDownLatch(3); mAliceSession.getCrypto().setRoomBlacklistUnverifiedDevices(roomFromAlicePOV.getRoomId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setRoomBlacklistUnverifiedDevices", "setRoomBlacklistUnverifiedDevices"); lock9.countDown(); } @Override public void onNetworkError(Exception e) { lock9.countDown(); } @Override public void onMatrixError(MatrixError e) { lock9.countDown(); } @Override public void onUnexpectedError(Exception e) { lock9.countDown(); } }); lock9.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setRoomBlacklistUnverifiedDevices")); // ensure that the messages are received results.clear(); final CountDownLatch lock10 = new CountDownLatch(3); activeLock.clear(); activeLock.add(lock10); activeMessage.clear(); activeMessage.add("message 3"); roomFromAlicePOV.sendEvent(buildTextEvent(activeMessage.get(0), mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock10.countDown(); } @Override public void onNetworkError(Exception e) { lock10.countDown(); } @Override public void onMatrixError(MatrixError e) { lock10.countDown(); } @Override public void onUnexpectedError(Exception e) { lock10.countDown(); } }); lock10.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("eventListenerBob2")); assertTrue(!results.containsKey("eventListenerSam2")); assertTrue(!results.containsKey("eventListenerEncyptedBob2")); assertTrue(results.containsKey("eventListenerEncyptedSam2")); final CountDownLatch lock11 = new CountDownLatch(3); mAliceSession.getCrypto().setRoomUnblacklistUnverifiedDevices(roomFromAlicePOV.getRoomId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("setRoomUnblacklistUnverifiedDevices", "setRoomUnblacklistUnverifiedDevices"); lock11.countDown(); } @Override public void onNetworkError(Exception e) { lock11.countDown(); } @Override public void onMatrixError(MatrixError e) { lock11.countDown(); } @Override public void onUnexpectedError(Exception e) { lock11.countDown(); } }); lock11.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("setRoomUnblacklistUnverifiedDevices")); // ensure that the messages are received results.clear(); final CountDownLatch lock12 = new CountDownLatch(3); activeLock.clear(); activeLock.add(lock12); activeMessage.clear(); activeMessage.add("message 3"); roomFromAlicePOV.sendEvent(buildTextEvent(activeMessage.get(0), mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { lock12.countDown(); } @Override public void onNetworkError(Exception e) { lock12.countDown(); } @Override public void onMatrixError(MatrixError e) { lock12.countDown(); } @Override public void onUnexpectedError(Exception e) { lock12.countDown(); } }); lock12.await(3000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("eventListenerBob2")); assertTrue(results.containsKey("eventListenerSam2")); assertTrue(!results.containsKey("eventListenerEncyptedBob2")); assertTrue(!results.containsKey("eventListenerEncyptedSam2")); mBobSession.clear(context); } @Test // Test for https://github.com/matrix-org/matrix-js-sdk/pull/359 // - Alice sends a message to Bob to a non encrypted room // - Bob logs in with a new device // - Alice turns the crypto ON in the room // - Alice sends a message // -> Bob must be able to decrypt this message public void test27_testEnableEncryptionAfterNonCryptedMessages() throws Exception { Log.e(LOG_TAG, "test27_testEnableEncryptionAfterNonCryptedMessages"); Context context = InstrumentationRegistry.getContext(); final HashMap<String, Object> results = new HashMap<>(); final String messageFromAlice = "Hello I'm Alice!"; final String message2FromAlice = "I'm still Alice!"; createAliceAccount(); createBobAccount(); final CountDownLatch lock00b = new CountDownLatch(2); mAliceSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto1", "enableCrypto1"); lock00b.countDown(); } @Override public void onNetworkError(Exception e) { lock00b.countDown(); } @Override public void onMatrixError(MatrixError e) { lock00b.countDown(); } @Override public void onUnexpectedError(Exception e) { lock00b.countDown(); } }); mBobSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto2", "enableCrypto2"); lock00b.countDown(); } @Override public void onNetworkError(Exception e) { lock00b.countDown(); } @Override public void onMatrixError(MatrixError e) { lock00b.countDown(); } @Override public void onUnexpectedError(Exception e) { lock00b.countDown(); } }) ; lock00b.await(5000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCrypto2")); assertTrue(results.containsKey("enableCrypto1")); mAliceSession.getCrypto().setWarnOnUnknownDevices(false); mBobSession.getCrypto().setWarnOnUnknownDevices(false); final CountDownLatch lock0 = new CountDownLatch(1); mAliceSession.createRoom(null, null, RoomState.DIRECTORY_VISIBILITY_PUBLIC, null, RoomState.GUEST_ACCESS_CAN_JOIN, RoomState.HISTORY_VISIBILITY_SHARED, new ApiCallback<String>() { @Override public void onSuccess(String roomId) { results.put("roomId", roomId); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("roomId")); mRoomId = (String) results.get("roomId"); final CountDownLatch lock1 = new CountDownLatch(1); mBobSession.joinRoom(mRoomId, new ApiCallback<String>() { @Override public void onSuccess(String info) { results.put("joinRoom", "joinRoom"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("joinRoom")); Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); final CountDownLatch lock2 = new CountDownLatch(1); roomFromAlicePOV.sendEvent(buildTextEvent(messageFromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("sendEvent1", "sendEvent1"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("sendEvent1")); // Make Bob come back to the room with a new device Credentials bobCredentials = mBobSession.getCredentials(); mBobSession.clear(context); MXSession bobSession2 = CryptoTestHelper.logAccountAndSync(context, bobCredentials.userId, MXTESTS_BOB_PWD); assertTrue(null != bobSession2); assertTrue(bobSession2.isCryptoEnabled()); assertTrue(!TextUtils.equals(bobSession2.getCrypto().getMyDevice().deviceId, bobCredentials.deviceId)); bobSession2.getCrypto().setWarnOnUnknownDevices(false); final CountDownLatch lock3 = new CountDownLatch(1); roomFromAlicePOV.enableEncryptionWithAlgorithm(MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_MEGOLM, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableEncryptionWithAlgorithm", "enableEncryptionWithAlgorithm"); lock3.countDown(); } @Override public void onNetworkError(Exception e) { lock3.countDown(); } @Override public void onMatrixError(MatrixError e) { lock3.countDown(); } @Override public void onUnexpectedError(Exception e) { lock3.countDown(); } }); lock3.await(5000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableEncryptionWithAlgorithm")); Room roomFromBobPOV2 = bobSession2.getDataHandler().getRoom(mRoomId); final CountDownLatch lock4 = new CountDownLatch(1); final ArrayList<Event> receivedEvents2 = new ArrayList<>(); EventTimeline.EventTimelineListener eventTimelineListener2 = new EventTimeline.EventTimelineListener() { public void onEvent(Event event, EventTimeline.Direction direction, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)) { receivedEvents2.add(event); lock4.countDown(); } else if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)) { lock4.countDown(); } } }; roomFromBobPOV2.getLiveTimeLine().addEventTimelineListener(eventTimelineListener2); roomFromAlicePOV.sendEvent(buildTextEvent(message2FromAlice, mAliceSession), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }); lock4.await(5000, TimeUnit.DAYS.MILLISECONDS); assertTrue(1 == receivedEvents2.size()); Event event = receivedEvents2.get(0); assertTrue(checkEncryptedEvent(event, mRoomId, message2FromAlice, mAliceSession)); bobSession2.clear(context); mAliceSession.clear(context); } //============================================================================================================== // private test routines //============================================================================================================== private MXSession mBobSession; private MXSession mAliceSession; private MXSession mSamSession; private String mRoomId; private int mMessagesCount; private int mReceivedMessagesFromAlice; private int mReceivedMessagesFromBob; public void createBobAccount() throws Exception { Context context = InstrumentationRegistry.getContext(); mBobSession = null; mBobSession = CryptoTestHelper.createAccountAndSync(context, MXTESTS_BOB + System.currentTimeMillis() + UUID.randomUUID().toString(), MXTESTS_BOB_PWD, true); assertTrue (null != mBobSession); } public void createAliceAccount() throws Exception { Context context = InstrumentationRegistry.getContext(); mAliceSession = null; mAliceSession = CryptoTestHelper.createAccountAndSync(context, MXTESTS_ALICE + System.currentTimeMillis() + UUID.randomUUID().toString(), MXTESTS_ALICE_PWD, true); assertTrue (null != mAliceSession); } public void createSamAccount() throws Exception { Context context = InstrumentationRegistry.getContext(); mSamSession = null; mSamSession = CryptoTestHelper.createAccountAndSync(context, MXTESTS_SAM + System.currentTimeMillis() + UUID.randomUUID().toString(), MXTESTS_SAM_PWD, true); assertTrue (null != mSamSession); } private void doE2ETestWithAliceInARoom() throws Exception { final HashMap<String, Object> results = new HashMap<>(); createAliceAccount(); final CountDownLatch lock0 = new CountDownLatch(1); mAliceSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableCrypto", "enableCrypto"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableCrypto")); mRoomId = null; final CountDownLatch lock1 = new CountDownLatch(1); mAliceSession.createRoom(new ApiCallback<String>() { @Override public void onSuccess(String roomId) { mRoomId = roomId; lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(null != mRoomId); Room room = mAliceSession.getDataHandler().getRoom(mRoomId); final CountDownLatch lock2 = new CountDownLatch(1); room.enableEncryptionWithAlgorithm(MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_MEGOLM, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { results.put("enableEncryptionWithAlgorithm", "enableEncryptionWithAlgorithm"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("enableEncryptionWithAlgorithm")); } private void doE2ETestWithAliceAndBobInARoom(boolean cryptedBob) throws Exception { final HashMap<String, String> statuses = new HashMap<>(); doE2ETestWithAliceInARoom(); Room room = mAliceSession.getDataHandler().getRoom(mRoomId); createBobAccount(); final CountDownLatch lock0 = new CountDownLatch(1); mBobSession.enableCrypto(cryptedBob, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { statuses.put("enableCrypto", "enableCrypto"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener bobEventListener = new MXEventListener() { @Override public void onNewRoom(String roomId) { if (TextUtils.equals(roomId, mRoomId)) { if (!statuses.containsKey("onNewRoom")) { statuses.put("onNewRoom", "onNewRoom"); lock1.countDown(); } } } }; mBobSession.getDataHandler().addListener(bobEventListener); room.invite(mBobSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { statuses.put("invite", "invite"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")); mBobSession.getDataHandler().removeListener(bobEventListener); final CountDownLatch lock2 = new CountDownLatch(2); mBobSession.joinRoom(mRoomId, new ApiCallback<String>() { @Override public void onSuccess(String info) { statuses.put("joinRoom", "joinRoom"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { statuses.put("onNetworkError", e.getMessage()); lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { statuses.put("onMatrixError", e.getMessage()); lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { statuses.put("onUnexpectedError", e.getMessage()); lock2.countDown(); } }); room.addEventListener(new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) { JsonObject contentToConsider = event.getContentAsJsonObject(); RoomMember member = JsonUtils.toRoomMember(contentToConsider); if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) { statuses.put("AliceJoin", "AliceJoin"); lock2.countDown(); } } } }); lock2.await(2000, TimeUnit.DAYS.MILLISECONDS); assertTrue(statuses + "", statuses.containsKey("joinRoom")); assertTrue(statuses + "", statuses.containsKey("AliceJoin")); mBobSession.getDataHandler().removeListener(bobEventListener); } private void doE2ETestWithAliceAndBobAndSamInARoom() throws Exception { final HashMap<String, String> statuses = new HashMap<>(); doE2ETestWithAliceAndBobInARoom(true); Room room = mAliceSession.getDataHandler().getRoom(mRoomId); createSamAccount(); final CountDownLatch lock0 = new CountDownLatch(1); mSamSession.enableCrypto(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { statuses.put("enableCrypto", "enableCrypto"); lock0.countDown(); } @Override public void onNetworkError(Exception e) { lock0.countDown(); } @Override public void onMatrixError(MatrixError e) { lock0.countDown(); } @Override public void onUnexpectedError(Exception e) { lock0.countDown(); } }); lock0.await(1000, TimeUnit.DAYS.MILLISECONDS); final CountDownLatch lock1 = new CountDownLatch(2); MXEventListener samEventListener = new MXEventListener() { @Override public void onNewRoom(String roomId) { if (TextUtils.equals(roomId, mRoomId)) { if (!statuses.containsKey("onNewRoom")) { statuses.put("onNewRoom", "onNewRoom"); lock1.countDown(); } } } }; mSamSession.getDataHandler().addListener(samEventListener); room.invite(mSamSession.getMyUserId(), new ApiCallback<Void>() { @Override public void onSuccess(Void info) { statuses.put("invite", "invite"); lock1.countDown(); } @Override public void onNetworkError(Exception e) { lock1.countDown(); } @Override public void onMatrixError(MatrixError e) { lock1.countDown(); } @Override public void onUnexpectedError(Exception e) { lock1.countDown(); } }); lock1.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")); mSamSession.getDataHandler().removeListener(samEventListener); final CountDownLatch lock2 = new CountDownLatch(1); mSamSession.joinRoom(mRoomId, new ApiCallback<String>() { @Override public void onSuccess(String info) { statuses.put("joinRoom", "joinRoom"); lock2.countDown(); } @Override public void onNetworkError(Exception e) { lock2.countDown(); } @Override public void onMatrixError(MatrixError e) { lock2.countDown(); } @Override public void onUnexpectedError(Exception e) { lock2.countDown(); } }); lock2.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(statuses.containsKey("joinRoom")); // wait the initial sync SystemClock.sleep(1000); mSamSession.getDataHandler().removeListener(samEventListener); } private Event buildTextEvent(String text, MXSession session) { Message message = new Message(); message.msgtype = Message.MSGTYPE_TEXT; message.body = text; return new Event(message, session.getCredentials().userId, mRoomId); } private void doE2ETestWithAliceAndBobInARoomWithCryptedMessages(boolean cryptedBob) throws Exception { doE2ETestWithAliceAndBobInARoom(cryptedBob); if (null != mBobSession.getCrypto()) { mBobSession.getCrypto().setWarnOnUnknownDevices(false); } if (null != mAliceSession.getCrypto()) { mAliceSession.getCrypto().setWarnOnUnknownDevices(false); } final Room roomFromBobPOV = mBobSession.getDataHandler().getRoom(mRoomId); final Room roomFromAlicePOV = mAliceSession.getDataHandler().getRoom(mRoomId); mMessagesCount = 0; final ArrayList<CountDownLatch> list = new ArrayList<>(); MXEventListener bobEventsListener = new MXEventListener() { @Override public void onLiveEvent(Event event, RoomState roomState) { if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE) && !TextUtils.equals(event.getSender(), mBobSession.getMyUserId())) { mMessagesCount++; list.get(0).countDown(); } } }; roomFromBobPOV.addEventListener(bobEventsListener); ApiCallback<Void> callback = new ApiCallback<Void>() { @Override public void onSuccess(Void info) { list.get(0).countDown(); } @Override public void onNetworkError(Exception e) { } @Override public void onMatrixError(MatrixError e) { } @Override public void onUnexpectedError(Exception e) { } }; final HashMap<String, Object> results = new HashMap<>(); CountDownLatch lock = new CountDownLatch(3); list.clear(); list.add(lock); mBobSession.getDataHandler().addListener(new MXEventListener() { @Override public void onToDeviceEvent(Event event) { results.put("onToDeviceEvent", event); list.get(0).countDown(); } }); roomFromAlicePOV.sendEvent(buildTextEvent(messagesFromAlice.get(0), mAliceSession), callback); lock.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(results.containsKey("onToDeviceEvent")); assertTrue(mMessagesCount == 1); lock = new CountDownLatch(1); list.clear(); list.add(lock); roomFromBobPOV.sendEvent(buildTextEvent(messagesFromBob.get(0), mBobSession), callback); // android does not echo the messages sent from itself mMessagesCount++; lock.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(mMessagesCount == 2); lock = new CountDownLatch(1); list.clear(); list.add(lock); roomFromBobPOV.sendEvent(buildTextEvent(messagesFromBob.get(1), mBobSession), callback); // android does not echo the messages sent from itself mMessagesCount++; lock.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(mMessagesCount == 3); lock = new CountDownLatch(1); list.clear(); list.add(lock); roomFromBobPOV.sendEvent(buildTextEvent(messagesFromBob.get(2), mBobSession), callback); // android does not echo the messages sent from itself mMessagesCount++; lock.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(mMessagesCount == 4); lock = new CountDownLatch(2); list.clear(); list.add(lock); roomFromAlicePOV.sendEvent(buildTextEvent(messagesFromAlice.get(1), mAliceSession), callback); lock.await(1000, TimeUnit.DAYS.MILLISECONDS); assertTrue(mMessagesCount == 5); } private boolean checkEncryptedEvent(Event event, String roomId, String clearMessage, MXSession senderSession) throws Exception { assertTrue(TextUtils.equals(event.getWireType(), Event.EVENT_TYPE_MESSAGE_ENCRYPTED)); assertTrue(null != event.getWireContent()); JsonObject eventWireContent = event.getWireContent().getAsJsonObject(); assertTrue(null == eventWireContent.get("body")); assertTrue(TextUtils.equals(eventWireContent.get("algorithm").getAsString(), MXCryptoAlgorithms.MXCRYPTO_ALGORITHM_MEGOLM)); assertTrue(null != eventWireContent.get("ciphertext")); assertTrue(null != eventWireContent.get("session_id")); assertTrue(null != eventWireContent.get("sender_key")); assertTrue(TextUtils.equals(eventWireContent.get("device_id").getAsString(), senderSession.getCredentials().deviceId)); assertTrue(event.eventId != null); assertTrue(TextUtils.equals(event.roomId, roomId)); assertTrue(TextUtils.equals(event.getType(), Event.EVENT_TYPE_MESSAGE)); assertTrue(event.getAge() < 10000); JsonObject eventContent = event.getContentAsJsonObject(); assertTrue(TextUtils.equals(eventContent.get("body").getAsString(), clearMessage)); assertTrue(TextUtils.equals(event.sender, senderSession.getMyUserId())); return true; } }