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 CHANGED
@@ -1,164 +1,472 @@
1
1
  # node-tpm2
2
2
 
3
- **TPM 2.0 attestation for Node.js** prebuilt native bindings, no `tpm2-tools`, no `tpm2-tss`, no Rust toolchain at install time.
3
+ Native TPM 2.0 for Node.js. Prebuilt binaries no `tpm2-tools`, no `tpm2-tss`, no Rust at install time.
4
4
 
5
- Use the TPM your OS already exposes: **TBS + Platform Crypto Provider** on Windows, **`/dev/tpmrm0`** on Linux. One small API for PCR reads, attestation key provisioning, quotes, and credential activation.
5
+ Talks to the TPM through OS-native paths: **TBS + Platform Crypto Provider** on Windows, **`/dev/tpmrm0`** on Linux. Returns buffers and typed records, not CLI text.
6
6
 
7
7
  ```javascript
8
8
  import { Tpm } from 'node-tpm2';
9
9
 
10
- const tpm = await Tpm.open();
10
+ if (!(await Tpm.isAvailable())) throw new Error('No TPM');
11
11
 
12
- const { akPublicDer, akBlob } = await tpm.attest.provisionAk();
13
- const quote = await tpm.attest.quote({
12
+ await using tpm = await Tpm.open();
13
+
14
+ const ak = await tpm.attest.provisionAk();
15
+ const { message, signature } = await ak.quote({
16
+ pcrSelection: [0, 1, 7],
17
+ qualifyingData: Buffer.from('challenge-nonce'),
18
+ });
19
+ ```
20
+
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
+
23
+ ---
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install node-tpm2
29
+ ```
30
+
31
+ Node **20+**. One prebuilt `.node` per platform via optional dependencies.
32
+
33
+ ---
34
+
35
+ ## Examples
36
+
37
+ ### Check the TPM
38
+
39
+ ```javascript
40
+ import { Tpm } from 'node-tpm2';
41
+
42
+ if (!(await Tpm.isAvailable())) {
43
+ console.log('No TPM or no access');
44
+ process.exit(1);
45
+ }
46
+
47
+ const info = await Tpm.info();
48
+ console.log(info.manufacturer, info.firmwareVersion, info.isVirtual ? '(virtual)' : '');
49
+ ```
50
+
51
+ ### Device attestation (dev / same-user)
52
+
53
+ Provision a user-scoped attestation key, quote PCRs bound to a server challenge, send `message` + `signature` + `akPublicDer` to your verifier.
54
+
55
+ ```javascript
56
+ import { Tpm } from 'node-tpm2';
57
+ import { writeFileSync } from 'node:fs';
58
+
59
+ const challenge = Buffer.from('server-issued-nonce-or-session-id');
60
+
61
+ const { akPublicDer, akBlob } = await Tpm.provisionAk();
62
+ writeFileSync('ak.blob.json', JSON.stringify({
63
+ public: akBlob.public.toString('base64'),
64
+ private: akBlob.private.toString('base64'),
65
+ }));
66
+
67
+ const { message, signature } = await Tpm.quote({
14
68
  akBlob,
15
69
  pcrSelection: [0, 1, 7],
16
- qualifyingData: Buffer.from('your-challenge-nonce'),
70
+ qualifyingData: challenge,
71
+ });
72
+
73
+ // → POST { akPublicDer, message, signature, pcrSelection } to your backend
74
+ ```
75
+
76
+ ### Handle style (grouped API)
77
+
78
+ ```javascript
79
+ import { Tpm } from 'node-tpm2';
80
+
81
+ await using tpm = await Tpm.open();
82
+
83
+ const pcrs = await tpm.pcr.read([0, 1, 7]);
84
+ const ekCert = await tpm.attest.ekCertificate(); // Buffer | null
85
+
86
+ const ak = await tpm.attest.provisionAk();
87
+ const quote = await ak.quote({
88
+ pcrSelection: [0, 1, 7],
89
+ qualifyingData: Buffer.from('challenge'),
17
90
  });
