From 9c170ae193ffc34748647a4dc033864969c42e81 Mon Sep 17 00:00:00 2001 From: Sangwook123 Date: Tue, 30 Apr 2024 12:51:43 +0900 Subject: [PATCH] [feat] #4 fake keystore --- .../security/fake/FakeAESKeyGenerator.kt | 51 ++++++ .../fake/FakeAndroidKeyStoreProvider.kt | 28 +++ .../sopt/common/security/fake/FakeKeyStore.kt | 161 ++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 core/common/src/main/java/org/sopt/common/security/fake/FakeAESKeyGenerator.kt create mode 100644 core/common/src/main/java/org/sopt/common/security/fake/FakeAndroidKeyStoreProvider.kt create mode 100644 core/common/src/main/java/org/sopt/common/security/fake/FakeKeyStore.kt diff --git a/core/common/src/main/java/org/sopt/common/security/fake/FakeAESKeyGenerator.kt b/core/common/src/main/java/org/sopt/common/security/fake/FakeAESKeyGenerator.kt new file mode 100644 index 0000000..5dc080c --- /dev/null +++ b/core/common/src/main/java/org/sopt/common/security/fake/FakeAESKeyGenerator.kt @@ -0,0 +1,51 @@ +package org.sopt.common.security.fake + +import android.security.keystore.KeyGenParameterSpec +import android.security.keystore.KeyProperties +import java.security.InvalidAlgorithmParameterException +import java.security.KeyStore +import java.security.SecureRandom +import java.security.spec.AlgorithmParameterSpec +import javax.crypto.KeyGenerator +import javax.crypto.KeyGeneratorSpi +import javax.crypto.SecretKey + +class FakeAESKeyGenerator : KeyGeneratorSpi() { + private val wrapped = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES) + private var keyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } + private var spec: KeyGenParameterSpec? = null + + override fun engineInit(random: SecureRandom) { + throw UnsupportedOperationException( + "Cannot initialize without a ${KeyGenParameterSpec::class.java.name} parameter" + ) + } + + override fun engineInit(params: AlgorithmParameterSpec?, random: SecureRandom) { + if (params == null || params !is KeyGenParameterSpec) { + throw InvalidAlgorithmParameterException( + "Cannot initialize without a ${KeyGenParameterSpec::class.java.name} parameter" + ) + } + spec = params + } + + override fun engineInit(keysize: Int, random: SecureRandom?) { + throw UnsupportedOperationException( + "Cannot initialize without a ${KeyGenParameterSpec::class.java.name} parameter" + ) + } + + override fun engineGenerateKey(): SecretKey { + val spec = spec ?: throw IllegalStateException("Not initialized") + + val secretKey = wrapped.generateKey() + keyStore.setKeyEntry( + spec.keystoreAlias, + secretKey, + null, + null + ) + return secretKey + } +} \ No newline at end of file diff --git a/core/common/src/main/java/org/sopt/common/security/fake/FakeAndroidKeyStoreProvider.kt b/core/common/src/main/java/org/sopt/common/security/fake/FakeAndroidKeyStoreProvider.kt new file mode 100644 index 0000000..c14ce76 --- /dev/null +++ b/core/common/src/main/java/org/sopt/common/security/fake/FakeAndroidKeyStoreProvider.kt @@ -0,0 +1,28 @@ +package org.sopt.common.security.fake + +import java.security.Provider +import java.security.Security + +class FakeAndroidKeyStoreProvider : Provider( + "AndroidKeyStore", + 1.0, + "Fake AndroidKeyStore provider" +) { + + init { + put( + "KeyStore.AndroidKeyStore", + FakeKeyStore::class.java.name + ) + put( + "KeyGenerator.AES", + FakeAESKeyGenerator::class.java.name + ) + } + + companion object { + fun setup() { + Security.addProvider(FakeAndroidKeyStoreProvider()) + } + } +} \ No newline at end of file diff --git a/core/common/src/main/java/org/sopt/common/security/fake/FakeKeyStore.kt b/core/common/src/main/java/org/sopt/common/security/fake/FakeKeyStore.kt new file mode 100644 index 0000000..245facf --- /dev/null +++ b/core/common/src/main/java/org/sopt/common/security/fake/FakeKeyStore.kt @@ -0,0 +1,161 @@ +package org.sopt.common.security.fake + +import java.io.InputStream +import java.io.OutputStream +import java.security.Key +import java.security.KeyStore +import java.security.KeyStoreSpi +import java.security.PrivateKey +import java.security.cert.Certificate +import java.util.Collections +import java.util.Date +import java.util.Enumeration +import javax.crypto.SecretKey + +class FakeKeyStore : KeyStoreSpi() { + companion object { + private val keys = mutableMapOf() + private val certs = mutableMapOf() + } + + override fun engineIsKeyEntry(alias: String?): Boolean { + alias ?: throw NullPointerException("alias == null") + + return keys.containsKey(alias) + } + + override fun engineIsCertificateEntry(alias: String?): Boolean { + alias ?: throw NullPointerException("alias == null") + + return certs.containsKey(alias) + } + + override fun engineGetCertificate(alias: String?): Certificate { + alias ?: throw NullPointerException("alias == null") + + return certs.getValue(alias) + } + + override fun engineGetCreationDate(alias: String?): Date { + alias ?: throw NullPointerException("alias == null") + + return Date() + } + + override fun engineDeleteEntry(alias: String?) { + alias ?: throw NullPointerException("alias == null") + + keys.remove(alias) + certs.remove(alias) + } + + override fun engineSetKeyEntry( + alias: String?, + key: Key?, + password: CharArray?, + chain: Array? + ) { + alias ?: throw NullPointerException("alias == null") + key ?: throw NullPointerException("key == null") + + keys[alias] = key + } + + override fun engineGetEntry( + alias: String?, + protParam: KeyStore.ProtectionParameter? + ): KeyStore.Entry { + alias ?: throw NullPointerException("alias == null") + + val key = keys[alias] + if (key != null) { + return when (key) { + is SecretKey -> KeyStore.SecretKeyEntry(key) + is PrivateKey -> KeyStore.PrivateKeyEntry(key, null) + else -> throw UnsupportedOperationException("Unsupported key type: $key") + } + } + val cert = certs[alias] + if (cert != null) { + return KeyStore.TrustedCertificateEntry(cert) + } + throw UnsupportedOperationException("No alias found in keys or certs, alias=$alias") + } + + override fun engineSetKeyEntry( + alias: String?, + key: ByteArray?, + chain: Array? + ) { + throw UnsupportedOperationException( + "Operation not supported because key encoding is unknown" + ) + } + + override fun engineStore(stream: OutputStream?, password: CharArray?) { + throw UnsupportedOperationException("Can not serialize AndroidKeyStore to OutputStream") + } + + override fun engineSize(): Int { + val uniqueAlias = mutableSetOf().apply { + addAll(keys.keys) + addAll(certs.keys) + } + return uniqueAlias.size + } + + override fun engineAliases(): Enumeration { + val uniqueAlias = mutableSetOf().apply { + addAll(keys.keys) + addAll(certs.keys) + } + return Collections.enumeration(uniqueAlias) + } + + override fun engineContainsAlias(alias: String?): Boolean { + alias ?: throw NullPointerException("alias == null") + + return keys.containsKey(alias) || certs.containsKey(alias) + } + + override fun engineLoad(stream: InputStream?, password: CharArray?) { + if (stream != null) { + throw IllegalArgumentException("InputStream not supported") + } + if (password != null) { + throw IllegalArgumentException("password not supported") + } + + } + + override fun engineGetCertificateChain(alias: String?): Array { + alias ?: throw NullPointerException("alias == null") + + val cert = certs[alias] ?: return arrayOf() + return arrayOf(cert) + } + + override fun engineSetCertificateEntry(alias: String?, cert: Certificate?) { + alias ?: throw NullPointerException("alias == null") + cert ?: throw NullPointerException("cert == null") + + certs[alias] = cert + } + + override fun engineGetCertificateAlias(cert: Certificate?): String? { + cert ?: throw NullPointerException("cert == null") + + for (entry in certs.entries) { + if (entry.value == cert) { + return entry.key + } + } + return null + } + + override fun engineGetKey(alias: String?, password: CharArray?): Key? { + alias ?: throw NullPointerException("alias == null") + + return keys[alias] + } +} \ No newline at end of file