Initial Commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
export { zeroize } from "./zeroize.js";
|
||||
@@ -0,0 +1,61 @@
|
||||
# zeroize
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Best-effort overwrite of a `Uint8Array` with zero bytes. Secret material — private keys, AEAD keys, plaintext buffers, intermediate KDF state — should be zeroized as soon as it is no longer needed so that its remnants become unreadable if the process is later snapshotted, dumped, or forked into an attacker-controlled context.
|
||||
|
||||
JavaScript does not let us pin memory or guarantee the garbage collector has not already copied the underlying `ArrayBuffer` into a new region. `zeroize` is therefore a mitigation, not a guarantee. Use it in combination with narrow scoping (allocate the buffer inside a function, zeroize, return nothing sensitive) to minimise the residency window.
|
||||
|
||||
## Features
|
||||
|
||||
- In-place; no allocation.
|
||||
- Backed by `Uint8Array#fill(0)`, which is inlined to a tight memset on every modern engine.
|
||||
- Zero dependencies.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
|
||||
const key = new Uint8Array(32);
|
||||
// …use the key…
|
||||
zeroize(key);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { zeroize } from "@suaveplan/crypto-utils/zeroize";
|
||||
import { concatBytes } from "@suaveplan/crypto-utils/bytes";
|
||||
|
||||
function withScopedSecret<T>(
|
||||
secretBuilder: () => Uint8Array,
|
||||
fn: (secret: Uint8Array) => T
|
||||
): T {
|
||||
const secret = secretBuilder();
|
||||
try {
|
||||
return fn(secret);
|
||||
} finally {
|
||||
zeroize(secret);
|
||||
}
|
||||
}
|
||||
|
||||
withScopedSecret(
|
||||
() => concatBytes(new Uint8Array(16).fill(0xaa), new Uint8Array(16).fill(0xbb)),
|
||||
(key) => {
|
||||
// key is live inside this block only
|
||||
return key.length;
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `zeroize(buf: Uint8Array): void` — fills `buf` with `0` in place. Returns nothing. Never throws.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`Uint8Array#fill(0)` is the minimum the runtime can do without dropping into `node:buffer` or platform-specific APIs. Some environments (OpenSSL-linked `node:crypto`) expose `sodium_memzero`-equivalents that are marked volatile to prevent compiler dead-store elimination; we do not rely on those here because they are not available in browsers or edge runtimes, and cross-runtime consistency is more valuable than the marginal hardening on Node. Engines do not currently eliminate `Uint8Array#fill` as dead, but a future JIT optimisation could; revisit this helper if `dudect`-style analysis reports leakage.
|
||||
@@ -0,0 +1,23 @@
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
import { zeroize } from "./zeroize.js";
|
||||
|
||||
describe("zeroize", () => {
|
||||
it("overwrites a populated buffer", { timeout: 5000 }, () => {
|
||||
const buf = new Uint8Array([1, 2, 3, 4, 5]);
|
||||
zeroize(buf);
|
||||
expect(Array.from(buf)).toEqual([0, 0, 0, 0, 0]);
|
||||
});
|
||||
|
||||
it("is a no-op on empty buffer", { timeout: 5000 }, () => {
|
||||
const buf = new Uint8Array();
|
||||
zeroize(buf);
|
||||
expect(buf.length).toBe(0);
|
||||
});
|
||||
|
||||
it("mutates in place (same reference)", { timeout: 5000 }, () => {
|
||||
const buf = new Uint8Array([1, 2, 3]);
|
||||
const ref = buf;
|
||||
zeroize(buf);
|
||||
expect(ref.buffer).toBe(buf.buffer);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Overwrite `buf` in-place with zero bytes.
|
||||
*
|
||||
* JavaScript cannot guarantee the garbage collector has not already relocated
|
||||
* or copied the underlying memory, so this is a best-effort operation. Its
|
||||
* purpose is to minimise the time window during which sensitive bytes
|
||||
* (secret keys, plaintext, derived material) remain readable on the heap
|
||||
* after they are no longer needed. Combine with aggressive buffer
|
||||
* scoping — allocate inside a function, zeroize, return nothing sensitive.
|
||||
*/
|
||||
export function zeroize(buf: Uint8Array): void {
|
||||
buf.fill(0);
|
||||
}
|
||||
Reference in New Issue
Block a user