SAS verif, support signing and verification of Cross Signing

This commit is contained in:
Valere 2020-01-17 14:52:53 +01:00
parent 859c75df98
commit 98ba2d39a8
15 changed files with 208 additions and 79 deletions

View file

@ -9,8 +9,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
import org.junit.Assert
import org.junit.Assert.assertNotNull
import org.junit.Assert.fail
import org.junit.Assert.*
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@ -95,7 +94,7 @@ class XSigningTest : InstrumentedTest {
}
@Test
fun test_CrossSigningTestAliceTrustBobNewDevice() {
fun test_CrossSigningTestAliceTrustBobNewDevice() {
val cryptoTestData = mCryptoTestHelper.doE2ETestWithAliceAndBobInARoom()
val aliceSession = cryptoTestData.firstSession
@ -156,10 +155,10 @@ class XSigningTest : InstrumentedTest {
}
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
if(data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId!!) == false) {
fail("Bob should see the new device")
}
bobKeysLatch.countDown()
bobKeysLatch.countDown()
}
})
mTestHelper.await(bobKeysLatch)
@ -189,7 +188,7 @@ class XSigningTest : InstrumentedTest {
override fun onSuccess(data: MXUsersDevicesMap<MXDeviceInfo>) {
//check that the device is seen
if(data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
if (data.getUserDeviceIds(bobUserId)?.contains(bobSecondDeviceId) == false) {
fail("Alice should see the new device")
}
aliceKeysLatch.countDown()
@ -197,18 +196,9 @@ class XSigningTest : InstrumentedTest {
})
mTestHelper.await(aliceKeysLatch)
val secondDevicetrustLatch = CountDownLatch(1)
aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId,object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) {
fail("Failed to check trust cause:${failure.localizedMessage}")
}
override fun onSuccess(data: Unit) {
secondDevicetrustLatch.countDown()
}
})
mTestHelper.await(secondDevicetrustLatch)
val result = aliceSession.getCrossSigningService().checkDeviceTrust(bobUserId, bobSecondDeviceId)
assertTrue("Bob second device should be trusted from alice POV", result.isSuccess())
}
}

View file

