Files
TREK/server/tests/unit/plugins/verify-signature.test.ts
Maurice 724c88c201 style: format server and shared with prettier
A mechanical prettier --write over server/src, server/tests, server/scripts
and shared/src with the workspaces' .prettierrc; nothing but layout and
import order changes, and every file keeps the same set of imports. shared
is formatted throughout and its format baseline is empty. The server
baseline keeps the 35 files that Prettier would grow past their lint:size
limit (they need a split first, which is not a formatting change), and the
generated entity files stay as their generator writes them.
2026-10-10 21:23:37 +02:00

83 lines
3.6 KiB
TypeScript

/**
* Author-signature verification (#plugins, #4). Ed25519 via node:crypto, in the
* minisign shapes TREK accepts: bare raw key+sig, minisign legacy ('Ed', signs
* the file), and minisign prehashed ('ED', signs BLAKE2b-512 of the file).
*/
import { verifyAuthorSignature, SignatureError } from '../../../src/nest/plugins/install/verify-signature';
import crypto from 'node:crypto';
import { describe, it, expect } from 'vitest';
// A raw Ed25519 keypair, exported as the 32-byte raw public key + a signer.
function makeKeypair() {
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519');
const rawPub = publicKey.export({ format: 'der', type: 'spki' }).subarray(-32); // last 32 bytes = the key
return {
rawPub,
sign: (msg: Buffer) => crypto.sign(null, msg, privateKey),
};
}
const KEY_ID = Buffer.from('0011223344556677', 'hex');
const b64 = (b: Buffer) => b.toString('base64');
describe('verifyAuthorSignature', () => {
const bytes = Buffer.from('the exact plugin.zip bytes');
it('accepts a bare raw Ed25519 key + 64-byte signature over the file', () => {
const { rawPub, sign } = makeKeypair();
expect(verifyAuthorSignature(bytes, b64(sign(bytes)), b64(rawPub))).toBe(true);
});
it('rejects a valid signature from a different key', () => {
const a = makeKeypair();
const b = makeKeypair();
expect(verifyAuthorSignature(bytes, b64(a.sign(bytes)), b64(b.rawPub))).toBe(false);
});
it('rejects a signature over tampered bytes', () => {
const { rawPub, sign } = makeKeypair();
const sig = b64(sign(bytes));
expect(verifyAuthorSignature(Buffer.from('tampered'), sig, b64(rawPub))).toBe(false);
});
it('verifies the minisign legacy format (Ed — signs the raw file)', () => {
const { rawPub, sign } = makeKeypair();
const pub = b64(Buffer.concat([Buffer.from('Ed'), KEY_ID, rawPub]));
const sig = b64(Buffer.concat([Buffer.from('Ed'), KEY_ID, sign(bytes)]));
expect(verifyAuthorSignature(bytes, sig, pub)).toBe(true);
});
it('verifies the minisign prehashed format (ED — signs BLAKE2b-512 of the file)', () => {
const { rawPub, sign } = makeKeypair();
const hash = crypto.createHash('blake2b512').update(bytes).digest();
const pub = b64(Buffer.concat([Buffer.from('Ed'), KEY_ID, rawPub]));
const sig = b64(Buffer.concat([Buffer.from('ED'), KEY_ID, sign(hash)]));
expect(verifyAuthorSignature(bytes, sig, pub)).toBe(true);
});
it('rejects when the key ids in key and signature disagree', () => {
const { rawPub, sign } = makeKeypair();
const pub = b64(Buffer.concat([Buffer.from('Ed'), KEY_ID, rawPub]));
const otherId = Buffer.from('8899aabbccddeeff', 'hex');
const sig = b64(Buffer.concat([Buffer.from('Ed'), otherId, sign(bytes)]));
expect(verifyAuthorSignature(bytes, sig, pub)).toBe(false);
});
it('parses a two-line minisign key (comment + base64)', () => {
const { rawPub, sign } = makeKeypair();
const pub = `untrusted comment: minisign public key ABC\n${b64(Buffer.concat([Buffer.from('Ed'), KEY_ID, rawPub]))}`;
const sig = `untrusted comment: signature\n${b64(Buffer.concat([Buffer.from('Ed'), KEY_ID, sign(bytes)]))}`;
expect(verifyAuthorSignature(bytes, sig, pub)).toBe(true);
});
it('throws SignatureError on a malformed key', () => {
expect(() => verifyAuthorSignature(bytes, b64(Buffer.alloc(64)), b64(Buffer.alloc(7)))).toThrow(SignatureError);
});
it('throws SignatureError on a malformed signature', () => {
const { rawPub } = makeKeypair();
expect(() => verifyAuthorSignature(bytes, b64(Buffer.alloc(10)), b64(rawPub))).toThrow(SignatureError);
});
});