node-tpm2 0.0.4-beta.4 → 0.0.5-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -17
- package/api.js +135 -9
- package/docs/api-reference.md +151 -35
- package/docs/roadmap.md +28 -32
- package/docs/windows-pcp.md +3 -0
- package/examples/nv-smoke.mjs +89 -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,15 @@ 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. Windows standard user → **`REQUIRES_ELEVATION`** (same TBS block as `pcr.extend`); run Admin PowerShell.
|
|
227
|
+
|
|
228
|
+
**Note:** On Windows, raw TBS may reject `nv.readPublic` for owner-range indices (`TPM_RC` ~`0xA6`) even when define/read/write succeed. Factory indices (`0x01c00002` EK cert) work. After `nv.define`, use the known size for read/write; `examples/nv-smoke.mjs` handles this.
|
|
229
|
+
|
|
230
|
+
**† `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
231
|
|
|
222
232
|
---
|
|
223
233
|
|
|
@@ -268,7 +278,31 @@ const reloaded = await tpm.keys.load(saved);
|
|
|
268
278
|
await reloaded.sign(digest);
|
|
269
279
|
```
|
|
270
280
|
|
|
271
|
-
|
|
281
|
+
const rsaKey = await tpm.keys.create({ type: 'rsa', sign: true, decrypt: true });
|
|
282
|
+
const plain = await rsaKey.decrypt(ciphertext);
|
|
283
|
+
|
|
284
|
+
Flat: `Tpm.createKey()`, `Tpm.signKeyBlob({ keyBlob, digest })`, `Tpm.decryptKeyBlob({ keyBlob, cipher })`.
|
|
285
|
+
|
|
286
|
+
### NV
|
|
287
|
+
|
|
288
|
+
```javascript
|
|
289
|
+
await tpm.nv.read('0x01c00002'); // EK cert index (read-only on most hardware)
|
|
290
|
+
await tpm.nv.readPublic('0x01800042'); // metadata before read/write
|
|
291
|
+
await tpm.nv.define({ handle: '0x01800042', size: 64 }); // owner NV — test machines only
|
|
292
|
+
await tpm.nv.write('0x01800042', data, 0);
|
|
293
|
+
await tpm.nv.undefine('0x01800042');
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Flat: `Tpm.nvRead`, `Tpm.nvWrite`, `Tpm.nvReadPublic`, `Tpm.nvDefine`, `Tpm.nvUndefine`. See `examples/nv-smoke.mjs`.
|
|
297
|
+
|
|
298
|
+
### Seal
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
const sealed = await tpm.seal.seal({ data: secret, pcrSelection: [23] });
|
|
302
|
+
const plain = await tpm.seal.unseal(sealed);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
Flat: `Tpm.seal`, `Tpm.unseal`.
|
|
272
306
|
|
|
273
307
|
### Attestation
|
|
274
308
|
|
|
@@ -347,8 +381,8 @@ try {
|
|
|
347
381
|
|------|------|:-------:|:---------:|----------------------|
|
|
348
382
|
| `TPM_UNAVAILABLE` | No TPM, no native binary, macOS, or backend not built | — | — | Install platform package / check TPM |
|
|
349
383
|
| `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
|
|
351
|
-
| `COMMAND_BLOCKED` | Windows TBS
|
|
384
|
+
| `REQUIRES_ELEVATION` | Windows operation needs Admin/SYSTEM | — | ✓ | Re-run elevated; **`pcr.extend`** and **`nv.define`/`nv.undefine`** from standard user |
|
|
385
|
+
| `COMMAND_BLOCKED` | Windows TBS blocked raw ordinal (e.g. ActivateCredential) | ✓ | — | Use NCrypt PCP — elevation does not help |
|
|
352
386
|
| `NOT_SUPPORTED` | Feature or PCP capability missing on this platform | — | sometimes | — |
|
|
353
387
|
| `INVALID_ARGUMENT` | Bad JS/Rust option (e.g. empty machine `keyName`) | — | sometimes | Fix caller input |
|
|
354
388
|
| `KEY_NOT_FOUND` | NCrypt key / blob locator not found | — | ✓ | Check persisted blob / key name |
|
|
@@ -367,7 +401,9 @@ When the TPM returns a non-zero response code, the library classifies it:
|
|
|
367
401
|
| Success | `rc === 0` | (no error) | `0` |
|
|
368
402
|
| Auth | `(rc & 0x0300) === 0x0300` | `AUTH_FAILED` | `0x38E` (`TPM_RC_AUTH_FAIL`) |
|
|
369
403
|
| Format | `(rc & 0xFF00) === 0x0100` or FMT1 bit set | `MARSHALLING_ERROR` | `0x125` (`TPM_RC_ASYMMETRIC`) |
|
|
370
|
-
| Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` | `0x80280400` |
|
|
404
|
+
| Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` * | `0x80280400` |
|
|
405
|
+
|
|
406
|
+
\* **`PCR_Extend`:** mapped to **`REQUIRES_ELEVATION`** (same `hresult` `0x80280400`) — Administrator can extend on Windows client; standard user should re-run elevated.
|
|
371
407
|
| Other | everything else | `TPM_RC` | vendor-specific |
|
|
372
408
|
|
|
373
409
|
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 +430,17 @@ PCP / NCrypt failures on Windows map through `classify_ncrypt` (`src/tbs/ncrypt.
|
|
|
394
430
|
|
|
395
431
|
---
|
|
396
432
|
|
|
397
|
-
## API reference
|
|
433
|
+
## API reference
|
|
398
434
|
|
|
399
|
-
Subsystem namespaces
|
|
435
|
+
Subsystem namespaces on `TpmHandle`. See [docs/api-reference.md](./docs/api-reference.md) for full detail.
|
|
400
436
|
|
|
401
437
|
| Namespace | Methods |
|
|
402
438
|
|-----------|---------|
|
|
403
439
|
| `tpm.random` | `bytes(n)` ✅ |
|
|
404
|
-
| `tpm.keys` | `create`, `load`, `KeyHandle.sign
|
|
405
|
-
| `tpm.pcr` | `
|
|
406
|
-
| `tpm.
|
|
440
|
+
| `tpm.keys` | `create`, `load`, `KeyHandle.sign`, `KeyHandle.decrypt` ✅ |
|
|
441
|
+
| `tpm.pcr` | `read`, `extend` ✅ |
|
|
442
|
+
| `tpm.nv` | `read`, `write`, `readPublic`, `define`, `undefine` ✅ |
|
|
443
|
+
| `tpm.seal` | `seal`, `unseal` ✅ |
|
|
407
444
|
|
|
408
445
|
---
|
|
409
446
|
|
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,81 @@ 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 }>`
|
|
394
425
|
|
|
395
|
-
**
|
|
426
|
+
**Implemented.** Returns NV index metadata from `NV_ReadPublic` without reading data.
|
|
427
|
+
|
|
428
|
+
**Windows caveat:** Raw TBS often rejects this command for **owner-range** indices (`0x01800000`–`0x01BFFFFF`) with `MARSHALLING_ERROR` / `TPM_RC` ~`0xA6`, even when the index exists. Factory indices (e.g. EK cert `0x01c00002`) work. After [`tpm.nv.define`](#tpmnvdefineopts-promisevoid), use the known `size` for read/write — the library falls back to owner auth automatically.
|
|
429
|
+
|
|
430
|
+
**Flat equivalent:** [`Tpm.nvReadPublic(handle)`](#tpm-nvreadpublic).
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
### `tpm.nv.define(opts): Promise<void>`
|
|
435
|
+
|
|
436
|
+
**Implemented.** Creates an owner NV index (`TPM2_NV_DefineSpace`).
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
type NvDefineOptions = {
|
|
440
|
+
handle: string | number; // 0x01800000..0x01BFFFFF
|
|
441
|
+
size: number; // 1..65535 bytes
|
|
442
|
+
auth?: Buffer; // index password (if using AUTH* attributes)
|
|
443
|
+
ownerAuth?: Buffer; // owner hierarchy password (often empty)
|
|
444
|
+
};
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
**Default attributes:** `OWNERREAD | OWNERWRITE | NO_DA` — read/write via owner auth on `TPM_RH_OWNER`.
|
|
448
|
+
|
|
449
|
+
**Destructive / privileged:** Consumes TPM NV space until [`tpm.nv.undefine`](#tpmnvundefinehandle-ownerauth). Refuses EK cert indices. **Not for production laptops without intent.** Windows standard user → **`REQUIRES_ELEVATION`** (re-run Admin PowerShell).
|
|
450
|
+
|
|
451
|
+
**Flat equivalent:** [`Tpm.nvDefine(opts)`](#tpm-nvdefine).
|
|
396
452
|
|
|
397
453
|
---
|
|
398
454
|
|
|
399
|
-
### `tpm.nv.
|
|
455
|
+
### `tpm.nv.undefine(handle, ownerAuth?): Promise<void>`
|
|
456
|
+
|
|
457
|
+
**Implemented.** Deletes an owner NV index (`TPM2_NV_UndefineSpace`).
|
|
400
458
|
|
|
401
|
-
**
|
|
459
|
+
**Flat equivalent:** [`Tpm.nvUndefine(handle, ownerAuth?)`](#tpm-nvundefine).
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
### `tpm.nv.write(handle, data, offset?, auth?)`
|
|
464
|
+
|
|
465
|
+
**Implemented.**
|
|
466
|
+
|
|
467
|
+
**Under the hood:** `NV_ReadPublic` bounds check → `NV_Write` with owner or index auth (`TPMA_NV_PPWRITE` / `TPMA_NV_AUTHWRITE`).
|
|
468
|
+
|
|
469
|
+
**Caveats:** Most factory NV indices are read-only. User-defined indices must be created with [`tpm.nv.define`](#tpmnvdefineopts-promisevoid) first.
|
|
470
|
+
|
|
471
|
+
**Flat equivalent:** [`Tpm.nvWrite(handle, data, offset?, auth?)`](#tpm-nvwrite).
|
|
402
472
|
|
|
403
473
|
---
|
|
404
474
|
|
|
@@ -410,7 +480,7 @@ Returned by `Tpm.open()`. All sub-namespaces are plain objects with async method
|
|
|
410
480
|
type KeyCreateOptions = {
|
|
411
481
|
type: 'ecc' | 'rsa';
|
|
412
482
|
sign?: boolean; // default true
|
|
413
|
-
decrypt?: boolean; // default false; RSA only
|
|
483
|
+
decrypt?: boolean; // default false; RSA only
|
|
414
484
|
};
|
|
415
485
|
```
|
|
416
486
|
|
|
@@ -448,7 +518,32 @@ At least one of `sign` or `decrypt` must be true. `decrypt: true` with `type: 'e
|
|
|
448
518
|
|
|
449
519
|
### `tpm.seal.seal(opts)` / `tpm.seal.unseal(blob)`
|
|
450
520
|
|
|
451
|
-
**
|
|
521
|
+
**Implemented.**
|
|
522
|
+
|
|
523
|
+
```typescript
|
|
524
|
+
type SealOptions = {
|
|
525
|
+
data: Buffer;
|
|
526
|
+
pcrSelection?: number[]; // SHA-256 bank; binds to current PCR values at seal time
|
|
527
|
+
};
|
|
528
|
+
|
|
529
|
+
const sealed = await tpm.seal.seal({ data: secret });
|
|
530
|
+
const plain = await tpm.seal.unseal(sealed);
|
|
531
|
+
|
|
532
|
+
// PCR-bound: unseal fails if PCR state changes
|
|
533
|
+
const bound = await tpm.seal.seal({ data: secret, pcrSelection: [7] });
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
**Under the hood:**
|
|
537
|
+
|
|
538
|
+
1. `CreatePrimary` — transient storage primary.
|
|
539
|
+
2. Optional `PolicyPCR` session when `pcrSelection` is set; policy digest embedded in sealed object.
|
|
540
|
+
3. `Create` — keyedhash sealed object (`fixedTPM | fixedParent | userWithAuth | noDA`).
|
|
541
|
+
4. Export `SEAL` wire blob (public + private + PCR metadata).
|
|
542
|
+
5. `unseal`: load + `Unseal` (with matching `PolicyPCR` when bound).
|
|
543
|
+
|
|
544
|
+
**Caveats:** PCR-bound seal requires the chosen PCRs to match at unseal time. `tpm.pcr.extend` on Windows needs elevation for many indices.
|
|
545
|
+
|
|
546
|
+
**Flat equivalents:** [`Tpm.seal(opts)`](#tpm-sealopts), [`Tpm.unseal(blob)`](#tpm-unsealblob).
|
|
452
547
|
|
|
453
548
|
---
|
|
454
549
|
|
|
@@ -460,7 +555,7 @@ At least one of `sign` or `decrypt` must be true. `decrypt: true` with `type: 'e
|
|
|
460
555
|
|
|
461
556
|
**Use for:** Chain-of-trust to manufacturer EK cert in attestation verification.
|
|
462
557
|
|
|
463
|
-
**Not for:**
|
|
558
|
+
**Not for:** Attestation quotes (use `attest.provisionAk`). For general NV access use [`tpm.nv.read`](#tpmnvreadhandle-offset-size-auth).
|
|
464
559
|
|
|
465
560
|
---
|
|
466
561
|
|
|
@@ -559,7 +654,13 @@ Returned by `tpm.keys.create()` and `tpm.keys.load()`.
|
|
|
559
654
|
|
|
560
655
|
### `key.decrypt(cipher)`
|
|
561
656
|
|
|
562
|
-
**
|
|
657
|
+
**Implemented.** RSA OAEP (SHA-256) via `TPM2_RSA_Decrypt`. Key must have been created with `decrypt: true` (RSA only).
|
|
658
|
+
|
|
659
|
+
**Under the hood:** Regenerate storage primary → `Load` → `RSA_Decrypt` with explicit OAEP scheme → flush.
|
|
660
|
+
|
|
661
|
+
**Use for:** Decrypting ciphertext produced for the TPM RSA key's public half.
|
|
662
|
+
|
|
663
|
+
**Not for:** ECC keys or sign-only RSA keys (`INVALID_ARGUMENT`).
|
|
563
664
|
|
|
564
665
|
---
|
|
565
666
|
|
|
@@ -727,7 +828,7 @@ Native Rust errors serialize as `__tpm2__code|message|suggestion|tpmRc|hresult`
|
|
|
727
828
|
| `ACCESS_DENIED` | OS denied device access (Linux permissions, container) |
|
|
728
829
|
| `REQUIRES_ELEVATION` | Windows Admin/SYSTEM required (machine AK, activation) |
|
|
729
830
|
| `COMMAND_BLOCKED` | Windows TBS driver blocked command ordinal |
|
|
730
|
-
| `NOT_SUPPORTED` |
|
|
831
|
+
| `NOT_SUPPORTED` | PCP/TBS capability gap on this platform | — | sometimes | — |
|
|
731
832
|
| `INVALID_ARGUMENT` | Bad options (wrong digest size, invalid key type, empty `keyName`) |
|
|
732
833
|
| `KEY_NOT_FOUND` | NCrypt persisted key missing |
|
|
733
834
|
| `ALREADY_EXISTS` | NCrypt key name collision (`overwrite: false`) |
|
|
@@ -762,32 +863,32 @@ TPM response codes map by class: auth → `AUTH_FAILED`, format → `MARSHALLING
|
|
|
762
863
|
| API | Linux user | Windows user | Windows Admin/SYSTEM |
|
|
763
864
|
|-----|:----------:|:------------:|:--------------------:|
|
|
764
865
|
| `Tpm.isAvailable()`, `open()`, `info()` | ✓ | ✓ | ✓ |
|
|
765
|
-
| `tpm.random.bytes`, `tpm.pcr.read
|
|
766
|
-
| `tpm.
|
|
866
|
+
| `tpm.random.bytes`, `tpm.pcr.read` | ✓ | ✓ | ✓ |
|
|
867
|
+
| `tpm.pcr.extend` | ✓ † | ✗ → `REQUIRES_ELEVATION` | ✓ † |
|
|
868
|
+
| `tpm.nv.read` / `tpm.nv.write` | ✓ ‡ | ✓ ‡ | ✓ |
|
|
869
|
+
| `tpm.nv.define` / `tpm.nv.undefine` | ✓ § | ✓ § | ✓ § |
|
|
870
|
+
| `tpm.keys.create/load`, `key.sign`, `key.decrypt` | ✓ | ✓ | ✓ |
|
|
871
|
+
| `tpm.seal.seal` / `tpm.seal.unseal` | ✓ | ✓ | ✓ |
|
|
767
872
|
| `tpm.attest.provisionAk()` user scope | ✓ | ✓ | ✓ |
|
|
768
873
|
| `tpm.attest.provisionAk({ scope: 'machine' })` | — | ✗ | ✓ |
|
|
769
874
|
| `ak.quote` / `Tpm.quote` | ✓ | ✓ | ✓ |
|
|
770
875
|
| `ak.activateCredential` | ✓ | ✗ | ✓ |
|
|
771
|
-
| `tpm.pcr.extend` | ✓* | ✓* | ✓ |
|
|
772
|
-
| `tpm.nv.*` (planned) | ✓* | ✓* | ✓ |
|
|
773
876
|
|
|
774
|
-
|
|
877
|
+
† **`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
878
|
|
|
776
|
-
|
|
879
|
+
‡ **`nv.read/write`:** Index permissions vary; EK cert indices are read-only. Writes to undefined indices fail at the TPM.
|
|
777
880
|
|
|
778
|
-
|
|
881
|
+
§ **`nv.define/undefine`:** Owner authorization required; owner NV range only. Destructive on NV space. Windows standard user → **`REQUIRES_ELEVATION`**.
|
|
779
882
|
|
|
780
|
-
|
|
883
|
+
Linux requires read/write on `/dev/tpmrm0`. Windows fleet pattern: provision machine AK once elevated → standard users quote forever.
|
|
781
884
|
|
|
782
|
-
|
|
885
|
+
---
|
|
783
886
|
|
|
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 |
|
|
887
|
+
## Deferred / not in public API
|
|
789
888
|
|
|
790
|
-
|
|
889
|
+
| Feature | Notes |
|
|
890
|
+
|---------|-------|
|
|
891
|
+
| *(none — all planned namespaces are implemented)* | See [roadmap](./roadmap.md) for hardening / polish |
|
|
791
892
|
|
|
792
893
|
---
|
|
793
894
|
|
|
@@ -800,12 +901,16 @@ Roadmap detail: [roadmap.md](./roadmap.md).
|
|
|
800
901
|
| Random | `tpm.random.bytes(n)` | `Tpm.randomBytes(n)` | `Buffer` |
|
|
801
902
|
| PCR read | `tpm.pcr.read(sel, bank?)` | `Tpm.pcrRead(sel, bank?)` | `Record<number, string>` |
|
|
802
903
|
| PCR extend | `tpm.pcr.extend(i, d)` | `Tpm.pcrExtend(i, d)` | `void` |
|
|
803
|
-
| NV | `tpm.nv.read
|
|
904
|
+
| NV read | `tpm.nv.read(h, off?, sz?, auth?)` | `Tpm.nvRead(...)` | `Buffer` |
|
|
905
|
+
| NV write | `tpm.nv.write(h, data, off?, auth?)` | `Tpm.nvWrite(...)` | `void` |
|
|
906
|
+
| NV readPublic | `tpm.nv.readPublic(h)` | `Tpm.nvReadPublic(h)` | `{ dataSize, attributes }` |
|
|
907
|
+
| NV define | `tpm.nv.define(opts)` | `Tpm.nvDefine(opts)` | `void` |
|
|
908
|
+
| NV undefine | `tpm.nv.undefine(h, ownerAuth?)` | `Tpm.nvUndefine(...)` | `void` |
|
|
804
909
|
| Create key | `tpm.keys.create(opts)` | `Tpm.createKey(opts)` | `KeyHandle` / `{ publicKeyDer, keyBlob }` |
|
|
805
910
|
| Load key | `tpm.keys.load(blob)` | — | `KeyHandle` |
|
|
806
911
|
| Sign | `key.sign(digest)` | `Tpm.signKeyBlob({ keyBlob, digest })` | `Buffer` |
|
|
807
|
-
| Decrypt | `key.decrypt(cipher)` |
|
|
808
|
-
| Seal | `tpm.seal.seal/unseal` |
|
|
912
|
+
| Decrypt | `key.decrypt(cipher)` | `Tpm.decryptKeyBlob({ keyBlob, cipher })` | `Buffer` |
|
|
913
|
+
| Seal | `tpm.seal.seal/unseal` | `Tpm.seal` / `Tpm.unseal` | `Buffer` |
|
|
809
914
|
| EK cert | `tpm.attest.ekCertificate()` | `Tpm.readEkCertificate()` | `Buffer \| null` |
|
|
810
915
|
| Provision AK | `tpm.attest.provisionAk(opts)` | `Tpm.provisionAk(opts)` | `AkHandle` / `{ akPublicDer, akBlob }` |
|
|
811
916
|
| Quote | `ak.quote(opts)` / `tpm.attest.quote(opts)` | `Tpm.quote({ akBlob, ... })` | `QuoteResult` |
|
|
@@ -886,17 +991,28 @@ All public exports from `'node-tpm2'`:
|
|
|
886
991
|
| `Tpm.activateCredential` | function | Implemented |
|
|
887
992
|
| `Tpm.createKey` | function | Implemented |
|
|
888
993
|
| `Tpm.signKeyBlob` | function | Implemented |
|
|
994
|
+
| `Tpm.decryptKeyBlob` | function | Implemented |
|
|
995
|
+
| `Tpm.nvRead` | function | Implemented |
|
|
996
|
+
| `Tpm.nvWrite` | function | Implemented |
|
|
997
|
+
| `Tpm.nvReadPublic` | function | Implemented |
|
|
998
|
+
| `Tpm.nvDefine` | function | Implemented |
|
|
999
|
+
| `Tpm.nvUndefine` | function | Implemented |
|
|
1000
|
+
| `Tpm.seal` | function | Implemented |
|
|
1001
|
+
| `Tpm.unseal` | function | Implemented |
|
|
889
1002
|
| `TpmHandle.info` | method | Implemented |
|
|
890
1003
|
| `TpmHandle.readPublic` | method | Implemented |
|
|
891
1004
|
| `TpmHandle.pcr.read` | method | Implemented |
|
|
892
1005
|
| `TpmHandle.pcr.extend` | method | Implemented |
|
|
893
1006
|
| `TpmHandle.random.bytes` | method | Implemented |
|
|
894
|
-
| `TpmHandle.nv.read` | method |
|
|
895
|
-
| `TpmHandle.nv.write` | method |
|
|
1007
|
+
| `TpmHandle.nv.read` | method | Implemented |
|
|
1008
|
+
| `TpmHandle.nv.write` | method | Implemented |
|
|
1009
|
+
| `TpmHandle.nv.readPublic` | method | Implemented |
|
|
1010
|
+
| `TpmHandle.nv.define` | method | Implemented |
|
|
1011
|
+
| `TpmHandle.nv.undefine` | method | Implemented |
|
|
896
1012
|
| `TpmHandle.keys.create` | method | Implemented |
|
|
897
1013
|
| `TpmHandle.keys.load` | method | Implemented |
|
|
898
|
-
| `TpmHandle.seal.seal` | method |
|
|
899
|
-
| `TpmHandle.seal.unseal` | method |
|
|
1014
|
+
| `TpmHandle.seal.seal` | method | Implemented |
|
|
1015
|
+
| `TpmHandle.seal.unseal` | method | Implemented |
|
|
900
1016
|
| `TpmHandle.attest.ekCertificate` | method | Implemented |
|
|
901
1017
|
| `TpmHandle.attest.provisionAk` | method | Implemented |
|
|
902
1018
|
| `TpmHandle.attest.quote` | method | Implemented |
|
|
@@ -904,7 +1020,7 @@ All public exports from `'node-tpm2'`:
|
|
|
904
1020
|
| `KeyHandle.export` | method | Implemented |
|
|
905
1021
|
| `KeyHandle.publicKeyDer` | getter | Implemented |
|
|
906
1022
|
| `KeyHandle.sign` | method | Implemented |
|
|
907
|
-
| `KeyHandle.decrypt` | method |
|
|
1023
|
+
| `KeyHandle.decrypt` | method | Implemented |
|
|
908
1024
|
| `AkHandle.export` | method | Implemented |
|
|
909
1025
|
| `AkHandle.publicKeyDer` | getter | Implemented |
|
|
910
1026
|
| `AkHandle.quote` | method | Implemented |
|