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.
dateKeys-dart/test/age_interop_support.dart

222 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/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);
}

Powered by TurnKey Linux.