tauri-plugin-secure-element-api 0.1.0-beta.1 → 0.1.0-beta.2

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
@@ -27,7 +27,7 @@ yarn add tauri-plugin-secure-element-api
27
27
 
28
28
  ```toml
29
29
  [dependencies]
30
- tauri-plugin-secure-element = "0.1.0-beta.1"
30
+ tauri-plugin-secure-element = "0.1.0-beta.2"
31
31
  ```
32
32
 
33
33
  ## Setup
@@ -95,11 +95,17 @@ import {
95
95
  signWithKey,
96
96
  deleteKey,
97
97
  type AuthenticationMode,
98
+ type SecureElementBacking,
99
+ type SecureElementCapabilities,
98
100
  } from "tauri-plugin-secure-element-api";
99
101
 
100
- // Check if secure element is supported
101
- const support = await checkSecureElementSupport();
102
- console.log("Secure element supported:", support.secureElementSupported);
102
+ // Check device secure element capabilities
103
+ const capabilities = await checkSecureElementSupport();
104
+ console.log("Strongest backing:", capabilities.strongest);
105
+ console.log(
106
+ "Can enforce biometric-only:",
107
+ capabilities.canEnforceBiometricOnly
108
+ );
103
109
 
104
110
  // Generate a new secure key
105
111
  const { publicKey, keyName } = await generateSecureKey(
@@ -122,18 +128,68 @@ await deleteKey("my-key-name");
122
128
 
123
129
  ### `checkSecureElementSupport()`
124
130
 
125
- Returns information about secure element support on the device.
131
+ Returns detailed information about secure element hardware capabilities on the device.
126
132
 
127
- **Returns:** `Promise<SecureElementSupport>`
133
+ **Returns:** `Promise<SecureElementCapabilities>`
128
134
 
129
135
  ```typescript
130
- interface SecureElementSupport {
131
- secureElementSupported: boolean;
132
- teeSupported: boolean;
136
+ /**
137
+ * Hardware backing tiers, ordered weakest → strongest:
138
+ * "none" < "firmware" < "integrated" < "discrete"
139
+ */
140
+ type SecureElementBacking = "none" | "firmware" | "integrated" | "discrete";
141
+
142
+ interface SecureElementCapabilities {
143
+ /** A discrete physical security chip is available (e.g. discrete TPM 2.0, macOS T2, Android StrongBox) */
144
+ discrete: boolean;
145
+ /** An on-die isolated security core is available (e.g. Apple Silicon Secure Enclave, ARM TrustZone/TEE) */
146
+ integrated: boolean;
147
+ /** Firmware-backed security is available but no dedicated secure processor (e.g. Windows fTPM via Intel PTT or AMD PSP) */
148
+ firmware: boolean;
149
+ /** The security is emulated/virtual (e.g. vTPM in a VM, iOS Simulator, Android Emulator) */
150
+ emulated: boolean;
151
+ /** The strongest hardware backing tier available on this device */
152
+ strongest: SecureElementBacking;
153
+ /** Whether biometric-only authentication can be enforced at the key level (Android API 30+ only) */
133
154
  canEnforceBiometricOnly: boolean;
134
155
  }
135
156
  ```
136
157
 
158
+ **Hardware Backing Tiers:**
159
+
160
+ | Tier | Description | Examples |
161
+ | ------------ | ---------------------------------------------- | -------------------------------------------------- |
162
+ | `none` | No secure element available (software-only) | Unsupported devices, some VMs |
163
+ | `firmware` | Firmware-backed, no dedicated secure processor | Windows fTPM (Intel PTT, AMD PSP) |
164
+ | `integrated` | On-die isolated security core | Apple Silicon Secure Enclave, ARM TrustZone/TEE |
165
+ | `discrete` | Physically separate security processor | Discrete TPM 2.0, macOS T2 chip, Android StrongBox |
166
+
167
+ **Usage Example:**
168
+
169
+ ```typescript
170
+ const caps = await checkSecureElementSupport();
171
+
172
+ // Check if any hardware backing is available
173
+ if (caps.strongest === "none") {
174
+ console.warn("No secure element available - keys will be software-only");
175
+ }
176
+
177
+ // Check for high-security backing (discrete or integrated)
178
+ if (caps.strongest === "discrete" || caps.strongest === "integrated") {
179
+ console.log("High-security hardware backing available");
180
+ }
181
+
182
+ // Warn if running in emulated environment
183
+ if (caps.emulated) {
184
+ console.warn("Running in emulator/VM - security may be reduced");
185
+ }
186
+
187
+ // Check before creating biometric-only keys
188
+ if (caps.canEnforceBiometricOnly) {
189
+ await generateSecureKey("my-key", "biometricOnly");
190
+ }
191
+ ```
192
+
137
193
  ### `generateSecureKey(keyName: string, authMode?: AuthenticationMode)`
138
194
 
139
195
  Generates a new secure key in the device's secure element.
@@ -175,9 +231,11 @@ Signs data using a key stored in the secure element.
175
231
  **Parameters:**
176
232
 
177
233
  - `keyName`: Name of the key to use
178
- - `data`: Data to sign as `Uint8Array`
234
+ - `data`: Raw data to sign as `Uint8Array` (do not pre-hash)
179
235
 
180
- **Returns:** `Promise<Uint8Array>` - The signature
236
+ **Returns:** `Promise<Uint8Array>` - DER-encoded ECDSA signature
237
+
238
+ **Important:** The plugin automatically hashes your data with SHA-256 before signing. Pass raw data, not a pre-computed hash. See [Signature Format](#signature-format) for details.
181
239
 
182
240
  ### `deleteKey(keyName?: string, publicKey?: string)`
183
241
 
@@ -197,6 +255,100 @@ Public keys are returned as base64-encoded strings in **X9.62 uncompressed point
197
255
 
198
256
  All keys use the **secp256r1 (P-256)** elliptic curve.
199
257
 
258
+ ## Signature Format
259
+
260
+ ### Hashing Convention
261
+
262
+ **The plugin automatically hashes your data with SHA-256 before signing.** This is handled internally on all platforms:
263
+
264
+ - **iOS/macOS**: Hashes data, then signs with `.ecdsaSignatureDigestX962SHA256`
265
+ - **Android**: Uses `SHA256withECDSA` which hashes internally
266
+ - **Windows**: Explicitly hashes with SHA-256 before calling `NCryptSignHash`
267
+
268
+ **Do not pre-hash your data.** Pass the raw data to `signWithKey()` and the plugin handles hashing.
269
+
270
+ ### Signature Output
271
+
272
+ Signatures are returned as **DER-encoded ECDSA signatures** (ASN.1 format), consistent across all platforms. Typical size is 70-72 bytes for P-256.
273
+
274
+ ```
275
+ SEQUENCE {
276
+ INTEGER r, -- 32-33 bytes (may have leading 0x00 for sign bit)
277
+ INTEGER s -- 32-33 bytes (may have leading 0x00 for sign bit)
278
+ }
279
+ ```
280
+
281
+ ### Verifying Signatures
282
+
283
+ To verify a signature produced by this plugin:
284
+
285
+ 1. Use the **raw original data** (not hashed)
286
+ 2. Use an **ECDSA-SHA256** verification algorithm (the verifier will hash internally)
287
+ 3. Use the **secp256r1 (P-256)** curve
288
+ 4. The signature is **DER-encoded**
289
+
290
+ **Example (Node.js):**
291
+
292
+ ```typescript
293
+ import { createVerify } from "crypto";
294
+
295
+ function verifySignature(
296
+ publicKeyBase64: string,
297
+ data: Uint8Array,
298
+ signatureDer: Uint8Array
299
+ ): boolean {
300
+ // Convert X9.62 public key to PEM format
301
+ const publicKeyBuffer = Buffer.from(publicKeyBase64, "base64");
302
+ const publicKey = {
303
+ key: publicKeyBuffer,
304
+ format: "raw",
305
+ type: "spki",
306
+ namedCurve: "P-256",
307
+ };
308
+
309
+ const verify = createVerify("SHA256");
310
+ verify.update(data); // Pass raw data - verify() hashes internally
311
+ return verify.verify(
312
+ { key: publicKey, dsaEncoding: "der" },
313
+ Buffer.from(signatureDer)
314
+ );
315
+ }
316
+ ```
317
+
318
+ **Example (Web Crypto API):**
319
+
320
+ ```typescript
321
+ async function verifySignature(
322
+ publicKeyBase64: string,
323
+ data: Uint8Array,
324
+ signatureDer: Uint8Array
325
+ ): Promise<boolean> {
326
+ const publicKeyBytes = Uint8Array.from(atob(publicKeyBase64), (c) =>
327
+ c.charCodeAt(0)
328
+ );
329
+
330
+ // Import the raw public key
331
+ const publicKey = await crypto.subtle.importKey(
332
+ "raw",
333
+ publicKeyBytes,
334
+ { name: "ECDSA", namedCurve: "P-256" },
335
+ false,
336
+ ["verify"]
337
+ );
338
+
339
+ // Note: Web Crypto expects raw R||S format, not DER
340
+ // You may need to convert DER to raw format (64 bytes)
341
+ const signatureRaw = derToRaw(signatureDer);
342
+
343
+ return crypto.subtle.verify(
344
+ { name: "ECDSA", hash: "SHA-256" },
345
+ publicKey,
346
+ signatureRaw,
347
+ data // Pass raw data - verify() hashes internally
348
+ );
349
+ }
350
+ ```
351
+
200
352
  ## Platform Support
201
353
 
202
354
  - **iOS**: Uses Secure Enclave for key generation and signing
package/dist-js/index.cjs CHANGED
@@ -20,8 +20,8 @@ async function generateSecureKey(keyName, authMode = "pinOrBiometric") {
20
20
  async function listKeys(keyName, publicKey) {
21
21
  return await core.invoke("plugin:secure-element|list_keys", {
22
22
  payload: {
23
- keyName: keyName || null,
24
- publicKey: publicKey || null,
23
+ keyName: keyName ?? null,
24
+ publicKey: publicKey ?? null,
25
25
  },
26
26
  }).then((r) => r.keys);
27
27
  }
@@ -40,8 +40,8 @@ async function signWithKey(keyName, data) {
40
40
  async function deleteKey(keyName, publicKey) {
41
41
  return await core.invoke("plugin:secure-element|delete_key", {
42
42
  payload: {
43
- keyName: keyName || null,
44
- publicKey: publicKey || null,
43
+ keyName: keyName ?? null,
44
+ publicKey: publicKey ?? null,
45
45
  },
46
46
  }).then((r) => r.success);
47
47
  }
@@ -4,7 +4,7 @@ export interface KeyInfo {
4
4
  }
5
5
  export declare function ping(value: string): Promise<string | null>;
6
6
  export type AuthenticationMode = "none" | "pinOrBiometric" | "biometricOnly";
7
- export type HardwareBacking = "secureEnclave" | "strongBox" | "tee";
7
+ export type HardwareBacking = "secureEnclave" | "strongBox" | "tee" | "ngc" | "tpm";
8
8
  export interface GenerateSecureKeyResult {
9
9
  publicKey: string;
10
10
  keyName: string;
@@ -19,9 +19,26 @@ export declare function signWithKey(keyName: string, data: Uint8Array): Promise<
19
19
  * At least one of keyName or publicKey must be provided.
20
20
  */
21
21
  export declare function deleteKey(keyName?: string, publicKey?: string): Promise<boolean>;
22
- export interface SecureElementSupport {
23
- secureElementSupported: boolean;
24
- teeSupported: boolean;
22
+ /**
23
+ * Secure element hardware backing tiers.
24
+ * Ordered weakest → strongest: none < firmware < integrated < discrete
25
+ */
26
+ export type SecureElementBacking = "none" | "firmware" | "integrated" | "discrete";
27
+ /**
28
+ * Secure element capabilities for the current device.
29
+ */
30
+ export interface SecureElementCapabilities {
31
+ /** A discrete physical security chip is available (e.g. discrete TPM, T2, StrongBox) */
32
+ discrete: boolean;
33
+ /** An on-die isolated security core is available (e.g. Secure Enclave, TrustZone/TEE) */
34
+ integrated: boolean;
35
+ /** Firmware-backed security is available but no dedicated secure processor (e.g. fTPM) */
36
+ firmware: boolean;
37
+ /** The security is emulated/virtual (e.g. vTPM in VM, iOS Simulator, Android Emulator) */
38
+ emulated: boolean;
39
+ /** The strongest tier available on this device */
40
+ strongest: SecureElementBacking;
41
+ /** Whether biometric-only authentication can be enforced at the key level */
25
42
  canEnforceBiometricOnly: boolean;
26
43
  }
27
- export declare function checkSecureElementSupport(): Promise<SecureElementSupport>;
44
+ export declare function checkSecureElementSupport(): Promise<SecureElementCapabilities>;
package/dist-js/index.js CHANGED
@@ -18,8 +18,8 @@ async function generateSecureKey(keyName, authMode = "pinOrBiometric") {
18
18
  async function listKeys(keyName, publicKey) {
19
19
  return await invoke("plugin:secure-element|list_keys", {
20
20
  payload: {
21
- keyName: keyName || null,
22
- publicKey: publicKey || null,
21
+ keyName: keyName ?? null,
22
+ publicKey: publicKey ?? null,
23
23
  },
24
24
  }).then((r) => r.keys);
25
25
  }
@@ -38,8 +38,8 @@ async function signWithKey(keyName, data) {
38
38
  async function deleteKey(keyName, publicKey) {
39
39
  return await invoke("plugin:secure-element|delete_key", {
40
40
  payload: {
41
- keyName: keyName || null,
42
- publicKey: publicKey || null,
41
+ keyName: keyName ?? null,
42
+ publicKey: publicKey ?? null,
43
43
  },
44
44
  }).then((r) => r.success);
45
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tauri-plugin-secure-element-api",
3
- "version": "0.1.0-beta.1",
3
+ "version": "0.1.0-beta.2",
4
4
  "description": "Tauri plugin for secure element use on iOS (Secure Enclave) and Android (Strongbox and TEE).",
5
5
  "repository": "https://github.com/dkackman/tauri-plugin-secure-element",
6
6
  "license": "Apache-2.0",