diff --git a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt index 01374bd1c3..be6c89e17c 100644 --- a/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt +++ b/matrix-sdk-android/src/androidTest/java/im/vector/matrix/android/internal/crypto/crosssigning/XSigningTest.kt @@ -45,6 +45,9 @@ class XSigningTest : InstrumentedTest { assertTrue("Signing Keys should be trusted", myCrossSigningKeys?.isTrusted == true) + assertTrue("Signing Keys should be trusted", aliceSession.getCrossSigningService().checkUserTrust(aliceSession.myUserId).isVerified()) + + mTestHelper.signout(aliceSession) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt index fe63681357..b0ff4ade4b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/DefaultCrossSigningService.kt @@ -38,11 +38,15 @@ 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.extensions.foldToCallback 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 import im.vector.matrix.android.internal.util.JsonCanonicalizer +import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import org.matrix.olm.OlmPkSigning import org.matrix.olm.OlmUtility import timber.log.Timber @@ -58,6 +62,8 @@ internal class DefaultCrossSigningService @Inject constructor( private val deviceListManager: DeviceListManager, private val uploadSigningKeysTask: UploadSigningKeysTask, private val uploadSignaturesTask: UploadSignaturesTask, + private val cryptoCoroutineScope: CoroutineScope, + private val coroutineDispatchers: MatrixCoroutineDispatchers, private val taskExecutor: TaskExecutor) : CrossSigningService { private var olmUtility: OlmUtility? = null @@ -200,6 +206,7 @@ internal class DefaultCrossSigningService @Inject constructor( val crossSigningInfo = MXCrossSigningInfo(myUserID, listOf(params.masterKey, params.userKey, params.selfSignedKey)) cryptoStore.setMyCrossSigningInfo(crossSigningInfo) cryptoStore.setUserKeysAsTrusted(myUserID) + cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding()) // TODO we should ensure that they are sent // TODO error handling? @@ -244,6 +251,21 @@ internal class DefaultCrossSigningService @Inject constructor( this.callback = object : MatrixCallback { override fun onSuccess(data: SignatureUploadResponse) { Timber.i("## CrossSigning - signatures succesfuly uploaded") + + // Force download of my keys now + kotlin.runCatching { + cryptoCoroutineScope.launch(coroutineDispatchers.crypto) { + deviceListManager.downloadKeys(listOf(myUserID), true) + } + }.foldToCallback(object : MatrixCallback { + override fun onSuccess(data: Any) { + callback?.onSuccess(Unit) + } + + override fun onFailure(failure: Throwable) { + callback?.onFailure(failure) + } + }) } override fun onFailure(failure: Throwable) { @@ -253,7 +275,6 @@ internal class DefaultCrossSigningService @Inject constructor( }.executeBy(taskExecutor) crossSigningState = CrossSigningState.Trusted - callback?.onSuccess(Unit) } override fun onFailure(failure: Throwable) { @@ -291,6 +312,9 @@ internal class DefaultCrossSigningService @Inject constructor( */ override fun checkUserTrust(userId: String): UserTrustResult { Timber.d("## CrossSigning checkUserTrust for $userId") + if (userId == credentials.userId) { + return checkSelfTrust() + } // I trust a user if I trust his master key // I can trust the master key if it is signed by my user key // TODO what if the master key is signed by a device key that i have verified @@ -328,6 +352,96 @@ internal class DefaultCrossSigningService @Inject constructor( return UserTrustResult.Success } + private fun checkSelfTrust(): 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) + + val myUserId = credentials.userId + val myCrossSigningInfo = cryptoStore.getCrossSigningInfo(myUserId) + + val myMasterKey = myCrossSigningInfo?.masterKey() + ?: return UserTrustResult.CrossSigningNotConfigured(myUserId) + + // Is the master key trusted + // 1) check if I know the private key + val masterPrivateKey = cryptoStore.getCrossSigningPrivateKeys()?.master + + var isMaterKeyTrusted = false + if (masterPrivateKey != null) { + // Check if private match public + var olmPkSigning: OlmPkSigning? = null + try { + olmPkSigning = OlmPkSigning() + val expectedPK = olmPkSigning.initWithSeed(Base64.decode(masterPrivateKey, Base64.NO_PADDING)) + isMaterKeyTrusted = myMasterKey.unpaddedBase64PublicKey == expectedPK + } catch (failure: Throwable) { + olmPkSigning?.releaseSigning() + } + } else { + // Maybe it's signed by a locally trusted device? + myMasterKey.signatures?.get(myUserId)?.forEach { (key, value) -> + val potentialDeviceId = if (key.startsWith("ed25519:")) key.substring("ed25519:".length) else key + val potentialDevice = cryptoStore.getUserDevice(myUserId, potentialDeviceId) + if (potentialDevice != null && potentialDevice.isVerified) { + // Check signature validity? + try { + olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable()) + isMaterKeyTrusted = true + return@forEach + } catch (failure: Throwable) { + // log + Timber.v(failure) + } + } + } + } + + if (!isMaterKeyTrusted) { + return UserTrustResult.KeysNotTrusted(myCrossSigningInfo) + } + + val myUserKey = myCrossSigningInfo.userKey() + ?: return UserTrustResult.CrossSigningNotConfigured(myUserId) + + val userKeySignaturesMadeByMyMasterKey = myUserKey.signatures + ?.get(myUserId) // Signatures made by me + ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") + + if (userKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { + Timber.d("## CrossSigning checkUserTrust false for $userId, USK not signed by MSK") + return UserTrustResult.KeyNotSigned(myUserKey) + } + + // Check that Alice USK signature of Alice MSK is valid + try { + olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable()) + } catch (failure: Throwable) { + return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey) + } + + val mySSKey = myCrossSigningInfo.selfSigningKey() + ?: return UserTrustResult.CrossSigningNotConfigured(myUserId) + + val SSKeySignaturesMadeByMyMasterKey = mySSKey.signatures + ?.get(myUserId) // Signatures made by me + ?.get("ed25519:${myMasterKey.unpaddedBase64PublicKey}") + + if (SSKeySignaturesMadeByMyMasterKey.isNullOrBlank()) { + Timber.d("## CrossSigning checkUserTrust false for $userId, SSK not signed by MSK") + return UserTrustResult.KeyNotSigned(mySSKey) + } + + // Check that Alice USK signature of Alice MSK is valid + try { + olmUtility!!.verifyEd25519Signature(SSKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable()) + } catch (failure: Throwable) { + return UserTrustResult.InvalidSignature(mySSKey, SSKeySignaturesMadeByMyMasterKey) + } + + return UserTrustResult.Success + } + override fun getUserCrossSigningKeys(userId: String): MXCrossSigningInfo? { return cryptoStore.getCrossSigningInfo(userId) } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt index a8369e5c04..3d0f68d4f6 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/Extensions.kt @@ -15,6 +15,7 @@ */ package im.vector.matrix.android.internal.crypto.crosssigning +import android.util.Base64 import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey import im.vector.matrix.android.internal.crypto.model.CryptoDeviceInfo import im.vector.matrix.android.internal.util.JsonCanonicalizer @@ -26,3 +27,7 @@ fun CryptoDeviceInfo.canonicalSignable(): String { fun CryptoCrossSigningKey.canonicalSignable(): String { return JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableJSONDictionary()) } + +fun ByteArray.toBase64NoPadding() : String? { + return Base64.encodeToString(this, Base64.NO_PADDING) +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/UserTrustResult.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/UserTrustResult.kt index cf6964c051..24fdfafa93 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/UserTrustResult.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/crosssigning/UserTrustResult.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.crypto.crosssigning import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo +import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey sealed class UserTrustResult { @@ -31,3 +32,5 @@ sealed class 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