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

298 lines
9.5 KiB

// PUBLIC_HEADER, CONTROL_CBOR, the .dkk and the Provider Profile
// (lib/src/header.dart, control.dart, accesskey.dart and profile.dart), as
// header.test.ts, control.test.ts, accesskey.test.ts and profile.test.ts of
// datekeys-ts: values of a fixed seed that encode and decode back to
// themselves, the copies of the secrets and their wiping, the texts that
// hide them, and the pinned registry. The values and texts of Go are in the
// differential (formats_header.json, formats_control.json,
// formats_framing.json, formats_profile.json and formats_encode.json). It
// reads no file: it runs on the VM and compiled to JavaScript.
library;
import 'dart:math';
import 'dart:typed_data';
import 'package:datekeys/src/accesskey.dart';
import 'package:datekeys/src/bytes.dart';
import 'package:datekeys/src/control.dart';
import 'package:datekeys/src/datekey.dart';
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/extension.dart';
import 'package:datekeys/src/framing.dart';
import 'package:datekeys/src/header.dart';
import 'package:datekeys/src/padding.dart';
import 'package:datekeys/src/profile.dart';
import 'package:test/test.dart';
String codeOf(void Function() body) {
try {
body();
return 'ok';
} on DateKeysException catch (e) {
return e.code.code;
}
}
Uint8List bytesOf(Random r, int n) =>
Uint8List.fromList([for (var i = 0; i < n; i++) r.nextInt(256)]);
// 0 to 3 extensions with distinct identifiers, unsorted.
List<Extension> extensionsOf(Random r, String prefix) => [
for (var i = 0; i < r.nextInt(4); i++)
Extension(
'$prefix${r.nextInt(1000)}x$i',
r.nextInt(3),
r.nextBool() ? bytesOf(r, 1 + r.nextInt(5)) : null,
),
];
void main() {
test('headers encode and decode back to themselves', () {
final r = Random(17);
for (var i = 0; i < 200; i++) {
final h = Header(
capsuleId: bytesOf(r, 16),
dateKey: DateKey('datekeys:quicknet:v1', 1 + r.nextInt(1 << 30)),
policy: AccessPolicy.values[r.nextInt(2)],
critical: extensionsOf(r, 'c'),
noncritical: extensionsOf(r, 'n'),
);
final b = encodeHeader(h);
final back = decodeHeader(b);
expect(
[back.capsuleId, back.dateKey, back.policy],
[h.capsuleId, h.dateKey, h.policy],
);
expect(back.critical, canonicalExtensions(h.critical));
expect(back.noncritical, canonicalExtensions(h.noncritical));
expect(encodeHeader(back), b);
expect(back.capsuleIdHex, toHex(h.capsuleId));
}
});
test('access policies by code and by name', () {
expect(
[
for (final p in AccessPolicy.values) [p.code, p.label, '$p'],
],
[
[0, 'time_only', 'time_only'],
[1, 'time_and_key', 'time_and_key'],
],
);
expect([0, 1, 2].map(AccessPolicy.fromCode).toList(), [
AccessPolicy.timeOnly,
AccessPolicy.timeAndKey,
null,
]);
expect(parsePolicy('time_and_key'), AccessPolicy.timeAndKey);
expect(() => parsePolicy('x'), throwsArgumentError);
expect(
codeOf(
() => encodeHeader(
Header(
capsuleId: Uint8List(15),
dateKey: const DateKey('a', 1),
policy: AccessPolicy.timeOnly,
),
),
),
'ERR_NON_CANONICAL_CBOR',
);
});
test('controls encode and decode back, of a length that does not depend '
'on L', () {
final r = Random(19);
for (var i = 0; i < 200; i++) {
final format = CapsuleFormat.values[i % 3];
final c = Control(
headerBinding: bytesOf(r, 32),
payloadIdentity: bytesOf(r, 32),
critical: extensionsOf(r, 'c'),
noncritical: extensionsOf(r, 'n'),
payloadLength: format.isPadded
? [0, 1, 4294967297, maxPayloadLength, r.nextInt(1 << 30)][i % 5]
: null,
padding: format.isPadded ? PaddingRule.values[r.nextInt(2)] : null,
);
final b = encodeControl(c, format);
final back = decodeControl(b, format);
expect(
[back.headerBinding, back.payloadIdentity, back.payloadLength],
[c.headerBinding, c.payloadIdentity, c.payloadLength],
);
expect(back.padding, c.padding);
expect(encodeControl(back, format), b);
if (c.critical.isEmpty && c.noncritical.isEmpty) {
expect(b.length, format.isPadded ? 103 : 91);
expect(b[20], format.version);
}
for (final other in CapsuleFormat.values.where((f) => f != format)) {
expect(
codeOf(() => decodeControl(b, other)),
'ERR_UNSUPPORTED_VERSION',
);
}
}
});
test('a control keeps its own copy of I_PAYLOAD, wipes it, and never '
'prints it', () {
final c = Control(
headerBinding: Uint8List(32),
payloadIdentity: Uint8List(32)..fillRange(0, 32, 0xab),
payloadLength: 1,
padding: PaddingRule.reforzado,
);
final b = encodeControl(c, CapsuleFormat.format2);
final back = decodeControl(b, CapsuleFormat.format2);
b.fillRange(0, b.length, 0);
expect(back.payloadIdentity, Uint8List(32)..fillRange(0, 32, 0xab));
expect('$back', isNot(contains('abab')));
expect('$back', contains('REDACTED'));
back.wipe();
expect(back.payloadIdentity, Uint8List(32));
expect(() => encodeControl(c, CapsuleFormat.format1), throwsArgumentError);
expect(
() => encodeControl(
Control(
headerBinding: Uint8List(32),
payloadIdentity: Uint8List(32),
padding: PaddingRule.bloque256,
),
CapsuleFormat.format3,
),
throwsArgumentError,
);
});
test('access keys encode and decode back, and never print their '
'material', () {
final r = Random(23);
for (var i = 0; i < 200; i++) {
final k = AccessKey(
credentialId: bytesOf(r, 16),
capsuleId: bytesOf(r, 16),
type: accessTypeX25519,
material: bytesOf(r, 32),
verification: r.nextBool() ? Verification(bytesOf(r, 32)) : null,
critical: extensionsOf(r, 'c'),
noncritical: extensionsOf(r, 'n'),
);
final dkk = encodeAccessKey(k);
final back = decodeAccessKey(dkk);
expect(
[back.credentialId, back.capsuleId, back.type, back.material],
[k.credentialId, k.capsuleId, k.type, k.material],
);
expect(back.verification, k.verification);
expect(back.critical, canonicalExtensions(k.critical));
expect(encodeAccessKey(back), dkk);
expect('$back', isNot(contains(toHex(k.material))));
// The key keeps its own copy of the material.
dkk.fillRange(0, dkk.length, 0);
expect(back.material, k.material);
back.wipe();
expect(back.material, Uint8List(32));
expect(codeOf(() => checkAccessKeyMaterial(back)), 'ok');
}
expect(
codeOf(
() => checkAccessKeyMaterial(
AccessKey(
credentialId: Uint8List(16),
capsuleId: Uint8List(16),
type: 'X25519',
material: Uint8List(32),
),
),
),
'ERR_ACCESS_INVALID',
);
expect(
codeOf(
() => marshalAccessKeyBody(
AccessKey(
credentialId: Uint8List(15),
capsuleId: Uint8List(16),
type: accessTypeX25519,
material: Uint8List(32),
),
),
),
'ERR_NON_CANONICAL_CBOR',
);
});
group('Provider Profiles', () {
final q = quicknet();
test('Quicknet is its pinned CBOR, and its range ends in 9999', () {
expect(toHex(canonicalCbor(q)), quicknetCanonicalCbor);
expect(toHex(profileHash(q)), quicknetProfileHash);
expect(decodeProfile(fromHex(quicknetCanonicalCbor)), q);
validateProfile(q);
expect(q.chainHashHex, quicknetChainHash);
expect(
formatRfc3339(roundTime(q, q.maxRound)),
startsWith('9999-12-31T23:59:5'),
);
expect(q.copyWith(genesisTime: maxUnixTime).maxRound, 1);
expect(q.copyWith(genesisTime: maxUnixTime + 1).maxRound, 0);
expect(q.copyWith(period: 0).maxRound, 0);
});
test('the default registry is built once and hands out copies', () {
final reg = defaultRegistry();
expect(defaultRegistry(), same(reg));
final p = reg.lookup(quicknetId)!;
expect(p, q);
p.chainHash.fillRange(0, 32, 0);
expect(reg.lookup(quicknetId), q);
expect(reg.lookup('datekeys:evmnet:v1'), isNull);
final copy = q.copy();
copy.publicKey[0] ^= 1;
expect(quicknet(), isNot(copy));
});
test('a network "default", or none, is left out of the chain hash', () {
final d = q.copyWith(network: 'default');
expect(chainInfoHash(d), chainInfoHash(q.copyWith(network: '')));
expect(
chainInfoHash(d),
isNot(chainInfoHash(q.copyWith(network: 'defaults'))),
);
validateProfile(d.copyWith(chainHash: chainInfoHash(d)));
expect(
codeOf(() => chainInfoHash(q.copyWith(period: maxChainHashPeriod + 1))),
'ERR_UNKNOWN_PROFILE',
);
expect(
chainInfoHash(q.copyWith(period: maxChainHashPeriod)),
hasLength(32),
);
});
test('a profile_id is [a-z0-9:._-], from 1 to 128, the first of '
'[a-z0-9]', () {
for (final s in ['datekeys:quicknet:v1', '0${'a' * 127}', 'a', '0:._-']) {
expect(validId(s), isTrue, reason: s);
}
for (final s in [
'',
'A',
':a',
'.a',
'a b',
'a/b',
'é',
'0${'a' * 128}',
'a\n',
]) {
expect(validId(s), isFalse, reason: s);
}
});
});
}

Powered by TurnKey Linux.