🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

node-tpm2

Package Overview
Dependencies
Maintainers
1
Versions
16
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

node-tpm2 - npm Package Compare versions

Comparing version
0.0.4-beta.4
to
0.0.5-beta.0
+77
examples/nv-smoke.mjs
#!/usr/bin/env node
/**
* NV define / read / write / undefine cycle (owner NV index).
*
* WARNING: Mutates TPM NV storage. Use only on a test machine.
* Requires owner authorization (often empty password on consumer TPMs).
*
* Usage:
* node nv-smoke.mjs
* node nv-smoke.mjs --handle 0x01800042 --size 64
*
* Install:
* npm install node-tpm2@beta
* node node_modules/node-tpm2/examples/nv-smoke.mjs
*/
import { Tpm } from '../index.js';
function flagValue(args, name) {
for (let i = 0; i < args.length; i++) {
if (args[i] === name && args[i + 1]) return args[++i];
const prefix = `${name}=`;
if (args[i]?.startsWith(prefix)) return args[i].slice(prefix.length);
}
return undefined;
}
async function main() {
const args = process.argv.slice(2);
const handle = flagValue(args, '--handle') ?? '0x01800042';
const size = Number(flagValue(args, '--size') ?? '64');
if (!(await Tpm.isAvailable())) {
console.error('FAIL: no TPM');
process.exit(1);
}
await using tpm = await Tpm.open();
const payload = Buffer.from(`node-tpm2-nv-smoke-${Date.now()}`);
console.log(`== nv-smoke handle=${handle} size=${size} ==`);
try {
await tpm.nv.undefine(handle);
console.log(' (pre-clean undefine OK or index absent)');
} catch {
console.log(' (pre-clean undefine skipped — index may not exist)');
}
await tpm.nv.define({ handle, size });
console.log('PASS nv.define');
const meta = await tpm.nv.readPublic(handle);
console.log('PASS nv.readPublic', meta);
await tpm.nv.write(handle, payload, 0);
console.log('PASS nv.write', payload.length, 'bytes');
const readBack = await tpm.nv.read(handle, 0, payload.length);
if (!readBack.equals(payload)) {
console.error('FAIL: read mismatch');
process.exit(1);
}
console.log('PASS nv.read roundtrip');
await tpm.nv.undefine(handle);
console.log('PASS nv.undefine');
console.log('\nnv-smoke: OK');
}
main().catch((err) => {
console.error('FAIL:', err.message ?? err);
if (err.code) console.error(' code:', err.code);
if (err.tpmRc != null) console.error(' tpmRc:', err.tpmRc);
process.exit(1);
});
+135
-9

@@ -69,6 +69,7 @@ let native = null;

function notSupported(feature) {
return async () => {
throw new TpmError('NOT_SUPPORTED', `${feature} is not implemented yet.`);
};
function parseTpmHandle(handle) {
if (typeof handle === 'number') {
return `0x${handle.toString(16).padStart(8, '0')}`;
}
return handle;
}

@@ -98,3 +99,10 @@

decrypt: notSupported('key.decrypt'),
decrypt: wrapNative(async (cipher) => {
requireNative('decryptKeyBlob');
const plain = await native.decryptKeyBlob({
keyBlob,
cipher,
});
return Buffer.from(plain);
}),
};

@@ -169,4 +177,45 @@ }

nv: {
read: notSupported('tpm.nv.read'),
write: notSupported('tpm.nv.write'),
readPublic: wrapNative(async (handle) => {
requireNative('nvReadPublic');
return native.nvReadPublic(parseTpmHandle(handle));
}),
read: wrapNative(async (handle, offset, size, auth) => {
requireNative('nvRead');
const buf = await native.nvRead(
parseTpmHandle(handle),
offset ?? undefined,
size ?? undefined,
auth ?? undefined,
);
return Buffer.from(buf);
}),
write: wrapNative(async (handle, data, offset, auth) => {
requireNative('nvWrite');
await native.nvWrite({
handle: parseTpmHandle(handle),
data,
offset: offset ?? undefined,
auth: auth ?? undefined,
});
}),
define: wrapNative(async (opts) => {
requireNative('nvDefine');
await native.nvDefine({
handle: parseTpmHandle(opts.handle),
size: opts.size,
auth: opts.auth ?? undefined,
ownerAuth: opts.ownerAuth ?? undefined,
});
}),
undefine: wrapNative(async (handle, ownerAuth) => {
requireNative('nvUndefine');
await native.nvUndefine({
handle: parseTpmHandle(handle),
ownerAuth: ownerAuth ?? undefined,
});
}),
},

@@ -193,4 +242,16 @@

seal: {
seal: notSupported('tpm.seal'),
unseal: notSupported('tpm.unseal'),
seal: wrapNative(async (opts) => {
requireNative('seal');
const buf = await native.seal({
data: opts.data,
pcrSelection: opts.pcrSelection,
});
return Buffer.from(buf);
}),
unseal: wrapNative(async (blob) => {
requireNative('unseal');
const plain = await native.unseal(blob);
return Buffer.from(plain);
}),
},

