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 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
- ### ECDSA (`ECDSA_SECP256R1_SHA256`)
29
+ ### Signature Algorithms
30
30
 
31
- - **Type:** Digital signature algorithm (asymmetric).
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
- ### RSA (`RSA_2048_PKCS1`)
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
- - **Type:** Asymmetric encryption algorithm.
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
- ## Biometric and Passcode Protection
39
+ - #### `RSA_2048_PKCS1`
44
40
 
45
- You can bind private key usage to user authentication with:
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
- - `requireAuthentication: true` when creating a key pair. (set to `false` to allow signing and decryption without authentication prompts)
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) Generate an auth-protected ECDSA key pair
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
- // 2) Optional: share public key with backend
74
+ // 3) Optional: share public key with backend
82
75
  const publicKeyPem = DeviceCrypto.getPublicKey(alias, { format: "PEM" });
83
76
 
84
- // 3) Sign with private key (prompts biometric/passcode on protected keys)
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
- // 4) Verify locally (usually done server-side with stored public key)
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) Generate an auth-protected RSA key pair
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
- // 2) Optional: retrieve/share public key with backend
118
+ // 3) Optional: retrieve/share public key with backend
118
119
  const publicKeyPem = DeviceCrypto.getPublicKey(alias, { format: "PEM" });
119
120
 
120
- // 3) Encrypt with public key
121
+ // 4) Encrypt with public key
121
122
  const encrypted = await DeviceCrypto.encrypt(alias, secret, {
122
123
  algorithmType,
123
124
  });
124
125
 
125
- // 4) Decrypt with private key (prompts biometric/passcode on protected keys)
126
- // Note: Data to encrypt must be in Base64 format.
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`.
@@ -1,7 +1,7 @@
1
1
  apply plugin: 'com.android.library'
2
2
 
3
3
  group = 'expo.modules.devicecrypto'
4
- version = '0.1.0'
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.0"
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;AAEF,YAAY,EAAE,qBAAqB,EAAE,CAAC"}
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};\n\nexport type { GenerateKeyPairResult };\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};"]}
@@ -1,6 +1,6 @@
1
1
  Pod::Spec.new do |s|
2
2
  s.name = 'DeviceCrypto'
3
- s.version = '0.1.0'
3
+ s.version = '0.1.1'
4
4
  s.summary = 'Device crypto'
5
5
  s.description = 'Device crypto for Expo using device hardware capabilities'
6
6
  s.author = 'Piotr Pietras'
@@ -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 buildECDSA_SECP256R1_SHA256(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
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 buildRSA_2048_PKCS1(alias: String, reqAuth: Bool, authMethod: AuthMethod) -> SecKey? {
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.buildECDSA_SECP256R1_SHA256(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
295
+ self.buildECDSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
293
296
  case .RSA_2048_PKCS1:
294
- self.buildRSA_2048_PKCS1(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
297
+ self.buildRSA(alias: alias, reqAuth: reqAuth, authMethod: authMethod!)
295
298
  default:
296
299
  throw NSError(
297
300
  domain: "DeviceCrypto",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-device-crypto",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Crypto for Expo using device hardware capabilities",
5
5
  "main": "build/index.js",
6
6
  "types": "build/index.d.ts",
@@ -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
+ };