suspend verif WIP

This commit is contained in:
Valere 2022-11-16 09:12:54 +01:00
commit cf366f7a9c
255 changed files with 16586 additions and 6989 deletions

8
.gitignore vendored
View File

@ -11,6 +11,9 @@
/benchmark-out /benchmark-out
/captures /captures
.externalNativeBuild .externalNativeBuild
rust-sdk/target/*
rust-sdk/src/uniffi/*
Cargo.lock
/tmp /tmp
/fastlane/private /fastlane/private
@ -23,3 +26,8 @@
/yarn.lock /yarn.lock
/node_modules /node_modules
**/out/failures **/out/failures
# For manual dependency to rust crypto sdk
matrix-sdk-android/src/main/jniLibs/
matrix-sdk-android/libs/crypto-android-release.aar

View File

@ -121,6 +121,15 @@ allprojects {
groups.jcenter.group.each { includeGroup it } groups.jcenter.group.each { includeGroup it }
} }
} }
maven {
url 'https://s01.oss.sonatype.org/content/repositories/snapshots'
content {
groups.mavenSnapshots.regex.each { includeGroupByRegex it }
groups.mavenSnapshots.group.each { includeGroup it }
}
}
} }
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all { tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {

View File

@ -1,5 +1,5 @@
ext.groups = [ ext.groups = [
jitpack : [ jitpack : [
regex: [ regex: [
], ],
group: [ group: [
@ -15,7 +15,7 @@ ext.groups = [
'com.github.Zhuinden', 'com.github.Zhuinden',
] ]
], ],
jitsi : [ jitsi : [
regex: [ regex: [
], ],
group: [ group: [
@ -24,7 +24,7 @@ ext.groups = [
'org.webkit', 'org.webkit',
] ]
], ],
google : [ google : [
regex: [ regex: [
'androidx\\..*', 'androidx\\..*',
'com\\.android\\.tools\\..*', 'com\\.android\\.tools\\..*',
@ -45,6 +45,13 @@ ext.groups = [
'com.vanniktech', 'com.vanniktech',
] ]
], ],
mavenSnapshots: [
regex: [
],
group: [
'org.matrix.rustcomponents'
]
],
mavenCentral: [ mavenCentral: [
regex: [ regex: [
], ],
@ -205,6 +212,7 @@ ext.groups = [
'org.jvnet.staxex', 'org.jvnet.staxex',
'org.maplibre.gl', 'org.maplibre.gl',
'org.matrix.android', 'org.matrix.android',
'org.matrix.rustcomponents',
'org.mockito', 'org.mockito',
'org.mongodb', 'org.mongodb',
'org.objenesis', 'org.objenesis',
@ -224,7 +232,7 @@ ext.groups = [
'xml-apis', 'xml-apis',
] ]
], ],
jcenter : [ jcenter : [
regex: [ regex: [
], ],
group: [ group: [

View File

@ -74,7 +74,7 @@ android {
testOptions { testOptions {
// Comment to run on Android 12 // Comment to run on Android 12
// execution 'ANDROIDX_TEST_ORCHESTRATOR' execution 'ANDROIDX_TEST_ORCHESTRATOR'
} }
buildTypes { buildTypes {
@ -110,6 +110,7 @@ android {
// Disabled for now, there are too many errors. Could be handled in another dedicated PR // Disabled for now, there are too many errors. Could be handled in another dedicated PR
// '-Xexplicit-api=strict', // or warning // '-Xexplicit-api=strict', // or warning
"-opt-in=kotlin.RequiresOptIn", "-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.OptIn",
// Opt in for kotlinx.coroutines.FlowPreview // Opt in for kotlinx.coroutines.FlowPreview
"-opt-in=kotlinx.coroutines.FlowPreview", "-opt-in=kotlinx.coroutines.FlowPreview",
] ]
@ -160,12 +161,25 @@ static def gitRevisionDate() {
return cmd.execute().text.trim() return cmd.execute().text.trim()
} }
configurations.all {
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
}
dependencies { dependencies {
implementation libs.jetbrains.coroutinesCore implementation libs.jetbrains.coroutinesCore
implementation libs.jetbrains.coroutinesAndroid implementation libs.jetbrains.coroutinesAndroid
implementation 'org.matrix.rustcomponents:crypto-android:0.2.1-SNAPSHOT'
//implementation files('libs/crypto-android-release.aar')
// implementation(name: 'crypto-android-release', ext: 'aar')
implementation 'net.java.dev.jna:jna:5.10.0@aar'
// implementation libs.androidx.appCompat
implementation libs.androidx.core implementation libs.androidx.core
rustCryptoImplementation libs.androidx.lifecycleLivedata
// Lifecycle // Lifecycle
implementation libs.androidx.lifecycleCommon implementation libs.androidx.lifecycleCommon
implementation libs.androidx.lifecycleProcess implementation libs.androidx.lifecycleProcess

View File

@ -19,13 +19,12 @@ package org.matrix.android.sdk
import android.content.Context import android.content.Context
import androidx.test.core.app.ApplicationProvider import androidx.test.core.app.ApplicationProvider
import org.junit.Rule import org.junit.Rule
import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.test.shared.createTimberTestRule import org.matrix.android.sdk.test.shared.createTimberTestRule
interface InstrumentedTest { interface InstrumentedTest {
@Rule // @Rule
fun retryTestRule() = RetryTestRule(3) // fun retryTestRule() = RetryTestRule(3)
@Rule @Rule
fun timberTestRule() = createTimberTestRule() fun timberTestRule() = createTimberTestRule()

View File

@ -85,13 +85,15 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) { internal fun runCryptoTest(context: Context, cryptoConfig: MXCryptoConfig? = null, autoSignoutOnClose: Boolean = true, block: suspend CoroutineScope.(CryptoTestHelper, CommonTestHelper) -> Unit) {
val testHelper = CommonTestHelper(context, cryptoConfig) val testHelper = CommonTestHelper(context, cryptoConfig)
val cryptoTestHelper = CryptoTestHelper(testHelper) val cryptoTestHelper = CryptoTestHelper(testHelper)
return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis) { return try {
try { runTest(dispatchTimeoutMs = TestConstants.timeOutMillis * 2) {
withContext(Dispatchers.Default) { withContext(Dispatchers.Main) {
block(cryptoTestHelper, testHelper) block(cryptoTestHelper, testHelper)
} }
} finally { }
if (autoSignoutOnClose) { } finally {
if (autoSignoutOnClose) {
runBlocking {
testHelper.cleanUpOpenedSessions() testHelper.cleanUpOpenedSessions()
} }
} }
@ -250,7 +252,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
// not sure why it's taking so long :/ // not sure why it's taking so long :/
wrapWithTimeout(90_000) { wrapWithTimeout(90_000) {
Log.v("#E2E TEST", "${otherSession.myUserId} tries to join room $roomID") Log.v("#E2E TEST", "${otherSession.myUserId.take(10)} tries to join room $roomID")
try { try {
otherSession.roomService().joinRoom(roomID) otherSession.roomService().joinRoom(roomID)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) { } catch (ex: JoinRoomFailure.JoinedWithTimeout) {
@ -432,6 +434,31 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
} }
} }
private val backoff = listOf(500L, 1_000L, 1_000L, 3_000L, 3_000L, 5_000L)
suspend fun betterRetryPeriodically(
timeout: Long = TestConstants.timeOutMillis,
// we use on fail to let caller report a proper error that will show nicely in junit test result with correct line
// just call fail with your message
onFail: (() -> Unit)? = null,
predicate: suspend () -> Boolean,
) {
var backoffTry = 0
val now = System.currentTimeMillis()
while (!predicate()) {
Timber.w("## VALR Trial nb $backoffTry")
withContext(Dispatchers.IO) {
delay(backoff[backoffTry.coerceAtMost(backoff.size - 1)])
}
backoffTry++
if (System.currentTimeMillis() - now > timeout) {
Timber.w("## VALR Trial fail")
onFail?.invoke()
return
}
}
Timber.w("## VALR Trial success for")
}
suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T { suspend fun <T> waitForCallback(timeout: Long = TestConstants.timeOutMillis, block: (MatrixCallback<T>) -> Unit): T {
return wrapWithTimeout(timeout) { return wrapWithTimeout(timeout) {
suspendCoroutine { continuation -> suspendCoroutine { continuation ->

View File

@ -37,4 +37,10 @@ data class CryptoTestData(
testHelper.signOutAndClose(it) testHelper.signOutAndClose(it)
} }
} }
suspend fun initializeCrossSigning(testHelper: CryptoTestHelper) {
sessions.forEach {
testHelper.initializeCrossSigning(it)
}
}
} }

View File

@ -33,18 +33,19 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult import org.matrix.android.sdk.api.session.crypto.model.OlmDecryptionResult
import org.matrix.android.sdk.internal.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.internal.crypto.verification.OutgoingSasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.getRoomSummary
import org.matrix.android.sdk.api.session.room.failure.JoinRoomFailure
import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@ -52,7 +53,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams
import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner import org.matrix.android.sdk.api.session.securestorage.EmptyKeySigner
import org.matrix.android.sdk.api.session.securestorage.KeyRef import org.matrix.android.sdk.api.session.securestorage.KeyRef
import org.matrix.android.sdk.api.util.toBase64NoPadding import timber.log.Timber
import java.util.UUID import java.util.UUID
import kotlin.coroutines.Continuation import kotlin.coroutines.Continuation
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -121,6 +122,80 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession)) return CryptoTestData(aliceRoomId, listOf(aliceSession, bobSession))
} }
suspend fun inviteNewUsersAndWaitForThemToJoin(session: Session, roomId: String, usernames: List<String>): List<Session> {
val newSessions = usernames.map { username ->
testHelper.createAccount(username, SessionTestParams(true)).also {
it.cryptoService().enableKeyGossiping(false)
}
}
val room = session.getRoom(roomId)!!
Log.v("#E2E TEST", "accounts for ${usernames.joinToString(",") { it.take(10) }} created")
// we want to invite them in the room
newSessions.forEach { newSession ->
Log.v("#E2E TEST", "${session.myUserId.take(10)} invites ${newSession.myUserId.take(10)}")
room.membershipService().invite(newSession.myUserId)
}
// All user should accept invite
newSessions.forEach { newSession ->
waitForAndAcceptInviteInRoom(newSession, roomId)
Log.v("#E2E TEST", "${newSession.myUserId.take(10)} joined room $roomId")
}
ensureMembersHaveJoined(session, newSessions, roomId)
return newSessions
}
private suspend fun ensureMembersHaveJoined(session: Session, invitedUserSessions: List<Session>, roomId: String) {
testHelper.betterRetryPeriodically(
onFail = {
fail("Members ${invitedUserSessions.map { it.myUserId.take(10) }} should have join from the pov of ${session.myUserId.take(10)}")
}
) {
invitedUserSessions.map { invitedUserSession ->
session.roomService().getRoomMember(invitedUserSession.myUserId, roomId)?.membership?.also {
Log.v("#E2E TEST", "${invitedUserSession.myUserId.take(10)} membership is $it")
}
}.all {
it == Membership.JOIN
}
}
}
private suspend fun waitForAndAcceptInviteInRoom(session: Session, roomId: String) {
testHelper.betterRetryPeriodically(
onFail = {
fail("${session.myUserId} cannot see the invite from ${session.myUserId.take(10)}")
}
) {
val roomSummary = session.getRoomSummary(roomId)
(roomSummary != null && roomSummary.membership == Membership.INVITE).also {
if (it) {
Log.v("#E2E TEST", "${session.myUserId.take(10)} can see the invite from ${roomSummary?.inviterId}")
}
}
}
// not sure why it's taking so long :/
Log.v("#E2E TEST", "${session.myUserId.take(10)} tries to join room $roomId")
try {
session.roomService().joinRoom(roomId)
} catch (ex: JoinRoomFailure.JoinedWithTimeout) {
// it's ok we will wait after
}
Log.v("#E2E TEST", "${session.myUserId} waiting for join echo ...")
testHelper.betterRetryPeriodically(
onFail = {
fail("${session.myUserId.take(10)} cannot see the join echo for ${roomId}")
}
) {
val roomSummary = session.getRoomSummary(roomId)
roomSummary != null && roomSummary.membership == Membership.JOIN
}
}
/** /**
* @return Alice and Bob sessions * @return Alice and Bob sessions
*/ */
@ -189,7 +264,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
return MegolmBackupCreationInfo( return MegolmBackupCreationInfo(
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
authData = createFakeMegolmBackupAuthData(), authData = createFakeMegolmBackupAuthData(),
recoveryKey = "fake" recoveryKey = BackupUtils.recoveryKeyFromPassphrase("3cnTdW")!!
) )
} }
@ -221,7 +296,6 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
} }
suspend fun initializeCrossSigning(session: Session) { suspend fun initializeCrossSigning(session: Session) {
testHelper.waitForCallback<Unit> {
session.cryptoService().crossSigningService() session.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -234,9 +308,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
) )
) )
} }
}, it })
)
}
} }
/** /**
@ -272,16 +344,13 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
) )
// set up megolm backup // set up megolm backup
val creationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null)
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it) val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo)
}
val version = testHelper.waitForCallback<KeysVersion> {
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
}
// Save it for gossiping // Save it for gossiping
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version) session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret -> creationInfo.recoveryKey.toBase64().let { secret ->
ssssService.storeSecret( ssssService.storeSecret(
KEYBACKUP_SECRET_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME,
secret, secret,
@ -298,61 +367,78 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
val bobVerificationService = bob.cryptoService().verificationService() val bobVerificationService = bob.cryptoService().verificationService()
val localId = UUID.randomUUID().toString() val localId = UUID.randomUUID().toString()
aliceVerificationService.requestKeyVerificationInDMs( val requestID = aliceVerificationService.requestKeyVerificationInDMs(
localId = localId, localId = localId,
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
otherUserId = bob.myUserId, otherUserId = bob.myUserId,
roomId = roomId roomId = roomId
).transactionId ).transactionId
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
onFail = {
fail("Bob should see an incoming request from alice")
}
) {
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull { bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
it.requestInfo?.fromDevice == alice.sessionParams.deviceId it.otherDeviceId == alice.sessionParams.deviceId
} != null } != null
} }
val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first { val incomingRequest = bobVerificationService.getExistingVerificationRequests(alice.myUserId).first {
it.requestInfo?.fromDevice == alice.sessionParams.deviceId it.otherDeviceId == alice.sessionParams.deviceId
} }
bobVerificationService.readyPendingVerificationInDMs(listOf(VerificationMethod.SAS), alice.myUserId, roomId, incomingRequest.transactionId!!)
var requestID: String? = null Timber.v("#TEST Incoming request is $incomingRequest")
Timber.v("#TEST let bob ready the verification with SAS method")
bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS),
alice.myUserId,
incomingRequest.transactionId
)
// wait for it to be readied // wait for it to be readied
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId) onFail = {
.firstOrNull { it.localId == localId } fail("Alice should see the verification in ready state")
if (outgoingRequest?.isReady == true) { }
requestID = outgoingRequest.transactionId!! ) {
true val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID)
} else { outgoingRequest?.state == EVerificationState.Ready
false
}
} }
aliceVerificationService.beginKeyVerificationInDMs( Timber.v("#TEST let alice start the verification")
aliceVerificationService.startKeyVerification(
VerificationMethod.SAS, VerificationMethod.SAS,
requestID!!,
roomId,
bob.myUserId, bob.myUserId,
bob.sessionParams.credentials.deviceId!! requestID,
) )
// we should reach SHOW SAS on both // we should reach SHOW SAS on both
var alicePovTx: OutgoingSasVerificationTransaction? = null var alicePovTx: SasVerificationTransaction? = null
var bobPovTx: IncomingSasVerificationTransaction? = null var bobPovTx: SasVerificationTransaction? = null
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction onFail = {
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}") fail("Alice should should see a verification code")
alicePovTx?.state == VerificationTxState.ShortCodeReady }
) {
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID)
as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx id:${requestID} is ${alicePovTx?.state}")
alicePovTx?.getDecimalCodeRepresentation() != null
} }
// wait for alice to get the ready // wait for alice to get the ready
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction onFail = {
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}") fail("Bob should should see a verification code")
if (bobPovTx?.state == VerificationTxState.OnStarted) { }
bobPovTx?.performAccept() ) {
} bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID)
bobPovTx?.state == VerificationTxState.ShortCodeReady as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is ${bobPovTx?.state}")
// bobPovTx?.state == VerificationTxState.ShortCodeReady
bobPovTx?.getDecimalCodeRepresentation() != null
} }
assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation()) assertEquals("SAS code do not match", alicePovTx!!.getDecimalCodeRepresentation(), bobPovTx!!.getDecimalCodeRepresentation())
@ -360,11 +446,11 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
bobPovTx!!.userHasVerifiedShortCode() bobPovTx!!.userHasVerifiedShortCode()
alicePovTx!!.userHasVerifiedShortCode() alicePovTx!!.userHasVerifiedShortCode()
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId) alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
} }
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId) bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
} }
} }

View File

@ -27,10 +27,6 @@ import org.junit.runners.JUnit4
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams import org.matrix.android.sdk.api.session.room.model.create.CreateRoomParams
@ -217,15 +213,11 @@ class E2EShareKeysConfigTest : InstrumentedTest {
Log.v("#E2E TEST", "Create and start key backup for bob ...") Log.v("#E2E TEST", "Create and start key backup for bob ...")
val keysBackupService = aliceSession.cryptoService().keysBackupService() val keysBackupService = aliceSession.cryptoService().keysBackupService()
val keyBackupPassword = "FooBarBaz" val keyBackupPassword = "FooBarBaz"
val megolmBackupCreationInfo = commonTestHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) val version = keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
}
val version = commonTestHelper.waitForCallback<KeysVersion> {
keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
commonTestHelper.waitForCallback<Unit> { commonTestHelper.retryPeriodically {
keysBackupService.backupAllGroupSessions(null, it) keysBackupService.getTotalNumbersOfKeys() == keysBackupService.getTotalNumbersOfBackedUpKeys()
} }
// signout // signout
@ -235,20 +227,15 @@ class E2EShareKeysConfigTest : InstrumentedTest {
newAliceSession.cryptoService().enableShareKeyOnInvite(true) newAliceSession.cryptoService().enableShareKeyOnInvite(true)
newAliceSession.cryptoService().keysBackupService().let { kbs -> newAliceSession.cryptoService().keysBackupService().let { kbs ->
val keyVersionResult = commonTestHelper.waitForCallback<KeysVersionResult?> { val keyVersionResult = kbs.getVersion(version.version)
kbs.getVersion(version.version, it)
}
val importedResult = commonTestHelper.waitForCallback<ImportRoomKeysResult> { val importedResult = kbs.restoreKeyBackupWithPassword(
kbs.restoreKeyBackupWithPassword(
keyVersionResult!!, keyVersionResult!!,
keyBackupPassword, keyBackupPassword,
null, null,
null, null,
null, null,
it
) )
}
assertEquals(2, importedResult.totalNumberOfKeys) assertEquals(2, importedResult.totalNumberOfKeys)
} }

View File

@ -18,12 +18,7 @@ package org.matrix.android.sdk.internal.crypto
import android.util.Log import android.util.Log
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import org.amshove.kluent.fail import org.amshove.kluent.fail
import org.amshove.kluent.internal.assertEquals import org.amshove.kluent.internal.assertEquals
import org.junit.Assert import org.junit.Assert
@ -40,17 +35,6 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.internal.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.internal.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
@ -92,7 +76,6 @@ class E2eeSanityTests : InstrumentedTest {
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true) val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val e2eRoomID = cryptoTestData.roomId val e2eRoomID = cryptoTestData.roomId
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!! val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
// we want to disable key gossiping to just check initial sending of keys // we want to disable key gossiping to just check initial sending of keys
aliceSession.cryptoService().enableKeyGossiping(false) aliceSession.cryptoService().enableKeyGossiping(false)
@ -100,70 +83,50 @@ class E2eeSanityTests : InstrumentedTest {
// add some more users and invite them // add some more users and invite them
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu") val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
.map { .let {
testHelper.createAccount(it, SessionTestParams(true)).also { cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
it.cryptoService().enableKeyGossiping(false)
}
} }
Log.v("#E2E TEST", "All accounts created")
// we want to invite them in the room
otherAccounts.forEach {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.membershipService().invite(it.myUserId)
}
// All user should accept invite
otherAccounts.forEach { otherSession ->
testHelper.waitForAndAcceptInviteInRoom(otherSession, e2eRoomID)
Log.v("#E2E TEST", "${otherSession.myUserId} joined room $e2eRoomID")
}
// check that alice see them as joined (not really necessary?)
ensureMembersHaveJoined(testHelper, aliceSession, otherAccounts, e2eRoomID)
Log.v("#E2E TEST", "All users have joined the room") Log.v("#E2E TEST", "All users have joined the room")
Log.v("#E2E TEST", "Alice is sending the message") Log.v("#E2E TEST", "Alice is sending the message")
val text = "This is my message" val text = "This is my message"
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text) val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
Assert.assertTrue("Message should be sent", sentEventId != null) Assert.assertTrue("Message should be sent", sentEventId != null)
// All should be able to decrypt // All should be able to decrypt
otherAccounts.forEach { otherSession -> otherAccounts.forEach { otherSession ->
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!) onFail = {
fail("${otherSession.myUserId.take(10)} should be able to decrypt")
}) {
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
}
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
timeLineEvent.root.getClearType() == EventType.MESSAGE && timeLineEvent.root.getClearType() == EventType.MESSAGE &&
timeLineEvent.root.mxDecryptionResult?.isSafe == true timeLineEvent.root.mxDecryptionResult?.isSafe == true
} }
} }
Log.v("#E2E TEST", "Everybody received the encrypted message and could decrypt")
// Add a new user to the room, and check that he can't decrypt // Add a new user to the room, and check that he can't decrypt
Log.v("#E2E TEST", "Create some new accounts and invite them")
val newAccount = listOf("adam") // , "adam", "manu") val newAccount = listOf("adam") // , "adam", "manu")
.map { .let {
testHelper.createAccount(it, SessionTestParams(true)) cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
} }
newAccount.forEach {
Log.v("#E2E TEST", "Alice invites ${it.myUserId}")
aliceRoomPOV.membershipService().invite(it.myUserId)
}
newAccount.forEach {
testHelper.waitForAndAcceptInviteInRoom(it, e2eRoomID)
}
ensureMembersHaveJoined(testHelper, aliceSession, newAccount, e2eRoomID)
// wait a bit // wait a bit
delay(3_000) delay(3_000)
// check that messages are encrypted (uisi) // check that messages are encrypted (uisi)
newAccount.forEach { otherSession -> newAccount.forEach { otherSession ->
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
onFail = {
fail("New Users shouldn't be able to decrypt history")
}
) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also { val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!).also {
Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") Log.v("#E2E TEST", "Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
} }
@ -181,7 +144,12 @@ class E2eeSanityTests : InstrumentedTest {
// new members should be able to decrypt it // new members should be able to decrypt it
newAccount.forEach { otherSession -> newAccount.forEach { otherSession ->
testHelper.retryPeriodically { // ("${otherSession.myUserId} should be able to decrypt")
testHelper.betterRetryPeriodically(
onFail = {
fail("New user ${otherSession.myUserId.take(10)} should be able to decrypt the second message")
}
) {
val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also { val timelineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(secondSentEventId!!).also {
Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}") Log.v("#E2E TEST", "Second Event seen by new user ${it?.root?.getClearType()}|${it?.root?.mCryptoError}")
} }
@ -223,12 +191,9 @@ class E2eeSanityTests : InstrumentedTest {
Log.v("#E2E TEST", "Create and start key backup for bob ...") Log.v("#E2E TEST", "Create and start key backup for bob ...")
val bobKeysBackupService = bobSession.cryptoService().keysBackupService() val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
val keyBackupPassword = "FooBarBaz" val keyBackupPassword = "FooBarBaz"
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it) val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
}
val version = testHelper.waitForCallback<KeysVersion> {
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
Log.v("#E2E TEST", "... Key backup started and enabled for bob") Log.v("#E2E TEST", "... Key backup started and enabled for bob")
// Bob session should now have // Bob session should now have
@ -242,7 +207,11 @@ class E2eeSanityTests : InstrumentedTest {
sentEventIds.add(it) sentEventIds.add(it)
} }
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
onFail = {
fail("Bob should be able to decrypt all messages")
}
) {
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -256,7 +225,13 @@ class E2eeSanityTests : InstrumentedTest {
// Let's wait a bit to be sure that bob has backed up the session // Let's wait a bit to be sure that bob has backed up the session
Log.v("#E2E TEST", "Force key backup for Bob...") Log.v("#E2E TEST", "Force key backup for Bob...")
testHelper.waitForCallback<Unit> { bobKeysBackupService.backupAllGroupSessions(null, it) } testHelper.betterRetryPeriodically(
onFail = {
fail("All keys should be backedup")
}
) {
bobKeysBackupService.getTotalNumbersOfBackedUpKeys() == bobKeysBackupService.getTotalNumbersOfKeys()
}
Log.v("#E2E TEST", "... Key backup done for Bob") Log.v("#E2E TEST", "... Key backup done for Bob")
// Now lets logout both alice and bob to ensure that we won't have any gossiping // Now lets logout both alice and bob to ensure that we won't have any gossiping
@ -276,7 +251,7 @@ class E2eeSanityTests : InstrumentedTest {
// check that bob can't currently decrypt // check that bob can't currently decrypt
Log.v("#E2E TEST", "check that bob can't currently decrypt") Log.v("#E2E TEST", "check that bob can't currently decrypt")
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also { val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}") Log.v("#E2E TEST", "Event seen by new user ${it.root.getClearType()}|${it.root.mCryptoError}")
} }
@ -284,25 +259,26 @@ class E2eeSanityTests : InstrumentedTest {
} }
} }
// after initial sync events are not decrypted, so we have to try manually // after initial sync events are not decrypted, so we have to try manually
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) // TODO CHANGE WHEN AVAILABLE FROM RUST
cryptoTestHelper.ensureCannotDecrypt(
sentEventIds,
newBobSession,
e2eRoomID,
MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID
) // MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// Let's now import keys from backup // Let's now import keys from backup
newBobSession.cryptoService().keysBackupService().let { kbs -> newBobSession.cryptoService().keysBackupService().let { kbs ->
val keyVersionResult = testHelper.waitForCallback<KeysVersionResult?> { val keyVersionResult = kbs.getVersion(version.version)
kbs.getVersion(version.version, it)
}
val importedResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importedResult = kbs.restoreKeyBackupWithPassword(
kbs.restoreKeyBackupWithPassword( keyVersionResult!!,
keyVersionResult!!, keyBackupPassword,
keyBackupPassword, null,
null, null,
null, null,
null, )
it
)
}
assertEquals(3, importedResult.totalNumberOfKeys) assertEquals(3, importedResult.totalNumberOfKeys)
} }
@ -342,7 +318,11 @@ class E2eeSanityTests : InstrumentedTest {
sentEventIds.add(it) sentEventIds.add(it)
} }
testHelper.retryPeriodically { testHelper.betterRetryPeriodically(
onFail = {
fail("${bobSession.myUserId.take(10)} should be able to decrypt message sent by alice}")
}
) {
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId) val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -366,7 +346,7 @@ class E2eeSanityTests : InstrumentedTest {
// Try to request // Try to request
sentEventIds.forEach { sentEventId -> sentEventIds.forEach { sentEventId ->
val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root val event = newBobSession.getRoom(e2eRoomID)!!.getTimelineEvent(sentEventId)!!.root
newBobSession.cryptoService().requestRoomKeyForEvent(event) newBobSession.cryptoService().reRequestRoomKeyForEvent(event)
} }
// Ensure that new bob still can't decrypt (keys must have been withheld) // Ensure that new bob still can't decrypt (keys must have been withheld)
@ -391,6 +371,8 @@ class E2eeSanityTests : InstrumentedTest {
// } // }
// } // }
// */
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null) cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
// Now mark new bob session as verified // Now mark new bob session as verified
@ -431,7 +413,7 @@ class E2eeSanityTests : InstrumentedTest {
firstMessage.let { text -> firstMessage.let { text ->
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId) val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -457,7 +439,7 @@ class E2eeSanityTests : InstrumentedTest {
secondMessage.let { text -> secondMessage.let { text ->
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!! secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId) val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
timeLineEvent != null && timeLineEvent != null &&
timeLineEvent.isEncrypted() && timeLineEvent.isEncrypted() &&
@ -488,11 +470,11 @@ class E2eeSanityTests : InstrumentedTest {
// Now let's verify bobs session, and re-request keys // Now let's verify bobs session, and re-request keys
bobSessionWithBetterKey.cryptoService() bobSessionWithBetterKey.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId)
newBobSession.cryptoService() newBobSession.cryptoService()
.verificationService() .verificationService()
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!) .markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId)
// now let new session request // now let new session request
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root) newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
@ -501,7 +483,7 @@ class E2eeSanityTests : InstrumentedTest {
// old session should have shared the key at earliest known index now // old session should have shared the key at earliest known index now
// we should be able to decrypt both // we should be able to decrypt both
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
val canDecryptFirst = try { val canDecryptFirst = try {
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "") newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
true true
@ -524,7 +506,7 @@ class E2eeSanityTests : InstrumentedTest {
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60)) val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
timeline.start() timeline.start()
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
val decryptedMsg = timeline.getSnapshot() val decryptedMsg = timeline.getSnapshot()
.filter { it.root.getClearType() == EventType.MESSAGE } .filter { it.root.getClearType() == EventType.MESSAGE }
.also { list -> .also { list ->
@ -543,76 +525,76 @@ class E2eeSanityTests : InstrumentedTest {
/** /**
* Test that if a better key is forwared (lower index, it is then used) * Test that if a better key is forwared (lower index, it is then used)
*/ */
@Test // @Test
fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> // fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
//
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true)) // val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
cryptoTestHelper.bootstrapSecurity(aliceSession) // cryptoTestHelper.bootstrapSecurity(aliceSession)
//
// now let's create a new login from alice // // now let's create a new login from alice
//
val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true)) // val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
//
val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId) // val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId) // val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
// initiate self verification // // initiate self verification
aliceSession.cryptoService().verificationService().requestKeyVerification( // aliceSession.cryptoService().verificationService().requestSelfKeyVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), // listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceNewSession.myUserId, // // aliceNewSession.myUserId,
listOf(aliceNewSession.sessionParams.deviceId!!) // // listOf(aliceNewSession.sessionParams.deviceId!!)
) // )
//
val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode) // val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
//
assertEquals("Decimal code should have matched", oldCode, newCode) // assertEquals("Decimal code should have matched", oldCode, newCode)
//
// Assert that devices are verified // // Assert that devices are verified
val newDeviceFromOldPov: CryptoDeviceInfo? = // val newDeviceFromOldPov: CryptoDeviceInfo? =
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId) // aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
val oldDeviceFromNewPov: CryptoDeviceInfo? = // val oldDeviceFromNewPov: CryptoDeviceInfo? =
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId) // aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
//
Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified) // Assert.assertTrue("new device should be verified from old point of view", newDeviceFromOldPov!!.isVerified)
Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified) // Assert.assertTrue("old device should be verified from new point of view", oldDeviceFromNewPov!!.isVerified)
//
// wait for secret gossiping to happen // // wait for secret gossiping to happen
testHelper.retryPeriodically { // testHelper.retryPeriodically {
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown() // aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
} // }
//
testHelper.retryPeriodically { // testHelper.retryPeriodically {
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null // aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
} // }
//
assertEquals( // assertEquals(
"MSK Private parts should be the same", // "MSK Private parts should be the same",
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master, // aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master // aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
) // )
assertEquals( // assertEquals(
"USK Private parts should be the same", // "USK Private parts should be the same",
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user, // aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user // aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
) // )
//
assertEquals( // assertEquals(
"SSK Private parts should be the same", // "SSK Private parts should be the same",
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned, // aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned // aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
) // )
//
// Let's check that we have the megolm backup key // // Let's check that we have the megolm backup key
assertEquals( // assertEquals(
"Megolm key should be the same", // "Megolm key should be the same",
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey, // aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey // aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
) // )
assertEquals( // assertEquals(
"Megolm version should be the same", // "Megolm version should be the same",
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version, // aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version // aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
) // )
} // }
@Test @Test
fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
@ -625,26 +607,23 @@ class E2eeSanityTests : InstrumentedTest {
user = aliceSession.myUserId, user = aliceSession.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
val bobAuthParams = UserPasswordAuth( val bobAuthParams = UserPasswordAuth(
user = bobSession!!.myUserId, user = bobSession!!.myUserId,
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(aliceAuthParams)
promise.resume(aliceAuthParams) }
} })
}, it)
}
testHelper.waitForCallback { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(bobAuthParams)
promise.resume(bobAuthParams) }
} })
}, it)
}
// add a second session for bob but not cross signed // add a second session for bob but not cross signed
@ -660,11 +639,11 @@ class E2eeSanityTests : InstrumentedTest {
// wait for it to be synced back the other side // wait for it to be synced back the other side
Timber.v("#TEST: Wait for message to be synced back") Timber.v("#TEST: Wait for message to be synced back")
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
} }
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
} }
@ -681,11 +660,11 @@ class E2eeSanityTests : InstrumentedTest {
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!! val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
Timber.v("#TEST: Wait for message to be synced back") Timber.v("#TEST: Wait for message to be synced back")
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
} }
testHelper.retryPeriodically { testHelper.betterRetryPeriodically {
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
} }
@ -693,94 +672,94 @@ class E2eeSanityTests : InstrumentedTest {
cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId) cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId)
} }
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> { // private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
return scope.async { // return scope.async {
suspendCancellableCoroutine { continuation -> // suspendCancellableCoroutine { continuation ->
var oldCode: String? = null // var oldCode: String? = null
val listener = object : VerificationService.Listener { // val listener = object : VerificationService.Listener {
//
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { // override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
val readyInfo = pr.readyInfo // val readyInfo = pr.readyInfo
if (readyInfo != null) { // if (readyInfo != null) {
beginKeyVerification( // beginKeyVerification(
VerificationMethod.SAS, // VerificationMethod.SAS,
userId, // userId,
readyInfo.fromDevice, // readyInfo.fromDevice,
readyInfo.transactionId // readyInfo.transactionId
//
) // )
} // }
} // }
//
override fun transactionUpdated(tx: VerificationTransaction) { // override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("##TEST", "exitsingPov: $tx") // Log.d("##TEST", "exitsingPov: $tx")
val sasTx = tx as OutgoingSasVerificationTransaction // val sasTx = tx as OutgoingSasVerificationTransaction
when (sasTx.uxState) { // when (sasTx.uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> { // OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
// for the test we just accept? // // for the test we just accept?
oldCode = sasTx.getDecimalCodeRepresentation() // oldCode = sasTx.getDecimalCodeRepresentation()
sasTx.userHasVerifiedShortCode() // sasTx.userHasVerifiedShortCode()
} // }
OutgoingSasVerificationTransaction.UxState.VERIFIED -> { // OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
removeListener(this) // removeListener(this)
// we can release this latch? // // we can release this latch?
continuation.resume(oldCode!!) // continuation.resume(oldCode!!)
} // }
else -> Unit // else -> Unit
} // }
} // }
} // }
addListener(listener) // addListener(listener)
continuation.invokeOnCancellation { removeListener(listener) } // continuation.invokeOnCancellation { removeListener(listener) }
} // }
} // }
} // }
//
private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> { // private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
return scope.async { // return scope.async {
suspendCancellableCoroutine { continuation -> // suspendCancellableCoroutine { continuation ->
var newCode: String? = null // var newCode: String? = null
//
val listener = object : VerificationService.Listener { // val listener = object : VerificationService.Listener {
//
override fun verificationRequestCreated(pr: PendingVerificationRequest) { // override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// let's ready // // let's ready
readyPendingVerification( // readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), // listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
userId, // userId,
pr.transactionId!! // pr.transactionId!!
) // )
} // }
//
var matchOnce = true // var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) { // override fun transactionUpdated(tx: VerificationTransaction) {
Log.d("##TEST", "newPov: $tx") // Log.d("##TEST", "newPov: $tx")
//
val sasTx = tx as IncomingSasVerificationTransaction // val sasTx = tx as IncomingSasVerificationTransaction
when (sasTx.uxState) { // when (sasTx.uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { // IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
// no need to accept as there was a request first it will auto accept // // no need to accept as there was a request first it will auto accept
} // }
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { // IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
if (matchOnce) { // if (matchOnce) {
sasTx.userHasVerifiedShortCode() // sasTx.userHasVerifiedShortCode()
newCode = sasTx.getDecimalCodeRepresentation() // newCode = sasTx.getDecimalCodeRepresentation()
matchOnce = false // matchOnce = false
} // }
} // }
IncomingSasVerificationTransaction.UxState.VERIFIED -> { // IncomingSasVerificationTransaction.UxState.VERIFIED -> {
removeListener(this) // removeListener(this)
continuation.resume(newCode!!) // continuation.resume(newCode!!)
} // }
else -> Unit // else -> Unit
} // }
} // }
} // }
addListener(listener) // addListener(listener)
continuation.invokeOnCancellation { removeListener(listener) } // continuation.invokeOnCancellation { removeListener(listener) }
} // }
} // }
} // }
private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) { private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
testHelper.retryPeriodically { testHelper.retryPeriodically {

View File

@ -52,9 +52,7 @@ class PreShareKeysTest : InstrumentedTest {
Log.d("#Test", "Room Key Received from alice $preShareCount") Log.d("#Test", "Room Key Received from alice $preShareCount")
// Force presharing of new outbound key // Force presharing of new outbound key
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
}
testHelper.retryPeriodically { testHelper.retryPeriodically {
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys() val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()

View File

@ -94,7 +94,9 @@ class UnwedgingTest : InstrumentedTest {
val bobSession = cryptoTestData.secondSession!! val bobSession = cryptoTestData.secondSession!!
val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting val aliceCryptoStore = (aliceSession.cryptoService() as DefaultCryptoService).cryptoStoreForTesting
val olmDevice = (aliceSession.cryptoService() as DefaultCryptoService).olmDeviceForTest
// bobSession.cryptoService().setWarnOnUnknownDevices(false)
// aliceSession.cryptoService().setWarnOnUnknownDevices(false)
val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!! val roomFromBobPOV = bobSession.getRoom(aliceRoomId)!!
val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!! val roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
@ -116,9 +118,9 @@ class UnwedgingTest : InstrumentedTest {
// - Store the olm session between A&B devices // - Store the olm session between A&B devices
// Let us pickle our session with bob here so we can later unpickle it // Let us pickle our session with bob here so we can later unpickle it
// and wedge our session. // and wedge our session.
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!) val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)
sessionIdsForBob!!.size shouldBeEqualTo 1 sessionIdsForBob!!.size shouldBeEqualTo 1
val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyDevice().identityKey()!!)!! val olmSession = aliceCryptoStore.getDeviceSession(sessionIdsForBob.first(), bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)!!
val oldSession = serializeForRealm(olmSession.olmSession) val oldSession = serializeForRealm(olmSession.olmSession)
@ -142,9 +144,10 @@ class UnwedgingTest : InstrumentedTest {
aliceCryptoStore.storeSession( aliceCryptoStore.storeSession(
OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!), OlmSessionWrapper(deserializeFromRealm<OlmSession>(oldSession)!!),
bobSession.cryptoService().getMyDevice().identityKey()!! bobSession.cryptoService().getMyCryptoDevice().identityKey()!!
) )
olmDevice.clearOlmSessionCache() // TODO mmm we can't do that with rust
// olmDevice.clearOlmSessionCache()
// Force new session, and key share // Force new session, and key share
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId) aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
@ -170,7 +173,6 @@ class UnwedgingTest : InstrumentedTest {
Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
// It's a trick to force key request on fail to decrypt // It's a trick to force key request on fail to decrypt
testHelper.waitForCallback<Unit> {
bobSession.cryptoService().crossSigningService() bobSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -183,9 +185,7 @@ class UnwedgingTest : InstrumentedTest {
) )
) )
} }
}, it })
)
}
// Wait until we received back the key // Wait until we received back the key
testHelper.retryPeriodically { testHelper.retryPeriodically {

View File

@ -35,8 +35,6 @@ import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
import org.matrix.android.sdk.common.SessionTestParams import org.matrix.android.sdk.common.SessionTestParams
@ -54,7 +52,6 @@ class XSigningTest : InstrumentedTest {
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper -> fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true)) val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
testHelper.waitForCallback<Unit> {
aliceSession.cryptoService().crossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning(object : UserInteractiveAuthInterceptor { .initializeCrossSigning(object : UserInteractiveAuthInterceptor {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
@ -66,10 +63,10 @@ class XSigningTest : InstrumentedTest {
) )
) )
} }
}, it) })
}
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
val myCrossSigningKeys = aliceSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
val masterPubKey = myCrossSigningKeys?.masterKey() val masterPubKey = myCrossSigningKeys?.masterKey()
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey) assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
val selfSigningKey = myCrossSigningKeys?.selfSigningKey() val selfSigningKey = myCrossSigningKeys?.selfSigningKey()
@ -79,7 +76,8 @@ class XSigningTest : InstrumentedTest {
assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true) assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted() == true)
assertTrue("Signing Keys should be trusted", aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId).isVerified()) val userTrustResult = aliceSession.cryptoService().crossSigningService().checkUserTrust(aliceSession.myUserId)
assertTrue("Signing Keys should be trusted", userTrustResult.isVerified())
testHelper.signOutAndClose(aliceSession) testHelper.signOutAndClose(aliceSession)
} }
@ -100,39 +98,30 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(aliceAuthParams)
promise.resume(aliceAuthParams) }
} })
}, it) bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
} override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
testHelper.waitForCallback<Unit> { promise.resume(bobAuthParams)
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { }
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { })
promise.resume(bobAuthParams)
}
}, it)
}
// Check that alice can see bob keys // Check that alice can see bob keys
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobSession.myUserId), true, it) } aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), true)
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId) val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobSession.myUserId)
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey()) assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey()) assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey()) assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
assertEquals( val myKeys = bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
"Bob keys from alice pov should match",
bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
)
assertEquals(
"Bob keys from alice pov should match",
bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey,
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey
)
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted()) assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
} }
@ -153,40 +142,34 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(aliceAuthParams)
promise.resume(aliceAuthParams) }
} })
}, it) bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
} override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
testHelper.waitForCallback<Unit> { promise.resume(bobAuthParams)
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { }
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { })
promise.resume(bobAuthParams)
}
}, it)
}
// Check that alice can see bob keys // Check that alice can see bob keys
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it) } aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId) val bobKeysFromAlicePOV = aliceSession.cryptoService().crossSigningService().getUserCrossSigningKeys(bobUserId)
assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false) assertTrue("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV?.isTrusted() == false)
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().trustUser(bobUserId, it) } aliceSession.cryptoService().crossSigningService().trustUser(bobUserId)
// Now bobs logs in on a new device and verifies it // 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 // We will want to test that in alice POV, this new device would be trusted by cross signing
val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true)) val bobSession2 = testHelper.logIntoAccount(bobUserId, SessionTestParams(true))
val bobSecondDeviceId = bobSession2.sessionParams.deviceId!! val bobSecondDeviceId = bobSession2.sessionParams.deviceId
// Check that bob first session sees the new login // Check that bob first session sees the new login
val data = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { val data = bobSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
}
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
fail("Bob should see the new device") fail("Bob should see the new device")
@ -196,14 +179,10 @@ class XSigningTest : InstrumentedTest {
assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice) assertNotNull("Bob Second device should be known and persisted from first", bobSecondDevicePOVFirstDevice)
// Manually mark it as trusted from first session // Manually mark it as trusted from first session
testHelper.waitForCallback<Unit> { bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId)
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
}
// Now alice should cross trust bob's second device // Now alice should cross trust bob's second device
val data2 = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> { val data2 = aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
}
// check that the device is seen // check that the device is seen
if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) { if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
@ -230,20 +209,16 @@ class XSigningTest : InstrumentedTest {
password = TestConstants.PASSWORD password = TestConstants.PASSWORD
) )
testHelper.waitForCallback<Unit> { aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(aliceAuthParams)
promise.resume(aliceAuthParams) }
} })
}, it) bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
} override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
testHelper.waitForCallback<Unit> { promise.resume(bobAuthParams)
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { }
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { })
promise.resume(bobAuthParams)
}
}, it)
}
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId) cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
@ -267,13 +242,11 @@ class XSigningTest : InstrumentedTest {
.getUserCrossSigningKeys(bobSession.myUserId)!! .getUserCrossSigningKeys(bobSession.myUserId)!!
.masterKey()!!.unpaddedBase64PublicKey!! .masterKey()!!.unpaddedBase64PublicKey!!
testHelper.waitForCallback<Unit> { bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor { override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) { promise.resume(bobAuthParams)
promise.resume(bobAuthParams) }
} })
}, it)
}
testHelper.retryPeriodically { testHelper.retryPeriodically {
val newBobMsk = aliceSession.cryptoService().crossSigningService() val newBobMsk = aliceSession.cryptoService().crossSigningService()

View File

@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.gossiping
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import junit.framework.TestCase.assertEquals
import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertNotNull
import junit.framework.TestCase.assertTrue import junit.framework.TestCase.assertTrue
import org.amshove.kluent.internal.assertEquals
import org.amshove.kluent.shouldBeEqualTo import org.amshove.kluent.shouldBeEqualTo
import org.junit.Assert import org.junit.Assert
import org.junit.Assert.assertNull import org.junit.Assert.assertNull
@ -100,7 +100,7 @@ class KeyShareTests : InstrumentedTest {
// Try to request // Try to request
aliceSession2.cryptoService().enableKeyGossiping(true) aliceSession2.cryptoService().enableKeyGossiping(true)
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root) aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
@ -163,8 +163,8 @@ class KeyShareTests : InstrumentedTest {
// Mark the device as trusted // Mark the device as trusted
Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyDevice().identityKey()}") Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyCryptoDevice().identityKey()}")
val aliceSecondSession = aliceSession2.cryptoService().getMyDevice() val aliceSecondSession = aliceSession2.cryptoService().getMyCryptoDevice()
Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}") Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}")
aliceSession.cryptoService().setDeviceVerification( aliceSession.cryptoService().setDeviceVerification(
@ -178,7 +178,11 @@ class KeyShareTests : InstrumentedTest {
aliceSession.sessionParams.deviceId ?: "" aliceSession.sessionParams.deviceId ?: ""
) )
aliceSession.cryptoService().deviceWithIdentityKey(aliceSecondSession.identityKey()!!, MXCRYPTO_ALGORITHM_OLM)!!.isVerified shouldBeEqualTo true aliceSession.cryptoService().deviceWithIdentityKey(
aliceSecondSession.userId,
aliceSecondSession.identityKey()!!,
MXCRYPTO_ALGORITHM_OLM
)!!.isVerified shouldBeEqualTo true
// Re request // Re request
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root) aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
@ -257,11 +261,11 @@ class KeyShareTests : InstrumentedTest {
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId } outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success ownDeviceReply != null && ownDeviceReply.result is RequestResult.Success
} }
commonTestHelper.signOutAndClose(aliceSession)
commonTestHelper.signOutAndClose(aliceNewSession)
} }
/**
* Tests that keys reshared with own verified session are done from the earliest known index
*/
@Test @Test
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest( fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(
context(), context(),
@ -375,9 +379,6 @@ class KeyShareTests : InstrumentedTest {
commonTestHelper.signOutAndClose(bobSession) commonTestHelper.signOutAndClose(bobSession)
} }
/**
* Tests that we don't cancel a request to early on first forward if the index is not good enough
*/
@Test @Test
fun test_dontCancelToEarly() = runCryptoTest( fun test_dontCancelToEarly() = runCryptoTest(
context(), context(),

View File

@ -26,7 +26,6 @@ import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.NoOpMatrixCallback
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@ -71,6 +70,7 @@ class WithHeldTests : InstrumentedTest {
val roomAlicePOV = aliceSession.getRoom(roomId)!! val roomAlicePOV = aliceSession.getRoom(roomId)!!
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true)) val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
// ============================= // =============================
// ACT // ACT
// ============================= // =============================
@ -155,15 +155,13 @@ class WithHeldTests : InstrumentedTest {
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession) val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
// Simulate no OTK // Simulate no OTK
aliceInterceptor!!.addRule( aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
MockOkHttpInterceptor.SimpleRule( "/keys/claim",
"/keys/claim", 200,
200, """
"""
{ "one_time_keys" : {} } { "one_time_keys" : {} }
""" """
) ))
)
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}") Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
val roomAlicePov = aliceSession.getRoom(testData.roomId)!! val roomAlicePov = aliceSession.getRoom(testData.roomId)!!
@ -191,10 +189,7 @@ class WithHeldTests : InstrumentedTest {
// Ensure that alice has marked the session to be shared with bob // Ensure that alice has marked the session to be shared with bob
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!! val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject( val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId)
bobSession.myUserId,
bobSession.sessionParams.credentials.deviceId
)
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex) Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
// Add a new device for bob // Add a new device for bob
@ -210,10 +205,7 @@ class WithHeldTests : InstrumentedTest {
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
} }
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject( val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId)
bobSecondSession.myUserId,
bobSecondSession.sessionParams.credentials.deviceId
)
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2) Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
@ -243,8 +235,8 @@ class WithHeldTests : InstrumentedTest {
cryptoTestHelper.initializeCrossSigning(bobSecondSession) cryptoTestHelper.initializeCrossSigning(bobSecondSession)
// Trust bob second device from Alice POV // Trust bob second device from Alice POV
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback()) aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId)
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback()) bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId)
var sessionId: String? = null var sessionId: String? = null
// Check that the // Check that the

