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.
113 lines
3.9 KiB
113 lines
3.9 KiB
// 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<AgeStanza> 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<AgeRecipient> recipients,
|
|
RandomSource random,
|
|
List<int> 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<int> stanzaLengths(Uint8List header) => [
|
|
for (final s in parseAgeHeader(header).stanzas)
|
|
marshalAgeHeaderWithoutMac([s]).length - 22 - 3,
|
|
];
|