You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
117 lines
6.9 KiB
117 lines
6.9 KiB
// Tests of security.ts: the empty area that writers write, the verdicts of
|
|
// the signature and of the seal without the context of a capsule, on the
|
|
// cases of TestSecurityVerdicts of the Go reference and a few more, and the
|
|
// Spanish lines of spec §29.7, as the reference gives them at spec-v0.11.
|
|
// The verdicts in the context of a capsule are tested by author.test.ts,
|
|
// securitycms.test.ts, security.failure.test.ts, fixtures.test.ts and
|
|
// vectors.test.ts.
|
|
|
|
import { describe, expect, it } from 'vitest';
|
|
import { h, hx } from './testing/testdata.ts';
|
|
import { arr, b, bn, map, t, u } from './testing/cborhex.ts';
|
|
import { encodeSecurity, evaluateSecurity, type Verdict, verdictLines, verdictText } from './security.ts';
|
|
|
|
const EMPTY = 'a20071646174656b6579732d73656375726974790101';
|
|
// An author-signature of alg 1 with a key of 32 zero bytes and a signature
|
|
// of 64: 105 bytes, as Go's EncodeAuthorSignature(1, ...) writes it.
|
|
const SIGNATURE = map([0, u(1)], [1, bn(32)], [2, bn(64)]);
|
|
const SEAL = map([0, u(1)], [1, b('010203')]);
|
|
|
|
// SECURITY_CBOR with keys 2 and 3 when given, as hex.
|
|
function security(signature?: string, seal?: string): Uint8Array {
|
|
const pairs: [number, string][] = [
|
|
[0, t('datekeys-security')],
|
|
[1, u(1)],
|
|
];
|
|
if (signature !== undefined) pairs.push([2, b(signature)]);
|
|
if (seal !== undefined) pairs.push([3, b(seal)]);
|
|
return h(map(...pairs));
|
|
}
|
|
|
|
describe('encodeSecurity', () => {
|
|
it('writes the empty area of 22 bytes, which yields F0 and S0', () => {
|
|
expect(hx(encodeSecurity())).toBe(EMPTY);
|
|
expect(SIGNATURE.length / 2).toBe(105);
|
|
expect(security(SIGNATURE).length).toBe(130);
|
|
});
|
|
});
|
|
|
|
describe('evaluateSecurity', () => {
|
|
it.each([
|
|
['empty', security(), 'F0', 'S0'],
|
|
['a signature of alg 1', security(SIGNATURE), 'F1', 'S0'],
|
|
['a seal of seal_type 1', security(undefined, SEAL), 'F0', 'S1'],
|
|
['both', security(SIGNATURE, SEAL), 'F1', 'S1'],
|
|
['alg 0', security(map([0, u(0)], [1, b('')], [2, b('')])), 'F1', 'S0'],
|
|
['a signature that is no map', security('01'), 'F1', 'S0'],
|
|
// The first row that holds decides: a seal with an unknown key and an
|
|
// unknown seal_type breaks its schema, S2, before its type is read.
|
|
['a seal with an unknown key', security(undefined, 'a300070141000200'), 'F0', 'S2'],
|
|
['seal_type 0', security(undefined, map([0, u(0)], [1, b('01')])), 'F0', 'S2'],
|
|
['a seal that is not CBOR', security(SIGNATURE, 'ff'), 'F1', 'S2'],
|
|
['a seal with seal_type 2^32', security(undefined, map([0, u(2 ** 32)], [1, b('')])), 'F0', 'S2'],
|
|
['a seal with a trailing byte', security(undefined, SEAL + '00'), 'F0', 'S2'],
|
|
['a seal without its token', security(undefined, map([0, u(1)])), 'F0', 'S2'],
|
|
['a seal with key 2 in place of its token', security(undefined, map([0, u(1)], [2, b('')])), 'F0', 'S2'],
|
|
['a seal with an empty token', security(undefined, map([0, u(7)], [1, b('')])), 'F0', 'S1'],
|
|
['version 2', h(map([0, t('datekeys-security')], [1, u(2)])), 'X', 'X'],
|
|
['another type tag', h(map([0, t('datekeys-head')], [1, u(1)])), 'X', 'X'],
|
|
['an unknown key 4', h(map([0, t('datekeys-security')], [1, u(1)], [4, b('01')])), 'X', 'X'],
|
|
['key 2 not a byte string', h(map([0, t('datekeys-security')], [1, u(1)], [2, u(1)])), 'X', 'X'],
|
|
['an empty key 2', h(map([0, t('datekeys-security')], [1, u(1)], [2, b('')])), 'X', 'X'],
|
|
['a key 2 of 65537 bytes', h(map([0, t('datekeys-security')], [1, u(1)], [2, bn(65537, 1)])), 'X', 'X'],
|
|
['a byte more', h(EMPTY + '00'), 'X', 'X'],
|
|
['not CBOR', new TextEncoder().encode('security'), 'X', 'X'],
|
|
['no key 1', h(map([0, t('datekeys-security')])), 'X', 'X'],
|
|
['an array', h(arr(t('datekeys-security'), u(1))), 'X', 'X'],
|
|
])('%s: %s and %s', (_, input, signature, seal) => {
|
|
expect(evaluateSecurity(input)).toEqual({ signature, seal });
|
|
});
|
|
});
|
|
|
|
describe('verdictLines', () => {
|
|
const F1 = 'No se ha comprobado ninguna firma: trátala como no firmada.';
|
|
const S1 = 'Lleva un sello de tiempo que esta versión no sabe comprobar: aquí no prueba nada.';
|
|
const S2 = 'El sello de tiempo es ilegible: no prueba nada.';
|
|
|
|
it('shows X alone, and otherwise the signature and then the seal unless it is S0', () => {
|
|
const lines = (signature: Verdict, seal: Verdict): string[] => verdictLines({ signature, seal });
|
|
expect(lines('X', 'X')).toEqual(['No se han podido comprobar la firma ni el sello: trátala como no firmada y sin fecha probada.']);
|
|
expect(lines('F0', 'S0')).toEqual(['Sin firma de autor.']);
|
|
expect(lines('F0', 'S1')).toEqual(['Sin firma de autor.', S1]);
|
|
expect(lines('F0', 'S2')).toEqual(['Sin firma de autor.', S2]);
|
|
expect(lines('F1', 'S0')).toEqual([F1]);
|
|
expect(lines('F1', 'S1')).toEqual([F1, S1]);
|
|
expect(lines('F1', 'S2')).toEqual([F1, S2]);
|
|
expect(verdictText('S0')).toBe('');
|
|
});
|
|
|
|
// F6 and S4 write the names that the reader found (spec §29.7, §29.10): a signer sealed before the round time or not, a signer who does not count.
|
|
it('writes the lines of F6 and S4 from the signers and the authority that were found', () => {
|
|
const t = { seconds: 1_790_000_000, nanos: 0 };
|
|
const line = (holder: string, before: boolean, result = 'valid') => ({ holder, issuer: `emisor de ${holder}`, result, sealTime: t, before });
|
|
const lines = verdictLines({
|
|
signature: 'F6',
|
|
seal: 'S4',
|
|
detail: { signers: [line('Ana', true), line('Luis', false)], foreign: [line('Otro', true, 'invalid')], sealHolder: 'TSA', sealTime: t },
|
|
});
|
|
expect(lines).toEqual([
|
|
'Firmado con un certificado a nombre de Ana, Luis. DateKeys no comprueba quién lo emitió: para eso, exporta la firma a un validador oficial.',
|
|
' Ana (emisor según su certificado: emisor de Ana), sellado el 2026-09-21T14:13:20Z, antes de la fecha de apertura.',
|
|
' Luis (emisor según su certificado: emisor de Luis), sellado el 2026-09-21T14:13:20Z, no antes de la fecha de apertura.',
|
|
' Otro firmante, Otro: invalid. No cuenta.',
|
|
'Según un sello a nombre de TSA, existía el 2026-09-21T14:13:20Z, antes de que la cápsula pudiera abrirse. DateKeys no comprueba quién emitió el sello.',
|
|
]);
|
|
});
|
|
|
|
// The verdicts of spec v0.11 (§29.7) whose text is fixed, and those whose text names a key, a holder or a time, F3, F4, F6
|
|
// and S4, which verdictLines writes from what the reader found and verdictText leaves empty.
|
|
it('has the text of the verdicts of v0.11 that do not name anything, and none for those that do', () => {
|
|
expect(verdictText('F2')).toBe('La firma no corresponde a este contenido.');
|
|
expect(verdictText('F5')).toBe('Faltan firmas o sellos que la propia cápsula exige: trátala como no firmada.');
|
|
expect(verdictText('S3')).toBe('El sello no corresponde a este contenido.');
|
|
expect(verdictText('S5')).toBe('Sellado después de la fecha de apertura: no prueba nada anterior.');
|
|
for (const v of ['F3', 'F4', 'F6', 'S4'] as const) expect(verdictText(v), v).toBe('');
|
|
});
|
|
});
|