mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2024-11-27 12:00:03 +03:00
Merge pull request #227 from vector-im/feature/encrypt_local_data
Encrypt Realm databases
This commit is contained in:
commit
504d7e95fd
8 changed files with 122 additions and 28 deletions
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package im.vector.riotredesign.core.utils
|
package im.vector.matrix.android.api.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
|
@ -23,8 +23,8 @@ import dagger.Provides
|
||||||
import im.vector.matrix.android.api.auth.Authenticator
|
import im.vector.matrix.android.api.auth.Authenticator
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.database.configureEncryption
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
import im.vector.matrix.android.internal.di.MatrixScope
|
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ internal abstract class AuthModule {
|
||||||
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
old.renameTo(File(context.filesDir, "matrix-sdk-auth.realm"))
|
||||||
}
|
}
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
|
.configureEncryption("matrix-sdk-auth", context)
|
||||||
.name("matrix-sdk-auth.realm")
|
.name("matrix-sdk-auth.realm")
|
||||||
.modules(AuthRealmModule())
|
.modules(AuthRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
|
@ -50,7 +51,6 @@ internal abstract class AuthModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
||||||
|
|
||||||
|
|
|
@ -29,12 +29,13 @@ import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStore
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreMigration
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
import im.vector.matrix.android.internal.crypto.store.db.RealmCryptoStoreModule
|
||||||
import im.vector.matrix.android.internal.crypto.store.db.hash
|
|
||||||
import im.vector.matrix.android.internal.crypto.tasks.*
|
import im.vector.matrix.android.internal.crypto.tasks.*
|
||||||
|
import im.vector.matrix.android.internal.database.configureEncryption
|
||||||
import im.vector.matrix.android.internal.di.CryptoDatabase
|
import im.vector.matrix.android.internal.di.CryptoDatabase
|
||||||
import im.vector.matrix.android.internal.session.SessionScope
|
import im.vector.matrix.android.internal.session.SessionScope
|
||||||
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
|
||||||
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
|
||||||
|
import im.vector.matrix.android.internal.util.md5
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -45,14 +46,16 @@ internal abstract class CryptoModule {
|
||||||
@Module
|
@Module
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
// Realm configuration, named to avoid clash with main cache realm configuration
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Provides
|
@Provides
|
||||||
@CryptoDatabase
|
@CryptoDatabase
|
||||||
@SessionScope
|
@SessionScope
|
||||||
fun providesRealmConfiguration(context: Context, credentials: Credentials): RealmConfiguration {
|
fun providesRealmConfiguration(context: Context, credentials: Credentials): RealmConfiguration {
|
||||||
|
val userIDHash = credentials.userId.md5()
|
||||||
|
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(File(context.filesDir, credentials.userId.hash()))
|
.directory(File(context.filesDir, userIDHash))
|
||||||
|
.configureEncryption("crypto_module_$userIDHash", context)
|
||||||
.name("crypto_store.realm")
|
.name("crypto_store.realm")
|
||||||
.modules(RealmCryptoStoreModule())
|
.modules(RealmCryptoStoreModule())
|
||||||
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
.schemaVersion(RealmCryptoStoreMigration.CRYPTO_STORE_SCHEMA_VERSION)
|
||||||
|
@ -169,6 +172,4 @@ internal abstract class CryptoModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
abstract fun bindDeleteDeviceWithUserPasswordTask(deleteDeviceWithUserPasswordTask: DefaultDeleteDeviceWithUserPasswordTask): DeleteDeviceWithUserPasswordTask
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,27 +25,9 @@ import java.io.ByteArrayInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.util.zip.GZIPInputStream
|
import java.util.zip.GZIPInputStream
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute a Hash of a String, using md5 algorithm
|
|
||||||
*/
|
|
||||||
fun String.hash() = try {
|
|
||||||
val digest = MessageDigest.getInstance("md5")
|
|
||||||
digest.update(toByteArray())
|
|
||||||
val bytes = digest.digest()
|
|
||||||
val sb = StringBuilder()
|
|
||||||
for (i in bytes.indices) {
|
|
||||||
sb.append(String.format("%02X", bytes[i]))
|
|
||||||
}
|
|
||||||
sb.toString().toLowerCase()
|
|
||||||
} catch (exc: Exception) {
|
|
||||||
// Should not happen, but just in case
|
|
||||||
hashCode().toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get realm, invoke the action, close realm, and return the result of the action
|
* Get realm, invoke the action, close realm, and return the result of the action
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* 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.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Base64
|
||||||
|
import im.vector.matrix.android.api.util.SecretStoringUtils
|
||||||
|
import io.realm.RealmConfiguration
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.security.SecureRandom
|
||||||
|
|
||||||
|
/**
|
||||||
|
* On creation a random key is generated, this key is then encrypted using the system KeyStore.
|
||||||
|
* The encrypted key is stored in shared preferences.
|
||||||
|
* When the database is opened again, the encrypted key is taken from the shared pref,
|
||||||
|
* then the Keystore is used to decrypt the key. The decrypted key is passed to the RealConfiguration.
|
||||||
|
*
|
||||||
|
* On android >=M, the KeyStore generates an AES key to encrypt/decrypt the database key,
|
||||||
|
* and the encrypted key is stored with the initialization vector in base64 in the shared pref.
|
||||||
|
* On android <M, the KeyStore cannot create AES keys, so a public/private key pair is generated,
|
||||||
|
* then we generate a random secret key. The database key is encrypted with the secret key; The secret
|
||||||
|
* key is encrypted with the public RSA key and stored with the encrypted key in the shared pref
|
||||||
|
*/
|
||||||
|
private object RealmKeysUtils {
|
||||||
|
|
||||||
|
private const val ENCRYPTED_KEY_PREFIX = "REALM_ENCRYPTED_KEY"
|
||||||
|
|
||||||
|
private val rng = SecureRandom()
|
||||||
|
|
||||||
|
private fun generateKeyForRealm(): ByteArray {
|
||||||
|
val keyForRealm = ByteArray(RealmConfiguration.KEY_LENGTH)
|
||||||
|
rng.nextBytes(keyForRealm)
|
||||||
|
return keyForRealm
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if there is already a key for this alias
|
||||||
|
*/
|
||||||
|
fun hasKeyForDatabase(alias: String, context: Context): Boolean {
|
||||||
|
val sharedPreferences = getSharedPreferences(context)
|
||||||
|
return sharedPreferences.contains("${ENCRYPTED_KEY_PREFIX}_$alias")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new secure random key for this database.
|
||||||
|
* The random key is then encrypted by the keystore, and the encrypted key is stored
|
||||||
|
* in shared preferences.
|
||||||
|
*
|
||||||
|
* @return the generate key (can be passed to Realm Configuration)
|
||||||
|
*/
|
||||||
|
fun createAndSaveKeyForDatabase(alias: String, context: Context): ByteArray {
|
||||||
|
val key = generateKeyForRealm()
|
||||||
|
val encodedKey = Base64.encodeToString(key, Base64.NO_PADDING)
|
||||||
|
val toStore = SecretStoringUtils.securelyStoreString(encodedKey, alias, context)
|
||||||
|
val sharedPreferences = getSharedPreferences(context)
|
||||||
|
sharedPreferences
|
||||||
|
.edit()
|
||||||
|
.putString("${ENCRYPTED_KEY_PREFIX}_$alias", Base64.encodeToString(toStore!!, Base64.NO_PADDING))
|
||||||
|
.apply()
|
||||||
|
return key
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the key for this database
|
||||||
|
* throws if something goes wrong
|
||||||
|
*/
|
||||||
|
fun extractKeyForDatabase(alias: String, context: Context): ByteArray {
|
||||||
|
val sharedPreferences = getSharedPreferences(context)
|
||||||
|
val encryptedB64 = sharedPreferences.getString("${ENCRYPTED_KEY_PREFIX}_$alias", null)
|
||||||
|
val encryptedKey = Base64.decode(encryptedB64, Base64.NO_PADDING)
|
||||||
|
val b64 = SecretStoringUtils.loadSecureSecret(encryptedKey, alias, context)
|
||||||
|
return Base64.decode(b64!!, Base64.NO_PADDING)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSharedPreferences(context: Context) =
|
||||||
|
context.getSharedPreferences("im.vector.matrix.android.keys", Context.MODE_PRIVATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun RealmConfiguration.Builder.configureEncryption(alias: String, context: Context): RealmConfiguration.Builder {
|
||||||
|
if (RealmKeysUtils.hasKeyForDatabase(alias, context)) {
|
||||||
|
Timber.i("Found key for alias:$alias")
|
||||||
|
RealmKeysUtils.extractKeyForDatabase(alias, context).also {
|
||||||
|
this.encryptionKey(it)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.i("Create key for DB alias:$alias")
|
||||||
|
RealmKeysUtils.createAndSaveKeyForDatabase(alias, context).also {
|
||||||
|
this.encryptionKey(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
|
@ -27,6 +27,7 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
import im.vector.matrix.android.internal.database.LiveEntityObserver
|
||||||
|
import im.vector.matrix.android.internal.database.configureEncryption
|
||||||
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
import im.vector.matrix.android.internal.database.model.SessionRealmModule
|
||||||
import im.vector.matrix.android.internal.di.Authenticated
|
import im.vector.matrix.android.internal.di.Authenticated
|
||||||
import im.vector.matrix.android.internal.di.SessionDatabase
|
import im.vector.matrix.android.internal.di.SessionDatabase
|
||||||
|
@ -74,6 +75,7 @@ internal abstract class SessionModule {
|
||||||
return RealmConfiguration.Builder()
|
return RealmConfiguration.Builder()
|
||||||
.directory(directory)
|
.directory(directory)
|
||||||
.name("disk_store.realm")
|
.name("disk_store.realm")
|
||||||
|
.configureEncryption("session_db_$childPath", context)
|
||||||
.modules(SessionRealmModule())
|
.modules(SessionRealmModule())
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
.build()
|
.build()
|
||||||
|
|
|
@ -18,6 +18,9 @@ package im.vector.matrix.android.internal.util
|
||||||
|
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute a Hash of a String, using md5 algorithm
|
||||||
|
*/
|
||||||
fun String.md5() = try {
|
fun String.md5() = try {
|
||||||
val digest = MessageDigest.getInstance("md5")
|
val digest = MessageDigest.getInstance("md5")
|
||||||
digest.update(toByteArray())
|
digest.update(toByteArray())
|
||||||
|
|
|
@ -21,10 +21,10 @@ import android.graphics.Bitmap
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.Person
|
import androidx.core.app.Person
|
||||||
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
import im.vector.matrix.android.api.session.content.ContentUrlResolver
|
||||||
|
import im.vector.matrix.android.api.util.SecretStoringUtils
|
||||||
import im.vector.riotredesign.BuildConfig
|
import im.vector.riotredesign.BuildConfig
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.di.ActiveSessionHolder
|
import im.vector.riotredesign.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotredesign.core.utils.SecretStoringUtils
|
|
||||||
import im.vector.riotredesign.features.settings.PreferencesManager
|
import im.vector.riotredesign.features.settings.PreferencesManager
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
Loading…
Reference in a new issue