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
package/README.md
CHANGED
|
@@ -1,164 +1,472 @@
|
|
|
1
1
|
# node-tpm2
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Native TPM 2.0 for Node.js. Prebuilt binaries — no `tpm2-tools`, no `tpm2-tss`, no Rust at install time.
|
|
4
4
|
|
|
5
|
-
|
|
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
|
-
|
|
10
|
+
if (!(await Tpm.isAvailable())) throw new Error('No TPM');
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
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:
|
|
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
|
-
|
|
92
|
+
const saved = ak.export(); // persist { public, private } for next session
|
|
20
93
|
```
|
|
21
94
|
|
|
22
|
-
|
|
95
|
+
### Windows fleet enrollment
|
|
23
96
|
|
|
24
|
-
|
|
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
|
-
**
|
|
99
|
+
**Once** at install time (Admin or SYSTEM): create a machine-scoped key with a stable name and persist the blob.
|
|
32
100
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
+
```javascript
|
|
141
|
+
import { Tpm } from 'node-tpm2';
|
|
61
142
|
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
150
|
+
### Credential activation (enrollment proof-of-possession)
|
|
67
151
|
|
|
68
|
-
|
|
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
|
-
|
|
164
|
+
### Errors
|
|
71
165
|
|
|
72
166
|
```javascript
|
|
73
|
-
|
|
167
|
+
import { Tpm, TpmError } from 'node-tpm2';
|
|
74
168
|
|
|
75
|
-
|
|
76
|
-
await
|
|
77
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
105
|
-
//
|
|
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
|
-
|
|
372
|
+
**Wire format** (native → JS): `__tpm2__code|message|suggestion|tpmRc|hresult` — empty trailing fields mean undefined.
|
|
111
373
|
|
|
112
|
-
|
|
374
|
+
**Stability:** error **codes** are semver-stable after `latest`. New codes may be added in minors; renames require a major.
|
|
113
375
|
|
|
114
|
-
|
|
376
|
+
### Stable error codes
|
|
115
377
|
|
|
116
|
-
| |
|
|
117
|
-
|
|
118
|
-
|
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
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
|
-
|
|
393
|
+
### TPM response code → `TpmError.code`
|
|
124
394
|
|
|
125
|
-
|
|
395
|
+
When the TPM returns a non-zero response code, the library classifies it:
|
|
126
396
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
146
|
-
| Windows
|
|
147
|
-
| macOS |
|
|
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
|
-
##
|
|
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
|
-
|
|
465
|
+
Docs: [getting-started.md](./docs/getting-started.md) · [windows-pcp.md](./docs/windows-pcp.md) · [roadmap.md](./docs/roadmap.md)
|
|
158
466
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|