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).
90 lines
3.5 KiB
TypeScript
90 lines
3.5 KiB
TypeScript
/**
|
|
* 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();
|
|
});
|
|
});
|