diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt index 28375a091b..50ffb3082a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/CryptoModule.kt @@ -66,6 +66,7 @@ import im.vector.matrix.android.internal.crypto.tasks.DefaultDownloadKeysForUser import im.vector.matrix.android.internal.crypto.tasks.DefaultEncryptEventTask import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDeviceInfoTask import im.vector.matrix.android.internal.crypto.tasks.DefaultGetDevicesTask +import im.vector.matrix.android.internal.crypto.tasks.DefaultInitializeCrossSigningTask import im.vector.matrix.android.internal.crypto.tasks.DefaultSendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.DefaultSendVerificationMessageTask import im.vector.matrix.android.internal.crypto.tasks.DefaultSetDeviceNameTask @@ -78,6 +79,7 @@ import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask import im.vector.matrix.android.internal.crypto.tasks.EncryptEventTask import im.vector.matrix.android.internal.crypto.tasks.GetDeviceInfoTask import im.vector.matrix.android.internal.crypto.tasks.GetDevicesTask +import im.vector.matrix.android.internal.crypto.tasks.InitializeCrossSigningTask import im.vector.matrix.android.internal.crypto.tasks.SendToDeviceTask import im.vector.matrix.android.internal.crypto.tasks.SendVerificationMessageTask import im.vector.matrix.android.internal.crypto.tasks.SetDeviceNameTask @@ -245,4 +247,7 @@ internal abstract class CryptoModule { @Binds abstract fun bindComputeShieldTrustTask(task: DefaultComputeTrustTask): ComputeTrustTask + + @Binds + abstract fun bindInitializeCrossSigningTask(task: DefaultInitializeCrossSigningTask): InitializeCrossSigningTask } 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 54392ad44c..2166e4be3a 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 @@ -17,22 +17,17 @@ package im.vector.matrix.android.internal.crypto.crosssigning import androidx.lifecycle.LiveData -import dagger.Lazy import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.session.crypto.crosssigning.CrossSigningService import im.vector.matrix.android.api.session.crypto.crosssigning.MXCrossSigningInfo import im.vector.matrix.android.api.util.Optional 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.CryptoCrossSigningKey -import im.vector.matrix.android.internal.crypto.model.KeyUsage import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.PrivateKeysInfo +import im.vector.matrix.android.internal.crypto.tasks.InitializeCrossSigningTask 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.UserId import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.task.TaskExecutor @@ -53,12 +48,9 @@ import javax.inject.Inject internal class DefaultCrossSigningService @Inject constructor( @UserId private val userId: String, private val cryptoStore: IMXCryptoStore, - private val myDeviceInfoHolder: Lazy, - private val olmDevice: MXOlmDevice, private val deviceListManager: DeviceListManager, - private val uploadSigningKeysTask: UploadSigningKeysTask, + private val initializeCrossSigningTask: InitializeCrossSigningTask, private val uploadSignaturesTask: UploadSignaturesTask, - private val computeTrustTask: ComputeTrustTask, private val taskExecutor: TaskExecutor, private val coroutineDispatchers: MatrixCoroutineDispatchers, private val cryptoCoroutineScope: CoroutineScope, @@ -151,153 +143,31 @@ internal class DefaultCrossSigningService @Inject constructor( override fun initializeCrossSigning(authParams: UserPasswordAuth?, callback: MatrixCallback?) { Timber.d("## CrossSigning initializeCrossSigning") - // ================= - // MASTER KEY - // ================= - val masterPkOlm = OlmPkSigning() - val masterKeyPrivateKey = OlmPkSigning.generateSeed() - val masterPublicKey = masterPkOlm.initWithSeed(masterKeyPrivateKey) - - Timber.v("## CrossSigning - masterPublicKey:$masterPublicKey") - - // ================= - // USER KEY - // ================= - val userSigningPkOlm = OlmPkSigning() - val uskPrivateKey = OlmPkSigning.generateSeed() - val uskPublicKey = userSigningPkOlm.initWithSeed(uskPrivateKey) - - Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey") - - // Sign userSigningKey with master - val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) - .key(uskPublicKey) - .build() - .canonicalSignable() - .let { masterPkOlm.sign(it) } - - // ================= - // SELF SIGNING KEY - // ================= - val selfSigningPkOlm = OlmPkSigning() - val sskPrivateKey = OlmPkSigning.generateSeed() - val sskPublicKey = selfSigningPkOlm.initWithSeed(sskPrivateKey) - - Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") - - // Sign userSigningKey with master - val signedSSK = JsonCanonicalizer.getCanonicalJson(Map::class.java, CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) - .key(sskPublicKey) - .build().signalableJSONDictionary()).let { masterPkOlm.sign(it) } - - // I need to upload the keys - val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER) - .key(masterPublicKey) - .build() - val params = UploadSigningKeysTask.Params( - masterKey = mskCrossSigningKeyInfo, - userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) - .key(uskPublicKey) - .signature(userId, masterPublicKey, signedUSK) - .build(), - selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) - .key(sskPublicKey) - .signature(userId, masterPublicKey, signedSSK) - .build(), - userPasswordAuth = authParams + val params = InitializeCrossSigningTask.Params( + authParams = authParams ) - - this.masterPkSigning = masterPkOlm - this.userPkSigning = userSigningPkOlm - this.selfSigningPkSigning = selfSigningPkOlm - - val crossSigningInfo = MXCrossSigningInfo(userId, listOf(params.masterKey, params.userKey, params.selfSignedKey)) - cryptoStore.setMyCrossSigningInfo(crossSigningInfo) - setUserKeysAsTrusted(userId, true) - cryptoStore.storePrivateKeysInfo(masterKeyPrivateKey?.toBase64NoPadding(), uskPrivateKey?.toBase64NoPadding(), sskPrivateKey?.toBase64NoPadding()) - - uploadSigningKeysTask.configureWith(params) { - this.executionThread = TaskThread.CRYPTO - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.i("## CrossSigning - Keys successfully uploaded") - - // Sign the current device with SSK - val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder() - - val myDevice = myDeviceInfoHolder.get().myDevice - val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) - val signedDevice = selfSigningPkOlm.sign(canonicalJson) - val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()) - .also { - it[userId] = (it[userId] - ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) - } - myDevice.copy(signatures = updateSignatures).let { - uploadSignatureQueryBuilder.withDeviceInfo(it) - } - - // sign MSK with device key (migration) and upload signatures - val message = JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()) - olmDevice.signMessage(message)?.let { sign -> - val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() - ?: HashMap()).also { - it[userId] = (it[userId] - ?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign) - } - mskCrossSigningKeyInfo.copy( - signatures = mskUpdatedSignatures - ).let { - uploadSignatureQueryBuilder.withSigningKeyInfo(it) - } - } - - resetTrustOnKeyChange() - uploadSignaturesTask.configureWith(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) { - // this.retryCount = 3 - this.executionThread = TaskThread.CRYPTO - this.callback = object : MatrixCallback { - override fun onSuccess(data: Unit) { - Timber.i("## CrossSigning - signatures successfully uploaded") - callback?.onSuccess(Unit) - } - - override fun onFailure(failure: Throwable) { - // Clear - Timber.e(failure, "## CrossSigning - Failed to upload signatures") - clearSigningKeys() - } - } - }.executeBy(taskExecutor) + initializeCrossSigningTask.configureWith(params) { + this.callbackThread = TaskThread.CRYPTO + this.callback = object : MatrixCallback { + override fun onFailure(failure: Throwable) { + callback?.onFailure(failure) } - override fun onFailure(failure: Throwable) { - Timber.e(failure, "## CrossSigning - Failed to upload signing keys") - clearSigningKeys() - callback?.onFailure(failure) + override fun onSuccess(data: InitializeCrossSigningTask.Result) { + val crossSigningInfo = MXCrossSigningInfo(userId, listOf(data.masterKeyInfo, data.userKeyInfo, data.selfSignedKeyInfo)) + cryptoStore.setMyCrossSigningInfo(crossSigningInfo) + setUserKeysAsTrusted(userId, true) + cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK) + masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) } + userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) } + selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) } + + callback?.onSuccess(Unit) } } }.executeBy(taskExecutor) } - private fun clearSigningKeys() { - masterPkSigning?.releaseSigning() - userPkSigning?.releaseSigning() - selfSigningPkSigning?.releaseSigning() - - masterPkSigning = null - userPkSigning = null - selfSigningPkSigning = null - - cryptoStore.setMyCrossSigningInfo(null) - cryptoStore.storePrivateKeysInfo(null, null, null) - } - - private fun resetTrustOnKeyChange() { - Timber.i("## CrossSigning - Clear all other user trust") - cryptoStore.clearOtherUserTrust() - } - override fun onSecretSSKGossip(sskPrivateKey: String) { Timber.i("## CrossSigning - onSecretSSKGossip") val mxCrossSigningInfo = getMyCrossSigningKeys() ?: return Unit.also { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt new file mode 100644 index 0000000000..9a7d84e235 --- /dev/null +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tasks/InitializeCrossSigningTask.kt @@ -0,0 +1,172 @@ +/* + * Copyright (c) 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.tasks + +import dagger.Lazy +import im.vector.matrix.android.internal.crypto.MXOlmDevice +import im.vector.matrix.android.internal.crypto.MyDeviceInfoHolder +import im.vector.matrix.android.internal.crypto.crosssigning.canonicalSignable +import im.vector.matrix.android.internal.crypto.crosssigning.toBase64NoPadding +import im.vector.matrix.android.internal.crypto.model.CryptoCrossSigningKey +import im.vector.matrix.android.internal.crypto.model.KeyUsage +import im.vector.matrix.android.internal.crypto.model.rest.UploadSignatureQueryBuilder +import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth +import im.vector.matrix.android.internal.di.UserId +import im.vector.matrix.android.internal.task.Task +import im.vector.matrix.android.internal.util.JsonCanonicalizer +import org.matrix.olm.OlmPkSigning +import timber.log.Timber +import javax.inject.Inject + +internal interface InitializeCrossSigningTask : Task { + data class Params( + val authParams: UserPasswordAuth? + ) + + data class Result( + val masterKeyPK: String, + val userKeyPK: String, + val selfSigningKeyPK: String, + val masterKeyInfo: CryptoCrossSigningKey, + val userKeyInfo: CryptoCrossSigningKey, + val selfSignedKeyInfo: CryptoCrossSigningKey + ) +} + +internal class DefaultInitializeCrossSigningTask @Inject constructor( + @UserId private val userId: String, + private val olmDevice: MXOlmDevice, + private val myDeviceInfoHolder: Lazy, + private val uploadSigningKeysTask: UploadSigningKeysTask, + private val uploadSignaturesTask: UploadSignaturesTask +) : InitializeCrossSigningTask { + + override suspend fun execute(params: InitializeCrossSigningTask.Params): InitializeCrossSigningTask.Result { + var masterPkOlm: OlmPkSigning? = null + var userSigningPkOlm: OlmPkSigning? = null + var selfSigningPkOlm: OlmPkSigning? = null + + try { + // ================= + // MASTER KEY + // ================= + + masterPkOlm = OlmPkSigning() + val masterKeyPrivateKey = OlmPkSigning.generateSeed() + val masterPublicKey = masterPkOlm.initWithSeed(masterKeyPrivateKey) + + Timber.v("## CrossSigning - masterPublicKey:$masterPublicKey") + + // ================= + // USER KEY + // ================= + userSigningPkOlm = OlmPkSigning() + val uskPrivateKey = OlmPkSigning.generateSeed() + val uskPublicKey = userSigningPkOlm.initWithSeed(uskPrivateKey) + + Timber.v("## CrossSigning - uskPublicKey:$uskPublicKey") + + // Sign userSigningKey with master + val signedUSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) + .key(uskPublicKey) + .build() + .canonicalSignable() + .let { masterPkOlm.sign(it) } + + // ================= + // SELF SIGNING KEY + // ================= + selfSigningPkOlm = OlmPkSigning() + val sskPrivateKey = OlmPkSigning.generateSeed() + val sskPublicKey = selfSigningPkOlm.initWithSeed(sskPrivateKey) + + Timber.v("## CrossSigning - sskPublicKey:$sskPublicKey") + + // Sign userSigningKey with master + val signedSSK = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) + .key(sskPublicKey) + .build() + .canonicalSignable() + .let { masterPkOlm.sign(it) } + + // I need to upload the keys + val mskCrossSigningKeyInfo = CryptoCrossSigningKey.Builder(userId, KeyUsage.MASTER) + .key(masterPublicKey) + .build() + val uploadSigningKeysParams = UploadSigningKeysTask.Params( + masterKey = mskCrossSigningKeyInfo, + userKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.USER_SIGNING) + .key(uskPublicKey) + .signature(userId, masterPublicKey, signedUSK) + .build(), + selfSignedKey = CryptoCrossSigningKey.Builder(userId, KeyUsage.SELF_SIGNING) + .key(sskPublicKey) + .signature(userId, masterPublicKey, signedSSK) + .build(), + userPasswordAuth = params.authParams + ) + + uploadSigningKeysTask.execute(uploadSigningKeysParams) + + // Sign the current device with SSK + val uploadSignatureQueryBuilder = UploadSignatureQueryBuilder() + + val myDevice = myDeviceInfoHolder.get().myDevice + val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, myDevice.signalableJSONDictionary()) + val signedDevice = selfSigningPkOlm.sign(canonicalJson) + val updateSignatures = (myDevice.signatures?.toMutableMap() ?: HashMap()) + .also { + it[userId] = (it[userId] + ?: HashMap()) + mapOf("ed25519:$sskPublicKey" to signedDevice) + } + myDevice.copy(signatures = updateSignatures).let { + uploadSignatureQueryBuilder.withDeviceInfo(it) + } + + // sign MSK with device key (migration) and upload signatures + val message = JsonCanonicalizer.getCanonicalJson(Map::class.java, mskCrossSigningKeyInfo.signalableJSONDictionary()) + olmDevice.signMessage(message)?.let { sign -> + val mskUpdatedSignatures = (mskCrossSigningKeyInfo.signatures?.toMutableMap() + ?: HashMap()).also { + it[userId] = (it[userId] + ?: HashMap()) + mapOf("ed25519:${myDevice.deviceId}" to sign) + } + mskCrossSigningKeyInfo.copy( + signatures = mskUpdatedSignatures + ).let { + uploadSignatureQueryBuilder.withSigningKeyInfo(it) + } + } + + // TODO should we ignore failure of that? + uploadSignaturesTask.execute(UploadSignaturesTask.Params(uploadSignatureQueryBuilder.build())) + + return InitializeCrossSigningTask.Result( + masterKeyPK = masterKeyPrivateKey.toBase64NoPadding(), + userKeyPK = uskPrivateKey.toBase64NoPadding(), + selfSigningKeyPK = sskPrivateKey.toBase64NoPadding(), + masterKeyInfo = uploadSigningKeysParams.masterKey, + userKeyInfo = uploadSigningKeysParams.userKey, + selfSignedKeyInfo = uploadSigningKeysParams.selfSignedKey + ) + } finally { + masterPkOlm?.releaseSigning() + userSigningPkOlm?.releaseSigning() + selfSigningPkOlm?.releaseSigning() + } + } +} diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt index 260e6165ba..c3d2c30079 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/tools/Tools.kt @@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.crypto.tools import org.matrix.olm.OlmPkDecryption import org.matrix.olm.OlmPkEncryption +import org.matrix.olm.OlmPkSigning fun withOlmEncryption(block: (OlmPkEncryption) -> T): T { val olmPkEncryption = OlmPkEncryption() @@ -36,3 +37,12 @@ fun withOlmDecryption(block: (OlmPkDecryption) -> T): T { olmPkDecryption.releaseDecryption() } } + +fun withOlmSigning(block: (OlmPkSigning) -> T): T { + val olmPkSigning = OlmPkSigning() + try { + return block(olmPkSigning) + } finally { + olmPkSigning.releaseSigning() + } +}