Prevent 4S / megolm backup desync + sign with MSK
This commit is contained in:
parent
3f8ddbec60
commit
22e0506814
1
changelog.d/5906.bugfix
Normal file
1
changelog.d/5906.bugfix
Normal file
@ -0,0 +1 @@
|
|||||||
|
Desynchronized 4S | Megolm backup causing Unusable backup
|
@ -16,25 +16,35 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
package org.matrix.android.sdk.api.session.crypto.keysbackup
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.CryptoCrossSigningKey
|
||||||
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A signature in a `KeysBackupVersionTrust` object.
|
* A signature in a `KeysBackupVersionTrust` object.
|
||||||
*/
|
*/
|
||||||
data class KeysBackupVersionTrustSignature(
|
|
||||||
/**
|
|
||||||
* The id of the device that signed the backup version.
|
|
||||||
*/
|
|
||||||
val deviceId: String?,
|
|
||||||
|
|
||||||
/**
|
sealed class KeysBackupVersionTrustSignature {
|
||||||
* The device that signed the backup version.
|
|
||||||
* Can be null if the device is not known.
|
|
||||||
*/
|
|
||||||
val device: CryptoDeviceInfo?,
|
|
||||||
|
|
||||||
/**
|
data class DeviceSignature(
|
||||||
* Flag to indicate the signature from this device is valid.
|
/**
|
||||||
*/
|
* The id of the device that signed the backup version.
|
||||||
val valid: Boolean,
|
*/
|
||||||
)
|
val deviceId: String?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device that signed the backup version.
|
||||||
|
* Can be null if the device is not known.
|
||||||
|
*/
|
||||||
|
val device: CryptoDeviceInfo?,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to indicate the signature from this device is valid.
|
||||||
|
*/
|
||||||
|
val valid: Boolean) : KeysBackupVersionTrustSignature()
|
||||||
|
|
||||||
|
data class UserSignature(
|
||||||
|
val keyId: String?,
|
||||||
|
val cryptoCrossSigningKey: CryptoCrossSigningKey?,
|
||||||
|
val valid: Boolean
|
||||||
|
) : KeysBackupVersionTrustSignature()
|
||||||
|
}
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.crosssigning
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.util.JsonDict
|
||||||
|
import org.matrix.android.sdk.internal.crypto.store.IMXCryptoStore
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
||||||
|
import org.matrix.olm.OlmPkSigning
|
||||||
|
import org.matrix.olm.OlmUtility
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Holds the OlmPkSigning for cross signing.
|
||||||
|
* Can be injected without having to get the full cross signing service
|
||||||
|
*/
|
||||||
|
@SessionScope
|
||||||
|
internal class CrossSigningOlm @Inject constructor(
|
||||||
|
private val cryptoStore: IMXCryptoStore,
|
||||||
|
) {
|
||||||
|
|
||||||
|
enum class KeyType {
|
||||||
|
SELF,
|
||||||
|
USER,
|
||||||
|
MASTER
|
||||||
|
}
|
||||||
|
|
||||||
|
var olmUtility: OlmUtility = OlmUtility()
|
||||||
|
|
||||||
|
var masterPkSigning: OlmPkSigning? = null
|
||||||
|
var userPkSigning: OlmPkSigning? = null
|
||||||
|
var selfSigningPkSigning: OlmPkSigning? = null
|
||||||
|
|
||||||
|
fun release() {
|
||||||
|
olmUtility.releaseUtility()
|
||||||
|
listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun signObject(type: KeyType, strToSign: String): Map<String, String> {
|
||||||
|
val myKeys = cryptoStore.getMyCrossSigningInfo()
|
||||||
|
val pubKey = when (type) {
|
||||||
|
KeyType.SELF -> myKeys?.selfSigningKey()
|
||||||
|
KeyType.USER -> myKeys?.userKey()
|
||||||
|
KeyType.MASTER -> myKeys?.masterKey()
|
||||||
|
}?.unpaddedBase64PublicKey
|
||||||
|
val pkSigning = when (type) {
|
||||||
|
KeyType.SELF -> selfSigningPkSigning
|
||||||
|
KeyType.USER -> userPkSigning
|
||||||
|
KeyType.MASTER -> masterPkSigning
|
||||||
|
}
|
||||||
|
if (pubKey == null || pkSigning == null) {
|
||||||
|
throw Throwable("Cannot sign from this account, public and/or privateKey Unknown $type|$pkSigning")
|
||||||
|
}
|
||||||
|
val signature = pkSigning.sign(strToSign)
|
||||||
|
return mapOf(
|
||||||
|
"ed25519:$pubKey" to signature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun verifySignature(type: KeyType, signable: JsonDict, signatures: Map<String, Map<String, String>>) {
|
||||||
|
val myKeys = cryptoStore.getMyCrossSigningInfo()
|
||||||
|
?: throw NoSuchElementException("Cross Signing not configured")
|
||||||
|
val myUserID = myKeys.userId
|
||||||
|
val pubKey = when (type) {
|
||||||
|
KeyType.SELF -> myKeys.selfSigningKey()
|
||||||
|
KeyType.USER -> myKeys.userKey()
|
||||||
|
KeyType.MASTER -> myKeys.masterKey()
|
||||||
|
}?.unpaddedBase64PublicKey ?: throw NoSuchElementException("Cross Signing not configured")
|
||||||
|
val signaturesMadeByMyKey = signatures[myUserID] // Signatures made by me
|
||||||
|
?.get("ed25519:$pubKey")
|
||||||
|
|
||||||
|
if (signaturesMadeByMyKey.isNullOrBlank()) {
|
||||||
|
throw IllegalArgumentException("Not signed with my key $type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that Alice USK signature of Bob MSK is valid
|
||||||
|
olmUtility.verifyEd25519Signature(signaturesMadeByMyKey, pubKey, JsonCanonicalizer.getCanonicalJson(Map::class.java, signable))
|
||||||
|
}
|
||||||
|
}
|
@ -54,7 +54,6 @@ import org.matrix.android.sdk.internal.util.JsonCanonicalizer
|
|||||||
import org.matrix.android.sdk.internal.util.logLimit
|
import org.matrix.android.sdk.internal.util.logLimit
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.olm.OlmPkSigning
|
import org.matrix.olm.OlmPkSigning
|
||||||
import org.matrix.olm.OlmUtility
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -72,19 +71,13 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
private val cryptoCoroutineScope: CoroutineScope,
|
private val cryptoCoroutineScope: CoroutineScope,
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
|
private val outgoingKeyRequestManager: OutgoingKeyRequestManager,
|
||||||
|
private val crossSigningOlm: CrossSigningOlm,
|
||||||
private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
|
private val updateTrustWorkerDataRepository: UpdateTrustWorkerDataRepository
|
||||||
) : CrossSigningService,
|
) : CrossSigningService,
|
||||||
DeviceListManager.UserDevicesUpdateListener {
|
DeviceListManager.UserDevicesUpdateListener {
|
||||||
|
|
||||||
private var olmUtility: OlmUtility? = null
|
|
||||||
|
|
||||||
private var masterPkSigning: OlmPkSigning? = null
|
|
||||||
private var userPkSigning: OlmPkSigning? = null
|
|
||||||
private var selfSigningPkSigning: OlmPkSigning? = null
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
olmUtility = OlmUtility()
|
|
||||||
|
|
||||||
// Try to get stored keys if they exist
|
// Try to get stored keys if they exist
|
||||||
cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo ->
|
cryptoStore.getMyCrossSigningInfo()?.let { mxCrossSigningInfo ->
|
||||||
@ -97,7 +90,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
masterPkSigning = pkSigning
|
crossSigningOlm.masterPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading master key success")
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## CrossSigning - Public master key does not match the private key")
|
Timber.w("## CrossSigning - Public master key does not match the private key")
|
||||||
@ -110,7 +103,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
userPkSigning = pkSigning
|
crossSigningOlm.userPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading User Signing key success")
|
Timber.i("## CrossSigning - Loading User Signing key success")
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## CrossSigning - Public User key does not match the private key")
|
Timber.w("## CrossSigning - Public User key does not match the private key")
|
||||||
@ -123,7 +116,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
?.let { privateKeySeed ->
|
?.let { privateKeySeed ->
|
||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
selfSigningPkSigning = pkSigning
|
crossSigningOlm.selfSigningPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading Self Signing key success")
|
Timber.i("## CrossSigning - Loading Self Signing key success")
|
||||||
} else {
|
} else {
|
||||||
Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
|
Timber.w("## CrossSigning - Public Self Signing key does not match the private key")
|
||||||
@ -145,8 +138,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun release() {
|
fun release() {
|
||||||
olmUtility?.releaseUtility()
|
crossSigningOlm.release()
|
||||||
listOf(masterPkSigning, userPkSigning, selfSigningPkSigning).forEach { it?.releaseSigning() }
|
|
||||||
deviceListManager.removeListener(this)
|
deviceListManager.removeListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,9 +171,9 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
|
cryptoStore.setMyCrossSigningInfo(crossSigningInfo)
|
||||||
setUserKeysAsTrusted(userId, true)
|
setUserKeysAsTrusted(userId, true)
|
||||||
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
|
cryptoStore.storePrivateKeysInfo(data.masterKeyPK, data.userKeyPK, data.selfSigningKeyPK)
|
||||||
masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
|
crossSigningOlm.masterPkSigning = OlmPkSigning().apply { initWithSeed(data.masterKeyPK.fromBase64()) }
|
||||||
userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
|
crossSigningOlm.userPkSigning = OlmPkSigning().apply { initWithSeed(data.userKeyPK.fromBase64()) }
|
||||||
selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
|
crossSigningOlm.selfSigningPkSigning = OlmPkSigning().apply { initWithSeed(data.selfSigningKeyPK.fromBase64()) }
|
||||||
|
|
||||||
callback.onSuccess(Unit)
|
callback.onSuccess(Unit)
|
||||||
}
|
}
|
||||||
@ -200,8 +192,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
masterPkSigning?.releaseSigning()
|
crossSigningOlm.masterPkSigning?.releaseSigning()
|
||||||
masterPkSigning = pkSigning
|
crossSigningOlm.masterPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading MSK success")
|
Timber.i("## CrossSigning - Loading MSK success")
|
||||||
cryptoStore.storeMSKPrivateKey(mskPrivateKey)
|
cryptoStore.storeMSKPrivateKey(mskPrivateKey)
|
||||||
return
|
return
|
||||||
@ -227,8 +219,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
selfSigningPkSigning?.releaseSigning()
|
crossSigningOlm.selfSigningPkSigning?.releaseSigning()
|
||||||
selfSigningPkSigning = pkSigning
|
crossSigningOlm.selfSigningPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading SSK success")
|
Timber.i("## CrossSigning - Loading SSK success")
|
||||||
cryptoStore.storeSSKPrivateKey(sskPrivateKey)
|
cryptoStore.storeSSKPrivateKey(sskPrivateKey)
|
||||||
return
|
return
|
||||||
@ -254,8 +246,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
userPkSigning?.releaseSigning()
|
crossSigningOlm.userPkSigning?.releaseSigning()
|
||||||
userPkSigning = pkSigning
|
crossSigningOlm.userPkSigning = pkSigning
|
||||||
Timber.i("## CrossSigning - Loading USK success")
|
Timber.i("## CrossSigning - Loading USK success")
|
||||||
cryptoStore.storeUSKPrivateKey(uskPrivateKey)
|
cryptoStore.storeUSKPrivateKey(uskPrivateKey)
|
||||||
return
|
return
|
||||||
@ -284,8 +276,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.masterKey()?.unpaddedBase64PublicKey) {
|
||||||
masterPkSigning?.releaseSigning()
|
crossSigningOlm.masterPkSigning?.releaseSigning()
|
||||||
masterPkSigning = pkSigning
|
crossSigningOlm.masterPkSigning = pkSigning
|
||||||
masterKeyIsTrusted = true
|
masterKeyIsTrusted = true
|
||||||
Timber.i("## CrossSigning - Loading master key success")
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
} else {
|
} else {
|
||||||
@ -301,8 +293,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.userKey()?.unpaddedBase64PublicKey) {
|
||||||
userPkSigning?.releaseSigning()
|
crossSigningOlm.userPkSigning?.releaseSigning()
|
||||||
userPkSigning = pkSigning
|
crossSigningOlm.userPkSigning = pkSigning
|
||||||
userKeyIsTrusted = true
|
userKeyIsTrusted = true
|
||||||
Timber.i("## CrossSigning - Loading master key success")
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
} else {
|
} else {
|
||||||
@ -318,8 +310,8 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val pkSigning = OlmPkSigning()
|
val pkSigning = OlmPkSigning()
|
||||||
try {
|
try {
|
||||||
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
if (pkSigning.initWithSeed(privateKeySeed) == mxCrossSigningInfo.selfSigningKey()?.unpaddedBase64PublicKey) {
|
||||||
selfSigningPkSigning?.releaseSigning()
|
crossSigningOlm.selfSigningPkSigning?.releaseSigning()
|
||||||
selfSigningPkSigning = pkSigning
|
crossSigningOlm.selfSigningPkSigning = pkSigning
|
||||||
selfSignedKeyIsTrusted = true
|
selfSignedKeyIsTrusted = true
|
||||||
Timber.i("## CrossSigning - Loading master key success")
|
Timber.i("## CrossSigning - Loading master key success")
|
||||||
} else {
|
} else {
|
||||||
@ -407,7 +399,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
// Check that Alice USK signature of Bob MSK is valid
|
// Check that Alice USK signature of Bob MSK is valid
|
||||||
try {
|
try {
|
||||||
olmUtility!!.verifyEd25519Signature(masterKeySignaturesMadeByMyUserKey, myUserKey.unpaddedBase64PublicKey, otherMasterKey.canonicalSignable())
|
crossSigningOlm.olmUtility.verifyEd25519Signature(
|
||||||
|
masterKeySignaturesMadeByMyUserKey,
|
||||||
|
myUserKey.unpaddedBase64PublicKey,
|
||||||
|
otherMasterKey.canonicalSignable()
|
||||||
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
return UserTrustResult.InvalidSignature(myUserKey, masterKeySignaturesMadeByMyUserKey)
|
||||||
}
|
}
|
||||||
@ -461,7 +457,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
if (potentialDevice != null && potentialDevice.isVerified) {
|
if (potentialDevice != null && potentialDevice.isVerified) {
|
||||||
// Check signature validity?
|
// Check signature validity?
|
||||||
try {
|
try {
|
||||||
olmUtility?.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable())
|
crossSigningOlm.olmUtility.verifyEd25519Signature(value, potentialDevice.fingerprint(), myMasterKey.canonicalSignable())
|
||||||
isMaterKeyTrusted = true
|
isMaterKeyTrusted = true
|
||||||
return@forEach
|
return@forEach
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
@ -490,7 +486,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
// Check that Alice USK signature of Alice MSK is valid
|
// Check that Alice USK signature of Alice MSK is valid
|
||||||
try {
|
try {
|
||||||
olmUtility!!.verifyEd25519Signature(userKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, myUserKey.canonicalSignable())
|
crossSigningOlm.olmUtility.verifyEd25519Signature(
|
||||||
|
userKeySignaturesMadeByMyMasterKey,
|
||||||
|
myMasterKey.unpaddedBase64PublicKey,
|
||||||
|
myUserKey.canonicalSignable()
|
||||||
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
return UserTrustResult.InvalidSignature(myUserKey, userKeySignaturesMadeByMyMasterKey)
|
||||||
}
|
}
|
||||||
@ -509,7 +509,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
// Check that Alice USK signature of Alice MSK is valid
|
// Check that Alice USK signature of Alice MSK is valid
|
||||||
try {
|
try {
|
||||||
olmUtility!!.verifyEd25519Signature(ssKeySignaturesMadeByMyMasterKey, myMasterKey.unpaddedBase64PublicKey, mySSKey.canonicalSignable())
|
crossSigningOlm.olmUtility.verifyEd25519Signature(
|
||||||
|
ssKeySignaturesMadeByMyMasterKey,
|
||||||
|
myMasterKey.unpaddedBase64PublicKey,
|
||||||
|
mySSKey.canonicalSignable()
|
||||||
|
)
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
return UserTrustResult.InvalidSignature(mySSKey, ssKeySignaturesMadeByMyMasterKey)
|
||||||
}
|
}
|
||||||
@ -562,7 +566,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
|
val userPubKey = myKeys.userKey()?.unpaddedBase64PublicKey
|
||||||
if (userPubKey == null || userPkSigning == null) {
|
if (userPubKey == null || crossSigningOlm.userPkSigning == null) {
|
||||||
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
|
callback.onFailure(Throwable("## CrossSigning - Cannot sign from this account, privateKeyUnknown $userPubKey"))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
@ -571,7 +575,7 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
val newSignature = JsonCanonicalizer.getCanonicalJson(
|
val newSignature = JsonCanonicalizer.getCanonicalJson(
|
||||||
Map::class.java,
|
Map::class.java,
|
||||||
otherMasterKeys.signalableJSONDictionary()
|
otherMasterKeys.signalableJSONDictionary()
|
||||||
).let { userPkSigning?.sign(it) }
|
).let { crossSigningOlm.userPkSigning?.sign(it) }
|
||||||
|
|
||||||
if (newSignature == null) {
|
if (newSignature == null) {
|
||||||
// race??
|
// race??
|
||||||
@ -618,13 +622,13 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
val ssPubKey = myKeys.selfSigningKey()?.unpaddedBase64PublicKey
|
||||||
if (ssPubKey == null || selfSigningPkSigning == null) {
|
if (ssPubKey == null || crossSigningOlm.selfSigningPkSigning == null) {
|
||||||
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
|
callback.onFailure(Throwable("Cannot sign from this account, public and/or privateKey Unknown $ssPubKey"))
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sign with self signing
|
// Sign with self signing
|
||||||
val newSignature = selfSigningPkSigning?.sign(device.canonicalSignable())
|
val newSignature = crossSigningOlm.selfSigningPkSigning?.sign(device.canonicalSignable())
|
||||||
|
|
||||||
if (newSignature == null) {
|
if (newSignature == null) {
|
||||||
// race??
|
// race??
|
||||||
@ -697,7 +701,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
// Check bob's device is signed by bob's SSK
|
// Check bob's device is signed by bob's SSK
|
||||||
try {
|
try {
|
||||||
olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
|
crossSigningOlm.olmUtility.verifyEd25519Signature(
|
||||||
|
otherSSKSignature,
|
||||||
|
otherKeys.selfSigningKey()?.unpaddedBase64PublicKey,
|
||||||
|
otherDevice.canonicalSignable()
|
||||||
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e))
|
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDeviceId, otherSSKSignature, e))
|
||||||
}
|
}
|
||||||
@ -747,7 +755,11 @@ internal class DefaultCrossSigningService @Inject constructor(
|
|||||||
|
|
||||||
// Check bob's device is signed by bob's SSK
|
// Check bob's device is signed by bob's SSK
|
||||||
try {
|
try {
|
||||||
olmUtility!!.verifyEd25519Signature(otherSSKSignature, otherKeys.selfSigningKey()?.unpaddedBase64PublicKey, otherDevice.canonicalSignable())
|
crossSigningOlm.olmUtility.verifyEd25519Signature(
|
||||||
|
otherSSKSignature,
|
||||||
|
otherKeys.selfSigningKey()?.unpaddedBase64PublicKey,
|
||||||
|
otherDevice.canonicalSignable()
|
||||||
|
)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e))
|
return legacyFallbackTrust(locallyTrusted, DeviceTrustResult.InvalidDeviceSignature(otherDevice.deviceId, otherSSKSignature, e))
|
||||||
}
|
}
|
||||||
|
@ -54,6 +54,7 @@ import org.matrix.android.sdk.internal.crypto.MXOlmDevice
|
|||||||
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
import org.matrix.android.sdk.internal.crypto.MegolmSessionData
|
||||||
import org.matrix.android.sdk.internal.crypto.ObjectSigner
|
import org.matrix.android.sdk.internal.crypto.ObjectSigner
|
||||||
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
import org.matrix.android.sdk.internal.crypto.actions.MegolmSessionDataImporter
|
||||||
|
import org.matrix.android.sdk.internal.crypto.crosssigning.CrossSigningOlm
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.SignalableMegolmBackupAuthData
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.BackupKeysResult
|
||||||
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
|
import org.matrix.android.sdk.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
|
||||||
@ -102,6 +103,7 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
private val cryptoStore: IMXCryptoStore,
|
private val cryptoStore: IMXCryptoStore,
|
||||||
private val olmDevice: MXOlmDevice,
|
private val olmDevice: MXOlmDevice,
|
||||||
private val objectSigner: ObjectSigner,
|
private val objectSigner: ObjectSigner,
|
||||||
|
private val crossSigningOlm: CrossSigningOlm,
|
||||||
// Actions
|
// Actions
|
||||||
private val megolmSessionDataImporter: MegolmSessionDataImporter,
|
private val megolmSessionDataImporter: MegolmSessionDataImporter,
|
||||||
// Tasks
|
// Tasks
|
||||||
@ -178,7 +180,6 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
|
val generatePrivateKeyResult = generatePrivateKeyWithPassword(password, backgroundProgressListener)
|
||||||
SignalableMegolmBackupAuthData(
|
SignalableMegolmBackupAuthData(
|
||||||
publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
|
publicKey = olmPkDecryption.setPrivateKey(generatePrivateKeyResult.privateKey),
|
||||||
@ -187,7 +188,6 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val publicKey = olmPkDecryption.generateKey()
|
val publicKey = olmPkDecryption.generateKey()
|
||||||
|
|
||||||
SignalableMegolmBackupAuthData(
|
SignalableMegolmBackupAuthData(
|
||||||
publicKey = publicKey
|
publicKey = publicKey
|
||||||
)
|
)
|
||||||
@ -195,13 +195,28 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
|
|
||||||
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary())
|
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, signalableMegolmBackupAuthData.signalableJSONDictionary())
|
||||||
|
|
||||||
|
val signatures = mutableMapOf<String, MutableMap<String, String>>()
|
||||||
|
|
||||||
|
val deviceSignature = objectSigner.signObject(canonicalJson)
|
||||||
|
deviceSignature.forEach { (userID, content) ->
|
||||||
|
signatures[userID] = content.toMutableMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have cross signing add signature, will throw if cross signing not properly configured
|
||||||
|
try {
|
||||||
|
val crossSign = crossSigningOlm.signObject(CrossSigningOlm.KeyType.MASTER, canonicalJson)
|
||||||
|
signatures[credentials.userId]?.putAll(crossSign)
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
// ignore and log
|
||||||
|
Timber.w(failure, "prepareKeysBackupVersion: failed to sign with cross signing keys")
|
||||||
|
}
|
||||||
|
|
||||||
val signedMegolmBackupAuthData = MegolmBackupAuthData(
|
val signedMegolmBackupAuthData = MegolmBackupAuthData(
|
||||||
publicKey = signalableMegolmBackupAuthData.publicKey,
|
publicKey = signalableMegolmBackupAuthData.publicKey,
|
||||||
privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt,
|
privateKeySalt = signalableMegolmBackupAuthData.privateKeySalt,
|
||||||
privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations,
|
privateKeyIterations = signalableMegolmBackupAuthData.privateKeyIterations,
|
||||||
signatures = objectSigner.signObject(canonicalJson)
|
signatures = signatures
|
||||||
)
|
)
|
||||||
|
|
||||||
val creationInfo = MegolmBackupCreationInfo(
|
val creationInfo = MegolmBackupCreationInfo(
|
||||||
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
algorithm = MXCRYPTO_ALGORITHM_MEGOLM_BACKUP,
|
||||||
authData = signedMegolmBackupAuthData,
|
authData = signedMegolmBackupAuthData,
|
||||||
@ -420,18 +435,41 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
|
|
||||||
for ((keyId, mySignature) in mySigs) {
|
for ((keyId, mySignature) in mySigs) {
|
||||||
// XXX: is this how we're supposed to get the device id?
|
// XXX: is this how we're supposed to get the device id?
|
||||||
var deviceId: String? = null
|
var deviceOrCrossSigningKeyId: String? = null
|
||||||
val components = keyId.split(":")
|
val components = keyId.split(":")
|
||||||
if (components.size == 2) {
|
if (components.size == 2) {
|
||||||
deviceId = components[1]
|
deviceOrCrossSigningKeyId = components[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (deviceId != null) {
|
// Let's check if it's my master key
|
||||||
val device = cryptoStore.getUserDevice(userId, deviceId)
|
val myMSKPKey = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.unpaddedBase64PublicKey
|
||||||
|
if (deviceOrCrossSigningKeyId == myMSKPKey) {
|
||||||
|
// we have to check if we can trust
|
||||||
|
|
||||||
|
var isSignatureValid = false
|
||||||
|
try {
|
||||||
|
crossSigningOlm.verifySignature(CrossSigningOlm.KeyType.MASTER, authData.signalableJSONDictionary(), authData.signatures)
|
||||||
|
isSignatureValid = true
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.w(failure, "getKeysBackupTrust: Bad signature from my user MSK")
|
||||||
|
}
|
||||||
|
val mskTrusted = cryptoStore.getMyCrossSigningInfo()?.masterKey()?.trustLevel?.isVerified() == true
|
||||||
|
if (isSignatureValid && mskTrusted) {
|
||||||
|
keysBackupVersionTrustIsUsable = true
|
||||||
|
}
|
||||||
|
val signature = KeysBackupVersionTrustSignature.UserSignature(
|
||||||
|
keyId = deviceOrCrossSigningKeyId,
|
||||||
|
cryptoCrossSigningKey = cryptoStore.getMyCrossSigningInfo()?.masterKey(),
|
||||||
|
valid = isSignatureValid
|
||||||
|
)
|
||||||
|
|
||||||
|
keysBackupVersionTrustSignatures.add(signature)
|
||||||
|
} else if (deviceOrCrossSigningKeyId != null) {
|
||||||
|
val device = cryptoStore.getUserDevice(userId, deviceOrCrossSigningKeyId)
|
||||||
var isSignatureValid = false
|
var isSignatureValid = false
|
||||||
|
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
Timber.v("getKeysBackupTrust: Signature from unknown device $deviceId")
|
Timber.v("getKeysBackupTrust: Signature from unknown device $deviceOrCrossSigningKeyId")
|
||||||
} else {
|
} else {
|
||||||
val fingerprint = device.fingerprint()
|
val fingerprint = device.fingerprint()
|
||||||
if (fingerprint != null) {
|
if (fingerprint != null) {
|
||||||
@ -448,8 +486,8 @@ internal class DefaultKeysBackupService @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val signature = KeysBackupVersionTrustSignature(
|
val signature = KeysBackupVersionTrustSignature.DeviceSignature(
|
||||||
deviceId = deviceId,
|
deviceId = deviceOrCrossSigningKeyId,
|
||||||
device = device,
|
device = device,
|
||||||
valid = isSignatureValid,
|
valid = isSignatureValid,
|
||||||
)
|
)
|
||||||
|
@ -128,7 +128,7 @@ class KeysBackupRestoreActivity : SimpleFragmentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun launch4SActivity() {
|
private fun launch4SActivity() {
|
||||||
SharedSecureStorageActivity.newIntent(
|
SharedSecureStorageActivity.newReadIntent(
|
||||||
context = this,
|
context = this,
|
||||||
keyId = null, // default key
|
keyId = null, // default key
|
||||||
requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME),
|
requestedSecrets = listOf(KEYBACKUP_SECRET_SSSS_NAME),
|
||||||
|
@ -22,4 +22,8 @@ sealed class KeyBackupSettingsAction : VectorViewModelAction {
|
|||||||
object Init : KeyBackupSettingsAction()
|
object Init : KeyBackupSettingsAction()
|
||||||
object GetKeyBackupTrust : KeyBackupSettingsAction()
|
object GetKeyBackupTrust : KeyBackupSettingsAction()
|
||||||
object DeleteKeyBackup : KeyBackupSettingsAction()
|
object DeleteKeyBackup : KeyBackupSettingsAction()
|
||||||
|
object SetUpKeyBackup : KeyBackupSettingsAction()
|
||||||
|
data class StoreIn4SSuccess(val recoveryKey: String, val alias: String) : KeyBackupSettingsAction()
|
||||||
|
object StoreIn4SReset : KeyBackupSettingsAction()
|
||||||
|
object StoreIn4SFailure : KeyBackupSettingsAction()
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package im.vector.app.features.crypto.keysbackup.settings
|
package im.vector.app.features.crypto.keysbackup.settings
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
@ -23,9 +24,13 @@ import com.airbnb.mvrx.viewModel
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.extensions.replaceFragment
|
import im.vector.app.core.extensions.replaceFragment
|
||||||
import im.vector.app.core.platform.SimpleFragmentActivity
|
import im.vector.app.core.platform.SimpleFragmentActivity
|
||||||
import im.vector.app.core.platform.WaitingViewData
|
import im.vector.app.core.platform.WaitingViewData
|
||||||
|
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
||||||
|
import im.vector.app.features.crypto.quads.SharedSecureStorageActivity
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.crosssigning.KEYBACKUP_SECRET_SSSS_NAME
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class KeysBackupManageActivity : SimpleFragmentActivity() {
|
class KeysBackupManageActivity : SimpleFragmentActivity() {
|
||||||
@ -41,6 +46,21 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
|
|||||||
|
|
||||||
private val viewModel: KeysBackupSettingsViewModel by viewModel()
|
private val viewModel: KeysBackupSettingsViewModel by viewModel()
|
||||||
|
|
||||||
|
private val secretStartForActivityResult = registerStartForActivityResult { activityResult ->
|
||||||
|
if (activityResult.resultCode == Activity.RESULT_OK) {
|
||||||
|
val result = activityResult.data?.getStringExtra(SharedSecureStorageActivity.EXTRA_DATA_RESULT)
|
||||||
|
val reset = activityResult.data?.getBooleanExtra(SharedSecureStorageActivity.EXTRA_DATA_RESET, false) ?: false
|
||||||
|
if (result != null) {
|
||||||
|
viewModel.handle(KeyBackupSettingsAction.StoreIn4SSuccess(result, SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS))
|
||||||
|
} else if (reset) {
|
||||||
|
// all have been reset so a new backup would have been created
|
||||||
|
viewModel.handle(KeyBackupSettingsAction.StoreIn4SReset)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.handle(KeyBackupSettingsAction.StoreIn4SFailure)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun initUiAndData() {
|
override fun initUiAndData() {
|
||||||
super.initUiAndData()
|
super.initUiAndData()
|
||||||
if (supportFragmentManager.fragments.isEmpty()) {
|
if (supportFragmentManager.fragments.isEmpty()) {
|
||||||
@ -69,6 +89,23 @@ class KeysBackupManageActivity : SimpleFragmentActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewModel.observeViewEvents {
|
||||||
|
when (it) {
|
||||||
|
KeysBackupViewEvents.OpenLegacyCreateBackup -> {
|
||||||
|
startActivity(KeysBackupSetupActivity.intent(this, false))
|
||||||
|
}
|
||||||
|
is KeysBackupViewEvents.RequestStore4SSecret -> {
|
||||||
|
secretStartForActivityResult.launch(
|
||||||
|
SharedSecureStorageActivity.newWriteIntent(
|
||||||
|
this,
|
||||||
|
null, // default key
|
||||||
|
listOf(KEYBACKUP_SECRET_SSSS_NAME to it.recoveryKey)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
|
@ -28,7 +28,6 @@ import im.vector.app.core.extensions.configureWith
|
|||||||
import im.vector.app.core.platform.VectorBaseFragment
|
import im.vector.app.core.platform.VectorBaseFragment
|
||||||
import im.vector.app.databinding.FragmentKeysBackupSettingsBinding
|
import im.vector.app.databinding.FragmentKeysBackupSettingsBinding
|
||||||
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
import im.vector.app.features.crypto.keysbackup.restore.KeysBackupRestoreActivity
|
||||||
import im.vector.app.features.crypto.keysbackup.setup.KeysBackupSetupActivity
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController) :
|
class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSettingsRecyclerViewController: KeysBackupSettingsRecyclerViewController) :
|
||||||
@ -58,9 +57,7 @@ class KeysBackupSettingsFragment @Inject constructor(private val keysBackupSetti
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun didSelectSetupMessageRecovery() {
|
override fun didSelectSetupMessageRecovery() {
|
||||||
context?.let {
|
viewModel.handle(KeyBackupSettingsAction.SetUpKeyBackup)
|
||||||
startActivity(KeysBackupSetupActivity.intent(it, false))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun didSelectRestoreMessageRecovery() {
|
override fun didSelectRestoreMessageRecovery() {
|
||||||
|
@ -29,9 +29,11 @@ import im.vector.app.core.ui.list.ItemStyle
|
|||||||
import im.vector.app.core.ui.list.genericItem
|
import im.vector.app.core.ui.list.genericItem
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -191,69 +193,105 @@ class KeysBackupSettingsRecyclerViewController @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
is Success -> {
|
is Success -> {
|
||||||
keysVersionTrust().signatures.forEach {
|
keysVersionTrust()
|
||||||
genericItem {
|
.signatures
|
||||||
id(UUID.randomUUID().toString())
|
.filterIsInstance<KeysBackupVersionTrustSignature.UserSignature>()
|
||||||
title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence())
|
.forEach {
|
||||||
|
val isUserVerified = it.cryptoCrossSigningKey?.trustLevel?.isVerified().orFalse()
|
||||||
val isDeviceKnown = it.device != null
|
val isSignatureValid = it.valid
|
||||||
val isDeviceVerified = it.device?.isVerified ?: false
|
val userId: String = it.cryptoCrossSigningKey?.userId ?: ""
|
||||||
val isSignatureValid = it.valid
|
if (userId == session.sessionParams.userId && isSignatureValid && isUserVerified) {
|
||||||
val deviceId: String = it.deviceId ?: ""
|
genericItem {
|
||||||
|
id(UUID.randomUUID().toString())
|
||||||
if (!isDeviceKnown) {
|
title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence())
|
||||||
description(
|
|
||||||
host.stringProvider
|
|
||||||
.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId)
|
|
||||||
.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
endIconResourceId(R.drawable.e2e_warning)
|
|
||||||
} else {
|
|
||||||
if (isSignatureValid) {
|
|
||||||
if (host.session.sessionParams.deviceId == it.deviceId) {
|
|
||||||
description(
|
description(
|
||||||
host.stringProvider
|
host.stringProvider
|
||||||
.getString(R.string.keys_backup_settings_valid_signature_from_this_device)
|
.getString(R.string.keys_backup_settings_signature_from_this_user)
|
||||||
.toEpoxyCharSequence()
|
.toEpoxyCharSequence()
|
||||||
)
|
)
|
||||||
endIconResourceId(R.drawable.e2e_verified)
|
endIconResourceId(R.drawable.e2e_verified)
|
||||||
} else {
|
|
||||||
if (isDeviceVerified) {
|
|
||||||
description(
|
|
||||||
host.stringProvider
|
|
||||||
.getString(R.string.keys_backup_settings_valid_signature_from_verified_device, deviceId)
|
|
||||||
.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
endIconResourceId(R.drawable.e2e_verified)
|
|
||||||
} else {
|
|
||||||
description(
|
|
||||||
host.stringProvider
|
|
||||||
.getString(R.string.keys_backup_settings_valid_signature_from_unverified_device, deviceId)
|
|
||||||
.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
endIconResourceId(R.drawable.e2e_warning)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Invalid signature
|
|
||||||
endIconResourceId(R.drawable.e2e_warning)
|
|
||||||
if (isDeviceVerified) {
|
|
||||||
description(
|
|
||||||
host.stringProvider
|
|
||||||
.getString(R.string.keys_backup_settings_invalid_signature_from_verified_device, deviceId)
|
|
||||||
.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
description(
|
|
||||||
host.stringProvider
|
|
||||||
.getString(R.string.keys_backup_settings_invalid_signature_from_unverified_device, deviceId)
|
|
||||||
.toEpoxyCharSequence()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} // end for each
|
keysVersionTrust()
|
||||||
|
.signatures
|
||||||
|
.filterIsInstance<KeysBackupVersionTrustSignature.DeviceSignature>()
|
||||||
|
.forEach {
|
||||||
|
genericItem {
|
||||||
|
id(UUID.randomUUID().toString())
|
||||||
|
title(host.stringProvider.getString(R.string.keys_backup_info_title_signature).toEpoxyCharSequence())
|
||||||
|
|
||||||
|
val isDeviceKnown = it.device != null
|
||||||
|
val isDeviceVerified = it.device?.isVerified ?: false
|
||||||
|
val isSignatureValid = it.valid
|
||||||
|
val deviceId: String = it.deviceId ?: ""
|
||||||
|
|
||||||
|
if (!isDeviceKnown) {
|
||||||
|
description(
|
||||||
|
host.stringProvider
|
||||||
|
.getString(R.string.keys_backup_settings_signature_from_unknown_device, deviceId)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
endIconResourceId(R.drawable.e2e_warning)
|
||||||
|
} else {
|
||||||
|
if (isSignatureValid) {
|
||||||
|
if (host.session.sessionParams.deviceId == it.deviceId) {
|
||||||
|
description(
|
||||||
|
host.stringProvider
|
||||||
|
.getString(R.string.keys_backup_settings_valid_signature_from_this_device)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
endIconResourceId(R.drawable.e2e_verified)
|
||||||
|
} else {
|
||||||
|
if (isDeviceVerified) {
|
||||||
|
description(
|
||||||
|
host.stringProvider
|
||||||
|
.getString(
|
||||||
|
R.string.keys_backup_settings_valid_signature_from_verified_device,
|
||||||
|
deviceId
|
||||||
|
)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
endIconResourceId(R.drawable.e2e_verified)
|
||||||
|
} else {
|
||||||
|
description(
|
||||||
|
host.stringProvider
|
||||||
|
.getString(
|
||||||
|
R.string.keys_backup_settings_valid_signature_from_unverified_device,
|
||||||
|
deviceId
|
||||||
|
)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
endIconResourceId(R.drawable.e2e_warning)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Invalid signature
|
||||||
|
endIconResourceId(R.drawable.e2e_warning)
|
||||||
|
if (isDeviceVerified) {
|
||||||
|
description(
|
||||||
|
host.stringProvider
|
||||||
|
.getString(
|
||||||
|
R.string.keys_backup_settings_invalid_signature_from_verified_device,
|
||||||
|
deviceId
|
||||||
|
)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
description(
|
||||||
|
host.stringProvider
|
||||||
|
.getString(
|
||||||
|
R.string.keys_backup_settings_invalid_signature_from_unverified_device,
|
||||||
|
deviceId
|
||||||
|
)
|
||||||
|
.toEpoxyCharSequence()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end for each
|
||||||
}
|
}
|
||||||
is Fail -> {
|
is Fail -> {
|
||||||
errorWithRetryItem {
|
errorWithRetryItem {
|
||||||
|
@ -25,8 +25,8 @@ import dagger.assisted.AssistedFactory
|
|||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
import org.matrix.android.sdk.api.NoOpMatrixCallback
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
@ -34,10 +34,16 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
|
|||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||||
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
|
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
|
||||||
|
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.api.session.crypto.keysbackup.extractCurveKeyFromRecoveryKey
|
||||||
|
import org.matrix.android.sdk.api.util.awaitCallback
|
||||||
|
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialState: KeysBackupSettingViewState,
|
||||||
session: Session
|
private val session: Session
|
||||||
) : VectorViewModel<KeysBackupSettingViewState, KeyBackupSettingsAction, EmptyViewEvents>(initialState),
|
) : VectorViewModel<KeysBackupSettingViewState, KeyBackupSettingsAction, KeysBackupViewEvents>(initialState),
|
||||||
KeysBackupStateListener {
|
KeysBackupStateListener {
|
||||||
|
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
@ -49,6 +55,8 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
|
|
||||||
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
|
private val keysBackupService: KeysBackupService = session.cryptoService().keysBackupService()
|
||||||
|
|
||||||
|
var pendingBackupCreationInfo: MegolmBackupCreationInfo? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setState {
|
setState {
|
||||||
this.copy(
|
this.copy(
|
||||||
@ -62,9 +70,18 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
|
|
||||||
override fun handle(action: KeyBackupSettingsAction) {
|
override fun handle(action: KeyBackupSettingsAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
KeyBackupSettingsAction.Init -> init()
|
KeyBackupSettingsAction.Init -> init()
|
||||||
KeyBackupSettingsAction.GetKeyBackupTrust -> getKeysBackupTrust()
|
KeyBackupSettingsAction.GetKeyBackupTrust -> getKeysBackupTrust()
|
||||||
KeyBackupSettingsAction.DeleteKeyBackup -> deleteCurrentBackup()
|
KeyBackupSettingsAction.DeleteKeyBackup -> deleteCurrentBackup()
|
||||||
|
KeyBackupSettingsAction.SetUpKeyBackup -> viewModelScope.launch {
|
||||||
|
setUpKeyBackup()
|
||||||
|
}
|
||||||
|
KeyBackupSettingsAction.StoreIn4SReset,
|
||||||
|
KeyBackupSettingsAction.StoreIn4SFailure -> {
|
||||||
|
pendingBackupCreationInfo = null
|
||||||
|
// nothing to do just stay on fragment
|
||||||
|
}
|
||||||
|
is KeyBackupSettingsAction.StoreIn4SSuccess -> viewModelScope.launch { completeBackupCreation() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +137,35 @@ class KeysBackupSettingsViewModel @AssistedInject constructor(@Assisted initialS
|
|||||||
getKeysBackupTrust()
|
getKeysBackupTrust()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun setUpKeyBackup() {
|
||||||
|
// We need to check if 4S is enabled first.
|
||||||
|
// If it is we need to use it, generate a random key
|
||||||
|
// for the backup and store it in the 4S
|
||||||
|
if (session.sharedSecretStorageService().isRecoverySetup()) {
|
||||||
|
val creationInfo = awaitCallback<MegolmBackupCreationInfo> {
|
||||||
|
session.cryptoService().keysBackupService().prepareKeysBackupVersion(null, null, it)
|
||||||
|
}
|
||||||
|
pendingBackupCreationInfo = creationInfo
|
||||||
|
val recoveryKey = extractCurveKeyFromRecoveryKey(creationInfo.recoveryKey)?.toBase64NoPadding()
|
||||||
|
_viewEvents.post(KeysBackupViewEvents.RequestStore4SSecret(recoveryKey!!))
|
||||||
|
} else {
|
||||||
|
// No 4S so we can open legacy flow
|
||||||
|
_viewEvents.post(KeysBackupViewEvents.OpenLegacyCreateBackup)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun completeBackupCreation() {
|
||||||
|
val info = pendingBackupCreationInfo ?: return
|
||||||
|
val version = awaitCallback<KeysVersion> {
|
||||||
|
session.cryptoService().keysBackupService().createKeysBackupVersion(info, it)
|
||||||
|
}
|
||||||
|
// Save it for gossiping
|
||||||
|
Timber.d("## BootstrapCrossSigningTask: Creating 4S - Save megolm backup key for gossiping")
|
||||||
|
session.cryptoService().keysBackupService().saveBackupRecoveryKey(info.recoveryKey, version = version.version)
|
||||||
|
|
||||||
|
// TODO catch, delete 4S account data
|
||||||
|
}
|
||||||
|
|
||||||
private fun deleteCurrentBackup() {
|
private fun deleteCurrentBackup() {
|
||||||
val keysBackupService = keysBackupService
|
val keysBackupService = keysBackupService
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.crypto.keysbackup.settings
|
||||||
|
|
||||||
|
import im.vector.app.core.platform.VectorViewEvents
|
||||||
|
|
||||||
|
sealed class KeysBackupViewEvents : VectorViewEvents {
|
||||||
|
object OpenLegacyCreateBackup : KeysBackupViewEvents()
|
||||||
|
data class RequestStore4SSecret(val recoveryKey: String) : KeysBackupViewEvents()
|
||||||
|
}
|
@ -48,8 +48,9 @@ class SharedSecureStorageActivity :
|
|||||||
@Parcelize
|
@Parcelize
|
||||||
data class Args(
|
data class Args(
|
||||||
val keyId: String?,
|
val keyId: String?,
|
||||||
val requestedSecrets: List<String>,
|
val requestedSecrets: List<String> = emptyList(),
|
||||||
val resultKeyStoreAlias: String
|
val resultKeyStoreAlias: String,
|
||||||
|
val writeSecrets: List<Pair<String, String>> = emptyList(),
|
||||||
) : Parcelable
|
) : Parcelable
|
||||||
|
|
||||||
private val viewModel: SharedSecureStorageViewModel by viewModel()
|
private val viewModel: SharedSecureStorageViewModel by viewModel()
|
||||||
@ -148,18 +149,36 @@ class SharedSecureStorageActivity :
|
|||||||
const val EXTRA_DATA_RESET = "EXTRA_DATA_RESET"
|
const val EXTRA_DATA_RESET = "EXTRA_DATA_RESET"
|
||||||
const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"
|
const val DEFAULT_RESULT_KEYSTORE_ALIAS = "SharedSecureStorageActivity"
|
||||||
|
|
||||||
fun newIntent(context: Context,
|
fun newReadIntent(context: Context,
|
||||||
keyId: String? = null,
|
keyId: String? = null,
|
||||||
requestedSecrets: List<String>,
|
requestedSecrets: List<String>,
|
||||||
resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent {
|
resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent {
|
||||||
require(requestedSecrets.isNotEmpty())
|
require(requestedSecrets.isNotEmpty())
|
||||||
return Intent(context, SharedSecureStorageActivity::class.java).also {
|
return Intent(context, SharedSecureStorageActivity::class.java).also {
|
||||||
it.putExtra(
|
it.putExtra(
|
||||||
Mavericks.KEY_ARG, Args(
|
Mavericks.KEY_ARG,
|
||||||
keyId,
|
Args(
|
||||||
requestedSecrets,
|
keyId = keyId,
|
||||||
resultKeyStoreAlias
|
requestedSecrets = requestedSecrets,
|
||||||
|
resultKeyStoreAlias = resultKeyStoreAlias
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newWriteIntent(context: Context,
|
||||||
|
keyId: String? = null,
|
||||||
|
writeSecrets: List<Pair<String, String>>,
|
||||||
|
resultKeyStoreAlias: String = DEFAULT_RESULT_KEYSTORE_ALIAS): Intent {
|
||||||
|
require(writeSecrets.isNotEmpty())
|
||||||
|
return Intent(context, SharedSecureStorageActivity::class.java).also {
|
||||||
|
it.putExtra(
|
||||||
|
Mavericks.KEY_ARG,
|
||||||
|
Args(
|
||||||
|
keyId = keyId,
|
||||||
|
writeSecrets = writeSecrets,
|
||||||
|
resultKeyStoreAlias = resultKeyStoreAlias
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,13 +39,20 @@ import kotlinx.coroutines.withContext
|
|||||||
import org.matrix.android.sdk.api.listeners.ProgressListener
|
import org.matrix.android.sdk.api.listeners.ProgressListener
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
|
import org.matrix.android.sdk.api.session.securestorage.IntegrityResult
|
||||||
|
import org.matrix.android.sdk.api.session.securestorage.KeyInfo
|
||||||
import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
|
import org.matrix.android.sdk.api.session.securestorage.KeyInfoResult
|
||||||
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
|
import org.matrix.android.sdk.api.session.securestorage.RawBytesKeySpec
|
||||||
|
import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageService
|
||||||
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
import org.matrix.android.sdk.api.util.toBase64NoPadding
|
||||||
import org.matrix.android.sdk.flow.flow
|
import org.matrix.android.sdk.flow.flow
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
|
sealed class RequestType {
|
||||||
|
data class ReadSecrets(val secretsName: List<String>) : RequestType()
|
||||||
|
data class WriteSecrets(val secretsNameValue: List<Pair<String, String>>) : RequestType()
|
||||||
|
}
|
||||||
|
|
||||||
data class SharedSecureStorageViewState(
|
data class SharedSecureStorageViewState(
|
||||||
val ready: Boolean = false,
|
val ready: Boolean = false,
|
||||||
val hasPassphrase: Boolean = true,
|
val hasPassphrase: Boolean = true,
|
||||||
@ -55,13 +62,17 @@ data class SharedSecureStorageViewState(
|
|||||||
val showResetAllAction: Boolean = false,
|
val showResetAllAction: Boolean = false,
|
||||||
val userId: String = "",
|
val userId: String = "",
|
||||||
val keyId: String?,
|
val keyId: String?,
|
||||||
val requestedSecrets: List<String>,
|
val requestType: RequestType,
|
||||||
val resultKeyStoreAlias: String
|
val resultKeyStoreAlias: String
|
||||||
) : MavericksState {
|
) : MavericksState {
|
||||||
|
|
||||||
constructor(args: SharedSecureStorageActivity.Args) : this(
|
constructor(args: SharedSecureStorageActivity.Args) : this(
|
||||||
keyId = args.keyId,
|
keyId = args.keyId,
|
||||||
requestedSecrets = args.requestedSecrets,
|
requestType = if (args.writeSecrets.isNotEmpty()) {
|
||||||
|
RequestType.WriteSecrets(args.writeSecrets)
|
||||||
|
} else {
|
||||||
|
RequestType.ReadSecrets(args.requestedSecrets)
|
||||||
|
},
|
||||||
resultKeyStoreAlias = args.resultKeyStoreAlias
|
resultKeyStoreAlias = args.resultKeyStoreAlias
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -87,14 +98,17 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
setState {
|
setState {
|
||||||
copy(userId = session.myUserId)
|
copy(userId = session.myUserId)
|
||||||
}
|
}
|
||||||
val integrityResult = session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestedSecrets, initialState.keyId)
|
if (initialState.requestType is RequestType.ReadSecrets) {
|
||||||
if (integrityResult !is IntegrityResult.Success) {
|
val integrityResult =
|
||||||
_viewEvents.post(
|
session.sharedSecretStorageService().checkShouldBeAbleToAccessSecrets(initialState.requestType.secretsName, initialState.keyId)
|
||||||
SharedSecureStorageViewEvent.Error(
|
if (integrityResult !is IntegrityResult.Success) {
|
||||||
stringProvider.getString(R.string.enter_secret_storage_invalid),
|
_viewEvents.post(
|
||||||
true
|
SharedSecureStorageViewEvent.Error(
|
||||||
)
|
stringProvider.getString(R.string.enter_secret_storage_invalid),
|
||||||
)
|
true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val keyResult = initialState.keyId?.let { session.sharedSecretStorageService().getKey(it) }
|
val keyResult = initialState.keyId?.let { session.sharedSecretStorageService().getKey(it) }
|
||||||
?: session.sharedSecretStorageService().getDefaultKey()
|
?: session.sharedSecretStorageService().getDefaultKey()
|
||||||
@ -226,20 +240,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
_viewEvents.post(SharedSecureStorageViewEvent.HideModalLoading)
|
||||||
setState { copy(checkingSSSSAction = Fail(IllegalArgumentException(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))) }
|
setState { copy(checkingSSSSAction = Fail(IllegalArgumentException(stringProvider.getString(R.string.bootstrap_invalid_recovery_key)))) }
|
||||||
}
|
}
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
initialState.requestedSecrets.forEach {
|
performRequest(keyInfo, keySpec, decryptedSecretMap)
|
||||||
if (session.accountDataService().getUserAccountDataEvent(it) != null) {
|
|
||||||
val res = session.sharedSecretStorageService().getSecret(
|
|
||||||
name = it,
|
|
||||||
keyId = keyInfo.id,
|
|
||||||
secretKey = keySpec
|
|
||||||
)
|
|
||||||
decryptedSecretMap[it] = res
|
|
||||||
} else {
|
|
||||||
Timber.w("## Cannot find secret $it in SSSS, skip")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}.fold({
|
}.fold({
|
||||||
setState { copy(checkingSSSSAction = Success(Unit)) }
|
setState { copy(checkingSSSSAction = Success(Unit)) }
|
||||||
@ -258,6 +260,37 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private suspend fun performRequest(keyInfo: KeyInfo, keySpec: RawBytesKeySpec, decryptedSecretMap: HashMap<String, String>) {
|
||||||
|
when (val requestType = initialState.requestType) {
|
||||||
|
is RequestType.ReadSecrets -> {
|
||||||
|
requestType.secretsName.forEach {
|
||||||
|
if (session.accountDataService().getUserAccountDataEvent(it) != null) {
|
||||||
|
val res = session.sharedSecretStorageService().getSecret(
|
||||||
|
name = it,
|
||||||
|
keyId = keyInfo.id,
|
||||||
|
secretKey = keySpec
|
||||||
|
)
|
||||||
|
decryptedSecretMap[it] = res
|
||||||
|
} else {
|
||||||
|
Timber.w("## Cannot find secret $it in SSSS, skip")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RequestType.WriteSecrets -> {
|
||||||
|
requestType.secretsNameValue.forEach {
|
||||||
|
val (name, value) = it
|
||||||
|
|
||||||
|
session.sharedSecretStorageService().storeSecret(
|
||||||
|
name = name,
|
||||||
|
secretBase64 = value,
|
||||||
|
keys = listOf(SharedSecretStorageService.KeyRef(keyInfo.id, keySpec))
|
||||||
|
)
|
||||||
|
decryptedSecretMap[name] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) {
|
private fun handleSubmitPassphrase(action: SharedSecureStorageAction.SubmitPassphrase) {
|
||||||
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
|
_viewEvents.post(SharedSecureStorageViewEvent.ShowModalLoading)
|
||||||
val decryptedSecretMap = HashMap<String, String>()
|
val decryptedSecretMap = HashMap<String, String>()
|
||||||
@ -302,17 +335,8 @@ class SharedSecureStorageViewModel @AssistedInject constructor(
|
|||||||
)
|
)
|
||||||
|
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
initialState.requestedSecrets.forEach {
|
withContext(Dispatchers.IO) {
|
||||||
if (session.accountDataService().getUserAccountDataEvent(it) != null) {
|
performRequest(keyInfo, keySpec, decryptedSecretMap)
|
||||||
val res = session.sharedSecretStorageService().getSecret(
|
|
||||||
name = it,
|
|
||||||
keyId = keyInfo.id,
|
|
||||||
secretKey = keySpec
|
|
||||||
)
|
|
||||||
decryptedSecretMap[it] = res
|
|
||||||
} else {
|
|
||||||
Timber.w("## Cannot find secret $it in SSSS, skip")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.fold({
|
}.fold({
|
||||||
|
@ -95,14 +95,12 @@ class VerificationBottomSheet : VectorBaseBottomSheetDialogFragment<BottomSheetV
|
|||||||
when (it) {
|
when (it) {
|
||||||
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
is VerificationBottomSheetViewEvents.Dismiss -> dismiss()
|
||||||
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
is VerificationBottomSheetViewEvents.AccessSecretStore -> {
|
||||||
secretStartForActivityResult.launch(
|
secretStartForActivityResult.launch(SharedSecureStorageActivity.newReadIntent(
|
||||||
SharedSecureStorageActivity.newIntent(
|
requireContext(),
|
||||||
requireContext(),
|
null, // use default key
|
||||||
null, // use default key
|
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
|
||||||
listOf(MASTER_KEY_SSSS_NAME, USER_SIGNING_KEY_SSSS_NAME, SELF_SIGNING_KEY_SSSS_NAME, KEYBACKUP_SECRET_SSSS_NAME),
|
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
|
||||||
SharedSecureStorageActivity.DEFAULT_RESULT_KEYSTORE_ALIAS
|
))
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
is VerificationBottomSheetViewEvents.ModalError -> {
|
is VerificationBottomSheetViewEvents.ModalError -> {
|
||||||
MaterialAlertDialogBuilder(requireContext())
|
MaterialAlertDialogBuilder(requireContext())
|
||||||
|
@ -1513,6 +1513,7 @@
|
|||||||
<string name="keys_backup_settings_status_not_setup">Your keys are not being backed up from this session.</string>
|
<string name="keys_backup_settings_status_not_setup">Your keys are not being backed up from this session.</string>
|
||||||
|
|
||||||
<string name="keys_backup_settings_signature_from_unknown_device">Backup has a signature from unknown session with ID %s.</string>
|
<string name="keys_backup_settings_signature_from_unknown_device">Backup has a signature from unknown session with ID %s.</string>
|
||||||
|
<string name="keys_backup_settings_signature_from_this_user">Backup has a valid signature from this user.</string>
|
||||||
<string name="keys_backup_settings_valid_signature_from_this_device">Backup has a valid signature from this session.</string>
|
<string name="keys_backup_settings_valid_signature_from_this_device">Backup has a valid signature from this session.</string>
|
||||||
<string name="keys_backup_settings_valid_signature_from_verified_device">Backup has a valid signature from verified session %s.</string>
|
<string name="keys_backup_settings_valid_signature_from_verified_device">Backup has a valid signature from verified session %s.</string>
|
||||||
<string name="keys_backup_settings_valid_signature_from_unverified_device">Backup has a valid signature from unverified session %s</string>
|
<string name="keys_backup_settings_valid_signature_from_unverified_device">Backup has a valid signature from unverified session %s</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user