// The security area against Go, on the VM: every case of // test/vectors/security_vectors.json, which tool/security_go_vectors.go // writes with package capsule of the reference, of which // security_vectors.g.dart holds a part; testdata/vectors/security.json; and // the security area of every fixture of format 3, with the commitments, the // message, the signature and the seal that its record gives. @TestOn('vm') library; import 'dart:convert'; import 'dart:io'; import 'package:datekeys/datekeys.dart'; import 'package:datekeys/src/bech32.dart' show bech32Encode; import 'package:datekeys/src/curve25519.dart' show verifyStrict; import 'package:test/test.dart'; import 'open_vectors_support.dart'; import 'security_support.dart'; import 'vectors/security_vectors.g.dart'; Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json; void main() { final vectors = readJson('test/vectors/security_vectors.json'); final evaluations = (vectors['evaluate']! as List).cast(); test('security_vectors.json is of this spec, with every kind of case', () { expect(vectors['spec'], specVersion); expect(vectors['generator'], 'tool/security_go_vectors.go'); expect(evaluations, hasLength(greaterThan(1500))); final verdicts = { for (final c in evaluations) '${c['signature']} ${c['seal']}', }; for (final v in ['X X', 'F0 S0', 'F1 S1', 'F2 S0', 'F3 S0', 'F4 S0']) { expect(verdicts, contains(v)); } for (final v in ['F0 S2', 'F2 S2', 'F3 S2', 'F4 S1']) { expect(verdicts, contains(v)); } final cms = [ for (final c in evaluations) ...((c['cms'] as List?) ?? const []), ]; expect(cms.where((p) => p == 'signature'), hasLength(greaterThan(20))); expect(cms.where((p) => p == 'seal'), hasLength(greaterThan(20))); expect( evaluations.where((c) => c['context'] == null), hasLength(greaterThan(100)), ); }); test('security_vectors.g.dart holds a part of the file', () { final part = jsonDecode(securityVectorsJson) as Json; for (final k in [ 'spec', 'generator', 'contexts', 'texts', 'bases', 'commitments', 'encode', 'lines', 'holder', ]) { expect(canonical(part[k]), canonical(vectors[k]), reason: k); } final all = [for (final c in evaluations) canonical(c)]; final some = (part['evaluate']! as List).cast(); expect(some, isNotEmpty); var at = 0; for (final c in some) { final k = canonical(c); while (at < all.length && all[at] != k) { at++; } expect(at, lessThan(all.length), reason: k); } }); test('every evaluation, with the verdicts and lines of Go', () { final contexts = contextsOf(vectors); final texts = (vectors['texts']! as List).cast(); final bases = [ for (final b in (vectors['bases']! as List).cast()) fromHex(b), ]; for (final c in evaluations) { final security = securityOf(c, bases); expect( evaluateDifferences(c, security, contexts, texts), isEmpty, reason: canonical(c), ); } }); test('security.json: the verdicts and the lines of each area in its ' 'context', () { final f = readJson('testdata/vectors/security.json'); expect(f['spec'], specVersion); final ctx = f['context']! as Json; final context = SecurityContext( controlCommit: fromHex(str(ctx, 'control_commit')), headDigest: fromHex(str(ctx, 'head_digest')), roundTime: parseRfc3339(str(ctx, 'round_time')), ); final cases = (f['vectors']! as List).cast(); expect(cases, hasLength(24)); var cms = 0; for (final c in cases) { final security = fromHex(str(c, 'hex')); final parts = cmsParts(security); if (parts.signature || parts.seal) cms++; final v = evaluateSecurity(security, context: context); // The key of F4 is in its line. expect( verdictDifferences(c, v, keys: false), isEmpty, reason: str(c, 'name'), ); expect(v.evaluated, isTrue, reason: str(c, 'name')); } // A seal of seal_type 2 whose token is not DER: S2, from the reader of // CMS. expect(cms, 1); }); group('the security area of each fixture of format 3', () { final names = Directory('testdata/fixtures') .listSync() .map((f) => f.uri.pathSegments.last) .where((n) => n.startsWith('format3_') && n.endsWith('.json')) .where((n) => !n.endsWith('.inspect.json') && !n.contains('.dkk')) .toList() ..sort(); test('there are 14, 3 signed and 1 sealed', () { expect(names, hasLength(14)); final records = [for (final n in names) readJson('testdata/fixtures/$n')]; expect(records.where((r) => r['signature'] != null), hasLength(3)); expect(records.where((r) => r['seal'] != null), hasLength(1)); }); for (final n in names) { test(n, () { final r = readJson('testdata/fixtures/$n'); final control = decodeControl( fromHex(str(r, 'control_cbor')), CapsuleFormat.format3, ); final head = fromHex(str(r, 'head_cbor')); final security = fromHex(str(r, 'security_cbor')); final cc = controlCommit(control, CapsuleFormat.format3); final hd = headDigest(head); final w = decodeSecurity(security); final sig = r['signature'] as Json?; if (sig != null) { expect(toHex(cc), sig['control_commit']); expect(toHex(hd), sig['head_digest']); expect(toHex(w!.signature!), sig['security_key_2']); final a = decodeAuthorSignature(w.signature!)!; expect([a.alg, toHex(a.value)], [sig['alg'], sig['signature']]); final sd = a.alg == algEd25519 ? signersDigest(algEd25519) : signersDigest(algCms, a.key); expect(toHex(sd), sig['signers_digest']); final m = authorMessage(cc, hd, sd); expect(String.fromCharCodes(m), sig['author_message']); expect(authorCode(m), sig['author_code']); if (a.alg == algEd25519) { expect(bech32Encode('dkauthor', a.key), sig['author_key']); expect(verifyStrict(a.key, m, a.value), isTrue); } else { expect(toHex(a.key), sig['signers']); } } final seal = r['seal'] as Json?; if (seal != null) { final s = decodeSeal(w!.seal!)!; expect( [s.sealType, toHex(s.token)], [seal['seal_type'], seal['token']], ); expect( toHex(sealSubject(cc, hd, sigPart(w.signature))), seal['seal_subject'], ); } // The verdicts of the record, in the context of the capsule, with // the results of the signers of alg 2 and the authority and t of a // seal of seal_type 2, which are also the earliest seal. final context = SecurityContext( controlCommit: cc, headDigest: hd, roundTime: parseRfc3339(str(r, 'unlock_at')), ); final v = evaluateSecurity(security, context: context); final want = r['verdicts']! as Json; expect(verdictDifferences(want, v, keys: false), isEmpty); final key = v.authorKey; expect( key == null ? null : bech32Encode('dkauthor', key), want['author_key'], ); final d = v.detail; final results = sig?['signer_results'] as List?; expect( canonical( d == null || d.signers.isEmpty ? null : signerResults(d.signers), ), canonical(results), ); expect(d?.foreign ?? const [], isEmpty); final sealedAt = [ if (seal != null) parseRfc3339(str(seal, 'time')), for (final s in (results ?? const []).cast()) if (s['result'] == 'valid') parseRfc3339(str(s, 'seal_time')), ]..sort(compareInstants); if (seal != null) { expect( [d?.sealHolder, timeText(d?.sealTime)], [seal['holder'], seal['time']], ); } expect(v.sealedAt, sealedAt.firstOrNull); }); } }); }