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/open_test.dart

564 lines
17 KiB

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>
3 days ago
// The head, the note, the inspection and the opening, on the VM and
// compiled to JavaScript: the parts of the vectors of Go that
// test/vectors/open_vectors.g.dart holds, with the small fixtures they edit,
// and what the API does with the caller: its errors, the output and the
// sinks, the evaluator of the security area and accept.
library;
import 'dart:convert';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
import 'package:test/test.dart';
import 'open_support.dart';
import 'tlock_support.dart' show applyEdits;
import 'vectors/open_vectors.g.dart';
final Map<String, Uint8List> _fixtures = {
for (final e in (jsonDecode(openFixturesJson) as Json).entries)
e.key: fromHex(e.value! as String),
};
Uint8List fixture(String file) => _fixtures[file]!;
final Json _cases = jsonDecode(openCasesJson) as Json;
/// The case of the part of open_cases.json named [name].
Json caseNamed(String name) => [
...(_cases['fixtures']! as List).cast<Json>(),
...(_cases['steps']! as List).cast<Json>(),
].firstWhere((c) => c['name'] == name);
/// The options of the opening of the case [name], with [output] and [sink].
OpenOptions optionsOf(
Json c, {
ByteSink? output,
FileSink? sink,
SecurityEvaluator? evaluator,
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>
3 days ago
Map<String, String> authorKeys = const {},
void Function(Verdicts v)? accept,
ReleaseSource? source,
}) {
final rel = c['release']! as Json;
final file = c['dkk_file'] as String?;
return OpenOptions(
source:
source ??
suppliedRelease(
Release(rel['round']! as int, fromHex(str(rel, 'signature'))),
),
now: () => parseRfc3339(str(c, 'now')),
accessKeyFile: file == null ? null : fromHex(file),
output: output,
sink: sink,
evaluator: evaluator ?? evaluateSecurityInput,
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>
3 days ago
authorKeys: authorKeys,
accept: accept,
);
}
void main() {
group('the vectors of Go', () {
for (final section in ['fixtures', 'steps']) {
final cases = (_cases[section]! as List).cast<Json>();
for (var i = 0; i < cases.length; i++) {
final c = cases[i];
test('$section: ${c['name']}', () async {
final dkc = capsuleOf(c, fixture);
expect(differences(c, await openCase(c, dkc)), isEmpty);
// From a source, every fourth: the others run on the VM.
if (i % 4 == 0) {
expect(
differences(c, await openCase(c, dkc, fromSource: true)),
isEmpty,
);
}
});
}
}
test('the heads', () {
final h = jsonDecode(openHeadsJson) as Json;
checkDecodeCases((h['decode']! as List).cast<Json>());
checkEncodeCases((h['encode']! as List).cast<Json>());
});
test('the notes', () {
final n = jsonDecode(openNotesJson) as Json;
checkNoteCases(
(n['check']! as List).cast<Json>(),
(n['note']! as List).cast<Json>(),
(n['standard']! as List).cast<Json>(),
);
});
test('the inspection: the views and the mutations', () {
final v = jsonDecode(openInspectJson) as Json;
for (final c in (v['views']! as List).cast<Json>()) {
final r = inspectCapsule(
capsuleOf(c, fixture),
extensions: switch (c['registry']) {
'standard' => const StandardExtensions(),
'reject_all' => const RejectAll('not today'),
_ => null,
},
);
expect(
inspectJson(inspectView(r, file: 'capsule.dkc')),
c['view'],
reason: str(c, 'name'),
);
}
final mutations = (v['mutations']! as List).cast<Json>();
expect(mutations, hasLength(greaterThan(300)));
for (final m in mutations) {
final dkc = applyEdits(fixture(str(m, 'base')), m['edits']! as List);
final r = inspectCapsule(dkc);
final c = r.checks.last;
expect(
[c.step, c.ok, c.error ?? 'ok', if (!c.ok) c.detail],
[
m['step'] ?? 8,
m['result'] == 'ok',
m['result'],
if (m['result'] != 'ok') m['text'],
],
reason: 'mutation ${m['index']}',
);
}
});
});
group('the caller', () {
final single = caseNamed('format3_single');
final keyed = caseNamed('time_and_key_recipients: the .dkk, decoded');
final plain = caseNamed('time_only_extensions');
test('gets an ArgumentError for options that do not go together, with '
'the output aborted', () async {
final dkc = capsuleOf(keyed, fixture);
final key = decodeAccessKey(fromHex(str(keyed, 'dkk')));
final out = RecordingOutput();
await expectLater(
openCapsule(
dkc,
OpenOptions(
source: suppliedRelease(),
now: () => Instant(0),
accessKey: key,
accessKeyFile: fromHex(str(keyed, 'dkk')),
output: out,
),
),
throwsArgumentError,
);
expect([out.aborted, out.closed], [isArgumentError, false]);
await expectLater(
openCapsule(
dkc,
OpenOptions(
source: suppliedRelease(),
now: () => Instant(0),
identities: [Uint8List(31)],
output: RecordingOutput(),
),
),
throwsArgumentError,
);
});
test('needs the sink for format 3 and the output for formats 1 and 2, '
'right after step 2, before any request', () async {
final calls = CaseSource(null, null);
final dkc = capsuleOf(single, fixture);
final out = RecordingOutput();
await expectLater(
openCapsule(dkc, optionsOf(single, source: calls, output: out)),
throwsA(
isArgumentError.having(
(e) => '${e.message}',
'message',
'open: a format 3 capsule holds files: OpenOptions.sink is '
'required',
),
),
);
expect(
[calls.calls, out.aborted, out.log],
[
0,
isArgumentError,
['abort'],
],
);
// Steps 3 to 8 do not matter: a truncated PUBLIC_HEADER after a valid
// prelude.
await expectLater(
openCapsule(dkc.sublist(0, 20), optionsOf(single, source: calls)),
throwsArgumentError,
);
await expectLater(
openCapsule(
capsuleOf(plain, fixture),
optionsOf(plain, source: calls, sink: MemoryFileSink()),
),
throwsA(
isArgumentError.having(
(e) => '${e.message}',
'message',
'open: a capsule of format 1 or 2 holds one content: '
'OpenOptions.output is required',
),
),
);
expect(calls.calls, 0);
// Steps 1 and 2 fail first: no format, nothing needed.
final r = await openCapsule(dkc.sublist(0, 10), optionsOf(single));
expect([r.error?.code, r.checks.last.step], [ErrorCode.integrity, 1]);
});
test('leaves the sink untouched in formats 1 and 2, and the output in '
'format 3', () async {
final sink = RecordingSink();
final out = RecordingOutput();
final r = await openCapsule(
capsuleOf(plain, fixture),
optionsOf(plain, output: out, sink: sink),
);
expect([r.ok, sink.log, out.closed], [true, isEmpty, true]);
expect(out.log.last, 'close');
final out3 = RecordingOutput();
final s = await openCapsule(
capsuleOf(single, fixture),
optionsOf(single, output: out3, sink: MemoryFileSink()),
);
expect([s.ok, out3.log], [true, isEmpty]);
});
test('hands the sink the files in the order of the head, each closed '
'before the next, and commits last', () async {
final sink = RecordingSink();
final r = await openCapsule(
capsuleOf(single, fixture),
optionsOf(single, sink: sink),
);
expect(r.ok, isTrue);
expect(sink.log, [
'begin',
'create 0',
for (final l in sink.log.where((l) => l.startsWith('write 0 '))) l,
'close 0',
'commit',
]);
final written = sink.log
.where((l) => l.startsWith('write 0 '))
.map((l) => int.parse(l.split(' ')[2]))
.fold(0, (a, b) => a + b);
expect(written, r.head!.files.single.size);
});
test('keeps the files in a MemoryFileSink, given once committed', () async {
final sink = MemoryFileSink();
final r = await openCapsule(
capsuleOf(single, fixture),
optionsOf(single, sink: sink),
);
expect(sink.head, same(r.head));
expect(sink.files!.single, hasLength(r.head!.files.single.size));
expect(
canonical(
filesOf(Outcome(r, RecordingOutput(), _asRecording(sink), 0, [])),
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>
3 days ago
),
canonical(single['files']),
);
expect(sink.aborted, isFalse);
});
test('gives the evaluator what Go\'s newSecurityContext takes, once the '
'head is read, and never fails for it', () async {
final seen = <SecurityInput>[];
final dkc = capsuleOf(single, fixture);
final keys = {'dkauthor1x': 'Ana'};
final r = await openCapsule(
dkc,
optionsOf(
single,
sink: MemoryFileSink(),
authorKeys: keys,
evaluator: (input) {
seen.add(input);
// The control is whole while the evaluator runs.
expect(input.control.payloadIdentity.any((b) => b != 0), isTrue);
return Verdicts(
signature: Verdict.noSignature,
seal: Verdict.noSeal,
);
},
),
);
expect(r.ok, isTrue);
final input = seen.single;
final s = splitCapsule(dkc);
expect(
[
input.format,
input.roundTime,
input.authorKeys,
toHex(input.control.headerBinding),
decodeHead(input.head).files.single.path,
input.security.isNotEmpty,
input.security.length <= r.areaLen!,
],
[
CapsuleFormat.format3,
r.inspection.unlockAt,
keys,
toHex(headerBinding(s.preludeBytes, s.publicHeader)),
r.head!.files.single.path,
true,
true,
],
);
expect(
[r.verdicts!.signature, r.verdicts!.seal],
[Verdict.noSignature, Verdict.noSeal],
);
// I_PAYLOAD is wiped once the opening ends.
expect(input.control.payloadIdentity.every((b) => b == 0), isTrue);
// An evaluator that throws: the capsule opens, not evaluated.
final t = await openCapsule(
dkc,
optionsOf(
single,
sink: MemoryFileSink(),
evaluator: (_) => throw StateError('broken'),
),
);
expect(
[t.ok, t.verdicts!.evaluated, t.verdicts!.error],
[true, false, isStateError],
);
// The default evaluates as Go: an area without a signature or a seal
// is F0 and S0.
expect(
OpenOptions(
source: suppliedRelease(r.release!),
now: () => r.inspection.unlockAt!,
).evaluator,
same(evaluateSecurityInput),
);
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>
3 days ago
final d = await openCapsule(
dkc,
optionsOf(single, sink: MemoryFileSink()),
);
final want = single['verdicts']! as Json;
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>
3 days ago
expect(
[
d.verdicts!.signature?.code,
d.verdicts!.seal?.code,
d.verdicts!.lines,
],
[want['signature'], want['seal'], want['lines']],
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>
3 days ago
);
expect(want['signature'], 'F0');
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>
3 days ago
});
test('shows the verdicts to accept before step 18, which may refuse '
'them', () async {
final dkc = capsuleOf(single, fixture);
final verdicts = Verdicts(
signature: Verdict.signedOther,
seal: Verdict.noSeal,
authorKey: Uint8List(32),
);
Verdicts? shown;
final sink = RecordingSink();
final out = RecordingOutput();
final r = await openCapsule(
dkc,
optionsOf(
single,
sink: sink,
output: out,
evaluator: (_) => verdicts,
accept: (v) {
shown = v;
expect(sink.log, isNot(contains('commit')));
throw 'not signed by Ana';
},
),
);
expect(shown, same(verdicts));
expect(
[r.ok, r.error, r.refusal, r.head, r.verdicts],
[false, null, 'not signed by Ana', null, null],
);
expect(sink.state, 'aborted');
expect(out.aborted, 'not signed by Ana');
expect(
[r.checks.last.step, r.checks.last.ok, r.checks.last.detail],
[
17,
true,
'payload authenticated; the caller refused its verdicts and nothing '
'was published',
],
);
// Accepted, they are those of the result.
final a = await openCapsule(
dkc,
optionsOf(
single,
sink: MemoryFileSink(),
evaluator: (_) => verdicts,
accept: (v) {},
),
);
expect([a.ok, a.verdicts], [true, same(verdicts)]);
});
test('does not wipe the credentials of the caller, and wipes the .dkk it '
'decodes', () async {
final dkc = capsuleOf(keyed, fixture);
final key = decodeAccessKey(fromHex(str(keyed, 'dkk')));
final material = Uint8List.fromList(key.material);
final identity = Uint8List.fromList(material);
final out = MemoryByteSink();
final rel = keyed['release']! as Json;
final r = await openCapsule(
dkc,
OpenOptions(
source: suppliedRelease(
Release(rel['round']! as int, fromHex(str(rel, 'signature'))),
),
now: () => parseRfc3339(str(keyed, 'now')),
accessKey: key,
identities: [identity],
output: out,
),
);
expect(r.ok, isTrue);
expect([key.material, identity], [material, material]);
expect(out.bytes, isNotNull);
});
});
group('the sinks of memory', () {
test('MemoryByteSink gives its bytes once closed, and wipes them on '
'abort', () {
final s = MemoryByteSink();
final a = Uint8List.fromList([1, 2, 3]);
s.add(a);
s.add(Uint8List.fromList([4]));
expect(s.bytes, isNull);
s.close();
expect(s.bytes, [1, 2, 3, 4]);
expect(() => s.add(Uint8List(1)), throwsStateError);
final t = MemoryByteSink();
final b = Uint8List.fromList([9, 9]);
t.add(b);
t.abort('failed');
expect(
[t.bytes, t.abortReason, b],
[
null,
'failed',
[0, 0],
],
);
expect(t.close, throwsStateError);
});
test('MemoryFileSink gives its files only once committed', () {
final s = MemoryFileSink();
final h = Head(
salt: Uint8List(32),
files: [
HeadFile(path: 'a', size: 1, start: 0, end: 1, sha256: Uint8List(32)),
HeadFile(path: 'b', size: 0, start: 1, end: 1, sha256: Uint8List(32)),
],
);
s.begin(h);
s.create(0)
..add(Uint8List.fromList([7]))
..close();
s.create(1).close();
expect([s.head, s.files], [null, null]);
s.commit();
expect(s.head, same(h));
expect(s.files, [
[7],
isEmpty,
]);
final t = MemoryFileSink()..begin(h);
final f = t.create(0)..add(Uint8List.fromList([5]));
t.abort('no');
expect([t.aborted, t.files, f.bytes], [true, null, null]);
});
});
group('the sources', () {
test('BytesSource reads views of its bytes, and readRange keeps '
'reading', () async {
final b = Uint8List.fromList(List.generate(100, (i) => i));
final s = BytesSource(b);
expect(s.length, 100);
expect(await s.read(98, 10), [98, 99]);
expect(await s.read(100, 10), isEmpty);
final c = ChunkySource(b, 7);
final r = await readSource(c, 3, 50);
expect(r, List.generate(50, (i) => i + 3));
expect(c.reads, 8);
expect(await readSource(c, 90, 50), List.generate(10, (i) => 90 + i));
});
test('a source that gives nothing before its end is an error of the '
'caller', () async {
final r = await openCapsuleSource(
_Short(capsuleOf(singleCase(), fixture)),
optionsOf(singleCase(), sink: MemoryFileSink()),
).then<Object?>((o) => o, onError: (Object e) => e);
expect(r, isStateError);
});
});
}
Json singleCase() => caseNamed('format3_single');
/// readRange of source.dart, which lib/datekeys.dart does not export.
Future<Uint8List> readSource(ByteSource s, int offset, int n) async {
final out = BytesBuilder();
for (var at = offset; at < offset + n && at < s.length;) {
final piece = await s.read(at, offset + n - at);
if (piece.isEmpty) break;
out.add(piece);
at += piece.length;
}
return out.takeBytes();
}
// A source whose reads past its first 100 bytes give nothing.
final class _Short implements ByteSource {
_Short(this._b);
final Uint8List _b;
@override
int get length => _b.length;
@override
Future<Uint8List> read(int offset, int n) async {
if (offset >= 100) return Uint8List(0);
final end = offset + n < 100 ? offset + n : 100;
return Uint8List.sublistView(_b, offset, end);
}
}
// A RecordingSink with the files of [m], for filesOf.
RecordingSink _asRecording(MemoryFileSink m) {
final r = RecordingSink();
r.files = [for (final f in m.files!) BytesBuilder()..add(f)];
return r;
}

Powered by TurnKey Linux.