node-tpm2 0.0.4-beta.3 → 0.0.5-beta.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 +396 -88
- package/api.js +233 -0
- package/docs/api-reference.md +1029 -0
- package/docs/roadmap.md +216 -0
- package/docs/windows-pcp.md +21 -0
- package/examples/nv-smoke.mjs +77 -0
- package/index.d.ts +89 -23
- package/native.cjs +65 -52
- package/native.d.ts +76 -0
- package/package.json +12 -10
|
@@ -0,0 +1,1029 @@
|
|
|
1
|
+
# node-tpm2 API Reference
|
|
2
|
+
|
|
3
|
+
Complete reference for the public JavaScript API of **node-tpm2** — native TPM 2.0 bindings for Node.js. The library talks to the TPM through OS-native transports (TBS on Windows, `/dev/tpmrm0` on Linux) and returns Buffers and typed records, not CLI text.
|
|
4
|
+
|
|
5
|
+
**Import:**
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { Tpm, TpmError } from 'node-tpm2';
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Related docs:** [Getting started](./getting-started.md) · [Windows PCP / fleet enrollment](./windows-pcp.md) · [Roadmap](./roadmap.md)
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Table of contents
|
|
16
|
+
|
|
17
|
+
1. [Architecture](#architecture)
|
|
18
|
+
2. [Package structure](#package-structure)
|
|
19
|
+
3. [Connection lifecycle](#connection-lifecycle)
|
|
20
|
+
4. [Namespace vs flat API](#namespace-vs-flat-api)
|
|
21
|
+
5. [Types and wire formats](#types-and-wire-formats)
|
|
22
|
+
6. [Root API (`Tpm`)](#root-api-tpm)
|
|
23
|
+
7. [Handle API (`TpmHandle`)](#handle-api-tpmhandle)
|
|
24
|
+
8. [Key handles (`KeyHandle`)](#key-handles-keyhandle)
|
|
25
|
+
9. [Attestation handles (`AkHandle`)](#attestation-handles-akhandle)
|
|
26
|
+
10. [Attestation flows](#attestation-flows)
|
|
27
|
+
11. [Error model (`TpmError`)](#error-model-tpmerror)
|
|
28
|
+
12. [Platform differences](#platform-differences)
|
|
29
|
+
13. [Privilege matrix](#privilege-matrix)
|
|
30
|
+
14. [Deferred / not in public API](#deferred--not-in-public-api)
|
|
31
|
+
15. [Symbol index](#symbol-index)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Architecture
|
|
36
|
+
|
|
37
|
+
node-tpm2 is a three-layer stack: JavaScript API (`api.js`) → N-API native addon (`native.cjs`, Rust `src/napi.rs`) → TPM transport (`src/tbs/`).
|
|
38
|
+
|
|
39
|
+
```mermaid
|
|
40
|
+
flowchart TB
|
|
41
|
+
subgraph js [JavaScript]
|
|
42
|
+
Tpm["Tpm / TpmHandle"]
|
|
43
|
+
Handles["KeyHandle / AkHandle"]
|
|
44
|
+
Tpm --> Handles
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
subgraph native [Native addon]
|
|
48
|
+
NAPI["napi.rs"]
|
|
49
|
+
TBS["tbs/ command codec"]
|
|
50
|
+
PCP["pcp.rs (Windows AK only)"]
|
|
51
|
+
NAPI --> TBS
|
|
52
|
+
NAPI --> PCP
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
subgraph os [OS transport]
|
|
56
|
+
Linux["/dev/tpmrm0"]
|
|
57
|
+
WinTBS["TBS Submit_Command"]
|
|
58
|
+
WinNCrypt["NCrypt PCP"]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
Tpm --> NAPI
|
|
62
|
+
TBS --> Linux
|
|
63
|
+
TBS --> WinTBS
|
|
64
|
+
PCP --> WinNCrypt
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Design rules:**
|
|
68
|
+
|
|
69
|
+
- **General keys, PCR, random, ReadPublic, NV, seal** use the shared TBS command path on both Linux and Windows so blobs and behavior stay aligned.
|
|
70
|
+
- **Attestation key persistence on Windows** uses NCrypt Platform Crypto Provider (PCP) because raw TBS cannot persist cross-user identity keys reliably.
|
|
71
|
+
- **Transient TPM handles** are created and flushed inside each native call. You do not manage TPM handle slots in JavaScript.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Package structure
|
|
76
|
+
|
|
77
|
+
For npm consumers:
|
|
78
|
+
|
|
79
|
+
| Artifact | Role |
|
|
80
|
+
|----------|------|
|
|
81
|
+
| `node-tpm2` (this package) | Pure JS entry: `index.js`, `api.js`, `index.d.ts` |
|
|
82
|
+
| `native.cjs` | N-API addon loaded at runtime (may be missing until build/install) |
|
|
83
|
+
| `native.d.ts` | Auto-generated types for native bindings (internal; not re-exported) |
|
|
84
|
+
| `node-tpm2-<platform>-<arch>-<libc>` | Optional dependency with prebuilt `.node` binary |
|
|
85
|
+
|
|
86
|
+
Install resolves the correct platform package automatically. Node **20+** required. macOS installs succeed but `Tpm.isAvailable()` returns `false` — there is no Apple TPM backend.
|
|
87
|
+
|
|
88
|
+
You only import from `'node-tpm2'`. Do not import `native.cjs` directly.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Connection lifecycle
|
|
93
|
+
|
|
94
|
+
### Probe without opening
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
if (!(await Tpm.isAvailable())) {
|
|
98
|
+
// No TPM, no native binary, macOS, or permission denied at probe time
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`isAvailable()` never throws. It returns `false` if the native module is missing, the platform is unsupported, or the TPM is unreachable.
|
|
103
|
+
|
|
104
|
+
### Open a session handle
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
await using tpm = await Tpm.open();
|
|
108
|
+
// ... use tpm.pcr, tpm.keys, tpm.attest, etc.
|
|
109
|
+
// Symbol.asyncDispose runs at block exit (Node 20+ explicit resource management)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**What `Tpm.open()` does:**
|
|
113
|
+
|
|
114
|
+
1. Verifies the native backend loaded (`requireNative('isAvailable')`).
|
|
115
|
+
2. Calls `isAvailable()`; if false, throws `TpmError` with code `TPM_UNAVAILABLE`.
|
|
116
|
+
3. Returns a **stateless** `TpmHandle` object — a namespace grouping API, not an open file descriptor.
|
|
117
|
+
|
|
118
|
+
**Disposal:** `[Symbol.asyncDispose]()` on `TpmHandle` is currently a no-op. Each operation opens a TBS context (or PCP session), performs work, and flushes transient TPM objects internally. Future versions may reuse a per-handle TBS context; disposal would then release it.
|
|
119
|
+
|
|
120
|
+
### Info without opening
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
const info = await Tpm.getFixedProperties();
|
|
124
|
+
// or
|
|
125
|
+
const info = await Tpm.info(); // alias
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
These call `TPM2_GetCapability` for fixed TPM properties and do not require `Tpm.open()`.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Namespace vs flat API
|
|
133
|
+
|
|
134
|
+
Every implemented operation exists in two forms:
|
|
135
|
+
|
|
136
|
+
| Style | Example | When to use |
|
|
137
|
+
|-------|---------|-------------|
|
|
138
|
+
| **Namespace (preferred)** | `await using tpm = await Tpm.open(); await tpm.pcr.read([0])` | New code; groups related operations; returns rich handles for keys/AK |
|
|
139
|
+
| **Flat (legacy-compatible)** | `await Tpm.pcrRead([0])` | Scripts, one-shot calls, README examples |
|
|
140
|
+
|
|
141
|
+
Flat methods on `Tpm` call the same native functions as the namespace methods. Differences:
|
|
142
|
+
|
|
143
|
+
- `Tpm.provisionAk()` returns `{ akPublicDer, akBlob }` — not an `AkHandle`.
|
|
144
|
+
- `Tpm.createKey()` returns `{ publicKeyDer, keyBlob }` — not a `KeyHandle`.
|
|
145
|
+
- `tpm.keys.create()` / `tpm.attest.provisionAk()` return handles with methods (`sign`, `quote`, `export`, …).
|
|
146
|
+
|
|
147
|
+
See [Namespace comparison table](#namespace-comparison-table).
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Types and wire formats
|
|
152
|
+
|
|
153
|
+
### `AkBlob` / `KeyBlob`
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
type AkBlob = { public: Buffer; private: Buffer };
|
|
157
|
+
type KeyBlob = AkBlob; // same wire shape; distinct type name for clarity
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
| Field | Contents |
|
|
161
|
+
|-------|----------|
|
|
162
|
+
| `public` | **TPM2B_PUBLIC** wire bytes (size prefix + `TPMT_PUBLIC` structure) |
|
|
163
|
+
| `private` | **TPM2B_PRIVATE** wire bytes (encrypted sensitive area) |
|
|
164
|
+
|
|
165
|
+
These are **exportable wrapped key blobs**, not persistent TPM handles. Persist them in your app (encrypted at rest). To use again: `tpm.keys.load(blob)` or pass `akBlob` to flat `Tpm.quote` / `Tpm.activateCredential`.
|
|
166
|
+
|
|
167
|
+
**Important:** Windows PCP attestation blobs (`PCP1`/`PCP2` magic) are **not** valid for `tpm.keys.load` or `key.sign`. The library rejects them with `NOT_SUPPORTED`. Use `tpm.attest` / `AkHandle` for PCP blobs.
|
|
168
|
+
|
|
169
|
+
### `TpmInfo`
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
type TpmInfo = {
|
|
173
|
+
manufacturer: string; // four-cc, e.g. "IFX ", "STM "
|
|
174
|
+
firmwareVersion: string;
|
|
175
|
+
isVirtual: boolean; // heuristic (swtpm, IBM, vendor string)
|
|
176
|
+
spec: string; // TPM 2.0 family indicator, typically "2.0"
|
|
177
|
+
};
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### `QuoteResult`
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
type QuoteResult = { message: Buffer; signature: Buffer };
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
| Field | Meaning |
|
|
187
|
+
|-------|---------|
|
|
188
|
+
| `message` | Raw **TPMS_ATTEST** structure from `TPM2_Quote` — send to your verifier for parsing |
|
|
189
|
+
| `signature` | Signature over `message` using the AK (ECDSA-SHA256 on Linux; RSASSA-SHA256 on Windows PCP RSA AK) |
|
|
190
|
+
|
|
191
|
+
Also pass `akPublicDer` (from provisioning) and `pcrSelection` / `qualifyingData` to the verifier out-of-band.
|
|
192
|
+
|
|
193
|
+
### `ReadPublicResult`
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
type ReadPublicResult = { publicKeyDer: Buffer; name: Buffer };
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
| Field | Meaning |
|
|
200
|
+
|-------|---------|
|
|
201
|
+
| `publicKeyDer` | SubjectPublicKeyInfo DER (RSA or EC) extracted from the object's public area |
|
|
202
|
+
| `name` | **TPM2B_NAME** — hash of the public area; used in policies and credential activation |
|
|
203
|
+
|
|
204
|
+
### Handle strings for `readPublic`
|
|
205
|
+
|
|
206
|
+
Persistent handles use hex strings with optional `0x` prefix, e.g. `'0x81010001'` (RSA EK), `'0x81010002'` (ECC EK).
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Root API (`Tpm`)
|
|
211
|
+
|
|
212
|
+
### `Tpm.isAvailable(): Promise<boolean>`
|
|
213
|
+
|
|
214
|
+
**Implemented.** Probes whether a TPM backend exists and is reachable.
|
|
215
|
+
|
|
216
|
+
1. If `native.cjs` failed to load → `false`.
|
|
217
|
+
2. On macOS → `false` (hard-coded).
|
|
218
|
+
3. On Linux/Windows → native `is_available()` (device node / TBS presence).
|
|
219
|
+
|
|
220
|
+
Never throws. Use before `open()` in user-facing apps.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### `Tpm.open(): Promise<TpmHandle>`
|
|
225
|
+
|
|
226
|
+
**Implemented.** Returns a namespace handle. Throws `TPM_UNAVAILABLE` if `isAvailable()` would be false.
|
|
227
|
+
|
|
228
|
+
Does not perform I/O beyond the availability check. See [Handle API](#handle-api-tpmhandle).
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### `Tpm.getFixedProperties(): Promise<TpmInfo>`
|
|
233
|
+
|
|
234
|
+
**Implemented.** Alias: `Tpm.info()`.
|
|
235
|
+
|
|
236
|
+
**Under the hood:** Marshals `TPM2_GetCapability` for `TPM_CAP_TPM_PROPERTIES`, reads manufacturer four-cc, firmware version fields, family indicator, and vendor strings. Virtual TPM detection is heuristic only.
|
|
237
|
+
|
|
238
|
+
**Use for:** Inventory, support diagnostics, detecting swtpm in CI.
|
|
239
|
+
|
|
240
|
+
**Not for:** Security decisions (virtual flag is not tamper-proof).
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### `Tpm.randomBytes(count): Promise<Buffer>`
|
|
245
|
+
|
|
246
|
+
**Implemented.** Flat form of `tpm.random.bytes`.
|
|
247
|
+
|
|
248
|
+
**Under the hood:** `TPM2_GetRandom` in chunks of ≤64 bytes per TPM spec, concatenated to `count` bytes.
|
|
249
|
+
|
|
250
|
+
| Parameter | Type | Meaning |
|
|
251
|
+
|-----------|------|---------|
|
|
252
|
+
| `count` | `number` | Bytes requested; `0` returns empty Buffer |
|
|
253
|
+
|
|
254
|
+
**Use for:** Hardware-backed entropy, nonces, key material mixing.
|
|
255
|
+
|
|
256
|
+
**Not for:** High-throughput streaming (each chunk is a TPM round-trip); use OS CSPRNG for bulk data unless you specifically need TPM RNG.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### `Tpm.pcrRead(selection, bank?): Promise<Record<number, string>>`
|
|
261
|
+
|
|
262
|
+
**Implemented.** Flat form of `tpm.pcr.read`.
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
### `Tpm.pcrExtend(index, digest): Promise<void>`
|
|
267
|
+
|
|
268
|
+
**Implemented.** Flat form of `tpm.pcr.extend`.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `Tpm.readPublic(handle): Promise<ReadPublicResult>`
|
|
273
|
+
|
|
274
|
+
**Implemented.** Flat form of `tpm.readPublic`.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
### `Tpm.readEkCertificate(): Promise<Buffer | null>`
|
|
279
|
+
|
|
280
|
+
**Implemented.** Flat form of `tpm.attest.ekCertificate`.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
### `Tpm.provisionAk(opts?): Promise<ProvisionAkResult>`
|
|
285
|
+
|
|
286
|
+
**Implemented.** Flat form of `tpm.attest.provisionAk`. Returns raw `{ akPublicDer, akBlob }` instead of `AkHandle`.
|
|
287
|
+
|
|
288
|
+
See [Attestation handles](#attestation-handles-akhandle) and [Attestation flows](#attestation-flows).
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### `Tpm.quote(opts): Promise<QuoteResult>`
|
|
293
|
+
|
|
294
|
+
**Implemented.** Flat form of `tpm.attest.quote` / `ak.quote`.
|
|
295
|
+
|
|
296
|
+
Requires `opts.akBlob` in the options object (unlike handle method, which binds the blob).
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
### `Tpm.activateCredential(opts): Promise<Buffer>`
|
|
301
|
+
|
|
302
|
+
**Implemented.** Flat form of `ak.activateCredential`. Requires `opts.akBlob`.
|
|
303
|
+
|
|
304
|
+
Returns the recovered secret (typically 32-byte seed) from the verifier's MakeCredential step.
|
|
305
|
+
|
|
306
|
+
---
|
|
307
|
+
|
|
308
|
+
### `Tpm.createKey(opts?): Promise<{ publicKeyDer, keyBlob }>`
|
|
309
|
+
|
|
310
|
+
**Implemented.** Flat form of `tpm.keys.create`. Returns plain object, not `KeyHandle`.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### `Tpm.signKeyBlob({ keyBlob, digest }): Promise<Buffer>`
|
|
315
|
+
|
|
316
|
+
**Implemented.** Flat form of `key.sign`. Signs with a wrapped blob without constructing a `KeyHandle`.
|
|
317
|
+
|
|
318
|
+
| Parameter | Meaning |
|
|
319
|
+
|-----------|---------|
|
|
320
|
+
| `keyBlob` | `{ public, private }` from create/load |
|
|
321
|
+
| `digest` | **32-byte SHA-256** digest (not raw message) |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Handle API (`TpmHandle`)
|
|
326
|
+
|
|
327
|
+
Returned by `Tpm.open()`. All sub-namespaces are plain objects with async methods.
|
|
328
|
+
|
|
329
|
+
### `tpm.info(): Promise<TpmInfo>`
|
|
330
|
+
|
|
331
|
+
**Implemented.** Delegates to `Tpm.getFixedProperties()`.
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
### `tpm.readPublic(handle): Promise<ReadPublicResult>`
|
|
336
|
+
|
|
337
|
+
**Implemented.**
|
|
338
|
+
|
|
339
|
+
**Under the hood:**
|
|
340
|
+
|
|
341
|
+
1. Parse handle string to `u32`.
|
|
342
|
+
2. `TPM2_ReadPublic` via TBS.
|
|
343
|
+
3. Convert `TPMT_PUBLIC` to SPKI DER; return name digest.
|
|
344
|
+
|
|
345
|
+
**Use for:** Reading EK public keys (`0x81010001`, `0x81010002`), verifying persistent objects.
|
|
346
|
+
|
|
347
|
+
**Not for:** Loading wrapped blobs (use `keys.load` instead).
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
### `tpm.pcr.read(selection, bank?): Promise<Record<number, string>>`
|
|
352
|
+
|
|
353
|
+
**Implemented.**
|
|
354
|
+
|
|
355
|
+
| Parameter | Default | Meaning |
|
|
356
|
+
|-----------|---------|---------|
|
|
357
|
+
| `selection` | — | Array of PCR indices (0–23 for SHA-256 bank) |
|
|
358
|
+
| `bank` | `'sha256'` | Only `'sha256'` supported today |
|
|
359
|
+
|
|
360
|
+
**Returns:** Map of index → **lowercase hex digest** (32 bytes = 64 hex chars for SHA-256).
|
|
361
|
+
|
|
362
|
+
**Under the hood:** Builds `TPML_PCR_SELECTION` for SHA-256, sends `TPM2_PCR_Read`, parses `TPML_DIGEST` in response order.
|
|
363
|
+
|
|
364
|
+
**Use for:** Remote attestation inputs, boot-state checks alongside quotes.
|
|
365
|
+
|
|
366
|
+
**Not for:** Extending PCRs (use [`tpm.pcr.extend`](#tpm-pcr-extendindex-digest)).
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### `tpm.pcr.extend(index, digest): Promise<void>`
|
|
371
|
+
|
|
372
|
+
**Implemented.**
|
|
373
|
+
|
|
374
|
+
| Parameter | Meaning |
|
|
375
|
+
|-----------|---------|
|
|
376
|
+
| `index` | Single PCR index (0–23 for SHA-256 bank) |
|
|
377
|
+
| `digest` | **32-byte** SHA-256 measurement (`Buffer`) |
|
|
378
|
+
|
|
379
|
+
**Under the hood:** Sends `TPM2_PCR_Extend` with `authHandle = TPM_RH_NULL`, one SHA-256 digest in `TPML_DIGEST_VALUES`. The TPM updates the bank as `SHA256(old_pcr || digest)`.
|
|
380
|
+
|
|
381
|
+
**Caveats:**
|
|
382
|
+
|
|
383
|
+
- **Linux:** Firmware may lock specific indices (often **0–7**). Prefer **16–23** for application measurements.
|
|
384
|
+
- **Windows standard user:** TBS returns `TPM_E_COMMAND_BLOCKED` → library maps to **`REQUIRES_ELEVATION`** (re-run Admin PowerShell). Not `COMMAND_BLOCKED`.
|
|
385
|
+
- **Windows Administrator:** Can extend on real client hardware (validated). Does not affect quotes that only select other PCRs (e.g. `[0,1,7]`).
|
|
386
|
+
|
|
387
|
+
**Flat equivalent:** [`Tpm.pcrExtend(index, digest)`](#tpm-pcrextendindex-digest-promisevoid).
|
|
388
|
+
|
|
389
|
+
---
|
|
390
|
+
|
|
391
|
+
### `tpm.random.bytes(count): Promise<Buffer>`
|
|
392
|
+
|
|
393
|
+
**Implemented.** Same as [`Tpm.randomBytes`](#tpm-randombytescount-promisebuffer).
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
### `tpm.nv.read(handle, offset?, size?, auth?)`
|
|
398
|
+
|
|
399
|
+
**Implemented.**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// handle: hex string ('0x01c00002') or number
|
|
403
|
+
await tpm.nv.read('0x01c00002');
|
|
404
|
+
await tpm.nv.read('0x01c00002', 0, 512);
|
|
405
|
+
await tpm.nv.read('0x01800001', 0, undefined, authBuffer);
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Under the hood:** `NV_ReadPublic` → size/attribute check → `NV_Read` with owner or index auth (based on `TPMA_NV_PPREAD` / `TPMA_NV_AUTHREAD`). Optional `auth` supplies the index password for `AUTHREAD` indices.
|
|
409
|
+
|
|
410
|
+
**Safe indices on consumer hardware:**
|
|
411
|
+
|
|
412
|
+
| Index | Typical use | Writable |
|
|
413
|
+
|-------|-------------|----------|
|
|
414
|
+
| `0x01c00002`, `0x01c0000A` | EK certificate (RSA / ECC) | **Read-only** (firmware) |
|
|
415
|
+
| `0x01c0000B` | EK template | Read-only |
|
|
416
|
+
| User-defined (`0x01800001`+) | Application data | **`nv.define` then read/write** |
|
|
417
|
+
|
|
418
|
+
Prefer read-only access to well-known TCG indices. Writes fail with `TPM_RC` / `AUTH_FAILED` when the index is not writable or auth is wrong.
|
|
419
|
+
|
|
420
|
+
**Flat equivalent:** [`Tpm.nvRead(handle, offset?, size?, auth?)`](#tpm-nvread).
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
### `tpm.nv.readPublic(handle): Promise<{ dataSize, attributes }>`
|
|
425
|
+
|
|
426
|
+
**Implemented.** Returns NV index metadata from `NV_ReadPublic` without reading data.
|
|
427
|
+
|
|
428
|
+
**Flat equivalent:** [`Tpm.nvReadPublic(handle)`](#tpm-nvreadpublic).
|
|
429
|
+
|
|
430
|
+
---
|
|
431
|
+
|
|
432
|
+
### `tpm.nv.define(opts): Promise<void>`
|
|
433
|
+
|
|
434
|
+
**Implemented.** Creates an owner NV index (`TPM2_NV_DefineSpace`).
|
|
435
|
+
|
|
436
|
+
```typescript
|
|
437
|
+
type NvDefineOptions = {
|
|
438
|
+
handle: string | number; // 0x01800000..0x01BFFFFF
|
|
439
|
+
size: number; // 1..65535 bytes
|
|
440
|
+
auth?: Buffer; // index password (if using AUTH* attributes)
|
|
441
|
+
ownerAuth?: Buffer; // owner hierarchy password (often empty)
|
|
442
|
+
};
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**Default attributes:** `OWNERREAD | OWNERWRITE | NO_DA` — read/write via owner auth on `TPM_RH_OWNER`.
|
|
446
|
+
|
|
447
|
+
**Destructive / privileged:** Consumes TPM NV space until [`tpm.nv.undefine`](#tpmnvundefinehandle-ownerauth). Refuses EK cert indices. **Not for production laptops without intent.**
|
|
448
|
+
|
|
449
|
+
**Flat equivalent:** [`Tpm.nvDefine(opts)`](#tpm-nvdefine).
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
### `tpm.nv.undefine(handle, ownerAuth?): Promise<void>`
|
|
454
|
+
|
|
455
|
+
**Implemented.** Deletes an owner NV index (`TPM2_NV_UndefineSpace`).
|
|
456
|
+
|
|
457
|
+
**Flat equivalent:** [`Tpm.nvUndefine(handle, ownerAuth?)`](#tpm-nvundefine).
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### `tpm.nv.write(handle, data, offset?, auth?)`
|
|
462
|
+
|
|
463
|
+
**Implemented.**
|
|
464
|
+
|
|
465
|
+
**Under the hood:** `NV_ReadPublic` bounds check → `NV_Write` with owner or index auth (`TPMA_NV_PPWRITE` / `TPMA_NV_AUTHWRITE`).
|
|
466
|
+
|
|
467
|
+
**Caveats:** Most factory NV indices are read-only. User-defined indices must be created with [`tpm.nv.define`](#tpmnvdefineopts-promisevoid) first.
|
|
468
|
+
|
|
469
|
+
**Flat equivalent:** [`Tpm.nvWrite(handle, data, offset?, auth?)`](#tpm-nvwrite).
|
|
470
|
+
|
|
471
|
+
---
|
|
472
|
+
|
|
473
|
+
### `tpm.keys.create(opts): Promise<KeyHandle>`
|
|
474
|
+
|
|
475
|
+
**Implemented.**
|
|
476
|
+
|
|
477
|
+
```typescript
|
|
478
|
+
type KeyCreateOptions = {
|
|
479
|
+
type: 'ecc' | 'rsa';
|
|
480
|
+
sign?: boolean; // default true
|
|
481
|
+
decrypt?: boolean; // default false; RSA only
|
|
482
|
+
};
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
**Under the hood (both OSes, TBS path):**
|
|
486
|
+
|
|
487
|
+
1. `CreatePrimary` — transient ECC P-256 storage primary under owner hierarchy.
|
|
488
|
+
2. `Create` — child signing key with `userWithAuth`, `fixedTPM`, `fixedParent`, `sensitiveDataOrigin`.
|
|
489
|
+
3. Flush storage primary; return wrapped `{ public, private }` + SPKI DER.
|
|
490
|
+
|
|
491
|
+
| Key type | Signing scheme | Notes |
|
|
492
|
+
|----------|----------------|-------|
|
|
493
|
+
| `ecc` | ECDSA-SHA256, NIST P-256 | Default |
|
|
494
|
+
| `rsa` | RSASSA-SHA256, 2048-bit, exponent 65537 | |
|
|
495
|
+
|
|
496
|
+
At least one of `sign` or `decrypt` must be true. `decrypt: true` with `type: 'ecc'` is rejected (`INVALID_ARGUMENT`).
|
|
497
|
+
|
|
498
|
+
**Use for:** Device-bound signing keys, software-held wrapped blobs, same-user persistence.
|
|
499
|
+
|
|
500
|
+
**Not for:** Attestation quotes (use `attest.provisionAk` — different template and policy). Not for Windows cross-user fleet keys (use machine-scoped AK).
|
|
501
|
+
|
|
502
|
+
---
|
|
503
|
+
|
|
504
|
+
### `tpm.keys.load(blob): Promise<KeyHandle>`
|
|
505
|
+
|
|
506
|
+
**Implemented.**
|
|
507
|
+
|
|
508
|
+
**Under the hood:**
|
|
509
|
+
|
|
510
|
+
1. Parse SPKI from `blob.public` via `keyBlobPublicDer` (validates TBS blob, rejects PCP).
|
|
511
|
+
2. Return `KeyHandle` wrapping the blob (no TPM load until `sign`).
|
|
512
|
+
|
|
513
|
+
**Use for:** Restoring previously exported keys.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
### `tpm.seal.seal(opts)` / `tpm.seal.unseal(blob)`
|
|
518
|
+
|
|
519
|
+
**Implemented.**
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
type SealOptions = {
|
|
523
|
+
data: Buffer;
|
|
524
|
+
pcrSelection?: number[]; // SHA-256 bank; binds to current PCR values at seal time
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const sealed = await tpm.seal.seal({ data: secret });
|
|
528
|
+
const plain = await tpm.seal.unseal(sealed);
|
|
529
|
+
|
|
530
|
+
// PCR-bound: unseal fails if PCR state changes
|
|
531
|
+
const bound = await tpm.seal.seal({ data: secret, pcrSelection: [7] });
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Under the hood:**
|
|
535
|
+
|
|
536
|
+
1. `CreatePrimary` — transient storage primary.
|
|
537
|
+
2. Optional `PolicyPCR` session when `pcrSelection` is set; policy digest embedded in sealed object.
|
|
538
|
+
3. `Create` — keyedhash sealed object (`fixedTPM | fixedParent | userWithAuth | noDA`).
|
|
539
|
+
4. Export `SEAL` wire blob (public + private + PCR metadata).
|
|
540
|
+
5. `unseal`: load + `Unseal` (with matching `PolicyPCR` when bound).
|
|
541
|
+
|
|
542
|
+
**Caveats:** PCR-bound seal requires the chosen PCRs to match at unseal time. `tpm.pcr.extend` on Windows needs elevation for many indices.
|
|
543
|
+
|
|
544
|
+
**Flat equivalents:** [`Tpm.seal(opts)`](#tpm-sealopts), [`Tpm.unseal(blob)`](#tpm-unsealblob).
|
|
545
|
+
|
|
546
|
+
---
|
|
547
|
+
|
|
548
|
+
### `tpm.attest.ekCertificate(): Promise<Buffer | null>`
|
|
549
|
+
|
|
550
|
+
**Implemented.**
|
|
551
|
+
|
|
552
|
+
**Under the hood:** Tries TCG standard EK cert NV indices `0x01c00002` then `0x01c0000A`. For each: `NV_ReadPublic` → `NV_Read` with owner auth. Returns DER certificate bytes, or `null` if neither index is provisioned.
|
|
553
|
+
|
|
554
|
+
**Use for:** Chain-of-trust to manufacturer EK cert in attestation verification.
|
|
555
|
+
|
|
556
|
+
**Not for:** Attestation quotes (use `attest.provisionAk`). For general NV access use [`tpm.nv.read`](#tpmnvreadhandle-offset-size-auth).
|
|
557
|
+
|
|
558
|
+
---
|
|
559
|
+
|
|
560
|
+
### `tpm.attest.provisionAk(opts?): Promise<AkHandle>`
|
|
561
|
+
|
|
562
|
+
**Implemented.**
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
type ProvisionAkOptions = {
|
|
566
|
+
keyName?: string; // Windows PCP persisted name; random if omitted
|
|
567
|
+
scope?: 'user' | 'machine'; // Windows only; default 'user'
|
|
568
|
+
overwrite?: boolean; // Windows: replace existing name
|
|
569
|
+
algorithm?: 'ecc' | 'rsa'; // deprecated; Windows always RSA via PCP
|
|
570
|
+
};
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
**Linux under the hood:**
|
|
574
|
+
|
|
575
|
+
1. Create storage primary.
|
|
576
|
+
2. Create AK with `adminWithPolicy` digest for ActivateCredential, ECC P-256.
|
|
577
|
+
3. Export TPM2B public/private; flush transients.
|
|
578
|
+
4. Return `AkHandle`.
|
|
579
|
+
|
|
580
|
+
**Windows under the hood:**
|
|
581
|
+
|
|
582
|
+
1. NCrypt PCP `CreatePersistedKey` / identity key (RSA-2048).
|
|
583
|
+
2. Blob format is PCP-specific (`PCP1`/`PCP2`); persisted under `keyName`.
|
|
584
|
+
3. `scope: 'machine'` sets DACL for standard-user quote at runtime (requires elevation at provision time).
|
|
585
|
+
|
|
586
|
+
See [Attestation flows](#attestation-flows) and [windows-pcp.md](./windows-pcp.md).
|
|
587
|
+
|
|
588
|
+
---
|
|
589
|
+
|
|
590
|
+
### `tpm.attest.quote(opts): Promise<QuoteResult>`
|
|
591
|
+
|
|
592
|
+
**Implemented.** Same as `ak.quote` but requires `opts.akBlob`.
|
|
593
|
+
|
|
594
|
+
**Under the hood:**
|
|
595
|
+
|
|
596
|
+
1. Create storage primary (Linux) or open PCP key (Windows).
|
|
597
|
+
2. Load AK from blob.
|
|
598
|
+
3. `TPM2_Quote` with PCR selection list, qualifying data (nonce), signature scheme (ECDSA-SHA256 Linux; TPM_ALG_NULL → RSASSA on Windows PCP).
|
|
599
|
+
4. Flush all transient handles.
|
|
600
|
+
|
|
601
|
+
| Parameter | Meaning |
|
|
602
|
+
|-----------|---------|
|
|
603
|
+
| `pcrSelection` | PCR indices to include in quote |
|
|
604
|
+
| `qualifyingData` | Server challenge / nonce (binds quote to session) |
|
|
605
|
+
| `bank` | `'sha256'` (default) |
|
|
606
|
+
|
|
607
|
+
---
|
|
608
|
+
|
|
609
|
+
### `tpm[Symbol.asyncDispose]()`
|
|
610
|
+
|
|
611
|
+
**Implemented (no-op).** Reserved for future resource cleanup.
|
|
612
|
+
|
|
613
|
+
---
|
|
614
|
+
|
|
615
|
+
## Key handles (`KeyHandle`)
|
|
616
|
+
|
|
617
|
+
Returned by `tpm.keys.create()` and `tpm.keys.load()`.
|
|
618
|
+
|
|
619
|
+
### `key.export(): KeyBlob`
|
|
620
|
+
|
|
621
|
+
**Implemented.** Returns `{ public, private }` Buffers for persistence. Same shape as `AkBlob`.
|
|
622
|
+
|
|
623
|
+
---
|
|
624
|
+
|
|
625
|
+
### `key.publicKeyDer` (getter)
|
|
626
|
+
|
|
627
|
+
**Implemented.** Read-only SPKI DER for the public key.
|
|
628
|
+
|
|
629
|
+
---
|
|
630
|
+
|
|
631
|
+
### `key.sign(digest): Promise<Buffer>`
|
|
632
|
+
|
|
633
|
+
**Implemented.**
|
|
634
|
+
|
|
635
|
+
**Under the hood:**
|
|
636
|
+
|
|
637
|
+
1. Create storage primary, `Load` wrapped blob.
|
|
638
|
+
2. `TPM2_Sign` with 32-byte digest, external hash ticket (caller must pre-hash with SHA-256).
|
|
639
|
+
3. Flush primary and key handles.
|
|
640
|
+
|
|
641
|
+
| Parameter | Meaning |
|
|
642
|
+
|-----------|---------|
|
|
643
|
+
| `digest` | **Exactly 32 bytes** — SHA-256 hash of message |
|
|
644
|
+
|
|
645
|
+
**Returns:** TPM2B signature wire bytes (DER-encoded signature inside).
|
|
646
|
+
|
|
647
|
+
**Use for:** Proof-of-possession, document signing with device key.
|
|
648
|
+
|
|
649
|
+
**Not for:** Raw message signing (hash in application first). Not for PCP AK blobs.
|
|
650
|
+
|
|
651
|
+
---
|
|
652
|
+
|
|
653
|
+
### `key.decrypt(cipher)`
|
|
654
|
+
|
|
655
|
+
**Implemented.** RSA OAEP (SHA-256) via `TPM2_RSA_Decrypt`. Key must have been created with `decrypt: true` (RSA only).
|
|
656
|
+
|
|
657
|
+
**Under the hood:** Regenerate storage primary → `Load` → `RSA_Decrypt` with explicit OAEP scheme → flush.
|
|
658
|
+
|
|
659
|
+
**Use for:** Decrypting ciphertext produced for the TPM RSA key's public half.
|
|
660
|
+
|
|
661
|
+
**Not for:** ECC keys or sign-only RSA keys (`INVALID_ARGUMENT`).
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Attestation handles (`AkHandle`)
|
|
666
|
+
|
|
667
|
+
Returned by `tpm.attest.provisionAk()`.
|
|
668
|
+
|
|
669
|
+
### `ak.export(): AkBlob`
|
|
670
|
+
|
|
671
|
+
**Implemented.** Wrapped public/private for persistence. On Windows with PCP, this is the PCP blob — store securely for runtime quote.
|
|
672
|
+
|
|
673
|
+
---
|
|
674
|
+
|
|
675
|
+
### `ak.publicKeyDer` (getter)
|
|
676
|
+
|
|
677
|
+
**Implemented.** SPKI DER of the attestation key public area.
|
|
678
|
+
|
|
679
|
+
---
|
|
680
|
+
|
|
681
|
+
### `ak.quote(opts): Promise<QuoteResult>`
|
|
682
|
+
|
|
683
|
+
**Implemented.** Same as [`tpm.attest.quote`](#tpm-attest-quoteopts-promisetquoteresult) without passing `akBlob`.
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
### `ak.activateCredential(opts): Promise<Buffer>`
|
|
688
|
+
|
|
689
|
+
**Implemented.**
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
type ActivateCredentialOptions = {
|
|
693
|
+
credentialBlob: Buffer; // from verifier MakeCredential
|
|
694
|
+
secret: Buffer; // encrypted seed from MakeCredential
|
|
695
|
+
};
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
**Under the hood:**
|
|
699
|
+
|
|
700
|
+
1. Load AK from blob.
|
|
701
|
+
2. Build policy session: `PolicySecret` (endorsement) + `PolicyCommandCode` (ActivateCredential).
|
|
702
|
+
3. **Linux:** `TPM2_ActivateCredential` via TBS with EK handle.
|
|
703
|
+
4. **Windows:** `NCryptDecrypt` with `NCRYPT_PCP_TPM12_IDACTIVATION_MAGIC` (PCP path — raw TBS activation is blocked for standard users).
|
|
704
|
+
|
|
705
|
+
**Returns:** Recovered credential secret (proves AK resides on same TPM as EK).
|
|
706
|
+
|
|
707
|
+
**Use for:** Enrollment proof-of-possession before trusting `akPublicDer`.
|
|
708
|
+
|
|
709
|
+
**Not for:** General decryption. On Windows, standard users cannot activate — enrollment must run elevated; runtime quote does not need activation.
|
|
710
|
+
|
|
711
|
+
---
|
|
712
|
+
|
|
713
|
+
## Attestation flows
|
|
714
|
+
|
|
715
|
+
### Threat model (read this for fleet keys)
|
|
716
|
+
|
|
717
|
+
Attestation keys prove **device identity**, not **application identity**.
|
|
718
|
+
|
|
719
|
+
- **Windows machine scope (`PCP2`):** The AK blob is a locator (`keyName` + scope), not a shared secret. Standard users with the fleet `keyName` can quote — by design, so runtime apps need no admin. A quote answers: “this enrolled TPM, these PCRs, this challenge.” It does not answer: “only my.exe ran.”
|
|
720
|
+
- **Linux:** Wrapped AK blobs are sensitive on shared hosts: any principal with `/dev/tpmrm0` access can load the blob and quote on that TPM.
|
|
721
|
+
- **Enrollment vs runtime:** Bind the device at enroll time (verify `akPublicDer`, register with your service). Use `qualifyingData` on each quote for replay resistance. User/app binding is your session layer, not the TPM quote alone.
|
|
722
|
+
|
|
723
|
+
See [windows-pcp.md](./windows-pcp.md#threat-model-device-vs-application) for the full Windows PCP framing.
|
|
724
|
+
|
|
725
|
+
### Dev / same-user (Linux and Windows)
|
|
726
|
+
|
|
727
|
+
```javascript
|
|
728
|
+
import { Tpm } from 'node-tpm2';
|
|
729
|
+
|
|
730
|
+
await using tpm = await Tpm.open();
|
|
731
|
+
|
|
732
|
+
const ak = await tpm.attest.provisionAk();
|
|
733
|
+
const challenge = Buffer.from('server-session-nonce');
|
|
734
|
+
|
|
735
|
+
const { message, signature } = await ak.quote({
|
|
736
|
+
pcrSelection: [0, 1, 7],
|
|
737
|
+
qualifyingData: challenge,
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
// Send to verifier: ak.publicKeyDer, message, signature, pcrSelection, challenge
|
|
741
|
+
const saved = ak.export();
|
|
742
|
+
```
|
|
743
|
+
|
|
744
|
+
Verifier checks: signature over `message`, PCR values inside attestation, `qualifyingData` matches challenge, optional EK cert chain.
|
|
745
|
+
|
|
746
|
+
### Persist and reload
|
|
747
|
+
|
|
748
|
+
```javascript
|
|
749
|
+
const saved = ak.export();
|
|
750
|
+
// ... store JSON { public: b64, private: b64 } encrypted ...
|
|
751
|
+
|
|
752
|
+
const reloaded = await tpm.attest.quote({
|
|
753
|
+
akBlob: saved,
|
|
754
|
+
pcrSelection: [0, 1, 7],
|
|
755
|
+
qualifyingData: challenge,
|
|
756
|
+
});
|
|
757
|
+
// Or on Windows/Linux after reload, use flat Tpm.quote({ akBlob: saved, ... })
|
|
758
|
+
```
|
|
759
|
+
|
|
760
|
+
### Windows fleet enrollment (machine scope)
|
|
761
|
+
|
|
762
|
+
**Once (Admin or SYSTEM):**
|
|
763
|
+
|
|
764
|
+
```javascript
|
|
765
|
+
const ak = await tpm.attest.provisionAk({
|
|
766
|
+
keyName: 'my-app-device-ak',
|
|
767
|
+
scope: 'machine',
|
|
768
|
+
overwrite: true,
|
|
769
|
+
});
|
|
770
|
+
// Persist ak.export(); register ak.publicKeyDer with enrollment service
|
|
771
|
+
```
|
|
772
|
+
|
|
773
|
+
**Every session (standard user):**
|
|
774
|
+
|
|
775
|
+
```javascript
|
|
776
|
+
const akBlob = loadPersistedBlob();
|
|
777
|
+
const quote = await Tpm.quote({
|
|
778
|
+
akBlob,
|
|
779
|
+
pcrSelection: [0, 1, 7],
|
|
780
|
+
qualifyingData: runtimeChallenge,
|
|
781
|
+
});
|
|
782
|
+
```
|
|
783
|
+
|
|
784
|
+
See [windows-pcp.md](./windows-pcp.md) for DACL and SYSTEM provisioning details.
|
|
785
|
+
|
|
786
|
+
### EK certificate + credential activation
|
|
787
|
+
|
|
788
|
+
```javascript
|
|
789
|
+
await using tpm = await Tpm.open();
|
|
790
|
+
|
|
791
|
+
const ekCert = await tpm.attest.ekCertificate(); // null if OEM didn't provision NV
|
|
792
|
+
const ek = await tpm.readPublic('0x81010001'); // RSA EK handle
|
|
793
|
+
|
|
794
|
+
// Verifier runs MakeCredential(ekPublic, akName, secret) → credentialBlob + secret
|
|
795
|
+
const recovered = await ak.activateCredential({ credentialBlob, secret });
|
|
796
|
+
// recovered proves AK is on TPM that owns EK
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
---
|
|
800
|
+
|
|
801
|
+
## Error model (`TpmError`)
|
|
802
|
+
|
|
803
|
+
```javascript
|
|
804
|
+
import { Tpm, TpmError } from 'node-tpm2';
|
|
805
|
+
|
|
806
|
+
try {
|
|
807
|
+
await Tpm.open();
|
|
808
|
+
} catch (err) {
|
|
809
|
+
if (err instanceof TpmError) {
|
|
810
|
+
err.code; // stable string — branch on this
|
|
811
|
+
err.message; // human-readable detail
|
|
812
|
+
err.suggestion; // optional remediation hint
|
|
813
|
+
err.tpmRc; // TPM 2.0 response code (number), when present
|
|
814
|
+
err.hresult; // Windows NCrypt HRESULT (number), when present
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
Native Rust errors serialize as `__tpm2__code|message|suggestion|tpmRc|hresult` and are parsed in `api.js`.
|
|
820
|
+
|
|
821
|
+
### Stable error codes
|
|
822
|
+
|
|
823
|
+
| Code | When |
|
|
824
|
+
|------|------|
|
|
825
|
+
| `TPM_UNAVAILABLE` | No TPM, missing native binary, macOS, backend not built |
|
|
826
|
+
| `ACCESS_DENIED` | OS denied device access (Linux permissions, container) |
|
|
827
|
+
| `REQUIRES_ELEVATION` | Windows Admin/SYSTEM required (machine AK, activation) |
|
|
828
|
+
| `COMMAND_BLOCKED` | Windows TBS driver blocked command ordinal |
|
|
829
|
+
| `NOT_SUPPORTED` | PCP/TBS capability gap on this platform | — | sometimes | — |
|
|
830
|
+
| `INVALID_ARGUMENT` | Bad options (wrong digest size, invalid key type, empty `keyName`) |
|
|
831
|
+
| `KEY_NOT_FOUND` | NCrypt persisted key missing |
|
|
832
|
+
| `ALREADY_EXISTS` | NCrypt key name collision (`overwrite: false`) |
|
|
833
|
+
| `MARSHALLING_ERROR` | Codec bug, malformed command, some NCrypt failures |
|
|
834
|
+
| `TRANSPORT_ERROR` | TBS / `/dev/tpmrm0` I/O failure |
|
|
835
|
+
| `AUTH_FAILED` | TPM auth-class response (policy/password) |
|
|
836
|
+
| `TPM_RC` | Other non-success TPM response |
|
|
837
|
+
|
|
838
|
+
TPM response codes map by class: auth → `AUTH_FAILED`, format → `MARSHALLING_ERROR`, Windows `0x80280400` → `COMMAND_BLOCKED`, else → `TPM_RC`.
|
|
839
|
+
|
|
840
|
+
---
|
|
841
|
+
|
|
842
|
+
## Platform differences
|
|
843
|
+
|
|
844
|
+
| Concern | Linux | Windows |
|
|
845
|
+
|---------|-------|---------|
|
|
846
|
+
| Transport | `/dev/tpmrm0` (resource manager) | TBS `Tbsip_Submit_Command` |
|
|
847
|
+
| Device access | User in `tss` group or equivalent | Standard user for most TBS ops |
|
|
848
|
+
| General `keys.*` | TBS wrapped blobs, ECC default | Same TBS path and blob format |
|
|
849
|
+
| `attest.provisionAk` | TBS ECC P-256 AK, transient export | NCrypt PCP RSA-2048 persisted key |
|
|
850
|
+
| Quote crypto | ECDSA-SHA256 explicit | RSASSA-SHA256 (PCP default scheme) |
|
|
851
|
+
| ActivateCredential | Full TBS policy path | NCrypt PCP ID activation |
|
|
852
|
+
| EK certificate | NV indices via TBS | Same |
|
|
853
|
+
| macOS | `isAvailable()` → false | — |
|
|
854
|
+
|
|
855
|
+
**Blob interchange:** Linux AK blobs are **not** portable to Windows PCP and vice versa. Fleet apps standardize on one platform's provisioning flow per device.
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
## Privilege matrix
|
|
860
|
+
|
|
861
|
+
| API | Linux user | Windows user | Windows Admin/SYSTEM |
|
|
862
|
+
|-----|:----------:|:------------:|:--------------------:|
|
|
863
|
+
| `Tpm.isAvailable()`, `open()`, `info()` | ✓ | ✓ | ✓ |
|
|
864
|
+
| `tpm.random.bytes`, `tpm.pcr.read` | ✓ | ✓ | ✓ |
|
|
865
|
+
| `tpm.pcr.extend` | ✓ † | ✗ → `REQUIRES_ELEVATION` | ✓ † |
|
|
866
|
+
| `tpm.nv.read` / `tpm.nv.write` | ✓ ‡ | ✓ ‡ | ✓ |
|
|
867
|
+
| `tpm.nv.define` / `tpm.nv.undefine` | ✓ § | ✓ § | ✓ § |
|
|
868
|
+
| `tpm.keys.create/load`, `key.sign`, `key.decrypt` | ✓ | ✓ | ✓ |
|
|
869
|
+
| `tpm.seal.seal` / `tpm.seal.unseal` | ✓ | ✓ | ✓ |
|
|
870
|
+
| `tpm.attest.provisionAk()` user scope | ✓ | ✓ | ✓ |
|
|
871
|
+
| `tpm.attest.provisionAk({ scope: 'machine' })` | — | ✗ | ✓ |
|
|
872
|
+
| `ak.quote` / `Tpm.quote` | ✓ | ✓ | ✓ |
|
|
873
|
+
| `ak.activateCredential` | ✓ | ✗ | ✓ |
|
|
874
|
+
|
|
875
|
+
† **`pcr.extend`:** Linux user OK (avoid boot PCRs 0–7). Windows user blocked → **`REQUIRES_ELEVATION`**; Admin/SYSTEM OK. See [windows-pcp.md](./windows-pcp.md).
|
|
876
|
+
|
|
877
|
+
‡ **`nv.read/write`:** Index permissions vary; EK cert indices are read-only. Writes to undefined indices fail at the TPM.
|
|
878
|
+
|
|
879
|
+
§ **`nv.define/undefine`:** Owner authorization required; owner NV range only. Destructive on NV space.
|
|
880
|
+
|
|
881
|
+
Linux requires read/write on `/dev/tpmrm0`. Windows fleet pattern: provision machine AK once elevated → standard users quote forever.
|
|
882
|
+
|
|
883
|
+
---
|
|
884
|
+
|
|
885
|
+
## Deferred / not in public API
|
|
886
|
+
|
|
887
|
+
| Feature | Notes |
|
|
888
|
+
|---------|-------|
|
|
889
|
+
| *(none — all planned namespaces are implemented)* | See [roadmap](./roadmap.md) for hardening / polish |
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## Namespace comparison table
|
|
894
|
+
|
|
895
|
+
| Operation | Namespace API | Flat API | Returns |
|
|
896
|
+
|-----------|---------------|----------|---------|
|
|
897
|
+
| Open session | `await Tpm.open()` | — | `TpmHandle` |
|
|
898
|
+
| TPM info | `tpm.info()` | `Tpm.info()` / `getFixedProperties()` | `TpmInfo` |
|
|
899
|
+
| Random | `tpm.random.bytes(n)` | `Tpm.randomBytes(n)` | `Buffer` |
|
|
900
|
+
| PCR read | `tpm.pcr.read(sel, bank?)` | `Tpm.pcrRead(sel, bank?)` | `Record<number, string>` |
|
|
901
|
+
| PCR extend | `tpm.pcr.extend(i, d)` | `Tpm.pcrExtend(i, d)` | `void` |
|
|
902
|
+
| NV read | `tpm.nv.read(h, off?, sz?, auth?)` | `Tpm.nvRead(...)` | `Buffer` |
|
|
903
|
+
| NV write | `tpm.nv.write(h, data, off?, auth?)` | `Tpm.nvWrite(...)` | `void` |
|
|
904
|
+
| NV readPublic | `tpm.nv.readPublic(h)` | `Tpm.nvReadPublic(h)` | `{ dataSize, attributes }` |
|
|
905
|
+
| NV define | `tpm.nv.define(opts)` | `Tpm.nvDefine(opts)` | `void` |
|
|
906
|
+
| NV undefine | `tpm.nv.undefine(h, ownerAuth?)` | `Tpm.nvUndefine(...)` | `void` |
|
|
907
|
+
| Create key | `tpm.keys.create(opts)` | `Tpm.createKey(opts)` | `KeyHandle` / `{ publicKeyDer, keyBlob }` |
|
|
908
|
+
| Load key | `tpm.keys.load(blob)` | — | `KeyHandle` |
|
|
909
|
+
| Sign | `key.sign(digest)` | `Tpm.signKeyBlob({ keyBlob, digest })` | `Buffer` |
|
|
910
|
+
| Decrypt | `key.decrypt(cipher)` | `Tpm.decryptKeyBlob({ keyBlob, cipher })` | `Buffer` |
|
|
911
|
+
| Seal | `tpm.seal.seal/unseal` | `Tpm.seal` / `Tpm.unseal` | `Buffer` |
|
|
912
|
+
| EK cert | `tpm.attest.ekCertificate()` | `Tpm.readEkCertificate()` | `Buffer \| null` |
|
|
913
|
+
| Provision AK | `tpm.attest.provisionAk(opts)` | `Tpm.provisionAk(opts)` | `AkHandle` / `{ akPublicDer, akBlob }` |
|
|
914
|
+
| Quote | `ak.quote(opts)` / `tpm.attest.quote(opts)` | `Tpm.quote({ akBlob, ... })` | `QuoteResult` |
|
|
915
|
+
| Activate | `ak.activateCredential(opts)` | `Tpm.activateCredential({ akBlob, ... })` | `Buffer` |
|
|
916
|
+
| ReadPublic | `tpm.readPublic(h)` | `Tpm.readPublic(h)` | `ReadPublicResult` |
|
|
917
|
+
|
|
918
|
+
---
|
|
919
|
+
|
|
920
|
+
## Code examples
|
|
921
|
+
|
|
922
|
+
### Complete signing workflow
|
|
923
|
+
|
|
924
|
+
```javascript
|
|
925
|
+
import { createHash } from 'node:crypto';
|
|
926
|
+
import { Tpm } from 'node-tpm2';
|
|
927
|
+
|
|
928
|
+
await using tpm = await Tpm.open();
|
|
929
|
+
|
|
930
|
+
const key = await tpm.keys.create({ type: 'ecc', sign: true });
|
|
931
|
+
const digest = createHash('sha256').update('document-body').digest();
|
|
932
|
+
const signature = await key.sign(digest);
|
|
933
|
+
|
|
934
|
+
const blob = key.export();
|
|
935
|
+
const restored = await tpm.keys.load(blob);
|
|
936
|
+
await restored.sign(digest);
|
|
937
|
+
```
|
|
938
|
+
|
|
939
|
+
### Attestation with error handling
|
|
940
|
+
|
|
941
|
+
```javascript
|
|
942
|
+
import { Tpm, TpmError } from 'node-tpm2';
|
|
943
|
+
|
|
944
|
+
try {
|
|
945
|
+
await using tpm = await Tpm.open();
|
|
946
|
+
const ak = await tpm.attest.provisionAk({ scope: 'machine', keyName: 'fleet-ak' });
|
|
947
|
+
} catch (err) {
|
|
948
|
+
if (err instanceof TpmError && err.code === 'REQUIRES_ELEVATION') {
|
|
949
|
+
// Re-run installer as Admin/SYSTEM
|
|
950
|
+
} else if (err instanceof TpmError && err.code === 'ALREADY_EXISTS') {
|
|
951
|
+
// Use overwrite: true
|
|
952
|
+
}
|
|
953
|
+
throw err;
|
|
954
|
+
}
|
|
955
|
+
```
|
|
956
|
+
|
|
957
|
+
### One-shot flat API (no handle)
|
|
958
|
+
|
|
959
|
+
```javascript
|
|
960
|
+
import { Tpm } from 'node-tpm2';
|
|
961
|
+
|
|
962
|
+
if (!(await Tpm.isAvailable())) process.exit(1);
|
|
963
|
+
|
|
964
|
+
const props = await Tpm.getFixedProperties();
|
|
965
|
+
const random = await Tpm.randomBytes(32);
|
|
966
|
+
const pcrs = await Tpm.pcrRead([0, 7], 'sha256');
|
|
967
|
+
```
|
|
968
|
+
|
|
969
|
+
---
|
|
970
|
+
|
|
971
|
+
## Symbol index
|
|
972
|
+
|
|
973
|
+
All public exports from `'node-tpm2'`:
|
|
974
|
+
|
|
975
|
+
| Symbol | Kind | Status |
|
|
976
|
+
|--------|------|--------|
|
|
977
|
+
| `TpmError` | class | Implemented |
|
|
978
|
+
| `Tpm.isAvailable` | function | Implemented |
|
|
979
|
+
| `Tpm.open` | function | Implemented |
|
|
980
|
+
| `Tpm.getFixedProperties` | function | Implemented |
|
|
981
|
+
| `Tpm.info` | function | Implemented |
|
|
982
|
+
| `Tpm.randomBytes` | function | Implemented |
|
|
983
|
+
| `Tpm.pcrRead` | function | Implemented |
|
|
984
|
+
| `Tpm.pcrExtend` | function | Implemented |
|
|
985
|
+
| `Tpm.readPublic` | function | Implemented |
|
|
986
|
+
| `Tpm.readEkCertificate` | function | Implemented |
|
|
987
|
+
| `Tpm.quote` | function | Implemented |
|
|
988
|
+
| `Tpm.provisionAk` | function | Implemented |
|
|
989
|
+
| `Tpm.activateCredential` | function | Implemented |
|
|
990
|
+
| `Tpm.createKey` | function | Implemented |
|
|
991
|
+
| `Tpm.signKeyBlob` | function | Implemented |
|
|
992
|
+
| `Tpm.decryptKeyBlob` | function | Implemented |
|
|
993
|
+
| `Tpm.nvRead` | function | Implemented |
|
|
994
|
+
| `Tpm.nvWrite` | function | Implemented |
|
|
995
|
+
| `Tpm.nvReadPublic` | function | Implemented |
|
|
996
|
+
| `Tpm.nvDefine` | function | Implemented |
|
|
997
|
+
| `Tpm.nvUndefine` | function | Implemented |
|
|
998
|
+
| `Tpm.seal` | function | Implemented |
|
|
999
|
+
| `Tpm.unseal` | function | Implemented |
|
|
1000
|
+
| `TpmHandle.info` | method | Implemented |
|
|
1001
|
+
| `TpmHandle.readPublic` | method | Implemented |
|
|
1002
|
+
| `TpmHandle.pcr.read` | method | Implemented |
|
|
1003
|
+
| `TpmHandle.pcr.extend` | method | Implemented |
|
|
1004
|
+
| `TpmHandle.random.bytes` | method | Implemented |
|
|
1005
|
+
| `TpmHandle.nv.read` | method | Implemented |
|
|
1006
|
+
| `TpmHandle.nv.write` | method | Implemented |
|
|
1007
|
+
| `TpmHandle.nv.readPublic` | method | Implemented |
|
|
1008
|
+
| `TpmHandle.nv.define` | method | Implemented |
|
|
1009
|
+
| `TpmHandle.nv.undefine` | method | Implemented |
|
|
1010
|
+
| `TpmHandle.keys.create` | method | Implemented |
|
|
1011
|
+
| `TpmHandle.keys.load` | method | Implemented |
|
|
1012
|
+
| `TpmHandle.seal.seal` | method | Implemented |
|
|
1013
|
+
| `TpmHandle.seal.unseal` | method | Implemented |
|
|
1014
|
+
| `TpmHandle.attest.ekCertificate` | method | Implemented |
|
|
1015
|
+
| `TpmHandle.attest.provisionAk` | method | Implemented |
|
|
1016
|
+
| `TpmHandle.attest.quote` | method | Implemented |
|
|
1017
|
+
| `TpmHandle[Symbol.asyncDispose]` | method | Implemented (no-op) |
|
|
1018
|
+
| `KeyHandle.export` | method | Implemented |
|
|
1019
|
+
| `KeyHandle.publicKeyDer` | getter | Implemented |
|
|
1020
|
+
| `KeyHandle.sign` | method | Implemented |
|
|
1021
|
+
| `KeyHandle.decrypt` | method | Implemented |
|
|
1022
|
+
| `AkHandle.export` | method | Implemented |
|
|
1023
|
+
| `AkHandle.publicKeyDer` | getter | Implemented |
|
|
1024
|
+
| `AkHandle.quote` | method | Implemented |
|
|
1025
|
+
| `AkHandle.activateCredential` | method | Implemented |
|
|
1026
|
+
|
|
1027
|
+
**TypeScript types** (from `index.d.ts`, not runtime exports): `TpmErrorCode`, `AkBlob`, `KeyBlob`, `QuoteOptions`, `QuoteResult`, `ReadPublicResult`, `ProvisionAkOptions`, `ProvisionAkResult`, `ActivateCredentialOptions`, `ActivateCredentialFlatOptions`, `KeyCreateOptions`, `SealOptions`, `TpmInfo`, `TpmHandle`, `KeyHandle`, `AkHandle`.
|
|
1028
|
+
|
|
1029
|
+
**Native-only** (used internally, not exported from package): `keyBlobPublicDer`.
|