3.2 KiB
Opaque key handles
(no submodules)
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_zeroizecallable.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 anunknown-typed boundary.
Usage: Basic Example
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
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 symbolbrand so the TypeScript compiler can refuse foreign objects masquerading asOpaqueKeyHandle. - 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
_zeroizefield 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
vaultlayer from needing directUint8Arrayaccess to derived keys; the orchestrator hands handles to AEAD operations and the AEAD layer callsunwrapimmediately before passing the bytes into the noble cipher.