mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-21 17:05:39 +03:00
suspend verif WIP
This commit is contained in:
commit
cf366f7a9c
255 changed files with 16586 additions and 6989 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
@ -11,6 +11,9 @@
|
|||
/benchmark-out
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
rust-sdk/target/*
|
||||
rust-sdk/src/uniffi/*
|
||||
Cargo.lock
|
||||
|
||||
/tmp
|
||||
/fastlane/private
|
||||
|
@ -23,3 +26,8 @@
|
|||
/yarn.lock
|
||||
/node_modules
|
||||
**/out/failures
|
||||
|
||||
# For manual dependency to rust crypto sdk
|
||||
matrix-sdk-android/src/main/jniLibs/
|
||||
|
||||
matrix-sdk-android/libs/crypto-android-release.aar
|
||||
|
|
|
@ -121,6 +121,15 @@ allprojects {
|
|||
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 {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
ext.groups = [
|
||||
jitpack : [
|
||||
jitpack : [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
|
@ -15,7 +15,7 @@ ext.groups = [
|
|||
'com.github.Zhuinden',
|
||||
]
|
||||
],
|
||||
jitsi : [
|
||||
jitsi : [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
|
@ -24,7 +24,7 @@ ext.groups = [
|
|||
'org.webkit',
|
||||
]
|
||||
],
|
||||
google : [
|
||||
google : [
|
||||
regex: [
|
||||
'androidx\\..*',
|
||||
'com\\.android\\.tools\\..*',
|
||||
|
@ -45,6 +45,13 @@ ext.groups = [
|
|||
'com.vanniktech',
|
||||
]
|
||||
],
|
||||
mavenSnapshots: [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
'org.matrix.rustcomponents'
|
||||
]
|
||||
],
|
||||
mavenCentral: [
|
||||
regex: [
|
||||
],
|
||||
|
@ -205,6 +212,7 @@ ext.groups = [
|
|||
'org.jvnet.staxex',
|
||||
'org.maplibre.gl',
|
||||
'org.matrix.android',
|
||||
'org.matrix.rustcomponents',
|
||||
'org.mockito',
|
||||
'org.mongodb',
|
||||
'org.objenesis',
|
||||
|
@ -224,7 +232,7 @@ ext.groups = [
|
|||
'xml-apis',
|
||||
]
|
||||
],
|
||||
jcenter : [
|
||||
jcenter : [
|
||||
regex: [
|
||||
],
|
||||
group: [
|
||||
|
|
|
@ -74,7 +74,7 @@ android {
|
|||
|
||||
testOptions {
|
||||
// Comment to run on Android 12
|
||||
// execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
execution 'ANDROIDX_TEST_ORCHESTRATOR'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -110,6 +110,7 @@ android {
|
|||
// Disabled for now, there are too many errors. Could be handled in another dedicated PR
|
||||
// '-Xexplicit-api=strict', // or warning
|
||||
"-opt-in=kotlin.RequiresOptIn",
|
||||
"-opt-in=kotlin.OptIn",
|
||||
// Opt in for kotlinx.coroutines.FlowPreview
|
||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
||||
]
|
||||
|
@ -160,12 +161,25 @@ static def gitRevisionDate() {
|
|||
return cmd.execute().text.trim()
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
resolutionStrategy.cacheChangingModulesFor 0, 'seconds'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation libs.jetbrains.coroutinesCore
|
||||
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
|
||||
|
||||
rustCryptoImplementation libs.androidx.lifecycleLivedata
|
||||
|
||||
// Lifecycle
|
||||
implementation libs.androidx.lifecycleCommon
|
||||
implementation libs.androidx.lifecycleProcess
|
||||
|
|
|
@ -19,13 +19,12 @@ package org.matrix.android.sdk
|
|||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import org.junit.Rule
|
||||
import org.matrix.android.sdk.common.RetryTestRule
|
||||
import org.matrix.android.sdk.test.shared.createTimberTestRule
|
||||
|
||||
interface InstrumentedTest {
|
||||
|
||||
@Rule
|
||||
fun retryTestRule() = RetryTestRule(3)
|
||||
// @Rule
|
||||
// fun retryTestRule() = RetryTestRule(3)
|
||||
|
||||
@Rule
|
||||
fun timberTestRule() = createTimberTestRule()
|
||||
|
|
|
@ -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) {
|
||||
val testHelper = CommonTestHelper(context, cryptoConfig)
|
||||
val cryptoTestHelper = CryptoTestHelper(testHelper)
|
||||
return runTest(dispatchTimeoutMs = TestConstants.timeOutMillis) {
|
||||
try {
|
||||
withContext(Dispatchers.Default) {
|
||||
return try {
|
||||
runTest(dispatchTimeoutMs = TestConstants.timeOutMillis * 2) {
|
||||
withContext(Dispatchers.Main) {
|
||||
block(cryptoTestHelper, testHelper)
|
||||
}
|
||||
} finally {
|
||||
if (autoSignoutOnClose) {
|
||||
}
|
||||
} finally {
|
||||
if (autoSignoutOnClose) {
|
||||
runBlocking {
|
||||
testHelper.cleanUpOpenedSessions()
|
||||
}
|
||||
}
|
||||
|
@ -250,7 +252,7 @@ class CommonTestHelper internal constructor(context: Context, val cryptoConfig:
|
|||
|
||||
// not sure why it's taking so long :/
|
||||
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 {
|
||||
otherSession.roomService().joinRoom(roomID)
|
||||
} 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 {
|
||||
return wrapWithTimeout(timeout) {
|
||||
suspendCoroutine { continuation ->
|
||||
|
|
|
@ -37,4 +37,10 @@ data class CryptoTestData(
|
|||
testHelper.signOutAndClose(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun initializeCrossSigning(testHelper: CryptoTestHelper) {
|
||||
sessions.forEach {
|
||||
testHelper.initializeCrossSigning(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.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.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.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.internal.crypto.verification.IncomingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.internal.crypto.verification.OutgoingSasVerificationTransaction
|
||||
import org.matrix.android.sdk.api.session.crypto.verification.EVerificationState
|
||||
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.VerificationTxState
|
||||
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.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.RoomHistoryVisibility
|
||||
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.securestorage.EmptyKeySigner
|
||||
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 kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
|
@ -121,6 +122,80 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
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
|
||||
*/
|
||||
|
@ -189,7 +264,7 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
return MegolmBackupCreationInfo(
|
||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
||||
authData = createFakeMegolmBackupAuthData(),
|
||||
recoveryKey = "fake"
|
||||
recoveryKey = BackupUtils.recoveryKeyFromPassphrase("3cnTdW")!!
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -221,7 +296,6 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
}
|
||||
|
||||
suspend fun initializeCrossSigning(session: Session) {
|
||||
testHelper.waitForCallback<Unit> {
|
||||
session.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
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
|
||||
val creationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val version = testHelper.waitForCallback<KeysVersion> {
|
||||
session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo, it)
|
||||
}
|
||||
val creationInfo = session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null)
|
||||
val version = session.cryptoService().keysBackupService().createKeysBackupVersion(creationInfo)
|
||||
|
||||
// Save it for gossiping
|
||||
session.cryptoService().keysBackupService().saveBackupRecoveryKey(creationInfo.recoveryKey, version = version.version)
|
||||
|
||||
extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()?.let { secret ->
|
||||
creationInfo.recoveryKey.toBase64().let { secret ->
|
||||
ssssService.storeSecret(
|
||||
KEYBACKUP_SECRET_SSSS_NAME,
|
||||
secret,
|
||||
|
@ -298,61 +367,78 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
val bobVerificationService = bob.cryptoService().verificationService()
|
||||
|
||||
val localId = UUID.randomUUID().toString()
|
||||
aliceVerificationService.requestKeyVerificationInDMs(
|
||||
val requestID = aliceVerificationService.requestKeyVerificationInDMs(
|
||||
localId = localId,
|
||||
methods = listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
otherUserId = bob.myUserId,
|
||||
roomId = roomId
|
||||
).transactionId
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically(
|
||||
onFail = {
|
||||
fail("Bob should see an incoming request from alice")
|
||||
}
|
||||
) {
|
||||
bobVerificationService.getExistingVerificationRequests(alice.myUserId).firstOrNull {
|
||||
it.requestInfo?.fromDevice == alice.sessionParams.deviceId
|
||||
it.otherDeviceId == alice.sessionParams.deviceId
|
||||
} != null
|
||||
}
|
||||
|
||||
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
|
||||
testHelper.retryPeriodically {
|
||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequests(bob.myUserId)
|
||||
.firstOrNull { it.localId == localId }
|
||||
if (outgoingRequest?.isReady == true) {
|
||||
requestID = outgoingRequest.transactionId!!
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
testHelper.betterRetryPeriodically(
|
||||
onFail = {
|
||||
fail("Alice should see the verification in ready state")
|
||||
}
|
||||
) {
|
||||
val outgoingRequest = aliceVerificationService.getExistingVerificationRequest(bob.myUserId, requestID)
|
||||
outgoingRequest?.state == EVerificationState.Ready
|
||||
}
|
||||
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
Timber.v("#TEST let alice start the verification")
|
||||
aliceVerificationService.startKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
roomId,
|
||||
bob.myUserId,
|
||||
bob.sessionParams.credentials.deviceId!!
|
||||
requestID,
|
||||
)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
var alicePovTx: OutgoingSasVerificationTransaction? = null
|
||||
var bobPovTx: IncomingSasVerificationTransaction? = null
|
||||
var alicePovTx: SasVerificationTransaction? = null
|
||||
var bobPovTx: SasVerificationTransaction? = null
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bob.myUserId, requestID!!) as? OutgoingSasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is ${alicePovTx?.uxState}")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
testHelper.betterRetryPeriodically(
|
||||
onFail = {
|
||||
fail("Alice should should see a verification code")
|
||||
}
|
||||
) {
|
||||
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
|
||||
testHelper.retryPeriodically {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID!!) as? IncomingSasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is ${alicePovTx?.uxState}")
|
||||
if (bobPovTx?.state == VerificationTxState.OnStarted) {
|
||||
bobPovTx?.performAccept()
|
||||
}
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
testHelper.betterRetryPeriodically(
|
||||
onFail = {
|
||||
fail("Bob should should see a verification code")
|
||||
}
|
||||
) {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(alice.myUserId, requestID)
|
||||
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())
|
||||
|
@ -360,11 +446,11 @@ class CryptoTestHelper(val testHelper: CommonTestHelper) {
|
|||
bobPovTx!!.userHasVerifiedShortCode()
|
||||
alicePovTx!!.userHasVerifiedShortCode()
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
alice.cryptoService().crossSigningService().isUserTrusted(bob.myUserId)
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
bob.cryptoService().crossSigningService().isUserTrusted(alice.myUserId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,6 @@ import org.junit.runners.JUnit4
|
|||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
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.room.model.RoomHistoryVisibility
|
||||
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 ...")
|
||||
val keysBackupService = aliceSession.cryptoService().keysBackupService()
|
||||
val keyBackupPassword = "FooBarBaz"
|
||||
val megolmBackupCreationInfo = commonTestHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
|
||||
}
|
||||
val version = commonTestHelper.waitForCallback<KeysVersion> {
|
||||
keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
val megolmBackupCreationInfo = keysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
|
||||
val version = keysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||
|
||||
commonTestHelper.waitForCallback<Unit> {
|
||||
keysBackupService.backupAllGroupSessions(null, it)
|
||||
commonTestHelper.retryPeriodically {
|
||||
keysBackupService.getTotalNumbersOfKeys() == keysBackupService.getTotalNumbersOfBackedUpKeys()
|
||||
}
|
||||
|
||||
// signout
|
||||
|
@ -235,20 +227,15 @@ class E2EShareKeysConfigTest : InstrumentedTest {
|
|||
newAliceSession.cryptoService().enableShareKeyOnInvite(true)
|
||||
|
||||
newAliceSession.cryptoService().keysBackupService().let { kbs ->
|
||||
val keyVersionResult = commonTestHelper.waitForCallback<KeysVersionResult?> {
|
||||
kbs.getVersion(version.version, it)
|
||||
}
|
||||
val keyVersionResult = kbs.getVersion(version.version)
|
||||
|
||||
val importedResult = commonTestHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
kbs.restoreKeyBackupWithPassword(
|
||||
val importedResult = kbs.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
assertEquals(2, importedResult.totalNumberOfKeys)
|
||||
}
|
||||
|
|
|
@ -18,12 +18,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||
|
||||
import android.util.Log
|
||||
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.suspendCancellableCoroutine
|
||||
import org.amshove.kluent.fail
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
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.session.Session
|
||||
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.content.EncryptedEventContent
|
||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||
|
@ -92,7 +76,6 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom(true)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val e2eRoomID = cryptoTestData.roomId
|
||||
|
||||
val aliceRoomPOV = aliceSession.getRoom(e2eRoomID)!!
|
||||
// we want to disable key gossiping to just check initial sending of keys
|
||||
aliceSession.cryptoService().enableKeyGossiping(false)
|
||||
|
@ -100,70 +83,50 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
// add some more users and invite them
|
||||
val otherAccounts = listOf("benoit", "valere", "ganfra") // , "adam", "manu")
|
||||
.map {
|
||||
testHelper.createAccount(it, SessionTestParams(true)).also {
|
||||
it.cryptoService().enableKeyGossiping(false)
|
||||
}
|
||||
.let {
|
||||
cryptoTestHelper.inviteNewUsersAndWaitForThemToJoin(aliceSession, e2eRoomID, it)
|
||||
}
|
||||
|
||||
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", "Alice is sending the message")
|
||||
|
||||
val text = "This is my message"
|
||||
val sentEventId: String? = sendMessageInRoom(testHelper, aliceRoomPOV, text)
|
||||
// val sentEvent = testHelper.sendTextMessage(aliceRoomPOV, "Hello all", 1).first()
|
||||
Assert.assertTrue("Message should be sent", sentEventId != null)
|
||||
|
||||
// All should be able to decrypt
|
||||
otherAccounts.forEach { otherSession ->
|
||||
testHelper.retryPeriodically {
|
||||
val timeLineEvent = otherSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId!!)
|
||||
testHelper.betterRetryPeriodically(
|
||||
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.isEncrypted() &&
|
||||
timeLineEvent.root.getClearType() == EventType.MESSAGE &&
|
||||
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
|
||||
Log.v("#E2E TEST", "Create some new accounts and invite them")
|
||||
val newAccount = listOf("adam") // , "adam", "manu")
|
||||
.map {
|
||||
testHelper.createAccount(it, SessionTestParams(true))
|
||||
.let {
|
||||
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
|
||||
delay(3_000)
|
||||
|
||||
// check that messages are encrypted (uisi)
|
||||
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 {
|
||||
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
|
||||
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 {
|
||||
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 ...")
|
||||
val bobKeysBackupService = bobSession.cryptoService().keysBackupService()
|
||||
val keyBackupPassword = "FooBarBaz"
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null, it)
|
||||
}
|
||||
val version = testHelper.waitForCallback<KeysVersion> {
|
||||
bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
val megolmBackupCreationInfo = bobKeysBackupService.prepareKeysBackupVersion(keyBackupPassword, null)
|
||||
val version = bobKeysBackupService.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||
|
||||
Log.v("#E2E TEST", "... Key backup started and enabled for bob")
|
||||
// Bob session should now have
|
||||
|
||||
|
@ -242,7 +207,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
sentEventIds.add(it)
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically(
|
||||
onFail = {
|
||||
fail("Bob should be able to decrypt all messages")
|
||||
}
|
||||
) {
|
||||
val timeLineEvent = bobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
|
@ -256,7 +225,13 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
// Let's wait a bit to be sure that bob has backed up the session
|
||||
|
||||
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")
|
||||
|
||||
// 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
|
||||
Log.v("#E2E TEST", "check that bob can't currently decrypt")
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
val timelineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(sentEventId)?.also {
|
||||
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
|
||||
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
|
||||
|
||||
newBobSession.cryptoService().keysBackupService().let { kbs ->
|
||||
val keyVersionResult = testHelper.waitForCallback<KeysVersionResult?> {
|
||||
kbs.getVersion(version.version, it)
|
||||
}
|
||||
val keyVersionResult = kbs.getVersion(version.version)
|
||||
|
||||
val importedResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
kbs.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
val importedResult = kbs.restoreKeyBackupWithPassword(
|
||||
keyVersionResult!!,
|
||||
keyBackupPassword,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
|
||||
assertEquals(3, importedResult.totalNumberOfKeys)
|
||||
}
|
||||
|
@ -342,7 +318,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
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)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
|
@ -366,7 +346,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
// Try to request
|
||||
sentEventIds.forEach { sentEventId ->
|
||||
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)
|
||||
|
@ -391,6 +371,8 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
// }
|
||||
// }
|
||||
|
||||
// */
|
||||
|
||||
cryptoTestHelper.ensureCannotDecrypt(sentEventIds, newBobSession, e2eRoomID, null)
|
||||
|
||||
// Now mark new bob session as verified
|
||||
|
@ -431,7 +413,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
firstMessage.let { text ->
|
||||
firstEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
val timeLineEvent = bobSessionWithBetterKey.getRoom(e2eRoomID)?.getTimelineEvent(firstEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
|
@ -457,7 +439,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
secondMessage.let { text ->
|
||||
secondEventId = sendMessageInRoom(testHelper, aliceRoomPOV, text)!!
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
val timeLineEvent = newBobSession.getRoom(e2eRoomID)?.getTimelineEvent(secondEventId)
|
||||
timeLineEvent != null &&
|
||||
timeLineEvent.isEncrypted() &&
|
||||
|
@ -488,11 +470,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
// Now let's verify bobs session, and re-request keys
|
||||
bobSessionWithBetterKey.cryptoService()
|
||||
.verificationService()
|
||||
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId!!)
|
||||
.markedLocallyAsManuallyVerified(newBobSession.myUserId, newBobSession.sessionParams.deviceId)
|
||||
|
||||
newBobSession.cryptoService()
|
||||
.verificationService()
|
||||
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId!!)
|
||||
.markedLocallyAsManuallyVerified(bobSessionWithBetterKey.myUserId, bobSessionWithBetterKey.sessionParams.deviceId)
|
||||
|
||||
// now let new session request
|
||||
newBobSession.cryptoService().reRequestRoomKeyForEvent(firstEventNewBobPov.root)
|
||||
|
@ -501,7 +483,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
// old session should have shared the key at earliest known index now
|
||||
// we should be able to decrypt both
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
val canDecryptFirst = try {
|
||||
newBobSession.cryptoService().decryptEvent(firstEventNewBobPov.root, "")
|
||||
true
|
||||
|
@ -524,7 +506,7 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
val timeline = aliceRoomPOV.timelineService().createTimeline(null, TimelineSettings(60))
|
||||
timeline.start()
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
val decryptedMsg = timeline.getSnapshot()
|
||||
.filter { it.root.getClearType() == EventType.MESSAGE }
|
||||
.also { list ->
|
||||
|
@ -543,76 +525,76 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
/**
|
||||
* Test that if a better key is forwared (lower index, it is then used)
|
||||
*/
|
||||
@Test
|
||||
fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
||||
val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
|
||||
cryptoTestHelper.bootstrapSecurity(aliceSession)
|
||||
|
||||
// now let's create a new login from alice
|
||||
|
||||
val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
|
||||
val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
|
||||
val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
|
||||
// initiate self verification
|
||||
aliceSession.cryptoService().verificationService().requestKeyVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceNewSession.myUserId,
|
||||
listOf(aliceNewSession.sessionParams.deviceId!!)
|
||||
)
|
||||
|
||||
val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
|
||||
|
||||
assertEquals("Decimal code should have matched", oldCode, newCode)
|
||||
|
||||
// Assert that devices are verified
|
||||
val newDeviceFromOldPov: CryptoDeviceInfo? =
|
||||
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||
val oldDeviceFromNewPov: CryptoDeviceInfo? =
|
||||
aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||
|
||||
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)
|
||||
|
||||
// wait for secret gossiping to happen
|
||||
testHelper.retryPeriodically {
|
||||
aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
"MSK Private parts should be the same",
|
||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
|
||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
|
||||
)
|
||||
assertEquals(
|
||||
"USK Private parts should be the same",
|
||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
|
||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
"SSK Private parts should be the same",
|
||||
aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
|
||||
aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
|
||||
)
|
||||
|
||||
// Let's check that we have the megolm backup key
|
||||
assertEquals(
|
||||
"Megolm key should be the same",
|
||||
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
|
||||
)
|
||||
assertEquals(
|
||||
"Megolm version should be the same",
|
||||
aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
|
||||
aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
|
||||
)
|
||||
}
|
||||
// @Test
|
||||
// fun testASelfInteractiveVerificationAndGossip() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
//
|
||||
// val aliceSession = testHelper.createAccount("alice", SessionTestParams(true))
|
||||
// cryptoTestHelper.bootstrapSecurity(aliceSession)
|
||||
//
|
||||
// // now let's create a new login from alice
|
||||
//
|
||||
// val aliceNewSession = testHelper.logIntoAccount(aliceSession.myUserId, SessionTestParams(true))
|
||||
//
|
||||
// val deferredOldCode = aliceSession.cryptoService().verificationService().readOldVerificationCodeAsync(this, aliceSession.myUserId)
|
||||
// val deferredNewCode = aliceNewSession.cryptoService().verificationService().readNewVerificationCodeAsync(this, aliceSession.myUserId)
|
||||
// // initiate self verification
|
||||
// aliceSession.cryptoService().verificationService().requestSelfKeyVerification(
|
||||
// listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
// // aliceNewSession.myUserId,
|
||||
// // listOf(aliceNewSession.sessionParams.deviceId!!)
|
||||
// )
|
||||
//
|
||||
// val (oldCode, newCode) = awaitAll(deferredOldCode, deferredNewCode)
|
||||
//
|
||||
// assertEquals("Decimal code should have matched", oldCode, newCode)
|
||||
//
|
||||
// // Assert that devices are verified
|
||||
// val newDeviceFromOldPov: CryptoDeviceInfo? =
|
||||
// aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceNewSession.sessionParams.deviceId)
|
||||
// val oldDeviceFromNewPov: CryptoDeviceInfo? =
|
||||
// aliceSession.cryptoService().getCryptoDeviceInfo(aliceSession.myUserId, aliceSession.sessionParams.deviceId)
|
||||
//
|
||||
// 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)
|
||||
//
|
||||
// // wait for secret gossiping to happen
|
||||
// testHelper.retryPeriodically {
|
||||
// aliceNewSession.cryptoService().crossSigningService().allPrivateKeysKnown()
|
||||
// }
|
||||
//
|
||||
// testHelper.retryPeriodically {
|
||||
// aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo() != null
|
||||
// }
|
||||
//
|
||||
// assertEquals(
|
||||
// "MSK Private parts should be the same",
|
||||
// aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master,
|
||||
// aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.master
|
||||
// )
|
||||
// assertEquals(
|
||||
// "USK Private parts should be the same",
|
||||
// aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user,
|
||||
// aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.user
|
||||
// )
|
||||
//
|
||||
// assertEquals(
|
||||
// "SSK Private parts should be the same",
|
||||
// aliceSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned,
|
||||
// aliceNewSession.cryptoService().crossSigningService().getCrossSigningPrivateKeys()!!.selfSigned
|
||||
// )
|
||||
//
|
||||
// // Let's check that we have the megolm backup key
|
||||
// assertEquals(
|
||||
// "Megolm key should be the same",
|
||||
// aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey,
|
||||
// aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.recoveryKey
|
||||
// )
|
||||
// assertEquals(
|
||||
// "Megolm version should be the same",
|
||||
// aliceSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version,
|
||||
// aliceNewSession.cryptoService().keysBackupService().getKeyBackupRecoveryKeyInfo()!!.version
|
||||
// )
|
||||
// }
|
||||
|
||||
@Test
|
||||
fun test_EncryptionDoesNotHinderVerification() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
|
@ -625,26 +607,23 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
user = aliceSession.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
val bobAuthParams = UserPasswordAuth(
|
||||
user = bobSession!!.myUserId,
|
||||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.waitForCallback {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
})
|
||||
|
||||
testHelper.waitForCallback {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
|
||||
// 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
|
||||
Timber.v("#TEST: Wait for message to be synced back")
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(sentEvent) != null
|
||||
}
|
||||
|
||||
|
@ -681,11 +660,11 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
|
||||
val secondEvent = sendMessageInRoom(testHelper, roomFromAlicePOV, "World")!!
|
||||
Timber.v("#TEST: Wait for message to be synced back")
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
bobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||
}
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
testHelper.betterRetryPeriodically {
|
||||
secondBobSession.roomService().getRoom(cryptoTestData.roomId)?.timelineService()?.getTimelineEvent(secondEvent) != null
|
||||
}
|
||||
|
||||
|
@ -693,94 +672,94 @@ class E2eeSanityTests : InstrumentedTest {
|
|||
cryptoTestHelper.ensureCannotDecrypt(listOf(secondEvent), secondBobSession, cryptoTestData.roomId)
|
||||
}
|
||||
|
||||
private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
return scope.async {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
var oldCode: String? = null
|
||||
val listener = object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
val readyInfo = pr.readyInfo
|
||||
if (readyInfo != null) {
|
||||
beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
userId,
|
||||
readyInfo.fromDevice,
|
||||
readyInfo.transactionId
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "exitsingPov: $tx")
|
||||
val sasTx = tx as OutgoingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
// for the test we just accept?
|
||||
oldCode = sasTx.getDecimalCodeRepresentation()
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
}
|
||||
OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
removeListener(this)
|
||||
// we can release this latch?
|
||||
continuation.resume(oldCode!!)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
addListener(listener)
|
||||
continuation.invokeOnCancellation { removeListener(listener) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
return scope.async {
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
var newCode: String? = null
|
||||
|
||||
val listener = object : VerificationService.Listener {
|
||||
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// let's ready
|
||||
readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
userId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
}
|
||||
|
||||
var matchOnce = true
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.d("##TEST", "newPov: $tx")
|
||||
|
||||
val sasTx = tx as IncomingSasVerificationTransaction
|
||||
when (sasTx.uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
// no need to accept as there was a request first it will auto accept
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
if (matchOnce) {
|
||||
sasTx.userHasVerifiedShortCode()
|
||||
newCode = sasTx.getDecimalCodeRepresentation()
|
||||
matchOnce = false
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
removeListener(this)
|
||||
continuation.resume(newCode!!)
|
||||
}
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
addListener(listener)
|
||||
continuation.invokeOnCancellation { removeListener(listener) }
|
||||
}
|
||||
}
|
||||
}
|
||||
// private suspend fun VerificationService.readOldVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
// return scope.async {
|
||||
// suspendCancellableCoroutine { continuation ->
|
||||
// var oldCode: String? = null
|
||||
// val listener = object : VerificationService.Listener {
|
||||
//
|
||||
// override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
// val readyInfo = pr.readyInfo
|
||||
// if (readyInfo != null) {
|
||||
// beginKeyVerification(
|
||||
// VerificationMethod.SAS,
|
||||
// userId,
|
||||
// readyInfo.fromDevice,
|
||||
// readyInfo.transactionId
|
||||
//
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
// Log.d("##TEST", "exitsingPov: $tx")
|
||||
// val sasTx = tx as OutgoingSasVerificationTransaction
|
||||
// when (sasTx.uxState) {
|
||||
// OutgoingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
// // for the test we just accept?
|
||||
// oldCode = sasTx.getDecimalCodeRepresentation()
|
||||
// sasTx.userHasVerifiedShortCode()
|
||||
// }
|
||||
// OutgoingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
// removeListener(this)
|
||||
// // we can release this latch?
|
||||
// continuation.resume(oldCode!!)
|
||||
// }
|
||||
// else -> Unit
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// addListener(listener)
|
||||
// continuation.invokeOnCancellation { removeListener(listener) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private suspend fun VerificationService.readNewVerificationCodeAsync(scope: CoroutineScope, userId: String): Deferred<String> {
|
||||
// return scope.async {
|
||||
// suspendCancellableCoroutine { continuation ->
|
||||
// var newCode: String? = null
|
||||
//
|
||||
// val listener = object : VerificationService.Listener {
|
||||
//
|
||||
// override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// // let's ready
|
||||
// readyPendingVerification(
|
||||
// listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
// userId,
|
||||
// pr.transactionId!!
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// var matchOnce = true
|
||||
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
// Log.d("##TEST", "newPov: $tx")
|
||||
//
|
||||
// val sasTx = tx as IncomingSasVerificationTransaction
|
||||
// when (sasTx.uxState) {
|
||||
// IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
// // no need to accept as there was a request first it will auto accept
|
||||
// }
|
||||
// IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
// if (matchOnce) {
|
||||
// sasTx.userHasVerifiedShortCode()
|
||||
// newCode = sasTx.getDecimalCodeRepresentation()
|
||||
// matchOnce = false
|
||||
// }
|
||||
// }
|
||||
// IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
// removeListener(this)
|
||||
// continuation.resume(newCode!!)
|
||||
// }
|
||||
// else -> Unit
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// addListener(listener)
|
||||
// continuation.invokeOnCancellation { removeListener(listener) }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
private suspend fun ensureMembersHaveJoined(testHelper: CommonTestHelper, aliceSession: Session, otherAccounts: List<Session>, e2eRoomID: String) {
|
||||
testHelper.retryPeriodically {
|
||||
|
|
|
@ -52,9 +52,7 @@ class PreShareKeysTest : InstrumentedTest {
|
|||
Log.d("#Test", "Room Key Received from alice $preShareCount")
|
||||
|
||||
// Force presharing of new outbound key
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID, it)
|
||||
}
|
||||
aliceSession.cryptoService().prepareToEncrypt(e2eRoomID)
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
val newKeysCount = bobSession.cryptoService().keysBackupService().getTotalNumbersOfKeys()
|
||||
|
|
|
@ -94,7 +94,9 @@ class UnwedgingTest : InstrumentedTest {
|
|||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
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 roomFromAlicePOV = aliceSession.getRoom(aliceRoomId)!!
|
||||
|
@ -116,9 +118,9 @@ class UnwedgingTest : InstrumentedTest {
|
|||
// - Store the olm session between A&B devices
|
||||
// Let us pickle our session with bob here so we can later unpickle it
|
||||
// and wedge our session.
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyDevice().identityKey()!!)
|
||||
val sessionIdsForBob = aliceCryptoStore.getDeviceSessionIds(bobSession.cryptoService().getMyCryptoDevice().identityKey()!!)
|
||||
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)
|
||||
|
||||
|
@ -142,9 +144,10 @@ class UnwedgingTest : InstrumentedTest {
|
|||
|
||||
aliceCryptoStore.storeSession(
|
||||
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
|
||||
aliceSession.cryptoService().discardOutboundSession(roomFromAlicePOV.roomId)
|
||||
|
@ -170,7 +173,6 @@ class UnwedgingTest : InstrumentedTest {
|
|||
Assert.assertTrue(messagesReceivedByBob[0].root.mCryptoError == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID)
|
||||
|
||||
// It's a trick to force key request on fail to decrypt
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -183,9 +185,7 @@ class UnwedgingTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, it
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
// Wait until we received back the key
|
||||
testHelper.retryPeriodically {
|
||||
|
|
|
@ -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.session.crypto.crosssigning.isCrossSignedVerified
|
||||
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.runSessionTest
|
||||
import org.matrix.android.sdk.common.SessionTestParams
|
||||
|
@ -54,7 +52,6 @@ class XSigningTest : InstrumentedTest {
|
|||
fun test_InitializeAndStoreKeys() = runSessionTest(context()) { testHelper ->
|
||||
val aliceSession = testHelper.createAccount(TestConstants.USER_ALICE, SessionTestParams(true))
|
||||
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
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()
|
||||
assertNotNull("Master key should be stored", masterPubKey?.unpaddedBase64PublicKey)
|
||||
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", 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)
|
||||
}
|
||||
|
@ -100,39 +98,30 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
})
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
|
||||
// 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)
|
||||
|
||||
assertNotNull("Alice can see bob Master key", bobKeysFromAlicePOV!!.masterKey())
|
||||
assertNull("Alice should not see bob User key", bobKeysFromAlicePOV.userKey())
|
||||
assertNotNull("Alice can see bob SelfSigned key", bobKeysFromAlicePOV.selfSigningKey())
|
||||
|
||||
assertEquals(
|
||||
"Bob keys from alice pov should match",
|
||||
bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey,
|
||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.masterKey()?.unpaddedBase64PublicKey
|
||||
)
|
||||
assertEquals(
|
||||
"Bob keys from alice pov should match",
|
||||
bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey,
|
||||
bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()?.selfSigningKey()?.unpaddedBase64PublicKey
|
||||
)
|
||||
val myKeys = bobSession.cryptoService().crossSigningService().getMyCrossSigningKeys()
|
||||
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.masterKey()?.unpaddedBase64PublicKey, myKeys?.masterKey()?.unpaddedBase64PublicKey)
|
||||
assertEquals("Bob keys from alice pov should match", bobKeysFromAlicePOV.selfSigningKey()?.unpaddedBase64PublicKey, myKeys?.selfSigningKey()?.unpaddedBase64PublicKey)
|
||||
|
||||
assertFalse("Bob keys from alice pov should not be trusted", bobKeysFromAlicePOV.isTrusted())
|
||||
}
|
||||
|
@ -153,40 +142,34 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
})
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
|
||||
// Check that alice can see bob keys
|
||||
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)
|
||||
|
||||
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
|
||||
// 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 bobSecondDeviceId = bobSession2.sessionParams.deviceId!!
|
||||
|
||||
val bobSecondDeviceId = bobSession2.sessionParams.deviceId
|
||||
// Check that bob first session sees the new login
|
||||
val data = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
bobSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
val data = bobSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
|
||||
|
||||
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||
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)
|
||||
|
||||
// Manually mark it as trusted from first session
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId, it)
|
||||
}
|
||||
bobSession.cryptoService().crossSigningService().trustDevice(bobSecondDeviceId)
|
||||
|
||||
// Now alice should cross trust bob's second device
|
||||
val data2 = testHelper.waitForCallback<MXUsersDevicesMap<CryptoDeviceInfo>> {
|
||||
aliceSession.cryptoService().downloadKeys(listOf(bobUserId), true, it)
|
||||
}
|
||||
val data2 = aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobUserId), true)
|
||||
|
||||
// check that the device is seen
|
||||
if (data2.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
|
||||
|
@ -230,20 +209,16 @@ class XSigningTest : InstrumentedTest {
|
|||
password = TestConstants.PASSWORD
|
||||
)
|
||||
|
||||
testHelper.waitForCallback<Unit> {
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
aliceSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(aliceAuthParams)
|
||||
}
|
||||
})
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
|
||||
cryptoTestHelper.verifySASCrossSign(aliceSession, bobSession, cryptoTestData.roomId)
|
||||
|
||||
|
@ -267,13 +242,11 @@ class XSigningTest : InstrumentedTest {
|
|||
.getUserCrossSigningKeys(bobSession.myUserId)!!
|
||||
.masterKey()!!.unpaddedBase64PublicKey!!
|
||||
|
||||
testHelper.waitForCallback<Unit> {
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
}, it)
|
||||
}
|
||||
bobSession.cryptoService().crossSigningService().initializeCrossSigning(object : UserInteractiveAuthInterceptor {
|
||||
override fun performStage(flowResponse: RegistrationFlowResponse, errCode: String?, promise: Continuation<UIABaseAuth>) {
|
||||
promise.resume(bobAuthParams)
|
||||
}
|
||||
})
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
val newBobMsk = aliceSession.cryptoService().crossSigningService()
|
||||
|
|
|
@ -19,9 +19,9 @@ package org.matrix.android.sdk.internal.crypto.gossiping
|
|||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import junit.framework.TestCase.assertNotNull
|
||||
import junit.framework.TestCase.assertTrue
|
||||
import org.amshove.kluent.internal.assertEquals
|
||||
import org.amshove.kluent.shouldBeEqualTo
|
||||
import org.junit.Assert
|
||||
import org.junit.Assert.assertNull
|
||||
|
@ -100,7 +100,7 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
// Try to request
|
||||
aliceSession2.cryptoService().enableKeyGossiping(true)
|
||||
aliceSession2.cryptoService().requestRoomKeyForEvent(receivedEvent.root)
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
||||
val eventMegolmSessionId = receivedEvent.root.content.toModel<EncryptedEventContent>()?.sessionId
|
||||
|
||||
|
@ -163,8 +163,8 @@ class KeyShareTests : InstrumentedTest {
|
|||
|
||||
// Mark the device as trusted
|
||||
|
||||
Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyDevice().identityKey()}")
|
||||
val aliceSecondSession = aliceSession2.cryptoService().getMyDevice()
|
||||
Log.v("#TEST", "=======> Alice device 1 is ${aliceSession.sessionParams.deviceId}|${aliceSession.cryptoService().getMyCryptoDevice().identityKey()}")
|
||||
val aliceSecondSession = aliceSession2.cryptoService().getMyCryptoDevice()
|
||||
Log.v("#TEST", "=======> Alice device 2 is ${aliceSession2.sessionParams.deviceId}|${aliceSecondSession.identityKey()}")
|
||||
|
||||
aliceSession.cryptoService().setDeviceVerification(
|
||||
|
@ -178,7 +178,11 @@ class KeyShareTests : InstrumentedTest {
|
|||
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
|
||||
aliceSession2.cryptoService().reRequestRoomKeyForEvent(receivedEvent.root)
|
||||
|
@ -257,11 +261,11 @@ class KeyShareTests : InstrumentedTest {
|
|||
outgoing?.results?.firstOrNull { it.userId == aliceSession.myUserId && it.fromDevice == aliceSession.sessionParams.deviceId }
|
||||
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
|
||||
fun test_reShareFromTheEarliestKnownIndexWithOwnVerifiedSession() = runCryptoTest(
|
||||
context(),
|
||||
|
@ -375,9 +379,6 @@ class KeyShareTests : InstrumentedTest {
|
|||
commonTestHelper.signOutAndClose(bobSession)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that we don't cancel a request to early on first forward if the index is not good enough
|
||||
*/
|
||||
@Test
|
||||
fun test_dontCancelToEarly() = runCryptoTest(
|
||||
context(),
|
||||
|
|
|
@ -26,7 +26,6 @@ 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.NoOpMatrixCallback
|
||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||
import org.matrix.android.sdk.api.session.crypto.MXCryptoError
|
||||
|
@ -71,6 +70,7 @@ class WithHeldTests : InstrumentedTest {
|
|||
val roomAlicePOV = aliceSession.getRoom(roomId)!!
|
||||
|
||||
val bobUnverifiedSession = testHelper.logIntoAccount(bobSession.myUserId, SessionTestParams(true))
|
||||
|
||||
// =============================
|
||||
// ACT
|
||||
// =============================
|
||||
|
@ -155,15 +155,13 @@ class WithHeldTests : InstrumentedTest {
|
|||
val aliceInterceptor = testHelper.getTestInterceptor(aliceSession)
|
||||
|
||||
// Simulate no OTK
|
||||
aliceInterceptor!!.addRule(
|
||||
MockOkHttpInterceptor.SimpleRule(
|
||||
"/keys/claim",
|
||||
200,
|
||||
"""
|
||||
aliceInterceptor!!.addRule(MockOkHttpInterceptor.SimpleRule(
|
||||
"/keys/claim",
|
||||
200,
|
||||
"""
|
||||
{ "one_time_keys" : {} }
|
||||
"""
|
||||
)
|
||||
)
|
||||
))
|
||||
Log.d("#TEST", "Recovery :${aliceSession.sessionParams.credentials.accessToken}")
|
||||
|
||||
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
|
||||
val sessionId = eventBobPOV!!.root.content.toModel<EncryptedEventContent>()!!.sessionId!!
|
||||
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.credentials.deviceId
|
||||
)
|
||||
val chainIndex = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSession.myUserId, bobSession.sessionParams.credentials.deviceId)
|
||||
|
||||
Assert.assertEquals("Alice should have marked bob's device for this session", 0, chainIndex)
|
||||
// Add a new device for bob
|
||||
|
@ -210,10 +205,7 @@ class WithHeldTests : InstrumentedTest {
|
|||
bobSecondSession.getRoom(testData.roomId)?.getTimelineEvent(secondMessageId) != null
|
||||
}
|
||||
|
||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(
|
||||
bobSecondSession.myUserId,
|
||||
bobSecondSession.sessionParams.credentials.deviceId
|
||||
)
|
||||
val chainIndex2 = aliceSession.cryptoService().getSharedWithInfo(testData.roomId, sessionId).getObject(bobSecondSession.myUserId, bobSecondSession.sessionParams.credentials.deviceId)
|
||||
|
||||
Assert.assertEquals("Alice should have marked bob's device for this session", 1, chainIndex2)
|
||||
|
||||
|
@ -243,8 +235,8 @@ class WithHeldTests : InstrumentedTest {
|
|||
cryptoTestHelper.initializeCrossSigning(bobSecondSession)
|
||||
|
||||
// Trust bob second device from Alice POV
|
||||
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId!!, NoOpMatrixCallback())
|
||||
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId!!, NoOpMatrixCallback())
|
||||
aliceSession.cryptoService().crossSigningService().trustDevice(bobSecondSession.sessionParams.deviceId)
|
||||
bobSecondSession.cryptoService().crossSigningService().trustDevice(aliceSession.sessionParams.deviceId)
|
||||
|
||||
var sessionId: String? = null
|
||||
// Check that the
|
||||
|
|
|
@ -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.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestData
|
||||
import org.matrix.android.sdk.internal.crypto.model.MXInboundMegolmSessionWrapper
|
||||
|
||||
/**
|
||||
* Data class to store result of [KeysBackupTestHelper.createKeysBackupScenarioWithPassword]
|
||||
*/
|
||||
internal data class KeysBackupScenarioData(
|
||||
val cryptoTestData: CryptoTestData,
|
||||
val aliceKeys: List<MXInboundMegolmSessionWrapper>,
|
||||
val aliceKeysCount: Int,
|
||||
val prepareKeysBackupDataResult: PrepareKeysBackupDataResult,
|
||||
val aliceSession2: Session
|
||||
) {
|
||||
|
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import androidx.test.filters.LargeTest
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.amshove.kluent.internal.assertFailsWith
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNotNull
|
||||
|
@ -30,19 +31,13 @@ import org.junit.runner.RunWith
|
|||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.android.sdk.InstrumentedTest
|
||||
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.session.crypto.crosssigning.DeviceTrustLevel
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupLastVersionResult
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.BackupUtils
|
||||
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.KeysBackupVersionTrust
|
||||
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.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.model.ImportRoomKeysResult
|
||||
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.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.waitFor
|
||||
import java.security.InvalidParameterException
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
|
@ -83,7 +77,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// - Check backup keys after having marked one as backed up
|
||||
val session = sessions[0]
|
||||
|
||||
cryptoStore.markBackupDoneForInboundGroupSessions(Collections.singletonList(session))
|
||||
cryptoStore.markBackupDoneForInboundGroupSessions(listOf(session))
|
||||
|
||||
assertEquals(sessionsCount, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||
assertEquals(1, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||
|
@ -118,9 +112,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(null, null)
|
||||
|
||||
assertEquals(MXCRYPTO_ALGORITHM_MEGOLM_BACKUP, megolmBackupCreationInfo.algorithm)
|
||||
assertNotNull(megolmBackupCreationInfo.authData.publicKey)
|
||||
|
@ -144,27 +136,20 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(null, null, it)
|
||||
}
|
||||
val megolmBackupCreationInfo =
|
||||
keysBackup.prepareKeysBackupVersion(null, null)
|
||||
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
||||
// Create the version
|
||||
val version = testHelper.waitForCallback<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
val version = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||
|
||||
// Backup must be enable now
|
||||
assertTrue(keysBackup.isEnabled())
|
||||
|
||||
// Check that it's signed with MSK
|
||||
val versionResult = testHelper.waitForCallback<KeysVersionResult?> {
|
||||
keysBackup.getVersion(version.version, it)
|
||||
}
|
||||
val trust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
keysBackup.getKeysBackupTrust(versionResult!!, it)
|
||||
}
|
||||
val versionResult = keysBackup.getVersion(version.version)
|
||||
val trust = keysBackup.getKeysBackupTrust(versionResult!!)
|
||||
|
||||
assertEquals("Should have 2 signatures", 2, trust.signatures.size)
|
||||
|
||||
|
@ -211,7 +196,6 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
assertEquals(2, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(false))
|
||||
assertEquals(0, cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true))
|
||||
|
||||
val stateObserver = StateObserver(keysBackup, latch, 5)
|
||||
|
||||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
@ -256,19 +240,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
assertEquals(2, nbOfKeys)
|
||||
|
||||
var lastBackedUpKeysProgress = 0
|
||||
|
||||
testHelper.waitForCallback<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
assertEquals(nbOfKeys, total)
|
||||
lastBackedUpKeysProgress = progress
|
||||
}
|
||||
}, it)
|
||||
testHelper.retryPeriodically {
|
||||
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
|
||||
}
|
||||
|
||||
assertEquals(nbOfKeys, lastBackedUpKeysProgress)
|
||||
|
||||
val backedUpKeys = cryptoTestData.firstSession.cryptoService().inboundGroupSessionsCount(true)
|
||||
|
||||
assertEquals("All keys must have been marked as backed up", nbOfKeys, backedUpKeys)
|
||||
|
@ -305,7 +280,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertNotNull(keyBackupData!!.sessionData)
|
||||
|
||||
// - Check pkDecryptionFromRecoveryKey() is able to create a OlmPkDecryption
|
||||
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey)
|
||||
val decryption = keysBackup.pkDecryptionFromRecoveryKey(keyBackupCreationInfo.recoveryKey.toBase58())
|
||||
assertNotNull(decryption)
|
||||
// - Check decryptKeyBackupData() returns stg
|
||||
val sessionData = keysBackup
|
||||
|
@ -313,7 +288,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keyBackupData,
|
||||
session.safeSessionId!!,
|
||||
cryptoTestData.roomId,
|
||||
decryption!!
|
||||
keyBackupCreationInfo.recoveryKey
|
||||
)
|
||||
assertNotNull(sessionData)
|
||||
// - Compare the decrypted megolm key with the original one
|
||||
|
@ -335,16 +310,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(null)
|
||||
|
||||
// - Restore the e2e backup from the homeserver
|
||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
|
||||
|
@ -401,7 +373,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// // Request is either sent or unsent
|
||||
// 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())
|
||||
|
||||
// - Trust the backup from the new device
|
||||
testHelper.waitForCallback<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersion(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
true,
|
||||
it
|
||||
true
|
||||
)
|
||||
}
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
@ -446,16 +415,17 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
val keysVersionResult = testData.aliceSession2.cryptoService()
|
||||
.keysBackupService()
|
||||
.getCurrentVersion()!!
|
||||
.toKeysVersionResult()
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
|
||||
.keysBackupService()
|
||||
.getKeysBackupTrust(keysVersionResult)
|
||||
|
||||
// - It must be trusted and must have 2 signatures now
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
|
@ -490,32 +460,32 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Trust the backup from the new device with the recovery key
|
||||
testHelper.waitForCallback<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
it
|
||||
)
|
||||
}
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey
|
||||
)
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
||||
// - 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())
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
.getCurrentVersion()!!
|
||||
.toKeysVersionResult()
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
|
||||
.keysBackupService()
|
||||
.getKeysBackupTrust(keysVersionResult)
|
||||
|
||||
// - It must be trusted and must have 2 signatures now
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
|
@ -548,13 +518,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - 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().keysBackupVersion!!,
|
||||
"Bad recovery key",
|
||||
it
|
||||
)
|
||||
}
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
BackupUtils.recoveryKeyFromPassphrase("Bad recovery key")!!,
|
||||
)
|
||||
|
||||
// - The new device must still see the previous backup as not trusted
|
||||
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
||||
|
@ -592,13 +559,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - Trust the backup from the new device with the password
|
||||
testHelper.waitForCallback<Unit> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
it
|
||||
)
|
||||
}
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password
|
||||
)
|
||||
|
||||
// Wait for backup state to be ReadyToBackUp
|
||||
keysBackupTestHelper.waitForKeysBackupToBeInState(testData.aliceSession2, KeysBackupState.ReadyToBackUp)
|
||||
|
@ -608,16 +572,16 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(testData.aliceSession2.cryptoService().keysBackupService().isEnabled())
|
||||
|
||||
// - Retrieve the last version from the server
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
val keysVersionResult = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
.getCurrentVersion()!!
|
||||
.toKeysVersionResult()
|
||||
|
||||
// - It must be the same
|
||||
assertEquals(testData.prepareKeysBackupDataResult.version, keysVersionResult!!.version)
|
||||
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().getKeysBackupTrust(keysVersionResult, it)
|
||||
}
|
||||
val keysBackupVersionTrust = testData.aliceSession2.cryptoService()
|
||||
.keysBackupService()
|
||||
.getKeysBackupTrust(keysVersionResult)
|
||||
|
||||
// - It must be trusted and must have 2 signatures now
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
|
@ -653,13 +617,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertEquals(KeysBackupState.NotTrusted, testData.aliceSession2.cryptoService().keysBackupService().getState())
|
||||
|
||||
// - 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().keysBackupVersion!!,
|
||||
badPassword,
|
||||
it
|
||||
)
|
||||
}
|
||||
testData.aliceSession2.cryptoService().keysBackupService().trustKeysBackupVersionWithPassphrase(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
badPassword,
|
||||
)
|
||||
|
||||
// - The new device must still see the previous backup as not trusted
|
||||
assertNotNull(testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion)
|
||||
|
@ -683,18 +644,15 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
// - Try to restore the e2e backup with a wrong recovery key
|
||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
||||
assertFailsWith<InvalidParameterException> {
|
||||
keysBackupService.restoreKeysWithRecoveryKey(
|
||||
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,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue(importRoomKeysResult is InvalidParameterException)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -714,20 +672,17 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// - Restore the e2e backup with the password
|
||||
val steps = ArrayList<StepProgressListener.Step>()
|
||||
|
||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
null,
|
||||
null,
|
||||
object : StepProgressListener {
|
||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||
steps.add(step)
|
||||
}
|
||||
},
|
||||
it
|
||||
)
|
||||
}
|
||||
val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeyBackupWithPassword(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
password,
|
||||
null,
|
||||
null,
|
||||
object : StepProgressListener {
|
||||
override fun onStepProgress(step: StepProgressListener.Step) {
|
||||
steps.add(step)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Check steps
|
||||
assertEquals(105, steps.size)
|
||||
|
@ -770,18 +725,15 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
// - Try to restore the e2e backup with a wrong password
|
||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
||||
assertFailsWith<InvalidParameterException> {
|
||||
keysBackupService.restoreKeyBackupWithPassword(
|
||||
keysBackupService.keysBackupVersion!!,
|
||||
wrongPassword,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
assertTrue(importRoomKeysResult is InvalidParameterException)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -799,16 +751,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
|
||||
|
||||
// - Restore the e2e backup with the recovery key.
|
||||
val importRoomKeysResult = testHelper.waitForCallback<ImportRoomKeysResult> {
|
||||
testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
val importRoomKeysResult = testData.aliceSession2.cryptoService().keysBackupService().restoreKeysWithRecoveryKey(
|
||||
testData.aliceSession2.cryptoService().keysBackupService().keysBackupVersion!!,
|
||||
testData.prepareKeysBackupDataResult.megolmBackupCreationInfo.recoveryKey,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
keysBackupTestHelper.checkRestoreSuccess(testData, importRoomKeysResult.totalNumberOfKeys, importRoomKeysResult.successfullyNumberOfImportedKeys)
|
||||
}
|
||||
|
@ -827,18 +776,15 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val keysBackupService = testData.aliceSession2.cryptoService().keysBackupService()
|
||||
|
||||
// - Try to restore the e2e backup with a password
|
||||
val importRoomKeysResult = testHelper.waitForCallbackError<ImportRoomKeysResult> {
|
||||
keysBackupService.restoreKeyBackupWithPassword(
|
||||
keysBackupService.keysBackupVersion!!,
|
||||
"password",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
it
|
||||
)
|
||||
}
|
||||
val importRoomKeysResult = keysBackupService.restoreKeyBackupWithPassword(
|
||||
keysBackupService.keysBackupVersion!!,
|
||||
"password",
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
|
||||
assertTrue(importRoomKeysResult is IllegalStateException)
|
||||
assertTrue(importRoomKeysResult.importedSessionInfo.size > 0)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -860,14 +806,10 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Get key backup version from the homeserver
|
||||
val keysVersionResult = testHelper.waitForCallback<KeysBackupLastVersionResult> {
|
||||
keysBackup.getCurrentVersion(it)
|
||||
}.toKeysVersionResult()
|
||||
val keysVersionResult = keysBackup.getCurrentVersion()!!.toKeysVersionResult()
|
||||
|
||||
// - Check the returned KeyBackupVersion is trusted
|
||||
val keysBackupVersionTrust = testHelper.waitForCallback<KeysBackupVersionTrust> {
|
||||
keysBackup.getKeysBackupTrust(keysVersionResult!!, it)
|
||||
}
|
||||
val keysBackupVersionTrust = keysBackup.getKeysBackupTrust(keysVersionResult!!)
|
||||
|
||||
assertNotNull(keysBackupVersionTrust)
|
||||
assertTrue(keysBackupVersionTrust.usable)
|
||||
|
@ -876,7 +818,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
val signature = keysBackupVersionTrust.signatures[0] as KeysBackupVersionTrustSignature.DeviceSignature
|
||||
assertTrue(signature.valid)
|
||||
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)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
@ -944,7 +886,9 @@ class KeysBackupTest : InstrumentedTest {
|
|||
(cryptoTestData.firstSession.cryptoService().keysBackupService() as DefaultKeysBackupService).store.resetBackupMarkers()
|
||||
|
||||
// - 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
|
||||
assertEquals(KeysBackupState.WrongBackUpVersion, keysBackup.getState())
|
||||
|
@ -980,11 +924,17 @@ class KeysBackupTest : InstrumentedTest {
|
|||
keysBackupTestHelper.prepareAndCreateKeysBackupData(keysBackup)
|
||||
|
||||
// Wait for keys backup to finish by asking again to backup keys.
|
||||
testHelper.waitForCallback<Unit> {
|
||||
keysBackup.backupAllGroupSessions(null, it)
|
||||
testHelper.retryPeriodically {
|
||||
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 aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
|
@ -1005,18 +955,16 @@ class KeysBackupTest : InstrumentedTest {
|
|||
|
||||
val stateObserver2 = StateObserver(keysBackup2)
|
||||
|
||||
testHelper.waitForCallbackError<Unit> { keysBackup2.backupAllGroupSessions(null, it) }
|
||||
testHelper.retryPeriodically {
|
||||
keysBackup2.getTotalNumbersOfKeys() == keysBackup2.getTotalNumbersOfBackedUpKeys()
|
||||
}
|
||||
|
||||
// Backup state must be NotTrusted
|
||||
assertEquals("Backup state must be NotTrusted", KeysBackupState.NotTrusted, keysBackup2.getState())
|
||||
assertFalse("Backup should not be enabled", keysBackup2.isEnabled())
|
||||
|
||||
// - Validate the old device from the new one
|
||||
aliceSession2.cryptoService().setDeviceVerification(
|
||||
DeviceTrustLevel(crossSigningVerified = false, locallyVerified = true),
|
||||
aliceSession2.myUserId,
|
||||
oldDeviceId
|
||||
)
|
||||
aliceSession2.cryptoService().verificationService().markedLocallyAsManuallyVerified(aliceSession2.myUserId, oldDeviceId)
|
||||
|
||||
// -> Backup should automatically enable on the new device
|
||||
suspendCancellableCoroutine<Unit> { continuation ->
|
||||
|
@ -1037,8 +985,13 @@ class KeysBackupTest : InstrumentedTest {
|
|||
// -> It must use the same backup version
|
||||
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
|
||||
|
@ -1070,7 +1023,7 @@ class KeysBackupTest : InstrumentedTest {
|
|||
assertTrue(keysBackup.isEnabled())
|
||||
|
||||
// Delete the backup
|
||||
testHelper.waitForCallback<Unit> { keysBackup.deleteBackup(keyBackupCreationInfo.version, it) }
|
||||
keysBackup.deleteBackup(keyBackupCreationInfo.version)
|
||||
|
||||
// Backup is now disabled
|
||||
assertFalse(keysBackup.isEnabled())
|
||||
|
|
|
@ -18,13 +18,10 @@ package org.matrix.android.sdk.internal.crypto.keysbackup
|
|||
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
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.crypto.keysbackup.KeysBackupService
|
||||
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.KeysVersion
|
||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupCreationInfo
|
||||
import org.matrix.android.sdk.common.CommonTestHelper
|
||||
import org.matrix.android.sdk.common.CryptoTestHelper
|
||||
import org.matrix.android.sdk.common.assertDictEquals
|
||||
|
@ -53,29 +50,22 @@ internal class KeysBackupTestHelper(
|
|||
|
||||
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 stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||
// val aliceKeys = cryptoStore.inboundGroupSessionsToBackup(100)
|
||||
|
||||
// - Do an e2e backup to the homeserver
|
||||
val prepareKeysBackupDataResult = prepareAndCreateKeysBackupData(keysBackup, password)
|
||||
|
||||
var lastProgress = 0
|
||||
var lastTotal = 0
|
||||
testHelper.waitForCallback<Unit> {
|
||||
keysBackup.backupAllGroupSessions(object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
lastProgress = progress
|
||||
lastTotal = total
|
||||
}
|
||||
}, it)
|
||||
testHelper.retryPeriodically {
|
||||
keysBackup.getTotalNumbersOfKeys() == keysBackup.getTotalNumbersOfBackedUpKeys()
|
||||
}
|
||||
val totalNumbersOfBackedUpKeys = cryptoTestData.firstSession.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||
|
||||
Assert.assertEquals(2, lastProgress)
|
||||
Assert.assertEquals(2, lastTotal)
|
||||
Assert.assertEquals(2, totalNumbersOfBackedUpKeys)
|
||||
|
||||
val aliceUserId = cryptoTestData.firstSession.myUserId
|
||||
|
||||
|
@ -83,19 +73,20 @@ internal class KeysBackupTestHelper(
|
|||
val aliceSession2 = testHelper.logIntoAccount(aliceUserId, KeysBackupTestConstants.defaultSessionParamsWithInitialSync)
|
||||
|
||||
// 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
|
||||
waitForKeysBackupToBeInState(aliceSession2, KeysBackupState.NotTrusted)
|
||||
|
||||
stateObserver.stopAndCheckStates(null)
|
||||
|
||||
return KeysBackupScenarioData(
|
||||
cryptoTestData,
|
||||
aliceKeys,
|
||||
val totalNumbersOfBackedUpKeysFromNewSession = aliceSession2.cryptoService().keysBackupService().getTotalNumbersOfBackedUpKeys()
|
||||
|
||||
return KeysBackupScenarioData(cryptoTestData,
|
||||
totalNumbersOfBackedUpKeysFromNewSession,
|
||||
prepareKeysBackupDataResult,
|
||||
aliceSession2
|
||||
)
|
||||
aliceSession2)
|
||||
}
|
||||
|
||||
suspend fun prepareAndCreateKeysBackupData(
|
||||
|
@ -104,18 +95,15 @@ internal class KeysBackupTestHelper(
|
|||
): PrepareKeysBackupDataResult {
|
||||
val stateObserver = StateObserver(keysBackup)
|
||||
|
||||
val megolmBackupCreationInfo = testHelper.waitForCallback<MegolmBackupCreationInfo> {
|
||||
keysBackup.prepareKeysBackupVersion(password, null, it)
|
||||
}
|
||||
val megolmBackupCreationInfo = keysBackup.prepareKeysBackupVersion(password, null)
|
||||
|
||||
Assert.assertNotNull(megolmBackupCreationInfo)
|
||||
|
||||
Assert.assertFalse("Key backup should not be enabled before creation", keysBackup.isEnabled())
|
||||
|
||||
// Create the version
|
||||
val keysVersion = testHelper.waitForCallback<KeysVersion> {
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo, it)
|
||||
}
|
||||
val keysVersion =
|
||||
keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
|
||||
|
||||
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(keys2)
|
||||
|
||||
|
@ -174,24 +162,27 @@ internal class KeysBackupTestHelper(
|
|||
* - The new device must have the same count of megolm keys
|
||||
* - Alice must have the same keys on both devices
|
||||
*/
|
||||
fun checkRestoreSuccess(
|
||||
suspend fun checkRestoreSuccess(
|
||||
testData: KeysBackupScenarioData,
|
||||
total: Int,
|
||||
imported: Int
|
||||
) {
|
||||
// - Imported keys number must be correct
|
||||
Assert.assertEquals(testData.aliceKeys.size, total)
|
||||
Assert.assertEquals(testData.aliceKeysCount, total)
|
||||
Assert.assertEquals(total, imported)
|
||||
|
||||
// - 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
|
||||
for (aliceKey1 in testData.aliceKeys) {
|
||||
val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
.getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
|
||||
Assert.assertNotNull(aliceKey2)
|
||||
assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
}
|
||||
// TODO can't access internals as we can switch from rust/kotlin
|
||||
// for (aliceKey1 in testData.aliceKeys) {
|
||||
// val aliceKey2 = (testData.aliceSession2.cryptoService().keysBackupService() as DefaultKeysBackupService).store
|
||||
// .getInboundGroupSession(aliceKey1.safeSessionId!!, aliceKey1.senderKey!!)
|
||||
// Assert.assertNotNull(aliceKey2)
|
||||
// assertKeysEquals(aliceKey1.exportKeys(), aliceKey2!!.exportKeys())
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,10 +18,9 @@ package org.matrix.android.sdk.internal.crypto.verification
|
|||
|
||||
import android.util.Log
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.runBlocking
|
||||
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.fail
|
||||
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.MXUsersDevicesMap
|
||||
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.VerificationMethod
|
||||
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.KeyVerificationStart
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.toValue
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
|
@ -55,82 +56,80 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
@Test
|
||||
fun test_aliceStartThenAliceCancel() = 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 bobTxCreatedLatch = CountDownLatch(1)
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
bobTxCreatedLatch.countDown()
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val txID = aliceVerificationService.beginKeyVerification(
|
||||
VerificationMethod.SAS,
|
||||
bobSession.myUserId,
|
||||
bobSession.cryptoService().getMyDevice().deviceId,
|
||||
null
|
||||
)
|
||||
assertNotNull("Alice should have a started transaction", txID)
|
||||
|
||||
val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
||||
|
||||
testHelper.await(bobTxCreatedLatch)
|
||||
bobVerificationService.removeListener(bobListener)
|
||||
|
||||
val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
|
||||
assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||
assertTrue(bobKeyTx is SASDefaultVerificationTransaction)
|
||||
assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||
assertTrue(aliceKeyTx is SASDefaultVerificationTransaction)
|
||||
assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||
|
||||
val aliceSasTx = aliceKeyTx as SASDefaultVerificationTransaction?
|
||||
val bobSasTx = bobKeyTx as SASDefaultVerificationTransaction?
|
||||
|
||||
assertEquals("Alice state should be started", VerificationTxState.Started, aliceSasTx!!.state)
|
||||
assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobSasTx!!.state)
|
||||
|
||||
// Let's cancel from alice side
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
|
||||
val bobListener2 = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if (tx.transactionId == txID) {
|
||||
val immutableState = (tx as SASDefaultVerificationTransaction).state
|
||||
if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
||||
cancelLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener2)
|
||||
|
||||
aliceSasTx.cancel(CancelCode.User)
|
||||
testHelper.await(cancelLatch)
|
||||
|
||||
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 = 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)
|
||||
|
||||
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))
|
||||
// TODO
|
||||
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
// cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||
// val aliceSession = cryptoTestData.firstSession
|
||||
// val bobSession = cryptoTestData.secondSession
|
||||
//
|
||||
// val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
// val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
//
|
||||
// val bobTxCreatedLatch = CountDownLatch(1)
|
||||
// val bobListener = object : VerificationService.Listener {
|
||||
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
// bobTxCreatedLatch.countDown()
|
||||
// }
|
||||
// }
|
||||
// bobVerificationService.addListener(bobListener)
|
||||
//
|
||||
// val bobDevice = bobSession.cryptoService().getMyCryptoDevice()
|
||||
//
|
||||
// aliceSession.cryptoService().downloadKeysIfNeeded(listOf(bobSession.myUserId), forceDownload = true)
|
||||
// val txID = aliceVerificationService.beginKeyVerification(bobSession.myUserId, bobDevice.deviceId)
|
||||
//
|
||||
// assertNotNull("Alice should have a started transaction", txID)
|
||||
//
|
||||
// val aliceKeyTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, txID!!)
|
||||
// assertNotNull("Alice should have a started transaction", aliceKeyTx)
|
||||
//
|
||||
// testHelper.await(bobTxCreatedLatch)
|
||||
// bobVerificationService.removeListener(bobListener)
|
||||
//
|
||||
// val bobKeyTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, txID)
|
||||
//
|
||||
// assertNotNull("Bob should have started verif transaction", bobKeyTx)
|
||||
// assertTrue(bobKeyTx is SasVerificationTransaction)
|
||||
// assertNotNull("Bob should have starting a SAS transaction", bobKeyTx)
|
||||
// assertTrue(aliceKeyTx is SasVerificationTransaction)
|
||||
// assertEquals("Alice and Bob have same transaction id", aliceKeyTx!!.transactionId, bobKeyTx!!.transactionId)
|
||||
//
|
||||
// assertEquals("Alice state should be started", VerificationTxState.OnStarted, aliceKeyTx.state)
|
||||
// assertEquals("Bob state should be started by alice", VerificationTxState.OnStarted, bobKeyTx.state)
|
||||
//
|
||||
// // Let's cancel from alice side
|
||||
// val cancelLatch = CountDownLatch(1)
|
||||
//
|
||||
// val bobListener2 = object : VerificationService.Listener {
|
||||
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
// if (tx.transactionId == txID) {
|
||||
// val immutableState = (tx as SasVerificationTransaction).state
|
||||
// if (immutableState is VerificationTxState.Cancelled && !immutableState.byMe) {
|
||||
// cancelLatch.countDown()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// bobVerificationService.addListener(bobListener2)
|
||||
//
|
||||
// aliceKeyTx.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)
|
||||
//
|
||||
// val aliceCancelState = aliceKeyTx.state as VerificationTxState.Cancelled
|
||||
// val bobCancelState = bobKeyTx.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)
|
||||
//
|
||||
// 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))
|
||||
}
|
||||
|
||||
@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 override fun onToDeviceEvent(event: Event?) {
|
||||
|
@ -171,16 +170,18 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
(tx as IncomingSasVerificationTransaction).performAccept()
|
||||
if (tx.state is VerificationTxState.SasStarted && tx is SasVerificationTransaction) {
|
||||
runBlocking {
|
||||
tx.acceptVerification()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, protocols = protocols)
|
||||
|
||||
|
@ -201,7 +202,7 @@ class SASTest : InstrumentedTest {
|
|||
val tid = "00000000"
|
||||
|
||||
// Bob should receive a cancel
|
||||
var canceledToDeviceEvent: Event? = null
|
||||
val canceledToDeviceEvent: Event? = null
|
||||
val cancelLatch = CountDownLatch(1)
|
||||
// TODO bobSession!!.dataHandler.addListener(object : MXEventListener() {
|
||||
// TODO override fun onToDeviceEvent(event: Event?) {
|
||||
|
@ -216,12 +217,11 @@ class SASTest : InstrumentedTest {
|
|||
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
|
||||
fakeBobStart(bobSession, aliceUserID, aliceDevice, tid, mac = mac)
|
||||
|
||||
testHelper.await(cancelLatch)
|
||||
|
||||
val cancelReq = canceledToDeviceEvent!!.content.toModel<KeyVerificationCancel>()!!
|
||||
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 aliceUserID = aliceSession.myUserId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyDevice().deviceId
|
||||
val aliceDevice = aliceSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
private fun fakeBobStart(
|
||||
private suspend fun fakeBobStart(
|
||||
bobSession: Session,
|
||||
aliceUserID: String?,
|
||||
aliceDevice: String?,
|
||||
tid: String,
|
||||
protocols: List<String> = SASDefaultVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||
hashes: List<String> = SASDefaultVerificationTransaction.KNOWN_HASHES,
|
||||
mac: List<String> = SASDefaultVerificationTransaction.KNOWN_MACS,
|
||||
codes: List<String> = SASDefaultVerificationTransaction.KNOWN_SHORT_CODES
|
||||
protocols: List<String> = SasVerificationTransaction.KNOWN_AGREEMENT_PROTOCOLS,
|
||||
hashes: List<String> = SasVerificationTransaction.KNOWN_HASHES,
|
||||
mac: List<String> = SasVerificationTransaction.KNOWN_MACS,
|
||||
codes: List<String> = SasVerificationTransaction.KNOWN_SHORT_CODES
|
||||
) {
|
||||
val startMessage = KeyVerificationStart(
|
||||
fromDevice = bobSession.cryptoService().getMyDevice().deviceId,
|
||||
fromDevice = bobSession.cryptoService().getMyCryptoDevice().deviceId,
|
||||
method = VerificationMethod.SAS.toValue(),
|
||||
transactionId = tid,
|
||||
keyAgreementProtocols = protocols,
|
||||
|
@ -307,29 +307,31 @@ class SASTest : InstrumentedTest {
|
|||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
|
||||
val aliceCreatedLatch = CountDownLatch(2)
|
||||
val aliceCancelledLatch = CountDownLatch(2)
|
||||
val createdTx = mutableListOf<SASDefaultVerificationTransaction>()
|
||||
val aliceCancelledLatch = CountDownLatch(1)
|
||||
val createdTx = mutableListOf<VerificationTransaction>()
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionCreated(tx: VerificationTransaction) {
|
||||
createdTx.add(tx as SASDefaultVerificationTransaction)
|
||||
createdTx.add(tx)
|
||||
aliceCreatedLatch.countDown()
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
// aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobUserId = bobSession!!.myUserId
|
||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
val bobDeviceId = bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
|
||||
testHelper.await(aliceCreatedLatch)
|
||||
testHelper.await(aliceCancelledLatch)
|
||||
// TODO
|
||||
// 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)
|
||||
}
|
||||
|
@ -337,191 +339,205 @@ class SASTest : InstrumentedTest {
|
|||
/**
|
||||
* Test that when alice starts a 'correct' request, bob agrees.
|
||||
*/
|
||||
@Test
|
||||
@Ignore("This test will be ignored until it is fixed")
|
||||
fun test_aliceAndBobAgreement() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
// @Test
|
||||
// @Ignore("This test will be ignored until it is fixed")
|
||||
// fun test_aliceAndBobAgreement() = 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 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
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
var accepted: ValidVerificationInfoAccept? = null
|
||||
var startReq: ValidVerificationInfoStart.SasVerificationInfoStart? = null
|
||||
|
||||
val aliceAcceptedLatch = CountDownLatch(1)
|
||||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.v("TEST", "== aliceTx state ${tx.state} => ${(tx as? OutgoingSasVerificationTransaction)?.uxState}")
|
||||
if ((tx as SASDefaultVerificationTransaction).state === VerificationTxState.OnAccepted) {
|
||||
val at = tx as SASDefaultVerificationTransaction
|
||||
accepted = at.accepted
|
||||
startReq = at.startReq
|
||||
aliceAcceptedLatch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
Log.v("TEST", "== bobTx state ${tx.state} => ${(tx as? IncomingSasVerificationTransaction)?.uxState}")
|
||||
if ((tx as IncomingSasVerificationTransaction).uxState === IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT) {
|
||||
bobVerificationService.removeListener(this)
|
||||
val at = tx as IncomingSasVerificationTransaction
|
||||
at.performAccept()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
val bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
testHelper.await(aliceAcceptedLatch)
|
||||
|
||||
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
|
||||
// fun test_aliceAndBobSASCode() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
// val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
// cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||
// val sasTestHelper = SasVerificationTestHelper(testHelper, cryptoTestHelper)
|
||||
// val aliceSession = cryptoTestData.firstSession
|
||||
// val bobSession = cryptoTestData.secondSession!!
|
||||
// val transactionId = sasTestHelper.requestVerificationAndWaitForReadyState(cryptoTestData, supportedMethods)
|
||||
//
|
||||
// val latch = CountDownLatch(2)
|
||||
// val aliceListener = object : VerificationService.Listener {
|
||||
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
// Timber.v("Alice transactionUpdated: ${tx.state}")
|
||||
// latch.countDown()
|
||||
// }
|
||||
// }
|
||||
// aliceSession.cryptoService().verificationService().addListener(aliceListener)
|
||||
// val bobListener = object : VerificationService.Listener {
|
||||
// override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
// Timber.v("Bob transactionUpdated: ${tx.state}")
|
||||
// latch.countDown()
|
||||
// }
|
||||
// }
|
||||
// bobSession.cryptoService().verificationService().addListener(bobListener)
|
||||
// aliceSession.cryptoService().verificationService().beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, transactionId)
|
||||
//
|
||||
// testHelper.await(latch)
|
||||
// val aliceTx =
|
||||
// aliceSession.cryptoService().verificationService().getExistingTransaction(bobSession.myUserId, transactionId) as SasVerificationTransaction
|
||||
// val bobTx = bobSession.cryptoService().verificationService().getExistingTransaction(aliceSession.myUserId, transactionId) as SasVerificationTransaction
|
||||
//
|
||||
// assertEquals("Should have same SAS", aliceTx.getDecimalCodeRepresentation(), bobTx.getDecimalCodeRepresentation())
|
||||
//
|
||||
// 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
|
||||
fun test_happyPath() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
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 bobSession = cryptoTestData.secondSession
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
|
||||
val aliceSASLatch = CountDownLatch(1)
|
||||
val verifiedLatch = CountDownLatch(2)
|
||||
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)
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
var acceptOnce = true
|
||||
var matchOnce = true
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
Timber.v("RequestUpdated pr=$pr")
|
||||
}
|
||||
|
||||
var matched = false
|
||||
var verified = false
|
||||
override fun transactionUpdated(tx: VerificationTransaction) {
|
||||
val uxState = (tx as IncomingSasVerificationTransaction).uxState
|
||||
Log.v("TEST", "== bobState ${uxState.name}")
|
||||
when (uxState) {
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
|
||||
if (acceptOnce) {
|
||||
acceptOnce = false
|
||||
tx.performAccept()
|
||||
Timber.v("Alice transactionUpdated: ${tx.state} on thread:${Thread.currentThread()}")
|
||||
if (tx !is SasVerificationTransaction) return
|
||||
when (tx.state) {
|
||||
VerificationTxState.SasShortCodeReady -> {
|
||||
if (!matched) {
|
||||
matched = true
|
||||
runBlocking {
|
||||
delay(500)
|
||||
tx.userHasVerifiedShortCode()
|
||||
}
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.SHOW_SAS -> {
|
||||
if (matchOnce) {
|
||||
matchOnce = false
|
||||
tx.userHasVerifiedShortCode()
|
||||
VerificationTxState.Verified -> {
|
||||
if (!verified) {
|
||||
verified = true
|
||||
verifiedLatch.countDown()
|
||||
}
|
||||
}
|
||||
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
|
||||
bobSASLatch.countDown()
|
||||
}
|
||||
else -> Unit
|
||||
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 bobDeviceId = bobSession.cryptoService().getMyDevice().deviceId
|
||||
aliceVerificationService.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId, null)
|
||||
testHelper.await(aliceSASLatch)
|
||||
testHelper.await(bobSASLatch)
|
||||
val bobDeviceId = runBlocking {
|
||||
bobSession.cryptoService().getMyCryptoDevice().deviceId
|
||||
}
|
||||
aliceVerificationService.startKeyVerification(VerificationMethod.SAS, bobUserId, transactionId)
|
||||
|
||||
Timber.v("Await after beginKey ${Thread.currentThread()}")
|
||||
testHelper.await(verifiedLatch)
|
||||
|
||||
// Assert that devices are verified
|
||||
val bobDeviceInfoFromAlicePOV: CryptoDeviceInfo? = aliceSession.cryptoService().getCryptoDeviceInfo(bobUserId, bobDeviceId)
|
||||
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("bob device should be verified from alice point of view", bobDeviceInfoFromAlicePOV!!.isVerified)
|
||||
|
@ -530,27 +546,21 @@ class SASTest : InstrumentedTest {
|
|||
@Test
|
||||
fun test_ConcurrentStart() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
|
||||
val cryptoTestData = cryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
|
||||
|
||||
cryptoTestData.initializeCrossSigning(cryptoTestHelper)
|
||||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession!!.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||
|
||||
val req = aliceVerificationService.requestKeyVerificationInDMs(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
bobSession.myUserId,
|
||||
cryptoTestData.roomId
|
||||
)
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
bobSession.myUserId,
|
||||
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")
|
||||
|
||||
|
@ -563,31 +573,27 @@ class SASTest : InstrumentedTest {
|
|||
bobVerificationService.readyPendingVerification(
|
||||
listOf(VerificationMethod.SAS, VerificationMethod.QR_CODE_SCAN, VerificationMethod.QR_CODE_SHOW),
|
||||
aliceSession.myUserId,
|
||||
requestID!!
|
||||
requestID
|
||||
)
|
||||
|
||||
// wait for alice to get the ready
|
||||
testHelper.retryPeriodically {
|
||||
val prAlicePOV = aliceVerificationService.getExistingVerificationRequests(bobSession.myUserId).firstOrNull()
|
||||
Log.v("TEST", "== prAlicePOV is $prAlicePOV")
|
||||
prAlicePOV?.transactionId == requestID && prAlicePOV?.isReady != null
|
||||
prAlicePOV?.transactionId == requestID && prAlicePOV.state == EVerificationState.Ready
|
||||
}
|
||||
|
||||
// Start concurrent!
|
||||
aliceVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
bobSession.myUserId,
|
||||
bobSession.sessionParams.deviceId!!
|
||||
aliceVerificationService.startKeyVerification(
|
||||
method = VerificationMethod.SAS,
|
||||
otherUserId = bobSession.myUserId,
|
||||
requestId = requestID,
|
||||
)
|
||||
|
||||
bobVerificationService.beginKeyVerificationInDMs(
|
||||
VerificationMethod.SAS,
|
||||
requestID!!,
|
||||
cryptoTestData.roomId,
|
||||
aliceSession.myUserId,
|
||||
aliceSession.sessionParams.deviceId!!
|
||||
bobVerificationService.startKeyVerification(
|
||||
method = VerificationMethod.SAS,
|
||||
otherUserId = aliceSession.myUserId,
|
||||
requestId = requestID,
|
||||
)
|
||||
|
||||
// we should reach SHOW SAS on both
|
||||
|
@ -595,15 +601,15 @@ class SASTest : InstrumentedTest {
|
|||
var bobPovTx: SasVerificationTransaction?
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
alicePovTx = aliceVerificationService.getExistingTransaction(bobSession.myUserId, requestID) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== alicePovTx is $alicePovTx")
|
||||
alicePovTx?.state == VerificationTxState.ShortCodeReady
|
||||
alicePovTx?.state == VerificationTxState.SasShortCodeReady
|
||||
}
|
||||
// wait for alice to get the ready
|
||||
testHelper.retryPeriodically {
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID!!) as? SasVerificationTransaction
|
||||
bobPovTx = bobVerificationService.getExistingTransaction(aliceSession.myUserId, requestID) as? SasVerificationTransaction
|
||||
Log.v("TEST", "== bobPovTx is $bobPovTx")
|
||||
bobPovTx?.state == VerificationTxState.ShortCodeReady
|
||||
bobPovTx?.state == VerificationTxState.SasShortCodeReady
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!!
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -17,6 +17,9 @@
|
|||
package org.matrix.android.sdk.internal.crypto.verification.qrcode
|
||||
|
||||
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.junit.FixMethodOrder
|
||||
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.registration.RegistrationFlowResponse
|
||||
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.VerificationEvent
|
||||
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
|
||||
|
@ -164,7 +169,6 @@ class VerificationTest : InstrumentedTest {
|
|||
val aliceSession = cryptoTestData.firstSession
|
||||
val bobSession = cryptoTestData.secondSession!!
|
||||
|
||||
testHelper.waitForCallback<Unit> { callback ->
|
||||
aliceSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -177,11 +181,9 @@ class VerificationTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, callback
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
testHelper.waitForCallback<Unit> { callback ->
|
||||
bobSession.cryptoService().crossSigningService()
|
||||
.initializeCrossSigning(
|
||||
object : UserInteractiveAuthInterceptor {
|
||||
|
@ -194,9 +196,8 @@ class VerificationTest : InstrumentedTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
}, callback
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val aliceVerificationService = aliceSession.cryptoService().verificationService()
|
||||
val bobVerificationService = bobSession.cryptoService().verificationService()
|
||||
|
@ -208,34 +209,35 @@ class VerificationTest : InstrumentedTest {
|
|||
val aliceListener = object : VerificationService.Listener {
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
// Step 4: Alice receive the ready request
|
||||
if (pr.isReady) {
|
||||
if (pr.state == EVerificationState.Ready) {
|
||||
aliceReadyPendingVerificationRequest = pr
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
aliceVerificationService.addListener(aliceListener)
|
||||
// aliceVerificationService.addListener(aliceListener)
|
||||
|
||||
val bobListener = object : VerificationService.Listener {
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// Step 2: Bob accepts the verification request
|
||||
bobVerificationService.readyPendingVerificationInDMs(
|
||||
bobSupportedMethods,
|
||||
aliceSession.myUserId,
|
||||
cryptoTestData.roomId,
|
||||
pr.transactionId!!
|
||||
)
|
||||
runBlocking {
|
||||
bobVerificationService.readyPendingVerification(
|
||||
bobSupportedMethods,
|
||||
aliceSession.myUserId,
|
||||
pr.transactionId
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun verificationRequestUpdated(pr: PendingVerificationRequest) {
|
||||
// Step 3: Bob is ready
|
||||
if (pr.isReady) {
|
||||
if (pr.state == EVerificationState.Ready) {
|
||||
bobReadyPendingVerificationRequest = pr
|
||||
latch.countDown()
|
||||
}
|
||||
}
|
||||
}
|
||||
bobVerificationService.addListener(bobListener)
|
||||
// bobVerificationService.addListener(bobListener)
|
||||
|
||||
val bobUserId = bobSession.myUserId
|
||||
// Step 1: Alice starts a verification request
|
||||
|
@ -243,15 +245,15 @@ class VerificationTest : InstrumentedTest {
|
|||
testHelper.await(latch)
|
||||
|
||||
aliceReadyPendingVerificationRequest!!.let { pr ->
|
||||
pr.isSasSupported() shouldBe expectedResultForAlice.sasIsSupported
|
||||
pr.otherCanShowQrCode() shouldBe expectedResultForAlice.otherCanShowQrCode
|
||||
pr.otherCanScanQrCode() shouldBe expectedResultForAlice.otherCanScanQrCode
|
||||
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
|
||||
pr.isSasSupported shouldBe expectedResultForBob.sasIsSupported
|
||||
pr.otherCanShowQrCode shouldBe expectedResultForBob.otherCanShowQrCode
|
||||
pr.otherCanScanQrCode shouldBe expectedResultForBob.otherCanScanQrCode
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -273,21 +275,42 @@ class VerificationTest : InstrumentedTest {
|
|||
val serviceOfVerifier = aliceSessionThatVerifies.cryptoService().verificationService()
|
||||
val serviceOfUserWhoReceivesCancellation = aliceSessionThatReceivesCanceledEvent.cryptoService().verificationService()
|
||||
|
||||
serviceOfVerifier.addListener(object : VerificationService.Listener {
|
||||
override fun verificationRequestCreated(pr: PendingVerificationRequest) {
|
||||
// Accept verification request
|
||||
serviceOfVerifier.readyPendingVerification(
|
||||
verificationMethods,
|
||||
pr.otherUserId,
|
||||
pr.transactionId!!,
|
||||
)
|
||||
var job: Job? = null
|
||||
job = async {
|
||||
serviceOfVerifier.requestEventFlow().collect {
|
||||
when (it) {
|
||||
is VerificationEvent.RequestAdded -> {
|
||||
val pr = it.request
|
||||
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,
|
||||
otherUserId = aliceSessionToVerify.myUserId,
|
||||
otherDevices = listOfNotNull(aliceSessionThatVerifies.sessionParams.deviceId, aliceSessionThatReceivesCanceledEvent.sessionParams.deviceId),
|
||||
)
|
||||
|
||||
testHelper.retryPeriodically {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ package org.matrix.android.sdk.api.session.crypto.verification
|
|||
interface IncomingSasVerificationTransaction : SasVerificationTransaction {
|
||||
val uxState: UxState
|
||||
|
||||
fun performAccept()
|
||||
override suspend fun acceptVerification()
|
||||
|
||||
enum class UxState {
|
||||
UNKNOWN,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 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
|
||||
* 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,
|
|
@ -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");
|
||||
* 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
|
||||
* 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,
|
|
@ -36,7 +36,7 @@ data class MessageVerificationRequestContent(
|
|||
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||
// Not parsed, but set after, using the eventId
|
||||
override val transactionId: String? = null
|
||||
) : MessageContent, VerificationInfoRequest {
|
||||
) : MessageContent, VerificationInfoRequest {
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
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.logger.LoggerTag
|
||||
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.toModel
|
||||
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.verification.DefaultVerificationService
|
||||
import org.matrix.android.sdk.internal.session.sync.ProgressReporter
|
||||
|
@ -37,15 +36,14 @@ import javax.inject.Inject
|
|||
private val loggerTag = LoggerTag("CryptoSyncHandler", LoggerTag.CRYPTO)
|
||||
|
||||
internal class CryptoSyncHandler @Inject constructor(
|
||||
private val cryptoService: DefaultCryptoService,
|
||||
private val cryptoService: Lazy<DefaultCryptoService>,
|
||||
private val verificationService: DefaultVerificationService
|
||||
) {
|
||||
|
||||
suspend fun handleToDevice(toDevice: ToDeviceSyncResponse, progressReporter: ProgressReporter? = null) {
|
||||
val total = toDevice.events?.size ?: 0
|
||||
toDevice.events
|
||||
?.filter { isSupportedToDevice(it) }
|
||||
?.forEachIndexed { index, event ->
|
||||
suspend fun handleToDevice(eventList: List<Event>, progressReporter: ProgressReporter? = null) {
|
||||
val total = eventList.size
|
||||
eventList.filter { isSupportedToDevice(it) }
|
||||
.forEachIndexed { index, event ->
|
||||
progressReporter?.reportProgress(index * 100F / total)
|
||||
// Decrypt event if necessary
|
||||
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}")
|
||||
} else {
|
||||
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.
|
||||
*
|
||||
|
@ -98,12 +92,12 @@ internal class CryptoSyncHandler @Inject constructor(
|
|||
if (event.getClearType() == EventType.ENCRYPTED) {
|
||||
var result: MXEventDecryptionResult? = null
|
||||
try {
|
||||
result = cryptoService.decryptEvent(event, timelineId ?: "")
|
||||
result = cryptoService.get().decryptEvent(event, timelineId ?: "")
|
||||
} catch (exception: MXCryptoError) {
|
||||
event.mCryptoError = (exception as? MXCryptoError.Base)?.errorType // setCryptoError(exception.cryptoError)
|
||||
val senderKey = event.content.toModel<OlmEventContent>()?.senderKey ?: "<unknown sender key>"
|
||||
// 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
|
||||
}?.deviceId ?: senderKey
|
||||
Timber.e("## CRYPTO | Failed to decrypt to device event from ${event.senderId}|$deviceId reason:<${event.mCryptoError ?: exception}>")
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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.CryptoDeviceInfo
|
||||
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.IncomingRoomKeyRequest
|
||||
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.RoomMemberContent
|
||||
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.ToDeviceSyncResponse
|
||||
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.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.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.rest.KeysUploadBody
|
||||
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.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.StreamEventsManager
|
||||
import org.matrix.android.sdk.internal.session.room.membership.LoadRoomMembersTask
|
||||
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.session.sync.handler.CryptoSyncHandler
|
||||
import org.matrix.android.sdk.internal.task.launchToCallback
|
||||
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||
import org.matrix.android.sdk.internal.util.time.Clock
|
||||
|
@ -181,18 +182,18 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
private val loadRoomMembersTask: LoadRoomMembersTask,
|
||||
private val cryptoSessionInfoProvider: CryptoSessionInfoProvider,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val taskExecutor: TaskExecutor,
|
||||
private val cryptoCoroutineScope: CoroutineScope,
|
||||
private val eventDecryptor: EventDecryptor,
|
||||
private val verificationMessageProcessor: VerificationMessageProcessor,
|
||||
private val liveEventManager: Lazy<StreamEventsManager>,
|
||||
private val unrequestedForwardManager: UnRequestedForwardManager,
|
||||
private val cryptoSyncHandler: CryptoSyncHandler,
|
||||
) : CryptoService {
|
||||
|
||||
private val isStarting = AtomicBoolean(false)
|
||||
private val isStarted = AtomicBoolean(false)
|
||||
|
||||
fun onStateEvent(roomId: String, event: Event) {
|
||||
override fun onStateEvent(roomId: String, event: Event) {
|
||||
when (event.type) {
|
||||
EventType.STATE_ROOM_ENCRYPTION -> onRoomEncryptionEvent(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
|
||||
if (event.isStateEvent()) {
|
||||
when (event.type) {
|
||||
|
@ -214,7 +215,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
if (!isInitialSync) {
|
||||
if (event.type != null && verificationMessageProcessor.shouldProcess(event.type)) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.dmVerif) {
|
||||
verificationMessageProcessor.process(event)
|
||||
verificationMessageProcessor.process(roomId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -222,65 +223,44 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
|
||||
// val gossipingBuffer = mutableListOf<Event>()
|
||||
|
||||
override fun setDeviceName(deviceId: String, deviceName: String, callback: MatrixCallback<Unit>) {
|
||||
override suspend fun setDeviceName(deviceId: String, deviceName: String) {
|
||||
setDeviceNameTask
|
||||
.configureWith(SetDeviceNameTask.Params(deviceId, deviceName)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<Unit> {
|
||||
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)
|
||||
.execute(SetDeviceNameTask.Params(deviceId, deviceName))
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
downloadKeys(listOf(userId), true)
|
||||
}
|
||||
}
|
||||
|
||||
override fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor, callback: MatrixCallback<Unit>) {
|
||||
deleteDeviceTask
|
||||
.configureWith(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
override suspend fun deleteDevice(deviceId: String, userInteractiveAuthInterceptor: UserInteractiveAuthInterceptor) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
deleteDeviceTask
|
||||
.execute(DeleteDeviceTask.Params(deviceId, userInteractiveAuthInterceptor, null))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCryptoVersion(context: Context, longFormat: Boolean): String {
|
||||
return if (longFormat) olmManager.getDetailedVersion(context) else olmManager.version
|
||||
}
|
||||
|
||||
override fun getMyDevice(): CryptoDeviceInfo {
|
||||
override suspend fun getMyCryptoDevice(): CryptoDeviceInfo {
|
||||
return myDeviceInfoHolder.get().myDevice
|
||||
}
|
||||
|
||||
override fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>) {
|
||||
getDevicesTask
|
||||
.configureWith {
|
||||
// this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<DevicesListResponse> {
|
||||
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 suspend fun fetchDevicesList(): List<DeviceInfo> {
|
||||
val data = getDevicesTask
|
||||
.execute(Unit)
|
||||
cryptoStore.saveMyDevicesInfo(data.devices.orEmpty())
|
||||
return data.devices.orEmpty()
|
||||
}
|
||||
|
||||
override fun getMyDevicesInfoLive(): LiveData<List<DeviceInfo>> {
|
||||
return cryptoStore.getLiveMyDevicesInfo()
|
||||
}
|
||||
|
||||
override suspend fun fetchDeviceInfo(deviceId: String): DeviceInfo {
|
||||
return getDeviceInfoTask.execute(GetDeviceInfoTask.Params(deviceId))
|
||||
}
|
||||
|
||||
override fun getMyDevicesInfoLive(deviceId: String): LiveData<Optional<DeviceInfo>> {
|
||||
return cryptoStore.getLiveMyDevicesInfo(deviceId)
|
||||
}
|
||||
|
@ -289,8 +269,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return cryptoStore.getMyDevicesInfo()
|
||||
}
|
||||
|
||||
override fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||
return cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||
override suspend fun inboundGroupSessionsCount(onlyBackedUp: Boolean): Int {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.inboundGroupSessionsCount(onlyBackedUp)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,7 +290,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*
|
||||
* @return true if the crypto is started
|
||||
*/
|
||||
fun isStarted(): Boolean {
|
||||
override fun isStarted(): Boolean {
|
||||
return isStarted.get()
|
||||
}
|
||||
|
||||
|
@ -328,14 +310,12 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* devices.
|
||||
*
|
||||
*/
|
||||
fun start() {
|
||||
override fun start() {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
internalStart()
|
||||
}
|
||||
// Just update
|
||||
fetchDevicesList(NoOpMatrixCallback())
|
||||
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
tryOrNull("Failed to update device list on start") {
|
||||
fetchDevicesList()
|
||||
}
|
||||
cryptoStore.tidyUpDataBase()
|
||||
}
|
||||
}
|
||||
|
@ -387,6 +367,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return
|
||||
}
|
||||
isStarting.set(true)
|
||||
ensureDevice()
|
||||
|
||||
// Open the store
|
||||
cryptoStore.open()
|
||||
|
@ -398,7 +379,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
/**
|
||||
* Close the crypto.
|
||||
*/
|
||||
fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||
override fun close() = runBlocking(coroutineDispatchers.crypto) {
|
||||
cryptoCoroutineScope.coroutineContext.cancelChildren(CancellationException("Closing crypto module"))
|
||||
incomingKeyRequestManager.close()
|
||||
outgoingKeyRequestManager.close()
|
||||
|
@ -427,80 +408,84 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*
|
||||
* @param syncResponse the syncResponse
|
||||
*/
|
||||
fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching {
|
||||
if (syncResponse.deviceLists != null) {
|
||||
deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||
}
|
||||
if (syncResponse.deviceOneTimeKeysCount != null) {
|
||||
val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
||||
oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
||||
}
|
||||
override suspend fun onSyncCompleted(syncResponse: SyncResponse) {
|
||||
// if (syncResponse.deviceLists != null) {
|
||||
// deviceListManager.handleDeviceListsChanges(syncResponse.deviceLists.changed, syncResponse.deviceLists.left)
|
||||
// }
|
||||
// if (syncResponse.deviceOneTimeKeysCount != null) {
|
||||
// val currentCount = syncResponse.deviceOneTimeKeysCount.signedCurve25519 ?: 0
|
||||
// oneTimeKeysUploader.updateOneTimeKeyCount(currentCount)
|
||||
// }
|
||||
|
||||
// unwedge if needed
|
||||
try {
|
||||
eventDecryptor.unwedgeDevicesIfNeeded()
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
|
||||
}
|
||||
// unwedge if needed
|
||||
try {
|
||||
eventDecryptor.unwedgeDevicesIfNeeded()
|
||||
} catch (failure: Throwable) {
|
||||
Timber.tag(loggerTag.value).w("unwedgeDevicesIfNeeded failed")
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
// 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
|
||||
// that there are no pending to_device
|
||||
val toDevices = syncResponse.toDevice?.events.orEmpty()
|
||||
if (isStarted() && toDevices.isEmpty()) {
|
||||
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||
deviceListManager.refreshOutdatedDeviceLists()
|
||||
// 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 (syncResponse.deviceUnusedFallbackKeyTypes != null &&
|
||||
// Generate a fallback key only if the server does not already have an unused fallback key.
|
||||
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||
oneTimeKeysUploader.needsNewFallback()
|
||||
}
|
||||
// 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
|
||||
// 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
|
||||
// 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.
|
||||
// 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
|
||||
// that there are no pending to_device
|
||||
val toDevices = syncResponse.toDevice?.events.orEmpty()
|
||||
if (isStarted() && toDevices.isEmpty()) {
|
||||
// Make sure we process to-device messages before generating new one-time-keys #2782
|
||||
deviceListManager.refreshOutdatedDeviceLists()
|
||||
// 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 (syncResponse.deviceUnusedFallbackKeyTypes != null &&
|
||||
// Generate a fallback key only if the server does not already have an unused fallback key.
|
||||
!syncResponse.deviceUnusedFallbackKeyTypes.contains(KEY_SIGNED_CURVE_25519_TYPE)) {
|
||||
oneTimeKeysUploader.needsNewFallback()
|
||||
}
|
||||
|
||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||
}
|
||||
oneTimeKeysUploader.maybeUploadOneTimeKeys()
|
||||
}
|
||||
|
||||
// Process pending key requests
|
||||
try {
|
||||
if (toDevices.isEmpty()) {
|
||||
// this is not blocking
|
||||
outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
|
||||
} else {
|
||||
Timber.tag(loggerTag.value)
|
||||
.w("Don't process key requests yet as there might be more to_device to catchup")
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
// just for safety but should not throw
|
||||
Timber.tag(loggerTag.value).w("failed to process pending request")
|
||||
}
|
||||
// Process pending key requests
|
||||
try {
|
||||
if (toDevices.isEmpty()) {
|
||||
// this is not blocking
|
||||
outgoingKeyRequestManager.requireProcessAllPendingKeyRequests()
|
||||
} else {
|
||||
Timber.tag(loggerTag.value)
|
||||
.w("Don't process key requests yet as there might be more to_device to catchup")
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
// just for safety but should not throw
|
||||
Timber.tag(loggerTag.value).w("failed to process pending request")
|
||||
}
|
||||
|
||||
try {
|
||||
incomingKeyRequestManager.processIncomingRequests()
|
||||
} catch (failure: Throwable) {
|
||||
// just for safety but should not throw
|
||||
Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
|
||||
}
|
||||
try {
|
||||
incomingKeyRequestManager.processIncomingRequests()
|
||||
} catch (failure: Throwable) {
|
||||
// just for safety but should not throw
|
||||
Timber.tag(loggerTag.value).w("failed to process incoming room key requests")
|
||||
}
|
||||
|
||||
unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events ->
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
events.forEach {
|
||||
onRoomKeyEvent(it, true)
|
||||
}
|
||||
}
|
||||
unrequestedForwardManager.postSyncProcessParkedKeysIfNeeded(clock.epochMillis()) { events ->
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
events.forEach {
|
||||
onRoomKeyEvent(it, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun logDbUsageInfo() {
|
||||
//
|
||||
}
|
||||
|
||||
override suspend fun setRoomUnBlacklistUnverifiedDevices(roomId: String) {
|
||||
cryptoStore.blockUnverifiedDevicesInRoom(roomId, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a device by curve25519 identity key.
|
||||
*
|
||||
|
@ -508,11 +493,18 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @param algorithm the encryption algorithm.
|
||||
* @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) {
|
||||
// We only deal in olm keys
|
||||
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 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()) {
|
||||
cryptoStore.getUserDevice(userId, deviceId)
|
||||
withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.getUserDevice(userId, deviceId)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||
getDeviceInfoTask
|
||||
.configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
// override fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>) {
|
||||
// getDeviceInfoTask
|
||||
// .configureWith(GetDeviceInfoTask.Params(deviceId)) {
|
||||
// this.executionThread = TaskThread.CRYPTO
|
||||
// this.callback = callback
|
||||
// }
|
||||
// .executeBy(taskExecutor)
|
||||
// }
|
||||
|
||||
override fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo> {
|
||||
return cryptoStore.getUserDeviceList(userId).orEmpty()
|
||||
}
|
||||
//
|
||||
// override fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>> {
|
||||
// return cryptoStore.getUserDeviceListFlow(userId)
|
||||
// }
|
||||
|
||||
override fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>> {
|
||||
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 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
|
||||
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 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)
|
||||
}
|
||||
|
||||
|
@ -684,8 +682,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
/**
|
||||
* @return the stored device keys for a user.
|
||||
*/
|
||||
override fun getUserDevices(userId: String): MutableList<CryptoDeviceInfo> {
|
||||
return cryptoStore.getUserDevices(userId)?.values?.toMutableList() ?: ArrayList()
|
||||
override suspend fun getUserDevices(userId: String): List<CryptoDeviceInfo> {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.getUserDevices(userId)?.values?.toList().orEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun isEncryptionEnabledForInvitedUser(): Boolean {
|
||||
|
@ -716,14 +716,13 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @param roomId the room identifier the event will be sent.
|
||||
* @param callback the asynchronous callback
|
||||
*/
|
||||
override fun encryptEventContent(
|
||||
override suspend fun encryptEventContent(
|
||||
eventContent: Content,
|
||||
eventType: String,
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>
|
||||
) {
|
||||
): MXEncryptEventContentResult {
|
||||
// moved to crypto scope to have uptodate values
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
return withContext(coroutineDispatchers.crypto) {
|
||||
val userIds = getRoomUserIds(roomId)
|
||||
var alg = roomEncryptorsStore.get(roomId)
|
||||
if (alg == null) {
|
||||
|
@ -738,11 +737,9 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
if (safeAlgorithm != null) {
|
||||
val t0 = clock.epochMillis()
|
||||
Timber.tag(loggerTag.value).v("encryptEventContent() starts")
|
||||
runCatching {
|
||||
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
|
||||
MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||
}.foldToCallback(callback)
|
||||
val content = safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
|
||||
Timber.tag(loggerTag.value).v("## CRYPTO | encryptEventContent() : succeeds after ${clock.epochMillis() - t0} ms")
|
||||
return@withContext MXEncryptEventContentResult(content, EventType.ENCRYPTED)
|
||||
} else {
|
||||
val algorithm = getEncryptionAlgorithm(roomId)
|
||||
val reason = String.format(
|
||||
|
@ -750,7 +747,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_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)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -858,7 +844,7 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
* @param event the key event.
|
||||
* @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
|
||||
Timber.tag(loggerTag.value)
|
||||
.i("onRoomKeyEvent(f:$acceptUnrequested) from: ${event.senderId} type<${event.getClearType()}> , session<${roomKeyContent.sessionId}>")
|
||||
|
@ -914,19 +900,27 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
): Boolean {
|
||||
return when (secretName) {
|
||||
MASTER_KEY_SSSS_NAME -> {
|
||||
crossSigningService.onSecretMSKGossip(secretValue)
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
crossSigningService.onSecretMSKGossip(secretValue)
|
||||
}
|
||||
true
|
||||
}
|
||||
SELF_SIGNING_KEY_SSSS_NAME -> {
|
||||
crossSigningService.onSecretSSKGossip(secretValue)
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
crossSigningService.onSecretSSKGossip(secretValue)
|
||||
}
|
||||
true
|
||||
}
|
||||
USER_SIGNING_KEY_SSSS_NAME -> {
|
||||
crossSigningService.onSecretUSKGossip(secretValue)
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
crossSigningService.onSecretUSKGossip(secretValue)
|
||||
}
|
||||
true
|
||||
}
|
||||
KEYBACKUP_SECRET_SSSS_NAME -> {
|
||||
keysBackupService.onSecretKeyGossip(secretValue)
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
keysBackupService.onSecretKeyGossip(secretValue)
|
||||
}
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
@ -1016,19 +1010,39 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
}
|
||||
// Prepare the device keys data to send
|
||||
// Sign it
|
||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
|
||||
var rest = getMyDevice().toRest()
|
||||
val myCryptoDevice = getMyCryptoDevice()
|
||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myCryptoDevice.signalableJSONDictionary())
|
||||
var rest = myCryptoDevice.toRest()
|
||||
|
||||
rest = rest.copy(
|
||||
signatures = objectSigner.signObject(canonicalJson)
|
||||
)
|
||||
|
||||
val uploadDeviceKeysParams = UploadKeysTask.Params(rest, null, null)
|
||||
val keyUploadBody = KeysUploadBody(
|
||||
deviceKeys = rest,
|
||||
)
|
||||
val uploadDeviceKeysParams = UploadKeysTask.Params(keyUploadBody)
|
||||
uploadKeysTask.execute(uploadDeviceKeysParams)
|
||||
|
||||
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.
|
||||
*
|
||||
|
@ -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
|
||||
* messages to unverified devices.
|
||||
|
@ -1214,11 +1244,11 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
*
|
||||
* @param event the event to decrypt again.
|
||||
*/
|
||||
override fun reRequestRoomKeyForEvent(event: Event) {
|
||||
override suspend fun reRequestRoomKeyForEvent(event: Event) {
|
||||
outgoingKeyRequestManager.requestKeyForEvent(event, true)
|
||||
}
|
||||
|
||||
override fun requestRoomKeyForEvent(event: Event) {
|
||||
suspend fun requestRoomKeyForEvent(event: Event) {
|
||||
outgoingKeyRequestManager.requestKeyForEvent(event, false)
|
||||
}
|
||||
|
||||
|
@ -1264,12 +1294,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return unknownDevices
|
||||
}
|
||||
|
||||
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<CryptoDeviceInfo>>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
runCatching {
|
||||
deviceListManager.downloadKeys(userIds, forceDownload)
|
||||
}.foldToCallback(callback)
|
||||
}
|
||||
suspend fun downloadKeys(userIds: List<String>, forceDownload: Boolean): MXUsersDevicesMap<CryptoDeviceInfo> {
|
||||
return deviceListManager.downloadKeys(userIds, forceDownload)
|
||||
}
|
||||
|
||||
override fun addNewSessionListener(newSessionListener: NewSessionListener) {
|
||||
|
@ -1333,8 +1359,8 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
return cryptoStore.getWithHeldMegolmSession(roomId, sessionId)
|
||||
}
|
||||
|
||||
override fun prepareToEncrypt(roomId: String, callback: MatrixCallback<Unit>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
override suspend fun prepareToEncrypt(roomId: String) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Timber.tag(loggerTag.value).d("prepareToEncrypt() roomId:$roomId Check room members up to date")
|
||||
// Ensure to load all room members
|
||||
try {
|
||||
|
@ -1354,19 +1380,10 @@ internal class DefaultCryptoService @Inject constructor(
|
|||
if (alg == null) {
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON, MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||
Timber.tag(loggerTag.value).e("prepareToEncrypt() : $reason")
|
||||
callback.onFailure(IllegalArgumentException("Missing algorithm"))
|
||||
return@launch
|
||||
throw IllegalArgumentException("Missing algorithm")
|
||||
}
|
||||
|
||||
runCatching {
|
||||
(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
|
||||
* ========================================================================================== */
|
||||
|
|
|
@ -61,7 +61,7 @@ internal class MyDeviceInfoHolder @Inject constructor(
|
|||
// myDevice.trustLevel = DeviceTrustLevel(crossSigned, true)
|
||||
|
||||
myDevice = CryptoDeviceInfo(
|
||||
credentials.deviceId!!,
|
||||
credentials.deviceId,
|
||||
credentials.userId,
|
||||
keys = keys,
|
||||
algorithms = MXCryptoAlgorithms.supportedAlgorithms(),
|
|
@ -19,6 +19,7 @@ package org.matrix.android.sdk.internal.crypto
|
|||
import android.content.Context
|
||||
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.rest.KeysUploadBody
|
||||
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.session.SessionScope
|
||||
|
@ -138,7 +139,7 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||
|
||||
private suspend fun fetchOtkCount(): Int? {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -227,9 +228,11 @@ internal class OneTimeKeysUploader @Inject constructor(
|
|||
// For now, we set the device id explicitly, as we may not be using the
|
||||
// same one as used in login.
|
||||
val uploadParams = UploadKeysTask.Params(
|
||||
deviceKeys = null,
|
||||
oneTimeKeys = oneTimeJson,
|
||||
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
|
||||
KeysUploadBody(
|
||||
deviceKeys = null,
|
||||
oneTimeKeys = oneTimeJson,
|
||||
fallbackKeys = fallbackJson.takeIf { fallbackJson.isNotEmpty() }
|
||||
)
|
||||
)
|
||||
return uploadKeysTask.executeRetry(uploadParams, 3)
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
* 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) {
|
||||
MXCRYPTO_ALGORITHM_MEGOLM -> megolmDecryptionFactory.create().apply {
|
||||
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
|
||||
newSessionListeners.toList().forEach {
|
||||
try {
|
||||
it.onNewSession(roomId, senderKey, sessionId)
|
||||
it.onNewSession(roomId, sessionId)
|
||||
} catch (ignore: Throwable) {
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
* 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.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.keysbackup.extractCurveKeyFromRecoveryKey
|
||||
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.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.content.SecretSendEventContent
|
||||
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.MessageEncrypter
|
||||
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
|
||||
SELF_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.selfSigned
|
||||
USER_SIGNING_KEY_SSSS_NAME -> cryptoStore.getCrossSigningPrivateKeys()?.user
|
||||
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey
|
||||
?.let {
|
||||
extractCurveKeyFromRecoveryKey(it)?.toBase64NoPadding()
|
||||
}
|
||||
KEYBACKUP_SECRET_SSSS_NAME -> cryptoStore.getKeyBackupRecoveryKeyInfo()?.recoveryKey?.toBase64()
|
||||
else -> null
|
||||
}
|
||||
if (secretValue == null) {
|
||||
|
@ -248,7 +243,7 @@ internal class SecretShareManager @Inject constructor(
|
|||
)
|
||||
try {
|
||||
withContext(coroutineDispatchers.io) {
|
||||
sendToDeviceTask.executeRetry(params, 3)
|
||||
sendToDeviceTask.execute(params)
|
||||
}
|
||||
Timber.tag(loggerTag.value)
|
||||
.d("Secret request sent for $secretName to ${cryptoDeviceInfo.shortDebugString()}")
|
|
@ -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");
|
||||
* 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
|
||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
|
||||
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim.map)
|
||||
val oneTimeKeys = withContext(coroutineDispatchers.io) {
|
||||
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
|
||||
|
|
|
@ -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");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -57,6 +57,7 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
|||
progressListener: ProgressListener?
|
||||
): ImportRoomKeysResult {
|
||||
val t0 = clock.epochMillis()
|
||||
val importedSession = mutableMapOf<String, MutableMap<String, MutableList<String>>>()
|
||||
|
||||
val totalNumbersOfKeys = megolmSessionsData.size
|
||||
var lastProgress = 0
|
||||
|
@ -70,18 +71,23 @@ internal class MegolmSessionDataImporter @Inject constructor(
|
|||
|
||||
if (null != decrypting) {
|
||||
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")
|
||||
|
||||
importedSession.getOrPut(roomId) { mutableMapOf() }
|
||||
.getOrPut(senderKey) { mutableListOf() }
|
||||
.add(sessionId)
|
||||
totalNumbersOfImportedKeys++
|
||||
|
||||
// cancel any outstanding room key requests for this session
|
||||
|
||||
Timber.tag(loggerTag.value).d("Imported megolm session $sessionId from backup=$fromBackup in ${megolmSessionData.roomId}")
|
||||
outgoingKeyRequestManager.postCancelRequestForSessionIfNeeded(
|
||||
megolmSessionData.sessionId ?: "",
|
||||
megolmSessionData.roomId ?: "",
|
||||
megolmSessionData.senderKey ?: "",
|
||||
sessionId,
|
||||
roomId,
|
||||
senderKey,
|
||||
tryOrNull {
|
||||
olmInboundGroupSessionWrappers
|
||||
.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
|
||||
when (decrypting) {
|
||||
is MXMegolmDecryption -> {
|
||||
decrypting.onNewSession(megolmSessionData.roomId, megolmSessionData.senderKey!!, sessionId!!)
|
||||
decrypting.onNewSession(megolmSessionData.roomId, senderKey, sessionId)
|
||||
}
|
||||
}
|
||||
} 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)")
|
||||
|
||||
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys)
|
||||
return ImportRoomKeysResult(totalNumbersOfKeys, totalNumbersOfImportedKeys, importedSession)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
* 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
|
||||
) {
|
||||
|
||||
fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||
suspend fun handle(trustLevel: DeviceTrustLevel, userId: String, deviceId: String) {
|
||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
||||
|
||||
// Sanity check
|
||||
|
|
|
@ -43,5 +43,5 @@ internal interface IMXDecrypting {
|
|||
* @param defaultKeysBackupService 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) {}
|
||||
}
|
||||
|
|
|
@ -189,7 +189,7 @@ internal class MXMegolmDecryption(
|
|||
* @param defaultKeysBackupService the keys backup service
|
||||
* @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()})")
|
||||
var exportFormat = false
|
||||
val roomKeyContent = event.getDecryptedContent()?.toModel<RoomKeyContent>() ?: return
|
||||
|
@ -360,6 +360,6 @@ internal class MXMegolmDecryption(
|
|||
*/
|
||||
fun onNewSession(roomId: String?, senderKey: String, sessionId: String) {
|
||||
Timber.tag(loggerTag.value).v("ON NEW SESSION $sessionId - $senderKey")
|
||||
newSessionListener?.onNewSession(roomId, senderKey, sessionId)
|
||||
newSessionListener?.onNewSession(roomId, sessionId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -184,7 +184,9 @@ internal class MXMegolmEncryption(
|
|||
trusted = true
|
||||
)
|
||||
|
||||
defaultKeysBackupService.maybeBackupKeys()
|
||||
cryptoCoroutineScope.launch {
|
||||
defaultKeysBackupService.maybeBackupKeys()
|
||||
}
|
||||
|
||||
return MXOutboundSessionInfo(
|
||||
sessionId = sessionId,
|
||||
|
|
|
@ -21,7 +21,8 @@ import androidx.work.BackoffPolicy
|
|||
import androidx.work.ExistingWorkPolicy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
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.auth.UserInteractiveAuthInterceptor
|
||||
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.isVerified
|
||||
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.fromBase64
|
||||
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.session.SessionScope
|
||||
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.logLimit
|
||||
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?
|
||||
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
setUserKeysAsTrusted(myUserId, checkUserTrust(myUserId).isVerified())
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Mmm this kind of a big issue
|
||||
|
@ -152,40 +154,30 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
* - Sign the keys and upload them
|
||||
* - Sign the current device with SSK and sign MSK with device key (migration) and upload signatures.
|
||||
*/
|
||||
override fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?, callback: MatrixCallback<Unit>) {
|
||||
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
|
||||
Timber.d("## CrossSigning initializeCrossSigning")
|
||||
|
||||
val params = InitializeCrossSigningTask.Params(
|
||||
interactiveAuthInterceptor = uiaInterceptor
|
||||
)
|
||||
initializeCrossSigningTask.configureWith(params) {
|
||||
this.callbackThread = TaskThread.CRYPTO
|
||||
this.callback = object : MatrixCallback<InitializeCrossSigningTask.Result> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
Timber.e(failure, "Error in initializeCrossSigning()")
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
|
||||
override fun onSuccess(data: InitializeCrossSigningTask.Result) {
|
||||
val crossSigningInfo = MXCrossSigningInfo(
|
||||
myUserId,
|
||||
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
|
||||
true
|
||||
)
|
||||
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)
|
||||
val data = initializeCrossSigningTask
|
||||
.execute(params)
|
||||
val crossSigningInfo = MXCrossSigningInfo(
|
||||
myUserId,
|
||||
listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo),
|
||||
true
|
||||
)
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
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()) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSecretMSKGossip(mskPrivateKey: String) {
|
||||
override suspend fun onSecretMSKGossip(mskPrivateKey: String) {
|
||||
Timber.i("## CrossSigning - onSecretSSKGossip")
|
||||
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
||||
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")
|
||||
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
||||
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")
|
||||
val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also {
|
||||
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?,
|
||||
uskKeyPrivateKey: String?,
|
||||
sskPrivateKey: String?
|
||||
|
@ -328,7 +320,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
|
||||
if (!masterKeyIsTrusted || !userKeyIsTrusted || !selfSignedKeyIsTrusted) {
|
||||
return UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
|
||||
return UserTrustResult.Failure("Keys not trusted $mxCrossSigningInfo") // UserTrustResult.KeysNotTrusted(mxCrossSigningInfo)
|
||||
} else {
|
||||
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||
val checkSelfTrust = checkSelfTrust()
|
||||
|
@ -354,18 +346,22 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
* └──▶ USK ────────────┘
|
||||
* .
|
||||
*/
|
||||
override fun isUserTrusted(otherUserId: String): Boolean {
|
||||
return cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
|
||||
override suspend fun isUserTrusted(otherUserId: String): Boolean {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted() == true
|
||||
}
|
||||
}
|
||||
|
||||
override fun isCrossSigningVerified(): Boolean {
|
||||
return checkSelfTrust().isVerified()
|
||||
override suspend fun isCrossSigningVerified(): Boolean {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
checkSelfTrust().isVerified()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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")
|
||||
if (otherUserId == myUserId) {
|
||||
return checkSelfTrust()
|
||||
|
@ -380,17 +376,17 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
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()
|
||||
?: return UserTrustResult.CrossSigningNotConfigured(myUserId)
|
||||
|
||||
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
|
||||
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
|
||||
?.get(myUserId) // Signatures made by me
|
||||
|
@ -398,7 +394,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
|
||||
if (masterKeySignaturesMadeByMyUserKey.isNullOrBlank()) {
|
||||
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
|
||||
|
@ -409,7 +405,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
otherMasterKey.canonicalSignable()
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
||||
return UserTrustResult.Failure("Invalid signature $masterKeySignaturesMadeByMyUserKey") // UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
||||
}
|
||||
|
||||
return UserTrustResult.Success
|
||||
|
@ -424,7 +420,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
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,
|
||||
// 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)
|
||||
|
@ -473,7 +469,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
|
||||
if (!isMaterKeyTrusted) {
|
||||
return UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||
return UserTrustResult.Failure("Keys not trusted $myCrossSigningInfo") // UserTrustResult.KeysNotTrusted(myCrossSigningInfo)
|
||||
}
|
||||
|
||||
val myUserKey = myCrossSigningInfo.userKey()
|
||||
|
@ -485,7 +481,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
|
||||
if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||
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
|
||||
|
@ -496,7 +492,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
myUserKey.canonicalSignable()
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
||||
return UserTrustResult.Failure("Invalid MSK signature of USK") // UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
||||
}
|
||||
|
||||
val mySSKey = myCrossSigningInfo.selfSigningKey()
|
||||
|
@ -508,7 +504,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
|
||||
if (ssKeySignaturesMadeByMyMasterKey.isNullOrBlank()) {
|
||||
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
|
||||
|
@ -519,26 +515,32 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
mySSKey.canonicalSignable()
|
||||
)
|
||||
} catch (failure: Throwable) {
|
||||
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
||||
return UserTrustResult.Failure("Invalid signature $ssKeySignaturesMadeByMyMasterKey") // UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
||||
}
|
||||
|
||||
return UserTrustResult.Success
|
||||
}
|
||||
|
||||
override fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return cryptoStore.getCrossSigningInfo(otherUserId)
|
||||
override suspend fun getUserCrossSigningKeys(otherUserId: String): MXCrossSigningInfo? {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.getCrossSigningInfo(otherUserId)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLiveCrossSigningKeys(userId: String): LiveData<Optional<MXCrossSigningInfo>> {
|
||||
return cryptoStore.getLiveCrossSigningInfo(userId)
|
||||
}
|
||||
|
||||
override fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||
return cryptoStore.getMyCrossSigningInfo()
|
||||
override suspend fun getMyCrossSigningKeys(): MXCrossSigningInfo? {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.getMyCrossSigningInfo()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||
return cryptoStore.getCrossSigningPrivateKeys()
|
||||
override suspend fun getCrossSigningPrivateKeys(): PrivateKeysInfo? {
|
||||
return withContext(coroutineDispatchers.io) {
|
||||
cryptoStore.getCrossSigningPrivateKeys()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getLiveCrossSigningPrivateKeys(): LiveData<Optional<PrivateKeysInfo>> {
|
||||
|
@ -555,24 +557,20 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
cryptoStore.getCrossSigningPrivateKeys()?.allKnown().orFalse()
|
||||
}
|
||||
|
||||
override fun trustUser(otherUserId: String, callback: MatrixCallback<Unit>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
override suspend fun trustUser(otherUserId: String) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
Timber.d("## CrossSigning - Mark user $otherUserId as trusted ")
|
||||
// We should have this user keys
|
||||
val otherMasterKeys = getUserCrossSigningKeys(otherUserId)?.masterKey()
|
||||
if (otherMasterKeys == null) {
|
||||
callback.onFailure(Throwable("## CrossSigning - Other master signing key is not known"))
|
||||
return@launch
|
||||
throw Throwable("## CrossSigning - Other master signing key is not known")
|
||||
}
|
||||
val myKeys = getUserCrossSigningKeys(myUserId)
|
||||
if (myKeys == null) {
|
||||
callback.onFailure(Throwable("## CrossSigning - CrossSigning is not setup for this account"))
|
||||
return@launch
|
||||
}
|
||||
?: throw Throwable("## CrossSigning - CrossSigning is not setup for this account")
|
||||
|
||||
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
|
||||
if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
|
||||
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
|
||||
return@launch
|
||||
throw Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey")
|
||||
}
|
||||
|
||||
// Sign the other MasterKey with our UserSigning key
|
||||
|
@ -580,12 +578,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
Map::class.java,
|
||||
otherMasterKeys.signalableJSONDictionary()
|
||||
).let { crossSigningOlm.userPkSigning?.sign(it) }
|
||||
|
||||
if (newSignature == null) {
|
||||
// race??
|
||||
callback.onFailure(Throwable("## CrossSigning - Failed to sign"))
|
||||
return@launch
|
||||
}
|
||||
?: // race??
|
||||
throw Throwable("## CrossSigning - Failed to sign")
|
||||
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, true)
|
||||
|
||||
|
@ -593,10 +587,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
val uploadQuery = UploadSignatureQueryBuilder()
|
||||
.withSigningKeyInfo(otherMasterKeys.copyForSignature(myUserId, userPubKey, newSignature))
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}.executeBy(taskExecutor)
|
||||
|
||||
uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
|
||||
|
||||
// Local echo for device cross trust, to avoid having to wait for a notification of key change
|
||||
cryptoStore.getUserDeviceList(otherUserId)?.forEach { device ->
|
||||
|
@ -607,8 +599,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun markMyMasterKeyAsTrusted() {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
override suspend fun markMyMasterKeyAsTrusted() {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
cryptoStore.markMyMasterKeyAsLocallyTrusted(true)
|
||||
checkSelfTrust()
|
||||
// re-verify all trusts
|
||||
|
@ -616,35 +608,26 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun trustDevice(deviceId: String, callback: MatrixCallback<Unit>) {
|
||||
cryptoCoroutineScope.launch(coroutineDispatchers.crypto) {
|
||||
override suspend fun trustDevice(deviceId: String) {
|
||||
withContext(coroutineDispatchers.crypto) {
|
||||
// This device should be yours
|
||||
val device = cryptoStore.getUserDevice(myUserId, deviceId)
|
||||
if (device == null) {
|
||||
callback.onFailure(IllegalArgumentException("This device [$deviceId] is not known, or not yours"))
|
||||
return@launch
|
||||
throw IllegalArgumentException("This device [$deviceId] is not known, or not yours")
|
||||
}
|
||||
|
||||
val myKeys = getUserCrossSigningKeys(myUserId)
|
||||
if (myKeys == null) {
|
||||
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
|
||||
return@launch
|
||||
}
|
||||
?: throw Throwable("CrossSigning is not setup for this account")
|
||||
|
||||
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
||||
if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
|
||||
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
|
||||
return@launch
|
||||
throw Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey")
|
||||
}
|
||||
|
||||
// Sign with self signing
|
||||
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(
|
||||
signatures = mapOf(
|
||||
myUserId
|
||||
|
@ -658,14 +641,16 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
val uploadQuery = UploadSignatureQueryBuilder()
|
||||
.withDeviceInfo(toUpload)
|
||||
.build()
|
||||
uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadQuery)) {
|
||||
this.executionThread = TaskThread.CRYPTO
|
||||
this.callback = callback
|
||||
}.executeBy(taskExecutor)
|
||||
uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadQuery))
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
?: return DeviceTrustResult.UnknownDevice(otherDeviceId)
|
||||
|
||||
|
@ -787,10 +772,12 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
|
||||
override fun onUsersDeviceUpdate(userIds: List<String>) {
|
||||
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()}")
|
||||
val workerParams = UpdateTrustWorker.Params(
|
||||
sessionId = sessionId,
|
||||
|
@ -808,7 +795,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
.enqueue()
|
||||
}
|
||||
|
||||
private fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||
private suspend fun setUserKeysAsTrusted(otherUserId: String, trusted: Boolean) {
|
||||
val currentTrust = cryptoStore.getCrossSigningInfo(otherUserId)?.isTrusted()
|
||||
cryptoStore.setUserKeysAsTrusted(otherUserId, trusted)
|
||||
// If it's me, recheck trust of all users and devices?
|
||||
|
@ -818,7 +805,10 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||
outgoingKeyRequestManager.onSelfCrossSigningTrustChanged(trusted)
|
||||
cryptoStore.updateUsersTrust {
|
||||
users.add(it)
|
||||
checkUserTrust(it).isVerified()
|
||||
// called within a real transaction, has to block
|
||||
runBlocking {
|
||||
checkUserTrust(it).isVerified()
|
||||
}
|
||||
}
|
||||
|
||||
users.forEach {
|
||||
|
|
|
@ -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");
|
||||
* 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.kotlin.where
|
||||
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.UserTrustResult
|
||||
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
|
||||
) : SessionWorkerParams
|
||||
|
||||
@Inject lateinit var crossSigningService: DefaultCrossSigningService
|
||||
@Inject lateinit var crossSigningService: CrossSigningService
|
||||
|
||||
// It breaks the crypto store contract, but we need to batch things :/
|
||||
@CryptoDatabase
|
||||
|
@ -174,9 +175,7 @@ internal class UpdateTrustWorker(context: Context, params: WorkerParameters, ses
|
|||
?.devices
|
||||
|
||||
val trustMap = devicesEntities?.associateWith { device ->
|
||||
// get up to date from DB has could have been updated
|
||||
val otherInfo = getCrossSigningInfo(cryptoRealm, userId)
|
||||
crossSigningService.checkDeviceTrust(myCrossSigningInfo, otherInfo, CryptoMapper.mapToModel(device))
|
||||
crossSigningService.checkDeviceTrust(userId, device.deviceId ?: "", CryptoMapper.mapToModel(device).trustLevel?.locallyVerified)
|
||||
}
|
||||
|
||||
// Update trust if needed
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -31,4 +32,6 @@ internal data class KeyVerificationReady(
|
|||
) : SendToDeviceObject, VerificationInfoReady {
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
|
@ -18,6 +18,7 @@ package org.matrix.android.sdk.internal.crypto.model.rest
|
|||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -32,4 +33,6 @@ internal data class KeyVerificationRequest(
|
|||
) : SendToDeviceObject, VerificationInfoRequest {
|
||||
|
||||
override fun toSendToDeviceObject() = this
|
||||
|
||||
override fun toEventContent() = toContent()
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
// Bob’s device ensures that it has a copy of Alice’s 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 Alice’s device,
|
||||
// Bob’s device replies with a to_device message with type set to m.key.verification.key,
|
||||
// sending Bob’s 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
|
||||
}
|
||||
}
|
||||
|
||||
// Alice’s and Bob’s 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
|
||||
}
|
||||
}
|
|
@ -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 Bob’s device,
|
||||
// Alice’s device stores the commitment value for later use.
|
||||
accepted = accept
|
||||
state = VerificationTxState.OnAccepted
|
||||
|
||||
// Alice’s 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 Alice’s 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 Bob’s device,
|
||||
// Alice’s device checks that the commitment property from the Bob’s m.key.verification.accept
|
||||
// message is the same as the expected value based on the value of the key property received
|
||||
// in Bob’s m.key.verification.key and the content of Alice’s 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
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
||||
// Bob’s device calculates the HMAC (as above) of its copies of Alice’s 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.
|
||||
// Bob’s device compares these with the HMAC values given in the m.key.verification.mac message.
|
||||
// If everything matches, then consider Alice’s 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))
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
|
||||
// Bob’s device calculates the HMAC (as above) of its copies of Alice’s 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.
|
||||
// Bob’s device compares these with the HMAC values given in the m.key.verification.mac message.
|
||||
// If everything matches, then consider Alice’s 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
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@
|
|||
package org.matrix.android.sdk.internal.crypto.verification
|
||||
|
||||
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,
|
||||
|
@ -37,9 +38,15 @@ internal interface VerificationInfoReady : VerificationInfo<ValidVerificationInf
|
|||
val methods: List<String>?
|
||||
|
||||
override fun asValidObject(): ValidVerificationInfoReady? {
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validFromDevice = fromDevice?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validMethods = methods?.takeIf { it.isNotEmpty() } ?: return null
|
||||
val validTransactionId = transactionId?.takeIf { it.isNotEmpty() } ?: return null.also {
|
||||
Timber.e("## SAS Invalid room ready content invalid transaction id $transactionId")
|
||||
}
|
||||
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(
|
||||
validTransactionId,
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
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.SasVerificationTransaction
|
||||
import org.matrix.android.sdk.internal.crypto.model.rest.VERIFICATION_METHOD_RECIPROCATE
|
||||
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 validMessageAuthenticationCodes = messageAuthenticationCodes
|
||||
?.takeIf {
|
||||
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256) ||
|
||||
it.contains(SASDefaultVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
|
||||
it.contains(SasVerificationTransaction.SAS_MAC_SHA256) ||
|
||||
it.contains(SasVerificationTransaction.SAS_MAC_SHA256_LONGKDF)
|
||||
}
|
||||
?: return null
|
||||
val validShortAuthenticationStrings = shortAuthenticationStrings?.takeIf { it.contains(SasMode.DECIMAL) } ?: return null
|
||||
|
|
|
@ -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()
|
||||
}
|
|
@ -56,7 +56,7 @@ internal class VerificationMessageProcessor @Inject constructor(
|
|||
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}")
|
||||
|
||||
// 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 it's send from me, we need to keep track of Requests or Start
|
||||
// done from another device of mine
|
||||
if (EventType.MESSAGE == event.getClearType()) {
|
||||
val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
|
||||
event.getClearContent().toModel<MessageVerificationRequestContent>()?.let {
|
||||
// if (EventType.MESSAGE == event.getClearType()) {
|
||||
// val msgType = event.getClearContent().toModel<MessageContent>()?.msgType
|
||||
// if (MessageType.MSGTYPE_VERIFICATION_REQUEST == msgType) {
|
||||
// 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) {
|
||||
// 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) }
|
||||
// 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.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()) {
|
||||
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 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)
|
||||
}
|
||||
// } 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
|
||||
}
|
||||
|
||||
|
@ -129,11 +133,11 @@ internal class VerificationMessageProcessor @Inject constructor(
|
|||
EventType.KEY_VERIFICATION_CANCEL,
|
||||
EventType.KEY_VERIFICATION_READY,
|
||||
EventType.KEY_VERIFICATION_DONE -> {
|
||||
verificationService.onRoomEvent(event)
|
||||
verificationService.onRoomEvent(roomId, event)
|
||||
}
|
||||
EventType.MESSAGE -> {
|
||||
if (MessageType.MSGTYPE_VERIFICATION_REQUEST == event.getClearContent().toModel<MessageContent>()?.msgType) {
|
||||
verificationService.onRoomRequestReceived(event)
|
||||
verificationService.onRoomRequestReceived(roomId, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)?
|
||||
)
|
||||
}
|
|
@ -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}")
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -113,6 +113,8 @@ internal interface SessionComponent {
|
|||
|
||||
fun networkConnectivityChecker(): NetworkConnectivityChecker
|
||||
|
||||
// fun olmMachine(): OlmMachine
|
||||
|
||||
fun taskExecutor(): TaskExecutor
|
||||
|
||||
fun inject(worker: SendEventWorker)
|
|
@ -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.
|
||||
*/
|
||||
@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
|
||||
* reconfigure themselves, optionally validating the URLs within.
|
||||
|
@ -59,5 +59,5 @@ data class Credentials(
|
|||
)
|
||||
|
||||
internal fun Credentials.sessionId(): String {
|
||||
return (if (deviceId.isNullOrBlank()) userId else "$userId|$deviceId").md5()
|
||||
return (if (deviceId.isBlank()) userId else "$userId|$deviceId").md5()
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ interface StepProgressListener {
|
|||
sealed class Step {
|
||||
data class ComputingKey(val progress: Int, val total: Int) : Step()
|
||||
object DownloadingKey : Step()
|
||||
data class DecryptingKey(val progress: Int, val total: Int) : Step()
|
||||
data class ImportingKey(val progress: Int, val total: Int) : Step()
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ package org.matrix.android.sdk.api.session.crypto
|
|||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
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.listeners.ProgressListener
|
||||
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.CryptoDeviceInfo
|
||||
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.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.MXEventDecryptionResult
|
||||
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.Event
|
||||
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.internal.crypto.model.SessionInfo
|
||||
|
||||
|
@ -51,9 +52,9 @@ interface CryptoService {
|
|||
|
||||
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
|
||||
|
||||
|
@ -65,15 +66,9 @@ interface CryptoService {
|
|||
|
||||
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>
|
||||
|
||||
fun setDevicesKnown(devices: List<MXDeviceInfo>, callback: MatrixCallback<Unit>?)
|
||||
|
||||
fun deviceWithIdentityKey(senderKey: String, algorithm: String): CryptoDeviceInfo?
|
||||
|
||||
fun getMyDevice(): CryptoDeviceInfo
|
||||
suspend fun getMyCryptoDevice(): CryptoDeviceInfo
|
||||
|
||||
fun getGlobalBlacklistUnverifiedDevices(): Boolean
|
||||
|
||||
|
@ -118,12 +113,12 @@ interface CryptoService {
|
|||
|
||||
fun setRoomBlockUnverifiedDevices(roomId: String, block: Boolean)
|
||||
|
||||
fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||
|
||||
fun getCryptoDeviceInfo(deviceId: String, callback: MatrixCallback<DeviceInfo>)
|
||||
suspend fun getCryptoDeviceInfo(userId: String, deviceId: String?): CryptoDeviceInfo?
|
||||
|
||||
fun getCryptoDeviceInfo(userId: String): List<CryptoDeviceInfo>
|
||||
|
||||
// fun getCryptoDeviceInfoFlow(userId: String): Flow<List<CryptoDeviceInfo>>
|
||||
|
||||
fun getLiveCryptoDeviceInfo(): LiveData<List<CryptoDeviceInfo>>
|
||||
|
||||
fun getLiveCryptoDeviceInfoWithId(deviceId: String): LiveData<Optional<CryptoDeviceInfo>>
|
||||
|
@ -132,15 +127,13 @@ interface CryptoService {
|
|||
|
||||
fun getLiveCryptoDeviceInfo(userIds: List<String>): LiveData<List<CryptoDeviceInfo>>
|
||||
|
||||
fun requestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun reRequestRoomKeyForEvent(event: Event)
|
||||
suspend fun reRequestRoomKeyForEvent(event: Event)
|
||||
|
||||
fun addRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun removeRoomKeysRequestListener(listener: GossipingRequestListener)
|
||||
|
||||
fun fetchDevicesList(callback: MatrixCallback<DevicesListResponse>)
|
||||
suspend fun fetchDevicesList(): List<DeviceInfo>
|
||||
|
||||
fun getMyDevicesInfo(): List<DeviceInfo>
|
||||
|
||||
|
@ -148,30 +141,35 @@ interface CryptoService {
|
|||
|
||||
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
|
||||
|
||||
// TODO This could be removed from this interface
|
||||
fun encryptEventContent(
|
||||
suspend fun encryptEventContent(
|
||||
eventContent: Content,
|
||||
eventType: String,
|
||||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>
|
||||
)
|
||||
roomId: String
|
||||
): MXEncryptEventContentResult
|
||||
|
||||
fun discardOutboundSession(roomId: String)
|
||||
|
||||
@Throws(MXCryptoError::class)
|
||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
||||
|
||||
fun getEncryptionAlgorithm(roomId: String): String?
|
||||
|
||||
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 removeSessionListener(listener: NewSessionListener)
|
||||
|
@ -199,10 +197,30 @@ interface CryptoService {
|
|||
* Perform any background tasks that can be done before a message is ready to
|
||||
* 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.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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 senderKey the sender key of the device which the Megolm session is shared with
|
||||
* @param sessionId the session id of the Megolm session
|
||||
*/
|
||||
fun onNewSession(roomId: String?, senderKey: String, sessionId: String)
|
||||
fun onNewSession(roomId: String?, sessionId: String)
|
||||
}
|
||||
|
|
|
@ -17,76 +17,109 @@
|
|||
package org.matrix.android.sdk.api.session.crypto.crosssigning
|
||||
|
||||
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.session.crypto.model.CryptoDeviceInfo
|
||||
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
|
||||
import org.matrix.android.sdk.api.util.Optional
|
||||
|
||||
interface CrossSigningService {
|
||||
/**
|
||||
* Is our own device signed by our own cross signing identity
|
||||
*/
|
||||
suspend fun isCrossSigningVerified(): Boolean
|
||||
|
||||
fun isCrossSigningVerified(): Boolean
|
||||
|
||||
fun isUserTrusted(otherUserId: String): Boolean
|
||||
// TODO this isn't used anywhere besides in tests?
|
||||
// Is this the local trust concept that we have for devices?
|
||||
suspend fun isUserTrusted(otherUserId: String): Boolean
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||
suspend fun checkUserTrust(otherUserId: String): UserTrustResult
|
||||
|
||||
/**
|
||||
* Initialize cross signing for this user.
|
||||
* Users needs to enter credentials
|
||||
*/
|
||||
fun initializeCrossSigning(
|
||||
uiaInterceptor: UserInteractiveAuthInterceptor?,
|
||||
callback: MatrixCallback<Unit>
|
||||
)
|
||||
suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?)
|
||||
|
||||
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?,
|
||||
uskKeyPrivateKey: String?,
|
||||
sskPrivateKey: String?
|
||||
): UserTrustResult
|
||||
/**
|
||||
* Inject the private cross signing keys, likely from backup, into our store.
|
||||
*
|
||||
* This will check if the injected private cross signing keys match the public ones provided
|
||||
* 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 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>>
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/** Do we have all our private cross signing keys in storage? */
|
||||
fun allPrivateKeysKnown(): Boolean
|
||||
|
||||
fun trustUser(
|
||||
otherUserId: String,
|
||||
callback: MatrixCallback<Unit>
|
||||
)
|
||||
/** Mark a user identity as trusted and sign and upload signatures of our user-signing key to the server */
|
||||
suspend fun trustUser(otherUserId: String)
|
||||
|
||||
fun markMyMasterKeyAsTrusted()
|
||||
/** Mark our own master key as trusted */
|
||||
suspend fun markMyMasterKeyAsTrusted()
|
||||
|
||||
/**
|
||||
* Sign one of your devices and upload the signature.
|
||||
*/
|
||||
fun trustDevice(
|
||||
deviceId: String,
|
||||
callback: MatrixCallback<Unit>
|
||||
)
|
||||
@Throws
|
||||
suspend fun trustDevice(deviceId: String)
|
||||
|
||||
fun checkDeviceTrust(
|
||||
otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
locallyTrusted: Boolean?
|
||||
): DeviceTrustResult
|
||||
suspend fun shieldForGroup(userIds: List<String>): RoomEncryptionTrustLevel
|
||||
|
||||
/**
|
||||
* Check if a device is trusted
|
||||
*
|
||||
* 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
|
||||
fun onSecretMSKGossip(mskPrivateKey: String)
|
||||
fun onSecretSSKGossip(sskPrivateKey: String)
|
||||
fun onSecretUSKGossip(uskPrivateKey: String)
|
||||
// TODO those three methods doesn't seem to be used anywhere?
|
||||
suspend fun onSecretMSKGossip(mskPrivateKey: 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
|
||||
}
|
||||
|
|
|
@ -23,10 +23,11 @@ sealed class UserTrustResult {
|
|||
// data class UnknownDevice(val deviceID: String) : UserTrustResult()
|
||||
data class CrossSigningNotConfigured(val userID: String) : UserTrustResult()
|
||||
|
||||
data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult()
|
||||
data class KeysNotTrusted(val key: MXCrossSigningInfo) : UserTrustResult()
|
||||
data class KeyNotSigned(val key: CryptoCrossSigningKey) : UserTrustResult()
|
||||
data class InvalidSignature(val key: CryptoCrossSigningKey, val signature: String) : UserTrustResult()
|
||||
data class Failure(val message: String) : UserTrustResult()
|
||||
// data class UnknownCrossSignatureInfo(val userID: String) : UserTrustResult()
|
||||
// data class KeysNotTrusted(val key: MXCrossSigningInfo) : 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
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -16,87 +16,74 @@
|
|||
|
||||
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.StepProgressListener
|
||||
import org.matrix.android.sdk.api.session.crypto.model.ImportRoomKeysResult
|
||||
|
||||
interface KeysBackupService {
|
||||
|
||||
/**
|
||||
* Retrieve the current version of the backup from the homeserver.
|
||||
*
|
||||
* 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].
|
||||
*
|
||||
* @param keysBackupCreationInfo the info object from [prepareKeysBackupVersion].
|
||||
* @param callback Asynchronous callback
|
||||
* @return KeysVersion
|
||||
*/
|
||||
fun createKeysBackupVersion(
|
||||
keysBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
callback: MatrixCallback<KeysVersion>
|
||||
)
|
||||
@Throws
|
||||
suspend fun createKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo): KeysVersion
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun getTotalNumbersOfBackedUpKeys(): Int
|
||||
suspend fun getTotalNumbersOfBackedUpKeys(): Int
|
||||
|
||||
/**
|
||||
* Start to back up keys immediately.
|
||||
*
|
||||
* @param progressListener the callback to follow the progress
|
||||
* @param callback the main callback
|
||||
*/
|
||||
fun backupAllGroupSessions(
|
||||
progressListener: ProgressListener?,
|
||||
callback: MatrixCallback<Unit>?
|
||||
)
|
||||
// /**
|
||||
// * Start to back up keys immediately.
|
||||
// *
|
||||
// * @param progressListener the callback to follow the progress
|
||||
// * @param callback the main callback
|
||||
// */
|
||||
// fun backupAllGroupSessions(progressListener: ProgressListener?,
|
||||
// callback: MatrixCallback<Unit>?)
|
||||
|
||||
/**
|
||||
* Check trust on a key backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun getKeysBackupTrust(
|
||||
keysBackupVersion: KeysVersionResult,
|
||||
callback: MatrixCallback<KeysBackupVersionTrust>
|
||||
)
|
||||
suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* It can be different than keysBackupVersion.
|
||||
* @param version the backup version
|
||||
* @param callback
|
||||
*/
|
||||
fun getVersion(
|
||||
version: String,
|
||||
callback: MatrixCallback<KeysVersionResult?>
|
||||
)
|
||||
suspend fun getVersion(version: String): KeysVersionResult?
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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.
|
||||
|
@ -104,7 +91,7 @@ interface KeysBackupService {
|
|||
* If one is present and has a valid signature from one of the user's verified
|
||||
* devices, start backing up to it.
|
||||
*/
|
||||
fun checkAndStartKeysBackup()
|
||||
suspend fun checkAndStartKeysBackup()
|
||||
|
||||
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 callback Asynchronous callback
|
||||
*/
|
||||
fun prepareKeysBackupVersion(
|
||||
password: String?,
|
||||
progressListener: ProgressListener?,
|
||||
callback: MatrixCallback<MegolmBackupCreationInfo>
|
||||
)
|
||||
suspend fun prepareKeysBackupVersion(password: String?, progressListener: ProgressListener?): MegolmBackupCreationInfo
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param version the backup version to delete.
|
||||
* @param callback Asynchronous callback
|
||||
* @param version the backup version to delete.
|
||||
*/
|
||||
fun deleteBackup(
|
||||
version: String,
|
||||
callback: MatrixCallback<Unit>?
|
||||
)
|
||||
@Throws
|
||||
suspend fun deleteBackup(version: String)
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
fun canRestoreKeys(): Boolean
|
||||
suspend fun canRestoreKeys(): Boolean
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
|
@ -152,40 +132,31 @@ interface KeysBackupService {
|
|||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param trust the trust to set to the keys backup.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun trustKeysBackupVersion(
|
||||
keysBackupVersion: KeysVersionResult,
|
||||
trust: Boolean,
|
||||
callback: MatrixCallback<Unit>
|
||||
)
|
||||
@Throws
|
||||
suspend fun trustKeysBackupVersion(keysBackupVersion: KeysVersionResult, trust: Boolean)
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @param recoveryKey the recovery key to challenge with the key backup public key.
|
||||
* @param callback block called when the operations completes.
|
||||
*/
|
||||
fun trustKeysBackupVersionWithRecoveryKey(
|
||||
keysBackupVersion: KeysVersionResult,
|
||||
recoveryKey: String,
|
||||
callback: MatrixCallback<Unit>
|
||||
)
|
||||
suspend fun trustKeysBackupVersionWithRecoveryKey(keysBackupVersion: KeysVersionResult, recoveryKey: IBackupRecoveryKey)
|
||||
|
||||
/**
|
||||
* Set trust on a keys backup version.
|
||||
*
|
||||
* @param keysBackupVersion the backup version to check.
|
||||
* @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,
|
||||
password: String,
|
||||
callback: MatrixCallback<Unit>
|
||||
password: String
|
||||
)
|
||||
|
||||
suspend fun onSecretKeyGossip(secret: String)
|
||||
|
||||
/**
|
||||
* 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 callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
||||
*/
|
||||
fun restoreKeysWithRecoveryKey(
|
||||
suspend fun restoreKeysWithRecoveryKey(
|
||||
keysVersionResult: KeysVersionResult,
|
||||
recoveryKey: String,
|
||||
recoveryKey: IBackupRecoveryKey,
|
||||
roomId: String?,
|
||||
sessionId: String?,
|
||||
stepProgressListener: StepProgressListener?,
|
||||
callback: MatrixCallback<ImportRoomKeysResult>
|
||||
)
|
||||
stepProgressListener: StepProgressListener?
|
||||
): ImportRoomKeysResult
|
||||
|
||||
/**
|
||||
* 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 callback Callback. It provides the number of found keys and the number of successfully imported keys.
|
||||
*/
|
||||
fun restoreKeyBackupWithPassword(
|
||||
suspend fun restoreKeyBackupWithPassword(
|
||||
keysBackupVersion: KeysVersionResult,
|
||||
password: String,
|
||||
roomId: String?,
|
||||
sessionId: String?,
|
||||
stepProgressListener: StepProgressListener?,
|
||||
callback: MatrixCallback<ImportRoomKeysResult>
|
||||
)
|
||||
stepProgressListener: StepProgressListener?
|
||||
): ImportRoomKeysResult
|
||||
|
||||
val keysBackupVersion: KeysVersionResult?
|
||||
|
||||
|
@ -234,10 +203,10 @@ interface KeysBackupService {
|
|||
fun getState(): KeysBackupState
|
||||
|
||||
// For gossiping
|
||||
fun saveBackupRecoveryKey(recoveryKey: String?, version: String?)
|
||||
fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||
fun saveBackupRecoveryKey(recoveryKey: IBackupRecoveryKey?, version: String?)
|
||||
suspend fun getKeyBackupRecoveryKeyInfo(): SavedKeyBackupKeyInfo?
|
||||
|
||||
fun isValidRecoveryKeyForCurrentVersion(recoveryKey: String, callback: MatrixCallback<Boolean>)
|
||||
suspend fun isValidRecoveryKeyForCurrentVersion(recoveryKey: IBackupRecoveryKey): Boolean
|
||||
|
||||
fun computePrivateKey(
|
||||
passphrase: String,
|
||||
|
|
|
@ -31,7 +31,7 @@ data class MegolmBackupCreationInfo(
|
|||
val authData: MegolmBackupAuthData,
|
||||
|
||||
/**
|
||||
* The Base58 recovery key.
|
||||
* The recovery key.
|
||||
*/
|
||||
val recoveryKey: String
|
||||
val recoveryKey: IBackupRecoveryKey
|
||||
)
|
||||
|
|
|
@ -17,6 +17,6 @@
|
|||
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
||||
|
||||
data class SavedKeyBackupKeyInfo(
|
||||
val recoveryKey: String,
|
||||
val recoveryKey: IBackupRecoveryKey,
|
||||
val version: String
|
||||
)
|
||||
|
|
|
@ -16,7 +16,21 @@
|
|||
|
||||
package org.matrix.android.sdk.api.session.crypto.model
|
||||
|
||||
import uniffi.olm.KeysImportResult
|
||||
|
||||
data class ImportRoomKeysResult(
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -104,6 +104,10 @@ class MXUsersDevicesMap<E> {
|
|||
map.clear()
|
||||
}
|
||||
|
||||
fun join(other: Map<out String, HashMap<String, E>>) {
|
||||
map.putAll(other)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add entries from another MXUsersDevicesMap.
|
||||
*
|
||||
|
|
|
@ -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?
|
||||
|
||||
}
|
|
@ -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 java.util.UUID
|
||||
|
||||
|
||||
/**
|
||||
* Stores current pending verification requests.
|
||||
*/
|
||||
data class PendingVerificationRequest(
|
||||
val ageLocalTs: Long,
|
||||
val state: EVerificationState,
|
||||
val isIncoming: Boolean = false,
|
||||
val localId: String = UUID.randomUUID().toString(),
|
||||
// val localId: String = UUID.randomUUID().toString(),
|
||||
val otherUserId: String,
|
||||
val otherDeviceId: String?,
|
||||
// in case of verification via room, it will be not null
|
||||
val roomId: String?,
|
||||
val transactionId: String? = null,
|
||||
val requestInfo: ValidVerificationInfoRequest? = null,
|
||||
val readyInfo: ValidVerificationInfoReady? = null,
|
||||
val transactionId: String,//? = null,
|
||||
// val requestInfo: ValidVerificationInfoRequest? = null,
|
||||
// val readyInfo: ValidVerificationInfoReady? = null,
|
||||
val cancelConclusion: CancelCode? = null,
|
||||
val isSuccessful: Boolean = false,
|
||||
val isFinished: Boolean = false,
|
||||
val handledByOtherSession: Boolean = false,
|
||||
// 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 isSent: Boolean = transactionId != null
|
||||
// val isReady: Boolean = readyInfo != 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,15 +26,15 @@ interface QrCodeVerificationTransaction : VerificationTransaction {
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
fun otherUserScannedMyQrCode()
|
||||
suspend fun otherUserScannedMyQrCode()
|
||||
|
||||
/**
|
||||
* Call when you do not confirm that other user has scanned your QR code.
|
||||
*/
|
||||
fun otherUserDidNotScannedMyQrCode()
|
||||
suspend fun otherUserDidNotScannedMyQrCode()
|
||||
}
|
||||
|
|
|
@ -18,19 +18,41 @@ package org.matrix.android.sdk.api.session.crypto.verification
|
|||
|
||||
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 getDecimalCodeRepresentation(): String
|
||||
fun getDecimalCodeRepresentation(): String?
|
||||
|
||||
/**
|
||||
* To be called by the client when the user has verified that
|
||||
* both short codes do match.
|
||||
*/
|
||||
fun userHasVerifiedShortCode()
|
||||
suspend fun userHasVerifiedShortCode()
|
||||
|
||||
fun shortCodeDoesNotMatch()
|
||||
suspend fun acceptVerification()
|
||||
|
||||
suspend fun shortCodeDoesNotMatch()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
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.LocalEcho
|
||||
|
||||
|
@ -29,86 +30,85 @@ import org.matrix.android.sdk.api.session.events.model.LocalEcho
|
|||
*/
|
||||
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.
|
||||
*/
|
||||
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,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String,
|
||||
transactionId: String?
|
||||
): String?
|
||||
/**
|
||||
* Request an interactive verification to begin
|
||||
*
|
||||
* This sends out a m.key.verification.request event over to-device messaging to
|
||||
* to this device.
|
||||
*
|
||||
* 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).
|
||||
*/
|
||||
fun requestKeyVerificationInDMs(
|
||||
@Throws
|
||||
suspend fun requestKeyVerificationInDMs(
|
||||
methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
localId: String? = LocalEcho.createLocalEchoId()
|
||||
): 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>,
|
||||
otherUserId: String,
|
||||
otherDevices: List<String>?
|
||||
): PendingVerificationRequest
|
||||
transactionId: String
|
||||
): Boolean
|
||||
|
||||
fun declineVerificationRequestInDMs(
|
||||
otherUserId: String,
|
||||
transactionId: String,
|
||||
roomId: String
|
||||
)
|
||||
suspend fun cancelVerificationRequest(request: PendingVerificationRequest)
|
||||
|
||||
// Only SAS method is supported for the moment
|
||||
// TODO Parameter otherDeviceId should be removed in this case
|
||||
fun beginKeyVerificationInDMs(
|
||||
suspend fun cancelVerificationRequest(otherUserId: String, transactionId: String)
|
||||
|
||||
suspend fun startKeyVerification(
|
||||
method: VerificationMethod,
|
||||
transactionId: String,
|
||||
roomId: String,
|
||||
otherUserId: String,
|
||||
otherDeviceId: String
|
||||
): String
|
||||
requestId: String
|
||||
): String?
|
||||
|
||||
/**
|
||||
* Returns false if the request is unknown.
|
||||
*/
|
||||
fun readyPendingVerificationInDMs(
|
||||
methods: List<VerificationMethod>,
|
||||
suspend fun reciprocateQRVerification(
|
||||
otherUserId: String,
|
||||
roomId: String,
|
||||
transactionId: String
|
||||
): Boolean
|
||||
requestId: String,
|
||||
scannedData: String
|
||||
): String?
|
||||
|
||||
/**
|
||||
* Returns false if the request is unknown.
|
||||
*/
|
||||
fun readyPendingVerification(
|
||||
methods: List<VerificationMethod>,
|
||||
otherUserId: String,
|
||||
transactionId: String
|
||||
): Boolean
|
||||
suspend fun sasCodeMatch(theyMatch: Boolean, transactionId: String)
|
||||
|
||||
// This starts the short SAS flow, the one that doesn't start with a request, deprecated
|
||||
|
||||
// using flow now?
|
||||
interface Listener {
|
||||
/**
|
||||
* 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
Loading…
Reference in a new issue