View File

@ -19,14 +19,13 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData import org.matrix.android.sdk.common.CryptoTestData
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
/** /**
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword] * Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
*/ */
internal data class KeysBackupScenarioData( internal data class KeysBackupScenarioData(
val cryptoTestData: CryptoTestData, val cryptoTestData: CryptoTestData,
val aliceKeys: List<MXInboundMegolmSessionWrapper>, val aliceKeysCount: Int,
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult, val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
val aliceSession2: Session val aliceSession2: Session
) { ) {

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest import androidx.test.filters.LargeTest
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.amshove.kluent.internal.assertFailsWith
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull import org.junit.Assert.assertNotNull
@ -30,19 +31,13 @@ import org.junit.runner.RunWith
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM_BACKUP
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult import org.matrix.android.sdk.api.session.crypto.keysbackup.toKeysVersionResult
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.getRoom import org.matrix.android.sdk.api.session.getRoom
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runSessionTest
@ -50,7 +45,6 @@ import org.matrix.android.sdk.common.RetryTestRule
import org.matrix.android.sdk.common.TestConstants import org.matrix.android.sdk.common.TestConstants
import org.matrix.android.sdk.common.waitFor import org.matrix.android.sdk.common.waitFor
import java.security.InvalidParameterException import java.security.InvalidParameterException
import java.util.Collections
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import kotlin.coroutines.resume import kotlin.coroutines.resume
@ -83,7 +77,7 @@ class KeysBackupTest : InstrumentedTest {
// - Check backup keys after having marked one as backed up // - Check backup keys after having marked one as backed up
val session = sessions[0] val session = sessions[0]
cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session)) cryptoStore.markBackupDoneForInboundGroupSessions(listOf(session))
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
@ -118,9 +112,7 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(null, null)
keysBackup.prepareKeysBackupVersion(null, null, it)
}
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm) assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
assertNotNull(megolmBackupCreationInfo.authData.publicKey) assertNotNull(megolmBackupCreationInfo.authData.publicKey)
@ -144,27 +136,20 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo =
keysBackup.prepareKeysBackupVersion(null, null, it) keysBackup.prepareKeysBackupVersion(null, null)
}
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())
// Create the version // Create the version
val version = testHelper.waitForCallback<KeysVersion> { val version = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
}
// Backup must be enable now // Backup must be enable now
assertTrue(keysBackup.isEnabled()) assertTrue(keysBackup.isEnabled())
// Check that it's signed with MSK // Check that it's signed with MSK
val versionResult = testHelper.waitForCallback<KeysVersionResult?> { val versionResult = keysBackup.getVersion(version.version)
keysBackup.getVersion(version.version, it) val trust = keysBackup.getKeysBackupTrust(versionResult!!)
}
val trust = testHelper.waitForCallback<KeysBackupVersionTrust> {
keysBackup.getKeysBackupTrust(versionResult!!, it)
}
assertEquals("Should have 2 signatures", 2, trust.signatures.size) assertEquals("Should have 2 signatures", 2, trust.signatures.size)
@ -211,7 +196,6 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false)) assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)) assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
val stateObserver = StateObserver(keysBackup, latch, 5) val stateObserver = StateObserver(keysBackup, latch, 5)
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
@ -256,19 +240,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(2, nbOfKeys) assertEquals(2, nbOfKeys)
var lastBackedUpKeysProgress = 0 testHelper.retryPeriodically {
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
testHelper.waitForCallback<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
assertEquals(nbOfKeys, total)
lastBackedUpKeysProgress = progress
}
}, it)
} }
assertEquals(nbOfKeys, lastBackedUpKeysProgress)
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true) val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys) assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
@ -305,7 +280,7 @@ class KeysBackupTest : InstrumentedTest {
assertNotNull(keyBackupData!!.sessionData) assertNotNull(keyBackupData!!.sessionData)
// - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption // - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey) val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey.toBase58())
assertNotNull(decryption) assertNotNull(decryption)
// - Check decryptKeyBackupData() returns stg // - Check decryptKeyBackupData() returns stg
val sessionData = keysBackup val sessionData = keysBackup
@ -313,7 +288,7 @@ class KeysBackupTest : InstrumentedTest {
keyBackupData, keyBackupData,
session.safeSessionId!!, session.safeSessionId!!,
cryptoTestData.roomId, cryptoTestData.roomId,
decryption!! keyBackupCreationInfo.recoveryKey
) )
assertNotNull(sessionData) assertNotNull(sessionData)
// - Compare the decrypted megolm key with the original one // - Compare the decrypted megolm key with the original one
@ -335,16 +310,13 @@ class KeysBackupTest : InstrumentedTest {
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
// - Restore the e2e backup from the homeserver // - Restore the e2e backup from the homeserver
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null,
null, null,
null, null
null, )
it
)
}
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
@ -401,7 +373,7 @@ class KeysBackupTest : InstrumentedTest {
// // Request is either sent or unsent // // Request is either sent or unsent
// assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null) // assertTrue(unsentRequestAfterRestoration == null && sentRequestAfterRestoration == null)
// //
// testData.cleanUp(mTestHelper) // testData.cleanUp(testHelper)
// } // }
/** /**
@ -430,13 +402,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Trust the backup from the new device // - Trust the backup from the new device
testHelper.waitForCallback<Unit> {
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion( testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
true, true
it
) )
}
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -446,16 +415,17 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) .keysBackupService()
}.toKeysVersionResult() .getCurrentVersion()!!
.toKeysVersionResult()
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) .keysBackupService()
} .getKeysBackupTrust(keysVersionResult)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -490,32 +460,32 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Trust the backup from the new device with the recovery key // - Trust the backup from the new device with the recovery key
testHelper.waitForCallback<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, )
it
)
}
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
// - Backup must be enabled on the new device, on the same version // - Backup must be enabled on the new device, on the same version
assertEquals(testData.prepareKeysBackupDataResult.version, testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version) assertEquals(
testData.prepareKeysBackupDataResult.version,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion?.version
)
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) .getCurrentVersion()!!
}.toKeysVersionResult() .toKeysVersionResult()
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) .keysBackupService()
} .getKeysBackupTrust(keysVersionResult)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -548,13 +518,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Try to trust the backup from the new device with a wrong recovery key // - Try to trust the backup from the new device with a wrong recovery key
testHelper.waitForCallbackError<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, BackupUtils.recoveryKeyFromPassphrase("Bad recovery key")!!,
"Bad recovery key", )
it
)
}
// - The new device must still see the previous backup as not trusted // - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -592,13 +559,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Trust the backup from the new device with the password // - Trust the backup from the new device with the password
testHelper.waitForCallback<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, password
password, )
it
)
}
// Wait for backup state to be ReadyToBackUp // Wait for backup state to be ReadyToBackUp
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp) keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
@ -608,16 +572,16 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled()) assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
// - Retrieve the last version from the server // - Retrieve the last version from the server
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it) .getCurrentVersion()!!
}.toKeysVersionResult() .toKeysVersionResult()
// - It must be the same // - It must be the same
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version) assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it) .keysBackupService()
} .getKeysBackupTrust(keysVersionResult)
// - It must be trusted and must have 2 signatures now // - It must be trusted and must have 2 signatures now
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -653,13 +617,10 @@ class KeysBackupTest : InstrumentedTest {
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState()) assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
// - Try to trust the backup from the new device with a wrong password // - Try to trust the backup from the new device with a wrong password
testHelper.waitForCallbackError<Unit> { testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, badPassword,
badPassword, )
it
)
}
// - The new device must still see the previous backup as not trusted // - The new device must still see the previous backup as not trusted
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion) assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
@ -683,18 +644,15 @@ class KeysBackupTest : InstrumentedTest {
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
// - Try to restore the e2e backup with a wrong recovery key // - Try to restore the e2e backup with a wrong recovery key
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> { assertFailsWith<InvalidParameterException> {
keysBackupService.restoreKeysWithRecoveryKey( keysBackupService.restoreKeysWithRecoveryKey(
keysBackupService.keysBackupVersion!!, keysBackupService.keysBackupVersion!!,
"EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d", BackupUtils.recoveryKeyFromBase58("EsTc LW2K PGiF wKEA 3As5 g5c4 BXwk qeeJ ZJV8 Q9fu gUMN UE4d")!!,
null, null,
null, null,
null, null,
it
) )
} }
assertTrue(importRoomKeysResult is InvalidParameterException)
} }
/** /**
@ -714,20 +672,17 @@ class KeysBackupTest : InstrumentedTest {
// - Restore the e2e backup with the password // - Restore the e2e backup with the password
val steps = ArrayList<StepProgressListener.Step>() val steps = ArrayList<StepProgressListener.Step>()
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, password,
password, null,
null, null,
null, object : StepProgressListener {
object : StepProgressListener { override fun onStepProgress(step: StepProgressListener.Step) {
override fun onStepProgress(step: StepProgressListener.Step) { steps.add(step)
steps.add(step) }
} }
}, )
it
)
}
// Check steps // Check steps
assertEquals(105, steps.size) assertEquals(105, steps.size)
@ -770,18 +725,15 @@ class KeysBackupTest : InstrumentedTest {
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
// - Try to restore the e2e backup with a wrong password // - Try to restore the e2e backup with a wrong password
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> { assertFailsWith<InvalidParameterException> {
keysBackupService.restoreKeyBackupWithPassword( keysBackupService.restoreKeyBackupWithPassword(
keysBackupService.keysBackupVersion!!, keysBackupService.keysBackupVersion!!,
wrongPassword, wrongPassword,
null, null,
null, null,
null, null,
it
) )
} }
assertTrue(importRoomKeysResult is InvalidParameterException)
} }
/** /**
@ -799,16 +751,13 @@ class KeysBackupTest : InstrumentedTest {
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password) val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
// - Restore the e2e backup with the recovery key. // - Restore the e2e backup with the recovery key.
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> { val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey( testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!, testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey, null,
null, null,
null, null
null, )
it
)
}
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys) keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
} }
@ -827,18 +776,15 @@ class KeysBackupTest : InstrumentedTest {
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService() val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
// - Try to restore the e2e backup with a password // - Try to restore the e2e backup with a password
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> { val importRoomKeysResult = keysBackupService.restoreKeyBackupWithPassword(
keysBackupService.restoreKeyBackupWithPassword( keysBackupService.keysBackupVersion!!,
keysBackupService.keysBackupVersion!!, "password",
"password", null,
null, null,
null, null,
null, )
it
)
}
assertTrue(importRoomKeysResult is IllegalStateException) assertTrue(importRoomKeysResult.importedSessionInfo.size > 0)
} }
/** /**
@ -860,14 +806,10 @@ class KeysBackupTest : InstrumentedTest {
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Get key backup version from the homeserver // Get key backup version from the homeserver
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> { val keysVersionResult = keysBackup.getCurrentVersion()!!.toKeysVersionResult()
keysBackup.getCurrentVersion(it)
}.toKeysVersionResult()
// - Check the returned KeyBackupVersion is trusted // - Check the returned KeyBackupVersion is trusted
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> { val keysBackupVersionTrust = keysBackup.getKeysBackupTrust(keysVersionResult!!)
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
}
assertNotNull(keysBackupVersionTrust) assertNotNull(keysBackupVersionTrust)
assertTrue(keysBackupVersionTrust.usable) assertTrue(keysBackupVersionTrust.usable)
@ -876,7 +818,7 @@ class KeysBackupTest : InstrumentedTest {
val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature
assertTrue(signature.valid) assertTrue(signature.valid)
assertNotNull(signature.device) assertNotNull(signature.device)
assertEquals(cryptoTestData.firstSession.cryptoService().getMyDevice().deviceId, signature.deviceId) assertEquals(cryptoTestData.firstSession.cryptoService().getMyCryptoDevice().deviceId, signature.deviceId)
assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId) assertEquals(signature.device!!.deviceId, cryptoTestData.firstSession.sessionParams.deviceId)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
@ -944,7 +886,9 @@ class KeysBackupTest : InstrumentedTest {
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers() (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
// - Make alice back up all her keys again // - Make alice back up all her keys again
testHelper.waitForCallbackError<Unit> { keysBackup.backupAllGroupSessions(null, it) } testHelper.retryPeriodically {
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
}
// -> That must fail and her backup state must be WrongBackUpVersion // -> That must fail and her backup state must be WrongBackUpVersion
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState()) assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState())
@ -980,11 +924,17 @@ class KeysBackupTest : InstrumentedTest {
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup) keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
// Wait for keys backup to finish by asking again to backup keys. // Wait for keys backup to finish by asking again to backup keys.
testHelper.waitForCallback<Unit> { testHelper.retryPeriodically {
keysBackup.backupAllGroupSessions(null, it) keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
} }
testHelper.retryPeriodically {
keysBackup.getState() == KeysBackupState.ReadyToBackUp
}
// testHelper.doSync<Unit> {
// keysBackup.backupAllGroupSessions(null, it)
// }
val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId!! val oldDeviceId = cryptoTestData.firstSession.sessionParams.deviceId
val oldKeyBackupVersion = keysBackup.currentBackupVersion val oldKeyBackupVersion = keysBackup.currentBackupVersion
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
@ -1005,18 +955,16 @@ class KeysBackupTest : InstrumentedTest {
val stateObserver2 = StateObserver(keysBackup2) val stateObserver2 = StateObserver(keysBackup2)
testHelper.waitForCallbackError<Unit> { keysBackup2.backupAllGroupSessions(null, it) } testHelper.retryPeriodically {
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
}
// Backup state must be NotTrusted // Backup state must be NotTrusted
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState()) assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState())
assertFalse("Backup should not be enabled", keysBackup2.isEnabled()) assertFalse("Backup should not be enabled", keysBackup2.isEnabled())
// - Validate the old device from the new one // - Validate the old device from the new one
aliceSession2.cryptoService().setDeviceVerification( aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
aliceSession2.myUserId,
oldDeviceId
)
// -> Backup should automatically enable on the new device // -> Backup should automatically enable on the new device
suspendCancellableCoroutine<Unit> { continuation -> suspendCancellableCoroutine<Unit> { continuation ->
@ -1037,8 +985,13 @@ class KeysBackupTest : InstrumentedTest {
// -> It must use the same backup version // -> It must use the same backup version
assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion) assertEquals(oldKeyBackupVersion, aliceSession2.cryptoService().keysBackupService().currentBackupVersion)
testHelper.waitForCallback<Unit> { // aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it)
aliceSession2.cryptoService().keysBackupService().backupAllGroupSessions(null, it) testHelper.retryPeriodically {
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
}
testHelper.retryPeriodically {
aliceSession2.cryptoService().keysBackupService().getState() == KeysBackupState.ReadyToBackUp
} }
// -> It must success // -> It must success
@ -1070,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
assertTrue(keysBackup.isEnabled()) assertTrue(keysBackup.isEnabled())
// Delete the backup // Delete the backup
testHelper.waitForCallback<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) } keysBackup.deleteBackup(keyBackupCreationInfo.version)
// Backup is now disabled // Backup is now disabled
assertFalse(keysBackup.isEnabled()) assertFalse(keysBackup.isEnabled())

View File

@ -18,13 +18,10 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import org.junit.Assert import org.junit.Assert
import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
import org.matrix.android.sdk.common.CommonTestHelper import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestHelper import org.matrix.android.sdk.common.CryptoTestHelper
import org.matrix.android.sdk.common.assertDictEquals import org.matrix.android.sdk.common.assertDictEquals
@ -53,29 +50,22 @@ internal class KeysBackupTestHelper(
waitForKeybackUpBatching() waitForKeybackUpBatching()
val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store // val cryptoStore = (cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store
val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService() val keysBackup = cryptoTestData.firstSession.cryptoService().keysBackupService()
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100) // val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
// - Do an e2e backup to the homeserver // - Do an e2e backup to the homeserver
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password) val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
var lastProgress = 0 testHelper.retryPeriodically {
var lastTotal = 0 keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
testHelper.waitForCallback<Unit> {
keysBackup.backupAllGroupSessions(object : ProgressListener {
override fun onProgress(progress: Int, total: Int) {
lastProgress = progress
lastTotal = total
}
}, it)
} }
val totalNumbersOfBackedUpKeys = cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
Assert.assertEquals(2, lastProgress) Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
Assert.assertEquals(2, lastTotal)
val aliceUserId = cryptoTestData.firstSession.myUserId val aliceUserId = cryptoTestData.firstSession.myUserId
@ -83,19 +73,20 @@ internal class KeysBackupTestHelper(
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync) val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
// Test check: aliceSession2 has no keys at login // Test check: aliceSession2 has no keys at login
Assert.assertEquals(0, aliceSession2.cryptoService().inboundGroupSessionsCount(false)) val inboundGroupSessionCount = aliceSession2.cryptoService().inboundGroupSessionsCount(false)
Assert.assertEquals(0, inboundGroupSessionCount)
// Wait for backup state to be NotTrusted // Wait for backup state to be NotTrusted
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted) waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
stateObserver.stopAndCheckStates(null) stateObserver.stopAndCheckStates(null)
return KeysBackupScenarioData( val totalNumbersOfBackedUpKeysFromNewSession = aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
cryptoTestData,
aliceKeys, return KeysBackupScenarioData(cryptoTestData,
totalNumbersOfBackedUpKeysFromNewSession,
prepareKeysBackupDataResult, prepareKeysBackupDataResult,
aliceSession2 aliceSession2)
)
} }
suspend fun prepareAndCreateKeysBackupData( suspend fun prepareAndCreateKeysBackupData(
@ -104,18 +95,15 @@ internal class KeysBackupTestHelper(
): PrepareKeysBackupDataResult { ): PrepareKeysBackupDataResult {
val stateObserver = StateObserver(keysBackup) val stateObserver = StateObserver(keysBackup)
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> { val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(password, null)
keysBackup.prepareKeysBackupVersion(password, null, it)
}
Assert.assertNotNull(megolmBackupCreationInfo) Assert.assertNotNull(megolmBackupCreationInfo)
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled()) Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled())
// Create the version // Create the version
val keysVersion = testHelper.waitForCallback<KeysVersion> { val keysVersion =
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it) keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
}
Assert.assertNotNull("Key backup version should not be null", keysVersion.version) Assert.assertNotNull("Key backup version should not be null", keysVersion.version)
@ -152,7 +140,7 @@ internal class KeysBackupTestHelper(
} }
} }
fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) { internal fun assertKeysEquals(keys1: MegolmSessionData?, keys2: MegolmSessionData?) {
Assert.assertNotNull(keys1) Assert.assertNotNull(keys1)
Assert.assertNotNull(keys2) Assert.assertNotNull(keys2)
@ -174,24 +162,27 @@ internal class KeysBackupTestHelper(
* - The new device must have the same count of megolm keys * - The new device must have the same count of megolm keys
* - Alice must have the same keys on both devices * - Alice must have the same keys on both devices
*/ */
fun checkRestoreSuccess( suspend fun checkRestoreSuccess(
testData: KeysBackupScenarioData, testData: KeysBackupScenarioData,
total: Int, total: Int,
imported: Int imported: Int
) { ) {
// - Imported keys number must be correct // - Imported keys number must be correct
Assert.assertEquals(testData.aliceKeys.size, total) Assert.assertEquals(testData.aliceKeysCount, total)
Assert.assertEquals(total, imported) Assert.assertEquals(total, imported)
// - The new device must have the same count of megolm keys // - The new device must have the same count of megolm keys
Assert.assertEquals(testData.aliceKeys.size, testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)) val inboundGroupSessionCount = testData.aliceSession2.cryptoService().inboundGroupSessionsCount(false)
Assert.assertEquals(testData.aliceKeysCount, inboundGroupSessionCount)
// - Alice must have the same keys on both devices // - Alice must have the same keys on both devices
for (aliceKey1 in testData.aliceKeys) { // TODO can't access internals as we can switch from rust/kotlin
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store // for (aliceKey1 in testData.aliceKeys) {
.getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!) // val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
Assert.assertNotNull(aliceKey2) // .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys()) // Assert.assertNotNull(aliceKey2)
} // assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
// }
} }
} }

View File

