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/authorkey_support.dart

274 lines
8.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// Helpers and tests of the author keys over test/vectors/authorkey.json, as
// tool/authorkey_go_vectors.go writes it: the signatures of Go's
// crypto/ed25519, the scalars modulo ℓ, and package authorkey of
// datekeys-go with the texts of its errors. authorkey_test.dart runs them
// over the part in authorkey.g.dart, also compiled to JavaScript, and
// authorkey_vm_test.dart over the whole file. They read no file.
import 'dart:typed_data';
import 'package:datekeys/src/age_writer.dart';
import 'package:datekeys/src/authorkey.dart';
import 'package:datekeys/src/bytes.dart';
import 'package:datekeys/src/curve25519.dart' show verifyStrict;
import 'package:datekeys/src/ed25519_sign.dart';
import 'package:datekeys/src/recipient.dart';
import 'package:datekeys/src/sha256.dart';
import 'package:test/test.dart';
import 'age_support.dart' show pattern;
import 'random_support.dart';
export 'random_support.dart';
/// The text of index [i] of the vectors, null for no error.
String? textOf(Json v, Object? i) {
final t = (v['texts']! as List)[i! as int] as String;
return t.isEmpty ? null : t;
}
/// The text of what [f] throws, null when it returns.
String? errorOf(void Function() f) {
try {
f();
return null;
} on AuthorKeyException catch (e) {
return e.message;
} on ArgumentError catch (e) {
return e.message as String;
}
}
/// The message of a signing case.
Uint8List messageOf(Json c) {
final n = c['message_pattern'] as int?;
return n != null ? pattern(n) : fromHex(c['message']! as String);
}
/// The bytes of a file of the vectors: parts {hex}, {byte, n} and {sealed:
/// {seed, passphrase, work_factor, plain}, length, sha256}, which is
/// written again here with SeededRandomSource and checked.
Uint8List fileOf(List<Object?> parts) {
final out = BytesBuilder(copy: false);
for (final p in parts.cast<Json>()) {
if (p['hex'] case final String h) {
out.add(fromHex(h));
} else if (p['sealed'] case final Json s) {
final f = ageEncrypt(fileOf(s['plain']! as List), [
ScryptRecipient(
s['passphrase']! as String,
workFactor: s['work_factor']! as int,
),
], random: seeded(s['seed']! as String));
expect(f.length, p['length']);
expect(toHex(sha256(f)), p['sha256']);
out.add(f);
} else {
out.add(
Uint8List(p['n']! as int)
..fillRange(0, p['n']! as int, p['byte']! as int),
);
}
}
return out.takeBytes();
}
/// The result of a parse as the vectors write it: the text, and the public
/// key when it passes.
Json parsed(Uint8List Function() f) {
try {
return {'public_key': toHex(f()), 'text': null};
} on AuthorKeyException catch (e) {
return {'text': e.message};
}
}
Json expectedParse(Json v, Json c) => {
if (c['public_key'] != null) 'public_key': c['public_key'],
'text': textOf(v, c['text']),
};
/// The tests of the signing cases [cases].
void signTests(List<Json> cases) {
test('Ed25519 signatures of crypto/ed25519 (${cases.length})', () {
for (final c in cases) {
final seed = fromHex(c['seed']! as String);
final pub = fromHex(c['public_key']! as String);
final msg = messageOf(c);
final sig = ed25519Sign(seed, pub, msg);
expect(toHex(sig), c['signature'], reason: c['name'] as String?);
if (c['valid'] == true) {
expect(toHex(ed25519PublicKey(seed)), c['public_key']);
expect(toHex(AuthorKey.fromSeed(seed).sign(msg)), c['signature']);
expect(
verifyStrict(pub, msg, sig),
isTrue,
reason: c['name'] as String?,
);
} else {
expect(verifyStrict(pub, msg, sig), isFalse);
}
}
});
}
/// The tests of the vectors [v] that authorkey.g.dart holds whole.
void authorKeyTests(Json v) {
test('the scalars modulo ℓ of math/big', () {
final s = v['scalars']! as Json;
for (final c in listOf(s['reduce'])) {
expect(
toHex(ed25519ReduceScalar(fromHex(c['in']! as String))),
c['out'],
reason: c['in'] as String?,
);
}
for (final c in listOf(s['muladd'])) {
final r = ed25519MulAdd(
fromHex(c['a']! as String),
fromHex(c['b']! as String),
fromHex(c['c']! as String),
);
expect(toHex(r), c['out']);
}
});
test('keys: the public key, its string, the secret, Marshal, String', () {
final k = v['keys']! as Json;
for (final c in listOf(k['keys'])) {
final key = AuthorKey.fromSeed(fromHex(c['seed']! as String));
expect(toHex(key.publicKey), c['public_key']);
expect(key.publicString, c['public']);
expect(authorPublicString(key.publicKey), c['public']);
expect(key.secret, c['secret']);
expect(decodeUtf8(marshalAuthorKey(key)), c['marshal']);
expect(key.toString(), c['string']);
expect('$key', c['gostring']);
expect(toHex(parseAuthorPublic(c['public']! as String)), c['public_key']);
expect(parseAuthorSecret(c['secret']! as String).secret, c['secret']);
}
for (final c in listOf(k['seed_errors'])) {
expect(
errorOf(() => AuthorKey.fromSeed(Uint8List(c['length']! as int))),
c['error'],
);
}
for (final c in listOf(k['public_errors'])) {
expect(
errorOf(() => authorPublicString(Uint8List(c['length']! as int))),
c['error'],
);
}
});
test('Generate draws its seed as Go does', () {
for (final c in listOf(v['generate'])) {
final r = RecordingSource(seeded(c['seed']! as String));
final key = AuthorKey.generate(r);
expect(r.json, c['draws']);
expect(key.secret, c['secret']);
expect(toHex(key.publicKey), c['public_key']);
}
});
for (final c in listOf(v['encrypt'])) {
if (c['error'] != null) {
test('Encrypt refuses an empty passphrase', () {
final key = AuthorKey.fromSeed(Uint8List(32));
expect(errorOf(() => encryptAuthorKey(key, '')), c['error']);
});
continue;
}
if (isWeb && c['node'] != true) continue;
test('Encrypt writes the bytes of Go: ${c['passphrase']}', () {
final key = AuthorKey.fromSeed(fromHex(c['key_seed']! as String));
final r = RecordingSource(seeded(c['seed']! as String));
final file = encryptAuthorKey(key, c['passphrase']! as String, random: r);
expect(r.json, c['draws']);
expect(toHex(file), c['file']);
final back = readAuthorKey(file, passphrase: c['passphrase']! as String);
expect(back.secret, key.secret);
});
}
test('ParsePublic, with the texts of Go', () {
for (final c in listOf(v['public'])) {
final b = fromHex(c['in']! as String);
final want = expectedParse(v, c);
expect(
parsed(() => parseAuthorPublicUtf8(b)),
want,
reason: c['in'] as String?,
);
final s = decodeUtf8(b);
if (s != null) expect(parsed(() => parseAuthorPublic(s)), want);
}
});
test('ParseSecret, with the texts of Go', () {
for (final c in listOf(v['secret'])) {
final b = fromHex(c['in']! as String);
final want = expectedParse(v, c);
expect(
parsed(() => parseAuthorSecretUtf8(b).publicKey),
want,
reason: c['in'] as String?,
);
final s = decodeUtf8(b);
if (s != null) {
expect(parsed(() => parseAuthorSecret(s).publicKey), want);
}
}
});
runeTests(v);
for (final c in listOf(v['read'])) {
if (isWeb && c['node'] != true) continue;
test('Read: ${c['name']}', () {
final file = fileOf(c['file']! as List);
final want = <String, Object?>{
'text': textOf(v, c['text']),
if (c['public_key'] != null) 'public_key': c['public_key'],
if (c['secret'] != null) 'secret': c['secret'],
};
Json got;
try {
final k = readAuthorKey(file, passphrase: c['passphrase']! as String);
got = {
'text': null,
'public_key': toHex(k.publicKey),
'secret': k.secret,
};
} on AuthorKeyException catch (e) {
got = {'text': e.message};
}
expect(got, want);
});
}
}
/// The rune cases of [v]: a valid string with one character replaced by a
/// rune of as many bytes, at each edge of the case and space sets of Go.
void runeTests(Json v) {
final r = v['runes']! as Json;
final cases = (r['cases']! as List).cast<List<Object?>>();
test('the runes at the edges of the sets of Go (${cases.length})', () {
final bases = [
utf8Bytes(r['public']! as String),
utf8Bytes(r['secret']! as String),
];
for (final c in cases) {
final kind = c[0]! as int;
final at = c[1]! as int;
final rune = c[2]! as int;
final e = utf8Bytes(String.fromCharCode(rune));
final b = Uint8List.fromList(bases[kind])..setRange(at, at + e.length, e);
final got = errorOf(
() => kind == 0 ? parseAuthorPublicUtf8(b) : parseAuthorSecretUtf8(b),
);
expect(got, textOf(v, c[3]), reason: 'U+${rune.toRadixString(16)}');
}
});
}

Powered by TurnKey Linux.