mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 00:13:09 +00:00
directory-keys-shared.test.ts: the store-down case now expects the store error instead of an 'invalid' signature, since a store outage no longer counts as a bad signature.
101 lines
4.2 KiB
TypeScript
101 lines
4.2 KiB
TypeScript
// @vitest-environment node
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { createHash, generateKeyPairSync, sign, type KeyObject } from 'node:crypto';
|
|
import { existsSync } from 'node:fs';
|
|
import { mkdtemp, rm } from 'node:fs/promises';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
import { canonicalJson } from '@/lib/extension-package';
|
|
import { setStoreForTests } from '@/lib/shared-store';
|
|
import { resetLocalStoreForTests } from '@/lib/shared-store/local';
|
|
import { separateFakeStore } from './shared-store-fake';
|
|
|
|
// Several replicas share one store (#1066): the newest keyring date seen
|
|
// from a directory, which stops a replayed old keyring, must hold on every
|
|
// replica, not only on the one that saw it.
|
|
|
|
function keypair() {
|
|
const { privateKey, publicKey } = generateKeyPairSync('ed25519');
|
|
const spki = publicKey.export({ type: 'spki', format: 'der' }) as Buffer;
|
|
const raw = spki.subarray(spki.length - 32);
|
|
return { privateKey, raw: raw.toString('base64'), keyId: createHash('sha256').update(raw).digest('hex').slice(0, 16) };
|
|
}
|
|
const sig = (key: KeyObject, text: string) => sign(null, Buffer.from(text, 'utf8'), key).toString('base64');
|
|
|
|
const DIR = 'https://directory.test';
|
|
const root = keypair();
|
|
const online = keypair();
|
|
const bytes = new TextEncoder().encode('zip bytes');
|
|
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
|
|
function ringAt(issuedAt: string) {
|
|
const body = {
|
|
_type: 'https://extensions.bulwarkmail.org/keyring/v1',
|
|
rootKeyId: root.keyId,
|
|
issuedAt,
|
|
keys: [{ keyId: online.keyId, publicKey: online.raw, status: 'active', notBefore: issuedAt, notAfter: null }],
|
|
};
|
|
return { ...body, signature: sig(root.privateKey, canonicalJson(body)) };
|
|
}
|
|
|
|
function signedHeaders() {
|
|
const statement = canonicalJson({
|
|
_type: 'https://extensions.bulwarkmail.org/release/v1', slug: 'jitsi-meet', version: '1.2.0', kind: 'plugin',
|
|
bundle: { sha256, size: bytes.byteLength }, source: { host: 'github.com', repo: 'o/r', commit: 'c'.repeat(40), subpath: '' },
|
|
approvedAt: '2026-10-14T00:00:00.000Z',
|
|
});
|
|
return new Headers({
|
|
'x-bundle-statement': Buffer.from(statement, 'utf8').toString('base64'),
|
|
'x-bundle-signature': sig(online.privateKey, statement),
|
|
'x-bundle-signature-key': online.keyId,
|
|
});
|
|
}
|
|
|
|
/** A replica's copy of the module: its own keyring cache, nothing in memory. */
|
|
async function replica() {
|
|
vi.resetModules();
|
|
const mod = await import('../directory-keys');
|
|
return (ring: unknown) => mod.checkBundleSignature({
|
|
directoryUrl: DIR, configuredRootKeys: root.raw, slug: 'jitsi-meet', version: '1.2.0', kind: 'plugin',
|
|
bytes, headers: signedHeaders(), fetchImpl: (async () => Response.json(ring)) as typeof fetch,
|
|
});
|
|
}
|
|
|
|
let dir: string;
|
|
let store: ReturnType<typeof separateFakeStore>;
|
|
|
|
beforeEach(async () => {
|
|
dir = await mkdtemp(path.join(tmpdir(), 'bw-keyrings-shared-'));
|
|
vi.stubEnv('ADMIN_STATE_DIR', path.join(dir, 'state'));
|
|
vi.stubEnv('ADMIN_CONFIG_DIR', path.join(dir, 'config'));
|
|
resetLocalStoreForTests();
|
|
store = separateFakeStore(path.join(dir, 'store'));
|
|
setStoreForTests(store);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
setStoreForTests(null);
|
|
resetLocalStoreForTests();
|
|
vi.unstubAllEnvs();
|
|
await rm(dir, { recursive: true, force: true });
|
|
});
|
|
|
|
describe('directory keyring dates in the shared store', () => {
|
|
it('refuses on another replica a keyring older than one this replica saw', async () => {
|
|
const a = await replica();
|
|
expect(await a(ringAt('2026-10-20T00:00:00.000Z'))).toMatchObject({ status: 'verified' });
|
|
|
|
const b = await replica();
|
|
expect(await b(ringAt('2026-10-14T00:00:00.000Z')))
|
|
.toMatchObject({ status: 'invalid', reason: expect.stringContaining('older than the one seen before') });
|
|
// Nothing was written to this replica's own state dir.
|
|
expect(existsSync(path.join(dir, 'state', 'directory-keyrings.json'))).toBe(false);
|
|
});
|
|
|
|
it('fails with the store outage while the store is down, so the install answers 503', async () => {
|
|
store.down = true;
|
|
const a = await replica();
|
|
await expect(a(ringAt('2026-10-20T00:00:00.000Z'))).rejects.toMatchObject({ name: 'StoreUnavailableError' });
|
|
});
|
|
});
|