@ -18,10 +18,9 @@ package org.matrix.android.sdk.internal.crypto.verification
import android.util.Log import android.util.Log
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
import org.junit.Assert.fail import org.junit.Assert.fail
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
@ -34,7 +33,8 @@ import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.SasMode import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
@ -46,6 +46,7 @@ import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.toValue import org.matrix.android.sdk.internal.crypto.model.rest.toValue
import timber.log.Timber
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class) @RunWith(AndroidJUnit4::class)
@ -55,82 +56,80 @@ class SASTest : InstrumentedTest {
@Test @Test
fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_aliceStartThenAliceCancel() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() // TODO
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession // cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val bobSession = cryptoTestData.secondSession // val aliceSession = cryptoTestData.firstSession
// val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService() //
val bobVerificationService = bobSession!!.cryptoService().verificationService() // val aliceVerificationService = aliceSession.cryptoService().verificationService()
// val bobVerificationService = bobSession!!.cryptoService().verificationService()
val bobTxCreatedLatch = CountDownLatch(1) //
val bobListener = object : VerificationService.Listener { // val bobTxCreatedLatch = CountDownLatch(1)
override fun transactionUpdated(tx: VerificationTransaction) { // val bobListener = object : VerificationService.Listener {
bobTxCreatedLatch.countDown() // override fun transactionUpdated(tx: VerificationTransaction) {
} // bobTxCreatedLatch.countDown()
} // }
bobVerificationService.addListener(bobListener) // }
// bobVerificationService.addListener(bobListener)
val txID = aliceVerificationService.beginKeyVerification( //
VerificationMethod.SAS, // val bobDevice = bobSession.cryptoService().getMyCryptoDevice()
bobSession.myUserId, //
bobSession.cryptoService().getMyDevice().deviceId, // aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), forceDownload = true)
null // val txID = aliceVerificationService.beginKeyVerification(bobSession.myUserId, bobDevice.deviceId)
) //
assertNotNull("Alice should have a started transaction", txID) // assertNotNull("Alice should have a started transaction", txID)
//
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!) // val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
assertNotNull("Alice should have a started transaction", aliceKeyTx) // assertNotNull("Alice should have a started transaction", aliceKeyTx)
//
testHelper.await(bobTxCreatedLatch) // testHelper.await(bobTxCreatedLatch)
bobVerificationService.removeListener(bobListener) // bobVerificationService.removeListener(bobListener)
//
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID) // val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
//
assertNotNull("Bob should have started verif transaction", bobKeyTx) // assertNotNull("Bob should have started verif transaction", bobKeyTx)
assertTrue(bobKeyTx is SASDefaultVerificationTransaction) // assertTrue(bobKeyTx is SasVerificationTransaction)
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx) // assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction) // assertTrue(aliceKeyTx is SasVerificationTransaction)
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId) // assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
//
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction? // assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction? // assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.state)
//
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state) // // Let's cancel from alice side
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state) // val cancelLatch = CountDownLatch(1)
//
// Let's cancel from alice side // val bobListener2 = object : VerificationService.Listener {
val cancelLatch = CountDownLatch(1) // override fun transactionUpdated(tx: VerificationTransaction) {
// if (tx.transactionId == txID) {
val bobListener2 = object : VerificationService.Listener { // val immutableState = (tx as SasVerificationTransaction).state
override fun transactionUpdated(tx: VerificationTransaction) { // if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
if (tx.transactionId == txID) { // cancelLatch.countDown()
val immutableState = (tx as SASDefaultVerificationTransaction).state // }
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) { // }
cancelLatch.countDown() // }
} // }
} // bobVerificationService.addListener(bobListener2)
} //
} // aliceKeyTx.cancel(CancelCode.User)
bobVerificationService.addListener(bobListener2) //
// testHelper.await(cancelLatch)
aliceSasTx.cancel(CancelCode.User) //
testHelper.await(cancelLatch) // assertTrue("Should be cancelled on alice side", aliceKeyTx.state is VerificationTxState.Cancelled)
// assertTrue("Should be cancelled on bob side", bobKeyTx.state is VerificationTxState.Cancelled)
assertTrue("Should be cancelled on alice side", aliceSasTx.state is VerificationTxState.Cancelled) //
assertTrue("Should be cancelled on bob side", bobSasTx.state is VerificationTxState.Cancelled) // val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
// val bobCancelState = bobKeyTx.state as VerificationTxState.Cancelled
val aliceCancelState = aliceSasTx.state as VerificationTxState.Cancelled //
val bobCancelState = bobSasTx.state as VerificationTxState.Cancelled // assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe)
// assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe)
assertTrue("Should be cancelled by me on alice side", aliceCancelState.byMe) //
assertFalse("Should be cancelled by other on bob side", bobCancelState.byMe) // assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode)
// assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode)
assertEquals("Should be User cancelled on alice side", CancelCode.User, aliceCancelState.cancelCode) //
assertEquals("Should be User cancelled on bob side", CancelCode.User, bobCancelState.cancelCode) // assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
// assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
assertNull(bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID))
assertNull(aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID))
} }
@Test @Test
@ -156,7 +155,7 @@ class SASTest : InstrumentedTest {
} }
} }
} }
bobSession.cryptoService().verificationService().addListener(bobListener) // bobSession.cryptoService().verificationService().addListener(bobListener)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) { // TODO override fun onToDeviceEvent(event: Event?) {
@ -171,16 +170,18 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { if (tx.state is VerificationTxState.SasStarted && tx is SasVerificationTransaction) {
(tx as IncomingSasVerificationTransaction).performAccept() runBlocking {
tx.acceptVerification()
}
} }
} }
} }
aliceSession.cryptoService().verificationService().addListener(aliceListener) // aliceSession.cryptoService().verificationService().addListener(aliceListener)
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
@ -201,7 +202,7 @@ class SASTest : InstrumentedTest {
val tid = "00000000" val tid = "00000000"
// Bob should receive a cancel // Bob should receive a cancel
var canceledToDeviceEvent: Event? = null val canceledToDeviceEvent: Event? = null
val cancelLatch = CountDownLatch(1) val cancelLatch = CountDownLatch(1)
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() { // TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
// TODO override fun onToDeviceEvent(event: Event?) { // TODO override fun onToDeviceEvent(event: Event?) {
@ -216,12 +217,11 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
testHelper.await(cancelLatch) testHelper.await(cancelLatch)
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!! val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
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, cancelReq.code)
} }
@ -253,7 +253,7 @@ class SASTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val aliceUserID = aliceSession.myUserId val aliceUserID = aliceSession.myUserId
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes) fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, codes = codes)
@ -263,18 +263,18 @@ class SASTest : InstrumentedTest {
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, cancelReq.code)
} }
private fun fakeBobStart( private suspend fun fakeBobStart(
bobSession: Session, bobSession: Session,
aliceUserID: String?, aliceUserID: String?,
aliceDevice: String?, aliceDevice: String?,
tid: String, tid: String,
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS, protocols: List<String> = SasVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES, hashes: List<String> = SasVerificationTransaction.KNOWN_HASHES,
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS, mac: List<String> = SasVerificationTransaction.KNOWN_MACS,
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES codes: List<String> = SasVerificationTransaction.KNOWN_SHORT_CODES
) { ) {
val startMessage = KeyVerificationStart( val startMessage = KeyVerificationStart(
fromDevice = bobSession.cryptoService().getMyDevice().deviceId, fromDevice = bobSession.cryptoService().getMyCryptoDevice().deviceId,
method = VerificationMethod.SAS.toValue(), method = VerificationMethod.SAS.toValue(),
transactionId = tid, transactionId = tid,
keyAgreementProtocols = protocols, keyAgreementProtocols = protocols,
@ -307,29 +307,31 @@ class SASTest : InstrumentedTest {
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val aliceCreatedLatch = CountDownLatch(2) val aliceCreatedLatch = CountDownLatch(2)
val aliceCancelledLatch = CountDownLatch(2) val aliceCancelledLatch = CountDownLatch(1)
val createdTx = mutableListOf<SASDefaultVerificationTransaction>() val createdTx = mutableListOf<VerificationTransaction>()
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun transactionCreated(tx: VerificationTransaction) { override fun transactionCreated(tx: VerificationTransaction) {
createdTx.add(tx as SASDefaultVerificationTransaction) createdTx.add(tx)
aliceCreatedLatch.countDown() aliceCreatedLatch.countDown()
} }
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
if ((tx as SASDefaultVerificationTransaction).state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) { if (tx.state is VerificationTxState.Cancelled && !(tx.state as VerificationTxState.Cancelled).byMe) {
aliceCancelledLatch.countDown() aliceCancelledLatch.countDown()
} }
} }
} }
aliceVerificationService.addListener(aliceListener) // aliceVerificationService.addListener(aliceListener)
val bobUserId = bobSession!!.myUserId val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId val bobDeviceId = bobSession.cryptoService().getMyCryptoDevice().deviceId
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceCreatedLatch) // TODO
testHelper.await(aliceCancelledLatch) // aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), forceDownload = true)
// aliceVerificationService.beginKeyVerification(listOf(VerificationMethod.SAS), bobUserId, bobDeviceId)
// aliceVerificationService.beginKeyVerification(bobUserId, bobDeviceId)
// testHelper.await(aliceCreatedLatch)
// testHelper.await(aliceCancelledLatch)
cryptoTestData.cleanUp(testHelper) cryptoTestData.cleanUp(testHelper)
} }
@ -337,191 +339,205 @@ class SASTest : InstrumentedTest {
/** /**
* Test that when alice starts a 'correct' request, bob agrees. * Test that when alice starts a 'correct' request, bob agrees.
*/ */
@Test // @Test
@Ignore("This test will be ignored until it is fixed") // @Ignore("This test will be ignored until it is fixed")
fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> // fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() // val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
//
// val aliceSession = cryptoTestData.firstSession
// val bobSession = cryptoTestData.secondSession
//
// val aliceVerificationService = aliceSession.cryptoService().verificationService()
// val bobVerificationService = bobSession!!.cryptoService().verificationService()
//
// val aliceAcceptedLatch = CountDownLatch(1)
// val aliceListener = object : VerificationService.Listener {
// override fun transactionUpdated(tx: VerificationTransaction) {
// if (tx.state is VerificationTxState.OnAccepted) {
// aliceAcceptedLatch.countDown()
// }
// }
// }
// aliceVerificationService.addListener(aliceListener)
//
// val bobListener = object : VerificationService.Listener {
// override fun transactionUpdated(tx: VerificationTransaction) {
// if (tx.state is VerificationTxState.OnStarted && tx is SasVerificationTransaction) {
// bobVerificationService.removeListener(this)
// runBlocking {
// tx.acceptVerification()
// }
// }
// }
// }
// bobVerificationService.addListener(bobListener)
//
// val bobUserId = bobSession.myUserId
// val bobDeviceId = runBlocking {
// bobSession.cryptoService().getMyCryptoDevice().deviceId
// }
//
// aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
// testHelper.await(aliceAcceptedLatch)
//
// aliceVerificationService.getExistingTransaction(bobUserId, )
//
// assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
//
// // check that agreement is valid
// assertTrue("Agreed Protocol should be Valid", accepted != null)
// assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
// assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
// assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
//
// accepted!!.shortAuthenticationStrings.forEach {
// assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
// }
// }
val aliceSession = cryptoTestData.firstSession // @Test
val bobSession = cryptoTestData.secondSession // fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceVerificationService = aliceSession.cryptoService().verificationService() // cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val bobVerificationService = bobSession!!.cryptoService().verificationService() // val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
// val aliceSession = cryptoTestData.firstSession
var accepted: ValidVerificationInfoAccept? = null // val bobSession = cryptoTestData.secondSession!!
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null // val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
//
val aliceAcceptedLatch = CountDownLatch(1) // val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener { // val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) { // override fun transactionUpdated(tx: VerificationTransaction) {
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}") // Timber.v("Alice transactionUpdated: ${tx.state}")
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) { // latch.countDown()
val at = tx as SASDefaultVerificationTransaction // }
accepted = at.accepted // }
startReq = at.startReq // aliceSession.cryptoService().verificationService().addListener(aliceListener)
aliceAcceptedLatch.countDown() // val bobListener = object : VerificationService.Listener {
} // override fun transactionUpdated(tx: VerificationTransaction) {
} // Timber.v("Bob transactionUpdated: ${tx.state}")
} // latch.countDown()
aliceVerificationService.addListener(aliceListener) // }
// }
val bobListener = object : VerificationService.Listener { // bobSession.cryptoService().verificationService().addListener(bobListener)
override fun transactionUpdated(tx: VerificationTransaction) { // aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}") //
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) { // testHelper.await(latch)
bobVerificationService.removeListener(this) // val aliceTx =
val at = tx as IncomingSasVerificationTransaction // aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
at.performAccept() // val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
} //
} // assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
} //
bobVerificationService.addListener(bobListener) // val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
// val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
val bobUserId = bobSession.myUserId //
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId // assertEquals(
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) // "Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
testHelper.await(aliceAcceptedLatch) // bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
// )
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false) // }
// check that agreement is valid
assertTrue("Agreed Protocol should be Valid", accepted != null)
assertTrue("Agreed Protocol should be known by alice", startReq!!.keyAgreementProtocols.contains(accepted!!.keyAgreementProtocol))
assertTrue("Hash should be known by alice", startReq!!.hashes.contains(accepted!!.hash))
assertTrue("Hash should be known by alice", startReq!!.messageAuthenticationCodes.contains(accepted!!.messageAuthenticationCode))
accepted!!.shortAuthenticationStrings.forEach {
assertTrue("all agreed Short Code should be known by alice", startReq!!.shortAuthenticationStrings.contains(it))
}
}
@Test
fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1)
val aliceListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
aliceSASLatch.countDown()
}
else -> Unit
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1)
val bobListener = object : VerificationService.Listener {
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState
when (uxState) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
}
else -> Unit
}
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
bobSASLatch.countDown()
}
}
}
bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
val verificationSAS = aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
testHelper.await(aliceSASLatch)
testHelper.await(bobSASLatch)
val aliceTx = aliceVerificationService.getExistingTransaction(bobUserId, verificationSAS!!) as SASDefaultVerificationTransaction
val bobTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, verificationSAS) as SASDefaultVerificationTransaction
assertEquals(
"Should have same SAS", aliceTx.getShortCodeRepresentation(SasMode.DECIMAL),
bobTx.getShortCodeRepresentation(SasMode.DECIMAL)
)
}
@Test @Test
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val sasVerificationTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
val transactionId = sasVerificationTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, listOf(VerificationMethod.SAS))
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService() val bobVerificationService = bobSession!!.cryptoService().verificationService()
val aliceSASLatch = CountDownLatch(1) val verifiedLatch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
var matchOnce = true
override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as OutgoingSasVerificationTransaction).uxState
Log.v("TEST", "== aliceState ${uxState.name}")
when (uxState) {
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
tx.userHasVerifiedShortCode()
}
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
if (matchOnce) {
matchOnce = false
aliceSASLatch.countDown()
}
}
else -> Unit
}
}
}
aliceVerificationService.addListener(aliceListener)
val bobSASLatch = CountDownLatch(1) override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
val bobListener = object : VerificationService.Listener { Timber.v("RequestUpdated pr=$pr")
var acceptOnce = true }
var matchOnce = true
var matched = false
var verified = false
override fun transactionUpdated(tx: VerificationTransaction) { override fun transactionUpdated(tx: VerificationTransaction) {
val uxState = (tx as IncomingSasVerificationTransaction).uxState Timber.v("Alice transactionUpdated: ${tx.state} on thread:${Thread.currentThread()}")
Log.v("TEST", "== bobState ${uxState.name}") if (tx !is SasVerificationTransaction) return
when (uxState) { when (tx.state) {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> { VerificationTxState.SasShortCodeReady -> {
if (acceptOnce) { if (!matched) {
acceptOnce = false matched = true
tx.performAccept() runBlocking {
delay(500)
tx.userHasVerifiedShortCode()
}
} }
} }
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> { VerificationTxState.Verified -> {
if (matchOnce) { if (!verified) {
matchOnce = false verified = true
tx.userHasVerifiedShortCode() verifiedLatch.countDown()
} }
} }
IncomingSasVerificationTransaction.UxState.VERIFIED -> { else -> Unit
bobSASLatch.countDown()
}
else -> Unit
} }
} }
} }
bobVerificationService.addListener(bobListener) // aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
var accepted = false
var matched = false
var verified = false
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("RequestUpdated: pr=$pr")
}
override fun transactionUpdated(tx: VerificationTransaction) {
Timber.v("Bob transactionUpdated: ${tx.state} on thread: ${Thread.currentThread()}")
if (tx !is SasVerificationTransaction) return
when (tx.state) {
// VerificationTxState.SasStarted -> {
// if (!accepted) {
// accepted = true
// runBlocking {
// tx.acceptVerification()
// }
// }
// }
VerificationTxState.SasShortCodeReady -> {
if (!matched) {
matched = true
runBlocking {
delay(500)
tx.userHasVerifiedShortCode()
}
}
}
VerificationTxState.Verified -> {
if (!verified) {
verified = true
verifiedLatch.countDown()
}
}
else -> Unit
}
}
}
// bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId val bobDeviceId = runBlocking {
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null) bobSession.cryptoService().getMyCryptoDevice().deviceId
testHelper.await(aliceSASLatch) }
testHelper.await(bobSASLatch) aliceVerificationService.startKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
Timber.v("Await after beginKey ${Thread.currentThread()}")
testHelper.await(verifiedLatch)
// Assert that devices are verified // Assert that devices are verified
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId) val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? = val aliceDeviceInfoFromBobPOV: CryptoDeviceInfo? =
bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyDevice().deviceId) bobSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.cryptoService().getMyCryptoDevice().deviceId)
assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified) assertTrue("alice device should be verified from bob point of view", aliceDeviceInfoFromBobPOV!!.isVerified)
assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified) assertTrue("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
@ -530,27 +546,21 @@ class SASTest : InstrumentedTest {
@Test @Test
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper -> fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom() val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession val bobSession = cryptoTestData.secondSession!!
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession!!.cryptoService().verificationService() val bobVerificationService = bobSession.cryptoService().verificationService()
val req = aliceVerificationService.requestKeyVerificationInDMs( val req = aliceVerificationService.requestKeyVerificationInDMs(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
bobSession.myUserId, bobSession.myUserId,
cryptoTestData.roomId cryptoTestData.roomId
) )
var requestID: String? = null val requestID = req.transactionId
testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
requestID = prAlicePOV?.transactionId
Log.v("TEST", "== alicePOV is $prAlicePOV")
prAlicePOV?.transactionId != null && prAlicePOV.localId == req.localId
}
Log.v("TEST", "== requestID is $requestID") Log.v("TEST", "== requestID is $requestID")
@ -563,31 +573,27 @@ class SASTest : InstrumentedTest {
bobVerificationService.readyPendingVerification( bobVerificationService.readyPendingVerification(
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW), listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
aliceSession.myUserId, aliceSession.myUserId,
requestID!! requestID
) )
// wait for alice to get the ready // wait for alice to get the ready
testHelper.retryPeriodically { testHelper.retryPeriodically {
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull() val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
Log.v("TEST", "== prAlicePOV is $prAlicePOV") Log.v("TEST", "== prAlicePOV is $prAlicePOV")
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null prAlicePOV?.transactionId == requestID && prAlicePOV.state == EVerificationState.Ready
} }
// Start concurrent! // Start concurrent!
aliceVerificationService.beginKeyVerificationInDMs( aliceVerificationService.startKeyVerification(
VerificationMethod.SAS, method = VerificationMethod.SAS,
requestID!!, otherUserId = bobSession.myUserId,
cryptoTestData.roomId, requestId = requestID,
bobSession.myUserId,
bobSession.sessionParams.deviceId!!
) )
bobVerificationService.beginKeyVerificationInDMs( bobVerificationService.startKeyVerification(
VerificationMethod.SAS, method = VerificationMethod.SAS,
requestID!!, otherUserId = aliceSession.myUserId,
cryptoTestData.roomId, requestId = requestID,
aliceSession.myUserId,
aliceSession.sessionParams.deviceId!!
) )
// we should reach SHOW SAS on both // we should reach SHOW SAS on both
@ -595,15 +601,15 @@ class SASTest : InstrumentedTest {
var bobPovTx: SasVerificationTransaction? var bobPovTx: SasVerificationTransaction?
testHelper.retryPeriodically { testHelper.retryPeriodically {
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== alicePovTx is $alicePovTx") Log.v("TEST", "== alicePovTx is $alicePovTx")
alicePovTx?.state == VerificationTxState.ShortCodeReady alicePovTx?.state == VerificationTxState.SasShortCodeReady
} }
// wait for alice to get the ready // wait for alice to get the ready
testHelper.retryPeriodically { testHelper.retryPeriodically {
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction
Log.v("TEST", "== bobPovTx is $bobPovTx") Log.v("TEST", "== bobPovTx is $bobPovTx")
bobPovTx?.state == VerificationTxState.ShortCodeReady bobPovTx?.state == VerificationTxState.SasShortCodeReady
} }
} }
} }

View File

@ -0,0 +1,128 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import kotlinx.coroutines.runBlocking
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper
import org.matrix.android.sdk.common.CryptoTestData
import org.matrix.android.sdk.common.CryptoTestHelper
import timber.log.Timber
import java.util.concurrent.CountDownLatch
class SasVerificationTestHelper(private val testHelper: CommonTestHelper, private val cryptoTestHelper: CryptoTestHelper) {
suspend fun requestVerificationAndWaitForReadyState(cryptoTestData: CryptoTestData, supportedMethods: List<VerificationMethod>): String {
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request
Timber.v("Alice request updated: $pr")
if (pr.state == EVerificationState.Ready) {
latch.countDown()
}
}
}
// aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request
Timber.v("Bob accepts the verification request")
runBlocking {
bobVerificationService.readyPendingVerification(
supportedMethods,
aliceSession.myUserId,
pr.transactionId
)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready
Timber.v("Bob request updated $pr")
if (pr.state == EVerificationState.Ready) {
bobReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
// bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(supportedMethods, bobUserId, cryptoTestData.roomId)
testHelper.await(latch)
// bobVerificationService.removeListener(bobListener)
// aliceVerificationService.removeListener(aliceListener)
return bobReadyPendingVerificationRequest?.transactionId!!
}
suspend fun requestSelfKeyAndWaitForReadyState(session1: Session, session2: Session, supportedMethods: List<VerificationMethod>): String {
val session1VerificationService = session1.cryptoService().verificationService()
val session2VerificationService = session2.cryptoService().verificationService()
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
if (pr.state == EVerificationState.Ready) {
latch.countDown()
}
}
}
// session1VerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
runBlocking {
session2VerificationService.readyPendingVerification(
supportedMethods,
session1.myUserId,
pr.transactionId
)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
Timber.v("Bob request updated $pr")
if (pr.state == EVerificationState.Ready) {
bobReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
// session2VerificationService.addListener(bobListener)
session1VerificationService.requestSelfKeyVerification(supportedMethods)
testHelper.await(latch)
// session2VerificationService.removeListener(bobListener)
// session1VerificationService.removeListener(aliceListener)
return bobReadyPendingVerificationRequest?.transactionId!!
}
}

View File