@ -29,6 +29,7 @@ import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationAccept
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationCancel
import im.vector.matrix.android.internal.crypto.model.rest.KeyVerificationStart
import im.vector.matrix.android.internal.crypto.model.rest.toValue
import org.junit.Assert.*
import org.junit.FixMethodOrder
import org.junit.Test
@ -36,9 +37,6 @@ import org.junit.runner.RunWith
import org.junit.runners.MethodSorters
import java.util.*
import java.util.concurrent.CountDownLatch
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.annotation.UiThreadTest
import org.junit.Rule
@RunWith(AndroidJUnit4::class)
@ -69,7 +67,7 @@ class SASTest : InstrumentedTest {
}
bobSasMgr.addListener(bobListener)
val txID = aliceSasMgr.beginKeyVerificationSAS(bobSession.myUserId, bobSession.getMyDevice().deviceId)
val txID = aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobSession.myUserId, bobSession.getMyDevice().deviceId)
assertNotNull("Alice should have a started transaction", txID)
val aliceKeyTx = aliceSasMgr.getExistingTransaction(bobSession.myUserId, txID!!)
@ -147,10 +145,10 @@ class SASTest : InstrumentedTest {
override fun transactionCreated(tx: SasVerificationTransaction) {}
override fun transactionUpdated(tx: SasVerificationTransaction) {
if (tx.transactionId == tid && tx.cancelledReason != null) {
cancelReason = tx.cancelledReason?.humanReadable
cancelLatch.countDown()
}
if (tx.transactionId == tid && tx.cancelledReason != null) {
cancelReason = tx.cancelledReason?.humanReadable
cancelLatch.countDown()
}
}
override fun markedAsManuallyVerified(userId: String, deviceId: String) {}
@ -278,7 +276,7 @@ class SASTest : InstrumentedTest {
codes: List<String> = SASVerificationTransaction.KNOWN_SHORT_CODES) {
val startMessage = KeyVerificationStart(
fromDevice = bobSession.getMyDevice().deviceId,
method = KeyVerificationStart.VERIF_METHOD_SAS,
method = VerificationMethod.SAS.toValue(),
transactionID = tid,
keyAgreementProtocols = protocols,
hashes = hashes,
@ -330,8 +328,8 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession!!.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
mTestHelper.await(aliceCreatedLatch)
mTestHelper.await(aliceCancelledLatch)
@ -388,7 +386,7 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
mTestHelper.await(aliceAcceptedLatch)
assertTrue("Should have receive a commitment", accepted!!.commitment?.trim()?.isEmpty() == false)
@ -444,7 +442,7 @@ class SASTest : InstrumentedTest {
IncomingSasVerificationTransaction.UxState.SHOW_ACCEPT -> {
tx.performAccept()
}
else -> Unit
else -> Unit
}
if (uxState === IncomingSasVerificationTransaction.UxState.SHOW_SAS) {
bobSASLatch.countDown()
@ -457,7 +455,7 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
val verificationSAS = aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
val verificationSAS = aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch)
@ -517,7 +515,7 @@ class SASTest : InstrumentedTest {
IncomingSasVerificationTransaction.UxState.VERIFIED -> {
bobSASLatch.countDown()
}
else -> Unit
else -> Unit
}
}
@ -527,7 +525,7 @@ class SASTest : InstrumentedTest {
val bobUserId = bobSession.myUserId
val bobDeviceId = bobSession.getMyDevice().deviceId
aliceSasMgr.beginKeyVerificationSAS(bobUserId, bobDeviceId)
aliceSasMgr.beginKeyVerification(VerificationMethod.SAS, bobUserId, bobDeviceId)
mTestHelper.await(aliceSASLatch)
mTestHelper.await(bobSASLatch)

View file

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.crypto.crosssigning
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.internal.crypto.crosssigning.DeviceTrustResult
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
@ -43,5 +44,5 @@ interface CrossSigningService {
*/
fun signDevice(deviceId: String, callback: MatrixCallback<SignatureUploadResponse>)
fun checkDeviceTrust(userId: String, deviceId: String, callback: MatrixCallback<Unit>)
fun checkDeviceTrust(userId: String, deviceId: String) : DeviceTrustResult
}

View file

@ -21,7 +21,9 @@ import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.crosssigning.DefaultCrossSigningService
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.tasks.*
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
@ -199,4 +201,7 @@ internal abstract class CryptoModule {
@Binds
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask)
: DeleteDeviceWithUserPasswordTask
@Binds
abstract fun bindCrossSigningService(crossSigningService: DefaultCrossSigningService) : CrossSigningService
}

View file

@ -26,13 +26,13 @@ import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningIn
import im.vector.matrix.android.internal.crypto.DeviceListManager
import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.*
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.UploadSignaturesTask
import im.vector.matrix.android.internal.crypto.tasks.UploadSigningKeysTask
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskConstraints
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -42,6 +42,8 @@ import org.matrix.olm.OlmUtility
import timber.log.Timber
import javax.inject.Inject
@SessionScope
internal class DefaultCrossSigningService @Inject constructor(
@UserId private val userId: String,
private val credentials: Credentials,
@ -153,9 +155,11 @@ internal class DefaultCrossSigningService @Inject constructor(
Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey")
// Sign userSigningKey with master
val signedUSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.USER_SIGNING)
val signedUSK = CrossSigningKeyInfo.Builder(myUserID, CrossSigningKeyInfo.KeyUsage.USER_SIGNING)
.key(uskPublicKey)
.build().signalableJSONDictionary()).let { masterPkOlm.sign(it) }
.build()
.canonicalSignable()
.let { masterPkOlm.sign(it) }
//=================
// SELF SIGNING KEY
@ -322,9 +326,6 @@ internal class DefaultCrossSigningService @Inject constructor(
return
}
// olmUtility?.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey,
// myUserKey.publicKeyBase64,
// masterKey.publicKeyBase64)
}
@ -399,7 +400,6 @@ internal class DefaultCrossSigningService @Inject constructor(
}
// Sign with self signing
// val newSignature = JsonCanonicalizer.getCanonicalJson(Map::class.java, device.signalableJSONDictionary()).let { userPkSigning?.sign(it) }
val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable())
if (newSignature == null) {
@ -416,7 +416,6 @@ internal class DefaultCrossSigningService @Inject constructor(
)
)
)
// device.addSignature(credentials.userId, ssPubKey, newSignature)
val uploadQuery = UploadSignatureQueryBuilder()
.withDeviceInfo(toUpload)
@ -441,30 +440,21 @@ internal class DefaultCrossSigningService @Inject constructor(
}.executeBy(taskExecutor)
}
override fun checkDeviceTrust(userId: String, deviceId: String, callback: MatrixCallback<Unit>) {
override fun checkDeviceTrust(userId: String, deviceId: String): DeviceTrustResult {
val otherDevice = cryptoStore.getUserDevice(userId, deviceId)
if (otherDevice == null) {
callback.onFailure(IllegalArgumentException("This device is not known, or not yours"))
return
}
?: return DeviceTrustResult.UnknownDevice(deviceId)
val myKeys = getUserCrossSigningKeys(credentials.userId)
if (myKeys == null) {
callback.onFailure(Throwable("CrossSigning is not setup for this account"))
return
}
?: return DeviceTrustResult.CrossSigningNotConfigured(credentials.userId)
if (!myKeys.isTrusted) return DeviceTrustResult.KeysNotTrusted(myKeys)
val otherKeys = getUserCrossSigningKeys(userId)
if (otherKeys == null) {
callback.onFailure(Throwable("CrossSigning is not setup for $userId"))
return
}
?: return DeviceTrustResult.CrossSigningNotConfigured(userId)
// TODO should we force verification ?
if (!otherKeys.isTrusted) {
callback.onFailure(Throwable("$userId is not trusted"))
return
}
if (!otherKeys.isTrusted) return DeviceTrustResult.KeysNotTrusted(otherKeys)
// Check if the trust chain is valid
/*
@ -486,26 +476,18 @@ internal class DefaultCrossSigningService @Inject constructor(
*/
val otherSSKSignature = otherDevice.signatures?.get(userId)?.get("ed25519:${otherKeys.selfSigningKey()?.unpaddedBase64PublicKey}")
if (otherSSKSignature == null) {
callback.onFailure(Throwable("Device ${otherDevice.deviceId} is not signed by $userId self signed key"))
return
}
?: return DeviceTrustResult.MissingDeviceSignature(deviceId, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey
?: "")
// Check bob's device is signed by bob's SSK
try {
olmUtility?.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
} catch (e: Throwable) {
callback.onFailure(Throwable("Invalid self signed signature for Device ${otherDevice.deviceId}"))
return DeviceTrustResult.InvalidDeviceSignature(deviceId, otherSSKSignature, e)
}
callback.onSuccess(Unit)
return DeviceTrustResult.Success(deviceId, true)
}
}
fun MXDeviceInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo
sealed class DeviceTrustResult {
data class Success(val deviceID: String, val crossSigned: Boolean) : DeviceTrustResult()
data class UnknownDevice(val deviceID: String) : DeviceTrustResult()
data class CrossSigningNotConfigured(val userID: String) : DeviceTrustResult()
data class KeysNotTrusted(val key: MXCrossSigningInfo) : DeviceTrustResult()
data class MissingDeviceSignature(val deviceId: String, val signingKey: String) : DeviceTrustResult()
data class InvalidDeviceSignature(val deviceId: String, val signingKey: String, val throwable: Throwable?) : DeviceTrustResult()
}
fun DeviceTrustResult.isSuccess(): Boolean = this is DeviceTrustResult.Success

