Files
suaveplan/packages/security-stack/crypto/crypto-vault/src/handle/opaque-handle.md
2026-05-27 23:47:53 +01:00

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 _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

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 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.