// The writer of capsules against Go, in the other direction: the capsules // that this library writes, opened by Go. test/vectors/capsule_interop.json // holds the recipes of capsule_interop_support.dart with what Go made of // each capsule (tool/capsule_interop_go_verdicts.go). Go must open it with // each credential, alone and together, to the files of the recipe, in the // byte order of their paths, with their SHA-256 and their mtime as the // writer stores it, refuse it without a credential, find 16 stanzas in // INNER_ACCESS_AGE, encode each layer and the .dkk again to the bytes // written, and, for a seeded sample, write the same bytes itself. A seeded // sample is written again here and must be the one that Go read. import 'package:datekeys/datekeys.dart'; import 'package:datekeys/src/body.dart' show areaLen; import 'package:datekeys/src/bytes.dart' show utf8Bytes; import 'package:test/test.dart'; import 'capsule_interop_support.dart'; import 'capsule_writer_support.dart'; /// The files that the recipe [r] puts in its capsule, as Go must open /// them. List expectedFiles(Json r) { final files = [...listOf(r['files'] ?? const [])] ..sort( (a, b) => compareBytes( utf8Bytes(a['path']! as String), utf8Bytes(b['path']! as String), ), ); return [ for (final f in files) { 'path': f['path'], 'size': contentOf(f).length, 'sha256': toHex(sha256Of(contentOf(f))), if (f['mtime'] case [final int s, _] when s >= 0 && s <= maxMTime) 'mtime': s, }, ]; } /// The tests of the samples of [doc], with the recipes that wrote them. void interopCases(Json doc, {required bool rewrite}) { final recipes = {for (final s in interopSamples()) s['name']: s}; for (final g in listOf(doc['samples'])) { final r = recipes[g['name']]!; test(g['name'], () async { expect(g['reencoded'], isTrue); final inspect = g['inspect']! as Json; expect(inspect['format'], 3); expect(inspect['policy'], r['policy'] ?? 'time_only'); expect(inspect['note'], r['note'] ?? ''); final tak = r['policy'] == 'time_and_key'; expect(g['inner_stanzas'], tak ? 16 : null); expect(g['dkk_capsule_digest'], r['portable'] == true ? true : null); expect(g['go_same'], r['random'] == 'seeded' ? true : null); final comment = (r['comment'] as String? ?? '').replaceAll('\r\n', '\n'); final opens = listOf(g['opens']); final names = [for (final o in opens) o['with']]; expect(names, [ if (!tak) 'time', if (tak) ...[ for (final l in (r['identities'] as List? ?? const [])) 'identity $l', if (r['words'] != null) 'words', if (r['portable'] == true) 'portable', 'all', 'none', ], ]); for (final o in opens) { if (o['with'] == 'none') { expect(o['result'], 'ERR_ACCESS_REQUIRED'); continue; } expect(o['result'], 'ok', reason: '${o['with']}'); expect(o['files'], expectedFiles(r)); expect(o['comment'], comment); expect(o['author'], r['author'] ?? ''); expect(o['verdicts'], ['F0', 'S0']); expect(o['area_len'], r['test_area_len'] ?? areaLen); } if (rewrite && r['random'] == 'seeded') { final w = await writeSample(r); expect(w.dkc.length, g['length_dkc']); expect(toHex(sha256Of(w.dkc)), g['sha256']); final dkk = w.dkk; expect(dkk == null ? null : toHex(sha256Of(dkk)), g['dkk_sha256']); } }); } }