@ -0,0 +1,221 @@
/*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
import timber.log.Timber
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class VerificationTest : InstrumentedTest {
data class ExpectedResult(
val sasIsSupported: Boolean = false,
val otherCanScanQrCode: Boolean = false,
val otherCanShowQrCode: Boolean = false
)
private val sas = listOf(
VerificationMethod.SAS
)
private val sasShow = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SHOW
)
private val sasScan = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SCAN
)
private val sasShowScan = listOf(
VerificationMethod.SAS,
VerificationMethod.QR_CODE_SHOW,
VerificationMethod.QR_CODE_SCAN
)
@Test
fun test_aliceAndBob_sas_sas() = doTest(
sas,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_sas_show() = doTest(
sas,
sasShow,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_sas() = doTest(
sasShow,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_sas_scan() = doTest(
sas,
sasScan,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_scan_sas() = doTest(
sasScan,
sas,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_scan_scan() = doTest(
sasScan,
sasScan,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_show() = doTest(
sasShow,
sasShow,
ExpectedResult(sasIsSupported = true),
ExpectedResult(sasIsSupported = true)
)
@Test
fun test_aliceAndBob_show_scan() = doTest(
sasShow,
sasScan,
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true)
)
@Test
fun test_aliceAndBob_scan_show() = doTest(
sasScan,
sasShow,
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanScanQrCode = true)
)
@Test
fun test_aliceAndBob_all_all() = doTest(
sasShowScan,
sasShowScan,
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true),
ExpectedResult(sasIsSupported = true, otherCanShowQrCode = true, otherCanScanQrCode = true)
)
private fun doTest(
aliceSupportedMethods: List<VerificationMethod>,
bobSupportedMethods: List<VerificationMethod>,
expectedResultForAlice: ExpectedResult,
expectedResultForBob: ExpectedResult
) = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!!
cryptoTestHelper.initializeCrossSigning(aliceSession)
cryptoTestHelper.initializeCrossSigning(bobSession)
val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService()
var aliceReadyPendingVerificationRequest: PendingVerificationRequest? = null
var bobReadyPendingVerificationRequest: PendingVerificationRequest? = null
val latch = CountDownLatch(2)
val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request
Timber.v("Alice is ready: ${pr.state}")
if (pr.state == EVerificationState.Ready) {
aliceReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
// aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request
Timber.v("Bob accepts the verification request")
runBlocking {
bobVerificationService.readyPendingVerification(
bobSupportedMethods,
aliceSession.myUserId,
pr.transactionId
)
}
}
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready
Timber.v("Bob is ready: ${pr.state}")
if (pr.state == EVerificationState.Ready) {
bobReadyPendingVerificationRequest = pr
latch.countDown()
}
}
}
// bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request
aliceVerificationService.requestKeyVerificationInDMs(aliceSupportedMethods, bobUserId, cryptoTestData.roomId)
testHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
pr.otherCanShowQrCode shouldBe expectedResultForAlice.otherCanShowQrCode
pr.otherCanScanQrCode shouldBe expectedResultForAlice.otherCanScanQrCode
}
bobReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
pr.otherCanShowQrCode shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode shouldBe expectedResultForBob.otherCanScanQrCode
}
cryptoTestData.cleanUp(testHelper)
}
}

View File

@ -1,46 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.amshove.kluent.shouldBe
import org.amshove.kluent.shouldNotBeEqualTo
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import org.matrix.android.sdk.InstrumentedTest
@RunWith(AndroidJUnit4::class)
@FixMethodOrder(MethodSorters.JVM)
class SharedSecretTest : InstrumentedTest {
@Test
fun testSharedSecretLengthCase() {
repeat(100) {
generateSharedSecretV2().length shouldBe 11
}
}
@Test
fun testSharedDiffCase() {
val sharedSecret1 = generateSharedSecretV2()
val sharedSecret2 = generateSharedSecretV2()
sharedSecret1 shouldNotBeEqualTo sharedSecret2
}
}

View File

@ -17,6 +17,9 @@
package org.matrix.android.sdk.internal.crypto.verification.qrcode package org.matrix.android.sdk.internal.crypto.verification.qrcode
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBe
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Ignore import org.junit.Ignore
@ -29,7 +32,9 @@ import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.auth.UserPasswordAuth import org.matrix.android.sdk.api.auth.UserPasswordAuth
import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse import org.matrix.android.sdk.api.auth.registration.RegistrationFlowResponse
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationEvent
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest import org.matrix.android.sdk.common.CommonTestHelper.Companion.runCryptoTest
@ -164,7 +169,6 @@ class VerificationTest : InstrumentedTest {
val aliceSession = cryptoTestData.firstSession val aliceSession = cryptoTestData.firstSession
val bobSession = cryptoTestData.secondSession!! val bobSession = cryptoTestData.secondSession!!
testHelper.waitForCallback<Unit> { callback ->
aliceSession.cryptoService().crossSigningService() aliceSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -177,11 +181,9 @@ class VerificationTest : InstrumentedTest {
) )
) )
} }
}, callback }
) )
}
testHelper.waitForCallback<Unit> { callback ->
bobSession.cryptoService().crossSigningService() bobSession.cryptoService().crossSigningService()
.initializeCrossSigning( .initializeCrossSigning(
object : UserInteractiveAuthInterceptor { object : UserInteractiveAuthInterceptor {
@ -194,9 +196,8 @@ class VerificationTest : InstrumentedTest {
) )
) )
} }
}, callback }
) )
}
val aliceVerificationService = aliceSession.cryptoService().verificationService() val aliceVerificationService = aliceSession.cryptoService().verificationService()
val bobVerificationService = bobSession.cryptoService().verificationService() val bobVerificationService = bobSession.cryptoService().verificationService()
@ -208,34 +209,35 @@ class VerificationTest : InstrumentedTest {
val aliceListener = object : VerificationService.Listener { val aliceListener = object : VerificationService.Listener {
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 4: Alice receive the ready request // Step 4: Alice receive the ready request
if (pr.isReady) { if (pr.state == EVerificationState.Ready) {
aliceReadyPendingVerificationRequest = pr aliceReadyPendingVerificationRequest = pr
latch.countDown() latch.countDown()
} }
} }
} }
aliceVerificationService.addListener(aliceListener) // aliceVerificationService.addListener(aliceListener)
val bobListener = object : VerificationService.Listener { val bobListener = object : VerificationService.Listener {
override fun verificationRequestCreated(pr: PendingVerificationRequest) { override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// Step 2: Bob accepts the verification request // Step 2: Bob accepts the verification request
bobVerificationService.readyPendingVerificationInDMs( runBlocking {
bobSupportedMethods, bobVerificationService.readyPendingVerification(
aliceSession.myUserId, bobSupportedMethods,
cryptoTestData.roomId, aliceSession.myUserId,
pr.transactionId!! pr.transactionId
) )
}
} }
override fun verificationRequestUpdated(pr: PendingVerificationRequest) { override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
// Step 3: Bob is ready // Step 3: Bob is ready
if (pr.isReady) { if (pr.state == EVerificationState.Ready) {
bobReadyPendingVerificationRequest = pr bobReadyPendingVerificationRequest = pr
latch.countDown() latch.countDown()
} }
} }
} }
bobVerificationService.addListener(bobListener) // bobVerificationService.addListener(bobListener)
val bobUserId = bobSession.myUserId val bobUserId = bobSession.myUserId
// Step 1: Alice starts a verification request // Step 1: Alice starts a verification request
@ -243,15 +245,15 @@ class VerificationTest : InstrumentedTest {
testHelper.await(latch) testHelper.await(latch)
aliceReadyPendingVerificationRequest!!.let { pr -> aliceReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported pr.isSasSupported shouldBe expectedResultForAlice.sasIsSupported
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode pr.otherCanShowQrCode shouldBe expectedResultForAlice.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode pr.otherCanScanQrCode shouldBe expectedResultForAlice.otherCanScanQrCode
} }
bobReadyPendingVerificationRequest!!.let { pr -> bobReadyPendingVerificationRequest!!.let { pr ->
pr.isSasSupported() shouldBe expectedResultForBob.sasIsSupported pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
pr.otherCanShowQrCode() shouldBe expectedResultForBob.otherCanShowQrCode pr.otherCanShowQrCode shouldBe expectedResultForBob.otherCanShowQrCode
pr.otherCanScanQrCode() shouldBe expectedResultForBob.otherCanScanQrCode pr.otherCanScanQrCode shouldBe expectedResultForBob.otherCanScanQrCode
} }
} }
@ -273,21 +275,42 @@ class VerificationTest : InstrumentedTest {
val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService() val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService()
val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService() val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService()
serviceOfVerifier.addListener(object : VerificationService.Listener { var job: Job? = null
override fun verificationRequestCreated(pr: PendingVerificationRequest) { job = async {
// Accept verification request serviceOfVerifier.requestEventFlow().collect {
serviceOfVerifier.readyPendingVerification( when (it) {
verificationMethods, is VerificationEvent.RequestAdded -> {
pr.otherUserId, val pr = it.request
pr.transactionId!!, serviceOfVerifier.readyPendingVerification(
) verificationMethods,
pr.otherUserId,
pr.transactionId!!,
)
job?.cancel()
}
is VerificationEvent.RequestUpdated,
is VerificationEvent.TransactionAdded,
is VerificationEvent.TransactionUpdated -> {
}
}
} }
}) }
job.await()
// serviceOfVerifier.addListener(object : VerificationService.Listener {
// override fun verificationRequestCreated(pr: PendingVerificationRequest) {
// // Accept verification request
// runBlocking {
// serviceOfVerifier.readyPendingVerification(
// verificationMethods,
// pr.otherUserId,
// pr.transactionId!!,
// )
// }
// }
// })
serviceOfVerified.requestKeyVerification( serviceOfVerified.requestSelfKeyVerification(
methods = verificationMethods, methods = verificationMethods,
otherUserId = aliceSessionToVerify.myUserId,
otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId),
) )
testHelper.retryPeriodically { testHelper.retryPeriodically {

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.api.session.crypto.keysbackup
import org.matrix.android.sdk.api.util.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.tools.withOlmDecryption
import org.matrix.olm.OlmPkMessage
class BackupRecoveryKey(private val key: ByteArray) : IBackupRecoveryKey {
override fun equals(other: Any?): Boolean {
if (other !is BackupRecoveryKey) return false
return this.toBase58() == other.toBase58()
}
override fun toBase58() = computeRecoveryKey(key)
override fun toBase64() = key.toBase64NoPadding()
override fun decryptV1(ephemeralKey: String, mac: String, ciphertext: String): String = withOlmDecryption {
it.setPrivateKey(key)
it.decrypt(OlmPkMessage().apply {
this.mEphemeralKey = ephemeralKey
this.mCipherText = ciphertext
this.mMac = mac
})
}
override fun megolmV1PublicKey() = v1pk
private val v1pk = object : IMegolmV1PublicKey {
override val publicKey: String
get() = withOlmDecryption {
it.setPrivateKey(key)
}
override val privateKeySalt: String?
get() = null // not use in kotlin sdk
override val privateKeyIterations: Int?
get() = null // not use in kotlin sdk
override val backupAlgorithm: String
get() = "" // not use in kotlin sdk
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.api.session.crypto.keysbackup
object BackupUtils {
fun recoveryKeyFromBase58(base58: String): IBackupRecoveryKey? {
return extractCurveKeyFromRecoveryKey(base58)?.let {
BackupRecoveryKey(it)
}
}
fun recoveryKeyFromPassphrase(passphrase: String): IBackupRecoveryKey? {
return extractCurveKeyFromRecoveryKey(passphrase)?.let {
BackupRecoveryKey(it)
}
}
}

View File

@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.verification
interface IncomingSasVerificationTransaction : SasVerificationTransaction { interface IncomingSasVerificationTransaction : SasVerificationTransaction {
val uxState: UxState val uxState: UxState
fun performAccept() override suspend fun acceptVerification()
enum class UxState { enum class UxState {
UNKNOWN, UNKNOWN,

View File

@ -5,7 +5,7 @@
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -1,11 +1,11 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,

View File

@ -36,7 +36,7 @@ data class MessageVerificationRequestContent(
@Json(name = "m.new_content") override val newContent: Content? = null, @Json(name = "m.new_content") override val newContent: Content? = null,
// Not parsed, but set after, using the eventId // Not parsed, but set after, using the eventId
override val transactionId: String? = null override val transactionId: String? = null
) : MessageContent, VerificationInfoRequest { ) : MessageContent, VerificationInfoRequest {
override fun toEventContent() = toContent() override fun toEventContent() = toContent()
} }

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.session.sync.handler package org.matrix.android.sdk.internal.session.sync.handler
import dagger.Lazy
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_OLM
import org.matrix.android.sdk.api.logger.LoggerTag import org.matrix.android.sdk.api.logger.LoggerTag
import org.matrix.android.sdk.api.session.crypto.MXCryptoError import org.matrix.android.sdk.api.session.crypto.MXCryptoError
@ -26,8 +27,6 @@ import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent import org.matrix.android.sdk.api.session.events.model.content.OlmEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.session.room.model.message.MessageContent import org.matrix.android.sdk.api.session.room.model.message.MessageContent
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.internal.crypto.DefaultCryptoService import org.matrix.android.sdk.internal.crypto.DefaultCryptoService
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
import org.matrix.android.sdk.internal.session.sync.ProgressReporter import org.matrix.android.sdk.internal.session.sync.ProgressReporter
@ -37,15 +36,14 @@ import javax.inject.Inject
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO) private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
internal class CryptoSyncHandler @Inject constructor( internal class CryptoSyncHandler @Inject constructor(
private val cryptoService: DefaultCryptoService, private val cryptoService: Lazy<DefaultCryptoService>,
private val verificationService: DefaultVerificationService private val verificationService: DefaultVerificationService
) { ) {
suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) { suspend fun handleToDevice(eventList: List<Event>, progressReporter: ProgressReporter? = null) {
val total = toDevice.events?.size ?: 0 val total = eventList.size
toDevice.events eventList.filter { isSupportedToDevice(it) }
?.filter { isSupportedToDevice(it) } .forEachIndexed { index, event ->
?.forEachIndexed { index, event ->
progressReporter?.reportProgress(index * 100F / total) progressReporter?.reportProgress(index * 100F / total)
// Decrypt event if necessary // Decrypt event if necessary
Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}") Timber.tag(loggerTag.value).i("To device event from ${event.senderId} of type:${event.type}")
@ -55,7 +53,7 @@ internal class CryptoSyncHandler @Inject constructor(
Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}") Timber.tag(loggerTag.value).e("handleToDeviceEvent() : Warning: Unable to decrypt to-device event : ${event.content}")
} else { } else {
verificationService.onToDeviceEvent(event) verificationService.onToDeviceEvent(event)
cryptoService.onToDeviceEvent(event) cryptoService.get().onToDeviceEvent(event)
} }
} }
} }
@ -82,10 +80,6 @@ internal class CryptoSyncHandler @Inject constructor(
} }
} }
fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoService.onSyncCompleted(syncResponse)
}
/** /**
* Decrypt an encrypted event. * Decrypt an encrypted event.
* *
@ -98,12 +92,12 @@ internal class CryptoSyncHandler @Inject constructor(
if (event.getClearType() == EventType.ENCRYPTED) { if (event.getClearType() == EventType.ENCRYPTED) {
var result: MXEventDecryptionResult? = null var result: MXEventDecryptionResult? = null
try { try {
result = cryptoService.decryptEvent(event, timelineId ?: "") result = cryptoService.get().decryptEvent(event, timelineId ?: "")
} catch (exception: MXCryptoError) { } catch (exception: MXCryptoError) {
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError) event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>" val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
// try to find device id to ease log reading // try to find device id to ease log reading
val deviceId = cryptoService.getCryptoDeviceInfo(event.senderId!!).firstOrNull { val deviceId = cryptoService.get().getCryptoDeviceInfo(event.senderId!!).firstOrNull {
it.identityKey() == senderKey it.identityKey() == senderKey
}?.deviceId ?: senderKey }?.deviceId ?: senderKey
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>") Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")

View File

@ -0,0 +1,262 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.crypto
import dagger.Binds
import dagger.Module
import dagger.Provides
import io.realm.RealmConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.session.crypto.CryptoService
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.verification.VerificationService
import org.matrix.android.sdk.internal.crypto.api.CryptoApi
import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningService
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.keysbackup.api.RoomKeysApi
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.CreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultCreateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteBackupTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultDeleteSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultGetSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultStoreSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DefaultUpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteBackupTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.DeleteSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupLastVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.GetSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreRoomSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.StoreSessionsDataTask
import org.matrix.android.sdk.internal.crypto.keysbackup.tasks.UpdateKeysBackupVersionTask
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStore
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreMigration
import org.matrix.android.sdk.internal.crypto.store.db.RealmCryptoStoreModule
import org.matrix.android.sdk.internal.crypto.tasks.ClaimOneTimeKeysForUsersDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultClaimOneTimeKeysForUsersDevice
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultDownloadKeysForUsers
import org.matrix.android.sdk.internal.crypto.tasks.DefaultEncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultGetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultInitializeCrossSigningTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendEventTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultSetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.DefaultUploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.DeleteDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.DownloadKeysForUsersTask
import org.matrix.android.sdk.internal.crypto.tasks.EncryptEventTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDeviceInfoTask
import org.matrix.android.sdk.internal.crypto.tasks.GetDevicesTask
import org.matrix.android.sdk.internal.crypto.tasks.InitializeCrossSigningTask
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.crypto.tasks.SetDeviceNameTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSignaturesTask
import org.matrix.android.sdk.internal.crypto.tasks.UploadSigningKeysTask
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationService
import org.matrix.android.sdk.internal.database.RealmKeysUtils
import org.matrix.android.sdk.internal.di.CryptoDatabase
import org.matrix.android.sdk.internal.di.SessionFilesDirectory
import org.matrix.android.sdk.internal.di.UserMd5
import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.cache.ClearCacheTask
import org.matrix.android.sdk.internal.session.cache.RealmClearCacheTask
import retrofit2.Retrofit
import java.io.File
@Module
internal abstract class CryptoModule {
@Module
companion object {
internal fun getKeyAlias(userMd5: String) = "crypto_module_$userMd5"
@JvmStatic
@Provides
@CryptoDatabase
@SessionScope
fun providesRealmConfiguration(
@SessionFilesDirectory directory: File,
@UserMd5 userMd5: String,
realmKeysUtils: RealmKeysUtils,
realmCryptoStoreMigration: RealmCryptoStoreMigration
): RealmConfiguration {
return RealmConfiguration.Builder()
.directory(directory)
.apply {
realmKeysUtils.configureEncryption(this, getKeyAlias(userMd5))
}
.name("crypto_store.realm")
.modules(RealmCryptoStoreModule())
.allowWritesOnUiThread(true)
.schemaVersion(realmCryptoStoreMigration.schemaVersion)
.migration(realmCryptoStoreMigration)
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoCoroutineScope(coroutineDispatchers: MatrixCoroutineDispatchers): CoroutineScope {
return CoroutineScope(SupervisorJob() + coroutineDispatchers.crypto)
}
@JvmStatic
@Provides
@CryptoDatabase
fun providesClearCacheTask(@CryptoDatabase realmConfiguration: RealmConfiguration): ClearCacheTask {
return RealmClearCacheTask(realmConfiguration)
}
@JvmStatic
@Provides
@SessionScope
fun providesCryptoAPI(retrofit: Retrofit): CryptoApi {
return retrofit.create(CryptoApi::class.java)
}
@JvmStatic
@Provides
@SessionScope
fun providesRoomKeysAPI(retrofit: Retrofit): RoomKeysApi {
return retrofit.create(RoomKeysApi::class.java)
}
}
@Binds
abstract fun bindCryptoService(service: DefaultCryptoService): CryptoService
@Binds
abstract fun bindKeysBackupService(service: DefaultKeysBackupService): KeysBackupService
@Binds
abstract fun bindDeleteDeviceTask(task: DefaultDeleteDeviceTask): DeleteDeviceTask
@Binds
abstract fun bindGetDevicesTask(task: DefaultGetDevicesTask): GetDevicesTask
@Binds
abstract fun bindGetDeviceInfoTask(task: DefaultGetDeviceInfoTask): GetDeviceInfoTask
@Binds
abstract fun bindSetDeviceNameTask(task: DefaultSetDeviceNameTask): SetDeviceNameTask
@Binds
abstract fun bindUploadKeysTask(task: DefaultUploadKeysTask): UploadKeysTask
@Binds
abstract fun bindUploadSigningKeysTask(task: DefaultUploadSigningKeysTask): UploadSigningKeysTask
@Binds
abstract fun bindUploadSignaturesTask(task: DefaultUploadSignaturesTask): UploadSignaturesTask
@Binds
abstract fun bindDownloadKeysForUsersTask(task: DefaultDownloadKeysForUsers): DownloadKeysForUsersTask
@Binds
abstract fun bindCreateKeysBackupVersionTask(task: DefaultCreateKeysBackupVersionTask): CreateKeysBackupVersionTask
@Binds
abstract fun bindDeleteBackupTask(task: DefaultDeleteBackupTask): DeleteBackupTask
@Binds
abstract fun bindDeleteRoomSessionDataTask(task: DefaultDeleteRoomSessionDataTask): DeleteRoomSessionDataTask
@Binds
abstract fun bindDeleteRoomSessionsDataTask(task: DefaultDeleteRoomSessionsDataTask): DeleteRoomSessionsDataTask
@Binds
abstract fun bindDeleteSessionsDataTask(task: DefaultDeleteSessionsDataTask): DeleteSessionsDataTask
@Binds
abstract fun bindGetKeysBackupLastVersionTask(task: DefaultGetKeysBackupLastVersionTask): GetKeysBackupLastVersionTask
@Binds
abstract fun bindGetKeysBackupVersionTask(task: DefaultGetKeysBackupVersionTask): GetKeysBackupVersionTask
@Binds
abstract fun bindGetRoomSessionDataTask(task: DefaultGetRoomSessionDataTask): GetRoomSessionDataTask
@Binds
abstract fun bindGetRoomSessionsDataTask(task: DefaultGetRoomSessionsDataTask): GetRoomSessionsDataTask
@Binds
abstract fun bindGetSessionsDataTask(task: DefaultGetSessionsDataTask): GetSessionsDataTask
@Binds
abstract fun bindStoreRoomSessionDataTask(task: DefaultStoreRoomSessionDataTask): StoreRoomSessionDataTask
@Binds
abstract fun bindStoreRoomSessionsDataTask(task: DefaultStoreRoomSessionsDataTask): StoreRoomSessionsDataTask
@Binds
abstract fun bindStoreSessionsDataTask(task: DefaultStoreSessionsDataTask): StoreSessionsDataTask
@Binds
abstract fun bindUpdateKeysBackupVersionTask(task: DefaultUpdateKeysBackupVersionTask): UpdateKeysBackupVersionTask
@Binds
abstract fun bindSendToDeviceTask(task: DefaultSendToDeviceTask): SendToDeviceTask
@Binds
abstract fun bindEncryptEventTask(task: DefaultEncryptEventTask): EncryptEventTask
@Binds
abstract fun bindSendVerificationMessageTask(task: DefaultSendVerificationMessageTask): SendVerificationMessageTask
@Binds
abstract fun bindClaimOneTimeKeysForUsersDeviceTask(task: DefaultClaimOneTimeKeysForUsersDevice): ClaimOneTimeKeysForUsersDeviceTask
@Binds
abstract fun bindCrossSigningService(service: DefaultCrossSigningService): CrossSigningService
@Binds
abstract fun bindVerificationService(service: DefaultVerificationService): VerificationService
@Binds
abstract fun bindCryptoStore(store: RealmCryptoStore): IMXCryptoStore
@Binds
abstract fun bindSendEventTask(task: DefaultSendEventTask): SendEventTask
@Binds
abstract fun bindInitalizeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask
}

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.crypto
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.content.EncryptedEventContent
import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import javax.inject.Inject
internal class DecryptRoomEventUseCase @Inject constructor(
private val olmDevice: MXOlmDevice,
private val cryptoStore: IMXCryptoStore,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
) {
suspend operator fun invoke(event: Event, requestKeysOnFail: Boolean = true): MXEventDecryptionResult {
if (event.roomId.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
if (encryptedEventContent.senderKey.isNullOrBlank() ||
encryptedEventContent.sessionId.isNullOrBlank() ||
encryptedEventContent.ciphertext.isNullOrBlank()) {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
try {
val olmDecryptionResult = olmDevice.decryptGroupMessage(
encryptedEventContent.ciphertext,
event.roomId,
"",
eventId = event.eventId.orEmpty(),
encryptedEventContent.sessionId,
encryptedEventContent.senderKey
)
if (olmDecryptionResult.payload != null) {
return MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
.orEmpty(),
isSafe = olmDecryptionResult.isSafe.orFalse()
)
} else {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
} catch (throwable: Throwable) {
if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
// So we know that session, but it's ratcheted and we can't decrypt at that index
// Check if partially withheld
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
if (withHeldInfo != null) {
// Encapsulate as withHeld exception
throw MXCryptoError.Base(
MXCryptoError.ErrorType.KEYS_WITHHELD,
withHeldInfo.code?.value ?: "",
withHeldInfo.reason
)
}
throw MXCryptoError.Base(
MXCryptoError.ErrorType.UNKNOWN_MESSAGE_INDEX,
"UNKNOWN_MESSAGE_INDEX",
null
)
}
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
throw MXCryptoError.Base(
MXCryptoError.ErrorType.OLM,
reason,
detailedReason
)
}
if (throwable is MXCryptoError.Base) {
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
// Check if it was withheld by sender to enrich error code
val withHeldInfo = cryptoStore.getWithHeldMegolmSession(event.roomId, encryptedEventContent.sessionId)
if (withHeldInfo != null) {
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
// Encapsulate as withHeld exception
throw MXCryptoError.Base(
MXCryptoError.ErrorType.KEYS_WITHHELD,
withHeldInfo.code?.value ?: "",
withHeldInfo.reason
)
}
if (requestKeysOnFail) {
requestKeysForEvent(event)
}
}
}
throw throwable
}
}
private fun requestKeysForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, false)
}
}

View File

@ -53,7 +53,6 @@ import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListen
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
@ -73,7 +72,10 @@ import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibility
import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent import org.matrix.android.sdk.api.session.room.model.RoomHistoryVisibilityContent
import org.matrix.android.sdk.api.session.room.model.RoomMemberContent import org.matrix.android.sdk.api.session.room.model.RoomMemberContent
import org.matrix.android.sdk.api.session.room.model.shouldShareHistory import org.matrix.android.sdk.api.session.room.model.shouldShareHistory
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
@ -86,6 +88,7 @@ import org.matrix.android.sdk.internal.crypto.crosssigning.DefaultCrossSigningSe
import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService import org.matrix.android.sdk.internal.crypto.keysbackup.DefaultKeysBackupService
import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE import org.matrix.android.sdk.internal.crypto.model.MXKey.Companion.KEY_SIGNED_CURVE_25519_TYPE
import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.SessionInfo
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.toRest import org.matrix.android.sdk.internal.crypto.model.toRest
import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository import org.matrix.android.sdk.internal.crypto.repository.WarnOnUnknownDeviceRepository
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -103,9 +106,7 @@ import org.matrix.android.sdk.internal.extensions.foldToCallback
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.session.StreamEventsManager import org.matrix.android.sdk.internal.session.StreamEventsManager
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.session.sync.handler.CryptoSyncHandler
import org.matrix.android.sdk.internal.task.TaskThread
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.time.Clock import org.matrix.android.sdk.internal.util.time.Clock
@ -181,18 +182,18 @@ internal class DefaultCryptoService @Inject constructor(
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider, private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
private val coroutineDispatchers: MatrixCoroutineDispatchers, private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val taskExecutor: TaskExecutor,
private val cryptoCoroutineScope: CoroutineScope, private val cryptoCoroutineScope: CoroutineScope,
private val eventDecryptor: EventDecryptor, private val eventDecryptor: EventDecryptor,
private val verificationMessageProcessor: VerificationMessageProcessor, private val verificationMessageProcessor: VerificationMessageProcessor,
private val liveEventManager: Lazy<StreamEventsManager>, private val liveEventManager: Lazy<StreamEventsManager>,
private val unrequestedForwardManager: UnRequestedForwardManager, private val unrequestedForwardManager: UnRequestedForwardManager,
private val cryptoSyncHandler: CryptoSyncHandler,
) : CryptoService { ) : CryptoService {
private val isStarting = AtomicBoolean(false) private val isStarting = AtomicBoolean(false)
private val isStarted = AtomicBoolean(false) private val isStarted = AtomicBoolean(false)
fun onStateEvent(roomId: String, event: Event) { override fun onStateEvent(roomId: String, event: Event) {
when (event.type) { when (event.type) {
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event) EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(roomId, event)
EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event) EventType.STATE_ROOM_MEMBER -> onRoomMembershipEvent(roomId, event)
@ -200,7 +201,7 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) { override fun onLiveEvent(roomId: String, event: Event, isInitialSync: Boolean) {
// handle state events // handle state events
if (event.isStateEvent()) { if (event.isStateEvent()) {
when (event.type) { when (event.type) {
@ -214,7 +215,7 @@ internal class DefaultCryptoService @Inject constructor(
if (!isInitialSync) { if (!isInitialSync) {
if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) { if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) { cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
verificationMessageProcessor.process(event) verificationMessageProcessor.process(roomId, event)
} }
} }
} }
@ -222,65 +223,44 @@ internal class DefaultCryptoService @Inject constructor(
// val gossipingBuffer = mutableListOf<Event>() // val gossipingBuffer = mutableListOf<Event>()
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) { override suspend fun setDeviceName(deviceId: String, deviceName: String) {
setDeviceNameTask setDeviceNameTask
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) { .execute(SetDeviceNameTask.Params(deviceId, deviceName))
this.executionThread = TaskThread.CRYPTO cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
this.callback = object : MatrixCallback<Unit> { downloadKeys(listOf(userId), true)
override fun onSuccess(data: Unit) { }
// bg refresh of crypto device
downloadKeys(listOf(userId), true, NoOpMatrixCallback())
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}
}
.executeBy(taskExecutor)
} }
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) { override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
deleteDeviceTask withContext(coroutineDispatchers.crypto) {
.configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) { deleteDeviceTask
this.executionThread = TaskThread.CRYPTO .execute(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null))
this.callback = callback }
}
.executeBy(taskExecutor)
} }
override fun getCryptoVersion(context: Context, longFormat: Boolean): String { override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
} }
override fun getMyDevice(): CryptoDeviceInfo { override suspend fun getMyCryptoDevice(): CryptoDeviceInfo {
return myDeviceInfoHolder.get().myDevice return myDeviceInfoHolder.get().myDevice
} }
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) { override suspend fun fetchDevicesList(): List<DeviceInfo> {
getDevicesTask val data = getDevicesTask
.configureWith { .execute(Unit)
// this.executionThread = TaskThread.CRYPTO cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
this.callback = object : MatrixCallback<DevicesListResponse> { return data.devices.orEmpty()
override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
override fun onSuccess(data: DevicesListResponse) {
// Save in local DB
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
callback.onSuccess(data)
}
}
}
.executeBy(taskExecutor)
} }
override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> { override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
return cryptoStore.getLiveMyDevicesInfo() return cryptoStore.getLiveMyDevicesInfo()
} }
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
return getDeviceInfoTask.execute(GetDeviceInfoTask.Params(deviceId))
}
override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> { override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
return cryptoStore.getLiveMyDevicesInfo(deviceId) return cryptoStore.getLiveMyDevicesInfo(deviceId)
} }
@ -289,8 +269,10 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getMyDevicesInfo() return cryptoStore.getMyDevicesInfo()
} }
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int { override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp) return withContext(coroutineDispatchers.io) {
cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
}
} }
/** /**
@ -308,7 +290,7 @@ internal class DefaultCryptoService @Inject constructor(
* *
* @return true if the crypto is started * @return true if the crypto is started
*/ */
fun isStarted(): Boolean { override fun isStarted(): Boolean {
return isStarted.get() return isStarted.get()
} }
@ -328,14 +310,12 @@ internal class DefaultCryptoService @Inject constructor(
* devices. * devices.
* *
*/ */
fun start() { override fun start() {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
internalStart() internalStart()
} tryOrNull("Failed to update device list on start") {
// Just update fetchDevicesList()
fetchDevicesList(NoOpMatrixCallback()) }
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
cryptoStore.tidyUpDataBase() cryptoStore.tidyUpDataBase()
} }
} }
@ -387,6 +367,7 @@ internal class DefaultCryptoService @Inject constructor(
return return
} }
isStarting.set(true) isStarting.set(true)
ensureDevice()
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
@ -398,7 +379,7 @@ internal class DefaultCryptoService @Inject constructor(
/** /**
* Close the crypto. * Close the crypto.
*/ */
fun close() = runBlocking(coroutineDispatchers.crypto) { override fun close() = runBlocking(coroutineDispatchers.crypto) {
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module")) cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
incomingKeyRequestManager.close() incomingKeyRequestManager.close()
outgoingKeyRequestManager.close() outgoingKeyRequestManager.close()
@ -427,80 +408,84 @@ internal class DefaultCryptoService @Inject constructor(
* *
* @param syncResponse the syncResponse * @param syncResponse the syncResponse
*/ */
fun onSyncCompleted(syncResponse: SyncResponse) { override suspend fun onSyncCompleted(syncResponse: SyncResponse) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { // if (syncResponse.deviceLists != null) {
runCatching { // deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
if (syncResponse.deviceLists != null) { // }
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left) // if (syncResponse.deviceOneTimeKeysCount != null) {
} // val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
if (syncResponse.deviceOneTimeKeysCount != null) { // oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0 // }
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
// unwedge if needed // unwedge if needed
try { try {
eventDecryptor.unwedgeDevicesIfNeeded() eventDecryptor.unwedgeDevicesIfNeeded()
} catch (failure: Throwable) { } catch (failure: Throwable) {
Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed") Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
} }
// There is a limit of to_device events returned per sync. // There is a limit of to_device events returned per sync.
// If we are in a case of such limited to_device sync we can't try to generate/upload // If we are in a case of such limited to_device sync we can't try to generate/upload
// new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate // new otk now, because there might be some pending olm pre-key to_device messages that would fail if we rotate
// the old otk too early. In this case we want to wait for the pending to_device before doing anything // the old otk too early. In this case we want to wait for the pending to_device before doing anything
// As per spec: // As per spec:
// If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response. // If there is a large queue of send-to-device messages, the server should limit the number sent in each /sync response.
// 100 messages is recommended as a reasonable limit. // 100 messages is recommended as a reasonable limit.
// The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure // The limit is not part of the spec, so it's probably safer to handle that when there are no more to_device ( so we are sure
// that there are no pending to_device // that there are no pending to_device
val toDevices = syncResponse.toDevice?.events.orEmpty() val toDevices = syncResponse.toDevice?.events.orEmpty()
if (isStarted() && toDevices.isEmpty()) { if (isStarted() && toDevices.isEmpty()) {
// Make sure we process to-device messages before generating new one-time-keys #2782 // Make sure we process to-device messages before generating new one-time-keys #2782
deviceListManager.refreshOutdatedDeviceLists() deviceListManager.refreshOutdatedDeviceLists()
// The presence of device_unused_fallback_key_types indicates that the server supports fallback keys. // The presence of device_unused_fallback_key_types indicates that the server supports fallback keys.
// If there's no unused signed_curve25519 fallback key we need a new one. // If there's no unused signed_curve25519 fallback key we need a new one.
if (syncResponse.deviceUnusedFallbackKeyTypes != null && if (syncResponse.deviceUnusedFallbackKeyTypes != null &&
// Generate a fallback key only if the server does not already have an unused fallback key. // Generate a fallback key only if the server does not already have an unused fallback key.
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) { !syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
oneTimeKeysUploader.needsNewFallback() oneTimeKeysUploader.needsNewFallback()
} }
oneTimeKeysUploader.maybeUploadOneTimeKeys() oneTimeKeysUploader.maybeUploadOneTimeKeys()
} }
// Process pending key requests // Process pending key requests
try { try {
if (toDevices.isEmpty()) { if (toDevices.isEmpty()) {
// this is not blocking // this is not blocking
outgoingKeyRequestManager.requireProcessAllPendingKeyRequests() outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
} else { } else {
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.w("Don't process key requests yet as there might be more to_device to catchup") .w("Don't process key requests yet as there might be more to_device to catchup")
} }
} catch (failure: Throwable) { } catch (failure: Throwable) {
// just for safety but should not throw // just for safety but should not throw
Timber.tag(loggerTag.value).w("failed to process pending request") Timber.tag(loggerTag.value).w("failed to process pending request")
} }
try { try {
incomingKeyRequestManager.processIncomingRequests() incomingKeyRequestManager.processIncomingRequests()
} catch (failure: Throwable) { } catch (failure: Throwable) {
// just for safety but should not throw // just for safety but should not throw
Timber.tag(loggerTag.value).w("failed to process incoming room key requests") Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
} }
unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events -> unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events ->
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
events.forEach { events.forEach {
onRoomKeyEvent(it, true) onRoomKeyEvent(it, true)
}
}
} }
} }
} }
} }
override fun logDbUsageInfo() {
//
}
override suspend fun setRoomUnBlacklistUnverifiedDevices(roomId: String) {
cryptoStore.blockUnverifiedDevicesInRoom(roomId, false)
}
/** /**
* Find a device by curve25519 identity key. * Find a device by curve25519 identity key.
* *
@ -508,11 +493,18 @@ internal class DefaultCryptoService @Inject constructor(
* @param algorithm the encryption algorithm. * @param algorithm the encryption algorithm.
* @return the device info, or null if not found / unsupported algorithm / crypto released * @return the device info, or null if not found / unsupported algorithm / crypto released
*/ */
override fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo? { override suspend fun deviceWithIdentityKey(userId: String, senderKey: String, algorithm: String): CryptoDeviceInfo? {
return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) { return if (algorithm != MXCRYPTO_ALGORITHM_MEGOLM && algorithm != MXCRYPTO_ALGORITHM_OLM) {
// We only deal in olm keys // We only deal in olm keys
null null
} else cryptoStore.deviceWithIdentityKey(senderKey) } else {
withContext(coroutineDispatchers.io) {
cryptoStore.deviceWithIdentityKey(senderKey).takeIf {
// check that the claimed user id matches
it?.userId == userId
}
}
}
} }
/** /**
@ -521,26 +513,32 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the user id * @param userId the user id
* @param deviceId the device id * @param deviceId the device id
*/ */
override fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? { override suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? {
return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) { return if (userId.isNotEmpty() && !deviceId.isNullOrEmpty()) {
cryptoStore.getUserDevice(userId, deviceId) withContext(coroutineDispatchers.io) {
cryptoStore.getUserDevice(userId, deviceId)
}
} else { } else {
null null
} }
} }
override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) { // override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
getDeviceInfoTask // getDeviceInfoTask
.configureWith(GetDeviceInfoTask.Params(deviceId)) { // .configureWith(GetDeviceInfoTask.Params(deviceId)) {
this.executionThread = TaskThread.CRYPTO // this.executionThread = TaskThread.CRYPTO
this.callback = callback // this.callback = callback
} // }
.executeBy(taskExecutor) // .executeBy(taskExecutor)
} // }
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> { override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId).orEmpty() return cryptoStore.getUserDeviceList(userId).orEmpty()
} }
//
// override fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>> {
// return cryptoStore.getUserDeviceListFlow(userId)
// }
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> { override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
return cryptoStore.getLiveDeviceList() return cryptoStore.getLiveDeviceList()
@ -564,7 +562,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method. * @param devices the devices. Note that the verified member of the devices in this list will not be updated by this method.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
override fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) { fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?) {
// build a devices map // build a devices map
val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId }) val devicesIdListByUserId = devices.groupBy({ it.userId }, { it.deviceId })
@ -602,7 +600,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param userId the owner of the device * @param userId the owner of the device
* @param deviceId the unique identifier for the device. * @param deviceId the unique identifier for the device.
*/ */
override fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { override suspend fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
setDeviceVerificationAction.handle(trustLevel, userId, deviceId) setDeviceVerificationAction.handle(trustLevel, userId, deviceId)
} }
@ -684,8 +682,10 @@ internal class DefaultCryptoService @Inject constructor(
/** /**
* @return the stored device keys for a user. * @return the stored device keys for a user.
*/ */
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> { override suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList() return withContext(coroutineDispatchers.io) {
cryptoStore.getUserDevices(userId)?.values?.toList().orEmpty()
}
} }
private fun isEncryptionEnabledForInvitedUser(): Boolean { private fun isEncryptionEnabledForInvitedUser(): Boolean {
@ -716,14 +716,13 @@ internal class DefaultCryptoService @Inject constructor(
* @param roomId the room identifier the event will be sent. * @param roomId the room identifier the event will be sent.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
override fun encryptEventContent( override suspend fun encryptEventContent(
eventContent: Content, eventContent: Content,
eventType: String, eventType: String,
roomId: String, roomId: String,
callback: MatrixCallback<MXEncryptEventContentResult> ): MXEncryptEventContentResult {
) {
// moved to crypto scope to have uptodate values // moved to crypto scope to have uptodate values
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { return withContext(coroutineDispatchers.crypto) {
val userIds = getRoomUserIds(roomId) val userIds = getRoomUserIds(roomId)
var alg = roomEncryptorsStore.get(roomId) var alg = roomEncryptorsStore.get(roomId)
if (alg == null) { if (alg == null) {
@ -738,11 +737,9 @@ internal class DefaultCryptoService @Inject constructor(
if (safeAlgorithm != null) { if (safeAlgorithm != null) {
val t0 = clock.epochMillis() val t0 = clock.epochMillis()
Timber.tag(loggerTag.value).v("encryptEventContent() starts") Timber.tag(loggerTag.value).v("encryptEventContent() starts")
runCatching { val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds) Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms") return@withContext MXEncryptEventContentResult(content, EventType.ENCRYPTED)
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
}.foldToCallback(callback)
} else { } else {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
val reason = String.format( val reason = String.format(
@ -750,7 +747,7 @@ internal class DefaultCryptoService @Inject constructor(
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON
) )
Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason") Timber.tag(loggerTag.value).e("encryptEventContent() : failed $reason")
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))) throw Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason))
} }
} }
} }
@ -778,17 +775,6 @@ internal class DefaultCryptoService @Inject constructor(
return internalDecryptEvent(event, timeline) return internalDecryptEvent(event, timeline)
} }
/**
* Decrypt an event asynchronously.
*
* @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @param callback the callback to return data or null
*/
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
eventDecryptor.decryptEventAsync(event, timeline, callback)
}
/** /**
* Decrypt an event. * Decrypt an event.
* *
@ -858,7 +844,7 @@ internal class DefaultCryptoService @Inject constructor(
* @param event the key event. * @param event the key event.
* @param acceptUnrequested, if true it will force to accept unrequested keys. * @param acceptUnrequested, if true it will force to accept unrequested keys.
*/ */
private fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) { private suspend fun onRoomKeyEvent(event: Event, acceptUnrequested: Boolean = false) {
val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return val roomKeyContent = event.getDecryptedContent().toModel<RoomKeyContent>() ?: return
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>") .i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>")
@ -914,19 +900,27 @@ internal class DefaultCryptoService @Inject constructor(
): Boolean { ): Boolean {
return when (secretName) { return when (secretName) {
MASTER_KEY_SSSS_NAME -> { MASTER_KEY_SSSS_NAME -> {
crossSigningService.onSecretMSKGossip(secretValue) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
crossSigningService.onSecretMSKGossip(secretValue)
}
true true
} }
SELF_SIGNING_KEY_SSSS_NAME -> { SELF_SIGNING_KEY_SSSS_NAME -> {
crossSigningService.onSecretSSKGossip(secretValue) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
crossSigningService.onSecretSSKGossip(secretValue)
}
true true
} }
USER_SIGNING_KEY_SSSS_NAME -> { USER_SIGNING_KEY_SSSS_NAME -> {
crossSigningService.onSecretUSKGossip(secretValue) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
crossSigningService.onSecretUSKGossip(secretValue)
}
true true
} }
KEYBACKUP_SECRET_SSSS_NAME -> { KEYBACKUP_SECRET_SSSS_NAME -> {
keysBackupService.onSecretKeyGossip(secretValue) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
keysBackupService.onSecretKeyGossip(secretValue)
}
true true
} }
else -> false else -> false
@ -1016,19 +1010,39 @@ internal class DefaultCryptoService @Inject constructor(
} }
// Prepare the device keys data to send // Prepare the device keys data to send
// Sign it // Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary()) val myCryptoDevice = getMyCryptoDevice()
var rest = getMyDevice().toRest() val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myCryptoDevice.signalableJSONDictionary())
var rest = myCryptoDevice.toRest()
rest = rest.copy( rest = rest.copy(
signatures = objectSigner.signObject(canonicalJson) signatures = objectSigner.signObject(canonicalJson)
) )
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null) val keyUploadBody = KeysUploadBody(
deviceKeys = rest,
)
val uploadDeviceKeysParams = UploadKeysTask.Params(keyUploadBody)
uploadKeysTask.execute(uploadDeviceKeysParams) uploadKeysTask.execute(uploadDeviceKeysParams)
cryptoStore.setDeviceKeysUploaded(true) cryptoStore.setDeviceKeysUploaded(true)
} }
override suspend fun receiveSyncChanges(
toDevice: ToDeviceSyncResponse?,
deviceChanges: DeviceListResponse?,
keyCounts: DeviceOneTimeKeysCountSyncResponse?
) {
withContext(coroutineDispatchers.crypto) {
deviceListManager.handleDeviceListsChanges(deviceChanges?.changed.orEmpty(), deviceChanges?.left.orEmpty())
if (keyCounts != null) {
val currentCount = keyCounts.signedCurve25519 ?: 0
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
}
cryptoSyncHandler.handleToDevice(toDevice?.events.orEmpty())
}
}
/** /**
* Export the crypto keys. * Export the crypto keys.
* *
@ -1131,6 +1145,22 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
override suspend fun downloadKeysIfNeeded(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
return deviceListManager.downloadKeys(userIds, forceDownload)
}
override suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo> {
return cryptoStore.getUserDeviceList(userId).orEmpty()
}
//
// fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>> {
// cryptoStore.getLiveDeviceList(userId).asFlow()
// }
//
// fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>> {
//
// }
/** /**
* Set the global override for whether the client should ever send encrypted * Set the global override for whether the client should ever send encrypted
* messages to unverified devices. * messages to unverified devices.
@ -1214,11 +1244,11 @@ internal class DefaultCryptoService @Inject constructor(
* *
* @param event the event to decrypt again. * @param event the event to decrypt again.
*/ */
override fun reRequestRoomKeyForEvent(event: Event) { override suspend fun reRequestRoomKeyForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, true) outgoingKeyRequestManager.requestKeyForEvent(event, true)
} }
override fun requestRoomKeyForEvent(event: Event) { suspend fun requestRoomKeyForEvent(event: Event) {
outgoingKeyRequestManager.requestKeyForEvent(event, false) outgoingKeyRequestManager.requestKeyForEvent(event, false)
} }
@ -1264,12 +1294,8 @@ internal class DefaultCryptoService @Inject constructor(
return unknownDevices return unknownDevices
} }
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) { suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { return deviceListManager.downloadKeys(userIds, forceDownload)
runCatching {
deviceListManager.downloadKeys(userIds, forceDownload)
}.foldToCallback(callback)
}
} }
override fun addNewSessionListener(newSessionListener: NewSessionListener) { override fun addNewSessionListener(newSessionListener: NewSessionListener) {
@ -1333,8 +1359,8 @@ internal class DefaultCryptoService @Inject constructor(
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId) return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
} }
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) { override suspend fun prepareToEncrypt(roomId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date") Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
// Ensure to load all room members // Ensure to load all room members
try { try {
@ -1354,19 +1380,10 @@ internal class DefaultCryptoService @Inject constructor(
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON) val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason") Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
callback.onFailure(IllegalArgumentException("Missing algorithm")) throw IllegalArgumentException("Missing algorithm")
return@launch
} }
runCatching {
(alg as? IMXGroupEncryption)?.preshareKey(userIds) (alg as? IMXGroupEncryption)?.preshareKey(userIds)
}.fold(
{ callback.onSuccess(Unit) },
{
Timber.tag(loggerTag.value).e(it, "prepareToEncrypt() failed.")
callback.onFailure(it)
}
)
} }
} }
@ -1394,6 +1411,14 @@ internal class DefaultCryptoService @Inject constructor(
} }
} }
override fun onE2ERoomMemberLoadedFromServer(roomId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
val userIds = getRoomUserIds(roomId)
// Because of LL we might want to update tracked users
deviceListManager.startTrackingDeviceList(userIds)
}
}
/* ========================================================================================== /* ==========================================================================================
* For test only * For test only
* ========================================================================================== */ * ========================================================================================== */

View File

