Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// The opening against Go: every case of test/vectors/open_cases.json, which
|
|
|
|
|
// tool/open_go_vectors.go writes with capsule.Open of the reference, opened
|
|
|
|
|
// in memory and from a source read in pieces: the same result, text, checks
|
|
|
|
|
// with their details, release requests, state of the sink, content or files,
|
|
|
|
|
// and unusable extensions, and an output that is closed only when a capsule
|
|
|
|
|
// of format 1 or 2 opens and aborted after any failure (spec §56).
|
|
|
|
|
@TestOn('vm')
|
|
|
|
|
library;
|
|
|
|
|
|
|
|
|
|
import 'dart:convert';
|
|
|
|
|
import 'dart:io';
|
|
|
|
|
import 'dart:typed_data';
|
|
|
|
|
|
|
|
|
|
import 'package:datekeys/datekeys.dart';
|
|
|
|
|
import 'package:datekeys/src/sha256.dart' show sha256;
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
import 'package:test/test.dart';
|
|
|
|
|
|
|
|
|
|
import 'open_support.dart';
|
|
|
|
|
|
|
|
|
|
Json readJson(String path) => jsonDecode(File(path).readAsStringSync()) as Json;
|
|
|
|
|
|
|
|
|
|
final _fixtures = <String, Uint8List>{};
|
|
|
|
|
|
|
|
|
|
Uint8List fixture(String file) => _fixtures.putIfAbsent(
|
|
|
|
|
file,
|
|
|
|
|
() => File('testdata/fixtures/$file').readAsBytesSync(),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
void main() {
|
|
|
|
|
final vectors = readJson('test/vectors/open_cases.json');
|
|
|
|
|
final fixtures = (vectors['fixtures']! as List).cast<Json>();
|
|
|
|
|
final steps = (vectors['steps']! as List).cast<Json>();
|
|
|
|
|
|
|
|
|
|
test('open_cases.json is of this spec, from the generator of this repo', () {
|
|
|
|
|
expect(vectors['spec'], specVersion);
|
|
|
|
|
expect(vectors['generator'], 'tool/open_go_vectors.go');
|
|
|
|
|
expect(fixtures, hasLength(greaterThanOrEqualTo(48)));
|
|
|
|
|
expect(steps, hasLength(greaterThan(100)));
|
|
|
|
|
// Every fixture opens with each of its credentials.
|
|
|
|
|
final names = {for (final c in fixtures) str(c, 'name').split(':').first};
|
|
|
|
|
final files = Directory('testdata/fixtures')
|
|
|
|
|
.listSync()
|
|
|
|
|
.map((f) => f.uri.pathSegments.last)
|
|
|
|
|
.where((n) => n.endsWith('.dkc'))
|
|
|
|
|
.map((n) => n.substring(0, n.length - 4))
|
|
|
|
|
.toSet();
|
|
|
|
|
expect(names, files);
|
|
|
|
|
expect(fixtures.every((c) => c['result'] == 'ok'), isTrue);
|
|
|
|
|
// The steps that the mutation corpus does not reach are here.
|
|
|
|
|
final failed = {
|
|
|
|
|
for (final c in steps)
|
|
|
|
|
if (c['result'] != 'ok') ((c['checks']! as List).last as List)[0],
|
|
|
|
|
};
|
|
|
|
|
expect(failed, containsAll(<int>[2, 3, 7, 8, 9, 10, 11, 12, 13, 14]));
|
|
|
|
|
expect(failed, containsAll(<int>[15, 17]));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('every fixture opens to its plaintext, or to the files of its record, '
|
|
|
|
|
'with each credential', () async {
|
|
|
|
|
for (final c in fixtures) {
|
|
|
|
|
final name = str(c, 'name').split(':').first;
|
|
|
|
|
final r = readJson('testdata/fixtures/$name.json');
|
|
|
|
|
final plaintext = fixture(str(r, 'plaintext_file'));
|
|
|
|
|
expect(toHex(sha256(plaintext)), r['plaintext_sha256'], reason: name);
|
|
|
|
|
final o = await openCase(c, capsuleOf(c, fixture));
|
|
|
|
|
expect(o.opened.ok, isTrue, reason: str(c, 'name'));
|
|
|
|
|
if (r['format'] != 3) {
|
|
|
|
|
expect(o.output.received.toBytes(), plaintext, reason: str(c, 'name'));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Format 3: each file is the range of BODY that the record names.
|
|
|
|
|
final at = r['content_offset']! as int;
|
|
|
|
|
final files = (r['files'] as List? ?? const []).cast<Json>();
|
|
|
|
|
expect(o.sink.files, hasLength(files.length), reason: str(c, 'name'));
|
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
|
|
|
final f = files[i];
|
|
|
|
|
expect(
|
|
|
|
|
o.sink.files[i].toBytes(),
|
|
|
|
|
plaintext.sublist(at + (f['start']! as int), at + (f['end']! as int)),
|
|
|
|
|
reason: '${c['name']}: ${f['path']}',
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
expect(
|
|
|
|
|
[o.opened.head!.comment, o.opened.head!.author, o.opened.areaLen],
|
|
|
|
|
[r['comment'] ?? '', r['declared_author'] ?? '', r['area_len']],
|
|
|
|
|
reason: str(c, 'name'),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
Stage 4c: steps 9 to 18, the opening of the three formats
lib/src/open.dart and open3.dart port Open and openBody of package capsule
of datekeys-go at c531e93, with the checks, codes, steps and texts of the
reference and the detail of each check: the .dkk of step 9.a, decoded or
still encoded, with its material, its critical extensions, its capsule_id
and its capsule_digest; at least one credential; the clock and the release,
with the rule of step 9 for the failures of its source (sourceFailure);
its verification at step 10; OUTER_TIME_AGE with the tlock stanza;
INNER_ACCESS_AGE with the X25519 identities, those of the .dkk and the key
of words, and the rules of the slots; CONTROL_CBOR, header_binding,
I_PAYLOAD and P; PAYLOAD_AGE streamed, with the padding of format 2
checked and never delivered, and in format 3 the frame of BODY, the area,
the head, each file to the sink with its SHA-256 and the padding, with the
precedence of spec §63; and the commit. A failure of age keeps the code of
the identity that reports it, or is ERR_INTEGRITY with the reason of its
phase, as classify of Go.
openCapsule opens a capsule in memory and openCapsuleSource one that a
ByteSource reads: the prefix of the inspection and the nonce of
PAYLOAD_AGE before the release, then pieces of 1 MiB. lib/src/sink.dart
has ByteSink, for the content of formats 1 and 2 and for each file, and
FileSink, for the files of format 3, as the dst and the Sink of Go, with
MemoryByteSink and MemoryFileSink. Nothing is presented as valid before
step 17 ends: the output is closed only then and aborted after any
failure, and the sink aborted after any failure that follows its begin
(spec §56).
The signature and the seal are stage 5: lib/src/verdicts.dart has the
verdicts and the SecurityEvaluator, given what newSecurityContext and
EvaluateSecurityIn of Go take, which never fails the opening; the default
evaluates nothing. OpenOptions.accept is Accept of Go. And
AgePayloadDecryptor.wipe clears the key of a STREAM left unread.
The tests run open_cases.json and the mutation corpus with the texts and
the checks of Go, in memory and from a source read in pieces; capsules of
several MiB made from the fixtures, for the streaming; a capsule with a
stanza for a key of words; and the caller, the sinks and the evaluator.
open_test.dart runs on Node.js too, with open_vectors.g.dart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
test('the comparison sees every field', () async {
|
|
|
|
|
final c = steps.firstWhere((c) => c['name'] == 'format 3: two files');
|
|
|
|
|
final o = await openCase(c, capsuleOf(c, fixture));
|
|
|
|
|
expect(differences(c, o), isEmpty);
|
|
|
|
|
for (final (key, value) in <(String, Object?)>[
|
|
|
|
|
('result', 'ERR_INTEGRITY'),
|
|
|
|
|
('text', 'ok.'),
|
|
|
|
|
('checks', (c['checks']! as List).sublist(1)),
|
|
|
|
|
('release_requests', 0),
|
|
|
|
|
('sink', 'aborted'),
|
|
|
|
|
('files', <Object?>[]),
|
|
|
|
|
('unusable', {'head': <Object?>[]}),
|
|
|
|
|
]) {
|
|
|
|
|
expect(differences({...c, key: value}, o), hasLength(1), reason: key);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
for (final (section, cases) in [('fixtures', fixtures), ('steps', steps)]) {
|
|
|
|
|
group(section, () {
|
|
|
|
|
for (final c in cases) {
|
|
|
|
|
test(str(c, 'name'), () async {
|
|
|
|
|
final dkc = capsuleOf(c, fixture);
|
|
|
|
|
final memory = await openCase(c, dkc);
|
|
|
|
|
expect(differences(c, memory), isEmpty, reason: 'in memory');
|
|
|
|
|
final source = await openCase(c, dkc, fromSource: true);
|
|
|
|
|
expect(differences(c, source), isEmpty, reason: 'from a source');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|