View file

@ -0,0 +1,29 @@
/*
* Copyright 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.crypto.crosssigning
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.rest.CrossSigningKeyInfo
import im.vector.matrix.android.internal.util.JsonCanonicalizer
fun MXDeviceInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}
fun CrossSigningKeyInfo.canonicalSignable(): String {
return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary())
}

View file

@ -72,6 +72,7 @@ data class CrossSigningKeyInfo(
userMap["ed25519:${signedWithNoPrefix}"] = signature
signatures = updated
}
// fun toXSigningKeys(): XSigningKeys {
// return XSigningKeys(
// userId = userId,

View file

@ -405,6 +405,7 @@ internal interface IMXCryptoStore {
fun setCrossSigningInfo(userId: String, info: MXCrossSigningInfo?)
fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?)
fun getCrossSigningPrivateKeys() : PrivateKeysInfo?
fun setUserKeysAsTrusted(userId: String, trusted: Boolean = true)

View file

@ -303,6 +303,17 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
}
}
override fun storePrivateKeysInfo(msk: String?, usk: String?, ssk: String?) {
doRealmTransaction(realmConfiguration) {realm ->
realm.where<CryptoMetadataEntity>().findFirst()?.apply {
xSignMasterPrivateKey = msk
xSignSelfSignedPrivateKey = ssk
xSignUserPrivateKey = usk
}
}
}
override fun getUserDevices(userId: String): Map<String, MXDeviceInfo>? {
return doRealmQueryAndCopy(realmConfiguration) {
it.where<UserEntity>()

View file

@ -15,6 +15,7 @@
*/
package im.vector.matrix.android.internal.crypto.tasks
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.MXKeysObject
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
@ -36,9 +37,14 @@ internal class DefaultUploadSignaturesTask @Inject constructor(
) : UploadSignaturesTask {
override suspend fun execute(params: UploadSignaturesTask.Params): SignatureUploadResponse {
return executeRequest(eventBus) {
val executeRequest = executeRequest<SignatureUploadResponse>(eventBus) {
apiCall = cryptoApi.uploadSignatures(params.signatures)
}
if (executeRequest.failures?.isNotEmpty() == true) {
//TODO better
throw Failure.OtherServerError(executeRequest.toString(), 400)
}
return executeRequest
}
}

View file

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.verification
import android.util.Base64
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.IncomingSasVerificationTransaction
import im.vector.matrix.android.api.session.crypto.sas.SasMode
@ -31,6 +32,7 @@ internal class DefaultIncomingSASVerificationTransaction(
setDeviceVerificationAction: SetDeviceVerificationAction,
override val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
deviceFingerprint: String,
transactionId: String,
otherUserID: String,
@ -39,6 +41,7 @@ internal class DefaultIncomingSASVerificationTransaction(
setDeviceVerificationAction,
credentials,
cryptoStore,
crossSigningService,
deviceFingerprint,
transactionId,
otherUserID,

View file

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.crypto.verification
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.OutgoingSasVerificationRequest
import im.vector.matrix.android.api.session.crypto.sas.SasVerificationTxState
@ -30,6 +31,7 @@ internal class DefaultOutgoingSASVerificationRequest(
setDeviceVerificationAction: SetDeviceVerificationAction,
credentials: Credentials,
cryptoStore: IMXCryptoStore,
crossSigningService: CrossSigningService,
deviceFingerprint: String,
transactionId: String,
otherUserId: String,
@ -38,6 +40,7 @@ internal class DefaultOutgoingSASVerificationRequest(
setDeviceVerificationAction,
credentials,
cryptoStore,
crossSigningService,
deviceFingerprint,
transactionId,
otherUserId,

View file

@ -21,8 +21,8 @@ import android.os.Looper
import dagger.Lazy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.sessionId
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.sas.*
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType
@ -56,7 +56,8 @@ internal class DefaultSasVerificationService @Inject constructor(
private val setDeviceVerificationAction: SetDeviceVerificationAction,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sasTransportRoomMessageFactory: SasTransportRoomMessageFactory,
private val sasTransportToDeviceFactory: SasTransportToDeviceFactory
private val sasTransportToDeviceFactory: SasTransportToDeviceFactory,
private val crossSigningService: CrossSigningService
) : VerificationTransaction.Listener, SasVerificationService {
private val uiHandler = Handler(Looper.getMainLooper())
@ -375,6 +376,7 @@ internal class DefaultSasVerificationService @Inject constructor(
setDeviceVerificationAction,
credentials,
cryptoStore,
crossSigningService,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
startReq.transactionID!!,
otherUserId,
@ -696,6 +698,7 @@ internal class DefaultSasVerificationService @Inject constructor(
setDeviceVerificationAction,
credentials,
cryptoStore,
crossSigningService,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
txID,
userId,
@ -793,6 +796,7 @@ internal class DefaultSasVerificationService @Inject constructor(
setDeviceVerificationAction,
credentials,
cryptoStore,
crossSigningService,
myDeviceInfoHolder.get().myDevice.fingerprint()!!,
transactionId,
otherUserId,

View file

@ -16,7 +16,9 @@
package im.vector.matrix.android.internal.crypto.verification
import android.os.Build
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService
import im.vector.matrix.android.api.session.crypto.sas.CancelCode
import im.vector.matrix.android.api.session.crypto.sas.EmojiRepresentation
import im.vector.matrix.android.api.session.crypto.sas.SasMode
@ -25,6 +27,7 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.internal.crypto.actions.SetDeviceVerificationAction
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.SignatureUploadResponse
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.extensions.toUnsignedInt
import org.matrix.olm.OlmSAS
@ -39,6 +42,7 @@ internal abstract class SASVerificationTransaction(
private val setDeviceVerificationAction: SetDeviceVerificationAction,
open val credentials: Credentials,
private val cryptoStore: IMXCryptoStore,
private val crossSigningService: CrossSigningService,
private val deviceFingerprint: String,
transactionId: String,
otherUserId: String,
@ -142,18 +146,46 @@ internal abstract class SASVerificationTransaction(
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:${credentials.deviceId}"
val macString = macUsingAgreedMethod(deviceFingerprint, baseInfo + keyId)
val keyStrings = macUsingAgreedMethod(keyId, baseInfo + "KEY_IDS")
if (macString.isNullOrBlank() || keyStrings.isNullOrBlank()) {
if (macString.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, mapOf(keyId to macString), keyStrings)
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.")
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
state = SasVerificationTxState.SendingMac
sendToOther(EventType.KEY_VERIFICATION_MAC, macMsg, SasVerificationTxState.MacSent, CancelCode.User) {
@ -227,7 +259,7 @@ internal abstract class SASVerificationTransaction(
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
val otherDeviceKey = otherUserKnownDevices?.get(keyIDNoPrefix)?.fingerprint()
if (otherDeviceKey == null) {
Timber.e("## SAS Verification: Could not find device $keyIDNoPrefix to verify")
Timber.w("## SAS Verification: Could not find device $keyIDNoPrefix to verify")
// just ignore and continue
return@forEach
}
@ -241,14 +273,46 @@ internal abstract class SASVerificationTransaction(
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
theirMac!!.mac!!.keys.forEach {
val keyIDNoPrefix = if (it.startsWith("ed25519:")) it.substring("ed25519:".length) else it
if (keyIDNoPrefix == otherCrossSigningMasterKeyPublic) {
//Check the signature
val mac = macUsingAgreedMethod(otherCrossSigningMasterKeyPublic, baseInfo + it)
if (mac != theirMac?.mac?.get(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()) {
if (verifiedDevices.isEmpty() && !otherMasterKeyIsVerified) {
Timber.e("## SAS Verification: No devices verified")
cancel(CancelCode.MismatchedKeys)
return
}
if (otherMasterKeyIsVerified) {
// we should trust this master key
// And check verification MSK -> SSK?
crossSigningService.trustUser(otherUserId, object : MatrixCallback<SignatureUploadResponse> {
override fun onFailure(failure: Throwable) {
Timber.e(failure,"## SAS Verification: Failed to trust User $otherUserId")
}
})
}
// TODO what if the otherDevice is not in this list? and should we
verifiedDevices.forEach {
setDeviceVerified(it, otherUserId)