@ -61,7 +61,7 @@ internal class MyDeviceInfoHolder @Inject constructor(
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true) // myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
myDevice = CryptoDeviceInfo( myDevice = CryptoDeviceInfo(
credentials.deviceId!!, credentials.deviceId,
credentials.userId, credentials.userId,
keys = keys, keys = keys,
algorithms = MXCryptoAlgorithms.supportedAlgorithms(), algorithms = MXCryptoAlgorithms.supportedAlgorithms(),

View File

@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
import android.content.Context import android.content.Context
import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.extensions.tryOrNull
import org.matrix.android.sdk.internal.crypto.model.MXKey import org.matrix.android.sdk.internal.crypto.model.MXKey
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadBody
import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse import org.matrix.android.sdk.internal.crypto.model.rest.KeysUploadResponse
import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask import org.matrix.android.sdk.internal.crypto.tasks.UploadKeysTask
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
@ -138,7 +139,7 @@ internal class OneTimeKeysUploader @Inject constructor(
private suspend fun fetchOtkCount(): Int? { private suspend fun fetchOtkCount(): Int? {
return tryOrNull("Unable to get OTK count") { return tryOrNull("Unable to get OTK count") {
val result = uploadKeysTask.execute(UploadKeysTask.Params(null, null, null)) val result = uploadKeysTask.execute(UploadKeysTask.Params(KeysUploadBody()))
result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) result.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
} }
} }
@ -227,9 +228,11 @@ internal class OneTimeKeysUploader @Inject constructor(
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params( val uploadParams = UploadKeysTask.Params(
deviceKeys = null, KeysUploadBody(
oneTimeKeys = oneTimeJson, deviceKeys = null,
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() } oneTimeKeys = oneTimeJson,
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
)
) )
return uploadKeysTask.executeRetry(uploadParams, 3) return uploadKeysTask.executeRetry(uploadParams, 3)
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -74,11 +74,11 @@ internal class RoomDecryptorProvider @Inject constructor(
val alg = when (algorithm) { val alg = when (algorithm) {
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply { MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply {
this.newSessionListener = object : NewSessionListener { this.newSessionListener = object : NewSessionListener {
override fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { override fun onNewSession(roomId: String?, sessionId: String) {
// PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor // PR reviewer: the parameter has been renamed so is now in conflict with the parameter of getOrCreateRoomDecryptor
newSessionListeners.toList().forEach { newSessionListeners.toList().forEach {
try { try {
it.onNewSession(roomId, senderKey, sessionId) it.onNewSession(roomId, sessionId)
} catch (ignore: Throwable) { } catch (ignore: Throwable) {
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2022 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,7 +28,6 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_S
import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.MASTER_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.SELF_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME import org.matrix.android.sdk.api.session.crypto.crosssigning.USER_SIGNING_KEY_SSSS_NAME
import org.matrix.android.sdk.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListener
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest import org.matrix.android.sdk.api.session.crypto.model.SecretShareRequest
@ -36,7 +35,6 @@ import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent import org.matrix.android.sdk.api.session.events.model.content.SecretSendEventContent
import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.events.model.toModel
import org.matrix.android.sdk.api.util.toBase64NoPadding
import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction import org.matrix.android.sdk.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter import org.matrix.android.sdk.internal.crypto.actions.MessageEncrypter
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
@ -153,10 +151,7 @@ internal class SecretShareManager @Inject constructor(
MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master MASTER_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.master
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey?.toBase64()
?.let {
extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
}
else -> null else -> null
} }
if (secretValue == null) { if (secretValue == null) {
@ -248,7 +243,7 @@ internal class SecretShareManager @Inject constructor(
) )
try { try {
withContext(coroutineDispatchers.io) { withContext(coroutineDispatchers.io) {
sendToDeviceTask.executeRetry(params, 3) sendToDeviceTask.execute(params)
} }
Timber.tag(loggerTag.value) Timber.tag(loggerTag.value)
.d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}") .d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}")

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -91,9 +91,25 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(
} }
// Let's now claim one time keys // Let's now claim one time keys
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim.map)
val oneTimeKeys = withContext(coroutineDispatchers.io) { val oneTimeKeys = withContext(coroutineDispatchers.io) {
oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT) oneTimeKeysForUsersDeviceTask.executeRetry(claimParams, ONE_TIME_KEYS_RETRY_COUNT)
}.oneTimeKeys.let { oneTimeKeys ->
val map = MXUsersDevicesMap<MXKey>()
oneTimeKeys?.let { oneTimeKeys ->
for ((userId, mapByUserId) in oneTimeKeys) {
for ((deviceId, deviceKey) in mapByUserId) {
val mxKey = MXKey.from(deviceKey)
if (mxKey != null) {
map.setObject(userId, deviceId, mxKey)
} else {
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
}
}
}
}
map
} }
// let now start olm session using the new otks // let now start olm session using the new otks

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -57,6 +57,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
progressListener: ProgressListener? progressListener: ProgressListener?
): ImportRoomKeysResult { ): ImportRoomKeysResult {
val t0 = clock.epochMillis() val t0 = clock.epochMillis()
val importedSession = mutableMapOf<String, MutableMap<String, MutableList<String>>>()
val totalNumbersOfKeys = megolmSessionsData.size val totalNumbersOfKeys = megolmSessionsData.size
var lastProgress = 0 var lastProgress = 0
@ -70,18 +71,23 @@ internal class MegolmSessionDataImporter @Inject constructor(
if (null != decrypting) { if (null != decrypting) {
try { try {
val sessionId = megolmSessionData.sessionId val sessionId = megolmSessionData.sessionId ?: return@forEachIndexed
val senderKey = megolmSessionData.senderKey ?: return@forEachIndexed
val roomId = megolmSessionData.roomId ?: return@forEachIndexed
Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId") Timber.tag(loggerTag.value).v("## importRoomKeys retrieve senderKey ${megolmSessionData.senderKey} sessionId $sessionId")
importedSession.getOrPut(roomId) { mutableMapOf() }
.getOrPut(senderKey) { mutableListOf() }
.add(sessionId)
totalNumbersOfImportedKeys++ totalNumbersOfImportedKeys++
// cancel any outstanding room key requests for this session // cancel any outstanding room key requests for this session
Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}") Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded( outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
megolmSessionData.sessionId ?: "", sessionId,
megolmSessionData.roomId ?: "", roomId,
megolmSessionData.senderKey ?: "", senderKey,
tryOrNull { tryOrNull {
olmInboundGroupSessionWrappers olmInboundGroupSessionWrappers
.firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId } .firstOrNull { it.session.sessionIdentifier() == megolmSessionData.sessionId }
@ -93,7 +99,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
// Have another go at decrypting events sent with this session // Have another go at decrypting events sent with this session
when (decrypting) { when (decrypting) {
is MXMegolmDecryption -> { is MXMegolmDecryption -> {
decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!) decrypting.onNewSession(megolmSessionData.roomId, senderKey, sessionId)
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -121,6 +127,6 @@ internal class MegolmSessionDataImporter @Inject constructor(
Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)") Timber.tag(loggerTag.value).v("## importMegolmSessionsData : sessions import " + (t1 - t0) + " ms (" + megolmSessionsData.size + " sessions)")
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys) return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys, importedSession)
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -29,7 +29,7 @@ internal class SetDeviceVerificationAction @Inject constructor(
private val defaultKeysBackupService: DefaultKeysBackupService private val defaultKeysBackupService: DefaultKeysBackupService
) { ) {
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) { suspend fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
val device = cryptoStore.getUserDevice(userId, deviceId) val device = cryptoStore.getUserDevice(userId, deviceId)
// Sanity check // Sanity check

View File

@ -43,5 +43,5 @@ internal interface IMXDecrypting {
* @param defaultKeysBackupService the keys backup service * @param defaultKeysBackupService the keys backup service
* @param forceAccept the keys backup service * @param forceAccept the keys backup service
*/ */
fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {} suspend fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean = false) {}
} }

View File

@ -189,7 +189,7 @@ internal class MXMegolmDecryption(
* @param defaultKeysBackupService the keys backup service * @param defaultKeysBackupService the keys backup service
* @param forceAccept if true will force to accept the forwarded key * @param forceAccept if true will force to accept the forwarded key
*/ */
override fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) { override suspend fun onRoomKeyEvent(event: Event, defaultKeysBackupService: DefaultKeysBackupService, forceAccept: Boolean) {
Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})") Timber.tag(loggerTag.value).v("onRoomKeyEvent(${event.getSenderKey()})")
var exportFormat = false var exportFormat = false
val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return
@ -360,6 +360,6 @@ internal class MXMegolmDecryption(
*/ */
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) { fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey") Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
newSessionListener?.onNewSession(roomId, senderKey, sessionId) newSessionListener?.onNewSession(roomId, sessionId)
} }
} }

View File

@ -184,7 +184,9 @@ internal class MXMegolmEncryption(
trusted = true trusted = true
) )
defaultKeysBackupService.maybeBackupKeys() cryptoCoroutineScope.launch {
defaultKeysBackupService.maybeBackupKeys()
}
return MXOutboundSessionInfo( return MXOutboundSessionInfo(
sessionId = sessionId, sessionId = sessionId,

View File

@ -21,7 +21,8 @@ import androidx.work.BackoffPolicy
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.MatrixCallback import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.matrix.android.sdk.api.MatrixCoroutineDispatchers import org.matrix.android.sdk.api.MatrixCoroutineDispatchers
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
@ -35,6 +36,7 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerif
import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isLocallyVerified
import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.api.util.fromBase64 import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.internal.crypto.DeviceListManager import org.matrix.android.sdk.internal.crypto.DeviceListManager
@ -48,8 +50,6 @@ import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.di.WorkManagerProvider import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.session.SessionScope import org.matrix.android.sdk.internal.session.SessionScope
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.TaskThread
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.JsonCanonicalizer import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.android.sdk.internal.util.logLimit import org.matrix.android.sdk.internal.util.logLimit
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
@ -127,7 +127,9 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
// Recover local trust in case private key are there? // Recover local trust in case private key are there?
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified()) cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
}
} }
} catch (e: Throwable) { } catch (e: Throwable) {
// Mmm this kind of a big issue // Mmm this kind of a big issue
@ -152,40 +154,30 @@ internal class DefaultCrossSigningService @Inject constructor(
* - Sign the keys and upload them * - Sign the keys and upload them
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures. * - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures.
*/ */
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) { override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
Timber.d("## CrossSigning initializeCrossSigning") Timber.d("## CrossSigning initializeCrossSigning")
val params = InitializeCrossSigningTask.Params( val params = InitializeCrossSigningTask.Params(
interactiveAuthInterceptor = uiaInterceptor interactiveAuthInterceptor = uiaInterceptor
) )
initializeCrossSigningTask.configureWith(params) { val data = initializeCrossSigningTask
this.callbackThread = TaskThread.CRYPTO .execute(params)
this.callback = object : MatrixCallback<InitializeCrossSigningTask.Result> { val crossSigningInfo = MXCrossSigningInfo(
override fun onFailure(failure: Throwable) { myUserId,
Timber.e(failure, "Error in initializeCrossSigning()") listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
callback.onFailure(failure) true
} )
withContext(coroutineDispatchers.crypto) {
override fun onSuccess(data: InitializeCrossSigningTask.Result) { cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
val crossSigningInfo = MXCrossSigningInfo( setUserKeysAsTrusted(myUserId, true)
myUserId, cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo), crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
true crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
) crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
cryptoStore.setMyCrossSigningInfo(crossSigningInfo) }
setUserKeysAsTrusted(myUserId, true)
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
callback.onSuccess(Unit)
}
}
}.executeBy(taskExecutor)
} }
override fun onSecretMSKGossip(mskPrivateKey: String) { override suspend fun onSecretMSKGossip(mskPrivateKey: String) {
Timber.i("## CrossSigning - onSecretSSKGossip") Timber.i("## CrossSigning - onSecretSSKGossip")
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known") Timber.e("## CrossSigning - onSecretMSKGossip() received secret but public key is not known")
@ -212,7 +204,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun onSecretSSKGossip(sskPrivateKey: String) { override suspend fun onSecretSSKGossip(sskPrivateKey: String) {
Timber.i("## CrossSigning - onSecretSSKGossip") Timber.i("## CrossSigning - onSecretSSKGossip")
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known") Timber.e("## CrossSigning - onSecretSSKGossip() received secret but public key is not known")
@ -239,7 +231,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun onSecretUSKGossip(uskPrivateKey: String) { override suspend fun onSecretUSKGossip(uskPrivateKey: String) {
Timber.i("## CrossSigning - onSecretUSKGossip") Timber.i("## CrossSigning - onSecretUSKGossip")
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ") Timber.e("## CrossSigning - onSecretUSKGossip() received secret but public key is not knwow ")
@ -265,7 +257,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun checkTrustFromPrivateKeys( override suspend fun checkTrustFromPrivateKeys(
masterKeyPrivateKey: String?, masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?, uskKeyPrivateKey: String?,
sskPrivateKey: String? sskPrivateKey: String?
@ -328,7 +320,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) { if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo) return UserTrustResult.Failure("Keys not trusted $mxCrossSigningInfo") // UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
} else { } else {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true) cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
val checkSelfTrust = checkSelfTrust() val checkSelfTrust = checkSelfTrust()
@ -354,18 +346,22 @@ internal class DefaultCrossSigningService @Inject constructor(
* USK * USK
* . * .
*/ */
override fun isUserTrusted(otherUserId: String): Boolean { override suspend fun isUserTrusted(otherUserId: String): Boolean {
return cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true return withContext(coroutineDispatchers.io) {
cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
}
} }
override fun isCrossSigningVerified(): Boolean { override suspend fun isCrossSigningVerified(): Boolean {
return checkSelfTrust().isVerified() return withContext(coroutineDispatchers.io) {
checkSelfTrust().isVerified()
}
} }
/** /**
* Will not force a download of the key, but will verify signatures trust chain. * Will not force a download of the key, but will verify signatures trust chain.
*/ */
override fun checkUserTrust(otherUserId: String): UserTrustResult { override suspend fun checkUserTrust(otherUserId: String): UserTrustResult {
Timber.v("## CrossSigning checkUserTrust for $otherUserId") Timber.v("## CrossSigning checkUserTrust for $otherUserId")
if (otherUserId == myUserId) { if (otherUserId == myUserId) {
return checkSelfTrust() return checkSelfTrust()
@ -380,17 +376,17 @@ internal class DefaultCrossSigningService @Inject constructor(
return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId)) return checkOtherMSKTrusted(myCrossSigningInfo, cryptoStore.getCrossSigningInfo(otherUserId))
} }
fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult { override fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult {
val myUserKey = myCrossSigningInfo?.userKey() val myUserKey = myCrossSigningInfo?.userKey()
?: return UserTrustResult.CrossSigningNotConfigured(myUserId) ?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
if (!myCrossSigningInfo.isTrusted()) { if (!myCrossSigningInfo.isTrusted()) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
} }
// Let's get the other user master key // Let's get the other user master key
val otherMasterKey = otherInfo?.masterKey() val otherMasterKey = otherInfo?.masterKey()
?: return UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "") ?: return UserTrustResult.Failure("Unknown MSK for ${otherInfo?.userId}") // UserTrustResult.UnknownCrossSignatureInfo(otherInfo?.userId ?: "")
val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures val masterKeySignaturesMadeByMyUserKey = otherMasterKey.signatures
?.get(myUserId) // Signatures made by me ?.get(myUserId) // Signatures made by me
@ -398,7 +394,7 @@ internal class DefaultCrossSigningService @Inject constructor(
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) { if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey") Timber.d("## CrossSigning checkUserTrust false for ${otherInfo.userId}, not signed by my UserSigningKey")
return UserTrustResult.KeyNotSigned(otherMasterKey) return UserTrustResult.Failure("MSK not signed by my USK $otherMasterKey") // UserTrustResult.KeyNotSigned(otherMasterKey)
} }
// Check that Alice USK signature of Bob MSK is valid // Check that Alice USK signature of Bob MSK is valid
@ -409,7 +405,7 @@ internal class DefaultCrossSigningService @Inject constructor(
otherMasterKey.canonicalSignable() otherMasterKey.canonicalSignable()
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey) return UserTrustResult.Failure("Invalid signature $masterKeySignaturesMadeByMyUserKey") // UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
} }
return UserTrustResult.Success return UserTrustResult.Success
@ -424,7 +420,7 @@ internal class DefaultCrossSigningService @Inject constructor(
return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId)) return checkSelfTrust(myCrossSigningInfo, cryptoStore.getUserDeviceList(myUserId))
} }
fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult { override fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult {
// Special case when it's me, // Special case when it's me,
// I have to check that MSK -> USK -> SSK // I have to check that MSK -> USK -> SSK
// and that MSK is trusted (i know the private key, or is signed by a trusted device) // and that MSK is trusted (i know the private key, or is signed by a trusted device)
@ -473,7 +469,7 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
if (!isMaterKeyTrusted) { if (!isMaterKeyTrusted) {
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
} }
val myUserKey = myCrossSigningInfo.userKey() val myUserKey = myCrossSigningInfo.userKey()
@ -485,7 +481,7 @@ internal class DefaultCrossSigningService @Inject constructor(
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK") Timber.d("## CrossSigning checkUserTrust false for $myUserId, USK not signed by MSK")
return UserTrustResult.KeyNotSigned(myUserKey) return UserTrustResult.Failure("USK not signed by MSK") // UserTrustResult.KeyNotSigned(myUserKey)
} }
// Check that Alice USK signature of Alice MSK is valid // Check that Alice USK signature of Alice MSK is valid
@ -496,7 +492,7 @@ internal class DefaultCrossSigningService @Inject constructor(
myUserKey.canonicalSignable() myUserKey.canonicalSignable()
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey) return UserTrustResult.Failure("Invalid MSK signature of USK") // UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
} }
val mySSKey = myCrossSigningInfo.selfSigningKey() val mySSKey = myCrossSigningInfo.selfSigningKey()
@ -508,7 +504,7 @@ internal class DefaultCrossSigningService @Inject constructor(
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK") Timber.d("## CrossSigning checkUserTrust false for $myUserId, SSK not signed by MSK")
return UserTrustResult.KeyNotSigned(mySSKey) return UserTrustResult.Failure("SSK not signed by MSK") // UserTrustResult.KeyNotSigned(mySSKey)
} }
// Check that Alice USK signature of Alice MSK is valid // Check that Alice USK signature of Alice MSK is valid
@ -519,26 +515,32 @@ internal class DefaultCrossSigningService @Inject constructor(
mySSKey.canonicalSignable() mySSKey.canonicalSignable()
) )
} catch (failure: Throwable) { } catch (failure: Throwable) {
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey) return UserTrustResult.Failure("Invalid signature $ssKeySignaturesMadeByMyMasterKey") // UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
} }
return UserTrustResult.Success return UserTrustResult.Success
} }
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? { override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
return cryptoStore.getCrossSigningInfo(otherUserId) return withContext(coroutineDispatchers.io) {
cryptoStore.getCrossSigningInfo(otherUserId)
}
} }
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> { override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
return cryptoStore.getLiveCrossSigningInfo(userId) return cryptoStore.getLiveCrossSigningInfo(userId)
} }
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? { override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
return cryptoStore.getMyCrossSigningInfo() return withContext(coroutineDispatchers.io) {
cryptoStore.getMyCrossSigningInfo()
}
} }
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? { override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
return cryptoStore.getCrossSigningPrivateKeys() return withContext(coroutineDispatchers.io) {
cryptoStore.getCrossSigningPrivateKeys()
}
} }
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> { override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
@ -555,24 +557,20 @@ internal class DefaultCrossSigningService @Inject constructor(
cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse() cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse()
} }
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) { override suspend fun trustUser(otherUserId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
Timber.d("## CrossSigning - Mark user $otherUserId as trusted ") Timber.d("## CrossSigning - Mark user $otherUserId as trusted ")
// We should have this user keys // We should have this user keys
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey() val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
if (otherMasterKeys == null) { if (otherMasterKeys == null) {
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known")) throw Throwable("## CrossSigning - Other master signing key is not known")
return@launch
} }
val myKeys = getUserCrossSigningKeys(myUserId) val myKeys = getUserCrossSigningKeys(myUserId)
if (myKeys == null) { ?: throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
return@launch
}
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
if (userPubKey == null || crossSigningOlm.userPkSigning == null) { if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")) throw Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")
return@launch
} }
// Sign the other MasterKey with our UserSigning key // Sign the other MasterKey with our UserSigning key
@ -580,12 +578,8 @@ internal class DefaultCrossSigningService @Inject constructor(
Map::class.java, Map::class.java,
otherMasterKeys.signalableJSONDictionary() otherMasterKeys.signalableJSONDictionary()
).let { crossSigningOlm.userPkSigning?.sign(it) } ).let { crossSigningOlm.userPkSigning?.sign(it) }
?: // race??
if (newSignature == null) { throw Throwable("## CrossSigning - Failed to sign")
// race??
callback.onFailure(Throwable("## CrossSigning - Failed to sign"))
return@launch
}
cryptoStore.setUserKeysAsTrusted(otherUserId, true) cryptoStore.setUserKeysAsTrusted(otherUserId, true)
@ -593,10 +587,8 @@ internal class DefaultCrossSigningService @Inject constructor(
val uploadQuery = UploadSignatureQueryBuilder() val uploadQuery = UploadSignatureQueryBuilder()
.withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature)) .withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature))
.build() .build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
this.executionThread = TaskThread.CRYPTO uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
this.callback = callback
}.executeBy(taskExecutor)
// Local echo for device cross trust, to avoid having to wait for a notification of key change // Local echo for device cross trust, to avoid having to wait for a notification of key change
cryptoStore.getUserDeviceList(otherUserId)?.forEach { device -> cryptoStore.getUserDeviceList(otherUserId)?.forEach { device ->
@ -607,8 +599,8 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun markMyMasterKeyAsTrusted() { override suspend fun markMyMasterKeyAsTrusted() {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
cryptoStore.markMyMasterKeyAsLocallyTrusted(true) cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
checkSelfTrust() checkSelfTrust()
// re-verify all trusts // re-verify all trusts
@ -616,35 +608,26 @@ internal class DefaultCrossSigningService @Inject constructor(
} }
} }
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) { override suspend fun trustDevice(deviceId: String) {
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { withContext(coroutineDispatchers.crypto) {
// This device should be yours // This device should be yours
val device = cryptoStore.getUserDevice(myUserId, deviceId) val device = cryptoStore.getUserDevice(myUserId, deviceId)
if (device == null) { if (device == null) {
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours")) throw IllegalArgumentException("This device [$deviceId] is not known, or not yours")
return@launch
} }
val myKeys = getUserCrossSigningKeys(myUserId) val myKeys = getUserCrossSigningKeys(myUserId)
if (myKeys == null) { ?: throw Throwable("CrossSigning is not setup for this account")
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
return@launch
}
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) { if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")) throw Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")
return@launch
} }
// Sign with self signing // Sign with self signing
val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable()) val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable())
?: throw Throwable("Failed to sign")
if (newSignature == null) {
// race??
callback.onFailure(Throwable("Failed to sign"))
return@launch
}
val toUpload = device.copy( val toUpload = device.copy(
signatures = mapOf( signatures = mapOf(
myUserId myUserId
@ -658,14 +641,16 @@ internal class DefaultCrossSigningService @Inject constructor(
val uploadQuery = UploadSignatureQueryBuilder() val uploadQuery = UploadSignatureQueryBuilder()
.withDeviceInfo(toUpload) .withDeviceInfo(toUpload)
.build() .build()
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) { uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
this.executionThread = TaskThread.CRYPTO
this.callback = callback
}.executeBy(taskExecutor)
} }
} }
override fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult { override suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel {
// Not used in kotlin SDK?
TODO("Not yet implemented")
}
override suspend fun checkDeviceTrust(otherUserId: String, otherDeviceId: String, locallyTrusted: Boolean?): DeviceTrustResult {
val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId) val otherDevice = cryptoStore.getUserDevice(otherUserId, otherDeviceId)
?: return DeviceTrustResult.UnknownDevice(otherDeviceId) ?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
@ -787,10 +772,12 @@ internal class DefaultCrossSigningService @Inject constructor(
override fun onUsersDeviceUpdate(userIds: List<String>) { override fun onUsersDeviceUpdate(userIds: List<String>) {
Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}") Timber.d("## CrossSigning - onUsersDeviceUpdate for users: ${userIds.logLimit()}")
checkTrustAndAffectedRoomShields(userIds) runBlocking {
checkTrustAndAffectedRoomShields(userIds)
}
} }
fun checkTrustAndAffectedRoomShields(userIds: List<String>) { override suspend fun checkTrustAndAffectedRoomShields(userIds: List<String>) {
Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}") Timber.d("## CrossSigning - checkTrustAndAffectedRoomShields for users: ${userIds.logLimit()}")
val workerParams = UpdateTrustWorker.Params( val workerParams = UpdateTrustWorker.Params(
sessionId = sessionId, sessionId = sessionId,
@ -808,7 +795,7 @@ internal class DefaultCrossSigningService @Inject constructor(
.enqueue() .enqueue()
} }
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) { private suspend fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted) cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
// If it's me, recheck trust of all users and devices? // If it's me, recheck trust of all users and devices?
@ -818,7 +805,10 @@ internal class DefaultCrossSigningService @Inject constructor(
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted) outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
cryptoStore.updateUsersTrust { cryptoStore.updateUsersTrust {
users.add(it) users.add(it)
checkUserTrust(it).isVerified() // called within a real transaction, has to block
runBlocking {
checkUserTrust(it).isVerified()
}
} }
users.forEach { users.forEach {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2020 The Matrix.org Foundation C.I.C. * Copyright (c) 2022 New Vector Ltd
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo import org.matrix.android.sdk.api.session.crypto.crosssigning.MXCrossSigningInfo
import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult import org.matrix.android.sdk.api.session.crypto.crosssigning.UserTrustResult
import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified import org.matrix.android.sdk.api.session.crypto.crosssigning.isCrossSignedVerified
@ -68,7 +69,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
val filename: String? = null val filename: String? = null
) : SessionWorkerParams ) : SessionWorkerParams
@Inject lateinit var crossSigningService: DefaultCrossSigningService @Inject lateinit var crossSigningService: CrossSigningService
// It breaks the crypto store contract, but we need to batch things :/ // It breaks the crypto store contract, but we need to batch things :/
@CryptoDatabase @CryptoDatabase
@ -174,9 +175,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
?.devices ?.devices
val trustMap = devicesEntities?.associateWith { device -> val trustMap = devicesEntities?.associateWith { device ->
// get up to date from DB has could have been updated crossSigningService.checkDeviceTrust(userId, device.deviceId ?: "", CryptoMapper.mapToModel(device).trustLevel?.locallyVerified)
val otherInfo = getCrossSigningInfo(cryptoRealm, userId)
crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
} }
// Update trust if needed // Update trust if needed

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoReady
/** /**
@ -31,4 +32,6 @@ internal data class KeyVerificationReady(
) : SendToDeviceObject, VerificationInfoReady { ) : SendToDeviceObject, VerificationInfoReady {
override fun toSendToDeviceObject() = this override fun toSendToDeviceObject() = this
override fun toEventContent() = toContent()
} }

View File

@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
import com.squareup.moshi.Json import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject import org.matrix.android.sdk.api.session.crypto.model.SendToDeviceObject
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest import org.matrix.android.sdk.internal.crypto.verification.VerificationInfoRequest
/** /**
@ -32,4 +33,6 @@ internal data class KeyVerificationRequest(
) : SendToDeviceObject, VerificationInfoRequest { ) : SendToDeviceObject, VerificationInfoRequest {
override fun toSendToDeviceObject() = this override fun toSendToDeviceObject() = this
override fun toEventContent() = toContent()
} }

View File

@ -1,265 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import android.util.Base64
import org.matrix.android.sdk.BuildConfig
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import timber.log.Timber
internal class DefaultIncomingSASDefaultVerificationTransaction(
setDeviceVerificationAction: SetDeviceVerificationAction,
override val userId: String,
override val deviceId: String?,
private val cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
deviceFingerprint: String,
transactionId: String,
otherUserID: String,
private val autoAccept: Boolean = false
) : SASDefaultVerificationTransaction(
setDeviceVerificationAction,
userId,
deviceId,
cryptoStore,
crossSigningService,
outgoingKeyRequestManager,
secretShareManager,
deviceFingerprint,
transactionId,
otherUserID,
null,
isIncoming = true
),
IncomingSasVerificationTransaction {
override val uxState: IncomingSasVerificationTransaction.UxState
get() {
return when (val immutableState = state) {
is VerificationTxState.OnStarted -> IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT
is VerificationTxState.SendingAccept,
is VerificationTxState.Accepted,
is VerificationTxState.OnKeyReceived,
is VerificationTxState.SendingKey,
is VerificationTxState.KeySent -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
is VerificationTxState.ShortCodeReady -> IncomingSasVerificationTransaction.UxState.SHOW_SAS
is VerificationTxState.ShortCodeAccepted,
is VerificationTxState.SendingMac,
is VerificationTxState.MacSent,
is VerificationTxState.Verifying -> IncomingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
is VerificationTxState.Verified -> IncomingSasVerificationTransaction.UxState.VERIFIED
is VerificationTxState.Cancelled -> {
if (immutableState.byMe) {
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_ME
} else {
IncomingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
}
}
else -> IncomingSasVerificationTransaction.UxState.UNKNOWN
}
}
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
Timber.v("## SAS I: received verification request from state $state")
if (state != VerificationTxState.None) {
Timber.e("## SAS I: received verification request from invalid state")
// should I cancel??
throw IllegalStateException("Interactive Key verification already started")
}
this.startReq = startReq
state = VerificationTxState.OnStarted
this.otherDeviceId = startReq.fromDevice
if (autoAccept) {
performAccept()
}
}
override fun performAccept() {
if (state != VerificationTxState.OnStarted) {
Timber.e("## SAS Cannot perform accept from state $state")
return
}
// Select a key agreement protocol, a hash algorithm, a message authentication code,
// and short authentication string methods out of the lists given in requester's message.
val agreedProtocol = startReq!!.keyAgreementProtocols.firstOrNull { KNOWN_AGREEMENT_PROTOCOLS.contains(it) }
val agreedHash = startReq!!.hashes.firstOrNull { KNOWN_HASHES.contains(it) }
val agreedMac = startReq!!.messageAuthenticationCodes.firstOrNull { KNOWN_MACS.contains(it) }
val agreedShortCode = startReq!!.shortAuthenticationStrings.filter { KNOWN_SHORT_CODES.contains(it) }
// No common key sharing/hashing/hmac/SAS methods.
// If a device is unable to complete the verification because the devices are unable to find a common key sharing,
// hashing, hmac, or SAS method, then it should send a m.key.verification.cancel message
if (listOf(agreedProtocol, agreedHash, agreedMac).any { it.isNullOrBlank() } ||
agreedShortCode.isNullOrEmpty()) {
// Failed to find agreement
Timber.e("## SAS Failed to find agreement ")
cancel(CancelCode.UnknownMethod)
return
}
// Bobs device ensures that it has a copy of Alices device key.
val mxDeviceInfo = cryptoStore.getUserDevice(userId = otherUserId, deviceId = otherDeviceId!!)
if (mxDeviceInfo?.fingerprint() == null) {
Timber.e("## SAS Failed to find device key ")
// TODO force download keys!!
// would be probably better to download the keys
// for now I cancel
cancel(CancelCode.User)
} else {
// val otherKey = info.identityKey()
// need to jump back to correct thread
val accept = transport.createAccept(
tid = transactionId,
keyAgreementProtocol = agreedProtocol!!,
hash = agreedHash!!,
messageAuthenticationCode = agreedMac!!,
shortAuthenticationStrings = agreedShortCode,
commitment = Base64.encodeToString("temporary commitment".toByteArray(), Base64.DEFAULT)
)
doAccept(accept)
}
}
private fun doAccept(accept: VerificationInfoAccept) {
this.accepted = accept.asValidObject()
Timber.v("## SAS incoming accept request id:$transactionId")
// The hash commitment is the hash (using the selected hash algorithm) of the unpadded base64 representation of QB,
// concatenated with the canonical JSON representation of the content of the m.key.verification.start message
val concat = getSAS().publicKey + startReq!!.canonicalJson
accept.commitment = hashUsingAgreedHashMethod(concat) ?: ""
// we need to send this to other device now
state = VerificationTxState.SendingAccept
sendToOther(EventType.KEY_VERIFICATION_ACCEPT, accept, VerificationTxState.Accepted, CancelCode.User) {
if (state == VerificationTxState.SendingAccept) {
// It is possible that we receive the next event before this one :/, in this case we should keep state
state = VerificationTxState.Accepted
}
}
}
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
Timber.v("## SAS invalid message for incoming request id:$transactionId")
cancel(CancelCode.UnexpectedMessage)
}
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
Timber.v("## SAS received key for request id:$transactionId")
if (state != VerificationTxState.SendingAccept && state != VerificationTxState.Accepted) {
Timber.e("## SAS received key from invalid state $state")
cancel(CancelCode.UnexpectedMessage)
return
}
otherKey = vKey.key
// Upon receipt of the m.key.verification.key message from Alices device,
// Bobs device replies with a to_device message with type set to m.key.verification.key,
// sending Bobs public key QB
val pubKey = getSAS().publicKey
val keyToDevice = transport.createKey(transactionId, pubKey)
// we need to send this to other device now
state = VerificationTxState.SendingKey
this.sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, VerificationTxState.KeySent, CancelCode.User) {
if (state == VerificationTxState.SendingKey) {
// It is possible that we receive the next event before this one :/, in this case we should keep state
state = VerificationTxState.KeySent
}
}
// Alices and Bobs devices perform an Elliptic-curve Diffie-Hellman
// (calculate the point (x,y)=dAQB=dBQA and use x as the result of the ECDH),
// using the result as the shared secret.
getSAS().setTheirPublicKey(otherKey)
shortCodeBytes = calculateSASBytes()
if (BuildConfig.LOG_PRIVATE_DATA) {
Timber.v("************ BOB CODE ${getDecimalCodeRepresentation(shortCodeBytes!!)}")
Timber.v("************ BOB EMOJI CODE ${getShortCodeRepresentation(SasMode.EMOJI)}")
}
state = VerificationTxState.ShortCodeReady
}
private fun calculateSASBytes(): ByteArray {
when (accepted?.keyAgreementProtocol) {
KEY_AGREEMENT_V1 -> {
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start message,
// - the Matrix ID of the user who sent the m.key.verification.accept message,
// - he device ID of the device that sent the m.key.verification.accept message
// - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$otherUserId$otherDeviceId$userId$deviceId$transactionId"
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
return getSAS().generateShortCode(sasInfo, 6)
}
KEY_AGREEMENT_V2 -> {
// Adds the SAS public key, and separate by |
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$otherUserId|$otherDeviceId|$otherKey|$userId|$deviceId|${getSAS().publicKey}|$transactionId"
return getSAS().generateShortCode(sasInfo, 6)
}
else -> {
// Protocol has been checked earlier
throw IllegalArgumentException()
}
}
}
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
Timber.v("## SAS I: received mac for request id:$transactionId")
// Check for state?
if (state != VerificationTxState.SendingKey &&
state != VerificationTxState.KeySent &&
state != VerificationTxState.ShortCodeReady &&
state != VerificationTxState.ShortCodeAccepted &&
state != VerificationTxState.SendingMac &&
state != VerificationTxState.MacSent) {
Timber.e("## SAS I: received key from invalid state $state")
cancel(CancelCode.UnexpectedMessage)
return
}
theirMac = vMac
// Do I have my Mac?
if (myMac != null) {
// I can check
verifyMacs(vMac)
}
// Wait for ShortCode Accepted
}
}

View File

@ -1,257 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.OutgoingSasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import timber.log.Timber
internal class DefaultOutgoingSASDefaultVerificationTransaction(
setDeviceVerificationAction: SetDeviceVerificationAction,
userId: String,
deviceId: String?,
cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
deviceFingerprint: String,
transactionId: String,
otherUserId: String,
otherDeviceId: String
) : SASDefaultVerificationTransaction(
setDeviceVerificationAction,
userId,
deviceId,
cryptoStore,
crossSigningService,
outgoingKeyRequestManager,
secretShareManager,
deviceFingerprint,
transactionId,
otherUserId,
otherDeviceId,
isIncoming = false
),
OutgoingSasVerificationTransaction {
override val uxState: OutgoingSasVerificationTransaction.UxState
get() {
return when (val immutableState = state) {
is VerificationTxState.None -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_START
is VerificationTxState.SendingStart,
is VerificationTxState.Started,
is VerificationTxState.OnAccepted,
is VerificationTxState.SendingKey,
is VerificationTxState.KeySent,
is VerificationTxState.OnKeyReceived -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_KEY_AGREEMENT
is VerificationTxState.ShortCodeReady -> OutgoingSasVerificationTransaction.UxState.SHOW_SAS
is VerificationTxState.ShortCodeAccepted,
is VerificationTxState.SendingMac,
is VerificationTxState.MacSent,
is VerificationTxState.Verifying -> OutgoingSasVerificationTransaction.UxState.WAIT_FOR_VERIFICATION
is VerificationTxState.Verified -> OutgoingSasVerificationTransaction.UxState.VERIFIED
is VerificationTxState.Cancelled -> {
if (immutableState.byMe) {
OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_OTHER
} else {
OutgoingSasVerificationTransaction.UxState.CANCELLED_BY_ME
}
}
else -> OutgoingSasVerificationTransaction.UxState.UNKNOWN
}
}
override fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart) {
Timber.e("## SAS O: onVerificationStart - unexpected id:$transactionId")
cancel(CancelCode.UnexpectedMessage)
}
fun start() {
if (state != VerificationTxState.None) {
Timber.e("## SAS O: start verification from invalid state")
// should I cancel??
throw IllegalStateException("Interactive Key verification already started")
}
val startMessage = transport.createStartForSas(
deviceId ?: "",
transactionId,
KNOWN_AGREEMENT_PROTOCOLS,
KNOWN_HASHES,
KNOWN_MACS,
KNOWN_SHORT_CODES
)
startReq = startMessage.asValidObject() as? ValidVerificationInfoStart.SasVerificationInfoStart
state = VerificationTxState.SendingStart
sendToOther(
EventType.KEY_VERIFICATION_START,
startMessage,
VerificationTxState.Started,
CancelCode.User,
null
)
}
// fun request() {
// if (state != VerificationTxState.None) {
// Timber.e("## start verification from invalid state")
// // should I cancel??
// throw IllegalStateException("Interactive Key verification already started")
// }
//
// val requestMessage = KeyVerificationRequest(
// fromDevice = session.sessionParams.deviceId ?: "",
// methods = listOf(KeyVerificationStart.VERIF_METHOD_SAS),
// timestamp = clock.epochMillis().toInt(),
// transactionId = transactionId
// )
//
// sendToOther(
// EventType.KEY_VERIFICATION_REQUEST,
// requestMessage,
// VerificationTxState.None,
// CancelCode.User,
// null
// )
// }
override fun onVerificationAccept(accept: ValidVerificationInfoAccept) {
Timber.v("## SAS O: onVerificationAccept id:$transactionId")
if (state != VerificationTxState.Started && state != VerificationTxState.SendingStart) {
Timber.e("## SAS O: received accept request from invalid state $state")
cancel(CancelCode.UnexpectedMessage)
return
}
// Check that the agreement is correct
if (!KNOWN_AGREEMENT_PROTOCOLS.contains(accept.keyAgreementProtocol) ||
!KNOWN_HASHES.contains(accept.hash) ||
!KNOWN_MACS.contains(accept.messageAuthenticationCode) ||
accept.shortAuthenticationStrings.intersect(KNOWN_SHORT_CODES).isEmpty()) {
Timber.e("## SAS O: received invalid accept")
cancel(CancelCode.UnknownMethod)
return
}
// Upon receipt of the m.key.verification.accept message from Bobs device,
// Alices device stores the commitment value for later use.
accepted = accept
state = VerificationTxState.OnAccepted
// Alices device creates an ephemeral Curve25519 key pair (dA,QA),
// and replies with a to_device message with type set to “m.key.verification.key”, sending Alices public key QA
val pubKey = getSAS().publicKey
val keyToDevice = transport.createKey(transactionId, pubKey)
// we need to send this to other device now
state = VerificationTxState.SendingKey
sendToOther(EventType.KEY_VERIFICATION_KEY, keyToDevice, VerificationTxState.KeySent, CancelCode.User) {
// It is possible that we receive the next event before this one :/, in this case we should keep state
if (state == VerificationTxState.SendingKey) {
state = VerificationTxState.KeySent
}
}
}
override fun onKeyVerificationKey(vKey: ValidVerificationInfoKey) {
Timber.v("## SAS O: onKeyVerificationKey id:$transactionId")
if (state != VerificationTxState.SendingKey && state != VerificationTxState.KeySent) {
Timber.e("## received key from invalid state $state")
cancel(CancelCode.UnexpectedMessage)
return
}
otherKey = vKey.key
// Upon receipt of the m.key.verification.key message from Bobs device,
// Alices device checks that the commitment property from the Bobs m.key.verification.accept
// message is the same as the expected value based on the value of the key property received
// in Bobs m.key.verification.key and the content of Alices m.key.verification.start message.
// check commitment
val concat = vKey.key + startReq!!.canonicalJson
val otherCommitment = hashUsingAgreedHashMethod(concat) ?: ""
if (accepted!!.commitment.equals(otherCommitment)) {
getSAS().setTheirPublicKey(otherKey)
shortCodeBytes = calculateSASBytes()
state = VerificationTxState.ShortCodeReady
} else {
// bad commitment
cancel(CancelCode.MismatchedCommitment)
}
}
private fun calculateSASBytes(): ByteArray {
when (accepted?.keyAgreementProtocol) {
KEY_AGREEMENT_V1 -> {
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start message,
// - the Matrix ID of the user who sent the m.key.verification.accept message,
// - he device ID of the device that sent the m.key.verification.accept message
// - the transaction ID.
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS$userId$deviceId$otherUserId$otherDeviceId$transactionId"
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
return getSAS().generateShortCode(sasInfo, 6)
}
KEY_AGREEMENT_V2 -> {
// Adds the SAS public key, and separate by |
val sasInfo = "MATRIX_KEY_VERIFICATION_SAS|$userId|$deviceId|${getSAS().publicKey}|$otherUserId|$otherDeviceId|$otherKey|$transactionId"
return getSAS().generateShortCode(sasInfo, 6)
}
else -> {
// Protocol has been checked earlier
throw IllegalArgumentException()
}
}
}
override fun onKeyVerificationMac(vMac: ValidVerificationInfoMac) {
Timber.v("## SAS O: onKeyVerificationMac id:$transactionId")
// There is starting to be a huge amount of state / race here :/
if (state != VerificationTxState.OnKeyReceived &&
state != VerificationTxState.ShortCodeReady &&
state != VerificationTxState.ShortCodeAccepted &&
state != VerificationTxState.KeySent &&
state != VerificationTxState.SendingMac &&
state != VerificationTxState.MacSent) {
Timber.e("## SAS O: received mac from invalid state $state")
cancel(CancelCode.UnexpectedMessage)
return
}
theirMac = vMac
// Do I have my Mac?
if (myMac != null) {
// I can check
verifyMacs(vMac)
}
// Wait for ShortCode Accepted
}
}

View File

@ -1,118 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.crosssigning.DeviceTrustLevel
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import timber.log.Timber
/**
* Generic interactive key verification transaction.
*/
internal abstract class DefaultVerificationTransaction(
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val crossSigningService: CrossSigningService,
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
private val secretShareManager: SecretShareManager,
private val userId: String,
override val transactionId: String,
override val otherUserId: String,
override var otherDeviceId: String? = null,
override val isIncoming: Boolean
) : VerificationTransaction {
lateinit var transport: VerificationTransport
interface Listener {
fun transactionUpdated(tx: VerificationTransaction)
}
protected var listeners = ArrayList<Listener>()
fun addListener(listener: Listener) {
if (!listeners.contains(listener)) listeners.add(listener)
}
fun removeListener(listener: Listener) {
listeners.remove(listener)
}
protected fun trust(
canTrustOtherUserMasterKey: Boolean,
toVerifyDeviceIds: List<String>,
eventuallyMarkMyMasterKeyAsTrusted: Boolean,
autoDone: Boolean = true
) {
Timber.d("## Verification: trust ($otherUserId,$otherDeviceId) , verifiedDevices:$toVerifyDeviceIds")
Timber.d("## Verification: trust Mark myMSK trusted $eventuallyMarkMyMasterKeyAsTrusted")
// TODO what if the otherDevice is not in this list? and should we
toVerifyDeviceIds.forEach {
setDeviceVerified(otherUserId, it)
}
// If not me sign his MSK and upload the signature
if (canTrustOtherUserMasterKey) {
// we should trust this master key
// And check verification MSK -> SSK?
if (otherUserId != userId) {
crossSigningService.trustUser(otherUserId, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## Verification: Failed to trust User $otherUserId")
}
})
} else {
// Notice other master key is mine because other is me
if (eventuallyMarkMyMasterKeyAsTrusted) {
// Mark my keys as trusted locally
crossSigningService.markMyMasterKeyAsTrusted()
}
}
}
if (otherUserId == userId) {
secretShareManager.onVerificationCompleteForDevice(otherDeviceId!!)
// If me it's reasonable to sign and upload the device signature
// Notice that i might not have the private keys, so may not be able to do it
crossSigningService.trustDevice(otherDeviceId!!, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
Timber.w("## Verification: Failed to sign new device $otherDeviceId, ${failure.localizedMessage}")
}
})
}
if (autoDone) {
state = VerificationTxState.Verified
transport.done(transactionId) {}
}
}
private fun setDeviceVerified(userId: String, deviceId: String) {
// TODO should not override cross sign status
setDeviceVerificationAction.handle(
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
userId,
deviceId
)
}
}

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
class KotlinQRVerification(
override val qrCodeText: String?,
override val state: VerificationTxState,
override val method: VerificationMethod,
override val transactionId: String,
override val otherUserId: String,
override val otherDeviceId: String?,
override val isIncoming: Boolean) : QrCodeVerificationTransaction {
override suspend fun userHasScannedOtherQrCode(otherQrCodeText: String) {
TODO("Not yet implemented")
}
override suspend fun otherUserScannedMyQrCode() {
TODO("Not yet implemented")
}
override suspend fun otherUserDidNotScannedMyQrCode() {
TODO("Not yet implemented")
}
override suspend fun cancel() {
TODO("Not yet implemented")
}
override suspend fun cancel(code: CancelCode) {
TODO("Not yet implemented")
}
override fun isToDeviceTransport(): Boolean {
TODO("Not yet implemented")
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
import org.matrix.android.sdk.api.session.crypto.verification.IVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SCAN
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_CODE_SHOW
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import org.matrix.android.sdk.internal.crypto.verification.qrcode.QrCodeData
import org.matrix.android.sdk.internal.crypto.verification.qrcode.toEncodedString
internal class KotlinVerificationRequest(
val requestId: String,
val incoming: Boolean,
val otherUserId: String,
var state: EVerificationState,
val ageLocalTs: Long
) : IVerificationRequest {
var roomId: String? = null
var qrCodeData: QrCodeData? = null
var targetDevices: List<String>? = null
var requestInfo: ValidVerificationInfoRequest? = null
var readyInfo: ValidVerificationInfoReady? = null
var cancelCode: CancelCode? = null
override fun requestId() = requestId
override fun incoming() = incoming
override fun otherUserId() = otherUserId
override fun roomId() = roomId
override fun targetDevices() = targetDevices
override fun state() = state
override fun ageLocalTs() = ageLocalTs
override fun otherDeviceId(): String? {
return if (incoming) {
requestInfo?.fromDevice
} else {
readyInfo?.fromDevice
}
}
override fun cancelCode(): CancelCode? = cancelCode
/**
* SAS is supported if I support it and the other party support it.
*/
override fun isSasSupported(): Boolean {
return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
}
/**
* Other can show QR code if I can scan QR code and other can show QR code.
*/
override fun otherCanShowQrCode(): Boolean {
return if (incoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
} else {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
}
}
/**
* Other can scan QR code if I can show QR code and other can scan QR code.
*/
override fun otherCanScanQrCode(): Boolean {
return if (incoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
} else {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
}
}
override fun qrCodeText() = qrCodeData?.toEncodedString()
override fun toString(): String {
return toPendingVerificationRequest().toString()
}
fun toPendingVerificationRequest(): PendingVerificationRequest {
return PendingVerificationRequest(
ageLocalTs = ageLocalTs,
state = state,
isIncoming = incoming,
otherUserId = otherUserId,
roomId = roomId,
transactionId = requestId,
cancelConclusion = cancelCode,
isFinished = isFinished(),
handledByOtherSession = state == EVerificationState.HandledByOtherSession,
targetDevices = targetDevices,
qrCodeText = qrCodeText(),
isSasSupported = isSasSupported(),
otherCanShowQrCode = otherCanShowQrCode(),
otherCanScanQrCode = otherCanScanQrCode(),
otherDeviceId = otherDeviceId()
)
}
}

View File

@ -1,428 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.extensions.toUnsignedInt
import org.matrix.olm.OlmSAS
import org.matrix.olm.OlmUtility
import timber.log.Timber
import java.util.Locale
/**
* Represents an ongoing short code interactive key verification between two devices.
*/
internal abstract class SASDefaultVerificationTransaction(
setDeviceVerificationAction: SetDeviceVerificationAction,
open val userId: String,
open val deviceId: String?,
private val cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
private val deviceFingerprint: String,
transactionId: String,
otherUserId: String,
otherDeviceId: String?,
isIncoming: Boolean
) : DefaultVerificationTransaction(
setDeviceVerificationAction,
crossSigningService,
outgoingKeyRequestManager,
secretShareManager,
userId,
transactionId,
otherUserId,
otherDeviceId,
isIncoming
),
SasVerificationTransaction {
companion object {
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
// Deprecated maybe removed later, use V2
const val KEY_AGREEMENT_V1 = "curve25519"
const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256"
// ordered by preferred order
val KNOWN_AGREEMENT_PROTOCOLS = listOf(KEY_AGREEMENT_V2, KEY_AGREEMENT_V1)
// ordered by preferred order
val KNOWN_HASHES = listOf("sha256")
// ordered by preferred order
val KNOWN_MACS = listOf(SAS_MAC_SHA256, SAS_MAC_SHA256_LONGKDF)
// older devices have limited support of emoji but SDK offers images for the 64 verification emojis
// so always send that we support EMOJI
val KNOWN_SHORT_CODES = listOf(SasMode.EMOJI, SasMode.DECIMAL)
}
override var state: VerificationTxState = VerificationTxState.None
set(newState) {
field = newState
listeners.forEach {
try {
it.transactionUpdated(this)
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
}
}
if (newState is VerificationTxState.TerminalTxState) {
releaseSAS()
}
}
private var olmSas: OlmSAS? = null
// Visible for test
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
// Visible for test
var accepted: ValidVerificationInfoAccept? = null
protected var otherKey: String? = null
protected var shortCodeBytes: ByteArray? = null
protected var myMac: ValidVerificationInfoMac? = null
protected var theirMac: ValidVerificationInfoMac? = null
protected fun getSAS(): OlmSAS {
if (olmSas == null) olmSas = OlmSAS()
return olmSas!!
}
// To override finalize(), all you need to do is simply declare it, without using the override keyword:
protected fun finalize() {
releaseSAS()
}
private fun releaseSAS() {
// finalization logic
olmSas?.releaseSas()
olmSas = null
}
/**
* To be called by the client when the user has verified that
* both short codes do match.
*/
override fun userHasVerifiedShortCode() {
Timber.v("## SAS short code verified by user for id:$transactionId")
if (state != VerificationTxState.ShortCodeReady) {
// ignore and cancel?
Timber.e("## Accepted short code from invalid state $state")
cancel(CancelCode.UnexpectedMessage)
return
}
state = VerificationTxState.ShortCodeAccepted
// Alice and Bob devices calculate the HMAC of their own device keys and a comma-separated,
// sorted list of the key IDs that they wish the other user to verify,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_MAC”,
// - the Matrix ID of the user whose key is being MAC-ed,
// - the device ID of the device sending the MAC,
// - the Matrix ID of the other user,
// - the device ID of the device receiving the MAC,
// - the transaction ID, and
// - the key ID of the key being MAC-ed, or the string “KEY_IDS” if the item being MAC-ed is the list of key IDs.
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$userId$deviceId$otherUserId$otherDeviceId$transactionId"
// Previously, with SAS verification, the m.key.verification.mac message only contained the user's device key.
// It should now contain both the device key and the MSK.
// So when Alice and Bob verify with SAS, the verification will verify the MSK.
val keyMap = HashMap<String, String>()
val keyId = "ed25519:$deviceId"
val macString = macUsingAgreedMethod(deviceFingerprint, baseInfo + keyId)
if (macString.isNullOrBlank()) {
// Should not happen
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
cancel(CancelCode.UnexpectedMessage)
return
}
keyMap[keyId] = macString
cryptoStore.getMyCrossSigningInfo()?.takeIf { it.isTrusted() }
?.masterKey()
?.unpaddedBase64PublicKey
?.let { masterPublicKey ->
val crossSigningKeyId = "ed25519:$masterPublicKey"
macUsingAgreedMethod(masterPublicKey, baseInfo + crossSigningKeyId)?.let { mskMacString ->
keyMap[crossSigningKeyId] = mskMacString
}
}
val keyStrings = macUsingAgreedMethod(keyMap.keys.sorted().joinToString(","), baseInfo + "KEY_IDS")
if (macString.isNullOrBlank() || keyStrings.isNullOrBlank()) {
// Should not happen
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
cancel(CancelCode.UnexpectedMessage)
return
}
val macMsg = transport.createMac(transactionId, keyMap, keyStrings)
myMac = macMsg.asValidObject()
state = VerificationTxState.SendingMac
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, VerificationTxState.MacSent, CancelCode.User) {
if (state == VerificationTxState.SendingMac) {
// It is possible that we receive the next event before this one :/, in this case we should keep state
state = VerificationTxState.MacSent
}
}
// Do I already have their Mac?
theirMac?.let { verifyMacs(it) }
// if not wait for it
}
override fun shortCodeDoesNotMatch() {
Timber.v("## SAS short code do not match for id:$transactionId")
cancel(CancelCode.MismatchedSas)
}
override fun isToDeviceTransport(): Boolean {
return transport is VerificationTransportToDevice
}
abstract fun onVerificationStart(startReq: ValidVerificationInfoStart.SasVerificationInfoStart)
abstract fun onVerificationAccept(accept: ValidVerificationInfoAccept)
abstract fun onKeyVerificationKey(vKey: ValidVerificationInfoKey)
abstract fun onKeyVerificationMac(vMac: ValidVerificationInfoMac)
protected fun verifyMacs(theirMacSafe: ValidVerificationInfoMac) {
Timber.v("## SAS verifying macs for id:$transactionId")
state = VerificationTxState.Verifying
// Keys have been downloaded earlier in process
val otherUserKnownDevices = cryptoStore.getUserDevices(otherUserId)
// Bobs device calculates the HMAC (as above) of its copies of Alices keys given in the message (as identified by their key ID),
// as well as the HMAC of the comma-separated, sorted list of the key IDs given in the message.
// Bobs device compares these with the HMAC values given in the m.key.verification.mac message.
// If everything matches, then consider Alices device keys as verified.
val baseInfo = "MATRIX_KEY_VERIFICATION_MAC$otherUserId$otherDeviceId$userId$deviceId$transactionId"
val commaSeparatedListOfKeyIds = theirMacSafe.mac.keys.sorted().joinToString(",")
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
if (theirMacSafe.keys != keyStrings) {
// WRONG!
cancel(CancelCode.MismatchedKeys)
return
}
val verifiedDevices = ArrayList<String>()
// cannot be empty because it has been validated
theirMacSafe.mac.keys.forEach {
val keyIDNoPrefix = it.removePrefix("ed25519:")
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
if (otherDeviceKey == null) {
Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify")
// just ignore and continue
return@forEach
}
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + it)
if (mac != theirMacSafe.mac[it]) {
// WRONG!
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
cancel(CancelCode.MismatchedKeys)
return
}
verifiedDevices.add(keyIDNoPrefix)
}
var otherMasterKeyIsVerified = false
val otherMasterKey = cryptoStore.getCrossSigningInfo(otherUserId)?.masterKey()
val otherCrossSigningMasterKeyPublic = otherMasterKey?.unpaddedBase64PublicKey
if (otherCrossSigningMasterKeyPublic != null) {
// Did the user signed his master key
theirMacSafe.mac.keys.forEach {
val keyIDNoPrefix = it.removePrefix("ed25519:")
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
// Check the signature
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
if (mac != theirMacSafe.mac[it]) {
// WRONG!
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
cancel(CancelCode.MismatchedKeys)
return
} else {
otherMasterKeyIsVerified = true
}
}
}
}
// if none of the keys could be verified, then error because the app
// should be informed about that
if (verifiedDevices.isEmpty() && !otherMasterKeyIsVerified) {
Timber.e("## SAS Verification: No devices verified")
cancel(CancelCode.MismatchedKeys)
return
}
trust(
otherMasterKeyIsVerified,
verifiedDevices,
eventuallyMarkMyMasterKeyAsTrusted = otherMasterKey?.trustLevel?.isVerified() == false
)
}
override fun cancel() {
cancel(CancelCode.User)
}
override fun cancel(code: CancelCode) {
state = VerificationTxState.Cancelled(code, true)
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
}
protected fun <T> sendToOther(
type: String,
keyToDevice: VerificationInfo<T>,
nextState: VerificationTxState,
onErrorReason: CancelCode,
onDone: (() -> Unit)?
) {
transport.sendToOther(type, keyToDevice, nextState, onErrorReason, onDone)
}
fun getShortCodeRepresentation(shortAuthenticationStringMode: String): String? {
if (shortCodeBytes == null) {
return null
}
when (shortAuthenticationStringMode) {
SasMode.DECIMAL -> {
if (shortCodeBytes!!.size < 5) return null
return getDecimalCodeRepresentation(shortCodeBytes!!)
}
SasMode.EMOJI -> {
if (shortCodeBytes!!.size < 6) return null
return getEmojiCodeRepresentation(shortCodeBytes!!).joinToString(" ") { it.emoji }
}
else -> return null
}
}
override fun supportsEmoji(): Boolean {
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI).orFalse()
}
override fun supportsDecimal(): Boolean {
return accepted?.shortAuthenticationStrings?.contains(SasMode.DECIMAL).orFalse()
}
protected fun hashUsingAgreedHashMethod(toHash: String): String? {
if ("sha256" == accepted?.hash?.lowercase(Locale.ROOT)) {
val olmUtil = OlmUtility()
val hashBytes = olmUtil.sha256(toHash)
olmUtil.releaseUtility()
return hashBytes
}
return null
}
private fun macUsingAgreedMethod(message: String, info: String): String? {
return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
else -> null
}
}
override fun getDecimalCodeRepresentation(): String {
return getDecimalCodeRepresentation(shortCodeBytes!!)
}
/**
* decimal: generate five bytes by using HKDF.
* Take the first 13 bits and convert it to a decimal number (which will be a number between 0 and 8191 inclusive),
* and add 1000 (resulting in a number between 1000 and 9191 inclusive).
* Do the same with the second 13 bits, and the third 13 bits, giving three 4-digit numbers.
* In other words, if the five bytes are B0, B1, B2, B3, and B4, then the first number is (B0 << 5 | B1 >> 3) + 1000,
* the second number is ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000, and the third number is ((B3 & 0x3f) << 7 | B4 >> 1) + 1000.
* (This method of converting 13 bits at a time is used to avoid requiring 32-bit clients to do big-number arithmetic,
* and adding 1000 to the number avoids having clients to worry about properly zero-padding the number when displaying to the user.)
* The three 4-digit numbers are displayed to the user either with dashes (or another appropriate separator) separating the three numbers,
* or with the three numbers on separate lines.
*/
fun getDecimalCodeRepresentation(byteArray: ByteArray): String {
val b0 = byteArray[0].toUnsignedInt() // need unsigned byte
val b1 = byteArray[1].toUnsignedInt() // need unsigned byte
val b2 = byteArray[2].toUnsignedInt() // need unsigned byte
val b3 = byteArray[3].toUnsignedInt() // need unsigned byte
val b4 = byteArray[4].toUnsignedInt() // need unsigned byte
// (B0 << 5 | B1 >> 3) + 1000
val first = (b0.shl(5) or b1.shr(3)) + 1000
// ((B1 & 0x7) << 10 | B2 << 2 | B3 >> 6) + 1000
val second = ((b1 and 0x7).shl(10) or b2.shl(2) or b3.shr(6)) + 1000
// ((B3 & 0x3f) << 7 | B4 >> 1) + 1000
val third = ((b3 and 0x3f).shl(7) or b4.shr(1)) + 1000
return "$first $second $third"
}
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
return getEmojiCodeRepresentation(shortCodeBytes!!)
}
/**
* emoji: generate six bytes by using HKDF.
* Split the first 42 bits into 7 groups of 6 bits, as one would do when creating a base64 encoding.
* For each group of 6 bits, look up the emoji from Appendix A corresponding
* to that number 7 emoji are selected from a list of 64 emoji (see Appendix A)
*/
private fun getEmojiCodeRepresentation(byteArray: ByteArray): List<EmojiRepresentation> {
val b0 = byteArray[0].toUnsignedInt()
val b1 = byteArray[1].toUnsignedInt()
val b2 = byteArray[2].toUnsignedInt()
val b3 = byteArray[3].toUnsignedInt()
val b4 = byteArray[4].toUnsignedInt()
val b5 = byteArray[5].toUnsignedInt()
return listOf(
getEmojiForCode((b0 and 0xFC).shr(2)),
getEmojiForCode((b0 and 0x3).shl(4) or (b1 and 0xF0).shr(4)),
getEmojiForCode((b1 and 0xF).shl(2) or (b2 and 0xC0).shr(6)),
getEmojiForCode((b2 and 0x3F)),
getEmojiForCode((b3 and 0xFC).shr(2)),
getEmojiForCode((b3 and 0x3).shl(4) or (b4 and 0xF0).shr(4)),
getEmojiForCode((b4 and 0xF).shl(2) or (b5 and 0xC0).shr(6))
)
}
}

