// The age writer against Go, in the other direction: the files that this // library writes, opened by Go. test/vectors/age_interop.json holds the // recipes of test/age_interop_support.dart, the files that this library // wrote from them with SeededRandomSource (their length and SHA-256, and // their bytes when small) and what filippo.io/age and agewrap made of each // with each opener (tool/age_interop_go_verdicts.go). Here each file is // written again from its recipe and must be the one that Go read; Go // opened it to the plaintext of the recipe, or refused it where the recipe // expects a refusal; and this library makes the same of it as Go: the same // plaintext or the same error text, and the same stanza rules. The vectors // come from a Dart constant, so that this also runs compiled to // JavaScript, without the expensive samples. import 'dart:convert'; import 'package:datekeys/datekeys.dart'; import 'package:datekeys/src/age.dart'; import 'package:datekeys/src/agewrap.dart'; import 'package:datekeys/src/sha256.dart'; import 'package:test/test.dart'; import 'age_interop_support.dart'; import 'age_writer_support.dart'; import 'vectors/age_interop.g.dart'; final Json vectors = jsonDecode(ageInteropJson) as Json; const recipeKeys = [ 'name', 'seed', 'recipients', 'inner', 'length', 'pieces', 'node', 'openers', ]; Json recipe(Json s) => { for (final k in recipeKeys) if (s.containsKey(k)) k: s[k], }; String rule(void Function() f) { try { f(); return 'ok'; } on DateKeysException catch (e) { return e.message; } } void main() { final samples = listOf(vectors['samples']); test('the recipes are those that the tool writes', () { expect(jsonDecode(jsonEncode(interopSamples())), samples.map(recipe)); }); for (final s in samples.where((s) => !isWeb || s['node'] != false)) { test(s['name'], () { final file = writeSample(s); expect(file.length, s['file_length']); expect(toHex(sha256(file)), s['file_sha256']); if (s['file'] != null) expect(toHex(file), s['file']); final header = parseAgeHeader(file); expect(header.length, s['header_length']); final stanzas = header.stanzas; expect([for (final st in stanzas) st.type], s['stanzas']); // Go opened it to the plaintext of the recipe with some opener, and // this library makes of it what Go made with each. final plain = toHex(sha256(finalPlaintext(s))); final openers = listOf(s['openers']); final verdicts = listOf(s['verdicts']); expect(verdicts, hasLength(openers.length)); expect(verdicts.where((v) => v['error'] == null), isNotEmpty); for (var i = 0; i < openers.length; i++) { final v = verdicts[i]; if (v['error'] == null) expect(v['plaintext_sha256'], plain); expect(openWith(file, openers[i]), v, reason: 'opener $i'); } // The stanza rules of agewrap, as Go applied them. final q = quicknet(); expect({ 'payload': rule(() => checkPayloadStanzas(stanzas)), 'access_0': rule(() => checkAccessStanzas(stanzas, 0)), 'access_16': rule(() => checkAccessStanzas(stanzas, accessSlots)), 'time_1000': rule( () => checkTimeStanzas( stanzas, round: 1000, chainHashHex: q.chainHashHex, profileId: q.id, ), ), }, s['rules']); }); } }