react-native-keystore-crypto-joe 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -0
- package/android/build.gradle +36 -0
- package/android/consumer-rules.pro +0 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/com/keystorecrypto/KeystoreCryptoModule.kt +138 -0
- package/android/src/main/java/com/keystorecrypto/KeystoreCryptoPackage.kt +15 -0
- package/ios/KeystoreCrypto.m +15 -0
- package/ios/KeystoreCrypto.swift +109 -0
- package/lib/typescript/src/index.d.ts +11 -0
- package/package.json +38 -0
- package/react-native-keystore-crypto.podspec +13 -0
- package/react-native.config.js +8 -0
- package/src/index.ts +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# react-native-keystore-crypto (starter)
|
|
2
|
+
|
|
3
|
+
> RSA-OAEP-SHA256 decrypt using **Android Keystore** / **iOS Keychain** with optional **biometric prompt**.
|
|
4
|
+
> Use this to provision a small secret (e.g., TOTP seed) sent by a server encrypted with the app's public key.
|
|
5
|
+
|
|
6
|
+
## Install (from Git)
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
yarn add git+https://github.com/yourname/react-native-keystore-crypto.git
|
|
10
|
+
# or
|
|
11
|
+
npm i git+https://github.com/yourname/react-native-keystore-crypto.git
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For local dev, you can `yarn add ../react-native-keystore-crypto`.
|
|
15
|
+
|
|
16
|
+
### iOS
|
|
17
|
+
```bash
|
|
18
|
+
cd ios && pod install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## API
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import KeystoreCrypto from 'react-native-keystore-crypto';
|
|
25
|
+
|
|
26
|
+
await KeystoreCrypto.generateKeyPair(alias: string): Promise<void>;
|
|
27
|
+
await KeystoreCrypto.getPublicKeyPem(alias: string): Promise<string>; // PEM SubjectPublicKeyInfo
|
|
28
|
+
await KeystoreCrypto.decryptRsaOaep(alias: string, ciphertextB64: string, requireBiometric?: boolean): Promise<string>; // plaintext base64
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Typical Flow
|
|
32
|
+
|
|
33
|
+
1. `generateKeyPair(alias)` once per device/user.
|
|
34
|
+
2. `getPublicKeyPem(alias)` → send to server.
|
|
35
|
+
3. Server encrypts secret using **RSA-OAEP-SHA256** with the given public key → returns `ciphertext_b64`.
|
|
36
|
+
4. App calls `decryptRsaOaep(alias, ciphertext_b64, true)` → gets `secret_b64` → store with `react-native-keychain` (bind to biometrics).
|
|
37
|
+
|
|
38
|
+
## Usage Example
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import KeystoreCrypto from 'react-native-keystore-crypto';
|
|
42
|
+
import * as Keychain from 'react-native-keychain';
|
|
43
|
+
import { Buffer } from 'buffer';
|
|
44
|
+
|
|
45
|
+
const KEY_ALIAS = 'otp_keypair_user123_deviceABC';
|
|
46
|
+
|
|
47
|
+
export async function provisionAndStoreSecret(ciphertextB64FromServer: string) {
|
|
48
|
+
try { await KeystoreCrypto.generateKeyPair(KEY_ALIAS); } catch {}
|
|
49
|
+
|
|
50
|
+
const secretB64 = await KeystoreCrypto.decryptRsaOaep(KEY_ALIAS, ciphertextB64FromServer, true);
|
|
51
|
+
const secretUtf8 = Buffer.from(secretB64, 'base64').toString('utf8');
|
|
52
|
+
|
|
53
|
+
await Keychain.setGenericPassword('k', secretB64, {
|
|
54
|
+
service: 'otp_secret',
|
|
55
|
+
accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED,
|
|
56
|
+
accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_CURRENT_SET,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
return { secretUtf8, secretB64 };
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Notes
|
|
64
|
+
|
|
65
|
+
- Android uses `RSA/ECB/OAEPWithSHA-256AndMGF1Padding` (OAEP SHA-256).
|
|
66
|
+
- iOS uses `SecKeyCreateDecryptedData(..., .rsaEncryptionOAEPSHA256, ...)`.
|
|
67
|
+
- RSA plaintext size is limited (≈190 bytes for 2048-bit + OAEP SHA-256). Use envelope encryption for larger payloads.
|
|
68
|
+
- iOS RSA decryption happens in **Keychain** (not Secure Enclave). To use Enclave, use EC keys (signing) rather than RSA decrypt.
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
repositories {
|
|
3
|
+
google()
|
|
4
|
+
mavenCentral()
|
|
5
|
+
}
|
|
6
|
+
dependencies {
|
|
7
|
+
classpath("com.android.tools.build:gradle:8.1.0")
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
apply plugin: 'com.android.library'
|
|
12
|
+
|
|
13
|
+
android {
|
|
14
|
+
compileSdkVersion 34
|
|
15
|
+
defaultConfig {
|
|
16
|
+
minSdkVersion 23
|
|
17
|
+
targetSdkVersion 34
|
|
18
|
+
consumerProguardFiles 'consumer-rules.pro'
|
|
19
|
+
}
|
|
20
|
+
sourceSets {
|
|
21
|
+
main {
|
|
22
|
+
java.srcDirs = ['src/main/java']
|
|
23
|
+
manifest.srcFile 'src/main/AndroidManifest.xml'
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
namespace "com.keystorecrypto"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
repositories {
|
|
30
|
+
google()
|
|
31
|
+
mavenCentral()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
dependencies {
|
|
35
|
+
implementation 'androidx.biometric:biometric:1.2.0-alpha05'
|
|
36
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
package com.keystorecrypto
|
|
2
|
+
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.os.Build
|
|
5
|
+
import android.security.keystore.KeyGenParameterSpec
|
|
6
|
+
import android.security.keystore.KeyProperties
|
|
7
|
+
import android.util.Base64
|
|
8
|
+
import androidx.biometric.BiometricPrompt
|
|
9
|
+
import androidx.core.content.ContextCompat
|
|
10
|
+
import com.facebook.react.bridge.*
|
|
11
|
+
import java.security.KeyPairGenerator
|
|
12
|
+
import java.security.KeyStore
|
|
13
|
+
import javax.crypto.Cipher
|
|
14
|
+
import javax.crypto.spec.OAEPParameterSpec
|
|
15
|
+
import javax.crypto.spec.PSource
|
|
16
|
+
|
|
17
|
+
class KeystoreCryptoModule(private val reactContext: ReactApplicationContext) :
|
|
18
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
19
|
+
|
|
20
|
+
override fun getName() = "KeystoreCrypto"
|
|
21
|
+
|
|
22
|
+
@ReactMethod
|
|
23
|
+
fun generateKeyPair(alias: String, promise: Promise) {
|
|
24
|
+
try {
|
|
25
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
26
|
+
promise.reject("UNSUPPORTED", "Android < 6.0 không hỗ trợ AndroidKeyStore")
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
val kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore")
|
|
30
|
+
val builder = KeyGenParameterSpec.Builder(
|
|
31
|
+
alias,
|
|
32
|
+
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
|
|
33
|
+
)
|
|
34
|
+
.setKeySize(2048)
|
|
35
|
+
.setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
|
|
36
|
+
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_OAEP)
|
|
37
|
+
.setUserAuthenticationRequired(true)
|
|
38
|
+
|
|
39
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
40
|
+
builder.setUserAuthenticationParameters(0, KeyProperties.AUTH_BIOMETRIC_STRONG)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
kpg.initialize(builder.build())
|
|
44
|
+
kpg.generateKeyPair()
|
|
45
|
+
promise.resolve(null)
|
|
46
|
+
} catch (e: Exception) {
|
|
47
|
+
promise.reject("GEN_KEY_ERR", e)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private fun getPrivateKey(alias: String): java.security.PrivateKey {
|
|
52
|
+
val ks = KeyStore.getInstance("AndroidKeyStore")
|
|
53
|
+
ks.load(null)
|
|
54
|
+
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
|
|
55
|
+
?: throw IllegalStateException("No private key for alias=$alias")
|
|
56
|
+
return entry.privateKey
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@ReactMethod
|
|
60
|
+
fun getPublicKeyPem(alias: String, promise: Promise) {
|
|
61
|
+
try {
|
|
62
|
+
val ks = KeyStore.getInstance("AndroidKeyStore")
|
|
63
|
+
ks.load(null)
|
|
64
|
+
val cert = ks.getCertificate(alias) ?: throw IllegalStateException("No cert for alias=$alias")
|
|
65
|
+
val spki = cert.publicKey.encoded
|
|
66
|
+
val b64 = Base64.encodeToString(spki, Base64.NO_WRAP)
|
|
67
|
+
val pem = "-----BEGIN PUBLIC KEY-----\n$b64\n-----END PUBLIC KEY-----"
|
|
68
|
+
promise.resolve(pem)
|
|
69
|
+
} catch (e: Exception) {
|
|
70
|
+
promise.reject("PUBKEY_ERR", e)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
private fun newOaepCipherForDecrypt(privateKey: java.security.PrivateKey): Cipher {
|
|
75
|
+
val cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding")
|
|
76
|
+
val oaep = OAEPParameterSpec(
|
|
77
|
+
"SHA-256",
|
|
78
|
+
"MGF1",
|
|
79
|
+
java.security.spec.MGF1ParameterSpec.SHA1, // Android OAEP default MGF1 SHA-1
|
|
80
|
+
PSource.PSpecified.DEFAULT
|
|
81
|
+
)
|
|
82
|
+
cipher.init(Cipher.DECRYPT_MODE, privateKey, oaep)
|
|
83
|
+
return cipher
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@ReactMethod
|
|
87
|
+
fun decryptRsaOaep(alias: String, ciphertextB64: String, requireBiometric: Boolean, promise: Promise) {
|
|
88
|
+
try {
|
|
89
|
+
val privateKey = getPrivateKey(alias)
|
|
90
|
+
val ct = Base64.decode(ciphertextB64, Base64.DEFAULT)
|
|
91
|
+
|
|
92
|
+
val activity: Activity? = currentActivity
|
|
93
|
+
if (requireBiometric) {
|
|
94
|
+
if (activity == null) {
|
|
95
|
+
promise.reject("NO_ACTIVITY", "No current activity for biometric prompt")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
activity.runOnUiThread {
|
|
99
|
+
try {
|
|
100
|
+
val cipher = newOaepCipherForDecrypt(privateKey)
|
|
101
|
+
val executor = ContextCompat.getMainExecutor(activity)
|
|
102
|
+
val callback = object : BiometricPrompt.AuthenticationCallback() {
|
|
103
|
+
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
|
|
104
|
+
try {
|
|
105
|
+
val c = result.cryptoObject?.cipher ?: cipher
|
|
106
|
+
val pt = c.doFinal(ct)
|
|
107
|
+
val outB64 = Base64.encodeToString(pt, Base64.NO_WRAP)
|
|
108
|
+
promise.resolve(outB64)
|
|
109
|
+
} catch (e: Exception) {
|
|
110
|
+
promise.reject("DECRYPT_ERR", e)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
override fun onAuthenticationError(code: Int, errString: CharSequence) {
|
|
114
|
+
promise.reject("AUTH_ERR", Exception("$code: $errString"))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
val prompt = BiometricPrompt(activity, executor, callback)
|
|
118
|
+
val info = BiometricPrompt.PromptInfo.Builder()
|
|
119
|
+
.setTitle("Xác thực để giải mã")
|
|
120
|
+
.setSubtitle("Dùng vân tay/face để giải mã secret")
|
|
121
|
+
.setNegativeButtonText("Huỷ")
|
|
122
|
+
.build()
|
|
123
|
+
prompt.authenticate(BiometricPrompt.CryptoObject(cipher))
|
|
124
|
+
} catch (e: Exception) {
|
|
125
|
+
promise.reject("PROMPT_ERR", e)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
val cipher = newOaepCipherForDecrypt(privateKey)
|
|
130
|
+
val pt = cipher.doFinal(ct)
|
|
131
|
+
val outB64 = Base64.encodeToString(pt, Base64.NO_WRAP)
|
|
132
|
+
promise.resolve(outB64)
|
|
133
|
+
}
|
|
134
|
+
} catch (e: Exception) {
|
|
135
|
+
promise.reject("DECRYPT_ERR", e)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.keystorecrypto
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class KeystoreCryptoPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(KeystoreCryptoModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
13
|
+
return emptyList()
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_MODULE(KeystoreCrypto, NSObject)
|
|
4
|
+
RCT_EXTERN_METHOD(generateKeyPair:(NSString *)alias
|
|
5
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
6
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
7
|
+
RCT_EXTERN_METHOD(getPublicKeyPem:(NSString *)alias
|
|
8
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
9
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
10
|
+
RCT_EXTERN_METHOD(decryptRsaOaep:(NSString *)alias
|
|
11
|
+
ciphertextB64:(NSString *)ciphertextB64
|
|
12
|
+
requireBiometric:(BOOL)requireBiometric
|
|
13
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
14
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
15
|
+
@end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import Foundation
|
|
2
|
+
import LocalAuthentication
|
|
3
|
+
import Security
|
|
4
|
+
|
|
5
|
+
@objc(KeystoreCrypto)
|
|
6
|
+
class KeystoreCrypto: NSObject {
|
|
7
|
+
|
|
8
|
+
@objc
|
|
9
|
+
func generateKeyPair(_ alias: String,
|
|
10
|
+
resolver resolve: RCTPromiseResolveBlock,
|
|
11
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
12
|
+
let tag = alias.data(using: .utf8)!
|
|
13
|
+
|
|
14
|
+
let access = SecAccessControlCreateWithFlags(
|
|
15
|
+
nil,
|
|
16
|
+
kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
|
|
17
|
+
.evaluatorAuthenticated, // requires auth (biometry/passcode)
|
|
18
|
+
nil
|
|
19
|
+
)!
|
|
20
|
+
|
|
21
|
+
let attributes: [String: Any] = [
|
|
22
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
23
|
+
kSecAttrKeySizeInBits as String: 2048,
|
|
24
|
+
kSecAttrIsPermanent as String: true,
|
|
25
|
+
kSecAttrApplicationTag as String: tag,
|
|
26
|
+
kSecAttrAccessControl as String: access
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
var error: Unmanaged<CFError>?
|
|
30
|
+
if let privateKey = SecKeyCreateRandomKey(attributes as CFDictionary, &error) {
|
|
31
|
+
_ = SecKeyCopyPublicKey(privateKey)
|
|
32
|
+
resolve(nil)
|
|
33
|
+
} else {
|
|
34
|
+
reject("GEN_KEY_ERR", "Failed to create RSA key", error?.takeRetainedValue())
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
@objc
|
|
39
|
+
func getPublicKeyPem(_ alias: String,
|
|
40
|
+
resolver resolve: RCTPromiseResolveBlock,
|
|
41
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
42
|
+
let tag = alias.data(using: .utf8)!
|
|
43
|
+
let q: [String: Any] = [
|
|
44
|
+
kSecClass as String: kSecClassKey,
|
|
45
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
46
|
+
kSecAttrApplicationTag as String: tag,
|
|
47
|
+
kSecReturnRef as String: true
|
|
48
|
+
]
|
|
49
|
+
var item: CFTypeRef?
|
|
50
|
+
let st = SecItemCopyMatching(q as CFDictionary, &item)
|
|
51
|
+
guard st == errSecSuccess, let priv = item as! SecKey? else {
|
|
52
|
+
reject("NO_KEY", "Key not found", nil); return
|
|
53
|
+
}
|
|
54
|
+
guard let pub = SecKeyCopyPublicKey(priv) else {
|
|
55
|
+
reject("NO_PUB", "No public key", nil); return
|
|
56
|
+
}
|
|
57
|
+
var err: Unmanaged<CFError>?
|
|
58
|
+
guard let spki = SecKeyCopyExternalRepresentation(pub, &err) as Data? else {
|
|
59
|
+
reject("EXPORT_ERR", "Export pub failed", err?.takeRetainedValue()); return
|
|
60
|
+
}
|
|
61
|
+
let b64 = spki.base64EncodedString(options: [.lineLength64Characters])
|
|
62
|
+
let pem = "-----BEGIN PUBLIC KEY-----\n\(b64)\n-----END PUBLIC KEY-----"
|
|
63
|
+
resolve(pem)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@objc
|
|
67
|
+
func decryptRsaOaep(_ alias: String,
|
|
68
|
+
ciphertextB64: String,
|
|
69
|
+
requireBiometric: Bool,
|
|
70
|
+
resolver resolve: RCTPromiseResolveBlock,
|
|
71
|
+
rejecter reject: RCTPromiseRejectBlock) {
|
|
72
|
+
|
|
73
|
+
let tag = alias.data(using: .utf8)!
|
|
74
|
+
let q: [String: Any] = [
|
|
75
|
+
kSecClass as String: kSecClassKey,
|
|
76
|
+
kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
|
|
77
|
+
kSecAttrApplicationTag as String: tag,
|
|
78
|
+
kSecReturnRef as String: true
|
|
79
|
+
]
|
|
80
|
+
var item: CFTypeRef?
|
|
81
|
+
let st = SecItemCopyMatching(q as CFDictionary, &item)
|
|
82
|
+
guard st == errSecSuccess, let privateKey = item as! SecKey? else {
|
|
83
|
+
reject("NO_KEY", "Key not found", nil); return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
guard let ct = Data(base64Encoded: ciphertextB64) else {
|
|
87
|
+
reject("BAD_INPUT", "ciphertextB64 invalid", nil); return
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
var context: LAContext? = nil
|
|
91
|
+
if requireBiometric {
|
|
92
|
+
let c = LAContext()
|
|
93
|
+
c.localizedReason = "Authenticate to decrypt secret"
|
|
94
|
+
context = c
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let alg = SecKeyAlgorithm.rsaEncryptionOAEPSHA256
|
|
98
|
+
guard SecKeyIsAlgorithmSupported(privateKey, .decrypt, alg) else {
|
|
99
|
+
reject("ALG_UNSUPPORTED", "Algorithm not supported", nil); return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var err: Unmanaged<CFError>?
|
|
103
|
+
if let clear = SecKeyCreateDecryptedData(privateKey, alg, ct as CFData, &err) as Data? {
|
|
104
|
+
resolve(clear.base64EncodedString())
|
|
105
|
+
} else {
|
|
106
|
+
reject("DECRYPT_ERR", "Decrypt failed", err?.takeRetainedValue())
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare module 'react-native-keystore-crypto' {
|
|
2
|
+
export function generateKeyPair(alias: string): Promise<void>;
|
|
3
|
+
export function getPublicKeyPem(alias: string): Promise<string>;
|
|
4
|
+
export function decryptRsaOaep(alias: string, ciphertextB64: string, requireBiometric?: boolean): Promise<string>;
|
|
5
|
+
const _default: {
|
|
6
|
+
generateKeyPair: typeof generateKeyPair;
|
|
7
|
+
getPublicKeyPem: typeof getPublicKeyPem;
|
|
8
|
+
decryptRsaOaep: typeof decryptRsaOaep;
|
|
9
|
+
};
|
|
10
|
+
export default _default;
|
|
11
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "react-native-keystore-crypto-joe",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "RSA OAEP decrypt via Android Keystore / iOS Keychain + biometric gating for React Native",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"module": "src/index.ts",
|
|
7
|
+
"types": "lib/typescript/src/index.d.ts",
|
|
8
|
+
"react-native": "src/index",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"react-native",
|
|
11
|
+
"keystore",
|
|
12
|
+
"keychain",
|
|
13
|
+
"rsa",
|
|
14
|
+
"oaep",
|
|
15
|
+
"biometric",
|
|
16
|
+
"secure",
|
|
17
|
+
"crypto"
|
|
18
|
+
],
|
|
19
|
+
"author": "Your Name",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"homepage": "https://github.com/yourname/react-native-keystore-crypto",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/yourname/react-native-keystore-crypto.git"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/yourname/react-native-keystore-crypto/issues"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "echo No build step required for Git install",
|
|
31
|
+
"prepare": "echo Skipping prepare on Git install"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"react": "*",
|
|
35
|
+
"react-native": "*"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {}
|
|
38
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Pod::Spec.new do |s|
|
|
2
|
+
s.name = "react-native-keystore-crypto"
|
|
3
|
+
s.version = "0.1.0"
|
|
4
|
+
s.summary = "RSA OAEP decrypt via Keychain/Keystore for React Native"
|
|
5
|
+
s.license = { :type => "MIT" }
|
|
6
|
+
s.author = { "Your Name" => "you@example.com" }
|
|
7
|
+
s.homepage = "https://github.com/yourname/react-native-keystore-crypto"
|
|
8
|
+
s.source = { :git => "https://github.com/yourname/react-native-keystore-crypto.git", :tag => "#{s.version}" }
|
|
9
|
+
s.platforms = { :ios => "12.0" }
|
|
10
|
+
s.source_files = "ios/*.{h,m,mm,swift}"
|
|
11
|
+
s.requires_arc = true
|
|
12
|
+
s.dependency 'React-Core'
|
|
13
|
+
end
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NativeModules } from 'react-native';
|
|
2
|
+
|
|
3
|
+
type Native = {
|
|
4
|
+
generateKeyPair(alias: string): Promise<void>;
|
|
5
|
+
getPublicKeyPem(alias: string): Promise<string>;
|
|
6
|
+
decryptRsaOaep(alias: string, ciphertextB64: string, requireBiometric?: boolean): Promise<string>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const { KeystoreCrypto } = NativeModules;
|
|
10
|
+
if (!KeystoreCrypto) {
|
|
11
|
+
throw new Error('KeystoreCrypto native module not linked');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default KeystoreCrypto as Native;
|