diff --git a/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt b/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt index 9582fa1411991aee4d214f8d19aa3d3cf596d698..d292d9e640fd6a5d7037bad02f992dc9f2fcf0c3 100644 --- a/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt +++ b/core/src/main/java/org/futo/circles/core/provider/KeyStoreProvider.kt @@ -1,37 +1,81 @@ package org.futo.circles.core.provider +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import android.util.Base64 import org.matrix.android.sdk.api.extensions.tryOrNull import java.security.KeyStore +import javax.crypto.Cipher +import javax.crypto.KeyGenerator import javax.crypto.SecretKey -import javax.crypto.spec.SecretKeySpec +import javax.crypto.spec.GCMParameterSpec import javax.inject.Inject -class KeyStoreProvider @Inject constructor() { - private val keyStore = KeyStore.getInstance("AndroidKeyStore") +class KeyStoreProvider @Inject constructor( + private val preferencesProvider: PreferencesProvider +) { + private val keyStore + get() = KeyStore.getInstance(ANDROID_KEYS_TORE).apply { load(null) } fun storeBsSpekePrivateKey(keyBytes: ByteArray, keyId: String) { - storeKey(keyBytes, "$ORG_FUTO_SSSS_KEY_PREFIX.$keyId") + tryOrNull { + val alias = "$ORG_FUTO_SSSS_KEY_PREFIX.$keyId" + val masterKey = getOrGenerateMasterKeyIfNotExist(alias) + ?: throw IllegalArgumentException("Failed to get master key") + val encrypted = encryptData(keyBytes, masterKey) + preferencesProvider.storeBSspekeEncryptedPrivateKeyBase64(alias, encrypted) + } } - fun getBsSpekePrivateKey(keyId: String): ByteArray? = getKey("$ORG_FUTO_SSSS_KEY_PREFIX.$keyId") + fun getBsSpekePrivateKey(keyId: String): ByteArray? = tryOrNull { + val alias = "$ORG_FUTO_SSSS_KEY_PREFIX.$keyId" + val masterKey = getOrGenerateMasterKeyIfNotExist(alias) + ?: throw IllegalArgumentException("Failed to get master key") + val encrypted = preferencesProvider.getBSspekeEncryptedPrivateKeyBase64(alias) + ?: throw IllegalArgumentException("Not saved in preferences $alias") + decryptData(encrypted, masterKey) + } - private fun storeKey(keyBytes: ByteArray, alias: String) = tryOrNull { - val secretKey: SecretKey = SecretKeySpec(keyBytes, "AES") - keyStore.load(null) - val protectionParameter = KeyStore.PasswordProtection(null) - val secretKeyEntry = KeyStore.SecretKeyEntry(secretKey) - keyStore.setEntry(alias, secretKeyEntry, protectionParameter) + private fun getOrGenerateMasterKeyIfNotExist(alias: String): SecretKey? { + if (!keyStore.containsAlias(alias)) { + KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEYS_TORE).apply { + init( + KeyGenParameterSpec.Builder( + alias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT + ) + .setKeySize(KEY_SIZE) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM) + .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .build() + ) + generateKey() + } + } + return keyStore.getKey(alias, null) as? SecretKey } + private fun encryptData(data: ByteArray, masterKey: SecretKey): String { + val cipher = Cipher.getInstance(AES_NO_PADDING) + cipher.init(Cipher.ENCRYPT_MODE, masterKey) + val ivString = Base64.encodeToString(cipher.iv, Base64.DEFAULT) + val encryptedBytes: ByteArray = cipher.doFinal(data) + return ivString + IV_SEPARATOR + Base64.encodeToString(encryptedBytes, Base64.DEFAULT) + } - private fun getKey(alias: String): ByteArray? = tryOrNull { - keyStore.load(null) - val secretKey = keyStore.getKey(alias, null) as SecretKey - secretKey.encoded + private fun decryptData(encryptedData: String, masterKey: SecretKey): ByteArray { + val (iv64, encrypted64) = encryptedData.split(IV_SEPARATOR) + val cipher = Cipher.getInstance(AES_NO_PADDING) + val spec = GCMParameterSpec(KEY_SIZE, Base64.decode(iv64, Base64.DEFAULT)) + cipher.init(Cipher.DECRYPT_MODE, masterKey, spec) + return cipher.doFinal(Base64.decode(encrypted64, Base64.DEFAULT)) } companion object { + private const val AES_NO_PADDING = "AES/GCM/NoPadding" + private const val IV_SEPARATOR = "]" + private const val KEY_SIZE = 128 + private const val ANDROID_KEYS_TORE = "AndroidKeyStore" private const val ORG_FUTO_SSSS_KEY_PREFIX = "org.futo.ssss.key" } } diff --git a/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt b/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt index 0be15e3a30d79d65e19254235a2eb961e332b530..5ba08a31f400e2f5de1e7ad76f3fecd0ef39e9f5 100644 --- a/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt +++ b/core/src/main/java/org/futo/circles/core/provider/PreferencesProvider.kt @@ -95,6 +95,15 @@ class PreferencesProvider @Inject constructor( getSharedPreferences().edit(true) { putStringSet(NOT_RESTORED_SESSION, set) } } + fun getBSspekeEncryptedPrivateKeyBase64(alias: String): String? { + return getSharedPreferences().getString(alias, null) + } + + fun storeBSspekeEncryptedPrivateKeyBase64(alias: String, key: String) { + getSharedPreferences().edit { putString(alias, key) } + } + + companion object { private const val PREFERENCES_NAME = "circles_preferences" private const val DEV_MODE_KEY = "developer_mode"