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.
232 lines
8.2 KiB
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);
|
|
});
|
|
}
|
|
});
|
|
}
|