first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
# SecureBuffer
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`SecureBuffer` is the canonical envelope for sensitive cryptographic
material in `@suaveplan/crypto-vault`. It owns a private `Uint8Array`,
exposes an opt-in `bytes` accessor, and overwrites the backing storage
with zero bytes the moment `close()` is called. A module-level registry
of `WeakRef<SecureBuffer>` lets `zeroizeAll()` sweep every live buffer in
one call — useful on lock, on a security event, or as a final
process-shutdown hook. Strings and JSON serializations are masked so the
buffer can never leak through accidental logging.
## Features
- Eager zeroization via `Uint8Array#fill(0)` on `close()`.
- Idempotent close (`close()` after `close()` is a no-op).
- Clone isolation — `clone()` allocates a fresh independent backing store.
- Slice copy semantics — `slice()` returns a fresh `Uint8Array`; the caller
cannot mutate the buffer through the result.
- Random fill via `fillRandom(rng?)` (uses `crypto.getRandomValues` by
default; honours an injected `Rng` with `nextBytes`).
- `toString` and `toJSON` print only `length` and `closed` flags.
- Global `zeroizeAll()` sweeps every reachable `SecureBuffer` and closes it.
## Usage: Basic Example
```ts
import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer";
const buf = new SecureBuffer(32);
buf.fillRandom();
// ... use buf.bytes ...
buf.close();
```
## Usage: Advanced Example
```ts
import {
SecureBuffer,
zeroizeAll,
} from "@suaveplan/crypto-vault/secure/secure-buffer";
// Initialise from existing key material — copied, never shared.
const seed = new Uint8Array(32).fill(7);
const masterBuf = new SecureBuffer(seed);
// Clone for an isolated branch derivation.
const branch = masterBuf.clone();
branch.bytes[0] = 1;
console.log(masterBuf.bytes[0]); // 7 — clone is fully isolated
// Lock everything in one shot.
zeroizeAll();
console.log(masterBuf.isClosed); // true
console.log(branch.isClosed); // true
```
## API Reference
### `class SecureBuffer`
#### `new SecureBuffer(lengthOrSource, options?)`
- `lengthOrSource: number | Uint8Array`. A `number` allocates a fresh
zero-filled buffer of that length. A `Uint8Array` is **copied** into the
internal store — the buffer never aliases caller storage.
- `options?: SecureBufferOptions = { clock?, rng? }`. Reserved for future
allocation telemetry; `fillRandom` reads `rng` directly.
Throws `CryptoVaultError(CRYPTOVAULT.SECURE.INVALID_LENGTH)` when the
numeric length is negative or non-integer.
#### `bytes: Uint8Array`
Mutable view of the live bytes. Throws
`CryptoVaultError(CRYPTOVAULT.SECURE.ZEROIZED)` once the buffer has been
closed.
#### `length: number`, `isClosed: boolean`
Length of the backing store and the closed flag.
#### `close(): void`
Overwrite the bytes with zeros and mark closed. Idempotent.
#### `clone(): SecureBuffer`
Allocate a fresh `SecureBuffer` with a copy of the current bytes.
#### `slice(start?, end?): Uint8Array`
Copy a range out into a new `Uint8Array`. The result is not registered.
#### `fillRandom(rng?): void`
Fill the bytes with CSPRNG output. Honours `rng?.nextBytes(n)` when
provided; otherwise falls back to `crypto.getRandomValues`.
#### `toString()`, `toJSON()`
Return `[SecureBuffer length=<n> closed=<bool>]` and a structurally
equivalent JSON object — never the raw bytes.
### `zeroizeAll(): void`
Walk every live `WeakRef<SecureBuffer>` in the module-level registry, call
`close()` on each non-closed instance, and prune the registry.
### `liveSecureBufferCount(): number`
Test helper — prune dead refs and return the number of live entries.
## Implementation Notes
- The registry stores `WeakRef`s so registered buffers can still be
garbage-collected normally.
- JavaScript cannot guarantee the GC has not relocated memory before
`close()`, so eager zeroization is best-effort. Combine with aggressive
scoping: allocate inside a function, close, return nothing sensitive.
- Memory-locking (`mlock`) is intentionally out of scope. A future native
binding will plug in via the `SecureBufferOptions` bag without breaking
callers.