mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-03-18 20:29:10 +03:00
TI: Import keys backup tests
This commit is contained in:
parent
f3fb07079e
commit
76e45431da
4 changed files with 1735 additions and 1 deletions
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* Copyright 2019 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.keysbackup
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import im.vector.matrix.android.InstrumentedTest
|
||||
import im.vector.matrix.android.api.listeners.ProgressListener
|
||||
import im.vector.matrix.android.common.assertByteArrayNotEqual
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.FixMethodOrder
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.MethodSorters
|
||||
import org.matrix.olm.OlmManager
|
||||
import org.matrix.olm.OlmPkDecryption
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
@FixMethodOrder(MethodSorters.JVM)
|
||||
class KeysBackupPasswordTest : InstrumentedTest {
|
||||
|
||||
@Before
|
||||
fun ensureLibLoaded() {
|
||||
OlmManager()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertArrayEquals(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check generatePrivateKeyWithPassword progress listener behavior
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_progress_ok() {
|
||||
val progressValues = ArrayList<Int>(101)
|
||||
var lastTotal = 0
|
||||
|
||||
generatePrivateKeyWithPassword(PASSWORD, object : ProgressListener {
|
||||
override fun onProgress(progress: Int, total: Int) {
|
||||
if (!progressValues.contains(progress)) {
|
||||
progressValues.add(progress)
|
||||
}
|
||||
|
||||
lastTotal = total
|
||||
}
|
||||
})
|
||||
|
||||
assertEquals(100, lastTotal)
|
||||
|
||||
// Ensure all values are here
|
||||
assertEquals(101, progressValues.size)
|
||||
|
||||
for (i in 0..100) {
|
||||
assertTrue(progressValues[i] == i)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad password
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badPassword_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad password
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(BAD_PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad password
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badIteration_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
generatePrivateKeyResult.salt,
|
||||
500_001)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check KeysBackupPassword utilities, with bad salt
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_badSalt_ok() {
|
||||
val generatePrivateKeyResult = generatePrivateKeyWithPassword(PASSWORD, null)
|
||||
|
||||
assertEquals(32, generatePrivateKeyResult.salt.length)
|
||||
assertEquals(500_000, generatePrivateKeyResult.iterations)
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), generatePrivateKeyResult.privateKey.size)
|
||||
|
||||
// Reverse operation, with bad iteration
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(PASSWORD,
|
||||
BAD_SALT,
|
||||
generatePrivateKeyResult.iterations)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
assertByteArrayNotEqual(generatePrivateKeyResult.privateKey, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check [retrievePrivateKeyWithPassword] with data coming from another platform (RiotWeb).
|
||||
*/
|
||||
@Test
|
||||
fun passwordConverter_crossPlatform_ok() {
|
||||
val password = "This is a passphrase!"
|
||||
val salt = "TO0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||
val iteration = 500_000
|
||||
|
||||
val retrievedPrivateKey = retrievePrivateKeyWithPassword(password, salt, iteration)
|
||||
|
||||
assertEquals(OlmPkDecryption.privateKeyLength(), retrievedPrivateKey.size)
|
||||
|
||||
// Data from RiotWeb
|
||||
val privateKeyBytes = byteArrayOf(
|
||||
116.toByte(), 224.toByte(), 229.toByte(), 224.toByte(), 9.toByte(), 3.toByte(), 178.toByte(), 162.toByte(),
|
||||
120.toByte(), 23.toByte(), 108.toByte(), 218.toByte(), 22.toByte(), 61.toByte(), 241.toByte(), 200.toByte(),
|
||||
235.toByte(), 173.toByte(), 236.toByte(), 100.toByte(), 115.toByte(), 247.toByte(), 33.toByte(), 132.toByte(),
|
||||
195.toByte(), 154.toByte(), 64.toByte(), 158.toByte(), 184.toByte(), 148.toByte(), 20.toByte(), 85.toByte())
|
||||
|
||||
assertArrayEquals(privateKeyBytes, retrievedPrivateKey)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PASSWORD = "password"
|
||||
private const val BAD_PASSWORD = "passw0rd"
|
||||
|
||||
private const val BAD_SALT = "AA0lxhQ9aYgGfMsclVWPIAublg8h9Nlu"
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright 2019 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.keysbackup
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupService
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupState
|
||||
import im.vector.matrix.android.api.session.crypto.keysbackup.KeysBackupStateListener
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertNull
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
/**
|
||||
* This class observe the state change of a KeysBackup object and provide a method to check the several state change
|
||||
* It checks all state transitions and detected forbidden transition
|
||||
*/
|
||||
internal class StateObserver(private val keysBackup: KeysBackupService,
|
||||
private val latch: CountDownLatch? = null,
|
||||
private val expectedStateChange: Int = -1) : KeysBackupStateListener {
|
||||
|
||||
private val allowedStateTransitions = listOf(
|
||||
KeysBackupState.BackingUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.BackingUp to KeysBackupState.WrongBackUpVersion,
|
||||
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Disabled,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.NotTrusted,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.Unknown,
|
||||
KeysBackupState.CheckingBackUpOnHomeserver to KeysBackupState.WrongBackUpVersion,
|
||||
|
||||
KeysBackupState.Disabled to KeysBackupState.Enabling,
|
||||
|
||||
KeysBackupState.Enabling to KeysBackupState.Disabled,
|
||||
KeysBackupState.Enabling to KeysBackupState.ReadyToBackUp,
|
||||
|
||||
KeysBackupState.NotTrusted to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
// This transition happens when we trust the device
|
||||
KeysBackupState.NotTrusted to KeysBackupState.ReadyToBackUp,
|
||||
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.WillBackUp,
|
||||
|
||||
KeysBackupState.Unknown to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
|
||||
KeysBackupState.WillBackUp to KeysBackupState.BackingUp,
|
||||
|
||||
KeysBackupState.WrongBackUpVersion to KeysBackupState.CheckingBackUpOnHomeserver,
|
||||
|
||||
// FIXME These transitions are observed during test, and I'm not sure they should occur. Don't have time to investigate now
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.BackingUp,
|
||||
KeysBackupState.ReadyToBackUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.WillBackUp to KeysBackupState.ReadyToBackUp,
|
||||
KeysBackupState.WillBackUp to KeysBackupState.Unknown
|
||||
)
|
||||
|
||||
private val stateList = ArrayList<KeysBackupState>()
|
||||
private var lastTransitionError: String? = null
|
||||
|
||||
init {
|
||||
keysBackup.addListener(this)
|
||||
}
|
||||
|
||||
// TODO Make expectedStates mandatory to enforce test
|
||||
fun stopAndCheckStates(expectedStates: List<KeysBackupState>?) {
|
||||
keysBackup.removeListener(this)
|
||||
|
||||
expectedStates?.let {
|
||||
assertEquals(it.size, stateList.size)
|
||||
|
||||
for (i in it.indices) {
|
||||
assertEquals("The state $i is not correct. states: " + stateList.joinToString(separator = " "), it[i], stateList[i])
|
||||
}
|
||||
}
|
||||
|
||||
assertNull("states: " + stateList.joinToString(separator = " "), lastTransitionError)
|
||||
}
|
||||
|
||||
override fun onStateChange(newState: KeysBackupState) {
|
||||
stateList.add(newState)
|
||||
|
||||
// Check that state transition is valid
|
||||
if (stateList.size >= 2
|
||||
&& !allowedStateTransitions.contains(stateList[stateList.size - 2] to newState)) {
|
||||
// Forbidden transition detected
|
||||
lastTransitionError = "Forbidden transition detected from " + stateList[stateList.size - 2] + " to " + newState
|
||||
}
|
||||
|
||||
if (expectedStateChange == stateList.size) {
|
||||
latch?.countDown()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1221,7 +1221,7 @@ internal class KeysBackup @Inject constructor(
|
|||
// Do not stay in KeysBackupState.WrongBackUpVersion but check what 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
|
||||
}
|
||||
|
@ -1339,6 +1339,32 @@ internal class KeysBackup @Inject constructor(
|
|||
return sessionBackupData
|
||||
}
|
||||
|
||||
/* ==========================================================================================
|
||||
* For test only
|
||||
* ========================================================================================== */
|
||||
|
||||
// Direct access for test only
|
||||
@VisibleForTesting
|
||||
val store
|
||||
get() = cryptoStore
|
||||
|
||||
@VisibleForTesting
|
||||
fun createFakeKeysBackupVersion(keysBackupCreationInfo: MegolmBackupCreationInfo,
|
||||
callback: MatrixCallback<KeysVersion>) {
|
||||
val createKeysBackupVersionBody = CreateKeysBackupVersionBody()
|
||||
createKeysBackupVersionBody.algorithm = keysBackupCreationInfo.algorithm
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
createKeysBackupVersionBody.authData = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
.fromJson(keysBackupCreationInfo.authData?.toJsonString() ?: "") as JsonDict?
|
||||
|
||||
createKeysBackupVersionTask
|
||||
.configureWith(createKeysBackupVersionBody) {
|
||||
this.callback = callback
|
||||
}
|
||||
.executeBy(taskExecutor)
|
||||
}
|
||||
|
||||
|
||||
companion object {
|
||||
// Maximum delay in ms in {@link maybeBackupKeys}
|
||||
private const val KEY_BACKUP_WAITING_TIME_TO_SEND_KEY_BACKUP_MILLIS = 10_000L
|
||||
|
|
Loading…
Add table
Reference in a new issue