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_framing_test.dart

332 lines
11 KiB

// The frames of a .dkc and of a .dkk (lib/src/framing.dart), the frame of
// BODY (lib/src/body.dart) and the incremental SHA-256 of the
// capsule_digest (lib/src/digest.dart), as framing.test.ts, body.test.ts
// and digest.test.ts of datekeys-ts, on capsules built here: the order of
// the checks, the steps of the inspection, the views and the limits. The
// texts of Go of every failure are in the differential
// (formats_framing.json and formats_body.json). It reads no file: it runs on
// the VM and compiled to JavaScript.
library;
import 'dart:typed_data';
import 'package:crypto/crypto.dart' as crypto;
import 'package:datekeys/src/big_endian.dart';
import 'package:datekeys/src/body.dart';
import 'package:datekeys/src/bytes.dart';
import 'package:datekeys/src/digest.dart';
import 'package:datekeys/src/errors.dart';
import 'package:datekeys/src/framing.dart';
import 'package:test/test.dart';
String codeOf(void Function() body) {
try {
body();
return 'ok';
} on DateKeysException catch (e) {
return e.code.code;
}
}
/// A .dkc of [format] whose sections are [header], [control] and
/// [payload] bytes of 1, 2 and 3.
Uint8List capsule(int format, int header, int control, int payload) {
final out = Uint8List(16 + header + control + payload)
..setAll(0, 'DKC1'.codeUnits);
out[4] = format;
writeUint32BE(out, 8, header);
writeUint32BE(out, 12, control);
out
..fillRange(16, 16 + header, 1)
..fillRange(16 + header, 16 + header + control, 2)
..fillRange(16 + header + control, out.length, 3);
return out;
}
void main() {
group('PRELUDE', () {
final ok = capsule(1, 121, 446, 0);
Uint8List withByte(int i, int v) => Uint8List.fromList(ok)..[i] = v;
test('reads the format and the lengths, and writes them back', () {
for (final f in CapsuleFormat.values) {
final p = parsePrelude(withByte(4, f.version));
expect(p, Prelude(f, 121, 446));
expect(preludeBytes(p), withByte(4, f.version).sublist(0, 16));
expect(payloadOffset(p), 16 + 121 + 446);
expect(f.isPadded, f != CapsuleFormat.format1);
expect(CapsuleFormat.fromVersion(f.version), f);
}
expect([0, 4, 255].map(CapsuleFormat.fromVersion), everyElement(isNull));
});
test('checks the magic, a whole prelude, the version, FLAGS and '
'RESERVED, and then the lengths, in the order of spec §23', () {
expect(codeOf(() => parsePrelude(Uint8List(0))), 'ERR_INVALID_MAGIC');
expect(codeOf(() => parsePrelude(ok.sublist(0, 3))), 'ERR_INVALID_MAGIC');
expect(
codeOf(() => parsePrelude(withByte(3, 0x32))),
'ERR_INVALID_MAGIC',
);
expect(codeOf(() => parsePrelude(ok.sublist(0, 4))), 'ERR_INTEGRITY');
expect(codeOf(() => parsePrelude(ok.sublist(0, 15))), 'ERR_INTEGRITY');
for (final v in [0, 4, 0xff]) {
expect(
codeOf(() => parsePrelude(withByte(4, v))),
'ERR_UNSUPPORTED_VERSION',
);
}
// Every bit of FLAGS and of RESERVED, alone.
for (final i in [5, 6, 7]) {
for (var bit = 1; bit < 256; bit *= 2) {
expect(
codeOf(() => parsePrelude(withByte(i, bit))),
'ERR_INVALID_FLAGS',
reason: 'byte $i, bit $bit',
);
}
}
Uint8List lengths(int h, int s) {
final b = Uint8List.fromList(ok.sublist(0, 16));
writeUint32BE(b, 8, h);
writeUint32BE(b, 12, s);
return b;
}
expect(
parsePrelude(lengths(maxPublicHeaderLen, maxSealedControlLen)),
Prelude(CapsuleFormat.format1, maxPublicHeaderLen, maxSealedControlLen),
);
expect(parsePrelude(lengths(1, 1)), Prelude(CapsuleFormat.format1, 1, 1));
for (final (h, s) in [
(0, 1),
(1, 0),
(maxPublicHeaderLen + 1, 1),
(1, maxSealedControlLen + 1),
(0xffffffff, 0xffffffff),
]) {
expect(codeOf(() => parsePrelude(lengths(h, s))), 'ERR_INTEGRITY');
}
// The version and FLAGS come before the lengths.
final both = lengths(0, 0)..[7] = 1;
expect(codeOf(() => parsePrelude(both)), 'ERR_INVALID_FLAGS');
both[4] = 9;
expect(codeOf(() => parsePrelude(both)), 'ERR_UNSUPPORTED_VERSION');
});
test('header_binding is the SHA-256 of the exact PRELUDE and header', () {
final s = splitCapsule(capsule(2, 20, 30, 40));
expect(
headerBinding(s.preludeBytes, s.publicHeader),
crypto.sha256.convert(capsule(2, 20, 30, 40).sublist(0, 36)).bytes,
);
});
});
group('splitCapsule', () {
final dkc = capsule(3, 20, 30, 40);
FramingException failure(List<int> b) {
try {
splitCapsule(b);
} on FramingException catch (e) {
return e;
}
fail('no failure');
}
test('gives views of the sections', () {
final s = splitCapsule(dkc);
expect(s.prelude, Prelude(CapsuleFormat.format3, 20, 30));
expect(s.preludeBytes, dkc.sublist(0, 16));
expect(s.publicHeader, Uint8List(20)..fillRange(0, 20, 1));
expect(s.sealedControl, Uint8List(30)..fillRange(0, 30, 2));
expect(s.payload, Uint8List(40)..fillRange(0, 40, 3));
// Views, not copies.
s.publicHeader[0] = 9;
expect(dkc[16], 9);
dkc[16] = 1;
});
test('reports the step of each failure of the frame', () {
for (final (cut, step, code) in [
(0, 1, 'ERR_INVALID_MAGIC'),
(3, 1, 'ERR_INVALID_MAGIC'),
(4, 1, 'ERR_INTEGRITY'),
(15, 1, 'ERR_INTEGRITY'),
(16, 3, 'ERR_INTEGRITY'),
(35, 3, 'ERR_INTEGRITY'),
]) {
final e = failure(dkc.sublist(0, cut));
expect([e.step, e.error.code.code], [step, code], reason: '$cut');
expect(e.prelude, step == 3 ? splitCapsule(dkc).prelude : isNull);
expect('$e', e.error.message);
}
expect(failure(Uint8List.fromList(dkc)..[4] = 9).step, 2);
expect(failure(Uint8List.fromList(dkc)..[5] = 1).step, 2);
});
test('leaves a short SEALED_CONTROL to step 5', () {
for (final cut in [36, 37, 65]) {
final s = splitCapsule(dkc.sublist(0, cut));
expect(s.publicHeader, hasLength(20));
expect(s.sealedControl, isNull);
expect(s.payload, isEmpty);
}
expect(truncatedSealedControl().code, ErrorCode.integrity);
expect(splitCapsule(dkc.sublist(0, 66)).payload, isEmpty);
expect(splitCapsule(dkc.sublist(0, 66)).sealedControl, hasLength(30));
});
});
group('DKK1', () {
test('frames and unframes a body', () {
final body = Uint8List.fromList([0xa0, 1, 2]);
final framed = concatBytes([dkkPreludeBytes(body.length), body]);
expect(toHex(framed.sublist(0, 12)), '444b4b310100000000000003');
expect(splitAccessKey(framed), body);
});
test('checks the frame in the order of spec §40', () {
final ok = concatBytes([dkkPreludeBytes(2), Uint8List(2)]);
Uint8List withByte(int i, int v) => Uint8List.fromList(ok)..[i] = v;
expect(codeOf(() => splitAccessKey(Uint8List(0))), 'ERR_INVALID_MAGIC');
expect(codeOf(() => splitAccessKey(withByte(0, 0))), 'ERR_INVALID_MAGIC');
expect(codeOf(() => splitAccessKey(ok.sublist(0, 11))), 'ERR_INTEGRITY');
expect(
codeOf(() => splitAccessKey(withByte(4, 2))),
'ERR_UNSUPPORTED_VERSION',
);
for (final i in [5, 6, 7]) {
for (var bit = 1; bit < 256; bit *= 2) {
expect(
codeOf(() => splitAccessKey(withByte(i, bit))),
'ERR_INVALID_FLAGS',
reason: 'byte $i, bit $bit',
);
}
}
for (final n in [0, maxDkkBodyLen + 1]) {
expect(
codeOf(() => splitAccessKey(dkkPreludeBytes(n))),
'ERR_INTEGRITY',
);
}
// BODY_LEN 0 after FLAGS.
expect(
codeOf(() => splitAccessKey(dkkPreludeBytes(0)..[5] = 1)),
'ERR_INVALID_FLAGS',
);
expect(codeOf(() => splitAccessKey(ok.sublist(0, 13))), 'ERR_INTEGRITY');
expect(
codeOf(() => splitAccessKey(concatBytes([ok, Uint8List(1)]))),
'ERR_INTEGRITY',
);
});
});
group('BODY', () {
BodyFrame parse(int area, int security, int head, int l) =>
parseBodyFrame(bodyFrameBytes(BodyFrame(area, security, head)), l);
test('reads the frame at its limits', () {
for (final (area, security, head, l, c) in [
(512, 22, 53, 577, 0),
(512, 512, 1, 525, 0),
(65536, 1, 1 << 24, 12 + 65536 + (1 << 24), 0),
(1024, 1024, 100, 1099511627776, 1099511626640),
(32768, 22, 53, 8936830510563328, 8936830510563328 - 12 - 32768 - 53),
]) {
final f = parse(area, security, head, l);
expect(f, BodyFrame(area, security, head));
expect(contentLength(f, l), c);
}
expect(
[areaUnit, maxAreaLen, areaLen, largeAreaLen, maxHeadLen],
[512, 65536, 32768, 65536, 16777216],
);
});
test('rejects every violation of the frame with ERR_INTEGRITY', () {
for (final (area, security, head, l) in [
(512, 22, 53, 11),
(0, 22, 53, 1000),
(511, 22, 53, 1000),
(513, 22, 53, 1000),
(66048, 22, 53, 100000),
(512, 0, 53, 1000),
(512, 513, 53, 1000),
(512, 22, 0, 1000),
(512, 22, (1 << 24) + 1, 1 << 30),
(512, 22, 53, 576),
]) {
expect(
codeOf(() => parse(area, security, head, l)),
'ERR_INTEGRITY',
reason: '$area $security $head $l',
);
}
expect(() => parseBodyFrame(Uint8List(11), 100), throwsArgumentError);
});
test('requires zeros after SECURITY_CBOR up to AREA_LEN', () {
final area = Uint8List(512)..fillRange(0, 22, 9);
checkArea(area, 22);
area[511] = 1;
expect(codeOf(() => checkArea(area, 22)), 'ERR_INTEGRITY');
checkArea(area, 512);
});
});
group('the digest of a .dkc', () {
final dkc = Uint8List.fromList([
for (var i = 0; i < 200003; i++) i * 7 % 251,
]);
final want = crypto.sha256.convert(dkc).bytes;
test('is the SHA-256 of the whole file, however it is cut', () {
expect(capsuleDigest(dkc), want);
for (final cut in [1, 7, 63, 64, 65, 4096, 65536, dkc.length]) {
final h = sha256Hasher();
for (var at = 0; at < dkc.length; at += cut) {
h.add(dkc.sublist(at, at + cut < dkc.length ? at + cut : dkc.length));
}
expect(h.digest(), want, reason: '$cut');
expect(h.digest, throwsStateError);
expect(() => h.add([1]), throwsStateError);
}
final empty = Sha256Hasher()..add(Uint8List(0));
expect(empty.digest(), crypto.sha256.convert(const []).bytes);
});
test('of a stream', () async {
final stream = Stream.fromIterable([
dkc.sublist(0, 1),
dkc.sublist(1, 70000),
dkc.sublist(70000),
]);
expect(await sha256Stream(stream), want);
expect(
await sha256Stream(const Stream.empty()),
crypto.sha256.convert(const []).bytes,
);
});
test('a different one is ERR_ACCESS_INVALID', () {
checkCapsuleDigest(capsuleDigest(dkc), want);
final other = Uint8List.fromList(want)..[31] ^= 1;
expect(
codeOf(() => checkCapsuleDigest(capsuleDigest(dkc), other)),
'ERR_ACCESS_INVALID',
);
expect(
codeOf(
() => checkCapsuleDigest(capsuleDigest(dkc), want.sublist(0, 31)),
),
'ERR_ACCESS_INVALID',
);
});
});
}

Powered by TurnKey Linux.