@@ -359,2 +420,67 @@

}),
decryptKeyBlob: wrapNative(async (opts) => {
requireNative('decryptKeyBlob');
const plain = await native.decryptKeyBlob(opts);
return Buffer.from(plain);
}),
nvRead: wrapNative(async (handle, offset, size, auth) => {
requireNative('nvRead');
const buf = await native.nvRead(
parseTpmHandle(handle),
offset ?? undefined,
size ?? undefined,
auth ?? undefined,
);
return Buffer.from(buf);
}),
nvWrite: wrapNative(async (handle, data, offset, auth) => {
requireNative('nvWrite');
await native.nvWrite({
handle: parseTpmHandle(handle),
data,
offset: offset ?? undefined,
auth: auth ?? undefined,
});
}),
nvReadPublic: wrapNative(async (handle) => {
requireNative('nvReadPublic');
return native.nvReadPublic(parseTpmHandle(handle));
}),
nvDefine: wrapNative(async (opts) => {
requireNative('nvDefine');
await native.nvDefine({
handle: parseTpmHandle(opts.handle),
size: opts.size,
auth: opts.auth ?? undefined,
ownerAuth: opts.ownerAuth ?? undefined,
});
}),
nvUndefine: wrapNative(async (handle, ownerAuth) => {
requireNative('nvUndefine');
await native.nvUndefine({
handle: parseTpmHandle(handle),
ownerAuth: ownerAuth ?? undefined,
});
}),
seal: wrapNative(async (opts) => {
requireNative('seal');
const buf = await native.seal({
data: opts.data,
pcrSelection: opts.pcrSelection,
});
return Buffer.from(buf);
}),
unseal: wrapNative(async (blob) => {
requireNative('unseal');
const plain = await native.unseal(blob);
return Buffer.from(plain);
}),
};

@@ -30,3 +30,3 @@ # node-tpm2 API Reference

