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.
DateKeys-App/src/lib/dkc/security.test.ts

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('');
});
});

Powered by TurnKey Linux.