expo-device-crypto 0.1.0 → 0.1.1
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 +76 -35
- package/android/build.gradle +2 -2
- package/build/DeviceCryptoModule.d.ts +2 -3
- package/build/DeviceCryptoModule.d.ts.map +1 -1
- package/build/DeviceCryptoModule.js +2 -2
- package/build/DeviceCryptoModule.js.map +1 -1
- package/ios/DeviceCrypto.podspec +1 -1
- package/ios/DeviceCrypto.swift +7 -4
- package/package.json +1 -1
- package/src/DeviceCryptoModule.ts +3 -5
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> ⚠️ This module is currently in beta and is not suitable for production use.
|
|
4
4
|
|
|
5
|
-
Hardware-backed cryptography for Expo apps using Android Keystore and Apple Secure Enclave/Keychain where available.
|
|
5
|
+
🔒 Hardware-backed cryptography for Expo apps using [Android Keystore](https://developer.android.com/privacy-and-security/keystore) and Apple [Secure Enclave](https://developer.apple.com/documentation/security/protecting-keys-with-the-secure-enclave)/Keychain where available.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
@@ -26,42 +26,29 @@ If you want to allow Face ID on iOS, add this to your app config:
|
|
|
26
26
|
|
|
27
27
|
## Supported Algorithms
|
|
28
28
|
|
|
29
|
-
###
|
|
29
|
+
### Signature Algorithms
|
|
30
30
|
|
|
31
|
-
-
|
|
32
|
-
- **Curve:** P-256 / secp256r1.
|
|
33
|
-
- **Hash:** SHA-256.
|
|
34
|
-
- **Use it for:** Authentication challenges, message signing, and tamper detection.
|
|
31
|
+
- #### `ECDSA_SECP256R1_SHA256`
|
|
35
32
|
|
|
36
|
-
|
|
33
|
+
**Curve:** P-256 / secp256r1. </br>
|
|
34
|
+
**Hash:** SHA-256. </br>
|
|
35
|
+
**Use it for:** Authentication challenges, message signing, and tamper detection.
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
- **Key size:** 2048 bits.
|
|
40
|
-
- **Padding:** PKCS#1 v1.5.
|
|
41
|
-
- **Use it for:** Encrypting small secrets that only the private key holder should decrypt.
|
|
37
|
+
### Encryption Algorithms
|
|
42
38
|
|
|
43
|
-
|
|
39
|
+
- #### `RSA_2048_PKCS1`
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
**Key size:** 2048 bits. </br>
|
|
42
|
+
**Padding:** PKCS#1 v1.5. </br>
|
|
43
|
+
**Use it for:** Encrypting small secrets that only the private key holder should decrypt.
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
- Use `AuthMethod.PASSCODE` or `AuthMethod.PASSCODE_OR_BIOMETRIC` to specify whether authentication should require only a passcode or allow biometrics when available.
|
|
49
|
-
|
|
50
|
-
Before using authentication-protected keys, verify that device authentication is available. If the user has not enabled at least a passcode, this function returns `AuthCheckResult.UNAVAILABLE`.
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import DeviceCrypto, { AuthCheckResult } from "expo-device-crypto";
|
|
54
|
-
|
|
55
|
-
const authStatus = DeviceCrypto.isAuthCheckAvailable();
|
|
56
|
-
if (authStatus !== AuthCheckResult.AVAILABLE) {
|
|
57
|
-
throw new Error(`Authentication unavailable: ${authStatus}`);
|
|
58
|
-
}
|
|
59
|
-
```
|
|
45
|
+
More coming soon
|
|
60
46
|
|
|
61
47
|
## Example: ECDSA with Biometric
|
|
62
48
|
|
|
63
49
|
```ts
|
|
64
50
|
import DeviceCrypto, {
|
|
51
|
+
AuthCheckResult,
|
|
65
52
|
AuthMethod,
|
|
66
53
|
SigningAlgorithm,
|
|
67
54
|
} from "expo-device-crypto";
|
|
@@ -71,24 +58,31 @@ const payload = "Sign this challenge payload";
|
|
|
71
58
|
const algorithmType = SigningAlgorithm.ECDSA_SECP256R1_SHA256;
|
|
72
59
|
const authMethod = AuthMethod.PASSCODE_OR_BIOMETRIC;
|
|
73
60
|
|
|
74
|
-
// 1)
|
|
61
|
+
// 1) Ensure device authentication is configured (passcode at minimum)
|
|
62
|
+
const authStatus = DeviceCrypto.isAuthCheckAvailable();
|
|
63
|
+
if (authStatus !== AuthCheckResult.AVAILABLE) {
|
|
64
|
+
throw new Error(`Authentication unavailable: ${authStatus}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 2) Generate an auth-protected ECDSA key pair
|
|
75
68
|
await DeviceCrypto.generateKeyPair(alias, {
|
|
76
69
|
requireAuthentication: true,
|
|
77
70
|
authMethod, // iOS: defined at key generation;
|
|
78
71
|
algorithmType,
|
|
79
72
|
});
|
|
80
73
|
|
|
81
|
-
//
|
|
74
|
+
// 3) Optional: share public key with backend
|
|
82
75
|
const publicKeyPem = DeviceCrypto.getPublicKey(alias, { format: "PEM" });
|
|
83
76
|
|
|
84
|
-
//
|
|
77
|
+
// 4) Sign with private key (prompts biometric/passcode on protected keys)
|
|
85
78
|
// Note: The data to sign must be in UTF-8 format.
|
|
79
|
+
// Note: This function should display the system user authentication prompt.
|
|
86
80
|
const signature = await DeviceCrypto.sign(alias, payload, {
|
|
87
81
|
algorithmType,
|
|
88
82
|
authMethod, // Android: defined when signing
|
|
89
83
|
});
|
|
90
84
|
|
|
91
|
-
//
|
|
85
|
+
// 5) Verify locally (usually done server-side with stored public key)
|
|
92
86
|
const isValid = await DeviceCrypto.verify(alias, payload, signature ?? "", {
|
|
93
87
|
algorithmType,
|
|
94
88
|
});
|
|
@@ -98,6 +92,7 @@ const isValid = await DeviceCrypto.verify(alias, payload, signature ?? "", {
|
|
|
98
92
|
|
|
99
93
|
```ts
|
|
100
94
|
import DeviceCrypto, {
|
|
95
|
+
AuthCheckResult,
|
|
101
96
|
AuthMethod,
|
|
102
97
|
EncryptionAlgorithm,
|
|
103
98
|
} from "expo-device-crypto";
|
|
@@ -107,26 +102,72 @@ const secret = "Sensitive token";
|
|
|
107
102
|
const algorithmType = EncryptionAlgorithm.RSA_2048_PKCS1;
|
|
108
103
|
const authMethod = AuthMethod.PASSCODE_OR_BIOMETRIC;
|
|
109
104
|
|
|
110
|
-
// 1)
|
|
105
|
+
// 1) Ensure device authentication is configured (passcode at minimum)
|
|
106
|
+
const authStatus = DeviceCrypto.isAuthCheckAvailable();
|
|
107
|
+
if (authStatus !== AuthCheckResult.AVAILABLE) {
|
|
108
|
+
throw new Error(`Authentication unavailable: ${authStatus}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 2) Generate an auth-protected RSA key pair
|
|
111
112
|
await DeviceCrypto.generateKeyPair(alias, {
|
|
112
113
|
algorithmType,
|
|
113
114
|
requireAuthentication: true,
|
|
114
115
|
authMethod, // iOS: defined at key generation;
|
|
115
116
|
});
|
|
116
117
|
|
|
117
|
-
//
|
|
118
|
+
// 3) Optional: retrieve/share public key with backend
|
|
118
119
|
const publicKeyPem = DeviceCrypto.getPublicKey(alias, { format: "PEM" });
|
|
119
120
|
|
|
120
|
-
//
|
|
121
|
+
// 4) Encrypt with public key
|
|
121
122
|
const encrypted = await DeviceCrypto.encrypt(alias, secret, {
|
|
122
123
|
algorithmType,
|
|
123
124
|
});
|
|
124
125
|
|
|
125
|
-
//
|
|
126
|
-
// Note: Data to
|
|
126
|
+
// 5) Decrypt with private key (prompts biometric/passcode on protected keys)
|
|
127
|
+
// Note: Data to decrypt must be in Base64 format.
|
|
128
|
+
// Note: This function should display the system user authentication prompt.
|
|
127
129
|
const decrypted = await DeviceCrypto.decrypt(alias, encrypted ?? "", {
|
|
128
130
|
algorithmType,
|
|
129
131
|
authMethod, // Android: defined when decrypting
|
|
130
132
|
});
|
|
131
133
|
```
|
|
132
134
|
|
|
135
|
+
> ⚠️ Because iOS Keychain/Secure Enclave binds authentication policy to key creation, `authMethod` must be set in `generateKeyPair`. On Android Keystore, authentication is applied at key usage time, so `authMethod` is provided in operations like `sign` and `decrypt`.
|
|
136
|
+
|
|
137
|
+
### Methods
|
|
138
|
+
|
|
139
|
+
- `isAuthCheckAvailable(): AuthCheckResult`
|
|
140
|
+
- Returns device authentication availability (`AVAILABLE`, `NO_HARDWARE`, `UNAVAILABLE`).
|
|
141
|
+
|
|
142
|
+
- `generateKeyPair(alias: string, options?: GenerateKeyPairOptions): Promise<GenerateKeyPairResult>`
|
|
143
|
+
- Creates a key pair for `alias` if it does not exist.
|
|
144
|
+
- Defaults: `requireAuthentication = false`, `authMethod = PASSCODE_OR_BIOMETRIC`.
|
|
145
|
+
|
|
146
|
+
- `getPublicKey(alias: string, options?: GetPublicKeyOptions): string | null`
|
|
147
|
+
- Returns public key in PEM format (or `null` if alias not found).
|
|
148
|
+
- Defaults: `format = "PEM"`.
|
|
149
|
+
|
|
150
|
+
- `removeKeyPair(alias: string): boolean`
|
|
151
|
+
- Removes the key pair for the alias.
|
|
152
|
+
|
|
153
|
+
- `aliases(): string[]`
|
|
154
|
+
- Lists stored key aliases.
|
|
155
|
+
|
|
156
|
+
- `sign(alias: string, data: string, options?: SignOptions): Promise<string | null>`
|
|
157
|
+
- Signs UTF-8 `data` with private key.
|
|
158
|
+
- Defaults: `algorithmType = ECDSA_SECP256R1_SHA256`, `promptTitle = "Unlock"`, `promptSubtitle = "Enter your PIN to continue"`, `authMethod = PASSCODE_OR_BIOMETRIC`.
|
|
159
|
+
|
|
160
|
+
- `verify(alias: string, data: string, signature: string, options?: VerifyOptions): Promise<boolean | null>`
|
|
161
|
+
- Verifies signature for UTF-8 `data`.
|
|
162
|
+
- Signature must be in Base64 format.
|
|
163
|
+
- Default: `algorithmType = ECDSA_SECP256R1_SHA256`.
|
|
164
|
+
|
|
165
|
+
- `encrypt(alias: string, data: string, options?: EncryptOptions): Promise<string | null>`
|
|
166
|
+
- Encrypts UTF-8 `data` with public key.
|
|
167
|
+
- Default: `algorithmType = RSA_2048_PKCS1`.
|
|
168
|
+
|
|
169
|
+
- `decrypt(alias: string, data: string, options?: DecryptOptions): Promise<string | null>`
|
|
170
|
+
- Decrypts Base64 `data` with private key.
|
|
171
|
+
- Defaults: `algorithmType = RSA_2048_PKCS1`, `promptTitle = "Unlock"`, `promptSubtitle = "Enter your PIN to continue"`, `authMethod = PASSCODE_OR_BIOMETRIC`.
|
|
172
|
+
|
|
173
|
+
> ✅ JSDoc type definitions are available in `./src/DeviceCryptoModule.ts`.
|
package/android/build.gradle
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
apply plugin: 'com.android.library'
|
|
2
2
|
|
|
3
3
|
group = 'expo.modules.devicecrypto'
|
|
4
|
-
version = '0.1.
|
|
4
|
+
version = '0.1.1'
|
|
5
5
|
|
|
6
6
|
def expoModulesCorePlugin = new File(project(":expo-modules-core").projectDir.absolutePath, "ExpoModulesCorePlugin.gradle")
|
|
7
7
|
apply from: expoModulesCorePlugin
|
|
@@ -35,7 +35,7 @@ android {
|
|
|
35
35
|
namespace "expo.modules.devicecrypto"
|
|
36
36
|
defaultConfig {
|
|
37
37
|
versionCode 1
|
|
38
|
-
versionName "0.1.
|
|
38
|
+
versionName "0.1.1"
|
|
39
39
|
}
|
|
40
40
|
lintOptions {
|
|
41
41
|
abortOnError false
|
|
@@ -46,8 +46,8 @@ declare const _default: {
|
|
|
46
46
|
/**
|
|
47
47
|
* Verifies the given data with the signature for the given alias.
|
|
48
48
|
* @param alias - The alias to use for the key pair.
|
|
49
|
-
* @param data - The data to verify.
|
|
50
|
-
* @param signature - The signature to verify.
|
|
49
|
+
* @param data - The data to verify in UTF-8 format.
|
|
50
|
+
* @param signature - The signature to verify in Base64 format.
|
|
51
51
|
* @param options - The options for the operation.
|
|
52
52
|
* @default {
|
|
53
53
|
* algoType: SigningAlgorithm.ECDSA_SHA256,
|
|
@@ -82,5 +82,4 @@ declare const _default: {
|
|
|
82
82
|
decrypt: (alias: string, data: string, options?: DecryptOptions) => Promise<string | null>;
|
|
83
83
|
};
|
|
84
84
|
export default _default;
|
|
85
|
-
export type { GenerateKeyPairResult };
|
|
86
85
|
//# sourceMappingURL=DeviceCryptoModule.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCryptoModule.d.ts","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EAEnB,WAAW,EACX,cAAc,EACd,aAAa,EAEb,cAAc,EAEf,MAAM,sBAAsB,CAAC;;IA6B5B;;;OAGG;gCAtBqB,eAAe;IAwBvC;;;;OAIG;6BAC4B,MAAM,YAAY,sBAAsB;IAQvE;;;;;OAKG;0BACmB,MAAM,YAAY,mBAAmB;IAQ3D;;;;OAIG;2BApDkB,MAAM,KAAG,OAAO;IAsDrC;;;OAGG;mBAxDQ,MAAM,EAAE;IA0DnB;;;;;;;;;;;;OAYG;kBACiB,MAAM,QAAQ,MAAM,YAAY,WAAW;IAS/D;;;;;;;;;;OAUG;oBAEM,MAAM,QACP,MAAM,aACD,MAAM,YACP,aAAa;IAOzB;;;;;;;;;OASG;qBACoB,MAAM,QAAQ,MAAM,YAAY,cAAc;IAMrE;;;;;;;;;;;;OAYG;qBACoB,MAAM,QAAQ,MAAM,YAAY,cAAc;;AArHvE,wBA8HE
|
|
1
|
+
{"version":3,"file":"DeviceCryptoModule.d.ts","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,eAAe,EACf,sBAAsB,EACtB,qBAAqB,EACrB,mBAAmB,EAEnB,WAAW,EACX,cAAc,EACd,aAAa,EAEb,cAAc,EAEf,MAAM,sBAAsB,CAAC;;IA6B5B;;;OAGG;gCAtBqB,eAAe;IAwBvC;;;;OAIG;6BAC4B,MAAM,YAAY,sBAAsB;IAQvE;;;;;OAKG;0BACmB,MAAM,YAAY,mBAAmB;IAQ3D;;;;OAIG;2BApDkB,MAAM,KAAG,OAAO;IAsDrC;;;OAGG;mBAxDQ,MAAM,EAAE;IA0DnB;;;;;;;;;;;;OAYG;kBACiB,MAAM,QAAQ,MAAM,YAAY,WAAW;IAS/D;;;;;;;;;;OAUG;oBAEM,MAAM,QACP,MAAM,aACD,MAAM,YACP,aAAa;IAOzB;;;;;;;;;OASG;qBACoB,MAAM,QAAQ,MAAM,YAAY,cAAc;IAMrE;;;;;;;;;;;;OAYG;qBACoB,MAAM,QAAQ,MAAM,YAAY,cAAc;;AArHvE,wBA8HE"}
|
|
@@ -76,8 +76,8 @@ export default {
|
|
|
76
76
|
/**
|
|
77
77
|
* Verifies the given data with the signature for the given alias.
|
|
78
78
|
* @param alias - The alias to use for the key pair.
|
|
79
|
-
* @param data - The data to verify.
|
|
80
|
-
* @param signature - The signature to verify.
|
|
79
|
+
* @param data - The data to verify in UTF-8 format.
|
|
80
|
+
* @param signature - The signature to verify in Base64 format.
|
|
81
81
|
* @param options - The options for the operation.
|
|
82
82
|
* @default {
|
|
83
83
|
* algoType: SigningAlgorithm.ECDSA_SHA256,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeviceCryptoModule.js","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport {\n AuthCheckResult,\n GenerateKeyPairOptions,\n GenerateKeyPairResult,\n GetPublicKeyOptions,\n AuthMethod,\n SignOptions,\n DecryptOptions,\n VerifyOptions,\n SigningAlgorithm,\n EncryptOptions,\n EncryptionAlgorithm,\n} from \"./DeviceCrypto.types\";\n\nfunction base64ToPem(base64: string, label = \"DATA\") {\n const normalized = base64.replace(/\\s+/g, \"\");\n const wrapped = normalized.match(/.{1,64}/g)?.join(\"\\n\") ?? \"\";\n\n return `-----BEGIN ${label}-----\\n${wrapped}\\n-----END ${label}-----\\n`;\n}\n\ndeclare class DeviceCryptoModule extends NativeModule {\n isAuthCheckAvailable(): AuthCheckResult;\n generateKeyPair(alias: string, o: any): GenerateKeyPairResult;\n getPublicKey(alias: string): string | null;\n removeKeyPair(alias: string): boolean;\n aliases(): string[];\n sign(alias: string, data: string, o: any): Promise<string | null>;\n verify(\n alias: string,\n data: string,\n signature: string,\n o: any\n ): boolean | null;\n encrypt(alias: string, data: string, o: any): Promise<string | null>;\n decrypt(alias: string, data: string, o: any): Promise<string | null>;\n}\n\nconst module = requireNativeModule<DeviceCryptoModule>(\"DeviceCrypto\");\n\nexport default {\n /**\n * Checks if the biometric or passcode authentication is available.\n * @returns The result of the operation.\n */\n isAuthCheckAvailable: module.isAuthCheckAvailable,\n /**\n * Creates a new ECDSA P‑256 key pair for the given alias, if it doesn’t already exist.\n * @param alias - The alias to use for the key pair.\n * @returns The result of the operation.\n */\n generateKeyPair: async (alias: string, options?: GenerateKeyPairOptions) => {\n const o = {\n algoType: options?.algorithmType,\n reqAuth: options?.requireAuthentication ?? false,\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n };\n return module.generateKeyPair(alias, o);\n },\n /**\n * Returns the public key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param options - The options for the operation.\n * @returns The public key.\n */\n getPublicKey: (alias: string, options?: GetPublicKeyOptions) => {\n const publicKey = module.getPublicKey(alias);\n if (!publicKey) return null;\n if (options?.format === \"PEM\") {\n return base64ToPem(publicKey, \"PUBLIC KEY\");\n }\n return base64ToPem(publicKey, \"PUBLIC KEY\");\n },\n /**\n * Removes the key pair for the given alias.\n * @param alias - The alias to use for the key pair.\n * @returns True if the key pair was removed, false otherwise.\n */\n removeKeyPair: module.removeKeyPair,\n /**\n * Lists the aliases for the key pairs.\n * @returns The aliases.\n */\n aliases: module.aliases,\n /**\n * Signs the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to sign.\n * @param options - The options for the operation.\n * @default { \n * algoType: SigningAlgorithm.ECDSA_SHA256, \n * promptTitle: \"Unlock\", \n * promptSubtitle: \"Enter your PIN to continue\", \n * authMethod: AuthMethod.PASSCODE_OR_BIOMETRIC \n * }\n * @returns The signature.\n */\n sign: async (alias: string, data: string, options?: SignOptions) => {\n const o = {\n title: options?.promptTitle ?? \"Unlock\",\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n algoType: options?.algorithmType ?? SigningAlgorithm.ECDSA_SECP256R1_SHA256,\n };\n return module.sign(alias, data, o);\n },\n /**\n * Verifies the given data with the signature for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to verify.\n * @param signature - The signature to verify.\n * @param options - The options for the operation.\n * @default { \n * algoType: SigningAlgorithm.ECDSA_SHA256, \n * }\n * @returns True if the signature is valid, false otherwise.\n */\n verify: async (\n alias: string,\n data: string,\n signature: string,\n options?: VerifyOptions\n ) => {\n const o = {\n algoType: options?.algorithmType ?? SigningAlgorithm.ECDSA_SECP256R1_SHA256,\n };\n return module.verify(alias, data, signature, o);\n },\n /**\n * Signs the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to sign in UTF-8 format.\n * @param options - The options for the operation.\n * @default { \n * algoType: EncryptionAlgorithm.RSA_2048_PKCS1, \n * }\n * @returns The signature.\n */\n encrypt: async (alias: string, data: string, options?: EncryptOptions) => {\n const o = {\n algoType: options?.algorithmType ?? EncryptionAlgorithm.RSA_2048_PKCS1,\n };\n return module.encrypt(alias, data, o);\n },\n /**\n * Decrypts the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to decrypt in Base64 format.\n * @param options - The options for the operation.\n * @default { \n * algoType: EncryptionAlgorithm.RSA_2048_PKCS1, \n * promptTitle: \"Unlock\", \n * promptSubtitle: \"Enter your PIN to continue\", \n * authMethod: AuthMethod.PASSCODE_OR_BIOMETRIC \n * }\n * @returns The decrypted data.\n */\n decrypt: async (alias: string, data: string, options?: DecryptOptions) => {\n const o = {\n title: options?.promptTitle ?? \"Unlock\",\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n algoType: options?.algorithmType ?? EncryptionAlgorithm.RSA_2048_PKCS1,\n };\n return module.decrypt(alias, data, o);\n },\n}
|
|
1
|
+
{"version":3,"file":"DeviceCryptoModule.js","sourceRoot":"","sources":["../src/DeviceCryptoModule.ts"],"names":[],"mappings":"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","sourcesContent":["import { NativeModule, requireNativeModule } from \"expo\";\n\nimport {\n AuthCheckResult,\n GenerateKeyPairOptions,\n GenerateKeyPairResult,\n GetPublicKeyOptions,\n AuthMethod,\n SignOptions,\n DecryptOptions,\n VerifyOptions,\n SigningAlgorithm,\n EncryptOptions,\n EncryptionAlgorithm,\n} from \"./DeviceCrypto.types\";\n\nfunction base64ToPem(base64: string, label = \"DATA\") {\n const normalized = base64.replace(/\\s+/g, \"\");\n const wrapped = normalized.match(/.{1,64}/g)?.join(\"\\n\") ?? \"\";\n\n return `-----BEGIN ${label}-----\\n${wrapped}\\n-----END ${label}-----\\n`;\n}\n\ndeclare class DeviceCryptoModule extends NativeModule {\n isAuthCheckAvailable(): AuthCheckResult;\n generateKeyPair(alias: string, o: any): GenerateKeyPairResult;\n getPublicKey(alias: string): string | null;\n removeKeyPair(alias: string): boolean;\n aliases(): string[];\n sign(alias: string, data: string, o: any): Promise<string | null>;\n verify(\n alias: string,\n data: string,\n signature: string,\n o: any\n ): boolean | null;\n encrypt(alias: string, data: string, o: any): Promise<string | null>;\n decrypt(alias: string, data: string, o: any): Promise<string | null>;\n}\n\nconst module = requireNativeModule<DeviceCryptoModule>(\"DeviceCrypto\");\n\nexport default {\n /**\n * Checks if the biometric or passcode authentication is available.\n * @returns The result of the operation.\n */\n isAuthCheckAvailable: module.isAuthCheckAvailable,\n /**\n * Creates a new ECDSA P‑256 key pair for the given alias, if it doesn’t already exist.\n * @param alias - The alias to use for the key pair.\n * @returns The result of the operation.\n */\n generateKeyPair: async (alias: string, options?: GenerateKeyPairOptions) => {\n const o = {\n algoType: options?.algorithmType,\n reqAuth: options?.requireAuthentication ?? false,\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n };\n return module.generateKeyPair(alias, o);\n },\n /**\n * Returns the public key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param options - The options for the operation.\n * @returns The public key.\n */\n getPublicKey: (alias: string, options?: GetPublicKeyOptions) => {\n const publicKey = module.getPublicKey(alias);\n if (!publicKey) return null;\n if (options?.format === \"PEM\") {\n return base64ToPem(publicKey, \"PUBLIC KEY\");\n }\n return base64ToPem(publicKey, \"PUBLIC KEY\");\n },\n /**\n * Removes the key pair for the given alias.\n * @param alias - The alias to use for the key pair.\n * @returns True if the key pair was removed, false otherwise.\n */\n removeKeyPair: module.removeKeyPair,\n /**\n * Lists the aliases for the key pairs.\n * @returns The aliases.\n */\n aliases: module.aliases,\n /**\n * Signs the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to sign.\n * @param options - The options for the operation.\n * @default { \n * algoType: SigningAlgorithm.ECDSA_SHA256, \n * promptTitle: \"Unlock\", \n * promptSubtitle: \"Enter your PIN to continue\", \n * authMethod: AuthMethod.PASSCODE_OR_BIOMETRIC \n * }\n * @returns The signature.\n */\n sign: async (alias: string, data: string, options?: SignOptions) => {\n const o = {\n title: options?.promptTitle ?? \"Unlock\",\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n algoType: options?.algorithmType ?? SigningAlgorithm.ECDSA_SECP256R1_SHA256,\n };\n return module.sign(alias, data, o);\n },\n /**\n * Verifies the given data with the signature for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to verify in UTF-8 format.\n * @param signature - The signature to verify in Base64 format.\n * @param options - The options for the operation.\n * @default { \n * algoType: SigningAlgorithm.ECDSA_SHA256, \n * }\n * @returns True if the signature is valid, false otherwise.\n */\n verify: async (\n alias: string,\n data: string,\n signature: string,\n options?: VerifyOptions\n ) => {\n const o = {\n algoType: options?.algorithmType ?? SigningAlgorithm.ECDSA_SECP256R1_SHA256,\n };\n return module.verify(alias, data, signature, o);\n },\n /**\n * Signs the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to sign in UTF-8 format.\n * @param options - The options for the operation.\n * @default { \n * algoType: EncryptionAlgorithm.RSA_2048_PKCS1, \n * }\n * @returns The signature.\n */\n encrypt: async (alias: string, data: string, options?: EncryptOptions) => {\n const o = {\n algoType: options?.algorithmType ?? EncryptionAlgorithm.RSA_2048_PKCS1,\n };\n return module.encrypt(alias, data, o);\n },\n /**\n * Decrypts the given data with the private key for the given alias.\n * @param alias - The alias to use for the key pair.\n * @param data - The data to decrypt in Base64 format.\n * @param options - The options for the operation.\n * @default { \n * algoType: EncryptionAlgorithm.RSA_2048_PKCS1, \n * promptTitle: \"Unlock\", \n * promptSubtitle: \"Enter your PIN to continue\", \n * authMethod: AuthMethod.PASSCODE_OR_BIOMETRIC \n * }\n * @returns The decrypted data.\n */\n decrypt: async (alias: string, data: string, options?: DecryptOptions) => {\n const o = {\n title: options?.promptTitle ?? \"Unlock\",\n subtitle: options?.promptSubtitle ?? \"Enter your PIN to continue\",\n authMethod: options?.authMethod ?? AuthMethod.PASSCODE_OR_BIOMETRIC,\n algoType: options?.algorithmType ?? EncryptionAlgorithm.RSA_2048_PKCS1,\n };\n return module.decrypt(alias, data, o);\n },\n};"]}
|
package/ios/DeviceCrypto.podspec
CHANGED
package/ios/DeviceCrypto.swift
CHANGED
|
@@ -23,6 +23,7 @@ enum AuthMethod: String {
|
|
|
23
23
|
enum AlgorithmType: String {
|
|
24
24
|
case ECDSA_SECP256R1_SHA256 = "ECDSA_SECP256R1_SHA256"
|
|
25
25
|
case RSA_2048_PKCS1 = "RSA_2048_PKCS1"
|
|
26
|
+
case RSA_2048_OAEP_SHA1 = "RSA_2048_OAEP_SHA1"
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
public class DeviceCryptoModule: Module {
|
|
@@ -32,6 +33,8 @@ public class DeviceCryptoModule: Module {
|
|
|
32
33
|
return .ecdsaSignatureMessageX962SHA256
|
|
33
34
|
case .RSA_2048_PKCS1:
|
|
34
35
|
return .rsaEncryptionPKCS1
|
|
36
|
+
case .RSA_2048_OAEP_SHA1:
|
|
37
|
+
return .rsaEncryptionOAEP
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -186,7 +189,7 @@ public class DeviceCryptoModule: Module {
|
|
|
186
189
|
return privateStatus == errSecSuccess || publicStatus == errSecSuccess
|
|
187
190
|
}
|
|
188
191
|
|
|
189
|
-
private func
|
|
192
|
+
private func buildECDSA(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
190
193
|
let accessFlags: SecAccessControlCreateFlags
|
|
191
194
|
if reqAuth {
|
|
192
195
|
switch authMethod {
|
|
@@ -224,7 +227,7 @@ public class DeviceCryptoModule: Module {
|
|
|
224
227
|
return SecKeyCreateRandomKey(attributes, nil)
|
|
225
228
|
}
|
|
226
229
|
|
|
227
|
-
private func
|
|
230
|
+
private func buildRSA(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
|
|
228
231
|
let accessFlags: SecAccessControlCreateFlags
|
|
229
232
|
if reqAuth {
|
|
230
233
|
switch authMethod {
|
|
@@ -289,9 +292,9 @@ public class DeviceCryptoModule: Module {
|
|
|
289
292
|
|
|
290
293
|
switch algoType {
|
|
291
294
|
case .ECDSA_SECP256R1_SHA256:
|
|
292
|
-
self.
|
|
295
|
+
self.buildECDSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
|
|
293
296
|
case .RSA_2048_PKCS1:
|
|
294
|
-
self.
|
|
297
|
+
self.buildRSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
|
|
295
298
|
default:
|
|
296
299
|
throw NSError(
|
|
297
300
|
domain: "DeviceCrypto",
|
package/package.json
CHANGED
|
@@ -109,8 +109,8 @@ export default {
|
|
|
109
109
|
/**
|
|
110
110
|
* Verifies the given data with the signature for the given alias.
|
|
111
111
|
* @param alias - The alias to use for the key pair.
|
|
112
|
-
* @param data - The data to verify.
|
|
113
|
-
* @param signature - The signature to verify.
|
|
112
|
+
* @param data - The data to verify in UTF-8 format.
|
|
113
|
+
* @param signature - The signature to verify in Base64 format.
|
|
114
114
|
* @param options - The options for the operation.
|
|
115
115
|
* @default {
|
|
116
116
|
* algoType: SigningAlgorithm.ECDSA_SHA256,
|
|
@@ -166,6 +166,4 @@ export default {
|
|
|
166
166
|
};
|
|
167
167
|
return module.decrypt(alias, data, o);
|
|
168
168
|
},
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
export type { GenerateKeyPairResult };
|
|
169
|
+
};
|