// Helpers of the tests of the age writer: the recipients and identities of // the cases of test/vectors/age_writer.json and age_interop.json, the // identity of OUTER_TIME_AGE, which the reader keeps private, a file written // in pieces and the lengths of the stanzas of a header. They read no file. import 'dart:typed_data'; import 'package:datekeys/datekeys.dart'; import 'package:datekeys/src/age.dart'; import 'package:datekeys/src/age_writer.dart'; import 'package:datekeys/src/agewrap.dart'; import 'package:datekeys/src/bytes.dart' show utf8Bytes; import 'package:datekeys/src/recipient.dart'; import 'package:datekeys/src/sha256.dart'; import 'package:datekeys/src/tlock.dart'; import 'random_support.dart'; export 'age_support.dart' show pattern; export 'random_support.dart'; /// The published Quicknet signatures of rounds 1000 and 1001, those of the /// fixtures. const releases = { 1000: 'b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39', 1001: 'b33bf3667cbd5a82de3a24b4e0e9fe5513cc1a0e840368c6e31f5fcfa79bea03f73896b25883abf2853d10337fb8fa41', }; /// The X25519 identity of a label: its raw secret is SHA-256("identity " + /// label), as in the generators. X25519Identity labelIdentity(String label) => X25519Identity(sha256(utf8Bytes('identity $label'))); /// The recipient of a case: `{x25519: age1…}`, `{scrypt: passphrase, /// work_factor: n}` or `{tlock: round}`, for Quicknet. AgeRecipient recipientOf(Json spec) { final x = spec['x25519'] as String?; if (x != null) return X25519Recipient.parse(x); final pass = spec['scrypt'] as String?; if (pass != null) { return ScryptRecipient(pass, workFactor: spec['work_factor']! as int); } return TimeRecipient(quicknet(), spec['tlock']! as int); } /// The identity that opens what [spec] wraps, when the case knows it: the /// X25519 identity it names, the passphrase with its work factor as the /// maximum, or the tlock identity of a round whose release is known. AgeIdentity? identityOf(Json spec) { final id = spec['identity'] as String?; if (id != null) return X25519Identity.parse(id); final pass = spec['scrypt'] as String?; if (pass != null) { return ScryptIdentity(pass, maxWorkFactor: spec['work_factor']! as int); } final round = spec['tlock'] as int?; final sig = releases[round]; if (round == null || sig == null) return null; return TimeIdentity(quicknet(), round, Release(round, fromHex(sig))); } /// The identity of OUTER_TIME_AGE, as Go's agewrap.TimeIdentity: the /// stanza rule of the file, then the tlock stanza opened with the release. /// open.dart has its own, private. final class TimeIdentity implements AgeIdentity { TimeIdentity(this._p, this._round, this._release); final Profile _p; final int _round; final Release _release; @override Uint8List unwrap(List stanzas) { checkTimeStanzas( stanzas, round: _round, chainHashHex: _p.chainHashHex, profileId: _p.id, ); final s = stanzas.single; return unwrapTlockStanza(_p, _round, _release, s.args, s.body); } } /// The file that [AgeEncryptor] writes when the plaintext arrives in pieces /// of the sizes that [step] gives, in turn. Uint8List encryptInPieces( Uint8List plaintext, List recipients, RandomSource random, List steps, ) { final e = AgeEncryptor(recipients, random: random); final out = BytesBuilder(copy: false) ..add(e.header) ..add(e.nonce); var k = 0; for (var i = 0; i < plaintext.length;) { final n = steps[k++ % steps.length]; final end = i + n < plaintext.length ? i + n : plaintext.length; e.add(plaintext, i, end).forEach(out.add); i = end; } out.add(e.close()); return out.takeBytes(); } /// The marshalled length of each stanza of [header], an age header. List stanzaLengths(Uint8List header) => [ for (final s in parseAgeHeader(header).stanzas) marshalAgeHeaderWithoutMac([s]).length - 22 - 3, ];