Initial Commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Opaque key handles
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`opaque-handle.ts` lets the broader `vault` layer hold a typed reference
|
||||
to a SecureBuffer without ever seeing the raw bytes. The handle is a
|
||||
frozen, brand-symbol-tagged object containing only a stable `id`, a
|
||||
`length`, and a `_zeroize` thunk. The mapping back to the SecureBuffer is
|
||||
held in a module-level `WeakMap` keyed by the handle, so a leaked handle
|
||||
on its own does not yield key material.
|
||||
|
||||
## Features
|
||||
|
||||
- `wrap(SecureBuffer): OpaqueKeyHandle` — frozen handle with a unique
|
||||
symbol id, the buffer's length, and a `_zeroize` callable.
|
||||
- `unwrap(OpaqueKeyHandle): SecureBuffer` — package-internal resolver,
|
||||
validates the brand and throws when the handle is malformed or the
|
||||
underlying buffer has been GC'd.
|
||||
- `isValidHandle(value): value is OpaqueKeyHandle` — runtime brand check
|
||||
for values arriving across an `unknown`-typed boundary.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer";
|
||||
import { wrap } from "@suaveplan/crypto-vault/handle/opaque-handle";
|
||||
|
||||
const buf = new SecureBuffer(32);
|
||||
buf.fillRandom();
|
||||
const handle = wrap(buf);
|
||||
console.log(handle.length); // 32
|
||||
handle._zeroize();
|
||||
console.log(buf.isClosed); // true
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { SecureBuffer } from "@suaveplan/crypto-vault/secure/secure-buffer";
|
||||
import {
|
||||
isValidHandle,
|
||||
wrap,
|
||||
} from "@suaveplan/crypto-vault/handle/opaque-handle";
|
||||
|
||||
function passToVaultLayer(handle: unknown) {
|
||||
if (!isValidHandle(handle)) {
|
||||
throw new Error("vault layer received a non-handle value");
|
||||
}
|
||||
// The vault layer stores the handle but cannot read the underlying bytes.
|
||||
return handle;
|
||||
}
|
||||
|
||||
const wrapped = wrap(new SecureBuffer(32));
|
||||
passToVaultLayer(wrapped);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `wrap(buffer: SecureBuffer): OpaqueKeyHandle`
|
||||
|
||||
Returns a frozen handle. The handle's `_zeroize` thunk forwards to the
|
||||
buffer's `close()` method.
|
||||
|
||||
### `unwrap(handle: OpaqueKeyHandle): SecureBuffer`
|
||||
|
||||
Resolves the handle back to the underlying SecureBuffer. Throws
|
||||
`CryptoVaultError(CRYPTOVAULT.OPAQUE.INVALID_HANDLE)` for missing brand,
|
||||
unknown handle, or GC'd buffer. **Internal — vault-side packages
|
||||
within `@suaveplan/*` may import this; consumers outside the project
|
||||
should treat handles as opaque.**
|
||||
|
||||
### `isValidHandle(value: unknown): value is OpaqueKeyHandle`
|
||||
|
||||
Runtime brand check.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The handle uses a `unique symbol` brand so the TypeScript compiler can
|
||||
refuse foreign objects masquerading as `OpaqueKeyHandle`.
|
||||
- The `WeakMap<OpaqueKeyHandle, SecureBuffer>` ensures that once the
|
||||
handle becomes unreachable, the SecureBuffer link is also collected.
|
||||
Callers that want to keep the buffer alive must keep a strong reference
|
||||
to the handle.
|
||||
- The `_zeroize` field deliberately starts with an underscore to flag
|
||||
it as an implementation detail rather than a stable public method.
|
||||
- This module is the boundary that prevents the `vault` layer from
|
||||
needing direct `Uint8Array` access to derived keys; the orchestrator
|
||||
hands handles to AEAD operations and the AEAD layer calls `unwrap`
|
||||
immediately before passing the bytes into the noble cipher.
|
||||
Reference in New Issue
Block a user