first commit
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user