mirror of
https://github.com/vector-im/element-android.git
synced 2024-11-16 02:05:06 +08:00
Merge pull request #3316 from vector-im/feature/bma/secretstoring_migration
Feature/bma/secretstoring migration
This commit is contained in:
commit
8d94b5548d
@ -59,6 +59,7 @@ Bugfix 🐛:
|
|||||||
- Properly clean the back stack if the user cancel registration when waiting for email validation
|
- Properly clean the back stack if the user cancel registration when waiting for email validation
|
||||||
- Fix read marker visibility/position when filtering some events
|
- Fix read marker visibility/position when filtering some events
|
||||||
- Fix user invitation in case of restricted profile api (#3306)
|
- Fix user invitation in case of restricted profile api (#3306)
|
||||||
|
- Make sure the SDK can retrieve the secret storage if the system is upgraded (#3304)
|
||||||
|
|
||||||
SDK API changes ⚠️:
|
SDK API changes ⚠️:
|
||||||
- RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205)
|
- RegistrationWizard.createAccount() parameters are now all optional, following Matrix spec (#3205)
|
||||||
|
@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.di.MatrixModule
|
|||||||
import org.matrix.android.sdk.internal.di.MatrixScope
|
import org.matrix.android.sdk.internal.di.MatrixScope
|
||||||
import org.matrix.android.sdk.internal.di.NetworkModule
|
import org.matrix.android.sdk.internal.di.NetworkModule
|
||||||
import org.matrix.android.sdk.internal.raw.RawModule
|
import org.matrix.android.sdk.internal.raw.RawModule
|
||||||
|
import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||||
|
|
||||||
@Component(modules = [
|
@Component(modules = [
|
||||||
TestModule::class,
|
TestModule::class,
|
||||||
@ -33,6 +34,7 @@ import org.matrix.android.sdk.internal.raw.RawModule
|
|||||||
NetworkModule::class,
|
NetworkModule::class,
|
||||||
AuthModule::class,
|
AuthModule::class,
|
||||||
RawModule::class,
|
RawModule::class,
|
||||||
|
SystemModule::class,
|
||||||
TestNetworkModule::class
|
TestNetworkModule::class
|
||||||
])
|
])
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
|
@ -0,0 +1,184 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.session.securestorage
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.junit.FixMethodOrder
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.junit.runners.MethodSorters
|
||||||
|
import org.matrix.android.sdk.InstrumentedTest
|
||||||
|
import org.matrix.android.sdk.internal.crypto.crosssigning.fromBase64
|
||||||
|
import org.matrix.android.sdk.internal.crypto.crosssigning.toBase64NoPadding
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
@FixMethodOrder(MethodSorters.JVM)
|
||||||
|
class SecretStoringUtilsTest : InstrumentedTest {
|
||||||
|
|
||||||
|
private val buildVersionSdkIntProvider = TestBuildVersionSdkIntProvider()
|
||||||
|
private val secretStoringUtils = SecretStoringUtils(context(), buildVersionSdkIntProvider)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TEST_STR = "This is something I want to store safely!"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringNominalCaseApi21() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringNominalCaseApi23() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringNominalCaseApi30() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.R
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testStringMigration21_23() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = secretStoringUtils.securelyStoreString(TEST_STR, alias)
|
||||||
|
|
||||||
|
// Simulate a system upgrade
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = secretStoringUtils.loadSecureSecret(encrypted, alias)
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testObjectNominalCaseApi21() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = ByteArrayOutputStream().also { outputStream ->
|
||||||
|
outputStream.use {
|
||||||
|
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toByteArray()
|
||||||
|
.toBase64NoPadding()
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = encrypted.fromBase64().inputStream().use {
|
||||||
|
secretStoringUtils.loadSecureSecret<String>(it, alias)
|
||||||
|
}
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testObjectNominalCaseApi23() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = ByteArrayOutputStream().also { outputStream ->
|
||||||
|
outputStream.use {
|
||||||
|
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toByteArray()
|
||||||
|
.toBase64NoPadding()
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = encrypted.fromBase64().inputStream().use {
|
||||||
|
secretStoringUtils.loadSecureSecret<String>(it, alias)
|
||||||
|
}
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testObjectNominalCaseApi30() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.R
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = ByteArrayOutputStream().also { outputStream ->
|
||||||
|
outputStream.use {
|
||||||
|
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toByteArray()
|
||||||
|
.toBase64NoPadding()
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = encrypted.fromBase64().inputStream().use {
|
||||||
|
secretStoringUtils.loadSecureSecret<String>(it, alias)
|
||||||
|
}
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testObjectMigration21_23() {
|
||||||
|
val alias = generateAlias()
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.LOLLIPOP
|
||||||
|
|
||||||
|
// Encrypt
|
||||||
|
val encrypted = ByteArrayOutputStream().also { outputStream ->
|
||||||
|
outputStream.use {
|
||||||
|
secretStoringUtils.securelyStoreObject(TEST_STR, alias, it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.toByteArray()
|
||||||
|
.toBase64NoPadding()
|
||||||
|
|
||||||
|
// Simulate a system upgrade
|
||||||
|
buildVersionSdkIntProvider.value = Build.VERSION_CODES.M
|
||||||
|
|
||||||
|
// Decrypt
|
||||||
|
val decrypted = encrypted.fromBase64().inputStream().use {
|
||||||
|
secretStoringUtils.loadSecureSecret<String>(it, alias)
|
||||||
|
}
|
||||||
|
decrypted shouldBeEqualTo TEST_STR
|
||||||
|
secretStoringUtils.safeDeleteKey(alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateAlias() = UUID.randomUUID().toString()
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.session.securestorage
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider
|
||||||
|
|
||||||
|
class TestBuildVersionSdkIntProvider : BuildVersionSdkIntProvider {
|
||||||
|
var value: Int = 0
|
||||||
|
|
||||||
|
override fun get() = value
|
||||||
|
}
|
@ -71,7 +71,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
|||||||
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
|
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
|
||||||
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
|
val toStore = secretStoringUtils.securelyStoreString(encodedKey, alias)
|
||||||
sharedPreferences.edit {
|
sharedPreferences.edit {
|
||||||
putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
|
putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore, Base64.NO_PADDING))
|
||||||
}
|
}
|
||||||
return key
|
return key
|
||||||
}
|
}
|
||||||
@ -84,7 +84,7 @@ internal class RealmKeysUtils @Inject constructor(context: Context,
|
|||||||
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
|
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
|
||||||
val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING)
|
val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING)
|
||||||
val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias)
|
val b64 = secretStoringUtils.loadSecureSecret(encryptedKey, alias)
|
||||||
return Base64.decode(b64!!, Base64.NO_PADDING)
|
return Base64.decode(b64, Base64.NO_PADDING)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
|
fun configureEncryption(realmConfigurationBuilder: RealmConfiguration.Builder, alias: String) {
|
||||||
|
@ -36,6 +36,7 @@ import org.matrix.android.sdk.internal.session.TestInterceptor
|
|||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
import org.matrix.android.sdk.internal.util.BackgroundDetectionObserver
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||||
import org.matrix.olm.OlmManager
|
import org.matrix.olm.OlmManager
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ import java.io.File
|
|||||||
NetworkModule::class,
|
NetworkModule::class,
|
||||||
AuthModule::class,
|
AuthModule::class,
|
||||||
RawModule::class,
|
RawModule::class,
|
||||||
|
SystemModule::class,
|
||||||
NoOpTestModule::class
|
NoOpTestModule::class
|
||||||
])
|
])
|
||||||
@MatrixScope
|
@MatrixScope
|
||||||
|
@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.session.user.accountdata.AccountDataModul
|
|||||||
import org.matrix.android.sdk.internal.session.widgets.WidgetModule
|
import org.matrix.android.sdk.internal.session.widgets.WidgetModule
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
||||||
|
import org.matrix.android.sdk.internal.util.system.SystemModule
|
||||||
|
|
||||||
@Component(dependencies = [MatrixComponent::class],
|
@Component(dependencies = [MatrixComponent::class],
|
||||||
modules = [
|
modules = [
|
||||||
@ -80,6 +81,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
|||||||
CacheModule::class,
|
CacheModule::class,
|
||||||
MediaModule::class,
|
MediaModule::class,
|
||||||
CryptoModule::class,
|
CryptoModule::class,
|
||||||
|
SystemModule::class,
|
||||||
PushersModule::class,
|
PushersModule::class,
|
||||||
OpenIdModule::class,
|
OpenIdModule::class,
|
||||||
WidgetModule::class,
|
WidgetModule::class,
|
||||||
|
@ -18,12 +18,14 @@
|
|||||||
|
|
||||||
package org.matrix.android.sdk.internal.session.securestorage
|
package org.matrix.android.sdk.internal.session.securestorage
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.security.KeyPairGeneratorSpec
|
import android.security.KeyPairGeneratorSpec
|
||||||
import android.security.keystore.KeyGenParameterSpec
|
import android.security.keystore.KeyGenParameterSpec
|
||||||
import android.security.keystore.KeyProperties
|
import android.security.keystore.KeyProperties
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import org.matrix.android.sdk.internal.util.system.BuildVersionSdkIntProvider
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.ByteArrayInputStream
|
import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
@ -32,6 +34,7 @@ import java.io.InputStream
|
|||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.security.KeyPairGenerator
|
import java.security.KeyPairGenerator
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
@ -58,23 +61,19 @@ import javax.security.auth.x500.X500Principal
|
|||||||
* is not available.
|
* is not available.
|
||||||
*
|
*
|
||||||
* <b>Android [K-M[</b>
|
* <b>Android [K-M[</b>
|
||||||
* For android >=KITKAT and <M, we use the keystore to generate and store a private/public key pair. Then for each secret, a
|
* For android >=L and <M, we use the keystore to generate and store a private/public key pair. Then for each secret, a
|
||||||
* random secret key in generated to perform encryption.
|
* random secret key in generated to perform encryption.
|
||||||
* This secret key is encrypted with the public RSA key and stored with the encrypted secret.
|
* This secret key is encrypted with the public RSA key and stored with the encrypted secret.
|
||||||
* In order to decrypt the encrypted secret key will be retrieved then decrypted with the RSA private key.
|
* In order to decrypt the encrypted secret key will be retrieved then decrypted with the RSA private key.
|
||||||
*
|
*
|
||||||
* <b>Older androids</b>
|
|
||||||
* For older androids as a fallback we generate an AES key from the alias using PBKDF2 with random salt.
|
|
||||||
* The salt and iv are stored with encrypted data.
|
|
||||||
*
|
|
||||||
* Sample usage:
|
* Sample usage:
|
||||||
* <code>
|
* <code>
|
||||||
* val secret = "The answer is 42"
|
* val secret = "The answer is 42"
|
||||||
* val KEncrypted = SecretStoringUtils.securelyStoreString(secret, "myAlias", context)
|
* val KEncrypted = SecretStoringUtils.securelyStoreString(secret, "myAlias")
|
||||||
* //This can be stored anywhere e.g. encoded in b64 and stored in preference for example
|
* //This can be stored anywhere e.g. encoded in b64 and stored in preference for example
|
||||||
*
|
*
|
||||||
* //to get back the secret, just call
|
* //to get back the secret, just call
|
||||||
* val kDecrypted = SecretStoringUtils.loadSecureSecret(KEncrypted!!, "myAlias", context)
|
* val kDecrypted = SecretStoringUtils.loadSecureSecret(KEncrypted, "myAlias")
|
||||||
* </code>
|
* </code>
|
||||||
*
|
*
|
||||||
* You can also just use this utility to store a secret key, and use any encryption algorithm that you want.
|
* You can also just use this utility to store a secret key, and use any encryption algorithm that you want.
|
||||||
@ -82,7 +81,10 @@ import javax.security.auth.x500.X500Principal
|
|||||||
* Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you
|
* Important: Keys stored in the keystore can be wiped out (depends of the OS version, like for example if you
|
||||||
* add a pin or change the schema); So you might and with a useless pile of bytes.
|
* add a pin or change the schema); So you might and with a useless pile of bytes.
|
||||||
*/
|
*/
|
||||||
internal class SecretStoringUtils @Inject constructor(private val context: Context) {
|
internal class SecretStoringUtils @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider
|
||||||
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
|
private const val ANDROID_KEY_STORE = "AndroidKeyStore"
|
||||||
@ -91,7 +93,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
|
|
||||||
private const val FORMAT_API_M: Byte = 0
|
private const val FORMAT_API_M: Byte = 0
|
||||||
private const val FORMAT_1: Byte = 1
|
private const val FORMAT_1: Byte = 1
|
||||||
private const val FORMAT_2: Byte = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private val keyStore: KeyStore by lazy {
|
private val keyStore: KeyStore by lazy {
|
||||||
@ -113,17 +114,17 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
/**
|
/**
|
||||||
* Encrypt the given secret using the android Keystore.
|
* Encrypt the given secret using the android Keystore.
|
||||||
* On android >= M, will directly use the keystore to generate a symmetric key
|
* On android >= M, will directly use the keystore to generate a symmetric key
|
||||||
* On android >= KitKat and <M, as symmetric key gen is not available, will use an symmetric key generated
|
* On android >= Lollipop and <M, as symmetric key gen is not available, will use an symmetric key generated
|
||||||
* in the keystore to encrypted a random symmetric key. The encrypted symmetric key is returned
|
* in the keystore to encrypted a random symmetric key. The encrypted symmetric key is returned
|
||||||
* in the bytearray (in can be stored anywhere, it is encrypted)
|
* in the bytearray (in can be stored anywhere, it is encrypted)
|
||||||
* On older version a key in generated from alias with random salt.
|
|
||||||
*
|
*
|
||||||
* The secret is encrypted using the following method: AES/GCM/NoPadding
|
* The secret is encrypted using the following method: AES/GCM/NoPadding
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun securelyStoreString(secret: String, keyAlias: String): ByteArray? {
|
fun securelyStoreString(secret: String, keyAlias: String): ByteArray {
|
||||||
return when {
|
return when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias)
|
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> encryptStringM(secret, keyAlias)
|
||||||
else -> encryptString(secret, keyAlias)
|
else -> encryptString(secret, keyAlias)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,25 +132,34 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
/**
|
/**
|
||||||
* Decrypt a secret that was encrypted by #securelyStoreString()
|
* Decrypt a secret that was encrypted by #securelyStoreString()
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("NewApi")
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String? {
|
fun loadSecureSecret(encrypted: ByteArray, keyAlias: String): String {
|
||||||
return when {
|
encrypted.inputStream().use { inputStream ->
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> decryptStringM(encrypted, keyAlias)
|
// First get the format
|
||||||
else -> decryptString(encrypted, keyAlias)
|
return when (val format = inputStream.read().toByte()) {
|
||||||
|
FORMAT_API_M -> decryptStringM(inputStream, keyAlias)
|
||||||
|
FORMAT_1 -> decryptString(inputStream, keyAlias)
|
||||||
|
else -> throw IllegalArgumentException("Unknown format $format")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) {
|
fun securelyStoreObject(any: Any, keyAlias: String, output: OutputStream) {
|
||||||
when {
|
when {
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any)
|
buildVersionSdkIntProvider.get() >= Build.VERSION_CODES.M -> saveSecureObjectM(keyAlias, output, any)
|
||||||
else -> saveSecureObject(keyAlias, output, any)
|
else -> saveSecureObject(keyAlias, output, any)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NewApi")
|
||||||
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T? {
|
fun <T> loadSecureSecret(inputStream: InputStream, keyAlias: String): T? {
|
||||||
return when {
|
// First get the format
|
||||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> loadSecureObjectM(keyAlias, inputStream)
|
return when (val format = inputStream.read().toByte()) {
|
||||||
else -> loadSecureObject(keyAlias, inputStream)
|
FORMAT_API_M -> loadSecureObjectM(keyAlias, inputStream)
|
||||||
|
FORMAT_1 -> loadSecureObject(keyAlias, inputStream)
|
||||||
|
else -> throw IllegalArgumentException("Unknown format $format")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +190,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
- Store the encrypted AES
|
- Store the encrypted AES
|
||||||
Generate a key pair for encryption
|
Generate a key pair for encryption
|
||||||
*/
|
*/
|
||||||
fun getOrGenerateKeyPairForAlias(alias: String): KeyStore.PrivateKeyEntry {
|
private fun getOrGenerateKeyPairForAlias(alias: String): KeyStore.PrivateKeyEntry {
|
||||||
val privateKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry)
|
val privateKeyEntry = (keyStore.getEntry(alias, null) as? KeyStore.PrivateKeyEntry)
|
||||||
|
|
||||||
if (privateKeyEntry != null) return privateKeyEntry
|
if (privateKeyEntry != null) return privateKeyEntry
|
||||||
@ -193,7 +203,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
.setAlias(alias)
|
.setAlias(alias)
|
||||||
.setSubject(X500Principal("CN=$alias"))
|
.setSubject(X500Principal("CN=$alias"))
|
||||||
.setSerialNumber(BigInteger.TEN)
|
.setSerialNumber(BigInteger.TEN)
|
||||||
// .setEncryptionRequired() requires that the phone as a pin/schema
|
// .setEncryptionRequired() requires that the phone has a pin/schema
|
||||||
.setStartDate(start.time)
|
.setStartDate(start.time)
|
||||||
.setEndDate(end.time)
|
.setEndDate(end.time)
|
||||||
.build()
|
.build()
|
||||||
@ -205,7 +215,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
fun encryptStringM(text: String, keyAlias: String): ByteArray? {
|
private fun encryptStringM(text: String, keyAlias: String): ByteArray {
|
||||||
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
||||||
|
|
||||||
val cipher = Cipher.getInstance(AES_MODE)
|
val cipher = Cipher.getInstance(AES_MODE)
|
||||||
@ -217,8 +227,8 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
private fun decryptStringM(encryptedChunk: ByteArray, keyAlias: String): String {
|
private fun decryptStringM(inputStream: InputStream, keyAlias: String): String {
|
||||||
val (iv, encryptedText) = formatMExtract(encryptedChunk.inputStream())
|
val (iv, encryptedText) = formatMExtract(inputStream)
|
||||||
|
|
||||||
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
||||||
|
|
||||||
@ -229,7 +239,7 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
return String(cipher.doFinal(encryptedText), Charsets.UTF_8)
|
return String(cipher.doFinal(encryptedText), Charsets.UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun encryptString(text: String, keyAlias: String): ByteArray? {
|
private fun encryptString(text: String, keyAlias: String): ByteArray {
|
||||||
// we generate a random symmetric key
|
// we generate a random symmetric key
|
||||||
val key = ByteArray(16)
|
val key = ByteArray(16)
|
||||||
secureRandom.nextBytes(key)
|
secureRandom.nextBytes(key)
|
||||||
@ -246,8 +256,8 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
return format1Make(encryptedKey, iv, encryptedBytes)
|
return format1Make(encryptedKey, iv, encryptedBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun decryptString(data: ByteArray, keyAlias: String): String? {
|
private fun decryptString(inputStream: InputStream, keyAlias: String): String {
|
||||||
val (encryptedKey, iv, encrypted) = format1Extract(ByteArrayInputStream(data))
|
val (encryptedKey, iv, encrypted) = format1Extract(inputStream)
|
||||||
|
|
||||||
// we need to decrypt the key
|
// we need to decrypt the key
|
||||||
val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey))
|
val sKeyBytes = rsaDecrypt(keyAlias, ByteArrayInputStream(encryptedKey))
|
||||||
@ -307,30 +317,11 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
output.write(bos1.toByteArray())
|
output.write(bos1.toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
// @RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
// @Throws(IOException::class)
|
|
||||||
// fun saveSecureObjectM(keyAlias: String, file: File, writeObject: Any) {
|
|
||||||
// FileOutputStream(file).use {
|
|
||||||
// saveSecureObjectM(keyAlias, it, writeObject)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @RequiresApi(Build.VERSION_CODES.M)
|
|
||||||
// @Throws(IOException::class)
|
|
||||||
// fun <T> loadSecureObjectM(keyAlias: String, file: File): T? {
|
|
||||||
// FileInputStream(file).use {
|
|
||||||
// return loadSecureObjectM<T>(keyAlias, it)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.M)
|
@RequiresApi(Build.VERSION_CODES.M)
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
private fun <T> loadSecureObjectM(keyAlias: String, inputStream: InputStream): T? {
|
private fun <T> loadSecureObjectM(keyAlias: String, inputStream: InputStream): T? {
|
||||||
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
val secretKey = getOrGenerateSymmetricKeyForAliasM(keyAlias)
|
||||||
|
|
||||||
val format = inputStream.read()
|
|
||||||
assert(format.toByte() == FORMAT_API_M)
|
|
||||||
|
|
||||||
val ivSize = inputStream.read()
|
val ivSize = inputStream.read()
|
||||||
val iv = ByteArray(ivSize)
|
val iv = ByteArray(ivSize)
|
||||||
inputStream.read(iv, 0, ivSize)
|
inputStream.read(iv, 0, ivSize)
|
||||||
@ -393,9 +384,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
|
private fun formatMExtract(bis: InputStream): Pair<ByteArray, ByteArray> {
|
||||||
val format = bis.read().toByte()
|
|
||||||
assert(format == FORMAT_API_M)
|
|
||||||
|
|
||||||
val ivSize = bis.read()
|
val ivSize = bis.read()
|
||||||
val iv = ByteArray(ivSize)
|
val iv = ByteArray(ivSize)
|
||||||
bis.read(iv, 0, ivSize)
|
bis.read(iv, 0, ivSize)
|
||||||
@ -414,9 +402,6 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun format1Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
|
private fun format1Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
|
||||||
val format = bis.read()
|
|
||||||
assert(format.toByte() == FORMAT_1)
|
|
||||||
|
|
||||||
val keySizeBig = bis.read()
|
val keySizeBig = bis.read()
|
||||||
val keySizeLow = bis.read()
|
val keySizeLow = bis.read()
|
||||||
val encryptedKeySize = keySizeBig.shl(8) + keySizeLow
|
val encryptedKeySize = keySizeBig.shl(8) + keySizeLow
|
||||||
@ -443,32 +428,4 @@ internal class SecretStoringUtils @Inject constructor(private val context: Conte
|
|||||||
|
|
||||||
return bos.toByteArray()
|
return bos.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun format2Make(salt: ByteArray, iv: ByteArray, encryptedBytes: ByteArray): ByteArray {
|
|
||||||
val bos = ByteArrayOutputStream(3 + salt.size + iv.size + encryptedBytes.size)
|
|
||||||
bos.write(FORMAT_2.toInt())
|
|
||||||
bos.write(salt.size)
|
|
||||||
bos.write(salt)
|
|
||||||
bos.write(iv.size)
|
|
||||||
bos.write(iv)
|
|
||||||
bos.write(encryptedBytes)
|
|
||||||
|
|
||||||
return bos.toByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun format2Extract(bis: InputStream): Triple<ByteArray, ByteArray, ByteArray> {
|
|
||||||
val format = bis.read()
|
|
||||||
assert(format.toByte() == FORMAT_2)
|
|
||||||
|
|
||||||
val saltSize = bis.read()
|
|
||||||
val salt = ByteArray(saltSize)
|
|
||||||
bis.read(salt)
|
|
||||||
|
|
||||||
val ivSize = bis.read()
|
|
||||||
val iv = ByteArray(ivSize)
|
|
||||||
bis.read(iv)
|
|
||||||
|
|
||||||
val encrypted = bis.readBytes()
|
|
||||||
return Triple(salt, iv, encrypted)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.util.system
|
||||||
|
|
||||||
|
internal interface BuildVersionSdkIntProvider {
|
||||||
|
/**
|
||||||
|
* Return the current version of the Android SDK
|
||||||
|
*/
|
||||||
|
fun get(): Int
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.util.system
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class DefaultBuildVersionSdkIntProvider @Inject constructor()
|
||||||
|
: BuildVersionSdkIntProvider {
|
||||||
|
override fun get() = Build.VERSION.SDK_INT
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.util.system
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
|
||||||
|
@Module
|
||||||
|
internal abstract class SystemModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindBuildVersionSdkIntProvider(provider: DefaultBuildVersionSdkIntProvider): BuildVersionSdkIntProvider
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user