Phase 2 red: crypto primitive tests and stub modules

Tests for the entire crypto/ public surface, written against the API
shape declared in the README. The accompanying src/crypto/ modules are
stubs that throw 'not implemented' so the test files compile and tests
fail with clear errors rather than module-not-found.

Tests cover:
  * init() resolves and is idempotent
  * fromBase64 / toBase64 / toBase64URL round-trips, including URL-safe
    input with stripped padding (the form Ente uses for auth tokens)
  * deriveKEK matches sodium.crypto_pwhash with Argon2id parameters
  * deriveLoginSubkey matches sodium.crypto_kdf_derive_from_key with
    subkey id 1 and ctx 'loginctx', truncated to 16 bytes
  * decryptBox round-trips, rejects tampering, wrong key, wrong nonce
  * decryptSealed round-trips, rejects wrong keypair and tampering
  * Secretstream pull decrypts multi-chunk streams in order, exposes
    per-chunk tags, rejects tampering, wrong key, and out-of-order chunks
  * Constants STREAM_CHUNK_SIZE (4 MiB) and STREAM_CHUNK_OVERHEAD (17)

Tests are commented to serve as the canonical API documentation per the
README development workflow policy. Verified: 29 tests fail (red), 3
trivial constant tests pass; lint and fmt-check are green.

eslint.config.mjs is updated to honour the leading-underscore convention
for intentionally unused parameters (the stubs).
This commit is contained in:
2026-05-09 12:43:52 -07:00
parent 64a3ace33a
commit 676d42c5eb
12 changed files with 695 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
/**
* Tests for `crypto.fromBase64`, `crypto.toBase64`, and
* `crypto.toBase64URL`.
*
* Ente delivers most binary fields as standard base64 strings (with `+`,
* `/`, and `=` padding). A few fields, notably the auth token returned by
* the login flow, are URL-safe base64 (with `-` and `_` instead of `+` and
* `/`, and stripped padding). quack must accept both forms on input and
* produce the right form on output.
*
* These tests pin the contract:
* - `toBase64(b)` produces standard base64 with padding.
* - `toBase64URL(b)` produces URL-safe base64 without padding.
* - `fromBase64(s)` accepts both forms transparently and round-trips.
*/
import { beforeAll, describe, expect, it } from "vitest";
import {
fromBase64,
init,
toBase64,
toBase64URL,
} from "../../src/crypto/index.js";
describe("crypto encoding helpers", () => {
beforeAll(async () => {
await init();
});
/**
* The test input contains bytes that produce `+`, `/`, and `=` in
* standard base64. Specifically the bytes `0xFB 0xFF 0xBF` encode to
* `+/+/` in standard base64 and `-_-_` in URL-safe base64. This makes
* the alphabet difference observable.
*/
const BYTES_WITH_AMBIGUOUS_CHARS = new Uint8Array([0xfb, 0xff, 0xbf]);
it("round-trips arbitrary bytes through standard base64", () => {
const original = new Uint8Array([0, 1, 2, 127, 128, 250, 255]);
const encoded = toBase64(original);
expect(typeof encoded).toBe("string");
expect(fromBase64(encoded)).toEqual(original);
});
it("toBase64 produces a standard-alphabet string", () => {
// Standard base64 may contain `+`, `/`, and trailing `=`. URL-safe
// base64 is forbidden from containing those characters. We test
// that toBase64 chose the standard alphabet.
const encoded = toBase64(BYTES_WITH_AMBIGUOUS_CHARS);
// For these specific bytes the encoding contains both `+` and `/`,
// proving the alphabet is the standard one.
expect(encoded).toMatch(/[+/]/);
});
it("toBase64URL produces a URL-safe string with no padding", () => {
const encoded = toBase64URL(BYTES_WITH_AMBIGUOUS_CHARS);
// URL-safe alphabet: no `+`, `/`, or `=`.
expect(encoded).not.toMatch(/[+/=]/);
// The substitutions `-` and `_` should appear.
expect(encoded).toMatch(/[-_]/);
});
it("fromBase64 accepts standard input", () => {
const standard = toBase64(BYTES_WITH_AMBIGUOUS_CHARS);
expect(fromBase64(standard)).toEqual(BYTES_WITH_AMBIGUOUS_CHARS);
});
it("fromBase64 accepts URL-safe input", () => {
const urlSafe = toBase64URL(BYTES_WITH_AMBIGUOUS_CHARS);
expect(fromBase64(urlSafe)).toEqual(BYTES_WITH_AMBIGUOUS_CHARS);
});
it("fromBase64 accepts URL-safe input even without padding", () => {
// Construct a URL-safe form with the padding stripped, as Ente
// delivers it. `fromBase64` must still decode it correctly.
const stripped = toBase64URL(BYTES_WITH_AMBIGUOUS_CHARS).replace(
/=+$/,
"",
);
expect(fromBase64(stripped)).toEqual(BYTES_WITH_AMBIGUOUS_CHARS);
});
it("fromBase64 rejects garbage", () => {
// Non-base64 characters should not silently decode to something. We
// do not commit to the exact error type but we do commit that the
// call cannot return data successfully.
expect(() => fromBase64("!!! not base64 !!!")).toThrow();
});
});