View File

@ -0,0 +1,474 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.crypto.verification
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.channels.Channel
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.EmojiRepresentation
import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.KEY_AGREEMENT_V1
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.KEY_AGREEMENT_V2
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.KNOWN_AGREEMENT_PROTOCOLS
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.KNOWN_HASHES
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.KNOWN_MACS
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.KNOWN_SHORT_CODES
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.SAS_MAC_SHA256
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction.Companion.SAS_MAC_SHA256_LONGKDF
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationAcceptContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationKeyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationMacContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationKey
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationMac
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationReady
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import org.matrix.olm.OlmSAS
import timber.log.Timber
import java.util.Locale
internal class SasV1Transaction(
private val channel: Channel<VerificationIntent>,
override var state: VerificationTxState,
override val transactionId: String,
override val otherUserId: String,
private val myUserId: String,
private val myTrustedMSK: String?,
override var otherDeviceId: String?,
private val myDeviceId: String,
private val myDeviceFingerprint: String,
override val isIncoming: Boolean,
val startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null,
val isToDevice: Boolean,
) : SasVerificationTransaction {
override val method: VerificationMethod
get() = VerificationMethod.SAS
companion object {
fun sasStart(inRoom: Boolean, fromDevice: String, requestId: String): VerificationInfoStart {
return if (inRoom) {
MessageVerificationStartContent(
fromDevice = fromDevice,
hashes = KNOWN_HASHES,
keyAgreementProtocols = KNOWN_AGREEMENT_PROTOCOLS,
messageAuthenticationCodes = KNOWN_MACS,
shortAuthenticationStrings = KNOWN_SHORT_CODES,
method = VERIFICATION_METHOD_SAS,
relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE,
eventId = requestId
),
sharedSecret = null
)
} else {
KeyVerificationStart(
fromDevice,
VERIFICATION_METHOD_SAS,
requestId,
KNOWN_AGREEMENT_PROTOCOLS,
KNOWN_HASHES,
KNOWN_MACS,
KNOWN_SHORT_CODES,
null
)
}
}
fun sasAccept(
inRoom: Boolean,
requestId: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>,
): VerificationInfoAccept {
return if (inRoom) {
MessageVerificationAcceptContent.create(
requestId,
keyAgreementProtocol,
hash,
commitment,
messageAuthenticationCode,
shortAuthenticationStrings
)
} else {
KeyVerificationAccept.create(
requestId,
keyAgreementProtocol,
hash,
commitment,
messageAuthenticationCode,
shortAuthenticationStrings
)
}
}
fun sasReady(
inRoom: Boolean,
requestId: String,
methods: List<String>,
fromDevice: String,
): VerificationInfoReady {
return if (inRoom) {
MessageVerificationReadyContent.create(
requestId,
methods,
fromDevice,
)
} else {
KeyVerificationReady(
fromDevice = fromDevice,
methods = methods,
transactionId = requestId,
)
}
}
fun sasKeyMessage(
inRoom: Boolean,
requestId: String,
pubKey: String,
): VerificationInfoKey {
return if (inRoom) {
MessageVerificationKeyContent.create(tid = requestId, pubKey = pubKey)
} else {
KeyVerificationKey.create(tid = requestId, pubKey = pubKey)
}
}
fun sasMacMessage(
inRoom: Boolean,
requestId: String,
validVerificationInfoMac: ValidVerificationInfoMac
): VerificationInfoMac {
return if (inRoom) {
MessageVerificationMacContent.create(
tid = requestId,
keys = validVerificationInfoMac.keys,
mac = validVerificationInfoMac.mac
)
} else {
KeyVerificationMac.create(
tid = requestId,
keys = validVerificationInfoMac.keys,
mac = validVerificationInfoMac.mac
)
}
}
}
private var olmSas: OlmSAS? = null
fun getSAS(): OlmSAS {
if (olmSas == null) olmSas = OlmSAS()
return olmSas!!
}
// To override finalize(), all you need to do is simply declare it, without using the override keyword:
protected fun finalize() {
releaseSAS()
}
private fun releaseSAS() {
// finalization logic
olmSas?.releaseSas()
olmSas = null
}
var accepted: ValidVerificationInfoAccept? = null
var otherKey: String? = null
var shortCodeBytes: ByteArray? = null
var myMac: ValidVerificationInfoMac? = null
var theirMac: ValidVerificationInfoMac? = null
override fun supportsEmoji(): Boolean {
return accepted?.shortAuthenticationStrings?.contains(SasMode.EMOJI) == true
}
override fun getEmojiCodeRepresentation(): List<EmojiRepresentation> {
return shortCodeBytes?.getEmojiCodeRepresentation().orEmpty()
}
override fun getDecimalCodeRepresentation(): String? {
return shortCodeBytes?.getDecimalCodeRepresentation()
}
override suspend fun userHasVerifiedShortCode() {
val deferred = CompletableDeferred<Unit>()
channel.send(
VerificationIntent.ActionSASCodeMatches(transactionId, deferred)
)
deferred.await()
}
override suspend fun acceptVerification() {
// nop
// as we are using verification request accept is automatic
}
override suspend fun shortCodeDoesNotMatch() {
val deferred = CompletableDeferred<Unit>()
channel.send(
VerificationIntent.ActionSASCodeDoesNotMatch(transactionId, deferred)
)
deferred.await()
}
override suspend fun cancel() {
val deferred = CompletableDeferred<Unit>()
channel.send(
VerificationIntent.ActionCancel(transactionId, deferred)
)
deferred.await()
}
override suspend fun cancel(code: CancelCode) {
val deferred = CompletableDeferred<Unit>()
channel.send(
VerificationIntent.ActionCancel(transactionId, deferred)
)
deferred.await()
}
override fun isToDeviceTransport() = isToDevice
fun calculateSASBytes(otherKey: String) {
this.otherKey = otherKey
getSAS().setTheirPublicKey(otherKey)
shortCodeBytes = when (accepted!!.keyAgreementProtocol) {
KEY_AGREEMENT_V1 -> {
// (Note: In all of the following HKDF is as defined in RFC 5869, and uses the previously agreed-on hash function as the hash function,
// the shared secret as the input keying material, no salt, and with the input parameter set to the concatenation of:
// - the string “MATRIX_KEY_VERIFICATION_SAS”,
// - the Matrix ID of the user who sent the m.key.verification.start message,
// - the device ID of the device that sent the m.key.verification.start message,
// - the Matrix ID of the user who sent the m.key.verification.accept message,
// - he device ID of the device that sent the m.key.verification.accept message
// - the transaction ID.
val sasInfo = buildString {
append("MATRIX_KEY_VERIFICATION_SAS")
if (isIncoming) {
append(otherUserId)
append(otherDeviceId)
append(myUserId)
append(myDeviceId)
append(getSAS().publicKey)
} else {
append(myUserId)
append(myDeviceId)
append(otherUserId)
append(otherDeviceId)
}
append(transactionId)
}
// decimal: generate five bytes by using HKDF.
// emoji: generate six bytes by using HKDF.
getSAS().generateShortCode(sasInfo, 6)
}
KEY_AGREEMENT_V2 -> {
val sasInfo = buildString {
append("MATRIX_KEY_VERIFICATION_SAS|")
if (isIncoming) {
append(otherUserId).append('|')
append(otherDeviceId).append('|')
append(otherKey).append('|')
append(myUserId).append('|')
append(myDeviceId).append('|')
append(getSAS().publicKey).append('|')
} else {
append(myUserId).append('|')
append(myDeviceId).append('|')
append(getSAS().publicKey).append('|')
append(otherUserId).append('|')
append(otherDeviceId).append('|')
append(otherKey).append('|')
}
append(transactionId)
}
getSAS().generateShortCode(sasInfo, 6)
}
else -> {
// Protocol has been checked earlier
throw IllegalArgumentException()
}
}
}
fun computeMyMac(): ValidVerificationInfoMac {
val baseInfo = buildString {
append("MATRIX_KEY_VERIFICATION_MAC")
append(myUserId)
append(myDeviceId)
append(otherUserId)
append(otherDeviceId)
append(transactionId)
}
// Previously, with SAS verification, the m.key.verification.mac message only contained the user's device key.
// It should now contain both the device key and the MSK.
// So when Alice and Bob verify with SAS, the verification will verify the MSK.
val keyMap = HashMap<String, String>()
val keyId = "ed25519:$myDeviceId"
val macString = macUsingAgreedMethod(myDeviceFingerprint, baseInfo + keyId)
if (macString.isNullOrBlank()) {
// Should not happen
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
throw IllegalStateException("Invalid mac for transaction ${transactionId}")
}
keyMap[keyId] = macString
if (myTrustedMSK != null) {
val crossSigningKeyId = "ed25519:$myTrustedMSK"
macUsingAgreedMethod(myTrustedMSK, baseInfo + crossSigningKeyId)?.let { mskMacString ->
keyMap[crossSigningKeyId] = mskMacString
}
}
val keyStrings = macUsingAgreedMethod(keyMap.keys.sorted().joinToString(","), baseInfo + "KEY_IDS")
if (keyStrings.isNullOrBlank()) {
// Should not happen
Timber.e("## SAS verification [$transactionId] failed to send KeyMac, empty key hashes.")
throw IllegalStateException("Invalid key mac for transaction ${transactionId}")
}
return ValidVerificationInfoMac(
transactionId,
keyMap,
keyStrings
).also {
myMac = it
}
}
sealed class MacVerificationResult {
object MismatchKeys : MacVerificationResult()
data class MismatchMacDevice(val deviceId: String) : MacVerificationResult()
object MismatchMacCrossSigning : MacVerificationResult()
object NoDevicesVerified : MacVerificationResult()
data class Success(val verifiedDeviceId: List<String>, val otherMskTrusted: Boolean) : MacVerificationResult()
}
fun verifyMacs(
theirMacSafe: ValidVerificationInfoMac,
otherUserKnownDevices: List<CryptoDeviceInfo>,
otherMasterKey: String?
): MacVerificationResult {
Timber.v("## SAS verifying macs for id:$transactionId")
// Bobs device calculates the HMAC (as above) of its copies of Alices keys given in the message (as identified by their key ID),
// as well as the HMAC of the comma-separated, sorted list of the key IDs given in the message.
// Bobs device compares these with the HMAC values given in the m.key.verification.mac message.
// If everything matches, then consider Alices device keys as verified.
val baseInfo = buildString {
append("MATRIX_KEY_VERIFICATION_MAC")
append(otherUserId)
append(otherDeviceId)
append(myUserId)
append(myDeviceId)
append(transactionId)
}
val commaSeparatedListOfKeyIds = theirMacSafe.mac.keys.sorted().joinToString(",")
val keyStrings = macUsingAgreedMethod(commaSeparatedListOfKeyIds, baseInfo + "KEY_IDS")
if (theirMacSafe.keys != keyStrings) {
// WRONG!
return MacVerificationResult.MismatchKeys
}
val verifiedDevices = ArrayList<String>()
// cannot be empty because it has been validated
theirMacSafe.mac.keys.forEach { entry ->
val keyIDNoPrefix = entry.removePrefix("ed25519:")
val otherDeviceKey = otherUserKnownDevices
.firstOrNull { it.deviceId == keyIDNoPrefix }
?.fingerprint()
if (otherDeviceKey == null) {
Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify")
// just ignore and continue
return@forEach
}
val mac = macUsingAgreedMethod(otherDeviceKey, baseInfo + entry)
if (mac != theirMacSafe.mac[entry]) {
// WRONG!
Timber.e("## SAS Verification: mac mismatch for $otherDeviceKey with id $keyIDNoPrefix")
// cancel(CancelCode.MismatchedKeys)
return MacVerificationResult.MismatchMacDevice(keyIDNoPrefix)
}
verifiedDevices.add(keyIDNoPrefix)
}
var otherMasterKeyIsVerified = false
if (otherMasterKey != null) {
// Did the user signed his master key
theirMacSafe.mac.keys.forEach {
val keyIDNoPrefix = it.removePrefix("ed25519:")
if (keyIDNoPrefix == otherMasterKey) {
// Check the signature
val mac = macUsingAgreedMethod(otherMasterKey, baseInfo + it)
if (mac != theirMacSafe.mac[it]) {
// WRONG!
Timber.e("## SAS Verification: mac mismatch for MasterKey with id $keyIDNoPrefix")
return MacVerificationResult.MismatchMacCrossSigning
} else {
otherMasterKeyIsVerified = true
}
}
}
}
// if none of the keys could be verified, then error because the app
// should be informed about that
if (verifiedDevices.isEmpty() && !otherMasterKeyIsVerified) {
Timber.e("## SAS Verification: No devices verified")
return MacVerificationResult.NoDevicesVerified
}
return MacVerificationResult.Success(
verifiedDevices,
otherMasterKeyIsVerified
)
}
private fun macUsingAgreedMethod(message: String, info: String): String? {
return when (accepted?.messageAuthenticationCode?.lowercase(Locale.ROOT)) {
SAS_MAC_SHA256_LONGKDF -> getSAS().calculateMacLongKdf(message, info)
SAS_MAC_SHA256 -> getSAS().calculateMac(message, info)
else -> null
}
}
}

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
import timber.log.Timber
/** /**
* A new event type is added to the key verification framework: m.key.verification.ready, * A new event type is added to the key verification framework: m.key.verification.ready,
@ -37,9 +38,15 @@ internal interface VerificationInfoReady : VerificationInfo<ValidVerificationInf
val methods: List<String>? val methods: List<String>?
override fun asValidObject(): ValidVerificationInfoReady? { override fun asValidObject(): ValidVerificationInfoReady? {
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null.also {
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null Timber.e("## SAS Invalid room ready content invalid transaction id $transactionId")
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null }
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null.also {
Timber.e("## SAS Invalid room ready content invalid fromDevice $fromDevice")
}
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null.also {
Timber.e("## SAS Invalid room ready content invalid methods $methods")
}
return ValidVerificationInfoReady( return ValidVerificationInfoReady(
validTransactionId, validTransactionId,

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.internal.crypto.verification package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.verification.SasMode import org.matrix.android.sdk.api.session.crypto.verification.SasMode
import org.matrix.android.sdk.api.session.crypto.verification.SasVerificationTransaction
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
@ -73,8 +74,8 @@ internal interface VerificationInfoStart : VerificationInfo<ValidVerificationInf
val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null val validHashes = hashes?.takeIf { it.contains("sha256") } ?: return null
val validMessageAuthenticationCodes = messageAuthenticationCodes val validMessageAuthenticationCodes = messageAuthenticationCodes
?.takeIf { ?.takeIf {
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256) || it.contains(SasVerificationTransaction.SAS_MAC_SHA256) ||
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF) it.contains(SasVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
} }
?: return null ?: return null
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null

View File

@ -0,0 +1,165 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.internal.crypto.verification
import kotlinx.coroutines.CompletableDeferred
import org.matrix.android.sdk.api.session.crypto.verification.IVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.PendingVerificationRequest
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoReady
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationMethod
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTransaction
internal sealed class VerificationIntent {
data class ActionRequestVerification(
val otherUserId: String,
// in case of verification in room
val roomId: String? = null,
val methods: List<VerificationMethod>,
// In case of to device it is sent to a list of devices
val targetDevices: List<String>? = null,
val deferred: CompletableDeferred<PendingVerificationRequest>,
) : VerificationIntent()
data class OnVerificationRequestReceived(
val validRequestInfo: ValidVerificationInfoRequest,
val senderId: String,
val roomId: String?,
val timeStamp: Long? = null,
// val deferred: CompletableDeferred<IVerificationRequest>,
) : VerificationIntent()
data class FailToSendRequest(
val request: PendingVerificationRequest,
) : VerificationIntent()
// data class UpdateRequest(
// val request: IVerificationRequest,
// ) : VerificationIntent()
data class ActionReadyRequest(
val transactionId: String,
val methods: List<VerificationMethod>,
val deferred: CompletableDeferred<PendingVerificationRequest?>
) : VerificationIntent()
data class OnReadyReceived(
val transactionId: String,
val fromUser: String,
val viaRoom: String?,
val readyInfo: ValidVerificationInfoReady,
) : VerificationIntent()
data class OnReadyByAnotherOfMySessionReceived(
val transactionId: String,
val fromUser: String,
val viaRoom: String?,
) : VerificationIntent()
data class GetExistingRequestInRoom(
val transactionId: String,
val roomId: String,
val deferred: CompletableDeferred<PendingVerificationRequest?>,
) : VerificationIntent()
data class GetExistingRequest(
val transactionId: String,
val otherUserId: String,
val deferred: CompletableDeferred<PendingVerificationRequest?>,
) : VerificationIntent()
data class GetExistingRequestsForUser(
val userId: String,
val deferred: CompletableDeferred<List<PendingVerificationRequest>>,
) : VerificationIntent()
data class GetExistingTransaction(
val transactionId: String,
val fromUser: String,
val deferred: CompletableDeferred<VerificationTransaction?>,
) : VerificationIntent()
data class ActionStartSasVerification(
val otherUserId: String,
val requestId: String,
val deferred: CompletableDeferred<VerificationTransaction>,
) : VerificationIntent()
data class ActionReciprocateQrVerification(
val otherUserId: String,
val requestId: String,
val scannedData: String,
val deferred: CompletableDeferred<VerificationTransaction?>,
) : VerificationIntent()
data class OnStartReceived(
val viaRoom: String?,
val fromUser: String,
val validVerificationInfoStart: ValidVerificationInfoStart,
) : VerificationIntent()
data class OnAcceptReceived(
val viaRoom: String?,
val fromUser: String,
val validAccept: ValidVerificationInfoAccept,
) : VerificationIntent()
data class OnKeyReceived(
val viaRoom: String?,
val fromUser: String,
val validKey: ValidVerificationInfoKey,
) : VerificationIntent()
data class OnMacReceived(
val viaRoom: String?,
val fromUser: String,
val validMac: ValidVerificationInfoMac,
) : VerificationIntent()
data class OnCancelReceived(
val viaRoom: String?,
val fromUser: String,
val validCancel: ValidVerificationInfoCancel,
) : VerificationIntent()
data class ActionSASCodeMatches(
val transactionId: String,
val deferred: CompletableDeferred<Unit>
) : VerificationIntent()
data class ActionSASCodeDoesNotMatch(
val transactionId: String,
val deferred: CompletableDeferred<Unit>
) : VerificationIntent()
data class ActionCancel(
val transactionId: String,
val deferred: CompletableDeferred<Unit>
) : VerificationIntent()
data class OnUnableToDecryptVerificationEvent(
val transactionId: String,
val roomId: String,
val fromUser: String,
) : VerificationIntent()
data class OnDoneReceived(
val viaRoom: String?,
val fromUser: String,
val transactionId: String,
) : VerificationIntent()
}

View File

@ -56,7 +56,7 @@ internal class VerificationMessageProcessor @Inject constructor(
return allowedTypes.contains(eventType) return allowedTypes.contains(eventType)
} }
suspend fun process(event: Event) { suspend fun process(roomId:String, event: Event) {
Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.getClearType()} from ${event.senderId}") Timber.v("## SAS Verification live observer: received msgId: ${event.eventId} msgtype: ${event.getClearType()} from ${event.senderId}")
// If the request is in the future by more than 5 minutes or more than 10 minutes in the past, // If the request is in the future by more than 5 minutes or more than 10 minutes in the past,
@ -74,45 +74,49 @@ internal class VerificationMessageProcessor @Inject constructor(
if (event.senderId == userId) { if (event.senderId == userId) {
// If it's send from me, we need to keep track of Requests or Start // If it's send from me, we need to keep track of Requests or Start
// done from another device of mine // done from another device of mine
if (EventType.MESSAGE == event.getClearType()) { // if (EventType.MESSAGE == event.getClearType()) {
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType // val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) { // if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let { // event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
// if (it.fromDevice != deviceId) {
// // The verification is requested from another device
// Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ")
// event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
// }
// }
// }
// } else if (EventType.KEY_VERIFICATION_START == event.getClearType()) {
// event.getClearContent().toModel<MessageVerificationStartContent>()?.let {
// if (it.fromDevice != deviceId) {
// // The verification is started from another device
// Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
// relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
// verificationService.onRoomRequestHandledByOtherDevice(event)
// }
// }
// } else
// we only care about room ready sent by me
if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
event.getClearContent().toModel<MessageVerificationReadyContent>()?.let {
if (it.fromDevice != deviceId) { if (it.fromDevice != deviceId) {
// The verification is requested from another device // The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction requested from other device tid:${event.eventId} ") Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
event.eventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
verificationService.onRoomReadyFromOneOfMyOtherDevice(event)
} }
} }
} else {
Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}")
} }
} else if (EventType.KEY_VERIFICATION_START == event.getClearType()) { // } else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
event.getClearContent().toModel<MessageVerificationStartContent>()?.let { // relatesToEventId?.let {
if (it.fromDevice != deviceId) { // transactionsHandledByOtherDevice.remove(it)
// The verification is started from another device // verificationService.onRoomRequestHandledByOtherDevice(event)
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ") // }
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) } // } else if (EventType.ENCRYPTED == event.getClearType()) {
verificationService.onRoomRequestHandledByOtherDevice(event) // verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
} // }
}
} else if (EventType.KEY_VERIFICATION_READY == event.getClearType()) {
event.getClearContent().toModel<MessageVerificationReadyContent>()?.let {
if (it.fromDevice != deviceId) {
// The verification is started from another device
Timber.v("## SAS Verification live observer: Transaction started by other device tid:$relatesToEventId ")
relatesToEventId?.let { txId -> transactionsHandledByOtherDevice.add(txId) }
verificationService.onRoomRequestHandledByOtherDevice(event)
}
}
} else if (EventType.KEY_VERIFICATION_CANCEL == event.getClearType() || EventType.KEY_VERIFICATION_DONE == event.getClearType()) {
relatesToEventId?.let {
transactionsHandledByOtherDevice.remove(it)
verificationService.onRoomRequestHandledByOtherDevice(event)
}
} else if (EventType.ENCRYPTED == event.getClearType()) {
verificationService.onPotentiallyInterestingEventRoomFailToDecrypt(event)
}
Timber.v("## SAS Verification ignoring message sent by me: ${event.eventId} type: ${event.getClearType()}")
return return
} }
@ -129,11 +133,11 @@ internal class VerificationMessageProcessor @Inject constructor(
EventType.KEY_VERIFICATION_CANCEL, EventType.KEY_VERIFICATION_CANCEL,
EventType.KEY_VERIFICATION_READY, EventType.KEY_VERIFICATION_READY,
EventType.KEY_VERIFICATION_DONE -> { EventType.KEY_VERIFICATION_DONE -> {
verificationService.onRoomEvent(event) verificationService.onRoomEvent(roomId, event)
} }
EventType.MESSAGE -> { EventType.MESSAGE -> {
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) { if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
verificationService.onRoomRequestReceived(event) verificationService.onRoomRequestReceived(roomId, event)
} }
} }
} }

View File

@ -1,128 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
/**
* Verification can be performed using toDevice events or via DM.
* This class abstracts the concept of transport for verification
*/
internal interface VerificationTransport {
/**
* Sends a message.
*/
fun <T> sendToOther(
type: String,
verificationInfo: VerificationInfo<T>,
nextState: VerificationTxState,
onErrorReason: CancelCode,
onDone: (() -> Unit)?
)
/**
* @param supportedMethods list of supported method by this client
* @param localId a local Id
* @param otherUserId the user id to send the verification request to
* @param roomId a room Id to use to send verification message
* @param toDevices list of device Ids
* @param callback will be called with eventId and ValidVerificationInfoRequest in case of success
*/
fun sendVerificationRequest(
supportedMethods: List<String>,
localId: String,
otherUserId: String,
roomId: String?,
toDevices: List<String>?,
callback: (String?, ValidVerificationInfoRequest?) -> Unit
)
fun cancelTransaction(
transactionId: String,
otherUserId: String,
otherUserDeviceId: String?,
code: CancelCode
)
fun cancelTransaction(
transactionId: String,
otherUserId: String,
otherUserDeviceIds: List<String>,
code: CancelCode
)
fun done(
transactionId: String,
onDone: (() -> Unit)?
)
/**
* Creates an accept message suitable for this transport.
*/
fun createAccept(
tid: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>
): VerificationInfoAccept
fun createKey(
tid: String,
pubKey: String
): VerificationInfoKey
/**
* Create start for SAS verification.
*/
fun createStartForSas(
fromDevice: String,
transactionId: String,
keyAgreementProtocols: List<String>,
hashes: List<String>,
messageAuthenticationCodes: List<String>,
shortAuthenticationStrings: List<String>
): VerificationInfoStart
/**
* Create start for QR code verification.
*/
fun createStartForQrCode(
fromDevice: String,
transactionId: String,
sharedSecret: String
): VerificationInfoStart
fun createMac(tid: String, mac: Map<String, String>, keys: String): VerificationInfoMac
fun createReady(
tid: String,
fromDevice: String,
methods: List<String>
): VerificationInfoReady
// TODO Refactor
fun sendVerificationReady(
keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String?,
callback: (() -> Unit)?
)
}

