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-dart/test/security_vm_test.dart

232 lines
8.2 KiB

// 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<Json>();
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<Json>();
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<String>();
final bases = [
for (final b in (vectors['bases']! as List).cast<String>()) 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<Json>();
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 <SignerLine>[], isEmpty);
final sealedAt = <Instant>[
if (seal != null) parseRfc3339(str(seal, 'time')),
for (final s in (results ?? const []).cast<Json>())
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);
});
}
});
}

Powered by TurnKey Linux.