18
91
 
19
- // quote.message + quote.signature send to your verifier
92
+ const saved = ak.export(); // persist { public, private } for next session
20
93
  ```
21
94
 
22
- ## Why node-tpm2
95
+ ### Windows fleet enrollment
23
96
 
24
- | | node-tpm2 | Shelling out to tpm2-tools |
25
- |---|-----------|----------------------------|
26
- | Install | `npm install` + prebuilt `.node` | OS packages, PATH, version drift |
27
- | API | Async JavaScript, structured errors | Parse CLI output |
28
- | Windows fleet | Machine-scoped PCP keys, cross-user quote | PCP/NCrypt scripting pain |
29
- | AK persistence | Wrapped blob (`akBlob`) — no persistent TPM handles in your app | Handle bookkeeping |
97
+ **Threat model:** A machine AK proves **this enrolled device**, not which app or user quoted. The blob is a locator (`keyName`), not a secret — see [Threat model in windows-pcp.md](./docs/windows-pcp.md#threat-model-device-vs-application).
30
98
 
31
- **Use it when you need:**
99
+ **Once** at install time (Admin or SYSTEM): create a machine-scoped key with a stable name and persist the blob.
32
100
 
33
- - **Device attestation** — prove boot/software state via PCR quotes bound to a challenge nonce
34
- - **Fleet enrollment** provision a machine-scoped attestation key at install time (Intune, SCCM, GPO); quote at runtime as the logged-in user
35
- - **Remote verification** export AK public key (SPKI DER) and quote blobs to your backend; verify with standard TPM quote rules
36
- - **EK-backed onboarding** — read the EK certificate, activate credentials during enrollment
101
+ ```javascript
102
+ import { Tpm } from 'node-tpm2';
103
+ import { writeFileSync } from 'node:fs';
37
104
 
38
- ## How it works
105
+ // Run elevated or as SYSTEM — see docs/windows-pcp.md
106
+ const { akPublicDer, akBlob } = await Tpm.provisionAk({
107
+ keyName: 'my-app-device-ak',
108
+ scope: 'machine',
109
+ overwrite: true,
110
+ });
39
111
 
112
+ writeFileSync('C:\\ProgramData\\my-app\\ak.blob.json', JSON.stringify({
113
+ public: akBlob.public.toString('base64'),
114
+ private: akBlob.private.toString('base64'),
115
+ }));
116
+ // Register akPublicDer + creation data with your enrollment service
40
117
  ```
41
- ┌─────────────────────────────────────────────────────────┐
42
- │ Your Node app (Tpm.open / flat helpers) │
43
- └──────────────────────────┬──────────────────────────────┘
44
-
45
- ┌──────────────────────────▼──────────────────────────────┐
46
- │ node-tpm2 (napi-rs) command build, sessions, blobs │
47
- └──────────────┬─────────────────────────┬────────────────┘
48
- │ │
49
- Linux │ │ Windows
50
- ▼ ▼
51
- /dev/tpmrm0 TBS + NCrypt PCP
52
- ECDSA P-256 AK RSA-2048 persisted AK
53
- TPM2B wrapped blob PCP1 (user) / PCP2 (machine)
118
+
119
+ **Every runtime session** (standard user): load the blob and quote — no elevation.
120
+
121
+ ```javascript
122
+ import { Tpm } from 'node-tpm2';
123
+ import { readFileSync } from 'node:fs';
124
+
125
+ const raw = JSON.parse(readFileSync('C:\\ProgramData\\my-app\\ak.blob.json', 'utf8'));
126
+ const akBlob = {
127
+ public: Buffer.from(raw.public, 'base64'),
128
+ private: Buffer.from(raw.private, 'base64'),
129
+ };
130
+
131
+ const quote = await Tpm.quote({
132
+ akBlob,
133
+ pcrSelection: [0, 1, 7],
134
+ qualifyingData: Buffer.from('runtime-challenge'),
135
+ });
54
136
  ```
55
137
 
56
- - **No persistent TPM handles in your process.** You keep an `akBlob` (portable wrapped key material). Each operation loads transiently, signs, and flushes.
57
- - **Platform-native AK formats.** Linux uses ECDSA P-256 TPM2B blobs; Windows uses Microsoft PCP (`PCP1` user / `PCP2` machine). Verifiers should accept both.
58
- - **Structured errors.** Failures throw `TpmError` with stable `code`, optional `suggestion`, `tpmRc` (TPM return code), and `hresult` (Windows NCrypt).
138
+ ### Read PCRs and TPM objects
59
139
 
60
- ## Install
140
+ ```javascript
141
+ import { Tpm } from 'node-tpm2';
61
142
 