View File

@ -1,302 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.launch
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.events.model.LocalEcho
import org.matrix.android.sdk.api.session.events.model.RelationType
import org.matrix.android.sdk.api.session.events.model.UnsignedData
import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationAcceptContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationCancelContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationDoneContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationKeyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationMacContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationReadyContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationStartContent
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.task.SemaphoreCoroutineSequencer
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
import java.util.concurrent.Executors
internal class VerificationTransportRoomMessage(
private val sendVerificationMessageTask: SendVerificationMessageTask,
private val userId: String,
private val userDeviceId: String?,
private val roomId: String,
private val localEchoEventFactory: LocalEchoEventFactory,
private val tx: DefaultVerificationTransaction?,
cryptoCoroutineScope: CoroutineScope,
private val clock: Clock,
) : VerificationTransport {
private val dispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher()
private val verificationSenderScope = CoroutineScope(cryptoCoroutineScope.coroutineContext + dispatcher)
private val sequencer = SemaphoreCoroutineSequencer()
override fun <T> sendToOther(
type: String,
verificationInfo: VerificationInfo<T>,
nextState: VerificationTxState,
onErrorReason: CancelCode,
onDone: (() -> Unit)?
) {
Timber.d("## SAS sending msg type $type")
Timber.v("## SAS sending msg info $verificationInfo")
val event = createEventAndLocalEcho(
type = type,
roomId = roomId,
content = verificationInfo.toEventContent()!!
)
verificationSenderScope.launch {
sequencer.post {
try {
val params = SendVerificationMessageTask.Params(event)
sendVerificationMessageTask.executeRetry(params, 5)
// Do I need to update local echo state to sent?
if (onDone != null) {
onDone()
} else {
tx?.state = nextState
}
} catch (failure: Throwable) {
tx?.cancel(onErrorReason)
}
}
}
}
override fun sendVerificationRequest(
supportedMethods: List<String>,
localId: String,
otherUserId: String,
roomId: String?,
toDevices: List<String>?,
callback: (String?, ValidVerificationInfoRequest?) -> Unit
) {
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
// This transport requires a room
requireNotNull(roomId)
val validInfo = ValidVerificationInfoRequest(
transactionId = "",
fromDevice = userDeviceId ?: "",
methods = supportedMethods,
timestamp = clock.epochMillis()
)
val info = MessageVerificationRequestContent(
body = "$userId is requesting to verify your key, but your client does not support in-chat key verification." +
" You will need to use legacy key verification to verify keys.",
fromDevice = validInfo.fromDevice,
toUserId = otherUserId,
timestamp = validInfo.timestamp,
methods = validInfo.methods
)
val content = info.toContent()
val event = createEventAndLocalEcho(
localId = localId,
type = EventType.MESSAGE,
roomId = roomId,
content = content
)
verificationSenderScope.launch {
val params = SendVerificationMessageTask.Params(event)
sequencer.post {
try {
val eventId = sendVerificationMessageTask.executeRetry(params, 5)
// Do I need to update local echo state to sent?
callback(eventId, validInfo)
} catch (failure: Throwable) {
callback(null, null)
}
}
}
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val event = createEventAndLocalEcho(
type = EventType.KEY_VERIFICATION_CANCEL,
roomId = roomId,
content = MessageVerificationCancelContent.create(transactionId, code).toContent()
)
verificationSenderScope.launch {
sequencer.post {
try {
val params = SendVerificationMessageTask.Params(event)
sendVerificationMessageTask.executeRetry(params, 5)
} catch (failure: Throwable) {
Timber.w(failure, "Failed to cancel verification transaction")
}
}
}
}
override fun cancelTransaction(
transactionId: String,
otherUserId: String,
otherUserDeviceIds: List<String>,
code: CancelCode
) = cancelTransaction(transactionId, otherUserId, null, code)
override fun done(
transactionId: String,
onDone: (() -> Unit)?
) {
Timber.d("## SAS sending done for $transactionId")
val event = createEventAndLocalEcho(
type = EventType.KEY_VERIFICATION_DONE,
roomId = roomId,
content = MessageVerificationDoneContent(
relatesTo = RelationDefaultContent(
RelationType.REFERENCE,
transactionId
)
).toContent()
)
verificationSenderScope.launch {
sequencer.post {
try {
val params = SendVerificationMessageTask.Params(event)
sendVerificationMessageTask.executeRetry(params, 5)
} catch (failure: Throwable) {
Timber.w(failure, "Failed to complete (done) verification")
// should we call onDone?
} finally {
onDone?.invoke()
}
}
}
}
override fun createAccept(
tid: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>
): VerificationInfoAccept =
MessageVerificationAcceptContent.create(
tid,
keyAgreementProtocol,
hash,
commitment,
messageAuthenticationCode,
shortAuthenticationStrings
)
override fun createKey(tid: String, pubKey: String): VerificationInfoKey = MessageVerificationKeyContent.create(tid, pubKey)
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = MessageVerificationMacContent.create(tid, mac, keys)
override fun createStartForSas(
fromDevice: String,
transactionId: String,
keyAgreementProtocols: List<String>,
hashes: List<String>,
messageAuthenticationCodes: List<String>,
shortAuthenticationStrings: List<String>
): VerificationInfoStart {
return MessageVerificationStartContent(
fromDevice,
hashes,
keyAgreementProtocols,
messageAuthenticationCodes,
shortAuthenticationStrings,
VERIFICATION_METHOD_SAS,
RelationDefaultContent(
type = RelationType.REFERENCE,
eventId = transactionId
),
null
)
}
override fun createStartForQrCode(
fromDevice: String,
transactionId: String,
sharedSecret: String
): VerificationInfoStart {
return MessageVerificationStartContent(
fromDevice,
null,
null,
null,
null,
VERIFICATION_METHOD_RECIPROCATE,
RelationDefaultContent(
type = RelationType.REFERENCE,
eventId = transactionId
),
sharedSecret
)
}
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
return MessageVerificationReadyContent(
fromDevice = fromDevice,
relatesTo = RelationDefaultContent(
type = RelationType.REFERENCE,
eventId = tid
),
methods = methods
)
}
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
return Event(
roomId = roomId,
originServerTs = clock.epochMillis(),
senderId = userId,
eventId = localId,
type = type,
content = content,
unsignedData = UnsignedData(age = null, transactionId = localId)
).also {
localEchoEventFactory.createLocalEcho(it)
}
}
override fun sendVerificationReady(
keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String?,
callback: (() -> Unit)?
) {
// Not applicable (send event is called directly)
Timber.w("## SAS ignored verification ready with methods: ${keyReq.methods}")
}
}

View File

@ -1,50 +0,0 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import kotlinx.coroutines.CoroutineScope
import org.matrix.android.sdk.internal.crypto.tasks.SendVerificationMessageTask
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
import org.matrix.android.sdk.internal.util.time.Clock
import javax.inject.Inject
internal class VerificationTransportRoomMessageFactory @Inject constructor(
private val sendVerificationMessageTask: SendVerificationMessageTask,
@UserId
private val userId: String,
@DeviceId
private val deviceId: String?,
private val localEchoEventFactory: LocalEchoEventFactory,
private val cryptoCoroutineScope: CoroutineScope,
private val clock: Clock,
) {
fun createTransport(roomId: String, tx: DefaultVerificationTransaction?): VerificationTransportRoomMessage {
return VerificationTransportRoomMessage(
sendVerificationMessageTask = sendVerificationMessageTask,
userId = userId,
userDeviceId = deviceId,
roomId = roomId,
localEchoEventFactory = localEchoEventFactory,
tx = tx,
cryptoCoroutineScope = cryptoCoroutineScope,
clock = clock,
)
}
}

View File

@ -1,287 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.ValidVerificationInfoRequest
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.model.message.MessageType
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationAccept
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationCancel
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationDone
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationKey
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationMac
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationReady
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationRequest
import org.matrix.android.sdk.internal.crypto.model.rest.KeyVerificationStart
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.configureWith
import org.matrix.android.sdk.internal.util.time.Clock
import timber.log.Timber
// TODO var could be val
internal class VerificationTransportToDevice(
private var tx: DefaultVerificationTransaction?,
private var sendToDeviceTask: SendToDeviceTask,
private val myDeviceId: String?,
private var taskExecutor: TaskExecutor,
private val clock: Clock,
) : VerificationTransport {
override fun sendVerificationRequest(
supportedMethods: List<String>,
localId: String,
otherUserId: String,
roomId: String?,
toDevices: List<String>?,
callback: (String?, ValidVerificationInfoRequest?) -> Unit
) {
Timber.d("## SAS sending verification request with supported methods: $supportedMethods")
val contentMap = MXUsersDevicesMap<Any>()
val validKeyReq = ValidVerificationInfoRequest(
transactionId = localId,
fromDevice = myDeviceId ?: "",
methods = supportedMethods,
timestamp = clock.epochMillis()
)
val keyReq = KeyVerificationRequest(
fromDevice = validKeyReq.fromDevice,
methods = validKeyReq.methods,
timestamp = validKeyReq.timestamp,
transactionId = validKeyReq.transactionId
)
toDevices?.forEach {
contentMap.setObject(otherUserId, it, keyReq)
}
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(MessageType.MSGTYPE_VERIFICATION_REQUEST, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## verification [${tx?.transactionId}] send toDevice request success")
callback.invoke(localId, validKeyReq)
}
override fun onFailure(failure: Throwable) {
Timber.e("## verification [${tx?.transactionId}] failed to send toDevice request")
}
}
}
.executeBy(taskExecutor)
}
override fun sendVerificationReady(
keyReq: VerificationInfoReady,
otherUserId: String,
otherDeviceId: String?,
callback: (() -> Unit)?
) {
Timber.d("## SAS sending verification ready with methods: ${keyReq.methods}")
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherDeviceId, keyReq)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_READY, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## verification [${tx?.transactionId}] send toDevice request success")
callback?.invoke()
}
override fun onFailure(failure: Throwable) {
Timber.e("## verification [${tx?.transactionId}] failed to send toDevice request")
}
}
}
.executeBy(taskExecutor)
}
override fun <T> sendToOther(
type: String,
verificationInfo: VerificationInfo<T>,
nextState: VerificationTxState,
onErrorReason: CancelCode,
onDone: (() -> Unit)?
) {
Timber.d("## SAS sending msg type $type")
Timber.v("## SAS sending msg info $verificationInfo")
val stateBeforeCall = tx?.state
val tx = tx ?: return
val contentMap = MXUsersDevicesMap<Any>()
val toSendToDeviceObject = verificationInfo.toSendToDeviceObject()
?: return Unit.also { tx.cancel() }
contentMap.setObject(tx.otherUserId, tx.otherDeviceId, toSendToDeviceObject)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(type, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [${tx.transactionId}] toDevice type '$type' success.")
if (onDone != null) {
onDone()
} else {
// we may have received next state (e.g received accept in sending_start)
// We only put next state if the state was what is was before we started
if (tx.state == stateBeforeCall) {
tx.state = nextState
}
}
}
override fun onFailure(failure: Throwable) {
Timber.e("## SAS verification [${tx.transactionId}] failed to send toDevice in state : ${tx.state}")
tx.cancel(onErrorReason)
}
}
}
.executeBy(taskExecutor)
}
override fun done(transactionId: String, onDone: (() -> Unit)?) {
val otherUserId = tx?.otherUserId ?: return
val otherUserDeviceId = tx?.otherDeviceId ?: return
val cancelMessage = KeyVerificationDone(transactionId)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_DONE, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
onDone?.invoke()
Timber.v("## SAS verification [$transactionId] done")
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## SAS verification [$transactionId] failed to done.")
}
}
}
.executeBy(taskExecutor)
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceId: String?, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>()
contentMap.setObject(otherUserId, otherUserDeviceId, cancelMessage)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
}
}
}
.executeBy(taskExecutor)
}
override fun cancelTransaction(transactionId: String, otherUserId: String, otherUserDeviceIds: List<String>, code: CancelCode) {
Timber.d("## SAS canceling transaction $transactionId for reason $code")
val cancelMessage = KeyVerificationCancel.create(transactionId, code)
val contentMap = MXUsersDevicesMap<Any>()
val messages = otherUserDeviceIds.associateWith { cancelMessage }
contentMap.setObjects(otherUserId, messages)
sendToDeviceTask
.configureWith(SendToDeviceTask.Params(EventType.KEY_VERIFICATION_CANCEL, contentMap)) {
this.callback = object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
Timber.v("## SAS verification [$transactionId] canceled for reason ${code.value}")
}
override fun onFailure(failure: Throwable) {
Timber.e(failure, "## SAS verification [$transactionId] failed to cancel.")
}
}
}
.executeBy(taskExecutor)
}
override fun createAccept(
tid: String,
keyAgreementProtocol: String,
hash: String,
commitment: String,
messageAuthenticationCode: String,
shortAuthenticationStrings: List<String>
): VerificationInfoAccept = KeyVerificationAccept.create(
tid,
keyAgreementProtocol,
hash,
commitment,
messageAuthenticationCode,
shortAuthenticationStrings
)
override fun createKey(tid: String, pubKey: String): VerificationInfoKey = KeyVerificationKey.create(tid, pubKey)
override fun createMac(tid: String, mac: Map<String, String>, keys: String) = KeyVerificationMac.create(tid, mac, keys)
override fun createStartForSas(
fromDevice: String,
transactionId: String,
keyAgreementProtocols: List<String>,
hashes: List<String>,
messageAuthenticationCodes: List<String>,
shortAuthenticationStrings: List<String>
): VerificationInfoStart {
return KeyVerificationStart(
fromDevice,
VERIFICATION_METHOD_SAS,
transactionId,
keyAgreementProtocols,
hashes,
messageAuthenticationCodes,
shortAuthenticationStrings,
null
)
}
override fun createStartForQrCode(
fromDevice: String,
transactionId: String,
sharedSecret: String
): VerificationInfoStart {
return KeyVerificationStart(
fromDevice,
VERIFICATION_METHOD_RECIPROCATE,
transactionId,
null,
null,
null,
null,
sharedSecret
)
}
override fun createReady(tid: String, fromDevice: String, methods: List<String>): VerificationInfoReady {
return KeyVerificationReady(
transactionId = tid,
fromDevice = fromDevice,
methods = methods
)
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright (c) 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification
import org.matrix.android.sdk.internal.crypto.tasks.SendToDeviceTask
import org.matrix.android.sdk.internal.di.DeviceId
import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.util.time.Clock
import javax.inject.Inject
internal class VerificationTransportToDeviceFactory @Inject constructor(
private val sendToDeviceTask: SendToDeviceTask,
@DeviceId val myDeviceId: String?,
private val taskExecutor: TaskExecutor,
private val clock: Clock,
) {
fun createTransport(tx: DefaultVerificationTransaction?): VerificationTransportToDevice {
return VerificationTransportToDevice(tx, sendToDeviceTask, myDeviceId, taskExecutor, clock)
}
}

View File

@ -1,284 +0,0 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.crypto.verification.qrcode
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
import org.matrix.android.sdk.api.session.crypto.verification.CancelCode
import org.matrix.android.sdk.api.session.crypto.verification.QrCodeVerificationTransaction
import org.matrix.android.sdk.api.session.crypto.verification.VerificationTxState
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.util.fromBase64
import org.matrix.android.sdk.internal.crypto.OutgoingKeyRequestManager
import org.matrix.android.sdk.internal.crypto.SecretShareManager
import org.matrix.android.sdk.internal.crypto.actions.SetDeviceVerificationAction
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64Safe
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
import org.matrix.android.sdk.internal.crypto.verification.DefaultVerificationTransaction
import org.matrix.android.sdk.internal.crypto.verification.ValidVerificationInfoStart
import timber.log.Timber
internal class DefaultQrCodeVerificationTransaction(
setDeviceVerificationAction: SetDeviceVerificationAction,
override val transactionId: String,
override val otherUserId: String,
override var otherDeviceId: String?,
private val crossSigningService: CrossSigningService,
outgoingKeyRequestManager: OutgoingKeyRequestManager,
secretShareManager: SecretShareManager,
private val cryptoStore: IMXCryptoStore,
// Not null only if other user is able to scan QR code
private val qrCodeData: QrCodeData?,
val userId: String,
val deviceId: String,
override val isIncoming: Boolean
) : DefaultVerificationTransaction(
setDeviceVerificationAction,
crossSigningService,
outgoingKeyRequestManager,
secretShareManager,
userId,
transactionId,
otherUserId,
otherDeviceId,
isIncoming
),
QrCodeVerificationTransaction {
override val qrCodeText: String?
get() = qrCodeData?.toEncodedString()
override var state: VerificationTxState = VerificationTxState.None
set(newState) {
field = newState
listeners.forEach {
try {
it.transactionUpdated(this)
} catch (e: Throwable) {
Timber.e(e, "## Error while notifying listeners")
}
}
}
override fun userHasScannedOtherQrCode(otherQrCodeText: String) {
val otherQrCodeData = otherQrCodeText.toQrCodeData() ?: run {
Timber.d("## Verification QR: Invalid QR Code Data")
cancel(CancelCode.QrCodeInvalid)
return
}
// Perform some checks
if (otherQrCodeData.transactionId != transactionId) {
Timber.d("## Verification QR: Invalid transaction actual ${otherQrCodeData.transactionId} expected:$transactionId")
cancel(CancelCode.UnknownTransaction)
return
}
// check master key
val myMasterKey = crossSigningService.getUserCrossSigningKeys(userId)?.masterKey()?.unpaddedBase64PublicKey
var canTrustOtherUserMasterKey = false
// Check the other device view of my MSK
when (otherQrCodeData) {
is QrCodeData.VerifyingAnotherUser -> {
// key2 (aka otherUserMasterCrossSigningPublicKey) is what the one displaying the QR code (other user) think my MSK is.
// Let's check that it's correct
// If not -> Cancel
if (otherQrCodeData.otherUserMasterCrossSigningPublicKey != myMasterKey) {
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.otherUserMasterCrossSigningPublicKey}")
cancel(CancelCode.MismatchedKeys)
return
} else Unit
}
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
// key1 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
// Let's check that I see the same MSK
// If not -> Cancel
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
cancel(CancelCode.MismatchedKeys)
return
} else {
// I can trust the MSK then (i see the same one, and other session tell me it's trusted by him)
canTrustOtherUserMasterKey = true
}
}
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
// key2 (aka userMasterCrossSigningPublicKey) is the session displaying the QR code view of our MSK.
// Let's check that it's the good one
// If not -> Cancel
if (otherQrCodeData.userMasterCrossSigningPublicKey != myMasterKey) {
Timber.d("## Verification QR: Invalid other master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
cancel(CancelCode.MismatchedKeys)
return
} else {
// Nothing special here, we will send a reciprocate start event, and then the other session will trust it's view of the MSK
}
}
}
val toVerifyDeviceIds = mutableListOf<String>()
// Let's now check the other user/device key material
when (otherQrCodeData) {
is QrCodeData.VerifyingAnotherUser -> {
// key1(aka userMasterCrossSigningPublicKey) is the MSK of the one displaying the QR code (i.e other user)
// Let's check that it matches what I think it should be
if (otherQrCodeData.userMasterCrossSigningPublicKey
!= crossSigningService.getUserCrossSigningKeys(otherUserId)?.masterKey()?.unpaddedBase64PublicKey) {
Timber.d("## Verification QR: Invalid user master key ${otherQrCodeData.userMasterCrossSigningPublicKey}")
cancel(CancelCode.MismatchedKeys)
return
} else {
// It does so i should mark it as trusted
canTrustOtherUserMasterKey = true
Unit
}
}
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
// key2 (aka otherDeviceKey) is my current device key in POV of the one displaying the QR code (i.e other device)
// Let's check that it's correct
if (otherQrCodeData.otherDeviceKey
!= cryptoStore.getUserDevice(userId, deviceId)?.fingerprint()) {
Timber.d("## Verification QR: Invalid other device key ${otherQrCodeData.otherDeviceKey}")
cancel(CancelCode.MismatchedKeys)
return
} else Unit // Nothing special here, we will send a reciprocate start event, and then the other session will trust my device
// and thus allow me to request SSSS secret
}
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
// key1 (aka otherDeviceKey) is the device key of the one displaying the QR code (i.e other device)
// Let's check that it matches what I have locally
if (otherQrCodeData.deviceKey
!= cryptoStore.getUserDevice(otherUserId, otherDeviceId ?: "")?.fingerprint()) {
Timber.d("## Verification QR: Invalid device key ${otherQrCodeData.deviceKey}")
cancel(CancelCode.MismatchedKeys)
return
} else {
// Yes it does -> i should trust it and sign then upload the signature
toVerifyDeviceIds.add(otherDeviceId ?: "")
Unit
}
}
}
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
// Nothing to verify
cancel(CancelCode.MismatchedKeys)
return
}
// All checks are correct
// Send the shared secret so that sender can trust me
// qrCodeData.sharedSecret will be used to send the start request
start(otherQrCodeData.sharedSecret)
trust(
canTrustOtherUserMasterKey = canTrustOtherUserMasterKey,
toVerifyDeviceIds = toVerifyDeviceIds.distinct(),
eventuallyMarkMyMasterKeyAsTrusted = true,
autoDone = false
)
}
private fun start(remoteSecret: String, onDone: (() -> Unit)? = null) {
if (state != VerificationTxState.None) {
Timber.e("## Verification QR: start verification from invalid state")
// should I cancel??
throw IllegalStateException("Interactive Key verification already started")
}
state = VerificationTxState.Started
val startMessage = transport.createStartForQrCode(
deviceId,
transactionId,
remoteSecret
)
transport.sendToOther(
EventType.KEY_VERIFICATION_START,
startMessage,
VerificationTxState.WaitingOtherReciprocateConfirm,
CancelCode.User,
onDone
)
}
override fun cancel() {
cancel(CancelCode.User)
}
override fun cancel(code: CancelCode) {
state = VerificationTxState.Cancelled(code, true)
transport.cancelTransaction(transactionId, otherUserId, otherDeviceId ?: "", code)
}
override fun isToDeviceTransport() = false
// Other user has scanned our QR code. check that the secret matched, so we can trust him
fun onStartReceived(startReq: ValidVerificationInfoStart.ReciprocateVerificationInfoStart) {
if (qrCodeData == null) {
// Should not happen
cancel(CancelCode.UnexpectedMessage)
return
}
if (startReq.sharedSecret.fromBase64Safe()?.contentEquals(qrCodeData.sharedSecret.fromBase64()) == true) {
// Ok, we can trust the other user
// We can only trust the master key in this case
// But first, ask the user for a confirmation
state = VerificationTxState.QrScannedByOther
} else {
// Display a warning
cancel(CancelCode.MismatchedKeys)
}
}
fun onDoneReceived() {
if (state != VerificationTxState.WaitingOtherReciprocateConfirm) {
cancel(CancelCode.UnexpectedMessage)
return
}
state = VerificationTxState.Verified
transport.done(transactionId) {}
}
override fun otherUserScannedMyQrCode() {
when (qrCodeData) {
is QrCodeData.VerifyingAnotherUser -> {
// Alice telling Bob that the code was scanned successfully is sufficient for Bob to trust Alice's key,
trust(true, emptyList(), false)
}
is QrCodeData.SelfVerifyingMasterKeyTrusted -> {
// I now know that I have the correct device key for other session,
// and can sign it with the self-signing key and upload the signature
trust(false, listOf(otherDeviceId ?: ""), false)
}
is QrCodeData.SelfVerifyingMasterKeyNotTrusted -> {
// I now know that i can trust my MSK
trust(true, emptyList(), true)
}
null -> Unit
}
}
override fun otherUserDidNotScannedMyQrCode() {
// What can I do then?
// At least remove the transaction...
cancel(CancelCode.MismatchedKeys)
}
}

View File

