Skip to content

Commit

Permalink
Add more tests for coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
dipu-bd committed Aug 17, 2024
1 parent 91ec549 commit 36b1b25
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 54 deletions.
3 changes: 0 additions & 3 deletions lib/src/algorithms/aead_cipher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,6 @@ class AEADCipherSink implements CipherSink, MACSinkBase {

@override
void init([List<int>? keypair]) {
if (keypair != null) {
_hasher.init(keypair);
}
if (_aad != null) {
_hasher.add(_aad!);
// pad with zero
Expand Down
2 changes: 1 addition & 1 deletion lib/src/algorithms/aes/xts.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

import 'dart:typed_data';

import 'package:cipherlib/cipherlib.dart';
import 'package:cipherlib/src/algorithms/padding.dart';
import 'package:cipherlib/src/core/cipher_sink.dart';
import 'package:cipherlib/src/core/salted_cipher.dart';
import 'package:cipherlib/src/utils/nonce.dart';
Expand Down
19 changes: 2 additions & 17 deletions lib/src/algorithms/padding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ class _ANSIPadding extends Padding {

class _PKCS7Padding extends Padding {
@override
final String name = "PKCS7";
String get name => "PKCS7";

const _PKCS7Padding();

Expand Down Expand Up @@ -284,7 +284,7 @@ class _PKCS7Padding extends Padding {
}
}

class _PKCS5Padding extends Padding {
class _PKCS5Padding extends _PKCS7Padding {
@override
final String name = "PKCS5";

Expand All @@ -305,19 +305,4 @@ class _PKCS5Padding extends Padding {
}
return true;
}

@override
int getPadLength(List<int> block, [int? size]) {
size ??= block.length;
int n = block[size - 1];
if (size < n) {
throw StateError('Invalid padding');
}
for (int p = size - n; p < size; p++) {
if (block[p] != n) {
throw StateError('Invalid padding');
}
}
return n;
}
}
50 changes: 50 additions & 0 deletions test/aes_ctr_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,56 @@ void main() {
});
});

group('treats counter as 64-bit number', () {
test('increment from maximum 32-bit integer', () {
var key = fromHex('2b7e151628aed2a6abf7158809cf4f3c');
var plain1 = randomBytes(16);
var plain2 = randomBytes(16);
var plain3 = randomBytes(16);

var iv1 = fromHex('000102030405060708090a00ffffffff');
var sink1 = AESInCTRModeSink(key, iv1);
sink1.add(plain1);
var out1 = sink1.add(plain2);
var out3 = sink1.add(plain3);

var iv2 = fromHex('000102030405060708090a0100000000');
var sink2 = AESInCTRModeSink(key, iv2);
var out2 = sink2.add(plain2);

var iv3 = fromHex('000102030405060708090a0100000001');
var sink3 = AESInCTRModeSink(key, iv3);
var out4 = sink3.add(plain3);

expect(toHex(out1), equals(toHex(out2)));
expect(toHex(out3), equals(toHex(out4)));
});

test('increment from maximum 64-bit integer', () {
var key = fromHex('2b7e151628aed2a6abf7158809cf4f3c');
var plain1 = randomBytes(16);
var plain2 = randomBytes(16);
var plain3 = randomBytes(16);

var iv1 = fromHex('0001020304050607ffffffffffffffff');
var sink1 = AESInCTRModeSink(key, iv1);
sink1.add(plain1);
var out1 = sink1.add(plain2);
var out3 = sink1.add(plain3);

var iv2 = fromHex('00010203040506070000000000000000');
var sink2 = AESInCTRModeSink(key, iv2);
var out2 = sink2.add(plain2);

var iv3 = fromHex('00010203040506070000000000000001');
var sink3 = AESInCTRModeSink(key, iv3);
var out4 = sink3.add(plain3);

expect(toHex(out1), equals(toHex(out2)));
expect(toHex(out3), equals(toHex(out4)));
});
});

group("PKCS#7 padding", () {
group('AES128', () {
var key = 'abcdefghijklmnop'.codeUnits;
Expand Down
17 changes: 17 additions & 0 deletions test/aes_gcm_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ void main() {
test("decryptor name is correct", () {
expect(AES(key).gcm(iv).decryptor.name, "AES#decrypt/GCM/NoPadding");
});
test("tagSize must be between 1 and 16", () {
for (int i = -10; i < 20; ++i) {
if (i >= 1 && i <= 16) {
AESInGCMModeEncryptSink(key, iv, null, i);
AESInGCMModeDecryptSink(key, iv, null, i);
} else {
expect(
() => AESInGCMModeEncryptSink(key, iv, null, i),
throwsStateError,
);
expect(
() => AESInGCMModeDecryptSink(key, iv, null, i),
throwsStateError,
);
}
}
});
test('encryptor sink test (no add after close)', () {
final aes = AES(key).gcm(iv);
var sink = aes.encryptor.createSink();
Expand Down
70 changes: 38 additions & 32 deletions test/aes_xts_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@ void main() {
test("decryptor name is correct", () {
expect(AES(key).xts(iv).decryptor.name, "AES#decrypt/XTS/NoPadding");
});
test("iv must be 16 bytes", () {
for (int i = 0; i < 20; ++i) {
if (i == 16) {
AESInXTSMode(key, Uint8List(i));
} else {
expect(() => AESInXTSMode(key, Uint8List(i)), throwsStateError);
}
}
});
test('encryptor sink test (no add after close)', () {
final aes = AES(key).xts(iv);
var sink = aes.encryptor.createSink();
Expand Down Expand Up @@ -56,6 +65,35 @@ void main() {
sink.reset();
expect([...sink.add(ciphertext), ...sink.close()], equals(output));
});
test('reset iv', () {
var key = randomBytes(32);
var iv = randomBytes(16);
var aes = AES(key).xts(iv);
for (int j = 16; j < 100; j++) {
aes.resetIV();
var inp = randomBytes(j);
var cipher = aes.encrypt(inp);
var plain = aes.decrypt(cipher);
expect(toHex(plain), equals(toHex(inp)), reason: '[size: $j]');
}
});
test('does not allow message size < 16 bytes', () {
var key = randomBytes(32);
var iv = randomBytes(16);
var aes = AES(key).xts(iv);
for (int j = 0; j < 16; j++) {
var inp = Uint8List(j);
expect(() => aes.encrypt(inp), throwsStateError, reason: '[size: $j]');
expect(() => aes.decrypt(inp), throwsStateError, reason: '[size: $j]');
}
});
test('does not allow invalid key sizes', () {
for (int x in [16, 24, 33, 49, 65]) {
var key = Uint8List(x);
var iv = Uint8List(16);
expect(() => AES(key).xts(iv), throwsStateError, reason: '[size: $x]');
}
});
});

// https://csrc.nist.gov/pubs/sp/800/38/a/finals
Expand Down Expand Up @@ -1499,36 +1537,4 @@ void main() {
}
});
});

test('reset iv', () {
var key = randomBytes(32);
var iv = randomBytes(16);
var aes = AES(key).xts(iv);
for (int j = 16; j < 100; j++) {
aes.resetIV();
var inp = randomBytes(j);
var cipher = aes.encrypt(inp);
var plain = aes.decrypt(cipher);
expect(toHex(plain), equals(toHex(inp)), reason: '[size: $j]');
}
});

test('does not allow message size < 16 bytes', () {
var key = randomBytes(32);
var iv = randomBytes(16);
var aes = AES(key).xts(iv);
for (int j = 0; j < 16; j++) {
var inp = Uint8List(j);
expect(() => aes.encrypt(inp), throwsStateError, reason: '[size: $j]');
expect(() => aes.decrypt(inp), throwsStateError, reason: '[size: $j]');
}
});

test('does not allow invalid key sizes', () {
for (int x in [16, 24, 33, 49, 65]) {
var key = Uint8List(x);
var iv = Uint8List(16);
expect(() => AES(key).xts(iv), throwsStateError, reason: '[size: $x]');
}
});
}
123 changes: 122 additions & 1 deletion test/chacha20_poly1305_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) 2024, Sudipto Chandra
// All rights reserved. Check LICENSE file for details.

import 'dart:async';
import 'dart:typed_data';

import 'package:cipherlib/cipherlib.dart';
Expand Down Expand Up @@ -31,13 +32,15 @@ void main() {
"6116",
);
test('convert', () async {
var tag = fromHex('1ae10b594f09e26a7e902ecbd0600691');
var res = chacha20poly1305(
sample.codeUnits,
key,
nonce: nonce,
aad: aad,
);
expect(res.tag.hex(), equals('1ae10b594f09e26a7e902ecbd0600691'));
expect(res.tag.bytes, equals(tag));
expect(res.verify(tag), true);
});
test('convert without aad', () {
var res = chacha20poly1305(
Expand Down Expand Up @@ -102,4 +105,122 @@ void main() {
expect(verified.data, equals(text), reason: '[text size: $j]');
}
});

group('functionality tests', () {
var key = fromHex(
"808182838485868788898a8b8c8d8e8f"
"909192939495969798999a9b9c9d9e9f",
);
var nonce = fromHex("070000004041424344454647");
var sample = "Ladies and Gentlemen of the class of '99: "
"If I could offer you only one tip for the future, "
"sunscreen would be it."
.codeUnits;
var aad = fromHex("50515253c0c1c2c3c4c5c6c7");
var cipher = fromHex(
"d31a8d34648e60db7b86afbc53ef7ec2"
"a4aded51296e08fea9e2b5a736ee62d6"
"3dbea45e8ca9671282fafb69da92728b"
"1a71de0a9e060b2905d6a5b67ecd3b36"
"92ddbd7f2d778b8c9803aee328091b58"
"fab324e4fad675945585808b4831d7bc"
"3ff4def08e4b7a9de576d26586cec64b"
"6116",
);
var algo = ChaCha20Poly1305(
key: key,
aad: aad,
nonce: nonce,
);

test('defines name correctly', () {
expect(algo.name, "ChaCha20/Poly1305");
});
test('accepts integer stream', () async {
var stream = Stream.fromIterable(sample);
var output = await algo.stream(stream).toList();
expect(output, equals(cipher));
});
test('accepts large integer stream', () async {
var input = List.generate(1200, (index) => index);
var stream = Stream.fromIterable(input);
var output = await algo.stream(stream).toList();
var expected = algo.convert(input).data;
expect(output, equals(expected));
});
test('accepts integer stream with onDigest callback', () async {
final done = Completer();
var stream = Stream.fromIterable(sample);
var outputStream = algo.stream(stream, (tag) {
expect(tag.hex(), equals('1ae10b594f09e26a7e902ecbd0600691'));
done.complete();
});
var output = await outputStream.toList();
expect(output, equals(cipher));
await done.future;
});
test('binds stream', () async {
var grouped = [
"Ladies and Gentlemen of the class of '99: ".codeUnits,
"If I could offer you only one tip for the future, ".codeUnits,
"sunscreen would be it.".codeUnits,
];
var stream = Stream.fromIterable(grouped);
var output = [];
await for (var out in algo.bind(stream)) {
output.addAll(out);
}
expect(output, equals(cipher));
});
test('binds large stream', () async {
var input = List.generate(1200, (index) => index);
var stream = Stream.fromIterable([input]);
var output = [];
await for (var out in algo.bind(stream)) {
output.addAll(out);
}
var expected = algo.convert(input).data;
expect(output, equals(expected));
});
test('binds stream with onDigest call', () async {
final done = Completer();
var grouped = [
"Ladies and Gentlemen of the class of '99: ".codeUnits,
"If I could offer you only one tip for the future, ".codeUnits,
"sunscreen would be it.".codeUnits,
];
var stream = Stream.fromIterable(grouped);
var outputStream = algo.bind(stream, (tag) {
expect(tag.hex(), equals('1ae10b594f09e26a7e902ecbd0600691'));
done.complete();
});
var output = [];
await for (var out in outputStream) {
output.addAll(out);
}
expect(output, equals(cipher));
await done.future;
});
test('sink test (no call after close)', () {
var sink = algo.createSink();
expect(sink.hashLength, 16);
expect(sink.derivedKeyLength, 16);

int step = 19;
for (int i = 0; i < sample.length; i += step) {
var inp = sample.skip(i).take(step).toList();
var out = cipher.skip(i).take(step).toList();
expect(sink.add(inp), equals(out));
}
expect(sink.close(), equals([]));
expect(sink.closed, true);
expect(sink.digest().hex(), '1ae10b594f09e26a7e902ecbd0600691');

expect(() => sink.add([1]), throwsStateError);

sink.reset(true);
expect(sink.add(sample), equals(cipher));
expect(sink.digest().hex(), '67a0fa25b34192a2844b8bbde2e76c92');
});
});
}
Loading

0 comments on commit 36b1b25

Please sign in to comment.