62
- ```bash
63
- npm install node-tpm2
143
+ await using tpm = await Tpm.open();
144
+
145
+ const digests = await tpm.pcr.read([0, 1, 7]); // { 0: 'abc…', … }
146
+ const ek = await tpm.readPublic('0x81010001'); // endorsement key
147
+ const { publicKeyDer, name } = ek;
64
148
  ```
65
149
 
66
- Node **20+**. npm installs a prebuilt native binary for your OS/arch via optional platform packages.
150
+ ### Credential activation (enrollment proof-of-possession)
67
151
 
68
- ## API at a glance
152
+ ```javascript
153
+ import { Tpm } from 'node-tpm2';
154
+
155
+ // credentialBlob + secret from your verifier's MakeCredential step
156
+ const recovered = await Tpm.activateCredential({
157
+ akBlob,
158
+ credentialBlob,
159
+ secret,
160
+ });
161
+ // recovered → proves AK is on the TPM that owns the EK
162
+ ```
69
163
 
70
- **Handle style** (grouped operations):
164
+ ### Errors
71
165
 
72
166
  ```javascript
73
- const tpm = await Tpm.open();
167
+ import { Tpm, TpmError } from 'node-tpm2';
74
168
 
75
- await tpm.info(); // manufacturer, firmware, virtual-TPM hint
76
- await tpm.pcr.read([0, 1, 7]); // SHA-256 PCR digests
77
- await tpm.attest.ekCertificate(); // EK cert from NV, or null
169
+ try {
170
+ await Tpm.provisionAk({ scope: 'machine', keyName: 'fleet-ak' });
171
+ } catch (err) {
172
+ if (err instanceof TpmError && err.code === 'REQUIRES_ELEVATION') {
173
+ // Windows: run enrollment elevated or as SYSTEM, not at runtime
174
+ }
175
+ }
176
+ ```
78
177
 