@ -113,6 +113,8 @@ internal interface SessionComponent {
fun networkConnectivityChecker(): NetworkConnectivityChecker fun networkConnectivityChecker(): NetworkConnectivityChecker
// fun olmMachine(): OlmMachine
fun taskExecutor(): TaskExecutor fun taskExecutor(): TaskExecutor
fun inject(worker: SendEventWorker) fun inject(worker: SendEventWorker)

View File

@ -49,7 +49,7 @@ data class Credentials(
/** /**
* ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified. * ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified.
*/ */
@Json(name = "device_id") val deviceId: String?, @Json(name = "device_id") val deviceId: String,
/** /**
* Optional client configuration provided by the server. If present, clients SHOULD use the provided object to * Optional client configuration provided by the server. If present, clients SHOULD use the provided object to
* reconfigure themselves, optionally validating the URLs within. * reconfigure themselves, optionally validating the URLs within.
@ -59,5 +59,5 @@ data class Credentials(
) )
internal fun Credentials.sessionId(): String { internal fun Credentials.sessionId(): String {
return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5() return (if (deviceId.isBlank()) userId else "$userId|$deviceId").md5()
} }

View File

@ -24,6 +24,7 @@ interface StepProgressListener {
sealed class Step { sealed class Step {
data class ComputingKey(val progress: Int, val total: Int) : Step() data class ComputingKey(val progress: Int, val total: Int) : Step()
object DownloadingKey : Step() object DownloadingKey : Step()
data class DecryptingKey(val progress: Int, val total: Int) : Step()
data class ImportingKey(val progress: Int, val total: Int) : Step() data class ImportingKey(val progress: Int, val total: Int) : Step()
} }

View File

@ -19,7 +19,6 @@ package org.matrix.android.sdk.api.session.crypto
import android.content.Context import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.paging.PagedList import androidx.paging.PagedList
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService import org.matrix.android.sdk.api.session.crypto.crosssigning.CrossSigningService
@ -29,10 +28,8 @@ import org.matrix.android.sdk.api.session.crypto.keyshare.GossipingRequestListen
import org.matrix.android.sdk.api.session.crypto.model.AuditTrail import org.matrix.android.sdk.api.session.crypto.model.AuditTrail
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo import org.matrix.android.sdk.api.session.crypto.model.DeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.DevicesListResponse
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest import org.matrix.android.sdk.api.session.crypto.model.IncomingRoomKeyRequest
import org.matrix.android.sdk.api.session.crypto.model.MXDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult import org.matrix.android.sdk.api.session.crypto.model.MXEncryptEventContentResult
import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult import org.matrix.android.sdk.api.session.crypto.model.MXEventDecryptionResult
import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap import org.matrix.android.sdk.api.session.crypto.model.MXUsersDevicesMap
@ -40,6 +37,10 @@ import org.matrix.android.sdk.api.session.crypto.verification.VerificationServic
import org.matrix.android.sdk.api.session.events.model.Content import org.matrix.android.sdk.api.session.events.model.Content
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent import org.matrix.android.sdk.api.session.events.model.content.RoomKeyWithHeldContent
import org.matrix.android.sdk.api.session.sync.model.DeviceListResponse
import org.matrix.android.sdk.api.session.sync.model.DeviceOneTimeKeysCountSyncResponse
import org.matrix.android.sdk.api.session.sync.model.SyncResponse
import org.matrix.android.sdk.api.session.sync.model.ToDeviceSyncResponse
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.model.SessionInfo import org.matrix.android.sdk.internal.crypto.model.SessionInfo
@ -51,9 +52,9 @@ interface CryptoService {
fun keysBackupService(): KeysBackupService fun keysBackupService(): KeysBackupService
fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) suspend fun setDeviceName(deviceId: String, deviceName: String)
fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor)
fun getCryptoVersion(context: Context, longFormat: Boolean): String fun getCryptoVersion(context: Context, longFormat: Boolean): String
@ -65,15 +66,9 @@ interface CryptoService {
fun setWarnOnUnknownDevices(warn: Boolean) fun setWarnOnUnknownDevices(warn: Boolean)
fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo>
fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> suspend fun getMyCryptoDevice(): CryptoDeviceInfo
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?)
fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo?
fun getMyDevice(): CryptoDeviceInfo
fun getGlobalBlacklistUnverifiedDevices(): Boolean fun getGlobalBlacklistUnverifiedDevices(): Boolean
@ -118,12 +113,12 @@ interface CryptoService {
fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean) fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean)
fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo? suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
// fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>> fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
@ -132,15 +127,13 @@ interface CryptoService {
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>> fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
fun requestRoomKeyForEvent(event: Event) suspend fun reRequestRoomKeyForEvent(event: Event)
fun reRequestRoomKeyForEvent(event: Event)
fun addRoomKeysRequestListener(listener: GossipingRequestListener) fun addRoomKeysRequestListener(listener: GossipingRequestListener)
fun removeRoomKeysRequestListener(listener: GossipingRequestListener) fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) suspend fun fetchDevicesList(): List<DeviceInfo>
fun getMyDevicesInfo(): List<DeviceInfo> fun getMyDevicesInfo(): List<DeviceInfo>
@ -148,30 +141,35 @@ interface CryptoService {
fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>>
fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo
suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int
fun isRoomEncrypted(roomId: String): Boolean fun isRoomEncrypted(roomId: String): Boolean
// TODO This could be removed from this interface // TODO This could be removed from this interface
fun encryptEventContent( suspend fun encryptEventContent(
eventContent: Content, eventContent: Content,
eventType: String, eventType: String,
roomId: String, roomId: String
callback: MatrixCallback<MXEncryptEventContentResult> ): MXEncryptEventContentResult
)
fun discardOutboundSession(roomId: String) fun discardOutboundSession(roomId: String)
@Throws(MXCryptoError::class) @Throws(MXCryptoError::class)
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
fun getEncryptionAlgorithm(roomId: String): String? fun getEncryptionAlgorithm(roomId: String): String?
fun shouldEncryptForInvitedMembers(roomId: String): Boolean fun shouldEncryptForInvitedMembers(roomId: String): Boolean
fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) suspend fun downloadKeysIfNeeded(userIds: List<String>, forceDownload: Boolean = false): MXUsersDevicesMap<CryptoDeviceInfo>
suspend fun getCryptoDeviceInfoList(userId: String): List<CryptoDeviceInfo>
// fun getLiveCryptoDeviceInfoList(userId: String): Flow<List<CryptoDeviceInfo>>
//
// fun getLiveCryptoDeviceInfoList(userIds: List<String>): Flow<List<CryptoDeviceInfo>>
fun addNewSessionListener(newSessionListener: NewSessionListener) fun addNewSessionListener(newSessionListener: NewSessionListener)
fun removeSessionListener(listener: NewSessionListener) fun removeSessionListener(listener: NewSessionListener)
@ -199,10 +197,30 @@ interface CryptoService {
* Perform any background tasks that can be done before a message is ready to * Perform any background tasks that can be done before a message is ready to
* send, in order to speed up sending of the message. * send, in order to speed up sending of the message.
*/ */
fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) suspend fun prepareToEncrypt(roomId: String)
/** /**
* Share all inbound sessions of the last chunk messages to the provided userId devices. * Share all inbound sessions of the last chunk messages to the provided userId devices.
*/ */
suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?) suspend fun sendSharedHistoryKeys(roomId: String, userId: String, sessionInfoSet: Set<SessionInfo>?)
/**
* When LL all room members might not be loaded when setting up encryption.
* This is called after room members have been loaded
* ... not sure if shoud be API
*/
fun onE2ERoomMemberLoadedFromServer(roomId: String)
suspend fun deviceWithIdentityKey(userId: String, senderKey: String, algorithm: String): CryptoDeviceInfo?
suspend fun setDeviceVerification(trustLevel: DeviceTrustLevel, userId: String, deviceId: String)
fun close()
fun start()
fun isStarted(): Boolean
suspend fun receiveSyncChanges(toDevice: ToDeviceSyncResponse?, deviceChanges: DeviceListResponse?, keyCounts: DeviceOneTimeKeysCountSyncResponse?)
fun onLiveEvent(roomId: String, event: Event, initialSync: Boolean)
fun onStateEvent(roomId: String, event: Event) {}
suspend fun onSyncCompleted(syncResponse: SyncResponse)
fun logDbUsageInfo()
suspend fun setRoomUnBlacklistUnverifiedDevices(roomId: String)
} }

View File

@ -23,8 +23,7 @@ interface NewSessionListener {
/** /**
* @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions * @param roomId the room id where the new Megolm session has been created for, may be null when importing from external sessions
* @param senderKey the sender key of the device which the Megolm session is shared with
* @param sessionId the session id of the Megolm session * @param sessionId the session id of the Megolm session
*/ */
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) fun onNewSession(roomId: String?, sessionId: String)
} }

View File

@ -17,76 +17,109 @@
package org.matrix.android.sdk.api.session.crypto.crosssigning package org.matrix.android.sdk.api.session.crypto.crosssigning
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor import org.matrix.android.sdk.api.auth.UserInteractiveAuthInterceptor
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.api.util.Optional
interface CrossSigningService { interface CrossSigningService {
/**
* Is our own device signed by our own cross signing identity
*/
suspend fun isCrossSigningVerified(): Boolean
fun isCrossSigningVerified(): Boolean // TODO this isn't used anywhere besides in tests?
// Is this the local trust concept that we have for devices?
fun isUserTrusted(otherUserId: String): Boolean suspend fun isUserTrusted(otherUserId: String): Boolean
/** /**
* Will not force a download of the key, but will verify signatures trust chain. * Will not force a download of the key, but will verify signatures trust chain.
* Checks that my trusted user key has signed the other user UserKey * Checks that my trusted user key has signed the other user UserKey
*/ */
fun checkUserTrust(otherUserId: String): UserTrustResult suspend fun checkUserTrust(otherUserId: String): UserTrustResult
/** /**
* Initialize cross signing for this user. * Initialize cross signing for this user.
* Users needs to enter credentials * Users needs to enter credentials
*/ */
fun initializeCrossSigning( suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
uiaInterceptor: UserInteractiveAuthInterceptor?,
callback: MatrixCallback<Unit>
)
fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null /**
* Does our own user have a valid cross signing identity uploaded.
*
* In other words has any of our devices uploaded public cross signing keys to the server.
*/
suspend fun isCrossSigningInitialized(): Boolean = getMyCrossSigningKeys() != null
fun checkTrustFromPrivateKeys( /**
masterKeyPrivateKey: String?, * Inject the private cross signing keys, likely from backup, into our store.
uskKeyPrivateKey: String?, *
sskPrivateKey: String? * This will check if the injected private cross signing keys match the public ones provided
): UserTrustResult * by the server and if they do so
*/
suspend fun checkTrustFromPrivateKeys(masterKeyPrivateKey: String?,
uskKeyPrivateKey: String?,
sskPrivateKey: String?): UserTrustResult
fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? /**
* Get the public cross signing keys for the given user
*
* @param otherUserId The ID of the user for which we would like to fetch the cross signing keys.
*/
suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo?
fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>>
fun getMyCrossSigningKeys(): MXCrossSigningInfo? /** Get our own public cross signing keys */
suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo?
fun getCrossSigningPrivateKeys(): PrivateKeysInfo? /** Get our own private cross signing keys */
suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo?
fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>>
/**
* Can we sign our other devices or other users?
*
* Returning true means that we have the private self-signing and user-signing keys at hand.
*/
fun canCrossSign(): Boolean fun canCrossSign(): Boolean
/** Do we have all our private cross signing keys in storage? */
fun allPrivateKeysKnown(): Boolean fun allPrivateKeysKnown(): Boolean
fun trustUser( /** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
otherUserId: String, suspend fun trustUser(otherUserId: String)
callback: MatrixCallback<Unit>
)
fun markMyMasterKeyAsTrusted() /** Mark our own master key as trusted */
suspend fun markMyMasterKeyAsTrusted()
/** /**
* Sign one of your devices and upload the signature. * Sign one of your devices and upload the signature.
*/ */
fun trustDevice( @Throws
deviceId: String, suspend fun trustDevice(deviceId: String)
callback: MatrixCallback<Unit>
)
fun checkDeviceTrust( suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel
otherUserId: String,
otherDeviceId: String, /**
locallyTrusted: Boolean? * Check if a device is trusted
): DeviceTrustResult *
* This will check that we have a valid trust chain from our own master key to a device, either
* using the self-signing key for our own devices or using the user-signing key and the master
* key of another user.
*/
suspend fun checkDeviceTrust(otherUserId: String,
otherDeviceId: String,
// TODO what is locallyTrusted used for?
locallyTrusted: Boolean?): DeviceTrustResult
// FIXME Those method do not have to be in the service // FIXME Those method do not have to be in the service
fun onSecretMSKGossip(mskPrivateKey: String) // TODO those three methods doesn't seem to be used anywhere?
fun onSecretSSKGossip(sskPrivateKey: String) suspend fun onSecretMSKGossip(mskPrivateKey: String)
fun onSecretUSKGossip(uskPrivateKey: String) suspend fun onSecretSSKGossip(sskPrivateKey: String)
suspend fun onSecretUSKGossip(uskPrivateKey: String)
suspend fun checkTrustAndAffectedRoomShields(userIds: List<String>)
fun checkSelfTrust(myCrossSigningInfo: MXCrossSigningInfo?, myDevices: List<CryptoDeviceInfo>?): UserTrustResult
fun checkOtherMSKTrusted(myCrossSigningInfo: MXCrossSigningInfo?, otherInfo: MXCrossSigningInfo?): UserTrustResult
} }

View File

@ -23,10 +23,11 @@ sealed class UserTrustResult {
// data class UnknownDevice(val deviceID: String) : UserTrustResult() // data class UnknownDevice(val deviceID: String) : UserTrustResult()
data class CrossSigningNotConfigured(val userID: String) : UserTrustResult() data class CrossSigningNotConfigured(val userID: String) : UserTrustResult()
data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult() data class Failure(val message: String) : UserTrustResult()
data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult() // data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult()
data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult() // data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult()
data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult() // data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult()
// data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult()
} }
fun UserTrustResult.isVerified() = this is UserTrustResult.Success fun UserTrustResult.isVerified() = this is UserTrustResult.Success

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2022 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 org.matrix.android.sdk.api.session.crypto.keysbackup
interface IBackupRecoveryKey {
fun toBase58(): String
fun toBase64(): String
fun decryptV1(ephemeralKey: String, mac: String, ciphertext: String): String
fun megolmV1PublicKey(): IMegolmV1PublicKey
}
interface IMegolmV1PublicKey {
val publicKey: String
val privateKeySalt: String?
val privateKeyIterations: Int?
val backupAlgorithm: String
}

View File

@ -16,87 +16,74 @@
package org.matrix.android.sdk.api.session.crypto.keysbackup package org.matrix.android.sdk.api.session.crypto.keysbackup
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.listeners.ProgressListener import org.matrix.android.sdk.api.listeners.ProgressListener
import org.matrix.android.sdk.api.listeners.StepProgressListener import org.matrix.android.sdk.api.listeners.StepProgressListener
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
interface KeysBackupService { interface KeysBackupService {
/** /**
* Retrieve the current version of the backup from the homeserver. * Retrieve the current version of the backup from the homeserver.
* *
* It can be different than keysBackupVersion. * It can be different than keysBackupVersion.
* @param callback Asynchronous callback
*/ */
fun getCurrentVersion(callback: MatrixCallback<KeysBackupLastVersionResult>) suspend fun getCurrentVersion(): KeysBackupLastVersionResult?
/** /**
* Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion]. * Create a new keys backup version and enable it, using the information return from [prepareKeysBackupVersion].
* *
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion]. * @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
* @param callback Asynchronous callback * @return KeysVersion
*/ */
fun createKeysBackupVersion( @Throws
keysBackupCreationInfo: MegolmBackupCreationInfo, suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion
callback: MatrixCallback<KeysVersion>
)
/** /**
* Facility method to get the total number of locally stored keys. * Facility method to get the total number of locally stored keys.
*/ */
fun getTotalNumbersOfKeys(): Int suspend fun getTotalNumbersOfKeys(): Int
/** /**
* Facility method to get the number of backed up keys. * Facility method to get the number of backed up keys.
*/ */
fun getTotalNumbersOfBackedUpKeys(): Int suspend fun getTotalNumbersOfBackedUpKeys(): Int
/** // /**
* Start to back up keys immediately. // * Start to back up keys immediately.
* // *
* @param progressListener the callback to follow the progress // * @param progressListener the callback to follow the progress
* @param callback the main callback // * @param callback the main callback
*/ // */
fun backupAllGroupSessions( // fun backupAllGroupSessions(progressListener: ProgressListener?,
progressListener: ProgressListener?, // callback: MatrixCallback<Unit>?)
callback: MatrixCallback<Unit>?
)
/** /**
* Check trust on a key backup version. * Check trust on a key backup version.
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param callback block called when the operations completes.
*/ */
fun getKeysBackupTrust( suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust
keysBackupVersion: KeysVersionResult,
callback: MatrixCallback<KeysBackupVersionTrust>
)
/** /**
* Return the current progress of the backup. * Return the current progress of the backup.
*/ */
fun getBackupProgress(progressListener: ProgressListener) suspend fun getBackupProgress(progressListener: ProgressListener)
/** /**
* Get information about a backup version defined on the homeserver. * Get information about a backup version defined on the homeserver.
* *
* It can be different than keysBackupVersion. * It can be different than keysBackupVersion.
* @param version the backup version * @param version the backup version
* @param callback
*/ */
fun getVersion( suspend fun getVersion(version: String): KeysVersionResult?
version: String,
callback: MatrixCallback<KeysVersionResult?>
)
/** /**
* This method fetches the last backup version on the server, then compare to the currently backup version use. * This method fetches the last backup version on the server, then compare to the currently backup version use.
* If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version. * If versions are not the same, the current backup is deleted (on server or locally), then the backup may be started again, using the last version.
* *
* @param callback true if backup is already using the last version, and false if it is not the case * @return true if backup is already using the last version, and false if it is not the case
*/ */
fun forceUsingLastVersion(callback: MatrixCallback<Boolean>) suspend fun forceUsingLastVersion(): Boolean
/** /**
* Check the server for an active key backup. * Check the server for an active key backup.
@ -104,7 +91,7 @@ interface KeysBackupService {
* If one is present and has a valid signature from one of the user's verified * If one is present and has a valid signature from one of the user's verified
* devices, start backing up to it. * devices, start backing up to it.
*/ */
fun checkAndStartKeysBackup() suspend fun checkAndStartKeysBackup()
fun addListener(listener: KeysBackupStateListener) fun addListener(listener: KeysBackupStateListener)
@ -122,29 +109,22 @@ interface KeysBackupService {
* @param progressListener a progress listener, as generating private key from password may take a while * @param progressListener a progress listener, as generating private key from password may take a while
* @param callback Asynchronous callback * @param callback Asynchronous callback
*/ */
fun prepareKeysBackupVersion( suspend fun prepareKeysBackupVersion(password: String?, progressListener: ProgressListener?): MegolmBackupCreationInfo
password: String?,
progressListener: ProgressListener?,
callback: MatrixCallback<MegolmBackupCreationInfo>
)
/** /**
* Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself. * Delete a keys backup version. It will delete all backed up keys on the server, and the backup itself.
* If we are backing up to this version. Backup will be stopped. * If we are backing up to this version. Backup will be stopped.
* *
* @param version the backup version to delete. * @param version the backup version to delete.
* @param callback Asynchronous callback
*/ */
fun deleteBackup( @Throws
version: String, suspend fun deleteBackup(version: String)
callback: MatrixCallback<Unit>?
)
/** /**
* Ask if the backup on the server contains keys that we may do not have locally. * Ask if the backup on the server contains keys that we may do not have locally.
* This should be called when entering in the state READY_TO_BACKUP * This should be called when entering in the state READY_TO_BACKUP
*/ */
fun canRestoreKeys(): Boolean suspend fun canRestoreKeys(): Boolean
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
@ -152,40 +132,31 @@ interface KeysBackupService {
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param trust the trust to set to the keys backup. * @param trust the trust to set to the keys backup.
* @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersion( @Throws
keysBackupVersion: KeysVersionResult, suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean)
trust: Boolean,
callback: MatrixCallback<Unit>
)
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param recoveryKey the recovery key to challenge with the key backup public key. * @param recoveryKey the recovery key to challenge with the key backup public key.
* @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersionWithRecoveryKey( suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: IBackupRecoveryKey)
keysBackupVersion: KeysVersionResult,
recoveryKey: String,
callback: MatrixCallback<Unit>
)
/** /**
* Set trust on a keys backup version. * Set trust on a keys backup version.
* *
* @param keysBackupVersion the backup version to check. * @param keysBackupVersion the backup version to check.
* @param password the pass phrase to challenge with the keyBackupVersion public key. * @param password the pass phrase to challenge with the keyBackupVersion public key.
* @param callback block called when the operations completes.
*/ */
fun trustKeysBackupVersionWithPassphrase( suspend fun trustKeysBackupVersionWithPassphrase(
keysBackupVersion: KeysVersionResult, keysBackupVersion: KeysVersionResult,
password: String, password: String
callback: MatrixCallback<Unit>
) )
suspend fun onSecretKeyGossip(secret: String)
/** /**
* Restore a backup with a recovery key from a given backup version stored on the homeserver. * Restore a backup with a recovery key from a given backup version stored on the homeserver.
* *
@ -196,14 +167,13 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener * @param stepProgressListener the step progress listener
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
fun restoreKeysWithRecoveryKey( suspend fun restoreKeysWithRecoveryKey(
keysVersionResult: KeysVersionResult, keysVersionResult: KeysVersionResult,
recoveryKey: String, recoveryKey: IBackupRecoveryKey,
roomId: String?, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?, stepProgressListener: StepProgressListener?
callback: MatrixCallback<ImportRoomKeysResult> ): ImportRoomKeysResult
)
/** /**
* Restore a backup with a password from a given backup version stored on the homeserver. * Restore a backup with a password from a given backup version stored on the homeserver.
@ -215,14 +185,13 @@ interface KeysBackupService {
* @param stepProgressListener the step progress listener * @param stepProgressListener the step progress listener
* @param callback Callback. It provides the number of found keys and the number of successfully imported keys. * @param callback Callback. It provides the number of found keys and the number of successfully imported keys.
*/ */
fun restoreKeyBackupWithPassword( suspend fun restoreKeyBackupWithPassword(
keysBackupVersion: KeysVersionResult, keysBackupVersion: KeysVersionResult,
password: String, password: String,
roomId: String?, roomId: String?,
sessionId: String?, sessionId: String?,
stepProgressListener: StepProgressListener?, stepProgressListener: StepProgressListener?
callback: MatrixCallback<ImportRoomKeysResult> ): ImportRoomKeysResult
)
val keysBackupVersion: KeysVersionResult? val keysBackupVersion: KeysVersionResult?
@ -234,10 +203,10 @@ interface KeysBackupService {
fun getState(): KeysBackupState fun getState(): KeysBackupState
// For gossiping // For gossiping
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?) fun saveBackupRecoveryKey(recoveryKey: IBackupRecoveryKey?, version: String?)
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo? suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>) suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: IBackupRecoveryKey): Boolean
fun computePrivateKey( fun computePrivateKey(
passphrase: String, passphrase: String,

View File

@ -31,7 +31,7 @@ data class MegolmBackupCreationInfo(
val authData: MegolmBackupAuthData, val authData: MegolmBackupAuthData,
/** /**
* The Base58 recovery key. * The recovery key.
*/ */
val recoveryKey: String val recoveryKey: IBackupRecoveryKey
) )

View File

@ -17,6 +17,6 @@
package org.matrix.android.sdk.api.session.crypto.keysbackup package org.matrix.android.sdk.api.session.crypto.keysbackup
data class SavedKeyBackupKeyInfo( data class SavedKeyBackupKeyInfo(
val recoveryKey: String, val recoveryKey: IBackupRecoveryKey,
val version: String val version: String
) )

View File

@ -16,7 +16,21 @@
package org.matrix.android.sdk.api.session.crypto.model package org.matrix.android.sdk.api.session.crypto.model
import uniffi.olm.KeysImportResult
data class ImportRoomKeysResult( data class ImportRoomKeysResult(
val totalNumberOfKeys: Int, val totalNumberOfKeys: Int,
val successfullyNumberOfImportedKeys: Int val successfullyNumberOfImportedKeys: Int,
) /**It's a map from room id to a map of the sender key to a list of session*/
val importedSessionInfo: Map<String, Map<String, List<String>>>
) {
companion object {
fun fromOlm(result: KeysImportResult): ImportRoomKeysResult {
return ImportRoomKeysResult(
result.total.toInt(),
result.imported.toInt(),
result.keys
)
}
}
}

View File

@ -104,6 +104,10 @@ class MXUsersDevicesMap<E> {
map.clear() map.clear()
} }
fun join(other: Map<out String, HashMap<String, E>>) {
map.putAll(other)
}
/** /**
* Add entries from another MXUsersDevicesMap. * Add entries from another MXUsersDevicesMap.
* *

View File

@ -0,0 +1,62 @@
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.crypto.verification
import org.matrix.android.sdk.internal.crypto.verification.KotlinVerificationRequest
enum class EVerificationState {
// outgoing started request
WaitingForReady,
// for incoming
Requested,
// both incoming/outgoing
Ready,
Started,
WeStarted,
WaitingForDone,
Done,
Cancelled,
HandledByOtherSession
}
// TODO remove that
interface IVerificationRequest{
fun requestId(): String
fun incoming(): Boolean
fun otherUserId(): String
fun roomId(): String?
// target devices in case of to_device self verification
fun targetDevices() : List<String>?
fun state(): EVerificationState
fun ageLocalTs(): Long
fun isSasSupported(): Boolean
fun otherCanShowQrCode(): Boolean
fun otherCanScanQrCode(): Boolean
fun otherDeviceId(): String?
fun qrCodeText() : String?
fun isFinished() : Boolean = state() == EVerificationState.Cancelled || state() == EVerificationState.Done
fun cancelCode(): CancelCode?
}

View File

@ -21,60 +21,36 @@ import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_QR_
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_SAS
import java.util.UUID import java.util.UUID
/** /**
* Stores current pending verification requests. * Stores current pending verification requests.
*/ */
data class PendingVerificationRequest( data class PendingVerificationRequest(
val ageLocalTs: Long, val ageLocalTs: Long,
val state: EVerificationState,
val isIncoming: Boolean = false, val isIncoming: Boolean = false,
val localId: String = UUID.randomUUID().toString(), // val localId: String = UUID.randomUUID().toString(),
val otherUserId: String, val otherUserId: String,
val otherDeviceId: String?,
// in case of verification via room, it will be not null
val roomId: String?, val roomId: String?,
val transactionId: String? = null, val transactionId: String,//? = null,
val requestInfo: ValidVerificationInfoRequest? = null, // val requestInfo: ValidVerificationInfoRequest? = null,
val readyInfo: ValidVerificationInfoReady? = null, // val readyInfo: ValidVerificationInfoReady? = null,
val cancelConclusion: CancelCode? = null, val cancelConclusion: CancelCode? = null,
val isSuccessful: Boolean = false, val isFinished: Boolean = false,
val handledByOtherSession: Boolean = false, val handledByOtherSession: Boolean = false,
// In case of to device it is sent to a list of devices // In case of to device it is sent to a list of devices
val targetDevices: List<String>? = null val targetDevices: List<String>? = null,
// if available store here the qr code to show
val qrCodeText: String? = null,
val isSasSupported: Boolean = false,
val otherCanShowQrCode: Boolean = false,
val otherCanScanQrCode: Boolean = false,
) { ) {
val isReady: Boolean = readyInfo != null // val isReady: Boolean = readyInfo != null
val isSent: Boolean = transactionId != null //
// val isFinished: Boolean = isSuccessful || cancelConclusion != null
val isFinished: Boolean = isSuccessful || cancelConclusion != null
/**
* SAS is supported if I support it and the other party support it.
*/
fun isSasSupported(): Boolean {
return requestInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_SAS).orFalse()
}
/**
* Other can show QR code if I can scan QR code and other can show QR code.
*/
fun otherCanShowQrCode(): Boolean {
return if (isIncoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
} else {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
}
}
/**
* Other can scan QR code if I can show QR code and other can scan QR code.
*/
fun otherCanScanQrCode(): Boolean {
return if (isIncoming) {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse()
} else {
requestInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SHOW).orFalse() &&
readyInfo?.methods?.contains(VERIFICATION_METHOD_QR_CODE_SCAN).orFalse()
}
}
} }

View File

@ -26,15 +26,15 @@ interface QrCodeVerificationTransaction : VerificationTransaction {
/** /**
* Call when you have scan the other user QR code. * Call when you have scan the other user QR code.
*/ */
fun userHasScannedOtherQrCode(otherQrCodeText: String) suspend fun userHasScannedOtherQrCode(otherQrCodeText: String)
/** /**
* Call when you confirm that other user has scanned your QR code. * Call when you confirm that other user has scanned your QR code.
*/ */
fun otherUserScannedMyQrCode() suspend fun otherUserScannedMyQrCode()
/** /**
* Call when you do not confirm that other user has scanned your QR code. * Call when you do not confirm that other user has scanned your QR code.
*/ */
fun otherUserDidNotScannedMyQrCode() suspend fun otherUserDidNotScannedMyQrCode()
} }

View File

@ -18,19 +18,41 @@ package org.matrix.android.sdk.api.session.crypto.verification
interface SasVerificationTransaction : VerificationTransaction { interface SasVerificationTransaction : VerificationTransaction {
fun supportsEmoji(): Boolean companion object {
const val SAS_MAC_SHA256_LONGKDF = "hmac-sha256"
const val SAS_MAC_SHA256 = "hkdf-hmac-sha256"
fun supportsDecimal(): Boolean // Deprecated maybe removed later, use V2
const val KEY_AGREEMENT_V1 = "curve25519"
const val KEY_AGREEMENT_V2 = "curve25519-hkdf-sha256"
// ordered by preferred order
val KNOWN_AGREEMENT_PROTOCOLS = listOf(KEY_AGREEMENT_V2, KEY_AGREEMENT_V1)
// ordered by preferred order
val KNOWN_HASHES = listOf("sha256")
// ordered by preferred order
val KNOWN_MACS = listOf(SAS_MAC_SHA256, SAS_MAC_SHA256_LONGKDF)
// older devices have limited support of emoji but SDK offers images for the 64 verification emojis
// so always send that we support EMOJI
val KNOWN_SHORT_CODES = listOf(SasMode.EMOJI, SasMode.DECIMAL)
}
fun supportsEmoji(): Boolean
fun getEmojiCodeRepresentation(): List<EmojiRepresentation> fun getEmojiCodeRepresentation(): List<EmojiRepresentation>
fun getDecimalCodeRepresentation(): String fun getDecimalCodeRepresentation(): String?
/** /**
* To be called by the client when the user has verified that * To be called by the client when the user has verified that
* both short codes do match. * both short codes do match.
*/ */
fun userHasVerifiedShortCode() suspend fun userHasVerifiedShortCode()
fun shortCodeDoesNotMatch() suspend fun acceptVerification()
suspend fun shortCodeDoesNotMatch()
} }

View File

@ -0,0 +1,43 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.session.crypto.verification
sealed class VerificationEvent(val transactionId: String) {
data class RequestAdded(val request: PendingVerificationRequest) : VerificationEvent(request.transactionId)
data class RequestUpdated(val request: PendingVerificationRequest) : VerificationEvent(request.transactionId)
data class TransactionAdded(val transaction: VerificationTransaction) : VerificationEvent(transaction.transactionId)
data class TransactionUpdated(val transaction: VerificationTransaction) : VerificationEvent(transaction.transactionId)
}
fun VerificationEvent.getRequest() : PendingVerificationRequest? {
return when(this) {
is VerificationEvent.RequestAdded -> this.request
is VerificationEvent.RequestUpdated -> this.request
is VerificationEvent.TransactionAdded -> null
is VerificationEvent.TransactionUpdated -> null
}
}
fun VerificationEvent.getTransaction() : VerificationTransaction? {
return when(this) {
is VerificationEvent.RequestAdded -> null
is VerificationEvent.RequestUpdated -> null
is VerificationEvent.TransactionAdded -> this.transaction
is VerificationEvent.TransactionUpdated -> this.transaction
}
}

View File

@ -16,6 +16,7 @@
package org.matrix.android.sdk.api.session.crypto.verification package org.matrix.android.sdk.api.session.crypto.verification
import kotlinx.coroutines.flow.Flow
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.events.model.LocalEcho import org.matrix.android.sdk.api.session.events.model.LocalEcho
@ -29,86 +30,85 @@ import org.matrix.android.sdk.api.session.events.model.LocalEcho
*/ */
interface VerificationService { interface VerificationService {
fun addListener(listener: Listener) // fun addListener(listener: Listener)
//
// fun removeListener(listener: Listener)
fun removeListener(listener: Listener) fun requestEventFlow(): Flow<VerificationEvent>
/** /**
* Mark this device as verified manually. * Mark this device as verified manually.
*/ */
fun markedLocallyAsManuallyVerified(userId: String, deviceID: String) suspend fun markedLocallyAsManuallyVerified(userId: String, deviceID: String)
fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction? suspend fun getExistingTransaction(otherUserId: String, tid: String): VerificationTransaction?
fun getExistingVerificationRequests(otherUserId: String): List<PendingVerificationRequest> suspend fun getExistingVerificationRequests(otherUserId: String): List<PendingVerificationRequest>
fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest? suspend fun getExistingVerificationRequest(otherUserId: String, tid: String?): PendingVerificationRequest?
fun getExistingVerificationRequestInRoom(roomId: String, tid: String?): PendingVerificationRequest? suspend fun getExistingVerificationRequestInRoom(roomId: String, tid: String): PendingVerificationRequest?
fun beginKeyVerification( /**
method: VerificationMethod, * Request an interactive verification to begin
otherUserId: String, *
otherDeviceId: String, * This sends out a m.key.verification.request event over to-device messaging to
transactionId: String? * to this device.
): String? *
* If no specific device should be verified, but we would like to request
* verification from all our devices, use [requestSelfKeyVerification] instead.
*/
suspend fun requestDeviceVerification(methods: List<VerificationMethod>, otherUserId: String, otherDeviceId: String?): PendingVerificationRequest?
/** /**
* Request key verification with another user via room events (instead of the to-device API). * Request key verification with another user via room events (instead of the to-device API).
*/ */
fun requestKeyVerificationInDMs( @Throws
suspend fun requestKeyVerificationInDMs(
methods: List<VerificationMethod>, methods: List<VerificationMethod>,
otherUserId: String, otherUserId: String,
roomId: String, roomId: String,
localId: String? = LocalEcho.createLocalEchoId() localId: String? = LocalEcho.createLocalEchoId()
): PendingVerificationRequest ): PendingVerificationRequest
fun cancelVerificationRequest(request: PendingVerificationRequest) /**
* Request a self key verification using to-device API (instead of room events).
*/
@Throws
suspend fun requestSelfKeyVerification(methods: List<VerificationMethod>): PendingVerificationRequest
/** /**
* Request a key verification from another user using toDevice events. * You should call this method after receiving a verification request.
* Accept the verification request advertising the given methods as supported
* Returns false if the request is unknown or transaction is not ready.
*/ */
fun requestKeyVerification( suspend fun readyPendingVerification(
methods: List<VerificationMethod>, methods: List<VerificationMethod>,
otherUserId: String, otherUserId: String,
otherDevices: List<String>? transactionId: String
): PendingVerificationRequest ): Boolean
fun declineVerificationRequestInDMs( suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
otherUserId: String,
transactionId: String,
roomId: String
)
// Only SAS method is supported for the moment suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String)
// TODO Parameter otherDeviceId should be removed in this case
fun beginKeyVerificationInDMs( suspend fun startKeyVerification(
method: VerificationMethod, method: VerificationMethod,
transactionId: String,
roomId: String,
otherUserId: String, otherUserId: String,
otherDeviceId: String requestId: String
): String ): String?
/** suspend fun reciprocateQRVerification(
* Returns false if the request is unknown.
*/
fun readyPendingVerificationInDMs(
methods: List<VerificationMethod>,
otherUserId: String, otherUserId: String,
roomId: String, requestId: String,
transactionId: String scannedData: String
): Boolean ): String?
/** suspend fun sasCodeMatch(theyMatch: Boolean, transactionId: String)
* Returns false if the request is unknown.
*/
fun readyPendingVerification(
methods: List<VerificationMethod>,
otherUserId: String,
transactionId: String
): Boolean
// This starts the short SAS flow, the one that doesn't start with a request, deprecated
// using flow now?
interface Listener { interface Listener {
/** /**
* Called when a verification request is created either by the user, or by the other user. * Called when a verification request is created either by the user, or by the other user.
@ -151,5 +151,6 @@ interface VerificationService {
} }
} }
fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event) suspend fun onPotentiallyInterestingEventRoomFailToDecrypt(event: Event)
suspend fun declineVerificationRequestInDMs(otherUserId: String, transactionId: String, roomId: String)
} }

Some files were not shown because too many files have changed in this diff Show More