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.
223 lines
6.8 KiB
223 lines
6.8 KiB
// The samples of the interoperability of the age writer: age files that
|
|
// this library writes with SeededRandomSource, from recipes, for Go to open
|
|
// (tool/age_interop_dart_samples.dart writes them, and
|
|
// tool/age_interop_go_verdicts.go opens them with filippo.io/age and the
|
|
// identities of agewrap and writes test/vectors/age_interop.json). Each
|
|
// recipe names its seed, its recipients, its plaintext (the pattern of its
|
|
// length, or another age file, as INNER_ACCESS_AGE inside OUTER_TIME_AGE),
|
|
// the pieces in which the plaintext arrives, and the identities that open
|
|
// it. 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/recipient.dart';
|
|
import 'package:datekeys/src/sha256.dart';
|
|
|
|
import 'age_writer_support.dart';
|
|
|
|
String _x(String label) => X25519Recipient.of(labelIdentity(label)).toString();
|
|
|
|
String _secret(String label) => labelIdentity(label).toString();
|
|
|
|
String _raw(String label) => toHex(labelIdentity(label).secretKey);
|
|
|
|
/// The recipes of the samples, in the form of age_interop.json.
|
|
List<Json> interopSamples() {
|
|
final sixteen = [for (var i = 0; i < 16; i++) 'interop slot $i'];
|
|
Json access(List<String> labels, int slots) => {
|
|
'access': [for (final l in labels) _raw(l)],
|
|
'slots': slots,
|
|
};
|
|
return [
|
|
for (final n in [0, 1, 65535, 65536, 65537, 131072, 3 << 20 | 1])
|
|
{
|
|
'name': 'x25519, $n bytes',
|
|
'seed': 'interop x25519 $n',
|
|
'recipients': [
|
|
{'x25519': _x('interop one')},
|
|
],
|
|
'length': n,
|
|
'node': n <= 131072,
|
|
'openers': [
|
|
{'x25519': _secret('interop one')},
|
|
{'payload': _raw('interop one')},
|
|
{'x25519': _secret('interop stranger')},
|
|
{'payload': _raw('interop stranger')},
|
|
],
|
|
},
|
|
{
|
|
'name': 'x25519, 200000 bytes in pieces',
|
|
'seed': 'interop pieces',
|
|
'recipients': [
|
|
{'x25519': _x('interop one')},
|
|
],
|
|
'length': 200000,
|
|
'pieces': [1, 7, 65535, 65537, 1000, 3],
|
|
'node': true,
|
|
'openers': [
|
|
{'x25519': _secret('interop one')},
|
|
],
|
|
},
|
|
{
|
|
'name': 'x25519, three recipients',
|
|
'seed': 'interop three',
|
|
'recipients': [
|
|
for (final l in ['interop a', 'interop b', 'interop c'])
|
|
{'x25519': _x(l)},
|
|
],
|
|
'length': 1000,
|
|
'node': true,
|
|
'openers': [
|
|
for (final l in ['interop a', 'interop b', 'interop c'])
|
|
{'x25519': _secret(l)},
|
|
access(['interop b'], 0),
|
|
access(['interop a', 'interop stranger'], 0),
|
|
{'payload': _raw('interop a')},
|
|
access(['interop a'], 16),
|
|
],
|
|
},
|
|
{
|
|
'name': 'x25519, sixteen recipients',
|
|
'seed': 'interop sixteen',
|
|
'recipients': [
|
|
for (final l in sixteen) {'x25519': _x(l)},
|
|
],
|
|
'length': 103,
|
|
'node': true,
|
|
'openers': [
|
|
access([sixteen[0]], 16),
|
|
access([sixteen[7]], 16),
|
|
access([sixteen[15]], 16),
|
|
access([sixteen[3], sixteen[9]], 16),
|
|
access(['interop stranger'], 16),
|
|
access([sixteen[5]], 0),
|
|
],
|
|
},
|
|
{
|
|
'name': 'tlock, round 1000',
|
|
'seed': 'interop tlock',
|
|
'recipients': [
|
|
{'tlock': 1000},
|
|
],
|
|
'length': 103,
|
|
'node': true,
|
|
'openers': [
|
|
{'tlock': 1000, 'release': 1000},
|
|
{'tlock': 1001, 'release': 1001},
|
|
],
|
|
},
|
|
{
|
|
'name': 'tlock over sixteen x25519, as SEALED_CONTROL',
|
|
'seed': 'interop sealed control',
|
|
'recipients': [
|
|
{'tlock': 1000},
|
|
],
|
|
'inner': {
|
|
'seed': 'interop inner access',
|
|
'recipients': [
|
|
for (final l in sixteen) {'x25519': _x(l)},
|
|
],
|
|
'length': 103,
|
|
},
|
|
'node': false,
|
|
'openers': [
|
|
{
|
|
'tlock': 1000,
|
|
'release': 1000,
|
|
'then': access([sixteen[11]], 16),
|
|
},
|
|
{
|
|
'tlock': 1000,
|
|
'release': 1000,
|
|
'then': access(['interop stranger'], 16),
|
|
},
|
|
],
|
|
},
|
|
for (final wf in [10, 16])
|
|
{
|
|
'name': 'scrypt, work factor $wf',
|
|
'seed': 'interop scrypt $wf',
|
|
'recipients': [
|
|
{'scrypt': 'una contraseña larga de prueba', 'work_factor': wf},
|
|
],
|
|
'length': 120,
|
|
'node': wf <= 10,
|
|
'openers': [
|
|
{'scrypt': 'una contraseña larga de prueba', 'max_work_factor': 16},
|
|
{'scrypt': 'otra contraseña', 'max_work_factor': 16},
|
|
{
|
|
'scrypt': 'una contraseña larga de prueba',
|
|
'max_work_factor': wf - 1,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
/// The file of [sample], as this library writes it from its recipe.
|
|
Uint8List writeSample(Json sample) {
|
|
final rs = [for (final r in listOf(sample['recipients'])) recipientOf(r)];
|
|
final inner = sample['inner'] as Json?;
|
|
final plain = inner == null
|
|
? pattern(sample['length']! as int)
|
|
: writeSample(inner);
|
|
final source = seeded(sample['seed']! as String);
|
|
final pieces = sample['pieces'] as List?;
|
|
if (pieces == null) return ageEncrypt(plain, rs, random: source);
|
|
return encryptInPieces(plain, rs, source, pieces.cast<int>());
|
|
}
|
|
|
|
/// The plaintext that [sample] holds in the end: the pattern of its length,
|
|
/// in its inner file when it has one.
|
|
Uint8List finalPlaintext(Json sample) {
|
|
final inner = sample['inner'] as Json?;
|
|
return pattern((inner ?? sample)['length']! as int);
|
|
}
|
|
|
|
/// What this library makes of [file] with [opener], in the form of the
|
|
/// verdicts of Go: the SHA-256 and the length of the plaintext, or the text
|
|
/// of the error.
|
|
Json openWith(Uint8List file, Json opener) {
|
|
try {
|
|
final plain = _open(file, opener);
|
|
return {
|
|
'plaintext_length': plain.length,
|
|
'plaintext_sha256': toHex(sha256(plain)),
|
|
};
|
|
} on AgeException catch (e) {
|
|
return {'error': e.message};
|
|
} on DateKeysException catch (e) {
|
|
return {'error': e.message};
|
|
}
|
|
}
|
|
|
|
Uint8List _open(Uint8List file, Json o) {
|
|
final AgeIdentity id;
|
|
if (o['x25519'] case final String s) {
|
|
id = X25519Identity.parse(s);
|
|
} else if (o['payload'] case final String raw) {
|
|
id = PayloadIdentity(fromHex(raw));
|
|
} else if (o['access'] case final List<Object?> ids) {
|
|
id = AccessIdentity(o['slots']! as int, [
|
|
for (final r in ids.cast<String>()) X25519Identity(fromHex(r)),
|
|
]);
|
|
} else if (o['scrypt'] case final String pass) {
|
|
id = ScryptIdentity(pass, maxWorkFactor: o['max_work_factor']! as int);
|
|
} else {
|
|
final round = o['tlock']! as int;
|
|
final release = o['release']! as int;
|
|
id = TimeIdentity(
|
|
quicknet(),
|
|
round,
|
|
Release(release, fromHex(releases[release]!)),
|
|
);
|
|
}
|
|
final plain = ageDecrypt(file, [id]);
|
|
final then = o['then'] as Json?;
|
|
return then == null ? plain : _open(plain, then);
|
|
}
|