node-tpm2 0.0.4-beta.4 → 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 +52 -17
- package/api.js +135 -9
- package/docs/api-reference.md +149 -35
- package/docs/roadmap.md +28 -32
- package/docs/windows-pcp.md +3 -0
- package/examples/nv-smoke.mjs +77 -0
- package/index.d.ts +50 -8
- package/native.cjs +60 -52
- package/native.d.ts +50 -0
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ const { message, signature } = await ak.quote({
|
|
|
18
18
|
});
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**Pre-release** (`0.0.x-beta`). [
|
|
21
|
+
**Pre-release** (`0.0.x-beta`). Full public API implemented; validated on real Windows 11 + Intel TPM. [API reference](./docs/api-reference.md) · [Roadmap](./docs/roadmap.md).
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -194,19 +194,21 @@ More detail: [getting-started.md](./docs/getting-started.md) · [api-reference.m
|
|
|
194
194
|
| `tpm.random.bytes(n)` | ✓ | ✓ | ✓ |
|
|
195
195
|
| **pcr** | | | |
|
|
196
196
|
| `tpm.pcr.read(...)` | ✓ | ✓ | ✓ |
|
|
197
|
-
| `tpm.pcr.extend(i, digest)` | ✓
|
|
197
|
+
| `tpm.pcr.extend(i, digest)` | ✓ † | ✗ → `REQUIRES_ELEVATION` | ✓ † |
|
|
198
198
|
| **nv** | | | |
|
|
199
|
-
| `tpm.nv.read(...)` | ✓
|
|
200
|
-
| `tpm.nv.write(...)` | ✓
|
|
199
|
+
| `tpm.nv.read(...)` | ✓ ‡ | ✓ ‡ | ✓ |
|
|
200
|
+
| `tpm.nv.write(...)` | ✓ ‡ | ✓ ‡ | ✓ |
|
|
201
|
+
| `tpm.nv.define(...)` | ✓ § | ✓ § | ✓ § |
|
|
202
|
+
| `tpm.nv.undefine(...)` | ✓ § | ✓ § | ✓ § |
|
|
201
203
|
| `tpm.attest.ekCertificate()` | ✓ | ✓ | ✓ |
|
|
202
204
|
| **keys** | | | |
|
|
203
205
|
| `tpm.keys.create(...)` | ✓ | ✓ | ✓ |
|
|
204
206
|
| `tpm.keys.load(blob)` | ✓ | ✓ | ✓ |
|
|
205
207
|
| `key.sign(digest)` | ✓ | ✓ | ✓ |
|
|
206
|
-
| `key.decrypt(cipher)` |
|
|
208
|
+
| `key.decrypt(cipher)` | ✓ | ✓ | ✓ |
|
|
207
209
|
| **seal** | | | |
|
|
208
|
-
| `tpm.seal(...)` | ✓
|
|
209
|
-
| `tpm.unseal(blob)` | ✓
|
|
210
|
+
| `tpm.seal.seal(...)` | ✓ | ✓ | ✓ |
|
|
211
|
+
| `tpm.unseal(blob)` | ✓ | ✓ | ✓ |
|
|
210
212
|
| **attest** | | | |
|
|
211
213
|
| `tpm.attest.provisionAk()` user | ✓ | ✓ | ✓ |
|
|
212
214
|
| `tpm.attest.provisionAk({ scope: 'machine' })` | — | ✗ | ✓ |
|
|
@@ -217,7 +219,13 @@ More detail: [getting-started.md](./docs/getting-started.md) · [api-reference.m
|
|
|
217
219
|
|
|
218
220
|
**Windows fleet pattern:** provision machine AK elevated or as SYSTEM once → persist `akBlob` → standard users quote forever after. See [docs/windows-pcp.md](./docs/windows-pcp.md).
|
|
219
221
|
|
|
220
|
-
**
|
|
222
|
+
**Hardware validation (beta):** Windows 11 Intel TPM — attestation suite, `random`, `keys`, `pcr.read` / `pcr.extend` (elevated), `nv.read`. Linux: CI + swtpm. Firmware or group policy can still deny specific PCR/NV operations — those surface as `TPM_RC` or `REQUIRES_ELEVATION`, not silent failure.
|
|
223
|
+
|
|
224
|
+
**‡ `nv.read/write`:** Success depends on index attributes and auth. EK cert indices (`0x01c00002`, `0x01c0000A`) are read-only.
|
|
225
|
+
|
|
226
|
+
**§ `nv.define/undefine`:** Owner NV range only (`0x01800000`–`0x01BFFFFF`). Requires owner authorization (often empty password). **Consumes NV space** until undefined — use only on test machines or with a chosen index.
|
|
227
|
+
|
|
228
|
+
**† `pcr.extend`:** Linux standard user (prefer indices **16–23** for experiments; avoid **0–7** boot/Secure Boot PCRs). **Windows standard user → `REQUIRES_ELEVATION`** (`TPM_E_COMMAND_BLOCKED` from TBS). Windows Administrator can extend on real hardware (validated). Standard-user failure is not `COMMAND_BLOCKED` — re-run elevated.
|
|
221
229
|
|
|
222
230
|
---
|
|
223
231
|
|
|
@@ -268,7 +276,31 @@ const reloaded = await tpm.keys.load(saved);
|
|
|
268
276
|
await reloaded.sign(digest);
|
|
269
277
|
```
|
|
270
278
|
|
|
271
|
-
|
|
279
|
+
const rsaKey = await tpm.keys.create({ type: 'rsa', sign: true, decrypt: true });
|
|
280
|
+
const plain = await rsaKey.decrypt(ciphertext);
|
|
281
|
+
|
|
282
|
+
Flat: `Tpm.createKey()`, `Tpm.signKeyBlob({ keyBlob, digest })`, `Tpm.decryptKeyBlob({ keyBlob, cipher })`.
|
|
283
|
+
|
|
284
|
+
### NV
|
|
285
|
+
|
|
286
|
+
```javascript
|
|
287
|
+
await tpm.nv.read('0x01c00002'); // EK cert index (read-only on most hardware)
|
|
288
|
+
await tpm.nv.readPublic('0x01800042'); // metadata before read/write
|
|
289
|
+
await tpm.nv.define({ handle: '0x01800042', size: 64 }); // owner NV — test machines only
|
|
290
|
+
await tpm.nv.write('0x01800042', data, 0);
|
|
291
|
+
await tpm.nv.undefine('0x01800042');
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Flat: `Tpm.nvRead`, `Tpm.nvWrite`, `Tpm.nvReadPublic`, `Tpm.nvDefine`, `Tpm.nvUndefine`. See `examples/nv-smoke.mjs`.
|
|
295
|
+
|
|
296
|
+
### Seal
|
|
297
|
+
|
|
298
|
+
```javascript
|
|
299
|
+
const sealed = await tpm.seal.seal({ data: secret, pcrSelection: [23] });
|
|
300
|
+
const plain = await tpm.seal.unseal(sealed);
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Flat: `Tpm.seal`, `Tpm.unseal`.
|
|
272
304
|
|
|
273
305
|
### Attestation
|
|
274
306
|
|
|
@@ -347,8 +379,8 @@ try {
|
|
|
347
379
|
|------|------|:-------:|:---------:|----------------------|
|
|
348
380
|
| `TPM_UNAVAILABLE` | No TPM, no native binary, macOS, or backend not built | — | — | Install platform package / check TPM |
|
|
349
381
|
| `ACCESS_DENIED` | OS denied device or key access | — | sometimes | Linux: `tss` group; container: pass device |
|
|
350
|
-
| `REQUIRES_ELEVATION` | Windows operation needs Admin/SYSTEM | — | ✓ | Re-run enrollment elevated or as SYSTEM |
|
|
351
|
-
| `COMMAND_BLOCKED` | Windows TBS
|
|
382
|
+
| `REQUIRES_ELEVATION` | Windows operation needs Admin/SYSTEM | — | ✓ | Re-run enrollment elevated or as SYSTEM; **`pcr.extend` from standard user** |
|
|
383
|
+
| `COMMAND_BLOCKED` | Windows TBS blocked raw ordinal (e.g. ActivateCredential) | ✓ | — | Use NCrypt PCP — elevation does not help |
|
|
352
384
|
| `NOT_SUPPORTED` | Feature or PCP capability missing on this platform | — | sometimes | — |
|
|
353
385
|
| `INVALID_ARGUMENT` | Bad JS/Rust option (e.g. empty machine `keyName`) | — | sometimes | Fix caller input |
|
|
354
386
|
| `KEY_NOT_FOUND` | NCrypt key / blob locator not found | — | ✓ | Check persisted blob / key name |
|
|
@@ -367,7 +399,9 @@ When the TPM returns a non-zero response code, the library classifies it:
|
|
|
367
399
|
| Success | `rc === 0` | (no error) | `0` |
|
|
368
400
|
| Auth | `(rc & 0x0300) === 0x0300` | `AUTH_FAILED` | `0x38E` (`TPM_RC_AUTH_FAIL`) |
|
|
369
401
|
| Format | `(rc & 0xFF00) === 0x0100` or FMT1 bit set | `MARSHALLING_ERROR` | `0x125` (`TPM_RC_ASYMMETRIC`) |
|
|
370
|
-
| Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` | `0x80280400` |
|
|
402
|
+
| Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` * | `0x80280400` |
|
|
403
|
+
|
|
404
|
+
\* **`PCR_Extend`:** mapped to **`REQUIRES_ELEVATION`** (same `hresult` `0x80280400`) — Administrator can extend on Windows client; standard user should re-run elevated.
|
|
371
405
|
| Other | everything else | `TPM_RC` | vendor-specific |
|
|
372
406
|
|
|
373
407
|
Auth-class and format-class detection follows TPM 2.0 response-code layout (see `src/tbs/rc.rs`). **`tpmRc` on the error is the full 32-bit value** from the TPM response header — use it for logs and TPM spec lookup.
|
|
@@ -394,16 +428,17 @@ PCP / NCrypt failures on Windows map through `classify_ncrypt` (`src/tbs/ncrypt.
|
|
|
394
428
|
|
|
395
429
|
---
|
|
396
430
|
|
|
397
|
-
## API reference
|
|
431
|
+
## API reference
|
|
398
432
|
|
|
399
|
-
Subsystem namespaces
|
|
433
|
+
Subsystem namespaces on `TpmHandle`. See [docs/api-reference.md](./docs/api-reference.md) for full detail.
|
|
400
434
|
|
|
401
435
|
| Namespace | Methods |
|
|
402
436
|
|-----------|---------|
|
|
403
437
|
| `tpm.random` | `bytes(n)` ✅ |
|
|
404
|
-
| `tpm.keys` | `create`, `load`, `KeyHandle.sign
|
|
405
|
-
| `tpm.pcr` | `
|
|
406
|
-
| `tpm.
|
|
438
|
+
| `tpm.keys` | `create`, `load`, `KeyHandle.sign`, `KeyHandle.decrypt` ✅ |
|
|
439
|
+
| `tpm.pcr` | `read`, `extend` ✅ |
|
|
440
|
+
| `tpm.nv` | `read`, `write`, `readPublic`, `define`, `undefine` ✅ |
|
|
441
|
+
| `tpm.seal` | `seal`, `unseal` ✅ |
|
|
407
442
|
|
|
408
443
|
---
|
|
409
444
|
|
package/api.js
CHANGED
|
@@ -66,10 +66,11 @@ export class TpmError extends Error {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
69
|
+
function parseTpmHandle(handle) {
|
|
70
|
+
if (typeof handle === 'number') {
|
|
71
|
+
return `0x${handle.toString(16).padStart(8, '0')}`;
|
|
72
|
+
}
|
|
73
|
+
return handle;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
76
|
function createKeyHandle(publicKeyDer, keyBlob) {
|
|
@@ -94,7 +95,14 @@ function createKeyHandle(publicKeyDer, keyBlob) {
|
|
|
94
95
|
return Buffer.from(sig);
|
|
95
96
|
}),
|
|
96
97
|
|
|
97
|
-
decrypt:
|
|
98
|
+
decrypt: wrapNative(async (cipher) => {
|
|
99
|
+
requireNative('decryptKeyBlob');
|
|
100
|
+
const plain = await native.decryptKeyBlob({
|
|
101
|
+
keyBlob,
|
|
102
|
+
cipher,
|
|
103
|
+
});
|
|
104
|
+
return Buffer.from(plain);
|
|
105
|
+
}),
|
|
98
106
|
};
|
|
99
107
|
}
|
|
100
108
|
|
|
@@ -164,8 +172,49 @@ function createTpmHandle() {
|
|
|
164
172
|
},
|
|
165
173
|
|
|
166
174
|
nv: {
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
readPublic: wrapNative(async (handle) => {
|
|
176
|
+
requireNative('nvReadPublic');
|
|
177
|
+
return native.nvReadPublic(parseTpmHandle(handle));
|
|
178
|
+
}),
|
|
179
|
+
|
|
180
|
+
read: wrapNative(async (handle, offset, size, auth) => {
|
|
181
|
+
requireNative('nvRead');
|
|
182
|
+
const buf = await native.nvRead(
|
|
183
|
+
parseTpmHandle(handle),
|
|
184
|
+
offset ?? undefined,
|
|
185
|
+
size ?? undefined,
|
|
186
|
+
auth ?? undefined,
|
|
187
|
+
);
|
|
188
|
+
return Buffer.from(buf);
|
|
189
|
+
}),
|
|
190
|
+
|
|
191
|
+
write: wrapNative(async (handle, data, offset, auth) => {
|
|
192
|
+
requireNative('nvWrite');
|
|
193
|
+
await native.nvWrite({
|
|
194
|
+
handle: parseTpmHandle(handle),
|
|
195
|
+
data,
|
|
196
|
+
offset: offset ?? undefined,
|
|
197
|
+
auth: auth ?? undefined,
|
|
198
|
+
});
|
|
199
|
+
}),
|
|
200
|
+
|
|
201
|
+
define: wrapNative(async (opts) => {
|
|
202
|
+
requireNative('nvDefine');
|
|
203
|
+
await native.nvDefine({
|
|
204
|
+
handle: parseTpmHandle(opts.handle),
|
|
205
|
+
size: opts.size,
|
|
206
|
+
auth: opts.auth ?? undefined,
|
|
207
|
+
ownerAuth: opts.ownerAuth ?? undefined,
|
|
208
|
+
});
|
|
209
|
+
}),
|
|
210
|
+
|
|
211
|
+
undefine: wrapNative(async (handle, ownerAuth) => {
|
|
212
|
+
requireNative('nvUndefine');
|
|
213
|
+
await native.nvUndefine({
|
|
214
|
+
handle: parseTpmHandle(handle),
|
|
215
|
+
ownerAuth: ownerAuth ?? undefined,
|
|
216
|
+
});
|
|
217
|
+
}),
|
|
169
218
|
},
|
|
170
219
|
|
|
171
220
|
keys: {
|
|
@@ -187,8 +236,20 @@ function createTpmHandle() {
|
|
|
187
236
|
},
|
|
188
237
|
|
|
189
238
|
seal: {
|
|
190
|
-
seal:
|
|
191
|
-
|
|
239
|
+
seal: wrapNative(async (opts) => {
|
|
240
|
+
requireNative('seal');
|
|
241
|
+
const buf = await native.seal({
|
|
242
|
+
data: opts.data,
|
|
243
|
+
pcrSelection: opts.pcrSelection,
|
|
244
|
+
});
|
|
245
|
+
return Buffer.from(buf);
|
|
246
|
+
}),
|
|
247
|
+
|
|
248
|
+
unseal: wrapNative(async (blob) => {
|
|
249
|
+
requireNative('unseal');
|
|
250
|
+
const plain = await native.unseal(blob);
|
|
251
|
+
return Buffer.from(plain);
|
|
252
|
+
}),
|
|
192
253
|
},
|
|
193
254
|
|
|
194
255
|
attest: {
|
|
@@ -352,4 +413,69 @@ export const Tpm = {
|
|
|
352
413
|
const sig = await native.signKeyBlob(opts);
|
|
353
414
|
return Buffer.from(sig);
|
|
354
415
|
}),
|
|
416
|
+
|
|
417
|
+
decryptKeyBlob: wrapNative(async (opts) => {
|
|
418
|
+
requireNative('decryptKeyBlob');
|
|
419
|
+
const plain = await native.decryptKeyBlob(opts);
|
|
420
|
+
return Buffer.from(plain);
|
|
421
|
+
}),
|
|
422
|
+
|
|
423
|
+
nvRead: wrapNative(async (handle, offset, size, auth) => {
|
|
424
|
+
requireNative('nvRead');
|
|
425
|
+
const buf = await native.nvRead(
|
|
426
|
+
parseTpmHandle(handle),
|
|
427
|
+
offset ?? undefined,
|
|
428
|
+
size ?? undefined,
|
|
429
|
+
auth ?? undefined,
|
|
430
|
+
);
|
|
431
|
+
return Buffer.from(buf);
|
|
432
|
+
}),
|
|
433
|
+
|
|
434
|
+
nvWrite: wrapNative(async (handle, data, offset, auth) => {
|
|
435
|
+
requireNative('nvWrite');
|
|
436
|
+
await native.nvWrite({
|
|
437
|
+
handle: parseTpmHandle(handle),
|
|
438
|
+
data,
|
|
439
|
+
offset: offset ?? undefined,
|
|
440
|
+
auth: auth ?? undefined,
|
|
441
|
+
});
|
|
442
|
+
}),
|
|
443
|
+
|
|
444
|
+
nvReadPublic: wrapNative(async (handle) => {
|
|
445
|
+
requireNative('nvReadPublic');
|
|
446
|
+
return native.nvReadPublic(parseTpmHandle(handle));
|
|
447
|
+
}),
|
|
448
|
+
|
|
449
|
+
nvDefine: wrapNative(async (opts) => {
|
|
450
|
+
requireNative('nvDefine');
|
|
451
|
+
await native.nvDefine({
|
|
452
|
+
handle: parseTpmHandle(opts.handle),
|
|
453
|
+
size: opts.size,
|
|
454
|
+
auth: opts.auth ?? undefined,
|
|
455
|
+
ownerAuth: opts.ownerAuth ?? undefined,
|
|
456
|
+
});
|
|
457
|
+
}),
|
|
458
|
+
|
|
459
|
+
nvUndefine: wrapNative(async (handle, ownerAuth) => {
|
|
460
|
+
requireNative('nvUndefine');
|
|
461
|
+
await native.nvUndefine({
|
|
462
|
+
handle: parseTpmHandle(handle),
|
|
463
|
+
ownerAuth: ownerAuth ?? undefined,
|
|
464
|
+
});
|
|
465
|
+
}),
|
|
466
|
+
|
|
467
|
+
seal: wrapNative(async (opts) => {
|
|
468
|
+
requireNative('seal');
|
|
469
|
+
const buf = await native.seal({
|
|
470
|
+
data: opts.data,
|
|
471
|
+
pcrSelection: opts.pcrSelection,
|
|
472
|
+
});
|
|
473
|
+
return Buffer.from(buf);
|
|
474
|
+
}),
|
|
475
|
+
|
|
476
|
+
unseal: wrapNative(async (blob) => {
|
|
477
|
+
requireNative('unseal');
|
|
478
|
+
const plain = await native.unseal(blob);
|
|
479
|
+
return Buffer.from(plain);
|
|
480
|
+
}),
|
|
355
481
|
};
|
package/docs/api-reference.md
CHANGED
|
@@ -27,7 +27,7 @@ import { Tpm, TpmError } from 'node-tpm2';
|
|
|
27
27
|
11. [Error model (`TpmError`)](#error-model-tpmerror)
|
|
28
28
|
12. [Platform differences](#platform-differences)
|
|
29
29
|
13. [Privilege matrix](#privilege-matrix)
|
|
30
|
-
14. [
|
|
30
|
+
14. [Deferred / not in public API](#deferred--not-in-public-api)
|
|
31
31
|
15. [Symbol index](#symbol-index)
|
|
32
32
|
|
|
33
33
|
---
|
|
@@ -66,7 +66,7 @@ flowchart TB
|
|
|
66
66
|
|
|
67
67
|
**Design rules:**
|
|
68
68
|
|
|
69
|
-
- **General keys, PCR, random, ReadPublic, seal
|
|
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
70
|
- **Attestation key persistence on Windows** uses NCrypt Platform Crypto Provider (PCP) because raw TBS cannot persist cross-user identity keys reliably.
|
|
71
71
|
- **Transient TPM handles** are created and flushed inside each native call. You do not manage TPM handle slots in JavaScript.
|
|
72
72
|
|
|
@@ -378,7 +378,11 @@ Returned by `Tpm.open()`. All sub-namespaces are plain objects with async method
|
|
|
378
378
|
|
|
379
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
380
|
|
|
381
|
-
**Caveats:**
|
|
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]`).
|
|
382
386
|
|
|
383
387
|
**Flat equivalent:** [`Tpm.pcrExtend(index, digest)`](#tpm-pcrextendindex-digest-promisevoid).
|
|
384
388
|
|
|
@@ -390,15 +394,79 @@ Returned by `Tpm.open()`. All sub-namespaces are plain objects with async method
|
|
|
390
394
|
|
|
391
395
|
---
|
|
392
396
|
|
|
393
|
-
### `tpm.nv.read(handle, offset?, size?)`
|
|
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`).
|
|
394
456
|
|
|
395
|
-
**
|
|
457
|
+
**Flat equivalent:** [`Tpm.nvUndefine(handle, ownerAuth?)`](#tpm-nvundefine).
|
|
396
458
|
|
|
397
459
|
---
|
|
398
460
|
|
|
399
|
-
### `tpm.nv.write(handle, data, offset?)`
|
|
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`).
|
|
400
466
|
|
|
401
|
-
**
|
|
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).
|
|
402
470
|
|
|
403
471
|
---
|
|
404
472
|
|
|
@@ -410,7 +478,7 @@ Returned by `Tpm.open()`. All sub-namespaces are plain objects with async method
|
|
|
410
478
|
type KeyCreateOptions = {
|
|
411
479
|
type: 'ecc' | 'rsa';
|
|
412
480
|
sign?: boolean; // default true
|
|
413
|
-
decrypt?: boolean; // default false; RSA only
|
|
481
|
+
decrypt?: boolean; // default false; RSA only
|
|
414
482
|
};
|
|
415
483
|
```
|
|
416
484
|
|
|
@@ -448,7 +516,32 @@ At least one of `sign` or `decrypt` must be true. `decrypt: true` with `type: 'e
|
|
|
448
516
|
|
|
449
517
|
### `tpm.seal.seal(opts)` / `tpm.seal.unseal(blob)`
|
|
450
518
|
|
|
451
|
-
**
|
|
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).
|
|
452
545
|
|
|
453
546
|
---
|
|
454
547
|
|
|
@@ -460,7 +553,7 @@ At least one of `sign` or `decrypt` must be true. `decrypt: true` with `type: 'e
|
|
|
460
553
|
|
|
461
554
|
**Use for:** Chain-of-trust to manufacturer EK cert in attestation verification.
|
|
462
555
|
|
|
463
|
-
**Not for:**
|
|
556
|
+
**Not for:** Attestation quotes (use `attest.provisionAk`). For general NV access use [`tpm.nv.read`](#tpmnvreadhandle-offset-size-auth).
|
|
464
557
|
|
|
465
558
|
---
|
|
466
559
|
|
|
@@ -559,7 +652,13 @@ Returned by `tpm.keys.create()` and `tpm.keys.load()`.
|
|
|
559
652
|
|
|
560
653
|
### `key.decrypt(cipher)`
|
|
561
654
|
|
|
562
|
-
**
|
|
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`).
|
|
563
662
|
|
|
564
663
|
---
|
|
565
664
|
|
|
@@ -727,7 +826,7 @@ Native Rust errors serialize as `__tpm2__code|message|suggestion|tpmRc|hresult`
|
|
|
727
826
|
| `ACCESS_DENIED` | OS denied device access (Linux permissions, container) |
|
|
728
827
|
| `REQUIRES_ELEVATION` | Windows Admin/SYSTEM required (machine AK, activation) |
|
|
729
828
|
| `COMMAND_BLOCKED` | Windows TBS driver blocked command ordinal |
|
|
730
|
-
| `NOT_SUPPORTED` |
|
|
829
|
+
| `NOT_SUPPORTED` | PCP/TBS capability gap on this platform | — | sometimes | — |
|
|
731
830
|
| `INVALID_ARGUMENT` | Bad options (wrong digest size, invalid key type, empty `keyName`) |
|
|
732
831
|
| `KEY_NOT_FOUND` | NCrypt persisted key missing |
|
|
733
832
|
| `ALREADY_EXISTS` | NCrypt key name collision (`overwrite: false`) |
|
|
@@ -762,32 +861,32 @@ TPM response codes map by class: auth → `AUTH_FAILED`, format → `MARSHALLING
|
|
|
762
861
|
| API | Linux user | Windows user | Windows Admin/SYSTEM |
|
|
763
862
|
|-----|:----------:|:------------:|:--------------------:|
|
|
764
863
|
| `Tpm.isAvailable()`, `open()`, `info()` | ✓ | ✓ | ✓ |
|
|
765
|
-
| `tpm.random.bytes`, `tpm.pcr.read
|
|
766
|
-
| `tpm.
|
|
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` | ✓ | ✓ | ✓ |
|
|
767
870
|
| `tpm.attest.provisionAk()` user scope | ✓ | ✓ | ✓ |
|
|
768
871
|
| `tpm.attest.provisionAk({ scope: 'machine' })` | — | ✗ | ✓ |
|
|
769
872
|
| `ak.quote` / `Tpm.quote` | ✓ | ✓ | ✓ |
|
|
770
873
|
| `ak.activateCredential` | ✓ | ✗ | ✓ |
|
|
771
|
-
| `tpm.pcr.extend` | ✓* | ✓* | ✓ |
|
|
772
|
-
| `tpm.nv.*` (planned) | ✓* | ✓* | ✓ |
|
|
773
874
|
|
|
774
|
-
|
|
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).
|
|
775
876
|
|
|
776
|
-
|
|
877
|
+
‡ **`nv.read/write`:** Index permissions vary; EK cert indices are read-only. Writes to undefined indices fail at the TPM.
|
|
777
878
|
|
|
778
|
-
|
|
879
|
+
§ **`nv.define/undefine`:** Owner authorization required; owner NV range only. Destructive on NV space.
|
|
779
880
|
|
|
780
|
-
|
|
881
|
+
Linux requires read/write on `/dev/tpmrm0`. Windows fleet pattern: provision machine AK once elevated → standard users quote forever.
|
|
781
882
|
|
|
782
|
-
|
|
883
|
+
---
|
|
783
884
|
|
|
784
|
-
|
|
785
|
-
|--------|-------|-------|
|
|
786
|
-
| `tpm.nv.read/write` | 4 | General NV; EK cert uses internal path today |
|
|
787
|
-
| `tpm.seal.seal` / `tpm.seal.unseal` | 5 | PCR-bound sealed storage |
|
|
788
|
-
| `key.decrypt(cipher)` | 2+ | RSA OAEP for decrypt keys |
|
|
885
|
+
## Deferred / not in public API
|
|
789
886
|
|
|
790
|
-
|
|
887
|
+
| Feature | Notes |
|
|
888
|
+
|---------|-------|
|
|
889
|
+
| *(none — all planned namespaces are implemented)* | See [roadmap](./roadmap.md) for hardening / polish |
|
|
791
890
|
|
|
792
891
|
---
|
|
793
892
|
|
|
@@ -800,12 +899,16 @@ Roadmap detail: [roadmap.md](./roadmap.md).
|
|
|
800
899
|
| Random | `tpm.random.bytes(n)` | `Tpm.randomBytes(n)` | `Buffer` |
|
|
801
900
|
| PCR read | `tpm.pcr.read(sel, bank?)` | `Tpm.pcrRead(sel, bank?)` | `Record<number, string>` |
|
|
802
901
|
| PCR extend | `tpm.pcr.extend(i, d)` | `Tpm.pcrExtend(i, d)` | `void` |
|
|
803
|
-
| NV | `tpm.nv.read
|
|
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` |
|
|
804
907
|
| Create key | `tpm.keys.create(opts)` | `Tpm.createKey(opts)` | `KeyHandle` / `{ publicKeyDer, keyBlob }` |
|
|
805
908
|
| Load key | `tpm.keys.load(blob)` | — | `KeyHandle` |
|
|
806
909
|
| Sign | `key.sign(digest)` | `Tpm.signKeyBlob({ keyBlob, digest })` | `Buffer` |
|
|
807
|
-
| Decrypt | `key.decrypt(cipher)` |
|
|
808
|
-
| Seal | `tpm.seal.seal/unseal` |
|
|
910
|
+
| Decrypt | `key.decrypt(cipher)` | `Tpm.decryptKeyBlob({ keyBlob, cipher })` | `Buffer` |
|
|
911
|
+
| Seal | `tpm.seal.seal/unseal` | `Tpm.seal` / `Tpm.unseal` | `Buffer` |
|
|
809
912
|
| EK cert | `tpm.attest.ekCertificate()` | `Tpm.readEkCertificate()` | `Buffer \| null` |
|
|
810
913
|
| Provision AK | `tpm.attest.provisionAk(opts)` | `Tpm.provisionAk(opts)` | `AkHandle` / `{ akPublicDer, akBlob }` |
|
|
811
914
|
| Quote | `ak.quote(opts)` / `tpm.attest.quote(opts)` | `Tpm.quote({ akBlob, ... })` | `QuoteResult` |
|
|
@@ -886,17 +989,28 @@ All public exports from `'node-tpm2'`:
|
|
|
886
989
|
| `Tpm.activateCredential` | function | Implemented |
|
|
887
990
|
| `Tpm.createKey` | function | Implemented |
|
|
888
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 |
|
|
889
1000
|
| `TpmHandle.info` | method | Implemented |
|
|
890
1001
|
| `TpmHandle.readPublic` | method | Implemented |
|
|
891
1002
|
| `TpmHandle.pcr.read` | method | Implemented |
|
|
892
1003
|
| `TpmHandle.pcr.extend` | method | Implemented |
|
|
893
1004
|
| `TpmHandle.random.bytes` | method | Implemented |
|
|
894
|
-
| `TpmHandle.nv.read` | method |
|
|
895
|
-
| `TpmHandle.nv.write` | method |
|
|
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 |
|
|
896
1010
|
| `TpmHandle.keys.create` | method | Implemented |
|
|
897
1011
|
| `TpmHandle.keys.load` | method | Implemented |
|
|
898
|
-
| `TpmHandle.seal.seal` | method |
|
|
899
|
-
| `TpmHandle.seal.unseal` | method |
|
|
1012
|
+
| `TpmHandle.seal.seal` | method | Implemented |
|
|
1013
|
+
| `TpmHandle.seal.unseal` | method | Implemented |
|
|
900
1014
|
| `TpmHandle.attest.ekCertificate` | method | Implemented |
|
|
901
1015
|
| `TpmHandle.attest.provisionAk` | method | Implemented |
|
|
902
1016
|
| `TpmHandle.attest.quote` | method | Implemented |
|
|
@@ -904,7 +1018,7 @@ All public exports from `'node-tpm2'`:
|
|
|
904
1018
|
| `KeyHandle.export` | method | Implemented |
|
|
905
1019
|
| `KeyHandle.publicKeyDer` | getter | Implemented |
|
|
906
1020
|
| `KeyHandle.sign` | method | Implemented |
|
|
907
|
-
| `KeyHandle.decrypt` | method |
|
|
1021
|
+
| `KeyHandle.decrypt` | method | Implemented |
|
|
908
1022
|
| `AkHandle.export` | method | Implemented |
|
|
909
1023
|
| `AkHandle.publicKeyDer` | getter | Implemented |
|
|
910
1024
|
| `AkHandle.quote` | method | Implemented |
|