-
Notifications
You must be signed in to change notification settings - Fork 0
Inji 214: Setup Native module for storing keys in hardware key store android #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tilak-puli
wants to merge
31
commits into
develop
Choose a base branch
from
inji-214
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
cf7e4d1
chore(inji-214): api for device supports hardware backed keystore
dhivya0413 342ab5a
chore(inji-214): add encrypt and decrypt functions to secure key store
tilak-puli aee7e9a
chore(inji-214): extract CipherBox and KeyGenerator from SecureKeystore
tilak-puli 3c6ac81
chore(inji-214): update GenerateKeyPair to return PEM encoded public …
tilak-puli 50cb5f4
chore(inji-214): create sign method to sign with RSA private Key
tilak-puli b958317
feat(inji-214): [Tilak|Dhivya] add hasAlias impl
dhivya0413 dc83d77
feat(inji-214): [Tilak|Dhivya] add hasAlias impl
dhivya0413 bebd99a
feat(inji-214): [Tilak|Dhivya] fix generate key pair pem string by us…
tilak-puli 3dd785a
feat(inji-214): [Tilak|Dhivya] add generate HmacSha method to generat…
tilak-puli 065d174
feat(inji-214): [Tilak|Dhivya] validate encrypted text before decryption
tilak-puli 150bc35
feat(inji-214): [Tilak|Dhivya] resolve conflicts
dhivya0413 059481b
feat(inji-214): [Dhivya|Tilak] refactor encryption and sign the priva…
dhivya0413 98b1f66
feat(inji-214): [Tilak|Dhivya] add auth prompt for signing with keypair
tilak-puli 7685f2f
feat(inji-214): [Tilak|Dhivya] use consistent error code for creat si…
tilak-puli d962d1c
feat(inji-214): [Tilak|Dhivya] enable device credential for auth in c…
tilak-puli 4d88e61
feat(inji-214): [Tilak|Dhivya] remove device credential for auth in c…
tilak-puli 08d9c8d
feat(inji-214): [Tilak|Dhivya] update documentation
tilak-puli 8cf5c81
feat(inji-214): [Tilak|Dhivya] use key to generate hmac and remove da…
tilak-puli 0a73fcf
feat(inji-214): [Tilak|Dhivya] use AndroidX for backward support and …
tilak-puli 9b1a4d2
feat(inji-214): [Tilak|Dhivya] Use kotlin co routines to block thread…
tilak-puli 6397a93
feat(inji-214): [Tilak|Dhivya] fix timeout based key auth is failing
tilak-puli 6cc5a08
feat(inji-214): [Tilak|Dhivya] rename authenticateAndPerform paramete…
tilak-puli 32c32ee
feat(INJI-214): generate hmacsha256 key
dhivya0413 cc8c763
feat(inji-214): [Tilak|Dhivya] throw KeyInvalidatedException if key i…
tilak-puli da8b19d
feat(inji-214): [Tilak|Dhivya] fix keyinfo throwing exception when ru…
tilak-puli 6dfb6ee
feat(INJI-214): add the exception block and remove all keys
dhivya0413 cfd3507
feat(inji-214): add api for biometric is enabled
dhivya0413 80ab687
feat(inji-214): [Tilak|Dhivya] move remove all keys into keystore
tilak-puli 62586f1
feat(inji-214): [Tilak|Dhivya] Disable Invalidation of keys on biomet…
tilak-puli 1d26b1f
feat(inji-214): [Tilak|Dhivya] Handle user not auth exception for sig…
tilak-puli 610df42
feat(inji-214): [Tilak|Dhivya] Add a api to update biometric popup text
tilak-puli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| SecureKeystore_kotlinVersion=1.7.0 | ||
| SecureKeystore_minSdkVersion=21 | ||
| SecureKeystore_minSdkVersion=23 | ||
| SecureKeystore_targetSdkVersion=31 | ||
| SecureKeystore_compileSdkVersion=31 | ||
| SecureKeystore_ndkversion=21.4.7075529 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| package="com.reactnativesecurekeystore"> | ||
|
|
||
| <uses-permission android:name="android.permission.USE_BIOMETRIC" /> | ||
| </manifest> |
13 changes: 13 additions & 0 deletions
13
android/src/main/java/com/reactnativesecurekeystore/CipherBox.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.reactnativesecurekeystore | ||
|
|
||
| import com.reactnativesecurekeystore.dto.EncryptedOutput | ||
| import java.security.Key | ||
| import java.security.PrivateKey | ||
| import java.security.Signature | ||
|
|
||
| interface CipherBox { | ||
| fun encryptData(key: Key, data: String): EncryptedOutput | ||
| fun decryptData(key: Key, encryptedOutput: EncryptedOutput): ByteArray | ||
| fun createSignature(key: PrivateKey, data: String): Signature | ||
| fun generateHmacSha(data: String): ByteArray | ||
| } |
54 changes: 54 additions & 0 deletions
54
android/src/main/java/com/reactnativesecurekeystore/CipherBoxImpl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| package com.reactnativesecurekeystore | ||
|
|
||
| import android.security.keystore.KeyProperties | ||
| import com.reactnativesecurekeystore.dto.EncryptedOutput | ||
| import java.security.Key | ||
| import java.security.MessageDigest | ||
| import java.security.PrivateKey | ||
| import java.security.Signature | ||
| import javax.crypto.Cipher | ||
| import javax.crypto.spec.GCMParameterSpec | ||
|
|
||
|
|
||
| const val CIPHER_ALGORITHM = | ||
| "${KeyProperties.KEY_ALGORITHM_AES}/${KeyProperties.BLOCK_MODE_GCM}/${KeyProperties.ENCRYPTION_PADDING_NONE}" | ||
| const val GCM_TAG_LEN = 128 | ||
| const val SIGN_ALGORITHM = "SHA256with${KeyProperties.KEY_ALGORITHM_RSA}" | ||
| const val HMAC_ALGORITHM = "SHA256" | ||
|
|
||
| class CipherBoxImpl : CipherBox { | ||
| override fun encryptData(key: Key, data: String): EncryptedOutput { | ||
| val cipher = Cipher.getInstance(CIPHER_ALGORITHM) | ||
| cipher.init(Cipher.ENCRYPT_MODE, key) | ||
|
|
||
| val encryptedData = cipher.doFinal(data.toByteArray()); | ||
|
|
||
| return EncryptedOutput(encryptedData, cipher.iv) | ||
| } | ||
|
|
||
| override fun createSignature(key: PrivateKey, data: String): Signature { | ||
| val hash = data.toByteArray(charset("UTF8")) | ||
| val signature = Signature.getInstance(SIGN_ALGORITHM).run { | ||
| initSign(key) | ||
| update(hash) | ||
| this | ||
| } | ||
|
|
||
| return signature | ||
| } | ||
|
|
||
| override fun generateHmacSha(data: String): ByteArray { | ||
| val messageDigest = MessageDigest.getInstance(HMAC_ALGORITHM) | ||
| messageDigest.update(data.toByteArray()) | ||
| return messageDigest.digest() | ||
| } | ||
|
|
||
| override fun decryptData(key: Key, encryptedOutput: EncryptedOutput): ByteArray { | ||
| val cipher = Cipher.getInstance(CIPHER_ALGORITHM) | ||
|
|
||
| val spec = GCMParameterSpec(GCM_TAG_LEN, encryptedOutput.iv) | ||
| cipher.init(Cipher.DECRYPT_MODE, key, spec) | ||
|
|
||
| return cipher.doFinal(encryptedOutput.encryptedData); | ||
| } | ||
| } | ||
63 changes: 63 additions & 0 deletions
63
android/src/main/java/com/reactnativesecurekeystore/DeviceCapability.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| package com.reactnativesecurekeystore | ||
|
|
||
| import android.os.Build | ||
| import android.security.keystore.KeyInfo | ||
| import android.util.Log | ||
| import java.security.GeneralSecurityException | ||
| import java.util.concurrent.atomic.AtomicBoolean | ||
| import javax.crypto.SecretKey | ||
| import javax.crypto.SecretKeyFactory | ||
|
|
||
|
|
||
| class DeviceCapability(private val secureKeystore: SecureKeystore, private val KeyGenerator: KeyGeneratorImpl) { | ||
| private val mutex = Object() | ||
|
|
||
| @Transient | ||
| var isSupportsSecureHardware: AtomicBoolean? = null | ||
|
|
||
| fun supportsHardwareKeyStore(): Boolean { | ||
| if (isSupportsSecureHardware != null) return isSupportsSecureHardware!!.get() | ||
|
|
||
| synchronized(mutex) { | ||
| /** double check if it has value in sync block */ | ||
| if (isSupportsSecureHardware != null) return isSupportsSecureHardware!!.get() | ||
| // Check the key stored in secure hardware using temporary key alias, and removed after checked | ||
| val key = KeyGenerator.generateKey(CHECK_HARDWARE_SUPPORT_KEY_ALIAS) | ||
| isSupportsSecureHardware = | ||
| AtomicBoolean(getSecurityLevel(key) == DeviceSecurityLevel.SECURE_HARDWARE) | ||
| Log.i("SecureStorage", "Device Supports Hardware $isSupportsSecureHardware") | ||
| secureKeystore.removeKey(CHECK_HARDWARE_SUPPORT_KEY_ALIAS) | ||
| return isSupportsSecureHardware!!.get() | ||
| } | ||
| } | ||
|
|
||
| /** Get the supported level of security for provided Key instance. */ | ||
| @Throws(GeneralSecurityException::class) | ||
| fun getSecurityLevel(key: SecretKey): DeviceSecurityLevel { | ||
| val keyInfo: KeyInfo = getKeyInfo(key) | ||
|
|
||
| val insideSecureHardware: Boolean = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { | ||
| /** SecurityLevel 1 indicates SECURITY_LEVEL_TRUSTED_ENVIRONMENT | ||
| * SecurityLevel 2 indicates SECURITY_LEVEL_STRONGBOX */ | ||
| (keyInfo.securityLevel == 1) or (keyInfo.securityLevel == 2) | ||
| } else { | ||
| keyInfo.isInsideSecureHardware() | ||
| } | ||
| Log.i("keystore", "Device has secure Hardware $insideSecureHardware") | ||
| if (insideSecureHardware) { | ||
| return DeviceSecurityLevel.SECURE_HARDWARE | ||
| } | ||
| return DeviceSecurityLevel.SECURE_SOFTWARE | ||
| } | ||
|
|
||
| /** Get information about provided key. */ | ||
| @Throws(GeneralSecurityException::class) | ||
| fun getKeyInfo(key: SecretKey): KeyInfo { | ||
| Log.i("keystore", "KeyInfo Details$key -> ${key.algorithm}") | ||
| val secretKeyFactory = SecretKeyFactory.getInstance(key.algorithm, KEYSTORE_TYPE) | ||
| return secretKeyFactory.getKeySpec( | ||
| key as SecretKey?, | ||
| KeyInfo::class.java | ||
| ) as KeyInfo | ||
| } | ||
| } |
10 changes: 10 additions & 0 deletions
10
android/src/main/java/com/reactnativesecurekeystore/DeviceSecurityLevel.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.reactnativesecurekeystore | ||
|
|
||
| /** Minimal required level of the security implementation. */ | ||
| enum class DeviceSecurityLevel { | ||
| /** Requires for the key to be stored in the Android Keystore, separate from the encrypted data. */ | ||
| SECURE_SOFTWARE, | ||
|
|
||
| /** Requires for the key to be stored on a secure hardware (Trusted Execution Environment or Secure Environment). */ | ||
| SECURE_HARDWARE; | ||
| } |
9 changes: 9 additions & 0 deletions
9
android/src/main/java/com/reactnativesecurekeystore/KeyGenerator.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.reactnativesecurekeystore | ||
|
|
||
| import java.security.KeyPair | ||
| import javax.crypto.SecretKey | ||
|
|
||
| interface KeyGenerator { | ||
| fun generateKey(alias: String): SecretKey | ||
| fun generateKeyPair(alias: String): KeyPair | ||
| } |
56 changes: 56 additions & 0 deletions
56
android/src/main/java/com/reactnativesecurekeystore/KeyGeneratorImpl.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.reactnativesecurekeystore | ||
|
|
||
| import android.security.keystore.KeyGenParameterSpec | ||
| import android.security.keystore.KeyProperties | ||
| import android.security.keystore.KeyProperties.* | ||
| import java.security.KeyPair | ||
| import java.security.KeyPairGenerator | ||
| import javax.crypto.KeyGenerator | ||
| import javax.crypto.SecretKey | ||
|
|
||
| const val KEY_PAIR_KEY_SIZE = 4096 | ||
| const val KEY_AUTH_TIMEOUT = 10 * 60 * 1000 | ||
|
|
||
| class KeyGeneratorImpl : com.reactnativesecurekeystore.KeyGenerator { | ||
| private val keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM_AES, KEYSTORE_TYPE) | ||
| private val keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_RSA, KEYSTORE_TYPE) | ||
|
|
||
| /** Generate secret key and store it in AndroidKeystore */ | ||
| override fun generateKey(alias: String): SecretKey { | ||
| keyGenerator.init( | ||
| getKeyGenSpecBuilder(alias).build() | ||
| ) | ||
|
|
||
| // Assumption: Generate Key also stores Key in Keystore | ||
| return keyGenerator.generateKey() | ||
| } | ||
|
|
||
| /** Generate a new key pair */ | ||
| override fun generateKeyPair(alias: String): KeyPair { | ||
| keyPairGenerator.initialize( | ||
| getKeyPairGenSpecBuilder(alias).build() | ||
| ) | ||
|
|
||
| return keyPairGenerator.generateKeyPair() | ||
| } | ||
|
|
||
| private fun getKeyGenSpecBuilder(alias: String): KeyGenParameterSpec.Builder { | ||
| val purposes = PURPOSE_DECRYPT or PURPOSE_ENCRYPT or PURPOSE_SIGN | ||
|
|
||
| return KeyGenParameterSpec.Builder(alias, purposes) | ||
| .setKeySize(ENCRYPTION_KEY_SIZE) | ||
| .setBlockModes(BLOCK_MODE_GCM) | ||
| .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) | ||
| } | ||
|
|
||
| private fun getKeyPairGenSpecBuilder(alias: String): KeyGenParameterSpec.Builder { | ||
| val purposes = PURPOSE_ENCRYPT or PURPOSE_DECRYPT or PURPOSE_SIGN or PURPOSE_VERIFY | ||
|
|
||
| return KeyGenParameterSpec.Builder(alias, purposes) | ||
| .setKeySize(KEY_PAIR_KEY_SIZE) | ||
| .setDigests(DIGEST_SHA256, DIGEST_SHA512, DIGEST_SHA1) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we should support SHA1 digest
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok. I will remove it. |
||
| .setEncryptionPaddings(ENCRYPTION_PADDING_RSA_PKCS1) | ||
| .setSignaturePaddings(SIGNATURE_PADDING_RSA_PKCS1) | ||
| .setUserAuthenticationRequired(true) | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope we know that HMAC SHA256 is different from SHA256. This implementation is simply calculating SHA256 but not HMAC SHA256. HMAC would require a AES key additionally.