From 859c75df983b085825cab9c1e6f00032abb06813 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 10 Jan 2020 18:29:23 +0100 Subject: [PATCH] Initial commit --- .gitignore | 2 + .../vector/matrix/android/InstrumentedTest.kt | 4 + .../matrix/android/common/CommonTestHelper.kt | 37 +- .../matrix/android/common/CryptoTestHelper.kt | 76 ++- .../crypto/crosssigning/XSigningTest.kt | 214 ++++++++ .../internal/crypto/verification/SASTest.kt | 62 ++- .../src/main/AndroidManifest.xml | 5 +- .../api/session/crypto/CryptoService.kt | 3 + .../crosssigning/CrossSigningService.kt | 47 ++ .../crypto/crosssigning/CrossSigningState.kt | 38 ++ .../crypto/crosssigning/MXCrossSigningInfo.kt | 47 ++ .../session/crypto/crosssigning/MXKeyInfo.kt | 80 +++ .../android/internal/crypto/CryptoModule.kt | 6 + .../internal/crypto/DefaultCryptoService.kt | 7 +- .../internal/crypto/DeviceListManager.kt | 25 +- .../crypto/IncomingRoomKeyRequestManager.kt | 2 +- .../android/internal/crypto/MXOlmDevice.kt | 1 + .../actions/SetDeviceVerificationAction.kt | 2 +- .../algorithms/megolm/MXMegolmDecryption.kt | 2 +- .../android/internal/crypto/api/CryptoApi.kt | 31 ++ .../DefaultCrossSigningService.kt | 511 ++++++++++++++++++ .../internal/crypto/keysbackup/KeysBackup.kt | 2 +- .../internal/crypto/model/MXDeviceInfo.kt | 8 + .../internal/crypto/model/MXKeysObject.kt | 29 + .../crypto/model/rest/CrossSigningKeyInfo.kt | 125 +++++ .../crypto/model/rest/DeleteDeviceParams.kt | 2 +- .../internal/crypto/model/rest/DeviceKeys.kt | 10 +- .../crypto/model/rest/KeysQueryResponse.kt | 21 +- .../model/rest/SignatureUploadResponse.kt | 50 ++ .../model/rest/UploadSignatureQueryBuilder.kt | 58 ++ .../crypto/model/rest/UploadSignaturesBody.kt | 31 ++ .../model/rest/UploadSigningKeysBody.kt | 35 ++ ...eleteDeviceAuth.kt => UserPasswordAuth.kt} | 6 +- .../crypto/model/rest/XSigningKeys.kt | 20 + .../internal/crypto/store/IMXCryptoStore.kt | 29 +- .../internal/crypto/store/PrivateKeysInfo.kt | 7 + .../crypto/store/db/RealmCryptoStore.kt | 152 +++++- .../store/db/RealmCryptoStoreMigration.kt | 37 +- .../crypto/store/db/RealmCryptoStoreModule.kt | 4 +- .../store/db/model/CrossSigningInfoEntity.kt | 51 ++ .../store/db/model/CryptoMetadataEntity.kt | 9 +- .../crypto/store/db/model/KeyInfoEntity.kt | 45 ++ .../crypto/store/db/model/UserEntity.kt | 8 +- .../db/query/CrossSigningInfoEntityQueries.kt | 37 ++ .../tasks/DeleteDeviceWithUserPasswordTask.kt | 4 +- .../crypto/tasks/UploadSignaturesTask.kt | 44 ++ .../crypto/tasks/UploadSigningKeysTask.kt | 92 ++++ ...faultIncomingSASVerificationTransaction.kt | 2 +- .../DefaultSasVerificationService.kt | 1 + .../session/room/send/EncryptEventWorker.kt | 5 + .../main/res/xml/network_security_config.xml | 16 + .../riotx/features/debug/DebugMenuActivity.kt | 76 +++ .../debug/res/layout/activity_debug_menu.xml | 7 + .../vector/riotx/core/di/ScreenComponent.kt | 3 + .../home/room/detail/RoomDetailViewModel.kt | 5 +- 55 files changed, 2134 insertions(+), 99 deletions(-) create mode 100644 matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningState.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXCrossSigningInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXKeyInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXKeysObject.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/CrossSigningKeyInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignaturesBody.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSigningKeysBody.kt rename matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/{DeleteDeviceAuth.kt => UserPasswordAuth.kt} (86%) create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/XSigningKeys.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CrossSigningInfoEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CrossSigningInfoEntityQueries.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSignaturesTask.kt create mode 100644 matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt create mode 100644 matrix-sdk-android/src/main/res/xml/network_security_config.xml diff --git a/.gitignore b/.gitignore index 421ac09561..9f442cce2b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ ktlint .idea/copyright/New_vector.xml .idea/copyright/profiles_settings.xml + +.idea/copyright/New_Vector_Ltd.xml diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt index 99fe7d29b4..7c29ec520c 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/InstrumentedTest.kt @@ -19,8 +19,12 @@ package im.vector.matrix.android import android.content.Context import androidx.test.core.app.ApplicationProvider import java.io.File +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import org.junit.Rule + interface InstrumentedTest { + fun context(): Context { return ApplicationProvider.getApplicationContext() } diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt index b16c865765..104cf94280 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CommonTestHelper.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.common import android.content.Context import android.net.Uri +import androidx.lifecycle.Observer import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixConfiguration @@ -31,6 +32,11 @@ import im.vector.matrix.android.api.session.room.Room import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings +import im.vector.matrix.android.api.session.sync.SyncState +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.junit.Assert.* import java.util.* import java.util.concurrent.CountDownLatch @@ -73,23 +79,26 @@ class CommonTestHelper(context: Context) { * @param session the session to sync */ fun syncSession(session: Session) { - // val lock = CountDownLatch(1) - - // val observer = androidx.lifecycle.Observer { syncState -> - // if (syncState is SyncState.Idle) { - // lock.countDown() - // } - // } - - // TODO observe? - // while (session.syncState().value !is SyncState.Idle) { - // sleep(100) - // } + val lock = CountDownLatch(1) session.open() session.startSync(true) - // await(lock) - // session.syncState().removeObserver(observer) + + val syncLiveData = runBlocking(Dispatchers.Main) { + session.getSyncStateLive() + } + val syncObserver = object : Observer { + override fun onChanged(t: SyncState?) { + if (session.hasAlreadySynced()) { + lock.countDown() + syncLiveData.removeObserver(this) + } + } + + } + GlobalScope.launch(Dispatchers.Main) { syncLiveData.observeForever(syncObserver) } + + await(lock) } /** diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt index df45249265..afc2d3a749 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/common/CryptoTestHelper.kt @@ -17,11 +17,15 @@ package im.vector.matrix.android.common import android.os.SystemClock +import androidx.lifecycle.Observer import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.toContent +import im.vector.matrix.android.api.session.room.model.Membership +import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams +import im.vector.matrix.android.api.session.room.roomSummaryQueryParams import im.vector.matrix.android.api.session.room.timeline.Timeline import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineSettings @@ -29,6 +33,10 @@ import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM import im.vector.matrix.android.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupAuthData import im.vector.matrix.android.internal.crypto.keysbackup.model.MegolmBackupCreationInfo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import org.junit.Assert.* import java.util.* import java.util.concurrent.CountDownLatch @@ -78,26 +86,32 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { val aliceSession = cryptoTestData.firstSession val aliceRoomId = cryptoTestData.roomId - val room = aliceSession.getRoom(aliceRoomId)!! + val aliceRoom = aliceSession.getRoom(aliceRoomId)!! val bobSession = mTestHelper.createAccount(TestConstants.USER_BOB, defaultSessionParams) val lock1 = CountDownLatch(2) -// val bobEventListener = object : MXEventListener() { -// override fun onNewRoom(roomId: String) { -// if (TextUtils.equals(roomId, aliceRoomId)) { -// if (!statuses.containsKey("onNewRoom")) { -// statuses["onNewRoom"] = "onNewRoom" -// lock1.countDown() -// } -// } -// } -// } -// -// bobSession.dataHandler.addListener(bobEventListener) - room.invite(bobSession.myUserId, callback = object : TestMatrixCallback(lock1) { + val bobRoomSummariesLive = runBlocking(Dispatchers.Main) { + bobSession.getRoomSummariesLive(roomSummaryQueryParams { }) + } + + val newRoomObserver = object : Observer> { + override fun onChanged(t: List?) { + if (t?.isNotEmpty() == true) { + statuses["onNewRoom"] = "onNewRoom" + lock1.countDown() + bobRoomSummariesLive.removeObserver(this) + } + } + } + + GlobalScope.launch(Dispatchers.Main) { + bobRoomSummariesLive.observeForever(newRoomObserver) + } + + aliceRoom.invite(bobSession.myUserId, callback = object : TestMatrixCallback(lock1) { override fun onSuccess(data: Unit) { statuses["invite"] = "invite" super.onSuccess(data) @@ -108,25 +122,27 @@ class CryptoTestHelper(val mTestHelper: CommonTestHelper) { assertTrue(statuses.containsKey("invite") && statuses.containsKey("onNewRoom")) -// bobSession.dataHandler.removeListener(bobEventListener) - val lock2 = CountDownLatch(2) - bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2)) + val roomJoinedObserver = object : Observer> { + override fun onChanged(t: List?) { + if (bobSession.getRoom(aliceRoomId) + ?.getRoomMember(aliceSession.myUserId) + ?.membership == Membership.JOIN) { + statuses["AliceJoin"] = "AliceJoin" + lock2.countDown() + bobRoomSummariesLive.removeObserver(this) + } + } + } -// room.addEventListener(object : MXEventListener() { -// override fun onLiveEvent(event: Event, roomState: RoomState) { -// if (TextUtils.equals(event.getType(), Event.EVENT_TYPE_STATE_ROOM_MEMBER)) { -// val contentToConsider = event.contentAsJsonObject -// val member = JsonUtils.toRoomMember(contentToConsider) -// -// if (TextUtils.equals(member.membership, RoomMember.MEMBERSHIP_JOIN)) { -// statuses["AliceJoin"] = "AliceJoin" -// lock2.countDown() -// } -// } -// } -// }) + GlobalScope.launch(Dispatchers.Main) { + bobRoomSummariesLive.observeForever(roomJoinedObserver) + } + + + + bobSession.joinRoom(aliceRoomId, callback = TestMatrixCallback(lock2)) mTestHelper.await(lock2) diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt new file mode 100644 index 0000000000..0c6377b8f2 --- /dev/null +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt @@ -0,0 +1,214 @@ +package im.vector.matrix.android.internal.crypto.crosssigning + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import im.vector.matrix.android.InstrumentedTest +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.common.* +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap +import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import org.junit.Assert +import org.junit.Assert.assertNotNull +import org.junit.Assert.fail +import org.junit.FixMethodOrder +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.MethodSorters +import java.util.concurrent.CountDownLatch + + +@RunWith(AndroidJUnit4::class) +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +class XSigningTest : InstrumentedTest { + + private val mTestHelper = CommonTestHelper(context()) + private val mCryptoTestHelper = CryptoTestHelper(mTestHelper) + + + @Test + fun test_InitializeAndStoreKeys() { + val aliceSession = mTestHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) + + + val aliceLatch = CountDownLatch(1) + aliceSession.getCrossSigningService() + .initializeCrossSigning(UserPasswordAuth( + user = aliceSession.myUserId, + password = TestConstants.PASSWORD + ), TestMatrixCallback(aliceLatch)) + + mTestHelper.await(aliceLatch) + + val myCrossSigningKeys = aliceSession.getCrossSigningService().getMyCrossSigningKeys() + val masterPubKey = myCrossSigningKeys?.masterKey() + Assert.assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey) + val selfSigningKey = myCrossSigningKeys?.selfSigningKey() + Assert.assertNotNull("SelfSigned key should be stored", selfSigningKey?.unpaddedBase64PublicKey) + val userKey = myCrossSigningKeys?.userKey() + Assert.assertNotNull("User key should be stored", userKey?.unpaddedBase64PublicKey) + + + Assert.assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted == true) + } + + @Test + fun test_CrossSigningCheckBobSeesTheKeys() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceAuthParams = UserPasswordAuth( + user = aliceSession.myUserId, + password = TestConstants.PASSWORD + ) + val bobAuthParams = UserPasswordAuth( + user = bobSession!!.myUserId, + password = TestConstants.PASSWORD + ) + + val aliceLatch = CountDownLatch(1) + val bobLatch = CountDownLatch(1) + + aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(aliceLatch)) + bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(bobLatch)) + + mTestHelper.await(aliceLatch) + mTestHelper.await(bobLatch) + + //Check that alice can see bob keys + val downloadLatch = CountDownLatch(1) + aliceSession.downloadKeys(listOf(bobSession.myUserId), true, TestMatrixCallback(downloadLatch)) + mTestHelper.await(downloadLatch) + + val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobSession.myUserId) + Assert.assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV?.masterKey()) + Assert.assertNull("Alice should not see bob User key", bobKeysFromAlicePOV?.userKey()) + Assert.assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV?.selfSigningKey()) + + Assert.assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV?.masterKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey) + Assert.assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV?.selfSigningKey()?.unpaddedBase64PublicKey, bobSession.getCrossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey) + + Assert.assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted == false) + + } + + @Test + fun test_CrossSigningTestAliceTrustBobNewDevice() { + val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom() + + val aliceSession = cryptoTestData.firstSession + val bobSession = cryptoTestData.secondSession + + val aliceAuthParams = UserPasswordAuth( + user = aliceSession.myUserId, + password = TestConstants.PASSWORD + ) + val bobAuthParams = UserPasswordAuth( + user = bobSession!!.myUserId, + password = TestConstants.PASSWORD + ) + + val aliceLatch = CountDownLatch(1) + val bobLatch = CountDownLatch(1) + + aliceSession.getCrossSigningService().initializeCrossSigning(aliceAuthParams, TestMatrixCallback(aliceLatch)) + bobSession.getCrossSigningService().initializeCrossSigning(bobAuthParams, TestMatrixCallback(bobLatch)) + + mTestHelper.await(aliceLatch) + mTestHelper.await(bobLatch) + + //Check that alice can see bob keys + val downloadLatch = CountDownLatch(1) + val bobUserId = bobSession.myUserId + aliceSession.downloadKeys(listOf(bobUserId), true, TestMatrixCallback(downloadLatch)) + mTestHelper.await(downloadLatch) + + val bobKeysFromAlicePOV = aliceSession.getCrossSigningService().getUserCrossSigningKeys(bobUserId) + Assert.assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted == false) + + + val trustLatch = CountDownLatch(1) + aliceSession.getCrossSigningService().trustUser(bobUserId, object : MatrixCallback { + override fun onSuccess(data: SignatureUploadResponse) { + trustLatch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail("Failed to trust bob") + } + }) + mTestHelper.await(trustLatch) + + // Now bobs logs in on a new device and verifies it + // We will want to test that in alice POV, this new device would be trusted by cross signing + + val bobSession2 = mTestHelper.logIntoAccount(bobUserId, SessionTestParams(true)) + val bobSecondDeviceId = bobSession2.sessionParams.credentials.deviceId + + + // Check that bob first session sees the new login + val bobKeysLatch = CountDownLatch(1) + bobSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback> { + override fun onFailure(failure: Throwable) { + fail("Failed to get device") + } + + override fun onSuccess(data: MXUsersDevicesMap) { + if(data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) { + fail("Bob should see the new device") + } + bobKeysLatch.countDown() + } + }) + mTestHelper.await(bobKeysLatch) + + val bobSecondDevicePOVFirstDevice = bobSession.getDeviceInfo(bobUserId, bobSecondDeviceId) + assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) + + // Manually mark it as trusted from first session + val bobSignLatch = CountDownLatch(1) + bobSession.getCrossSigningService().signDevice(bobSecondDeviceId!!, object : MatrixCallback { + override fun onSuccess(data: SignatureUploadResponse) { + bobSignLatch.countDown() + } + + override fun onFailure(failure: Throwable) { + fail("Failed to trust bob ${failure.localizedMessage}") + } + }) + mTestHelper.await(bobSignLatch) + + // Now alice should cross trust bob's second device + val aliceKeysLatch = CountDownLatch(1) + aliceSession.downloadKeys(listOf(bobUserId), true, object : MatrixCallback> { + override fun onFailure(failure: Throwable) { + fail("Failed to get device") + } + + override fun onSuccess(data: MXUsersDevicesMap) { + //check that the device is seen + if(data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { + fail("Alice should see the new device") + } + aliceKeysLatch.countDown() + } + }) + mTestHelper.await(aliceKeysLatch) + + val secondDevicetrustLatch = CountDownLatch(1) + aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId,object : MatrixCallback { + override fun onFailure(failure: Throwable) { + fail("Failed to check trust cause:${failure.localizedMessage}") + } + + override fun onSuccess(data: Unit) { + secondDevicetrustLatch.countDown() + } + }) + + mTestHelper.await(secondDevicetrustLatch) + + } +} diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt index c05523f009..fcc4beda48 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/verification/SASTest.kt @@ -36,6 +36,10 @@ import org.junit.runner.RunWith import org.junit.runners.MethodSorters import java.util.* import java.util.concurrent.CountDownLatch +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.test.annotation.UiThreadTest +import org.junit.Rule + @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.NAME_ASCENDING) @@ -135,8 +139,24 @@ class SASTest : InstrumentedTest { val tid = "00000000" // Bob should receive a cancel - var canceledToDeviceEvent: Event? = null + var cancelReason: String? = null val cancelLatch = CountDownLatch(1) + + + val bobListener = object : SasVerificationService.SasVerificationListener { + override fun transactionCreated(tx: SasVerificationTransaction) {} + + override fun transactionUpdated(tx: SasVerificationTransaction) { + if (tx.transactionId == tid && tx.cancelledReason != null) { + cancelReason = tx.cancelledReason?.humanReadable + cancelLatch.countDown() + } + } + + override fun markedAsManuallyVerified(userId: String, deviceId: String) {} + } + bobSession.getSasVerificationService().addListener(bobListener) + // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { // TODO override fun onToDeviceEvent(event: Event?) { // TODO if (event!!.getType() == CryptoEvent.EVENT_TYPE_KEY_VERIFICATION_CANCEL) { @@ -156,8 +176,8 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionUpdated(tx: SasVerificationTransaction) { - if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { - (tx as IncomingSASVerificationTransaction).performAccept() + if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + (tx as IncomingSasVerificationTransaction).performAccept() } } @@ -169,8 +189,7 @@ class SASTest : InstrumentedTest { mTestHelper.await(cancelLatch) - val cancelReq = canceledToDeviceEvent!!.content.toModel()!! - assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReq.code) + assertEquals("Request should be cancelled with m.unknown_method", CancelCode.UnknownMethod.value, cancelReason) cryptoTestData.close() } @@ -257,14 +276,15 @@ class SASTest : InstrumentedTest { hashes: List = SASVerificationTransaction.KNOWN_HASHES, mac: List = SASVerificationTransaction.KNOWN_MACS, codes: List = SASVerificationTransaction.KNOWN_SHORT_CODES) { - val startMessage = KeyVerificationStart() - startMessage.fromDevice = bobSession.getMyDevice().deviceId - startMessage.method = KeyVerificationStart.VERIF_METHOD_SAS - startMessage.transactionID = tid - startMessage.keyAgreementProtocols = protocols - startMessage.hashes = hashes - startMessage.messageAuthenticationCodes = mac - startMessage.shortAuthenticationStrings = codes + val startMessage = KeyVerificationStart( + fromDevice = bobSession.getMyDevice().deviceId, + method = KeyVerificationStart.VERIF_METHOD_SAS, + transactionID = tid, + keyAgreementProtocols = protocols, + hashes = hashes, + messageAuthenticationCodes = mac, + shortAuthenticationStrings = codes + ) val contentMap = MXUsersDevicesMap() contentMap.setObject(aliceUserID, aliceDevice, startMessage) @@ -344,8 +364,8 @@ class SASTest : InstrumentedTest { override fun transactionUpdated(tx: SasVerificationTransaction) { if ((tx as SASVerificationTransaction).state === SasVerificationTxState.OnAccepted) { val at = tx as SASVerificationTransaction - accepted = at.accepted - startReq = at.startReq + accepted = at.accepted as? KeyVerificationAccept + startReq = at.startReq as? KeyVerificationStart aliceAcceptedLatch.countDown() } } @@ -356,8 +376,8 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionUpdated(tx: SasVerificationTransaction) { - if ((tx as IncomingSASVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { - val at = tx as IncomingSASVerificationTransaction + if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { + val at = tx as IncomingSasVerificationTransaction at.performAccept() } } @@ -401,7 +421,7 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionUpdated(tx: SasVerificationTransaction) { - val uxState = (tx as OutgoingSASVerificationRequest).uxState + val uxState = (tx as OutgoingSasVerificationRequest).uxState when (uxState) { OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { aliceSASLatch.countDown() @@ -419,7 +439,7 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionUpdated(tx: SasVerificationTransaction) { - val uxState = (tx as IncomingSASVerificationTransaction).uxState + val uxState = (tx as IncomingSasVerificationTransaction).uxState when (uxState) { IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { tx.performAccept() @@ -465,7 +485,7 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionUpdated(tx: SasVerificationTransaction) { - val uxState = (tx as OutgoingSASVerificationRequest).uxState + val uxState = (tx as OutgoingSasVerificationRequest).uxState when (uxState) { OutgoingSasVerificationRequest.UxState.SHOW_SAS -> { tx.userHasVerifiedShortCode() @@ -486,7 +506,7 @@ class SASTest : InstrumentedTest { override fun transactionCreated(tx: SasVerificationTransaction) {} override fun transactionUpdated(tx: SasVerificationTransaction) { - val uxState = (tx as IncomingSASVerificationTransaction).uxState + val uxState = (tx as IncomingSasVerificationTransaction).uxState when (uxState) { IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { tx.performAccept() diff --git a/matrix-sdk-android/src/main/AndroidManifest.xml b/matrix-sdk-android/src/main/AndroidManifest.xml index 7191d9c8d5..e8762b21f2 100644 --- a/matrix-sdk-android/src/main/AndroidManifest.xml +++ b/matrix-sdk-android/src/main/AndroidManifest.xml @@ -6,9 +6,10 @@ - + - diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt index 986cbb698b..9dbc679ba2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/CryptoService.kt @@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.crypto import android.content.Context import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.listeners.ProgressListener +import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService import im.vector.matrix.android.api.session.crypto.keyshare.RoomKeysRequestListener import im.vector.matrix.android.api.session.crypto.sas.SasVerificationService @@ -47,6 +48,8 @@ interface CryptoService { fun isCryptoEnabled(): Boolean fun getSasVerificationService(): SasVerificationService + + fun getCrossSigningService(): CrossSigningService fun getKeysBackupService(): KeysBackupService diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt new file mode 100644 index 0000000000..3a73e00c4f --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningService.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.api.session.crypto.crosssigning + +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth + +interface CrossSigningService { + + fun isUserTrusted(userId: String) : Boolean + + fun checkUserTrust(userId: String, callback: MatrixCallback? = null) + + /** + * Initialize cross signing for this user. + * Users needs to enter credentials + */ + fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback? = null) + + fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? + + fun getMyCrossSigningKeys(): MXCrossSigningInfo? + + fun trustUser(userId: String, callback: MatrixCallback) + + /** + * Sign one of your devices and upload the signature + */ + fun signDevice(deviceId: String, callback: MatrixCallback) + + fun checkDeviceTrust(userId: String, deviceId: String, callback: MatrixCallback) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningState.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningState.kt new file mode 100644 index 0000000000..774aea9f5b --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/CrossSigningState.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.api.session.crypto.crosssigning + +/** + * Defines the account cross signing state. + * + */ +enum class CrossSigningState { + /** Current state is unknown, need to download user keys from server to resolve */ + Unknown, + /** Currently dowloading user keys*/ + CheckingState, + /** No Cross signing keys are defined on the server */ + Disabled, + /** CrossSigning keys are beeing created and uploaded to the server */ + Enabling, + /** Cross signing keys exists and are trusted*/ + Trusted, + /** Cross signing keys exists but are not yet trusted*/ + Untrusted, + /** The local cross signing keys do not match with the server keys*/ + Conflicted +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXCrossSigningInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXCrossSigningInfo.kt new file mode 100644 index 0000000000..9c559998a3 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXCrossSigningInfo.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.api.session.crypto.crosssigning + +import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo + +data class MXCrossSigningInfo( + + /** + * the user id + */ +// @Json(name = "user_id") + var userId: String, + +// @Json(name = "user_keys") + var crossSigningKeys: List = ArrayList(), + + val isTrusted: Boolean = false + +) { + + fun masterKey(): CrossSigningKeyInfo? = crossSigningKeys + .firstOrNull { it.usages?.contains(CrossSigningKeyInfo.KeyUsage.MASTER.value) == true } + + + fun userKey(): CrossSigningKeyInfo? = crossSigningKeys + .firstOrNull { it.usages?.contains(CrossSigningKeyInfo.KeyUsage.USER_SIGNING.value) == true } + + + fun selfSigningKey(): CrossSigningKeyInfo? = crossSigningKeys + .firstOrNull { it.usages?.contains(CrossSigningKeyInfo.KeyUsage.SELF_SIGNING.value) == true } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXKeyInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXKeyInfo.kt new file mode 100644 index 0000000000..4302438a01 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/crypto/crosssigning/MXKeyInfo.kt @@ -0,0 +1,80 @@ +///* +// * Copyright 2020 New Vector 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 im.vector.matrix.android.api.session.crypto.crosssigning +// +//import com.squareup.moshi.Json +//import com.squareup.moshi.JsonClass +//import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo +// +// +//@JsonClass(generateAdapter = true) +//data class MXKeyInfo( +// +// @Json(name = "public_key") +// val publicKeyBase64: String, +// val privateKeyBase64: String?, +// +// @Json(name = "is_trusted") +// val isTrusted: Boolean = false, +// +// +// @Json(name = "usage") +// val usage: List = ArrayList(), +// +// /** +// * The signature of this MXDeviceInfo. +// * A map from "" to a map from ":" to "" +// */ +// @Json(name = "signatures") +// var signatures: Map>? = null +// +//) { +// +// data class Builder( +// private val publicKeyBase64: String, +// private val usage: CrossSigningKeyInfo.KeyUsage, +// private var trusted: Boolean = false, +// private val signatures: ArrayList> = ArrayList() +// ) { +// +// fun signature(userId: String, keyUsedToSignBase64: String, base64Signature: String) = apply { +// signatures.add(Triple(userId, keyUsedToSignBase64, base64Signature)) +// } +// +// fun trusted(trusted: Boolean) = apply { +// this.trusted = trusted +// } +// +// fun build(): MXKeyInfo { +// +// val signMap = HashMap>() +// signatures.forEach { info -> +// val uMap = signMap[info.first] +// ?: HashMap().also { signMap[info.first] = it } +// uMap["ed25519:${info.second}"] = info.third +// } +// +// return MXKeyInfo( +// publicKeyBase64 = publicKeyBase64, +// usage = listOf(usage.value), +// isTrusted = trusted, +// signatures = signMap +// ) +// } +// } +//} +// diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 22ccf44983..4a2e18cb2d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -132,6 +132,12 @@ internal abstract class CryptoModule { @Binds abstract fun bindUploadKeysTask(uploadKeysTask: DefaultUploadKeysTask): UploadKeysTask + @Binds + abstract fun bindUploadSigningKeysTask(uploadKeysTask: DefaultUploadSigningKeysTask): UploadSigningKeysTask + + @Binds + abstract fun bindUploadSignaturesTask(uploadSignaturesTask: DefaultUploadSignaturesTask): UploadSignaturesTask + @Binds abstract fun bindDownloadKeysForUsersTask(downloadKeysForUsersTask: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt index bf981cc673..4eaf428b95 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DefaultCryptoService.kt @@ -44,6 +44,7 @@ import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAct import im.vector.matrix.android.internal.crypto.algorithms.IMXEncrypting import im.vector.matrix.android.internal.crypto.algorithms.megolm.MXMegolmEncryptionFactory import im.vector.matrix.android.internal.crypto.algorithms.olm.MXOlmEncryptionFactory +import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup import im.vector.matrix.android.internal.crypto.model.ImportRoomKeysResult import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo @@ -113,6 +114,8 @@ internal class DefaultCryptoService @Inject constructor( private val roomDecryptorProvider: RoomDecryptorProvider, // The SAS verification service. private val sasVerificationService: DefaultSasVerificationService, + + private val crossSigningService: DefaultCrossSigningService, // private val incomingRoomKeyRequestManager: IncomingRoomKeyRequestManager, // @@ -317,6 +320,8 @@ internal class DefaultCryptoService @Inject constructor( */ override fun getSasVerificationService() = sasVerificationService + override fun getCrossSigningService() = crossSigningService + /** * A sync response has been received * @@ -364,7 +369,7 @@ internal class DefaultCryptoService @Inject constructor( */ override fun getDeviceInfo(userId: String, deviceId: String?): MXDeviceInfo? { return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { - cryptoStore.getUserDevice(deviceId, userId) + cryptoStore.getUserDevice(userId, deviceId) } else { null } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt index b2002f0916..526c584576 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/DeviceListManager.kt @@ -171,8 +171,8 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM for ((k, value) in failures) { val statusCode = when (val status = value["status"]) { is Double -> status.toInt() - is Int -> status.toInt() - else -> 0 + is Int -> status.toInt() + else -> 0 } if (statusCode == 503) { synchronized(notReadyToRetryHS) { @@ -289,7 +289,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM val mutableDevices = devices.toMutableMap() for ((deviceId, deviceInfo) in devices) { // Get the potential previously store device keys for this device - val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId) + val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(userId, deviceId) // in some race conditions (like unit tests) // the self device must be seen as verified @@ -315,6 +315,25 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM // Note that devices which aren't in the response will be removed from the stores cryptoStore.storeUserDevices(userId, mutableDevices) } + + + + //Handle cross signing keys update + val masterKey = response.masterKeys?.get(userId)?.also { + Timber.d("## CrossSigning : Got keys for $userId : MSK ${it.unpaddedBase64PublicKey}") + } + val selfSigningKey = response.selfSigningKeys?.get(userId)?.also { + Timber.d("## CrossSigning : Got keys for $userId : SSK ${it.unpaddedBase64PublicKey}") + } + val userSigningKey = response.userSigningKeys?.get(userId)?.also { + Timber.d("## CrossSigning : Got keys for $userId : USK ${it.unpaddedBase64PublicKey}") + } + cryptoStore.storeUserCrossSigningKeys( + userId, + masterKey, + selfSigningKey, + userSigningKey + ) } return onKeysDownloadSucceed(filteredUsers, response.failures) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt index e8d8bf0f35..e7c500edbf 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/IncomingRoomKeyRequestManager.kt @@ -107,7 +107,7 @@ internal class IncomingRoomKeyRequestManager @Inject constructor( cryptoStore.deleteIncomingRoomKeyRequest(request) } // if the device is verified already, share the keys - val device = cryptoStore.getUserDevice(deviceId!!, userId) + val device = cryptoStore.getUserDevice(userId, deviceId!!) if (device != null) { if (device.isVerified) { Timber.v("## processReceivedRoomKeyRequests() : device is already verified: sharing keys") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt index 6171b32811..75a4ba5ed7 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXOlmDevice.kt @@ -141,6 +141,7 @@ internal class MXOlmDevice @Inject constructor( */ fun release() { olmAccount?.releaseAccount() + olmUtility?.releaseUtility() } /** diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt index 2d0c77c768..cc22c9830d 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/actions/SetDeviceVerificationAction.kt @@ -28,7 +28,7 @@ internal class SetDeviceVerificationAction @Inject constructor( private val keysBackup: KeysBackup) { fun handle(verificationStatus: Int, deviceId: String, userId: String) { - val device = cryptoStore.getUserDevice(deviceId, userId) + val device = cryptoStore.getUserDevice(userId, deviceId) // Sanity check if (null == device) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt index 81ac1403df..49871434f9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/algorithms/megolm/MXMegolmDecryption.kt @@ -297,7 +297,7 @@ internal class MXMegolmDecryption(private val userId: String, runCatching { deviceListManager.downloadKeys(listOf(userId), false) } .mapCatching { val deviceId = request.deviceId - val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId) + val deviceInfo = cryptoStore.getUserDevice(userId, deviceId ?: "") if (deviceInfo == null) { throw RuntimeException() } else { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/api/CryptoApi.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/api/CryptoApi.kt index b2e880c2f3..a4747b1932 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/api/CryptoApi.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/api/CryptoApi.kt @@ -16,6 +16,7 @@ */ package im.vector.matrix.android.internal.crypto.api +import im.vector.matrix.android.internal.crypto.model.MXKeysObject import im.vector.matrix.android.internal.crypto.model.rest.* import im.vector.matrix.android.internal.network.NetworkConstants import retrofit2.Call @@ -65,6 +66,36 @@ internal interface CryptoApi { @POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "keys/query") fun downloadKeysForUsers(@Body params: KeysQueryBody): Call + + /** + * CrossSigning - Uploading signing keys + * Public keys for the cross-signing keys are uploaded to the servers using /keys/device_signing/upload. + * This endpoint requires UI Auth. + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/device_signing/upload") + fun uploadSigningKeys(@Body params: UploadSigningKeysBody): Call + + + /** + * CrossSigning - Uploading signatures + * Signatures of device keys can be up + * loaded using /keys/signatures/upload. + * For example, Alice signs one of her devices (HIJKLMN) (using her self-signing key), + * her own master key (using her HIJKLMN device), Bob's master key (using her user-signing key). + * + * The response contains a failures property, which is a map of user ID to device ID to failure reason, if any of the uploaded keys failed. + * The homeserver should verify that the signatures on the uploaded keys are valid. + * If a signature is not valid, the homeserver should set the corresponding entry in failures to a JSON object + * with the errcode property set to M_INVALID_SIGNATURE. + * + * After Alice uploads a signature for her own devices or master key, + * her signature will be included in the results of the /keys/query request when anyone requests her keys. + * However, signatures made for other users' keys, made by her user-signing key, will not be included. + */ + @POST(NetworkConstants.URI_API_PREFIX_PATH_UNSTABLE + "keys/signatures/upload") + fun uploadSignatures(@Body params: Map?): Call + + /** * Claim one-time keys. * Doc: https://matrix.org/docs/spec/client_server/r0.4.0.html#post-matrix-client-r0-keys-claim diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt new file mode 100644 index 0000000000..850fa7c923 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -0,0 +1,511 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.crosssigning + +import android.util.Base64 +import dagger.Lazy +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService +import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningState +import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.internal.crypto.DeviceListManager +import im.vector.matrix.android.internal.crypto.MXOlmDevice +import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.rest.* +import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask +import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask +import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.task.TaskConstraints +import im.vector.matrix.android.internal.task.TaskExecutor +import im.vector.matrix.android.internal.task.configureWith +import im.vector.matrix.android.internal.util.JsonCanonicalizer +import org.matrix.olm.OlmPkSigning +import org.matrix.olm.OlmUtility +import timber.log.Timber +import javax.inject.Inject + +internal class DefaultCrossSigningService @Inject constructor( + @UserId private val userId: String, + private val credentials: Credentials, + private val cryptoStore: IMXCryptoStore, + private val myDeviceInfoHolder: Lazy, + private val olmDevice: MXOlmDevice, + private val deviceListManager: DeviceListManager, + private val uploadSigningKeysTask: UploadSigningKeysTask, + private val uploadSignaturesTask: UploadSignaturesTask, + private val taskExecutor: TaskExecutor) : CrossSigningService { + + + private var olmUtility: OlmUtility? = null + + private var crossSigningState: CrossSigningState = CrossSigningState.Unknown + + private var masterPkSigning: OlmPkSigning? = null + private var userPkSigning: OlmPkSigning? = null + private var selfSigningPkSigning: OlmPkSigning? = null + + init { + try { + olmUtility = OlmUtility() + + //Try to get stored keys if they exist + cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo -> + Timber.i("## CrossSigning - Found Existing self signed keys") + Timber.i("## CrossSigning - Checking if private keys are known") + + cryptoStore.getCrossSigningPrivateKeys()?.let { privateKeyinfo -> + privateKeyinfo.master?.let { privateKey -> + val keySeed = Base64.decode(privateKey, Base64.NO_PADDING) + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) { + masterPkSigning = pkSigning + Timber.i("## CrossSigning - Loading master key success") + } else { + Timber.w("## CrossSigning - Public master key does not match the private key") + // TODO untrust + } + } + privateKeyinfo.user?.let { privateKey -> + val keySeed = Base64.decode(privateKey, Base64.NO_PADDING) + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) { + userPkSigning = pkSigning + Timber.i("## CrossSigning - Loading User Signing key success") + } else { + Timber.w("## CrossSigning - Public User key does not match the private key") + // TODO untrust + } + } + privateKeyinfo.selfSigned?.let { privateKey -> + val keySeed = Base64.decode(privateKey, Base64.NO_PADDING) + val pkSigning = OlmPkSigning() + if (pkSigning.initWithSeed(keySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) { + selfSigningPkSigning = pkSigning + Timber.i("## CrossSigning - Loading Self Signing key success") + } else { + Timber.w("## CrossSigning - Public Self Signing key does not match the private key") + // TODO untrust + } + } + } + + } + } catch (e: Throwable) { + // Mmm this kind of a big issue + Timber.e(e, "Failed to initialize Cross Signing") + } + } + + + fun release() { + olmUtility?.releaseUtility() + listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() } + } + + + /** + * - Make 3 key pairs (MSK, USK, SSK) + * - Save the private keys with proper security + * - Sign the keys and upload them + * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures + */ + override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback?) { + Timber.d("## CrossSigning initializeCrossSigning") + // TODO sync that + crossSigningState = CrossSigningState.Enabling + + val myUserID = credentials.userId + + //================= + // MASTER KEY + //================= + val masterPkOlm = OlmPkSigning() + val masterKeyPrivateKey = OlmPkSigning.generateSeed() + val masterPublicKey = masterPkOlm.initWithSeed(masterKeyPrivateKey) + + Timber.v("## CrossSigning - masterPublicKey:$masterPublicKey") + + //================= + // USER KEY + //================= + val userSigningPkOlm = OlmPkSigning() + val uskPrivateKey = OlmPkSigning.generateSeed() + val uskPublicKey = userSigningPkOlm.initWithSeed(uskPrivateKey) + + Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey") + + // Sign userSigningKey with master + val signedUSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.USER_SIGNING) + .key(uskPublicKey) + .build().signalableJSONDictionary()).let { masterPkOlm.sign(it) } + + //================= + // SELF SIGNING KEY + //================= + val selfSigningPkOlm = OlmPkSigning() + val sskPrivateKey = OlmPkSigning.generateSeed() + val sskPublicKey = selfSigningPkOlm.initWithSeed(sskPrivateKey) + + Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") + + + // Sign userSigningKey with master + val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.SELF_SIGNING) + .key(sskPublicKey) + .build().signalableJSONDictionary()).let { masterPkOlm.sign(it) } + + + // I need to upload the keys + val mskCrossSigningKeyInfo = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.MASTER) + .key(masterPublicKey) + .build() + val params = UploadSigningKeysTask.Params( + masterKey = mskCrossSigningKeyInfo, + userKey = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.USER_SIGNING) + .key(uskPublicKey) + .signature(myUserID, masterPublicKey, signedUSK) + .build(), + selfSignedKey = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.SELF_SIGNING) + .key(sskPublicKey) + .signature(myUserID, masterPublicKey, signedSSK) + .build(), + userPasswordAuth = authParams + ) + + this.masterPkSigning = masterPkOlm + this.userPkSigning = userSigningPkOlm + this.selfSigningPkSigning = selfSigningPkOlm + + val crossSigningInfo = MXCrossSigningInfo(myUserID, listOf(params.masterKey, params.userKey, params.selfSignedKey)) + cryptoStore.setMyCrossSigningInfo(crossSigningInfo) + cryptoStore.setUserKeysAsTrusted(myUserID) + + // TODO we should ensure that they are sent + // TODO error handling? + uploadSigningKeysTask.configureWith(params) { + this.retryCount = 3 + this.constraints = TaskConstraints(true) + this.callback = object : MatrixCallback { + override fun onSuccess(data: KeysQueryResponse) { + Timber.i("## CrossSigning - Keys succesfully uploaded") + + // Sign the current device with SSK + val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder() + + val myDevice = myDeviceInfoHolder.get().myDevice + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) + val signedDevice = selfSigningPkOlm.sign(canonicalJson) + val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()).also { + it[myUserID] = (it[myUserID] + ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) + } + myDevice.copy(signatures = updateSignatures).let { + uploadSignatureQueryBuilder.withDeviceInfo(it) + } + + // sign MSK with device key (migration) and upload signatures + olmDevice.signMessage(JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()))?.let { sign -> + val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() + ?: HashMap()).also { + it[myUserID] = (it[myUserID] + ?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign) + } + mskCrossSigningKeyInfo.copy( + signatures = mskUpdatedSignatures + ).let { + uploadSignatureQueryBuilder.withSigningKeyInfo(it) + } + } + + uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) { + this.retryCount = 3 + this.constraints = TaskConstraints(true) + this.callback = object : MatrixCallback { + override fun onSuccess(data: SignatureUploadResponse) { + Timber.i("## CrossSigning - signatures succesfuly uploaded") + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## CrossSigning - Failed to upload signatures") + } + } + }.executeBy(taskExecutor) + + + callback?.onSuccess(Unit) + crossSigningState = CrossSigningState.Trusted + } + + override fun onFailure(failure: Throwable) { + Timber.e(failure, "## CrossSigning - Failed to upload signing keys") + callback?.onFailure(failure) + } + } + }.executeBy(taskExecutor) + + + } + + /** + * + * ┏━━━━━━━━┓ ┏━━━━━━━━┓ + * ┃ ALICE ┃ ┃ BOB ┃ + * ┗━━━━━━━━┛ ┗━━━━━━━━┛ + * MSK ┌────────────▶MSK + * │ + * │ │ │ + * │ SSK │ └──▶ SSK ──────────────────┐ + * │ │ │ + * │ │ USK │ + * └──▶ USK ────────────┘ (not visible by │ + * Alice) │ + * ▼ + * ┌──────────────┐ + * │ BOB's Device │ + * └──────────────┘ + */ + override fun isUserTrusted(userId: String): Boolean { + return cryptoStore.getCrossSigningInfo(userId)?.isTrusted == true + } + + /** + * Will not force a download of the key, but will verify signatures trust chain + */ + override fun checkUserTrust(userId: String, callback: MatrixCallback?) { + Timber.d("## CrossSigning checkUserTrust for $userId") + // I trust a user if I trust his master key + // I can trust the master key if it is signed by my user key + // TODO what if the master key is signed by a device key that i have verified + + // First let's get my user key + val myUserKey = cryptoStore.getCrossSigningInfo(credentials.userId)?.userKey() + if (myUserKey == null) { + Timber.d("## CrossSigning checkUserTrust false, CrossSigning is not enabled (userKey not defined)") + callback?.onSuccess(false) + return + } + + // Let's get the other user master key + val masterKey = cryptoStore.getCrossSigningInfo(userId)?.masterKey() + if (masterKey == null) { + Timber.d("## CrossSigning checkUserTrust false for $userId, ") + callback?.onSuccess(false) + return + } + + val masterKeySignaturesMadeByMyUserKey = masterKey.signatures + ?.get(credentials.userId) // Signatures made by me + ?.get("ed25519:${myUserKey.unpaddedBase64PublicKey}") + + + if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { + Timber.d("## CrossSigning checkUserTrust false for $userId, not signed by my UserSigningKey") + callback?.onSuccess(false) + return + } + +// olmUtility?.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, +// myUserKey.publicKeyBase64, +// masterKey.publicKeyBase64) + + } + + override fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? { + return cryptoStore.getCrossSigningInfo(userId) + } + + override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { + return cryptoStore.getMyCrossSigningInfo() + } + + override fun trustUser(userId: String, callback: MatrixCallback) { + //We should have this user keys + val otherMasterKeys = getUserCrossSigningKeys(userId)?.masterKey() + if (otherMasterKeys == null) { + callback.onFailure(Throwable("Other master signing key is not known")) + return + } + val myKeys = getUserCrossSigningKeys(credentials.userId) + if (myKeys == null) { + callback.onFailure(Throwable("CrossSigning is not setup for this account")) + return + } + val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey + if (userPubKey == null || userPkSigning == null) { + callback.onFailure(Throwable("Cannot sign from this account, privateKeyUnknown $userPubKey")) + return + } + + // Sign the other MasterKey with our UserSiging key + val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, + otherMasterKeys.signalableJSONDictionary()).let { userPkSigning?.sign(it) } + + if (newSignature == null) { + // race?? + callback.onFailure(Throwable("Failed to sign")) + return + } + + otherMasterKeys.addSignature(credentials.userId, userPubKey, newSignature) + cryptoStore.setUserKeysAsTrusted(userId, true) + // TODO update local copy with new signature directly here? kind of local echo of trust? + + val uploadQuery = UploadSignatureQueryBuilder() + .withSigningKeyInfo(otherMasterKeys) + .build() + uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { + this.callback = callback + }.executeBy(taskExecutor) + + } + + + override fun signDevice(deviceId: String, callback: MatrixCallback) { + // This device should be yours + val device = cryptoStore.getUserDevice(credentials.userId, deviceId) + if (device == null) { + callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) + return + } + + val myKeys = getUserCrossSigningKeys(credentials.userId) + if (myKeys == null) { + callback.onFailure(Throwable("CrossSigning is not setup for this account")) + return + } + + val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey + if (ssPubKey == null || selfSigningPkSigning == null) { + callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) + return + } + + // Sign with self signing +// val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, device.signalableJSONDictionary()).let { userPkSigning?.sign(it) } + val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable()) + + if (newSignature == null) { + // race?? + callback.onFailure(Throwable("Failed to sign")) + return + } + val toUpload = device.copy( + signatures = mapOf( + credentials.userId + to + mapOf( + "ed25519:${ssPubKey}" to newSignature + ) + ) + ) +// device.addSignature(credentials.userId, ssPubKey, newSignature) + + val uploadQuery = UploadSignatureQueryBuilder() + .withDeviceInfo(toUpload) + .build() + uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { + this.callback = object : MatrixCallback { + override fun onFailure(failure: Throwable) { + callback.onFailure(failure) + } + + override fun onSuccess(data: SignatureUploadResponse) { + val watchedFailure = data.failures?.get(userId)?.get(deviceId) + if (watchedFailure == null) { + callback.onSuccess(data) + } else { + val failure = MoshiProvider.providesMoshi().adapter(UploadResponseFailure::class.java).fromJson(watchedFailure.toString())?.message + ?: watchedFailure.toString() + callback.onFailure(Throwable(failure)) + } + } + } + }.executeBy(taskExecutor) + } + + override fun checkDeviceTrust(userId: String, deviceId: String, callback: MatrixCallback) { + val otherDevice = cryptoStore.getUserDevice(userId, deviceId) + if (otherDevice == null) { + callback.onFailure(IllegalArgumentException("This device is not known, or not yours")) + return + } + + val myKeys = getUserCrossSigningKeys(credentials.userId) + if (myKeys == null) { + callback.onFailure(Throwable("CrossSigning is not setup for this account")) + return + } + + val otherKeys = getUserCrossSigningKeys(userId) + if (otherKeys == null) { + callback.onFailure(Throwable("CrossSigning is not setup for $userId")) + return + } + + // TODO should we force verification ? + if (!otherKeys.isTrusted) { + callback.onFailure(Throwable("$userId is not trusted")) + return + } + + // Check if the trust chain is valid + /* + * ┏━━━━━━━━┓ ┏━━━━━━━━┓ + * ┃ ALICE ┃ ┃ BOB ┃ + * ┗━━━━━━━━┛ ┗━━━━━━━━┛ + * MSK ┌────────────▶MSK + * │ + * │ │ │ + * │ SSK │ └──▶ SSK ──────────────────┐ + * │ │ │ + * │ │ USK │ + * └──▶ USK ────────────┘ (not visible by │ + * Alice) │ + * ▼ + * ┌──────────────┐ + * │ BOB's Device │ + * └──────────────┘ + */ + + val otherSSKSignature = otherDevice.signatures?.get(userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}") + + if (otherSSKSignature == null) { + callback.onFailure(Throwable("Device ${otherDevice.deviceId} is not signed by $userId self signed key")) + return + } + + + // Check bob's device is signed by bob's SSK + try { + olmUtility?.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable()) + } catch (e: Throwable) { + callback.onFailure(Throwable("Invalid self signed signature for Device ${otherDevice.deviceId}")) + } + + callback.onSuccess(Unit) + + } +} + +fun MXDeviceInfo.canonicalSignable(): String { + return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt index 99267ee89c..73646de361 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/keysbackup/KeysBackup.kt @@ -402,7 +402,7 @@ internal class KeysBackup @Inject constructor( } if (deviceId != null) { - val device = cryptoStore.getUserDevice(deviceId, userId) + val device = cryptoStore.getUserDevice(userId, deviceId) var isSignatureValid = false if (device == null) { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt index cc9b3bff74..e0e6e6e4ce 100755 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXDeviceInfo.kt @@ -147,6 +147,14 @@ data class MXDeviceInfo( return map } + fun addSignature(userId: String, signedWithNoPrefix: String, signature: String) = apply { + val updated = (signatures?.toMutableMap() ?: HashMap()) + val userMap = updated[userId]?.toMutableMap() + ?: HashMap().also { updated[userId] = it } + userMap["ed25519:${signedWithNoPrefix}"] = signature + signatures = updated + } + /** * @return a dictionary of the parameters */ diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXKeysObject.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXKeysObject.kt new file mode 100644 index 0000000000..e3b17d5513 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/MXKeysObject.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2019 New Vector 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 im.vector.matrix.android.internal.crypto.model + +interface MXKeysObject { + + val userId: String + + val keys: Map? + + val signatures: Map>? + +// fun signalableJSONDictionary(): Map +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/CrossSigningKeyInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/CrossSigningKeyInfo.kt new file mode 100644 index 0000000000..b5a4e8936a --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/CrossSigningKeyInfo.kt @@ -0,0 +1,125 @@ +package im.vector.matrix.android.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.model.MXKeysObject + +/** + * "self_signing_key": { + * "user_id": "@alice:example.com", + * "usage": ["self_signing"], + * "keys": { + * "ed25519:base64+self+signing+public+key": "base64+self+signing+public+key" + * }, + * "signatures": { + * "@alice:example.com": { + * "ed25519:base64+master+public+key": "base64+signature" + * } + * } + * } + */ +@JsonClass(generateAdapter = true) +data class CrossSigningKeyInfo( + /** + * The user who owns the key + */ + @Json(name = "user_id") + override var userId: String, + /** + * Allowed uses for the key. + * Must contain "master" for master keys, "self_signing" for self-signing keys, and "user_signing" for user-signing keys. + * See CrossSigningKeyInfo#KEY_USAGE_* constants + */ + @Json(name = "usage") + val usages: List?, + + /** + * An object that must have one entry, + * whose name is "ed25519:" followed by the unpadded base64 encoding of the public key, + * and whose value is the unpadded base64 encoding of the public key. + */ + @Json(name = "keys") + override var keys: Map?, + + /** + * Signatures of the key. + * A self-signing or user-signing key must be signed by the master key. + * A master key may be signed by a device. + */ + @Json(name = "signatures") + override var signatures: Map>? = null +) : MXKeysObject { + // Shortcut to get key as "keys" is an object that must have one entry + val unpaddedBase64PublicKey: String? = keys?.values?.firstOrNull() + + val isMasterKey = usages?.contains(KeyUsage.MASTER.value) ?: false + val isSelfSigningKey = usages?.contains(KeyUsage.SELF_SIGNING.value) ?: false + val isUserKey = usages?.contains(KeyUsage.USER_SIGNING.value) ?: false + + fun signalableJSONDictionary(): Map { + val map = HashMap() + userId.let { map["user_id"] = it } + usages?.let { map["usage"] = it } + keys?.let { map["keys"] = it } + + return map + } + + fun addSignature(userId: String, signedWithNoPrefix: String, signature: String) = apply { + val updated = (signatures?.toMutableMap() ?: HashMap()) + val userMap = updated[userId]?.toMutableMap() + ?: HashMap().also { updated[userId] = it } + userMap["ed25519:${signedWithNoPrefix}"] = signature + signatures = updated + } +// fun toXSigningKeys(): XSigningKeys { +// return XSigningKeys( +// userId = userId, +// usage = usages ?: emptyList(), +// keys = keys ?: emptyMap(), +// signatures = signatures +// +// ) +// } + + data class Builder( + val userId: String, + val usage: KeyUsage, + private var base64Pkey: String? = null, + private val signatures: ArrayList> = ArrayList() + ) { + + fun key(publicKeyBase64: String) = apply { + base64Pkey = publicKeyBase64 + } + + fun signature(userId: String, keySignedBase64: String, base64Signature: String) = apply { + signatures.add(Triple(userId, keySignedBase64, base64Signature)) + } + + fun build(): CrossSigningKeyInfo { + val b64key = base64Pkey ?: throw IllegalArgumentException("") + + val signMap = HashMap>() + signatures.forEach { info -> + val uMap = signMap[info.first] + ?: HashMap().also { signMap[info.first] = it } + uMap["ed25519:${info.second}"] = info.third + } + + return CrossSigningKeyInfo( + userId = userId, + usages = listOf(usage.value), + keys = mapOf("ed25519:$b64key" to b64key), + signatures = signMap + ) + } + } + + enum class KeyUsage(val value: String) { + MASTER("master"), + SELF_SIGNING("self_signing"), + USER_SIGNING("user_signing") + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt index f823de2eb3..fc8eff2875 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceParams.kt @@ -24,5 +24,5 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) internal data class DeleteDeviceParams( @Json(name = "auth") - var deleteDeviceAuth: DeleteDeviceAuth? = null + var userPasswordAuth: UserPasswordAuth? = null ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceKeys.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceKeys.kt index 3486bfdabe..72dc2daf88 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceKeys.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeviceKeys.kt @@ -18,12 +18,12 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass -import im.vector.matrix.android.api.util.JsonDict +import im.vector.matrix.android.internal.crypto.model.MXKeysObject @JsonClass(generateAdapter = true) data class DeviceKeys( @Json(name = "user_id") - val userId: String, + override val userId: String, @Json(name = "device_id") val deviceId: String, @@ -32,8 +32,8 @@ data class DeviceKeys( val algorithms: List, @Json(name = "keys") - val keys: Map, + override val keys: Map, @Json(name = "signatures") - val signatures: JsonDict -) + override val signatures: Map>? +) : MXKeysObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryResponse.kt index d13bf9954f..0527b5ae3f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryResponse.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/KeysQueryResponse.kt @@ -1,6 +1,5 @@ /* - * 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. @@ -23,6 +22,11 @@ import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo /** * This class represents the response to /keys/query request made by downloadKeysForUsers + * + * After uploading cross-signing keys, they will be included under the /keys/query endpoint under the master_keys, + * self_signing_keys and user_signing_keys properties. + * + * The user_signing_keys property will only be included when a user requests their own keys. */ @JsonClass(generateAdapter = true) data class KeysQueryResponse( @@ -38,5 +42,16 @@ data class KeysQueryResponse( * The failures sorted by homeservers. TODO Bad comment ? * TODO Use MXUsersDevicesMap? */ - var failures: Map>? = null + var failures: Map>? = null, + + @Json(name = "master_keys") + var masterKeys: Map? = null, + + @Json(name = "self_signing_keys") + var selfSigningKeys: Map? = null, + + @Json(name = "user_signing_keys") + var userSigningKeys: Map? = null + ) + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt new file mode 100644 index 0000000000..4cfb608c46 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/SignatureUploadResponse.kt @@ -0,0 +1,50 @@ +/* + * 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 im.vector.matrix.android.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo + +/** + * Upload Signature response + */ +@JsonClass(generateAdapter = true) +data class SignatureUploadResponse( + + /** + * The response contains a failures property, which is a map of user ID to device ID to failure reason, + * if any of the uploaded keys failed. + * The homeserver should verify that the signatures on the uploaded keys are valid. + * If a signature is not valid, the homeserver should set the corresponding entry in failures to a JSON object + * with the errcode property set to M_INVALID_SIGNATURE. + */ + var failures: Map>? = null + +) + + +@JsonClass(generateAdapter = true) +data class UploadResponseFailure( + @Json(name = "status") + val status: Int, + @Json(name = "errCode") + val errCode: String, + @Json(name = "message") + val message: String +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt new file mode 100644 index 0000000000..0f10ba18b7 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignatureQueryBuilder.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.model.rest + +import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo +import im.vector.matrix.android.internal.crypto.model.MXKeysObject + +/** + * Helper class to build CryptoApi#uploadSignatures params + */ +data class UploadSignatureQueryBuilder( + private val deviceInfoList: ArrayList = ArrayList(), + private val signingKeyInfoList: ArrayList = ArrayList() +) { + + fun withDeviceInfo(deviceInfo: MXDeviceInfo) = apply { + deviceInfoList.add(deviceInfo) + } + + fun withSigningKeyInfo(info: CrossSigningKeyInfo) = apply { + signingKeyInfoList.add(info) + } + + fun build(): Map> { + val map = HashMap>() + + val usersList = (deviceInfoList.map { it.userId } + signingKeyInfoList.mapNotNull { it.userId }).distinct() + + usersList.forEach { userID -> + val userMap = HashMap() + deviceInfoList.filter { it.userId == userID }.forEach { deviceInfo -> + userMap[deviceInfo.deviceId] = deviceInfo.toDeviceKeys() + } + signingKeyInfoList.filter { it.userId == userID }.forEach { keyInfo -> + keyInfo.unpaddedBase64PublicKey?.let { base64Key -> + userMap[base64Key] = keyInfo + } + } + map[userID] = userMap + } + + return map + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignaturesBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignaturesBody.kt new file mode 100644 index 0000000000..113a26aff9 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSignaturesBody.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class UploadSignaturesBody( + @Json(name = "master_key") + val masterKey: CrossSigningKeyInfo? = null, + + @Json(name = "self_signing_key") + val selfSigningKey: CrossSigningKeyInfo? = null, + + @Json(name = "user_signing_key") + val userSigningKey: CrossSigningKeyInfo? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSigningKeysBody.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSigningKeysBody.kt new file mode 100644 index 0000000000..ea7934dabc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UploadSigningKeysBody.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.auth.registration.AuthParams + +@JsonClass(generateAdapter = true) +internal data class UploadSigningKeysBody( + @Json(name = "master_key") + val masterKey: CrossSigningKeyInfo? = null, + + @Json(name = "self_signing_key") + val selfSigningKey: CrossSigningKeyInfo? = null, + + @Json(name = "user_signing_key") + val userSigningKey: CrossSigningKeyInfo? = null, + + @Json(name = "auth") + val auth: UserPasswordAuth? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UserPasswordAuth.kt similarity index 86% rename from matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt rename to matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UserPasswordAuth.kt index 53ba4179eb..0945de030a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/DeleteDeviceAuth.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/UserPasswordAuth.kt @@ -1,5 +1,6 @@ /* * Copyright 2016 OpenMarket Ltd + * Copyright 2020 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +18,13 @@ package im.vector.matrix.android.internal.crypto.model.rest import com.squareup.moshi.Json import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.auth.data.LoginFlowTypes /** * This class provides the authentication data to delete a device */ @JsonClass(generateAdapter = true) -internal data class DeleteDeviceAuth( +data class UserPasswordAuth( // device device session id @Json(name = "session") @@ -30,7 +32,7 @@ internal data class DeleteDeviceAuth( // registration information @Json(name = "type") - var type: String? = null, + var type: String? = LoginFlowTypes.PASSWORD, @Json(name = "user") var user: String? = null, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/XSigningKeys.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/XSigningKeys.kt new file mode 100644 index 0000000000..56043ca521 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/model/rest/XSigningKeys.kt @@ -0,0 +1,20 @@ +package im.vector.matrix.android.internal.crypto.model.rest + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import im.vector.matrix.android.internal.crypto.model.MXKeysObject + +@JsonClass(generateAdapter = true) +data class XSigningKeys( + @Json(name = "user_id") + override val userId: String, + + @Json(name = "usage") + val usage: List, + + @Json(name = "keys") + override val keys: Map, + + @Json(name = "signatures") + override val signatures: Map>? +) : MXKeysObject diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt index 4e8dfcb2b0..502778c82c 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/IMXCryptoStore.kt @@ -17,12 +17,14 @@ package im.vector.matrix.android.internal.crypto.store +import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper +import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.store.db.model.KeysBackupDataEntity import org.matrix.olm.OlmAccount @@ -163,7 +165,7 @@ internal interface IMXCryptoStore { * @param userId the user's id. * @return the device */ - fun getUserDevice(deviceId: String, userId: String): MXDeviceInfo? + fun getUserDevice(userId: String, deviceId: String): MXDeviceInfo? /** * Retrieve a device by its identity key. @@ -181,6 +183,11 @@ internal interface IMXCryptoStore { */ fun storeUserDevices(userId: String, devices: Map?) + + fun storeUserCrossSigningKeys(userId: String, masterKey: CrossSigningKeyInfo?, + selfSigningKey: CrossSigningKeyInfo?, + userSigningKey: CrossSigningKeyInfo?) + /** * Retrieve the known devices for a user. * @@ -381,4 +388,24 @@ internal interface IMXCryptoStore { fun addNewSessionListener(listener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener) + + + //============================================= + // CROSS SIGNING + //============================================= + + /** + * Gets the current crosssigning info + */ + fun getMyCrossSigningInfo() : MXCrossSigningInfo? + fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) + + + fun getCrossSigningInfo(userId: String) : MXCrossSigningInfo? + fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) + + + fun getCrossSigningPrivateKeys() : PrivateKeysInfo? + + fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt new file mode 100644 index 0000000000..e2add0beff --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/PrivateKeysInfo.kt @@ -0,0 +1,7 @@ +package im.vector.matrix.android.internal.crypto.store + +data class PrivateKeysInfo( + val master: String? = null, + val selfSigned: String? = null, + val user: String? = null +) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt index c194a400ce..3371208640 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStore.kt @@ -17,21 +17,26 @@ package im.vector.matrix.android.internal.crypto.store.db import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest import im.vector.matrix.android.internal.crypto.NewSessionListener import im.vector.matrix.android.internal.crypto.OutgoingRoomKeyRequest import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper +import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo import im.vector.matrix.android.internal.crypto.model.rest.RoomKeyRequestBody import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore +import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo import im.vector.matrix.android.internal.crypto.store.db.model.* import im.vector.matrix.android.internal.crypto.store.db.query.delete +import im.vector.matrix.android.internal.crypto.store.db.query.get import im.vector.matrix.android.internal.crypto.store.db.query.getById import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate import im.vector.matrix.android.internal.session.SessionScope import io.realm.Realm import io.realm.RealmConfiguration +import io.realm.RealmList import io.realm.Sort import io.realm.kotlin.where import org.matrix.olm.OlmAccount @@ -187,7 +192,7 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati } } - override fun getUserDevice(deviceId: String, userId: String): MXDeviceInfo? { + override fun getUserDevice(userId: String, deviceId: String): MXDeviceInfo? { return doRealmQueryAndCopy(realmConfiguration) { it.where() .equalTo(DeviceInfoEntityFields.PRIMARY_KEY, DeviceInfoEntity.createPrimaryKey(userId, deviceId)) @@ -231,6 +236,73 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati } } + override fun storeUserCrossSigningKeys(userId: String, + masterKey: CrossSigningKeyInfo?, + selfSigningKey: CrossSigningKeyInfo?, + userSigningKey: CrossSigningKeyInfo?) { + doRealmTransaction(realmConfiguration) { realm -> + UserEntity.getOrCreate(realm, userId) + .let { userEntity -> + if (masterKey == null || selfSigningKey == null) { + // The user has disabled cross signing? + userEntity.crossSigningInfoEntity?.deleteFromRealm() + userEntity.crossSigningInfoEntity = null + } else { + CrossSigningInfoEntity.getOrCreate(realm, userId).let { signingInfo -> + // What should we do if we detect a change of the keys? + + val existingMaster = signingInfo.getMasterKey() + if (existingMaster != null && existingMaster.publicKeyBase64 == masterKey.unpaddedBase64PublicKey) { + //update signatures? + existingMaster.putSignatures(masterKey.signatures) + existingMaster.usages = masterKey.usages?.toTypedArray()?.let { RealmList(*it) } + ?: RealmList() + } else { + val keyEntity = realm.createObject(KeyInfoEntity::class.java).apply { + this.publicKeyBase64 = masterKey.unpaddedBase64PublicKey + this.usages = masterKey.usages?.toTypedArray()?.let { RealmList(*it) } + ?: RealmList() + this.putSignatures(masterKey.signatures) + } + signingInfo.setMasterKey(keyEntity) + } + + val existingSelfSigned = signingInfo.getSelfSignedKey() + if (existingSelfSigned != null && existingSelfSigned.publicKeyBase64 == selfSigningKey.unpaddedBase64PublicKey) { + //update signatures? + existingSelfSigned.putSignatures(selfSigningKey.signatures) + existingSelfSigned.usages = selfSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } + ?: RealmList() + } else { + val keyEntity = realm.createObject(KeyInfoEntity::class.java).apply { + this.publicKeyBase64 = selfSigningKey.unpaddedBase64PublicKey + this.usages = selfSigningKey.usages?.toTypedArray()?.let { RealmList(*it) } + ?: RealmList() + this.putSignatures(selfSigningKey.signatures) + } + signingInfo.setSelfSignedKey(keyEntity) + } + + userEntity.crossSigningInfoEntity = signingInfo + } + } + } + } + + } + + override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { + return doRealmQueryAndCopy(realmConfiguration) { realm -> + realm.where().findFirst() + }?.let { + PrivateKeysInfo( + master = it.xSignMasterPrivateKey, + selfSigned = it.xSignSelfSignedPrivateKey, + user = it.xSignUserPrivateKey + ) + } + } + override fun getUserDevices(userId: String): Map? { return doRealmQueryAndCopy(realmConfiguration) { it.where() @@ -731,4 +803,82 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati } .toMutableList() } + + /* ========================================================================================== + * Cross Signing + * ========================================================================================== */ + override fun getMyCrossSigningInfo(): MXCrossSigningInfo? { + return doRealmQueryAndCopy(realmConfiguration) { + it.where().findFirst() + }?.userId?.let { + getCrossSigningInfo(it) + } + } + + override fun setMyCrossSigningInfo(info: MXCrossSigningInfo?) { + doRealmQueryAndCopy(realmConfiguration) { + it.where().findFirst() + }?.userId?.let { + setCrossSigningInfo(it, info) + } + } + + override fun setUserKeysAsTrusted(userId: String, trusted: Boolean) { + doRealmTransaction(realmConfiguration) { realm -> + realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) + .findFirst() + ?.isTrusted = trusted + } + } + + override fun getCrossSigningInfo(userId: String): MXCrossSigningInfo? { + return doRealmQueryAndCopy(realmConfiguration) { realm -> + realm.where(CrossSigningInfoEntity::class.java) + .equalTo(CrossSigningInfoEntityFields.USER_ID, userId) + .findFirst() + }?.let { xsignInfo -> + MXCrossSigningInfo( + userId = userId, + crossSigningKeys = xsignInfo.crossSigningKeys.mapNotNull { + val pubKey = it.publicKeyBase64 ?: return@mapNotNull null + CrossSigningKeyInfo( + userId = userId, + keys = mapOf("ed25519:$pubKey" to pubKey), + usages = it.usages.map { it }, + signatures = it.getSignatures() + + ) + }, + isTrusted = xsignInfo.isTrusted + ) + } + } + + override fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?) { + doRealmTransaction(realmConfiguration) { realm -> + + var existing = CrossSigningInfoEntity.get(realm, userId) + if (info == null) { + // Delete known if needed + existing?.deleteFromRealm() + // TODO notify, we might need to untrust things? + } else { + // Just override existing, caller should check and untrust id needed + existing = CrossSigningInfoEntity.getOrCreate(realm, userId) + val xkeys = RealmList() + info.crossSigningKeys.forEach { info -> + xkeys.add( + realm.createObject(KeyInfoEntity::class.java).also { keyInfoEntity -> + keyInfoEntity.publicKeyBase64 = info.unpaddedBase64PublicKey + keyInfoEntity.usages = info.usages?.let { RealmList(*it.toTypedArray()) } + ?: RealmList() + keyInfoEntity.putSignatures(info.signatures) + } + ) + } + existing.crossSigningKeys = xkeys + } + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt index 7c6fae88e8..52473a48e2 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreMigration.kt @@ -16,15 +16,50 @@ package im.vector.matrix.android.internal.crypto.store.db +import im.vector.matrix.android.internal.crypto.store.db.model.* +import im.vector.matrix.android.internal.crypto.store.db.model.KeyInfoEntity import io.realm.DynamicRealm import io.realm.RealmMigration import timber.log.Timber internal object RealmCryptoStoreMigration : RealmMigration { - const val CRYPTO_STORE_SCHEMA_VERSION = 0L + // Version 1L added Cross Signing info persistence + const val CRYPTO_STORE_SCHEMA_VERSION = 1L override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { Timber.v("Migrating Realm Crypto from $oldVersion to $newVersion") + + if (oldVersion <= 0) { + Timber.d("Step 0 -> 1") + Timber.d("Create KeyInfoEntity") + + val keyInfoEntitySchema = realm.schema.create("KeyInfoEntity") + .addField(KeyInfoEntityFields.PUBLIC_KEY_BASE64, String::class.java) + .addField(KeyInfoEntityFields.SIGNATURES, String::class.java) + .addRealmListField(KeyInfoEntityFields.USAGES.`$`, String::class.java) + + + Timber.d("Create CrossSigningInfoEntity") + + val crossSigningInfoSchema = realm.schema.create("CrossSigningInfoEntity") + .addField(CrossSigningInfoEntityFields.USER_ID, String::class.java) + .addField(CrossSigningInfoEntityFields.IS_TRUSTED, Boolean::class.java) + .addPrimaryKey(CrossSigningInfoEntityFields.USER_ID) + .addRealmListField(CrossSigningInfoEntityFields.CROSS_SIGNING_KEYS.`$`, keyInfoEntitySchema) + + + Timber.d("Updating UserEntity table") + realm.schema.get("UserEntity") + ?.addRealmObjectField(UserEntityFields.CROSS_SIGNING_INFO_ENTITY.`$`, crossSigningInfoSchema) + + + Timber.d("Updating CryptoMetadataEntity table") + realm.schema.get("CryptoMetadataEntity") + ?.addField(CryptoMetadataEntityFields.X_SIGN_MASTER_PRIVATE_KEY, String::class.java) + ?.addField(CryptoMetadataEntityFields.X_SIGN_USER_PRIVATE_KEY, String::class.java) + ?.addField(CryptoMetadataEntityFields.X_SIGN_SELF_SIGNED_PRIVATE_KEY, String::class.java) + + } } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt index 998274020a..692a404d99 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/RealmCryptoStoreModule.kt @@ -32,6 +32,8 @@ import io.realm.annotations.RealmModule OlmInboundGroupSessionEntity::class, OlmSessionEntity::class, OutgoingRoomKeyRequestEntity::class, - UserEntity::class + UserEntity::class, + KeyInfoEntity::class, + CrossSigningInfoEntity::class ]) internal class RealmCryptoStoreModule diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CrossSigningInfoEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CrossSigningInfoEntity.kt new file mode 100644 index 0000000000..8d0daf66fc --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CrossSigningInfoEntity.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.store.db.model + +import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo +import io.realm.RealmList +import io.realm.RealmObject +import io.realm.annotations.PrimaryKey + +internal open class CrossSigningInfoEntity( + @PrimaryKey + var userId: String? = null, + var crossSigningKeys: RealmList = RealmList(), + var isTrusted: Boolean = false +) : RealmObject() { + + companion object + + fun getMasterKey() = crossSigningKeys.firstOrNull { it.usages.contains(CrossSigningKeyInfo.KeyUsage.MASTER.value) } + + fun setMasterKey(info: KeyInfoEntity?) { + crossSigningKeys + .filter { it.usages.contains(CrossSigningKeyInfo.KeyUsage.MASTER.value) } + .forEach { crossSigningKeys.remove(it) } + info?.let { crossSigningKeys.add(it) } + } + + fun getSelfSignedKey() = crossSigningKeys.firstOrNull { it.usages.contains(CrossSigningKeyInfo.KeyUsage.SELF_SIGNING.value) } + + fun setSelfSignedKey(info: KeyInfoEntity?) { + crossSigningKeys + .filter { it.usages.contains(CrossSigningKeyInfo.KeyUsage.SELF_SIGNING.value) } + .forEach { crossSigningKeys.remove(it) } + info?.let { crossSigningKeys.add(it) } + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CryptoMetadataEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CryptoMetadataEntity.kt index eae202f966..beb7cfd797 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CryptoMetadataEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/CryptoMetadataEntity.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.store.db.model import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +import io.realm.Realm import io.realm.RealmObject import io.realm.annotations.PrimaryKey import org.matrix.olm.OlmAccount @@ -34,7 +35,13 @@ internal open class CryptoMetadataEntity( // Settings for blacklisting unverified devices. var globalBlacklistUnverifiedDevices: Boolean = false, // The keys backup version currently used. Null means no backup. - var backupVersion: String? = null + var backupVersion: String? = null, + + var xSignMasterPrivateKey: String? = null, + var xSignUserPrivateKey: String? = null, + var xSignSelfSignedPrivateKey: String? = null + +// var crossSigningInfoEntity: CrossSigningInfoEntity? = null ) : RealmObject() { // Deserialize data diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt new file mode 100644 index 0000000000..6e2418f37c --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/KeyInfoEntity.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.store.db.model + +import im.vector.matrix.android.internal.crypto.store.db.deserializeFromRealm +import im.vector.matrix.android.internal.crypto.store.db.serializeForRealm +import io.realm.RealmList +import io.realm.RealmObject + + +internal open class KeyInfoEntity( + var publicKeyBase64: String? = null, +// var isTrusted: Boolean = false, + var usages: RealmList = RealmList(), + /** + * The signature of this MXDeviceInfo. + * A map from "" to a map from ":" to "" + */ + var signatures: String? = null +) : RealmObject() { + + // Deserialize data + fun getSignatures(): Map>? { + return deserializeFromRealm(signatures) + } + + // Serialize data + fun putSignatures(deviceInfo: Map>?) { + signatures = serializeForRealm(deviceInfo) + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/UserEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/UserEntity.kt index 27cd7fe226..6b502c1403 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/UserEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/model/UserEntity.kt @@ -20,9 +20,11 @@ import io.realm.RealmList import io.realm.RealmObject import io.realm.annotations.PrimaryKey -internal open class UserEntity(@PrimaryKey var userId: String? = null, - var devices: RealmList = RealmList(), - var deviceTrackingStatus: Int = 0) +internal open class UserEntity( + @PrimaryKey var userId: String? = null, + var devices: RealmList = RealmList(), + var crossSigningInfoEntity: CrossSigningInfoEntity? = null, + var deviceTrackingStatus: Int = 0) : RealmObject() { companion object diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CrossSigningInfoEntityQueries.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CrossSigningInfoEntityQueries.kt new file mode 100644 index 0000000000..0128611fce --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/store/db/query/CrossSigningInfoEntityQueries.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2020 New Vector 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 im.vector.matrix.android.internal.crypto.store.db.query + +import im.vector.matrix.android.internal.crypto.store.db.model.CrossSigningInfoEntity +import im.vector.matrix.android.internal.crypto.store.db.model.UserEntityFields +import io.realm.Realm +import io.realm.kotlin.createObject +import io.realm.kotlin.where + +internal fun CrossSigningInfoEntity.Companion.getOrCreate(realm: Realm, userId: String): CrossSigningInfoEntity { + return realm.where() + .equalTo(UserEntityFields.USER_ID, userId) + .findFirst() + ?: realm.createObject(userId) +} + +internal fun CrossSigningInfoEntity.Companion.get(realm: Realm, userId: String): CrossSigningInfoEntity? { + return realm.where() + .equalTo(UserEntityFields.USER_ID, userId) + .findFirst() +} + diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt index 19e0f6efb5..4ebdac2635 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/DeleteDeviceWithUserPasswordTask.kt @@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.crypto.tasks import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.crypto.api.CryptoApi -import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceAuth +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.network.executeRequest @@ -44,7 +44,7 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor( return executeRequest(eventBus) { apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams() .apply { - deleteDeviceAuth = DeleteDeviceAuth() + userPasswordAuth = UserPasswordAuth() .apply { type = LoginFlowTypes.PASSWORD session = params.authSession diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSignaturesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSignaturesTask.kt new file mode 100644 index 0000000000..6a38a69dda --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSignaturesTask.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2019 New Vector 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 im.vector.matrix.android.internal.crypto.tasks + +import im.vector.matrix.android.internal.crypto.api.CryptoApi +import im.vector.matrix.android.internal.crypto.model.MXKeysObject +import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + + +internal interface UploadSignaturesTask : Task { + data class Params( + val signatures: Map> + ) +} + +internal class DefaultUploadSignaturesTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : UploadSignaturesTask { + + override suspend fun execute(params: UploadSignaturesTask.Params): SignatureUploadResponse { + return executeRequest(eventBus) { + apiCall = cryptoApi.uploadSignatures(params.signatures) + } + } + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt new file mode 100644 index 0000000000..bf454ac903 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/UploadSigningKeysTask.kt @@ -0,0 +1,92 @@ +/* + * Copyright 2019 New Vector 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 im.vector.matrix.android.internal.crypto.tasks + +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse +import im.vector.matrix.android.internal.crypto.api.CryptoApi +import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo +import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse +import im.vector.matrix.android.internal.crypto.model.rest.UploadSigningKeysBody +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import im.vector.matrix.android.internal.di.MoshiProvider +import im.vector.matrix.android.internal.network.executeRequest +import im.vector.matrix.android.internal.task.Task +import org.greenrobot.eventbus.EventBus +import javax.inject.Inject + + +internal interface UploadSigningKeysTask : Task { + data class Params( + // the device keys to send. + val masterKey: CrossSigningKeyInfo, + // the one-time keys to send. + val userKey: CrossSigningKeyInfo, + // the explicit device_id to use for upload (default is to use the same as that used during auth). + val selfSignedKey: CrossSigningKeyInfo, + val userPasswordAuth: UserPasswordAuth? + ) +} + +internal class DefaultUploadSigningKeysTask @Inject constructor( + private val cryptoApi: CryptoApi, + private val eventBus: EventBus +) : UploadSigningKeysTask { + override suspend fun execute(params: UploadSigningKeysTask.Params): KeysQueryResponse { + + val uploadQuery = UploadSigningKeysBody( + masterKey = params.masterKey, + userSigningKey = params.userKey, + selfSigningKey = params.selfSignedKey, + auth = params.userPasswordAuth.takeIf { params.userPasswordAuth?.session != null } + ) + try { + // Make a first request to start user-interactive authentication + return executeRequest(eventBus) { + apiCall = cryptoApi.uploadSigningKeys(uploadQuery) + } + } catch (throwable: Throwable) { + if (throwable is Failure.OtherServerError + && throwable.httpCode == 401 + && params.userPasswordAuth != null + /* Avoid infinite loop */ + && params.userPasswordAuth.session.isNullOrEmpty() + ) { + try { + MoshiProvider.providesMoshi() + .adapter(RegistrationFlowResponse::class.java) + .fromJson(throwable.errorBody) + } catch (e: Exception) { + null + }?.let { + // Retry with authentication + return executeRequest(eventBus) { + apiCall = cryptoApi.uploadSigningKeys( + uploadQuery.copy(auth = params.userPasswordAuth.copy(session = it.session)) + ) + } + } + } + // Other error + throw throwable + + } + + } + + +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt index 349d6a79ca..901770ca07 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultIncomingSASVerificationTransaction.kt @@ -108,7 +108,7 @@ internal class DefaultIncomingSASVerificationTransaction( } // Bob’s device ensures that it has a copy of Alice’s device key. - val mxDeviceInfo = cryptoStore.getUserDevice(deviceId = otherDeviceId!!, userId = otherUserId) + val mxDeviceInfo = cryptoStore.getUserDevice(userId = otherUserId, deviceId = otherDeviceId!!) if (mxDeviceInfo?.fingerprint() == null) { Timber.e("## SAS Failed to find device key ") diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt index 8fa91e2f4a..ec8ebb2e3e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/verification/DefaultSasVerificationService.kt @@ -21,6 +21,7 @@ import android.os.Looper import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.auth.data.Credentials +import im.vector.matrix.android.api.auth.data.sessionId import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.sas.* import im.vector.matrix.android.api.session.events.model.Event diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt index 6f1593bc08..758634c391 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/send/EncryptEventWorker.kt @@ -20,6 +20,7 @@ import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.squareup.moshi.JsonClass +import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.events.model.Event @@ -75,6 +76,10 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters) localMutableContent.remove(it) } + crypto.downloadKeys(listOf("@testxsigningvfe:matrix.org"), true, object : MatrixCallback { + + }) + var error: Throwable? = null var result: MXEncryptEventContentResult? = null try { diff --git a/matrix-sdk-android/src/main/res/xml/network_security_config.xml b/matrix-sdk-android/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000000..e40c61c229 --- /dev/null +++ b/matrix-sdk-android/src/main/res/xml/network_security_config.xml @@ -0,0 +1,16 @@ + + + + + + + + + + localhost + 127.0.0.1 + + 10.0.2.2 + + + diff --git a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt index 6624a05985..ef680e800a 100644 --- a/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt +++ b/vector/src/debug/java/im/vector/riotx/features/debug/DebugMenuActivity.kt @@ -21,17 +21,37 @@ import android.app.NotificationManager import android.content.Context import android.content.Intent import android.os.Build +import android.text.InputType +import android.widget.EditText +import androidx.appcompat.app.AlertDialog import androidx.core.app.NotificationCompat import androidx.core.app.Person import butterknife.OnClick +import im.vector.matrix.android.api.MatrixCallback +import im.vector.matrix.android.api.failure.Failure +import im.vector.matrix.android.internal.auth.data.LoginFlowTypes +import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.riotx.R +import im.vector.riotx.core.di.ActiveSessionHolder +import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.platform.VectorBaseActivity import im.vector.riotx.features.debug.sas.DebugSasEmojiActivity +import javax.inject.Inject class DebugMenuActivity : VectorBaseActivity() { override fun getLayoutRes() = R.layout.activity_debug_menu + + @Inject + lateinit var activeSessionHolder: ActiveSessionHolder + + override fun injectWith(injector: ScreenComponent) { + injector.inject(this) + } + @OnClick(R.id.debug_test_text_view_link) fun testTextViewLink() { startActivity(Intent(this, TestLinkifyActivity::class.java)) @@ -140,4 +160,60 @@ class DebugMenuActivity : VectorBaseActivity() { fun testCrash() { throw RuntimeException("Application crashed from user demand") } + + @OnClick(R.id.debug_initialise_xsigning) + fun testXSigning() { + activeSessionHolder.getActiveSession().getCrossSigningService().initializeCrossSigning(null, object : MatrixCallback { + + override fun onFailure(failure: Throwable) { + if (failure is Failure.OtherServerError + && failure.httpCode == 401 + ) { + try { + MoshiProvider.providesMoshi() + .adapter(RegistrationFlowResponse::class.java) + .fromJson(failure.errorBody) + } catch (e: Exception) { + null + }?.let { + // Retry with authentication + if (it.flows?.any { it.stages?.contains(LoginFlowTypes.PASSWORD) == true } == true) { + // Ask for password + val inflater = this@DebugMenuActivity.layoutInflater + val layout = inflater.inflate(R.layout.dialog_base_edit_text, null) + + val input = layout.findViewById(R.id.edit_text).also { + it.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD + } + + val activeSession = activeSessionHolder.getActiveSession() + AlertDialog.Builder(this@DebugMenuActivity) + .setTitle("Confirm password") + .setView(layout) + .setPositiveButton(R.string.ok) { _, _ -> + val pass = input.text.toString() + + activeSession.getCrossSigningService().initializeCrossSigning( + UserPasswordAuth( + session = it.session, + user = activeSession.myUserId, + password = pass + ) + ) + } + .setNegativeButton(R.string.cancel, null) + .show() + } else { + // can't do this from here + AlertDialog.Builder(this@DebugMenuActivity) + .setTitle(R.string.dialog_title_error) + .setMessage("You cannot do that from mobile") + .setPositiveButton(R.string.ok, null) + .show() + } + } + } + } + }) + } } diff --git a/vector/src/debug/res/layout/activity_debug_menu.xml b/vector/src/debug/res/layout/activity_debug_menu.xml index 01ab061f6a..5d18121f5c 100644 --- a/vector/src/debug/res/layout/activity_debug_menu.xml +++ b/vector/src/debug/res/layout/activity_debug_menu.xml @@ -61,6 +61,13 @@ android:layout_height="wrap_content" android:text="Crash the app" /> + + diff --git a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt index 2680bd15c1..4503143052 100644 --- a/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/riotx/core/di/ScreenComponent.kt @@ -26,6 +26,7 @@ import im.vector.riotx.core.preference.UserAvatarPreference import im.vector.riotx.features.MainActivity import im.vector.riotx.features.crypto.keysbackup.settings.KeysBackupManageActivity import im.vector.riotx.features.crypto.verification.VerificationBottomSheet +import im.vector.riotx.features.debug.DebugMenuActivity import im.vector.riotx.features.home.HomeActivity import im.vector.riotx.features.home.HomeModule import im.vector.riotx.features.createdirect.CreateDirectRoomActivity @@ -139,6 +140,8 @@ interface ScreenComponent { fun inject(permalinkHandlerActivity: PermalinkHandlerActivity) + fun inject(activity: DebugMenuActivity) + @Component.Factory interface Factory { fun create(vectorComponent: VectorComponent, diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 9ca1feae21..4701eecb7e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -50,6 +50,7 @@ import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap +import im.vector.riotx.BuildConfig import im.vector.riotx.R import im.vector.riotx.core.extensions.postLiveEvent import im.vector.riotx.core.platform.VectorViewModel @@ -188,7 +189,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro is RoomDetailAction.AcceptVerificationRequest -> handleAcceptVerification(action) is RoomDetailAction.DeclineVerificationRequest -> handleDeclineVerification(action) is RoomDetailAction.RequestVerification -> handleRequestVerification(action) - is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) + is RoomDetailAction.ResumeVerification -> handleResumeRequestVerification(action) } } @@ -809,7 +810,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro private fun handleAcceptVerification(action: RoomDetailAction.AcceptVerificationRequest) { Timber.v("## SAS handleAcceptVerification ${action.otherUserId}, roomId:${room.roomId}, txId:${action.transactionId}") if (session.getSasVerificationService().readyPendingVerificationInDMs(action.otherUserId, room.roomId, - action.transactionId)) { + action.transactionId)) { _requestLiveData.postValue(LiveEvent(Success(action))) } }