79
- const ak = await tpm.attest.provisionAk(); // returns AkHandle
80
- await ak.quote({ pcrSelection: [7], qualifyingData: nonce });
81
- await ak.export(); // persist akBlob
82
- await ak.activateCredential({ credentialBlob, secret });
178
+ More detail: [getting-started.md](./docs/getting-started.md) · [api-reference.md](./docs/api-reference.md) · [windows-pcp.md](./docs/windows-pcp.md) · [Error reference](#error-reference)
179
+
180
+ ---
83
181
 
84
- await using tpm = await Tpm.open(); // Symbol.asyncDispose when done
182
+ ## Privilege matrix
183
+
184
+ **Legend:** ✓ standard user (with normal TPM access) · ✗ needs elevation · — not applicable · \* policy/firmware may block
185
+
186
+ | API | Linux standard user | Windows standard user | Windows Admin / SYSTEM |
187
+ |-----|:-------------------:|:---------------------:|:----------------------:|
188
+ | **Root** | | | |
189
+ | `Tpm.isAvailable()` | ✓ | ✓ | ✓ |
190
+ | `Tpm.open()` | ✓ | ✓ | ✓ |
191
+ | `tpm.info()` | ✓ | ✓ | ✓ |
192
+ | `tpm.readPublic(handle)` | ✓ | ✓ | ✓ |
193
+ | **random** | | | |
194
+ | `tpm.random.bytes(n)` | ✓ | ✓ | ✓ |
195
+ | **pcr** | | | |
196
+ | `tpm.pcr.read(...)` | ✓ | ✓ | ✓ |
197
+ | `tpm.pcr.extend(i, digest)` | ✓ † | ✗ → `REQUIRES_ELEVATION` | ✓ † |
198
+ | **nv** | | | |
199
+ | `tpm.nv.read(...)` | ✓ ‡ | ✓ ‡ | ✓ |
200
+ | `tpm.nv.write(...)` | ✓ ‡ | ✓ ‡ | ✓ |
201
+ | `tpm.nv.define(...)` | ✓ § | ✓ § | ✓ § |
202
+ | `tpm.nv.undefine(...)` | ✓ § | ✓ § | ✓ § |
203
+ | `tpm.attest.ekCertificate()` | ✓ | ✓ | ✓ |
204
+ | **keys** | | | |
205
+ | `tpm.keys.create(...)` | ✓ | ✓ | ✓ |
206
+ | `tpm.keys.load(blob)` | ✓ | ✓ | ✓ |
207
+ | `key.sign(digest)` | ✓ | ✓ | ✓ |
208
+ | `key.decrypt(cipher)` | ✓ | ✓ | ✓ |
209
+ | **seal** | | | |
210
+ | `tpm.seal.seal(...)` | ✓ | ✓ | ✓ |
211
+ | `tpm.unseal(blob)` | ✓ | ✓ | ✓ |
212
+ | **attest** | | | |
213
+ | `tpm.attest.provisionAk()` user | ✓ | ✓ | ✓ |
214
+ | `tpm.attest.provisionAk({ scope: 'machine' })` | — | ✗ | ✓ |
215
+ | `ak.quote(...)` / `Tpm.quote(...)` | ✓ | ✓ | ✓ |
216
+ | `ak.activateCredential(...)` | ✓ | ✗ | ✓ |
217
+
218
+ **Linux standard user** requires read/write on `/dev/tpmrm0` (commonly the `tss` group). That is a one-time deploy permission, not root for every call.
219
+
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).
221
+
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.
229
+
230
+ ---
231
+
232
+ ## API reference (shipped)
233
+
234
+ Import: `import { Tpm, TpmError } from 'node-tpm2'`
235
+
236
+ All flat methods also exist on `Tpm.*` (e.g. `Tpm.pcrRead` ≡ `tpm.pcr.read`).
237
+
238
+ ### Availability
239
+
240
+ ```javascript
241
+ await Tpm.isAvailable(); // boolean, never throws
242
+ await Tpm.info(); // { manufacturer, firmwareVersion, isVirtual, spec }
243
+ ```
244
+
245
+ ### Handle
246
+
247
+ ```javascript
248
+ await using tpm = await Tpm.open();
249
+ await tpm.readPublic('0x81000001'); // → { publicKeyDer, name }
250
+ ```
251
+
252
+ ### PCR
253
+
254
+ ```javascript
255
+ await tpm.pcr.read([0, 1, 7], 'sha256'); // → { 0: 'hex…', 1: 'hex…', … }
256
+ await tpm.pcr.extend(7, digest); // digest: 32-byte Buffer (SHA-256 bank)
257
+ await Tpm.pcrExtend(7, digest); // flat
258
+ ```
259
+
260
+ ### Random
261
+
262
+ ```javascript
263
+ await tpm.random.bytes(32); // Buffer from TPM2_GetRandom
264
+ await Tpm.randomBytes(32); // flat
265
+ ```
266
+
267
+ ### Keys (device-bound signing)
268
+
269
+ ```javascript
270
+ const key = await tpm.keys.create({ type: 'ecc', sign: true });
271
+ const digest = crypto.createHash('sha256').update('payload').digest();
272
+ const signature = await key.sign(digest);
273
+ const saved = key.export();
274
+
275
+ const reloaded = await tpm.keys.load(saved);
276
+ await reloaded.sign(digest);
277
+ ```
278
+
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);
85
301
  ```
86
302
 
87
- **Flat style** (same operations, functional entry points):
303
+ Flat: `Tpm.seal`, `Tpm.unseal`.
304
+
305
+ ### Attestation
306
+
307
+ ```javascript
308
+ const ak = await tpm.attest.provisionAk({
309
+ keyName: 'my-app-device-ak', // Windows: required for machine scope
310
+ scope: 'machine', // Windows: 'user' | 'machine'
311
+ overwrite: true,
312
+ });
313
+
314
+ const akBlob = ak.export(); // { public, private }
315
+ await ak.quote({ pcrSelection: [0, 1, 7], qualifyingData: Buffer.from('nonce') });
316
+ await tpm.attest.ekCertificate(); // Buffer | null
317
+ await ak.activateCredential({ credentialBlob, secret });
318
+ ```
319
+
320
+ ### Flat equivalents
88
321
 
89
322
  ```javascript