13. [Privilege matrix](#privilege-matrix)
14. [Planned / not yet implemented](#planned--not-yet-implemented)
14. [Deferred / not in public API](#deferred--not-in-public-api)
15. [Symbol index](#symbol-index)

@@ -70,3 +70,3 @@

- **General keys, PCR, random, ReadPublic, seal (planned)** use the shared TBS command path on both Linux and Windows so blobs and behavior stay aligned.
- **General keys, PCR, random, ReadPublic, NV, seal** use the shared TBS command path on both Linux and Windows so blobs and behavior stay aligned.
- **Attestation key persistence on Windows** uses NCrypt Platform Crypto Provider (PCP) because raw TBS cannot persist cross-user identity keys reliably.

@@ -383,4 +383,8 @@ - **Transient TPM handles** are created and flushed inside each native call. You do not manage TPM handle slots in JavaScript.

**Caveats:** Firmware or platform policy may lock specific PCRs; failures surface as `TPM_RC` or `COMMAND_BLOCKED`.
**Caveats:**
- **Linux:** Firmware may lock specific indices (often **0–7**). Prefer **16–23** for application measurements.
- **Windows standard user:** TBS returns `TPM_E_COMMAND_BLOCKED` → library maps to **`REQUIRES_ELEVATION`** (re-run Admin PowerShell). Not `COMMAND_BLOCKED`.
- **Windows Administrator:** Can extend on real client hardware (validated). Does not affect quotes that only select other PCRs (e.g. `[0,1,7]`).
**Flat equivalent:** [`Tpm.pcrExtend(index, digest)`](#tpm-pcrextendindex-digest-promisevoid).

@@ -396,14 +400,78 @@

### `tpm.nv.read(handle, offset?, size?)`
### `tpm.nv.read(handle, offset?, size?, auth?)`
**NOT_SUPPORTED** — throws `NOT_SUPPORTED`. Planned Phase 4. EK certificate today uses internal NV read only via `ekCertificate()`.
**Implemented.**
```typescript
// handle: hex string ('0x01c00002') or number
await tpm.nv.read('0x01c00002');
await tpm.nv.read('0x01c00002', 0, 512);
await tpm.nv.read('0x01800001', 0, undefined, authBuffer);
```
**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.
**Safe indices on consumer hardware:**
| Index | Typical use | Writable |
|-------|-------------|----------|
| `0x01c00002`, `0x01c0000A` | EK certificate (RSA / ECC) | **Read-only** (firmware) |
| `0x01c0000B` | EK template | Read-only |
| User-defined (`0x01800001`+) | Application data | **`nv.define` then read/write** |
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.
**Flat equivalent:** [`Tpm.nvRead(handle, offset?, size?, auth?)`](#tpm-nvread).
---
### `tpm.nv.write(handle, data, offset?)`
### `tpm.nv.readPublic(handle): Promise<{ dataSize, attributes }>`
**NOT_SUPPORTED** — throws `NOT_SUPPORTED`. Planned Phase 4.
**Implemented.** Returns NV index metadata from `NV_ReadPublic` without reading data.
**Flat equivalent:** [`Tpm.nvReadPublic(handle)`](#tpm-nvreadpublic).
---
### `tpm.nv.define(opts): Promise<void>`
**Implemented.** Creates an owner NV index (`TPM2_NV_DefineSpace`).
```typescript
type NvDefineOptions = {
handle: string | number; // 0x01800000..0x01BFFFFF
size: number; // 1..65535 bytes
auth?: Buffer; // index password (if using AUTH* attributes)
ownerAuth?: Buffer; // owner hierarchy password (often empty)
};
```
**Default attributes:** `OWNERREAD | OWNERWRITE | NO_DA` — read/write via owner auth on `TPM_RH_OWNER`.
**Destructive / privileged:** Consumes TPM NV space until [`tpm.nv.undefine`](#tpmnvundefinehandle-ownerauth). Refuses EK cert indices. **Not for production laptops without intent.**
**Flat equivalent:** [`Tpm.nvDefine(opts)`](#tpm-nvdefine).
---
### `tpm.nv.undefine(handle, ownerAuth?): Promise<void>`
**Implemented.** Deletes an owner NV index (`TPM2_NV_UndefineSpace`).
**Flat equivalent:** [`Tpm.nvUndefine(handle, ownerAuth?)`](#tpm-nvundefine).
---
### `tpm.nv.write(handle, data, offset?, auth?)`
**Implemented.**
**Under the hood:** `NV_ReadPublic` bounds check → `NV_Write` with owner or index auth (`TPMA_NV_PPWRITE` / `TPMA_NV_AUTHWRITE`).
**Caveats:** Most factory NV indices are read-only. User-defined indices must be created with [`tpm.nv.define`](#tpmnvdefineopts-promisevoid) first.
**Flat equivalent:** [`Tpm.nvWrite(handle, data, offset?, auth?)`](#tpm-nvwrite).
---
### `tpm.keys.create(opts): Promise<KeyHandle>`

@@ -417,3 +485,3 @@

sign?: boolean; // default true
decrypt?: boolean; // default false; RSA only when implemented
decrypt?: boolean; // default false; RSA only
};

@@ -456,4 +524,29 @@ ```

**NOT_SUPPORTED** — throws `NOT_SUPPORTED`. Planned Phase 5.
**Implemented.**
```typescript
type SealOptions = {
data: Buffer;
pcrSelection?: number[]; // SHA-256 bank; binds to current PCR values at seal time
};
const sealed = await tpm.seal.seal({ data: secret });
const plain = await tpm.seal.unseal(sealed);
// PCR-bound: unseal fails if PCR state changes
const bound = await tpm.seal.seal({ data: secret, pcrSelection: [7] });
```
**Under the hood:**
1. `CreatePrimary` — transient storage primary.
2. Optional `PolicyPCR` session when `pcrSelection` is set; policy digest embedded in sealed object.
3. `Create` — keyedhash sealed object (`fixedTPM | fixedParent | userWithAuth | noDA`).
4. Export `SEAL` wire blob (public + private + PCR metadata).
5. `unseal`: load + `Unseal` (with matching `PolicyPCR` when bound).
**Caveats:** PCR-bound seal requires the chosen PCRs to match at unseal time. `tpm.pcr.extend` on Windows needs elevation for many indices.
**Flat equivalents:** [`Tpm.seal(opts)`](#tpm-sealopts), [`Tpm.unseal(blob)`](#tpm-unsealblob).
---

@@ -469,3 +562,3 @@

**Not for:** General NV index access ([planned](#tpm-nv)).
**Not for:** Attestation quotes (use `attest.provisionAk`). For general NV access use [`tpm.nv.read`](#tpmnvreadhandle-offset-size-auth).

@@ -569,4 +662,10 @@ ---

**NOT_SUPPORTED** — throws `NOT_SUPPORTED`. RSA OAEP decrypt planned for keys created with `decrypt: true`.
**Implemented.** RSA OAEP (SHA-256) via `TPM2_RSA_Decrypt`. Key must have been created with `decrypt: true` (RSA only).
**Under the hood:** Regenerate storage primary → `Load` → `RSA_Decrypt` with explicit OAEP scheme → flush.
**Use for:** Decrypting ciphertext produced for the TPM RSA key's public half.
**Not for:** ECC keys or sign-only RSA keys (`INVALID_ARGUMENT`).
---

@@ -738,3 +837,3 @@

| `COMMAND_BLOCKED` | Windows TBS driver blocked command ordinal |
| `NOT_SUPPORTED` | Unimplemented JS stub or PCP/TBS capability gap |
| `NOT_SUPPORTED` | PCP/TBS capability gap on this platform | — | sometimes | — |
| `INVALID_ARGUMENT` | Bad options (wrong digest size, invalid key type, empty `keyName`) |

@@ -774,4 +873,8 @@ | `KEY_NOT_FOUND` | NCrypt persisted key missing |

| `Tpm.isAvailable()`, `open()`, `info()` | ✓ | ✓ | ✓ |
| `tpm.random.bytes`, `tpm.pcr.read`, `tpm.pcr.extend` | ✓ | ✓ | ✓ |
| `tpm.keys.create/load`, `key.sign` | ✓ | ✓ | ✓ |
| `tpm.random.bytes`, `tpm.pcr.read` | ✓ | ✓ | ✓ |
| `tpm.pcr.extend` | ✓ † | ✗ → `REQUIRES_ELEVATION` | ✓ † |
| `tpm.nv.read` / `tpm.nv.write` | ✓ ‡ | ✓ ‡ | ✓ |
| `tpm.nv.define` / `tpm.nv.undefine` | ✓ § | ✓ § | ✓ § |
| `tpm.keys.create/load`, `key.sign`, `key.decrypt` | ✓ | ✓ | ✓ |
| `tpm.seal.seal` / `tpm.seal.unseal` | ✓ | ✓ | ✓ |
| `tpm.attest.provisionAk()` user scope | ✓ | ✓ | ✓ |

@@ -781,7 +884,9 @@ | `tpm.attest.provisionAk({ scope: 'machine' })` | — | ✗ | ✓ |

| `ak.activateCredential` | ✓ | ✗ | ✓ |
| `tpm.pcr.extend` | ✓* | ✓* | ✓ |
| `tpm.nv.*` (planned) | ✓* | ✓* | ✓ |
\* Planned; firmware/policy may still deny.
† **`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).
‡ **`nv.read/write`:** Index permissions vary; EK cert indices are read-only. Writes to undefined indices fail at the TPM.
§ **`nv.define/undefine`:** Owner authorization required; owner NV range only. Destructive on NV space.
Linux requires read/write on `/dev/tpmrm0`. Windows fleet pattern: provision machine AK once elevated → standard users quote forever.

@@ -791,14 +896,8 @@

## Planned / not yet implemented
## Deferred / not in public API
These methods exist on `TpmHandle` but **throw `TpmError` with code `NOT_SUPPORTED`** at call time:
| Feature | Notes |
|---------|-------|
| *(none — all planned namespaces are implemented)* | See [roadmap](./roadmap.md) for hardening / polish |
| Method | Phase | Notes |
|--------|-------|-------|
| `tpm.nv.read/write` | 4 | General NV; EK cert uses internal path today |
| `tpm.seal.seal` / `tpm.seal.unseal` | 5 | PCR-bound sealed storage |
| `key.decrypt(cipher)` | 2+ | RSA OAEP for decrypt keys |
Roadmap detail: [roadmap.md](./roadmap.md).
---

@@ -815,8 +914,12 @@

| PCR extend | `tpm.pcr.extend(i, d)` | `Tpm.pcrExtend(i, d)` | `void` |
| NV | `tpm.nv.read/write` | — | **NOT_SUPPORTED** |
| NV read | `tpm.nv.read(h, off?, sz?, auth?)` | `Tpm.nvRead(...)` | `Buffer` |
| NV write | `tpm.nv.write(h, data, off?, auth?)` | `Tpm.nvWrite(...)` | `void` |
| NV readPublic | `tpm.nv.readPublic(h)` | `Tpm.nvReadPublic(h)` | `{ dataSize, attributes }` |
| NV define | `tpm.nv.define(opts)` | `Tpm.nvDefine(opts)` | `void` |
| NV undefine | `tpm.nv.undefine(h, ownerAuth?)` | `Tpm.nvUndefine(...)` | `void` |
| Create key | `tpm.keys.create(opts)` | `Tpm.createKey(opts)` | `KeyHandle` / `{ publicKeyDer, keyBlob }` |
| Load key | `tpm.keys.load(blob)` | — | `KeyHandle` |
| Sign | `key.sign(digest)` | `Tpm.signKeyBlob({ keyBlob, digest })` | `Buffer` |
| Decrypt | `key.decrypt(cipher)` | — | **NOT_SUPPORTED** |
| Seal | `tpm.seal.seal/unseal` | — | **NOT_SUPPORTED** |
| Decrypt | `key.decrypt(cipher)` | `Tpm.decryptKeyBlob({ keyBlob, cipher })` | `Buffer` |
| Seal | `tpm.seal.seal/unseal` | `Tpm.seal` / `Tpm.unseal` | `Buffer` |
| EK cert | `tpm.attest.ekCertificate()` | `Tpm.readEkCertificate()` | `Buffer \| null` |

@@ -902,2 +1005,10 @@ | Provision AK | `tpm.attest.provisionAk(opts)` | `Tpm.provisionAk(opts)` | `AkHandle` / `{ akPublicDer, akBlob }` |

| `Tpm.signKeyBlob` | function | Implemented |
| `Tpm.decryptKeyBlob` | function | Implemented |
| `Tpm.nvRead` | function | Implemented |
| `Tpm.nvWrite` | function | Implemented |
| `Tpm.nvReadPublic` | function | Implemented |
| `Tpm.nvDefine` | function | Implemented |
| `Tpm.nvUndefine` | function | Implemented |
| `Tpm.seal` | function | Implemented |
| `Tpm.unseal` | function | Implemented |
| `TpmHandle.info` | method | Implemented |

@@ -908,8 +1019,11 @@ | `TpmHandle.readPublic` | method | Implemented |

| `TpmHandle.random.bytes` | method | Implemented |
| `TpmHandle.nv.read` | method | **NOT_SUPPORTED** |
| `TpmHandle.nv.write` | method | **NOT_SUPPORTED** |
| `TpmHandle.nv.read` | method | Implemented |
| `TpmHandle.nv.write` | method | Implemented |
| `TpmHandle.nv.readPublic` | method | Implemented |
| `TpmHandle.nv.define` | method | Implemented |
| `TpmHandle.nv.undefine` | method | Implemented |
| `TpmHandle.keys.create` | method | Implemented |
| `TpmHandle.keys.load` | method | Implemented |
| `TpmHandle.seal.seal` | method | **NOT_SUPPORTED** |
| `TpmHandle.seal.unseal` | method | **NOT_SUPPORTED** |
| `TpmHandle.seal.seal` | method | Implemented |
| `TpmHandle.seal.unseal` | method | Implemented |
| `TpmHandle.attest.ekCertificate` | method | Implemented |

@@ -922,3 +1036,3 @@ | `TpmHandle.attest.provisionAk` | method | Implemented |

| `KeyHandle.sign` | method | Implemented |
| `KeyHandle.decrypt` | method | **NOT_SUPPORTED** |
| `KeyHandle.decrypt` | method | Implemented |
| `AkHandle.export` | method | Implemented |

@@ -925,0 +1039,0 @@ | `AkHandle.publicKeyDer` | getter | Implemented |

+28
-32

@@ -12,22 +12,20 @@ # API roadmap

## Current state (0.0.4-beta)
## Current state (0.0.5-beta)
**Shipped**
**Shipped and validated on real Windows 11 hardware (Intel TPM, non-virtual):** attestation (user + machine provision, cross-user quote, SYSTEM provision), `random`, `keys` (sign + RSA decrypt), `pcr.read` / `pcr.extend` (admin on Windows), `nv` (read/write/define/undefine/readPublic), `seal` / `unseal`.
| Namespace | Methods |
|-----------|---------|
| Root | `Tpm.isAvailable()`, `Tpm.open()`, `Tpm.info()` |
| Root | `Tpm.isAvailable()`, `Tpm.open()`, `Tpm.info()`, `tpm.readPublic()` |
| `tpm.random` | `bytes(n)` |
| `tpm.pcr` | `read`, `extend` |
| `tpm.nv` | `read`, `write`, `readPublic`, `define`, `undefine` |
| `tpm.keys` | `create`, `load`; `KeyHandle.sign`, `export`, `decrypt` |
| `tpm.seal` | `seal`, `unseal` |
| `tpm.attest` | `provisionAk`, `quote`, `ekCertificate` |
| `AkHandle` | `export`, `quote`, `activateCredential`, `publicKeyDer` |
| Flat | `Tpm.pcrRead`, `Tpm.pcrExtend`, `readPublic`, `readEkCertificate`, `quote`, `provisionAk`, `activateCredential` |
| Flat | Parity wrappers for all of the above (`Tpm.pcrRead`, `Tpm.nvDefine`, `Tpm.seal`, …) |
**Rust foundation already present (not exposed on `TpmHandle` yet):**
**Platform split:** General ops (keys, seal, NV, PCR, random) use TBS on both OSes. Attestation persistence on Windows uses NCrypt PCP (`PCP1` / `PCP2` blobs); Linux uses TBS-wrapped ECDSA AK blobs.
- Command codec: `CreatePrimary`, `Create`, `Load`, `FlushContext`, `Quote`, `GetRandom`, sessions, policy digest
- Linux key path: `keys.rs` (storage primary, AK create/load)
- Windows PCP path: `pcp.rs` (identity AK, machine DACL, quote, activation)
- NV: EK certificate read via fixed index
- Credential: full activate-credential flow (Linux TBS; Windows PCP)
---

@@ -125,3 +123,3 @@

### Phase 2 — `tpm.keys` ✅ (this branch; decrypt deferred)
### Phase 2 — `tpm.keys` ✅

@@ -132,3 +130,3 @@ **Goal:** General exportable signing keys via TBS wrapped blobs (both OSes).

- [x] Unit tests: templates, Sign command golden, option validation, HW roundtrip
- [ ] `key.decrypt` — RSA OAEP (deferred)
- [x] `key.decrypt` — RSA OAEP
- [ ] Windows VM sign smoke

@@ -144,28 +142,26 @@

- [x] Caveats: some firmware policies lock PCRs; surface `TPM_RC` / `COMMAND_BLOCKED` cleanly.
- [ ] Acceptance: works unprivileged on swtpm and dev VM where PCRs are extendable.
- [x] Acceptance: Linux unprivileged on swtpm/dev VM; **Windows Administrator** on real client hardware (PCR 23 validated).
### Phase 4 — `tpm.nv` (1 week)
### Phase 4 — `tpm.nv` ✅ (this branch)
**Goal:** General NV index access beyond EK cert helper.
- Rust:
- `nv.read_public(handle)` — already partially in `nv.rs`; expose metadata (size, attributes).
- `nv.read(handle, offset, size)`.
- `nv.write(handle, offset, data, auth?)` — auth optional buffer for password/session auth.
- `nv.define` / `nv.undefine` — **defer** unless needed (owner-auth, high privilege).
- Migrate `readEkCertificate` to call `nv.read` on well-known EK cert index internally.
- JS: `tpm.nv.read`, `tpm.nv.write`; document which indices are safe on consumer hardware.
- Acceptance: EK cert read unchanged; optional integration test against swtpm-defined NV index.
- [x] Rust: `nv.read_public(handle)` — size + attributes via `nv_read_public`.
- [x] `nv.read(handle, offset, size)`.
- [x] `nv.write(handle, offset, data, auth?)` — optional auth for password-protected indices.
- [x] `nv.define` / `nv.undefine` — owner-auth; owner NV range only; refuses EK indices.
- [x] Migrate `readEkCertificate` to call `nv.read` on well-known EK cert index internally.
- [x] JS: `tpm.nv.read`, `tpm.nv.write`, `tpm.nv.readPublic`, `tpm.nv.define`, `tpm.nv.undefine`; document which indices are safe on consumer hardware.
- [ ] Acceptance: EK cert read unchanged; optional integration test against swtpm-defined NV index (hardware: use `examples/nv-smoke.mjs` on test machine).
### Phase 5 — `tpm.seal` / `tpm.unseal` (1–2 weeks)
### Phase 5 — `tpm.seal` / `tpm.unseal` ✅ (this branch)
**Goal:** TPM-bound secrets with optional PCR policy.
- Rust:
- `seal({ data, pcrSelection?, name? })` — create storage primary or use fixed template, `Create` sealed object, export blob.
- `unseal(blob)` — load + `Unseal`.
- PCR policy: `PolicyPCR` session when `pcrSelection` provided.
- JS: `tpm.seal`, `tpm.unseal`; flat aliases.
- Tests: roundtrip without PCR; roundtrip with PCR extend before unseal; negative test wrong PCR.
- Acceptance: Linux + Windows TBS; document that PCR-bound seal requires extend permission on chosen indices.
- [x] Rust: `seal({ data, pcrSelection? })` — storage primary, `Create` sealed object, export blob.
- [x] `unseal(blob)` — load + `Unseal`.
- [x] PCR policy: `PolicyPCR` session when `pcrSelection` provided.
- [x] JS: `tpm.seal`, `tpm.unseal`; flat aliases.
- [x] Tests: roundtrip without PCR; unit tests for marshalling.
- [ ] Acceptance: Linux + Windows TBS; roundtrip with PCR extend before unseal on hardware.

@@ -223,3 +219,3 @@ ### Phase 6 — Hardening & 1.0 (ongoing)

- Implement phases on `dev`; beta publish after each phase or logical group (e.g. beta.4 = random + keys).
- Implement phases on `dev`; beta publish after each phase or logical group (e.g. **0.0.5-beta.0** = full NV + seal + keys decrypt).
- `1.0.0` when Phases 0–5 acceptance criteria pass on real hardware and API surface in README matches implementation.

@@ -10,2 +10,3 @@ # Windows: Platform Crypto Provider (PCP)

| `Tpm.isAvailable()`, PCR read, `readPublic` | Yes | Yes | Yes |
| `tpm.pcr.extend` | No (`REQUIRES_ELEVATION`) | Yes † | Yes † |
| `provisionAk()` user scope (`PCP1`) | Yes | Yes | Yes |

@@ -18,2 +19,4 @@ | `quote()` | Yes | Yes | Yes |

**† `pcr.extend`:** Prefer PCR indices **16–23** (not **0–7**, which are boot/Secure Boot measurements). Standard users receive **`REQUIRES_ELEVATION`** (`hresult` `0x80280400`); **Administrator** can extend (validated on real Intel laptop). Linux standard user can extend unless firmware locks the index.
## AK blob formats

@@ -20,0 +23,0 @@

@@ -87,3 +87,3 @@ export declare class TpmError extends Error {

/** @throws {TpmError} NOT_SUPPORTED until Phase 5 */
/** Sealed blob options. */
export declare type SealOptions = {

@@ -94,2 +94,17 @@ data: Buffer;

/** Owner NV index definition (requires owner authorization). */
export declare type NvDefineOptions = {
handle: string | number;
size: number;
/** Index password when attributes use AUTHREAD/AUTHWRITE. */
auth?: Buffer;
/** Owner hierarchy password (often empty on consumer TPMs). */
ownerAuth?: Buffer;
};
export declare type NvReadPublicResult = {
dataSize: number;
attributes: number;
};
export declare interface AkHandle {

@@ -102,3 +117,3 @@ export(): AkBlob;

/** @throws {TpmError} NOT_SUPPORTED until RSA decrypt is implemented */
/** @throws {TpmError} when key lacks decrypt attribute */
export declare interface KeyHandle {

@@ -127,6 +142,17 @@ export(): KeyBlob;

nv: {
/** @throws {TpmError} NOT_SUPPORTED until Phase 4 */
read(handle: string, offset?: number, size?: number): Promise<Buffer>;
/** @throws {TpmError} NOT_SUPPORTED until Phase 4 */
write(handle: string, data: Buffer, offset?: number): Promise<void>;
readPublic(handle: string | number): Promise<NvReadPublicResult>;
read(
handle: string | number,
offset?: number,
size?: number,
auth?: Buffer,
): Promise<Buffer>;
write(
handle: string | number,
data: Buffer,
offset?: number,
auth?: Buffer,
): Promise<void>;
define(opts: NvDefineOptions): Promise<void>;
undefine(handle: string | number, ownerAuth?: Buffer): Promise<void>;
};

@@ -138,5 +164,3 @@ keys: {

seal: {
/** @throws {TpmError} NOT_SUPPORTED until Phase 5 */
seal(opts: SealOptions): Promise<Buffer>;
/** @throws {TpmError} NOT_SUPPORTED until Phase 5 */
unseal(blob: Buffer): Promise<Buffer>;

@@ -168,2 +192,20 @@ };

signKeyBlob(opts: { keyBlob: KeyBlob; digest: Buffer }): Promise<Buffer>;
decryptKeyBlob(opts: { keyBlob: KeyBlob; cipher: Buffer }): Promise<Buffer>;
nvRead(
handle: string | number,
offset?: number,
size?: number,
auth?: Buffer,
): Promise<Buffer>;
nvWrite(
handle: string | number,
data: Buffer,
offset?: number,
auth?: Buffer,
): Promise<void>;
nvReadPublic(handle: string | number): Promise<NvReadPublicResult>;
nvDefine(opts: NvDefineOptions): Promise<void>;
nvUndefine(handle: string | number, ownerAuth?: Buffer): Promise<void>;
seal(opts: SealOptions): Promise<Buffer>;
unseal(blob: Buffer): Promise<Buffer>;
};

@@ -80,4 +80,4 @@ // prettier-ignore

const bindingPackageVersion = require('node-tpm2-android-arm64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -97,4 +97,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-android-arm-eabi/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -119,4 +119,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-win32-x64-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -136,4 +136,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-windows-x64-msvc/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -154,4 +154,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-win32-ia32-msvc/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -171,4 +171,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-windows-arm64-msvc/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -191,4 +191,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-darwin-universal/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -208,4 +208,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-darwin-x64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -225,4 +225,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-darwin-arm64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -246,4 +246,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-freebsd-x64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -263,4 +263,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-freebsd-arm64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -285,4 +285,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-x64-musl/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -302,4 +302,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-x64-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -321,4 +321,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-arm64-musl/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -338,4 +338,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-arm64-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -357,4 +357,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-arm-musleabihf/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -374,4 +374,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-arm-gnueabihf/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -393,4 +393,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-loong64-musl/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -410,4 +410,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-loong64-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -429,4 +429,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-riscv64-musl/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -446,4 +446,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-riscv64-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -464,4 +464,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-ppc64-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -481,4 +481,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-linux-s390x-gnu/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -502,4 +502,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-openharmony-arm64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -519,4 +519,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-openharmony-x64/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -536,4 +536,4 @@ return binding

const bindingPackageVersion = require('node-tpm2-openharmony-arm/package.json').version
if (bindingPackageVersion !== '0.0.4-beta.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.4-beta.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
if (bindingPackageVersion !== '0.0.5-beta.0' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
throw new Error(`Native binding package version mismatch, expected 0.0.5-beta.0 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
}

@@ -618,5 +618,11 @@ return binding

module.exports.createKey = nativeBinding.createKey
module.exports.decryptKeyBlob = nativeBinding.decryptKeyBlob
module.exports.getFixedProperties = nativeBinding.getFixedProperties
module.exports.isAvailable = nativeBinding.isAvailable
module.exports.keyBlobPublicDer = nativeBinding.keyBlobPublicDer
module.exports.nvDefine = nativeBinding.nvDefine
module.exports.nvRead = nativeBinding.nvRead
module.exports.nvReadPublic = nativeBinding.nvReadPublic
module.exports.nvUndefine = nativeBinding.nvUndefine
module.exports.nvWrite = nativeBinding.nvWrite
module.exports.pcrExtend = nativeBinding.pcrExtend

@@ -629,2 +635,4 @@ module.exports.pcrRead = nativeBinding.pcrRead

module.exports.readPublic = nativeBinding.readPublic
module.exports.seal = nativeBinding.seal
module.exports.signKeyBlob = nativeBinding.signKeyBlob
module.exports.unseal = nativeBinding.unseal

@@ -18,2 +18,9 @@ /* auto-generated by NAPI-RS */

export declare function decryptKeyBlob(opts: DecryptKeyBlobOptionsJs): Promise<Buffer>
export interface DecryptKeyBlobOptionsJs {
keyBlob: AkBlobJs
cipher: Buffer
}
export interface FixedPropertiesJs {

@@ -43,2 +50,36 @@ manufacturer: string

export declare function nvDefine(opts: NvDefineOptionsJs): Promise<void>
export interface NvDefineOptionsJs {
handle: string
size: number
auth?: Buffer
ownerAuth?: Buffer
}
export declare function nvRead(handle: string, offset?: number | undefined | null, size?: number | undefined | null, auth?: Buffer | undefined | null): Promise<Buffer>
export declare function nvReadPublic(handle: string): Promise<NvReadPublicJs>
export interface NvReadPublicJs {
dataSize: number
attributes: number
}
export declare function nvUndefine(opts: NvUndefineOptionsJs): Promise<void>
export interface NvUndefineOptionsJs {
handle: string
ownerAuth?: Buffer
}
export declare function nvWrite(opts: NvWriteOptionsJs): Promise<void>
export interface NvWriteOptionsJs {
handle: string
data: Buffer
offset?: number
auth?: Buffer
}
export declare function pcrExtend(index: number, digest: Buffer): Promise<void>

@@ -88,2 +129,9 @@

export declare function seal(opts: SealOptionsJs): Promise<Buffer>
export interface SealOptionsJs {
data: Buffer
pcrSelection?: Array<number>
}
export declare function signKeyBlob(opts: SignKeyBlobOptionsJs): Promise<Buffer>

@@ -95,1 +143,3 @@

}
export declare function unseal(blob: Buffer): Promise<Buffer>
{
"name": "node-tpm2",
"version": "0.0.4-beta.4",
"version": "0.0.5-beta.0",
"description": "TPM 2.0 attestation for Node.js — prebuilt native bindings, PCR quotes, and fleet-ready Windows PCP keys. No tpm2-tools.",

@@ -84,12 +84,12 @@ "type": "module",

"optionalDependencies": {
"node-tpm2-windows-x64-msvc": "0.0.4-beta.4",
"node-tpm2-windows-arm64-msvc": "0.0.4-beta.4",
"node-tpm2-linux-x64-gnu": "0.0.4-beta.4",
"node-tpm2-linux-arm64-gnu": "0.0.4-beta.4",
"node-tpm2-linux-x64-musl": "0.0.4-beta.4",
"node-tpm2-linux-arm64-musl": "0.0.4-beta.4",
"node-tpm2-darwin-arm64": "0.0.4-beta.4",
"node-tpm2-win32-x64-msvc": "0.0.4-beta.4",
"node-tpm2-win32-arm64-msvc": "0.0.4-beta.4"
"node-tpm2-windows-x64-msvc": "0.0.5-beta.0",
"node-tpm2-windows-arm64-msvc": "0.0.5-beta.0",
"node-tpm2-linux-x64-gnu": "0.0.5-beta.0",
"node-tpm2-linux-arm64-gnu": "0.0.5-beta.0",
"node-tpm2-linux-x64-musl": "0.0.5-beta.0",
"node-tpm2-linux-arm64-musl": "0.0.5-beta.0",
"node-tpm2-darwin-arm64": "0.0.5-beta.0",
"node-tpm2-win32-x64-msvc": "0.0.5-beta.0",
"node-tpm2-win32-arm64-msvc": "0.0.5-beta.0"
}
}
+52
-17

@@ -21,3 +21,3 @@ # node-tpm2

**Pre-release** (`0.0.x-beta`). [Roadmap](./docs/roadmap.md) for remaining namespaces.
**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).

@@ -198,6 +198,8 @@ ---

| `tpm.pcr.read(...)` | ✓ | ✓ | ✓ |
| `tpm.pcr.extend(i, digest)` | ✓ * | ✓ * | ✓ |
| `tpm.pcr.extend(i, digest)` | ✓ † | ✗ → `REQUIRES_ELEVATION` | ✓ † |
| **nv** | | | |
| `tpm.nv.read(...)` | ✓ *planned* | ✓ *planned* | ✓ |
| `tpm.nv.write(...)` | ✓ *planned* | ✓ *planned* | ✓ |
| `tpm.nv.read(...)` | ✓ ‡ | ✓ ‡ | ✓ |
| `tpm.nv.write(...)` | ✓ ‡ | ✓ ‡ | ✓ |
| `tpm.nv.define(...)` | ✓ § | ✓ § | ✓ § |
| `tpm.nv.undefine(...)` | ✓ § | ✓ § | ✓ § |
| `tpm.attest.ekCertificate()` | ✓ | ✓ | ✓ |

@@ -208,6 +210,6 @@ | **keys** | | | |

| `key.sign(digest)` | ✓ | ✓ | ✓ |
| `key.decrypt(cipher)` | — *planned* | — *planned* | — *planned* |
| `key.decrypt(cipher)` | ✓ | ✓ | ✓ |
| **seal** | | | |
| `tpm.seal(...)` | ✓ *planned* | ✓ *planned* | ✓ |
| `tpm.unseal(blob)` | ✓ *planned* | ✓ *planned* | ✓ |
| `tpm.seal.seal(...)` | ✓ | ✓ | ✓ |
| `tpm.unseal(blob)` | ✓ | ✓ | ✓ |
| **attest** | | | |

@@ -223,4 +225,10 @@ | `tpm.attest.provisionAk()` user | ✓ | ✓ | ✓ |

**Planned rows** are design targets from the [roadmap](./docs/roadmap.md); unprivileged use matches the Phase 0 spike (`GetRandom`, `CreatePrimary` succeeded on Windows 11 without admin). Firmware or group policy can still deny specific PCR/NV operations — those surface as `TPM_RC` or `COMMAND_BLOCKED`, not silent failure.
**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.
**‡ `nv.read/write`:** Success depends on index attributes and auth. EK cert indices (`0x01c00002`, `0x01c0000A`) are read-only.
**§ `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.
**† `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.
---

@@ -275,4 +283,28 @@

Flat: `Tpm.createKey()`, `Tpm.signKeyBlob({ keyBlob, digest })`. RSA `decrypt` is not yet implemented.
const rsaKey = await tpm.keys.create({ type: 'rsa', sign: true, decrypt: true });
const plain = await rsaKey.decrypt(ciphertext);
Flat: `Tpm.createKey()`, `Tpm.signKeyBlob({ keyBlob, digest })`, `Tpm.decryptKeyBlob({ keyBlob, cipher })`.
### NV
```javascript
await tpm.nv.read('0x01c00002'); // EK cert index (read-only on most hardware)
await tpm.nv.readPublic('0x01800042'); // metadata before read/write
await tpm.nv.define({ handle: '0x01800042', size: 64 }); // owner NV — test machines only
await tpm.nv.write('0x01800042', data, 0);
await tpm.nv.undefine('0x01800042');
```
Flat: `Tpm.nvRead`, `Tpm.nvWrite`, `Tpm.nvReadPublic`, `Tpm.nvDefine`, `Tpm.nvUndefine`. See `examples/nv-smoke.mjs`.
### Seal
```javascript
const sealed = await tpm.seal.seal({ data: secret, pcrSelection: [23] });
const plain = await tpm.seal.unseal(sealed);
```
Flat: `Tpm.seal`, `Tpm.unseal`.
### Attestation

@@ -355,4 +387,4 @@

| `ACCESS_DENIED` | OS denied device or key access | — | sometimes | Linux: `tss` group; container: pass device |
| `REQUIRES_ELEVATION` | Windows operation needs Admin/SYSTEM | — | ✓ | Re-run enrollment elevated or as SYSTEM |
| `COMMAND_BLOCKED` | Windows TBS driver blocked the command ordinal | ✓ | — | Use NCrypt PCP path (e.g. activation) |
| `REQUIRES_ELEVATION` | Windows operation needs Admin/SYSTEM | — | ✓ | Re-run enrollment elevated or as SYSTEM; **`pcr.extend` from standard user** |
| `COMMAND_BLOCKED` | Windows TBS blocked raw ordinal (e.g. ActivateCredential) | ✓ | — | Use NCrypt PCP — elevation does not help |
| `NOT_SUPPORTED` | Feature or PCP capability missing on this platform | — | sometimes | — |

@@ -376,3 +408,5 @@ | `INVALID_ARGUMENT` | Bad JS/Rust option (e.g. empty machine `keyName`) | — | sometimes | Fix caller input |

| Format | `(rc & 0xFF00) === 0x0100` or FMT1 bit set | `MARSHALLING_ERROR` | `0x125` (`TPM_RC_ASYMMETRIC`) |
| Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` | `0x80280400` |
| Windows TBS blocked | `rc === 0x80280400` | `COMMAND_BLOCKED` * | `0x80280400` |
\* **`PCR_Extend`:** mapped to **`REQUIRES_ELEVATION`** (same `hresult` `0x80280400`) — Administrator can extend on Windows client; standard user should re-run elevated.
| Other | everything else | `TPM_RC` | vendor-specific |

@@ -404,5 +438,5 @@

## API reference (planned)
## API reference
Subsystem namespaces not yet on `TpmHandle`. See [docs/roadmap.md](./docs/roadmap.md) for phases and acceptance criteria.
Subsystem namespaces on `TpmHandle`. See [docs/api-reference.md](./docs/api-reference.md) for full detail.

@@ -412,5 +446,6 @@ | Namespace | Methods |

| `tpm.random` | `bytes(n)` ✅ |
| `tpm.keys` | `create`, `load`, `KeyHandle.sign` ✅ · `decrypt` planned |
| `tpm.pcr` | `extend(index, digest)` |
| `tpm.seal` | `seal`, `unseal` |
| `tpm.keys` | `create`, `load`, `KeyHandle.sign`, `KeyHandle.decrypt` ✅ |
| `tpm.pcr` | `read`, `extend` ✅ |
| `tpm.nv` | `read`, `write`, `readPublic`, `define`, `undefine` ✅ |
| `tpm.seal` | `seal`, `unseal` ✅ |

@@ -417,0 +452,0 @@ ---