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 +163 -11
- package/dist-js/index.cjs +4 -4
- package/dist-js/index.d.ts +22 -5
- package/dist-js/index.js +4 -4
- package/package.json +1 -1
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.
|
|
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
|
|
101
|
-
const
|
|
102
|
-
console.log("
|
|
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
|
|
131
|
+
Returns detailed information about secure element hardware capabilities on the device.
|
|
126
132
|
|
|
127
|
-
**Returns:** `Promise<
|
|
133
|
+
**Returns:** `Promise<SecureElementCapabilities>`
|
|
128
134
|
|
|
129
135
|
```typescript
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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`:
|
|
234
|
+
- `data`: Raw data to sign as `Uint8Array` (do not pre-hash)
|
|
179
235
|
|
|
180
|
-
**Returns:** `Promise<Uint8Array>` -
|
|
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
|
|
24
|
-
publicKey: publicKey
|
|
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
|
|
44
|
-
publicKey: publicKey
|
|
43
|
+
keyName: keyName ?? null,
|
|
44
|
+
publicKey: publicKey ?? null,
|
|
45
45
|
},
|
|
46
46
|
}).then((r) => r.success);
|
|
47
47
|
}
|
package/dist-js/index.d.ts
CHANGED
|
@@ -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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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<
|
|
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
|
|
22
|
-
publicKey: publicKey
|
|
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
|
|
42
|
-
publicKey: publicKey
|
|
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.
|
|
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",
|