90
- await Tpm.isAvailable();
91
- await Tpm.provisionAk({ keyName: 'my-app-ak' });
92
- await Tpm.quote({ akBlob, pcrSelection: [0], qualifyingData: nonce });
93
323
  await Tpm.pcrRead([0, 1, 7]);
324
+ await Tpm.readPublic('0x81010001');
94
325
  await Tpm.readEkCertificate();
326
+ await Tpm.provisionAk({ scope: 'user' });
327
+ await Tpm.quote({ akBlob, pcrSelection: [7], qualifyingData: nonce });
95
328
  await Tpm.activateCredential({ akBlob, credentialBlob, secret });
96
329
  ```
97
330
 
98
- Errors:
331
+ ### Types
332
+
333
+ ```typescript
334
+ type AkBlob = { public: Buffer; private: Buffer };
335
+
336
+ type ProvisionAkOptions = {
337
+ keyName?: string;
338
+ scope?: 'user' | 'machine';
339
+ overwrite?: boolean;
340
+ };
341
+
342
+ type QuoteOptions = {
343
+ akBlob: AkBlob;
344
+ pcrSelection: number[];
345
+ qualifyingData: Buffer;
346
+ bank?: 'sha256';
347
+ };
348
+ ```
349
+
350
+ ---
351
+
352
+ ## Error reference
353
+
354
+ Failures throw `TpmError` (subclass of `Error`). Inspect **`code`** for programmatic handling; use **`message`** for logs; **`tpmRc`** / **`hresult`** carry raw platform codes when present.
99
355
 
100
356
  ```javascript
