Missing backup signature

Ensure device keys before bootstrap cross signing
This commit is contained in:
valere 2023-04-28 18:58:38 +02:00
parent fbb645d9d4
commit 85b9dda092
4 changed files with 90 additions and 23 deletions

View file

@ -26,6 +26,7 @@ import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertTrue
import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@ -128,6 +129,7 @@ class KeysBackupTest : InstrumentedTest {
@Test
fun createKeysBackupVersionTest() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val bobSession = testHelper.createAccount(TestConstants.USER_BOB, KeysBackupTestConstants.defaultSessionParams)
Log.d("#E2E", "Initializing crosssigning for ${bobSession.myUserId.take(8)}")
cryptoTestHelper.initializeCrossSigning(bobSession)
val keysBackup = bobSession.cryptoService().keysBackupService()
@ -136,12 +138,14 @@ class KeysBackupTest : InstrumentedTest {
assertFalse(keysBackup.isEnabled())
Log.d("#E2E", "prepareKeysBackupVersion")
val megolmBackupCreationInfo =
keysBackup.prepareKeysBackupVersion(null, null)
assertFalse(keysBackup.isEnabled())
// Create the version
Log.d("#E2E", "createKeysBackupVersion")
val version = keysBackup.createKeysBackupVersion(megolmBackupCreationInfo)
// Backup must be enable now
@ -151,6 +155,7 @@ class KeysBackupTest : InstrumentedTest {
val versionResult = keysBackup.getVersion(version.version)
val trust = keysBackup.getKeysBackupTrust(versionResult!!)
Log.d("#E2E", "Check backup signatures")
assertEquals("Should have 2 signatures", 2, trust.signatures.size)
trust.signatures
@ -672,11 +677,16 @@ class KeysBackupTest : InstrumentedTest {
*/
@Test
fun testBackupWithPassword() = runCryptoTest(context()) { cryptoTestHelper, testHelper ->
val keysBackupTestHelper = KeysBackupTestHelper(testHelper, cryptoTestHelper)
val password = "password"
val testData = keysBackupTestHelper.createKeysBackupScenarioWithPassword(password)
Assume.assumeTrue(
"Can't report progress same way in rust",
testData.cryptoTestData.firstSession.cryptoService().name() != "rust-sdk"
)
// - Restore the e2e backup with the password
val steps = ArrayList<StepProgressListener.Step>()

View file

@ -82,6 +82,7 @@ import org.matrix.rustcomponents.sdk.crypto.RequestType
import org.matrix.rustcomponents.sdk.crypto.RoomKeyCounts
import org.matrix.rustcomponents.sdk.crypto.ShieldColor
import org.matrix.rustcomponents.sdk.crypto.ShieldState
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
import org.matrix.rustcomponents.sdk.crypto.setLogger
import timber.log.Timber
import java.io.File
@ -916,7 +917,7 @@ internal class OlmMachine @Inject constructor(
}
@Throws(CryptoStoreException::class)
suspend fun checkAuthDataSignature(authData: KeysAlgorithmAndData): Boolean {
suspend fun checkAuthDataSignature(authData: KeysAlgorithmAndData): SignatureVerification {
return withContext(coroutineDispatchers.computation) {
val adapter = moshi
.newBuilder()
@ -929,7 +930,7 @@ internal class OlmMachine @Inject constructor(
)
)
inner.verifyBackup(serializedAuthData).trusted
inner.verifyBackup(serializedAuthData)
}
}
}

View file

@ -28,10 +28,13 @@ import org.matrix.android.sdk.api.session.crypto.crosssigning.isVerified
import org.matrix.android.sdk.api.session.crypto.model.CryptoDeviceInfo
import org.matrix.android.sdk.api.session.crypto.model.RoomEncryptionTrustLevel
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.crypto.network.OutgoingRequestsProcessor
import org.matrix.rustcomponents.sdk.crypto.Request
import javax.inject.Inject
internal class RustCrossSigningService @Inject constructor(
private val olmMachine: OlmMachine,
private val outgoingRequestsProcessor: OutgoingRequestsProcessor,
private val computeShieldForGroup: ComputeShieldForGroupUseCase
) : CrossSigningService {
@ -78,6 +81,10 @@ internal class RustCrossSigningService @Inject constructor(
* Users needs to enter credentials
*/
override suspend fun initializeCrossSigning(uiaInterceptor: UserInteractiveAuthInterceptor?) {
// ensure our keys are sent before initialising
outgoingRequestsProcessor.processOutgoingRequests(olmMachine) {
it is Request.KeysUpload
}
olmMachine.bootstrapCrossSigning(uiaInterceptor)
}

View file

@ -43,6 +43,7 @@ import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupService
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupState
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupStateListener
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrust
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysBackupVersionTrustSignature
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersion
import org.matrix.android.sdk.api.session.crypto.keysbackup.KeysVersionResult
import org.matrix.android.sdk.api.session.crypto.keysbackup.MegolmBackupAuthData
@ -67,6 +68,8 @@ import org.matrix.android.sdk.internal.util.JsonCanonicalizer
import org.matrix.olm.OlmException
import org.matrix.rustcomponents.sdk.crypto.Request
import org.matrix.rustcomponents.sdk.crypto.RequestType
import org.matrix.rustcomponents.sdk.crypto.SignatureState
import org.matrix.rustcomponents.sdk.crypto.SignatureVerification
import timber.log.Timber
import java.security.InvalidParameterException
import javax.inject.Inject
@ -266,14 +269,56 @@ internal class RustKeyBackupService @Inject constructor(
private suspend fun checkBackupTrust(algAndData: KeysAlgorithmAndData?): KeysBackupVersionTrust {
if (algAndData == null) return KeysBackupVersionTrust(usable = false)
try {
val isTrusted = olmMachine.checkAuthDataSignature(algAndData)
return KeysBackupVersionTrust(isTrusted)
val authData = olmMachine.checkAuthDataSignature(algAndData)
val signatures = authData.mapRustToAPI()
return KeysBackupVersionTrust(authData.trusted, signatures)
} catch (failure: Throwable) {
Timber.w(failure, "Failed to trust backup")
return KeysBackupVersionTrust(usable = false)
}
}
private suspend fun SignatureVerification.mapRustToAPI(): List<KeysBackupVersionTrustSignature> {
val signatures = mutableListOf<KeysBackupVersionTrustSignature>()
// signature state of own device
val ownDeviceState = this.deviceSignature
if (ownDeviceState != SignatureState.MISSING && ownDeviceState != SignatureState.INVALID) {
// we can add it
signatures.add(
KeysBackupVersionTrustSignature.DeviceSignature(
olmMachine.deviceId(),
olmMachine.getCryptoDeviceInfo(olmMachine.userId(), olmMachine.deviceId()),
ownDeviceState == SignatureState.VALID_AND_TRUSTED
)
)
}
// signature state of our own identity
val ownIdentityState = this.userIdentitySignature
if (ownIdentityState != SignatureState.MISSING && ownIdentityState != SignatureState.INVALID) {
// we can add it
val masterKey = olmMachine.getIdentity(olmMachine.userId())?.toMxCrossSigningInfo()?.masterKey()
signatures.add(
KeysBackupVersionTrustSignature.UserSignature(
masterKey?.unpaddedBase64PublicKey,
masterKey,
ownIdentityState == SignatureState.VALID_AND_TRUSTED
)
)
}
signatures.addAll(
this.otherDevicesSignatures
.filter { it.value == SignatureState.VALID_AND_TRUSTED || it.value == SignatureState.VALID_BUT_NOT_TRUSTED }
.map {
KeysBackupVersionTrustSignature.DeviceSignature(
it.key,
olmMachine.getCryptoDeviceInfo(olmMachine.userId(), it.key),
ownDeviceState == SignatureState.VALID_AND_TRUSTED
)
}
)
return signatures
}
override suspend fun getKeysBackupTrust(keysBackupVersion: KeysVersionResult): KeysBackupVersionTrust {
return withContext(coroutineDispatchers.crypto) {
checkBackupTrust(keysBackupVersion)
@ -341,7 +386,7 @@ internal class RustKeyBackupService @Inject constructor(
val authData = getMegolmBackupAuthData(keysBackupData)
when {
authData == null -> {
authData == null -> {
Timber.w("isValidRecoveryKeyForKeysBackupVersion: Key backup is missing required data")
throw IllegalArgumentException("Missing element")
}
@ -349,7 +394,7 @@ internal class RustKeyBackupService @Inject constructor(
Timber.w("isValidRecoveryKeyForKeysBackupVersion: Public keys mismatch")
throw IllegalArgumentException("Invalid recovery key or password")
}
else -> {
else -> {
// This case is fine, the public key on the server matches the public key the
// recovery key produced.
}
@ -428,10 +473,10 @@ internal class RustKeyBackupService @Inject constructor(
roomId != null && sessionId != null -> {
sender.downloadBackedUpKeys(version, roomId, sessionId)
}
roomId != null -> {
roomId != null -> {
sender.downloadBackedUpKeys(version, roomId)
}
else -> {
else -> {
sender.downloadBackedUpKeys(version)
}
}
@ -570,20 +615,24 @@ internal class RustKeyBackupService @Inject constructor(
}
}
override suspend fun restoreKeysWithRecoveryKey(keysVersionResult: KeysVersionResult,
recoveryKey: IBackupRecoveryKey,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
override suspend fun restoreKeysWithRecoveryKey(
keysVersionResult: KeysVersionResult,
recoveryKey: IBackupRecoveryKey,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?
): ImportRoomKeysResult {
Timber.v("restoreKeysWithRecoveryKey: From backup version: ${keysVersionResult.version}")
return restoreBackup(keysVersionResult, recoveryKey, roomId, sessionId, stepProgressListener)
}
override suspend fun restoreKeyBackupWithPassword(keysBackupVersion: KeysVersionResult,
password: String,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?): ImportRoomKeysResult {
override suspend fun restoreKeyBackupWithPassword(
keysBackupVersion: KeysVersionResult,
password: String,
roomId: String?,
sessionId: String?,
stepProgressListener: StepProgressListener?
): ImportRoomKeysResult {
Timber.v("[MXKeyBackup] restoreKeyBackup with password: From backup version: ${keysBackupVersion.version}")
val recoveryKey = withContext(coroutineDispatchers.crypto) {
recoveryKeyFromPassword(password, keysBackupVersion)
@ -752,13 +801,13 @@ internal class RustKeyBackupService @Inject constructor(
val authData = getMegolmBackupAuthData(keysBackupData)
return when {
authData == null -> {
authData == null -> {
throw IllegalArgumentException("recoveryKeyFromPassword: invalid parameter")
}
authData.privateKeySalt.isNullOrBlank() || authData.privateKeyIterations == null -> {
throw java.lang.IllegalArgumentException("recoveryKeyFromPassword: Salt and/or iterations not found in key backup auth data")
}
else -> {
else -> {
BackupRecoveryKey.fromPassphrase(password, authData.privateKeySalt, authData.privateKeyIterations)
}
}
@ -811,7 +860,7 @@ internal class RustKeyBackupService @Inject constructor(
suspend fun maybeBackupKeys() {
withContext(coroutineDispatchers.crypto) {
when {
isStuck() -> {
isStuck() -> {
// If not already done, or in error case, check for a valid backup version on the homeserver.
// If there is one, maybeBackupKeys will be called again.
checkAndStartKeysBackup()
@ -829,7 +878,7 @@ internal class RustKeyBackupService @Inject constructor(
tryOrNull("AUTO backup failed") { backupKeys() }
}
}
else -> {
else -> {
Timber.v("maybeBackupKeys: Skip it because state: ${getState()}")
}
}
@ -907,7 +956,7 @@ internal class RustKeyBackupService @Inject constructor(
// is available on the homeserver
checkAndStartKeysBackup()
}
else ->
else ->
// Come back to the ready state so that we will retry on the next received key
keysBackupStateManager.state = KeysBackupState.ReadyToBackUp
}