357
+ import { Tpm, TpmError } from 'node-tpm2';
358
+
101
359
  try {
102
360
  await Tpm.provisionAk({ scope: 'machine', keyName: 'fleet-ak' });
103
361
  } catch (err) {
104
- if (err.code === 'REQUIRES_ELEVATION') {
105
- // Windows: machine keys need Admin/SYSTEM at provision time only
362
+ if (err instanceof TpmError) {
363
+ err.code; // stable string branch on this
364
+ err.message; // human detail (includes context + hex codes)
365
+ err.suggestion; // optional remediation
366
+ err.tpmRc; // TPM 2.0 response code (number), when applicable
367
+ err.hresult; // Windows NCrypt / Win32 HRESULT (number), when applicable
106
368
  }
107
369
  }
108
370
  ```
109
371
 
110
- Full reference: [docs/getting-started.md](./docs/getting-started.md) · Windows fleet: [docs/windows-pcp.md](./docs/windows-pcp.md)
372
+ **Wire format** (native → JS): `__tpm2__code|message|suggestion|tpmRc|hresult` empty trailing fields mean undefined.
111
373
 
112
- ## Privileges (honest summary)
374
+ **Stability:** error **codes** are semver-stable after `latest`. New codes may be added in minors; renames require a major.
113
375
 
114
- There is **no separate TPM daemon to install**, but the OS still controls access:
376
+ ### Stable error codes
115
377
 
116
- | | Linux | Windows |
117
- |---|-------|---------|
118
- | **Typical runtime** (quote, PCR read) | User in `tss` group (or equivalent access to `/dev/tpmrm0`) | Standard user no elevation |
119
- | **User-scoped AK** (`provisionAk()`) | Same as runtime | Standard user |
120
- | **Machine-scoped AK** (`scope: 'machine'`) | N/A | **Admin or SYSTEM at enrollment** then standard users quote with the saved blob |
121
- | **Credential activation** | TPM policy dependent | Elevated / SYSTEM |
378
+ | Code | When | `tpmRc` | `hresult` | Typical `suggestion` |
379
+ |------|------|:-------:|:---------:|----------------------|
380
+ | `TPM_UNAVAILABLE` | No TPM, no native binary, macOS, or backend not built | | | Install platform package / check TPM |
381
+ | `ACCESS_DENIED` | OS denied device or key access | | sometimes | Linux: `tss` group; container: pass device |
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 |
384
+ | `NOT_SUPPORTED` | Feature or PCP capability missing on this platform | — | sometimes | — |
385
+ | `INVALID_ARGUMENT` | Bad JS/Rust option (e.g. empty machine `keyName`) | — | sometimes | Fix caller input |
386
+ | `KEY_NOT_FOUND` | NCrypt key / blob locator not found | — | ✓ | Check persisted blob / key name |
387
+ | `ALREADY_EXISTS` | NCrypt key name already exists | — | ✓ | Use `overwrite: true` |
388
+ | `MARSHALLING_ERROR` | Codec bug, malformed TPM command, or unclassified NCrypt failure | sometimes | sometimes | Report bug or check firmware |
389
+ | `TRANSPORT_ERROR` | TBS / `/dev/tpmrm0` I/O failure | — | — | Retry; check driver / device node |
390
+ | `AUTH_FAILED` | TPM auth-class response (policy / password / hierarchy) | ✓ | — | Check object auth or policy |
391
+ | `TPM_RC` | Other TPM non-success response | ✓ | — | See `tpmRc` nibble / TPM spec |
122
392
 
123
- **Fleet pattern:** installer runs once elevated (or as SYSTEM) saves `akBlob` → app quotes unprivileged forever after. See [docs/windows-pcp.md](./docs/windows-pcp.md).
393
+ ### TPM response code → `TpmError.code`
124
394
 
125
- ## Validate your install
395
+ When the TPM returns a non-zero response code, the library classifies it:
126
396
 
127
- ```bash
128
- npm ls node-tpm2
129
- node node_modules/node-tpm2/examples/smoke-test.mjs runtime
130
- ```
397
+ | TPM RC class | Condition | Maps to | Example `tpmRc` |
398
+ |--------------|-----------|---------|-----------------|
399
+ | Success | `rc === 0` | (no error) | `0` |
400
+ | Auth | `(rc & 0x0300) === 0x0300` | `AUTH_FAILED` | `0x38E` (`TPM_RC_AUTH_FAIL`) |
401
+ | Format | `(rc & 0xFF00) === 0x0100` or FMT1 bit set | `MARSHALLING_ERROR` | `0x125` (`TPM_RC_ASYMMETRIC`) |
402
+ | Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` * | `0x80280400` |
131
403
 
132
- Windows fleet smoke (elevated provision, then standard-user quote):
404
+ \* **`PCR_Extend`:** mapped to **`REQUIRES_ELEVATION`** (same `hresult` `0x80280400`) — Administrator can extend on Windows client; standard user should re-run elevated.
405
+ | Other | everything else | `TPM_RC` | vendor-specific |
133
406
 
134
- ```bash
135
- node node_modules/node-tpm2/examples/smoke-test.mjs provision-machine --key-name my-app-device-ak --out ak.blob.json
136
- node node_modules/node-tpm2/examples/smoke-test.mjs quote --in ak.blob.json
137
- ```
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.
408
+
409
+ ### Windows NCrypt HRESULT → `TpmError.code`
410
+
411
+ PCP / NCrypt failures on Windows map through `classify_ncrypt` (`src/tbs/ncrypt.rs`):
138
412
 
139
- Use paths under `node_modules/node-tpm2/` after `npm install` (not `examples/` at the project root).
413
+ | HRESULT | Name | Typical code | Notes |
414
+ |---------|------|--------------|-------|
415
+ | `0x80090011` | `NTE_NOT_FOUND` | `KEY_NOT_FOUND` | Missing persisted key |
416
+ | `0x80090016` | `NTE_BAD_KEYSET` | `KEY_NOT_FOUND` | Key set not found |
417
+ | `0x8009000B` | `NTE_EXISTS` | `ALREADY_EXISTS` | Key name collision |
418
+ | `0x80090027` | `NTE_INVALID_PARAMETER` | `INVALID_ARGUMENT` | Bad NCrypt parameter |
419
+ | `0x80090030` | `NTE_DEVICE_NOT_READY` | `REQUIRES_ELEVATION` | Often privilege / readiness |
420
+ | `0x80090010` | `NTE_PERM` | `REQUIRES_ELEVATION` | Permission |
421
+ | `0x80090029` | `NTE_BAD_FLAGS` | `REQUIRES_ELEVATION` | Bad flags |
422
+ | `0x8009000F` | `NTE_INTERNAL_ERROR` | `REQUIRES_ELEVATION` | Machine provision from standard user (observed) |
423
+ | `0x80280084` | PCP activation / `TPM_RC_VALUE` | `REQUIRES_ELEVATION` | Standard user activation; elevated → `MARSHALLING_ERROR` |
424
+ | `0x5` / `0x80070005` | Access denied | `REQUIRES_ELEVATION` or `ACCESS_DENIED` | Machine provision → elevation |
425
+ | (other) | — | `MARSHALLING_ERROR` | Unmapped NCrypt failure |
140
426
 
141
- ## Platform support
427
+ **Transport** errors from `/dev/tpmrm0` or TBS that mention permission denied are promoted to `ACCESS_DENIED`; other I/O errors stay `TRANSPORT_ERROR`.
428
+
429
+ ---
430
+
431
+ ## API reference
432
+
433
+ Subsystem namespaces on `TpmHandle`. See [docs/api-reference.md](./docs/api-reference.md) for full detail.
434
+
435
+ | Namespace | Methods |
436
+ |-----------|---------|
437
+ | `tpm.random` | `bytes(n)` ✅ |
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` ✅ |
442
+
443
+ ---
444
+
445
+ ## Platforms
142
446
 
143
447
  | Platform | Status | Attestation key |
144
448
  |----------|--------|-----------------|
145
- | Linux (glibc/musl, x64/arm64) | Supported | ECDSA P-256 TPM2B |
146
- | Windows (x64/arm64) | Supported | RSA-2048 PCP |
147
- | macOS | Not supported (`isAvailable()` → false) | — |
449
+ | Linux x64/arm64 gnu/musl | Supported | ECDSA P-256 TPM2B |
450
+ | Windows x64/arm64 | Supported | RSA-2048 PCP |
451
+ | macOS | Unavailable | `isAvailable()` → `false` |
452
+
453
+ ---
148
454
 
149
- ## Development
455
+ ## Contributing
150
456
 
151
457
  ```bash
152
458
  git clone https://github.com/stacks0x/tpm2.git && cd tpm2
153
459
  npm install && npm run build
460
+ cargo test --lib
461
+ npm run verify:package
154
462
  node examples/smoke-test.mjs runtime
155
463
  ```
156
464
 
157
- Rust probe for low-level validation (repo only, **not** published to npm):
465
+ Docs: [getting-started.md](./docs/getting-started.md) · [windows-pcp.md](./docs/windows-pcp.md) · [roadmap.md](./docs/roadmap.md)
158
466
 
159
- ```powershell
160
- cargo run --no-default-features --features probe-bin --bin tbs-probe -- all
161
- ```
467
+ Low-level Rust validation: `cargo run --no-default-features --features probe-bin --bin tbs-probe --` (repo only, not published to npm).
468
+
469
+ ---
162
470
 
163
471
  ## License
164
472