diff --git a/packages/native_crypto/test/src/cipher_text_test.dart b/packages/native_crypto/test/src/cipher_text_test.dart new file mode 100644 index 0000000..b466b55 --- /dev/null +++ b/packages/native_crypto/test/src/cipher_text_test.dart @@ -0,0 +1,192 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: cipher_text_test.dart +// Created Date: 26/05/2022 20:45:38 +// Last Modified: 26/05/2022 21:29:51 +// ----- +// Copyright (c) 2022 + +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_crypto/native_crypto.dart'; + +void main() { + setUp(() { + Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk; + }); + + group('fromBytes', () { + test('throws if length is not the one expected', () { + final Uint8List bytes = Uint8List.fromList([1, 2, 3, 4, 5]); + expect( + () => CipherText.fromBytes( + bytes, + ivLength: 1, + messageLength: 1, + tagLength: 1, + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('Invalid cipher text length'), + ), + ), + ); + }); + + test('throws if length is bigger than expected', () { + final Uint8List bytes = Uint8List.fromList([1, 3, 3, 3, 1]); + Cipher.bytesCountPerChunk = 2; + expect( + () => CipherText.fromBytes( + bytes, + ivLength: 1, + messageLength: 3, + tagLength: 1, + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('Cipher text is too big'), + ), + ), + ); + }); + + test('throws if data is empty', () { + final Uint8List bytes = Uint8List(0); + expect( + () => CipherText.fromBytes( + bytes, + ivLength: 1, + messageLength: 3, + tagLength: 1, + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('Passed data is empty'), + ), + ), + ); + }); + + test('throws if one of the length is negative', () { + final Uint8List bytes = Uint8List(0); + expect( + () => CipherText.fromBytes( + bytes, + ivLength: -1, + messageLength: 1, + tagLength: 1, + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('Invalid length'), + ), + ), + ); + }); + }); + + group('get.cipherAlgorithm', () { + test('throws if not set', () { + final CipherText cipherText = CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + expect( + () => cipherText.cipherAlgorithm, + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_cipher', + ) + .having( + (e) => e.message, + 'message', + contains('Cipher algorithm is not specified'), + ), + ), + ); + }); + + test('returns the expected value', () { + final CipherText cipherText = CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + cipherAlgorithm: CipherAlgorithm.aes, + ); + expect(cipherText.cipherAlgorithm, CipherAlgorithm.aes); + }); + }); + + group('Lengths', () { + test('get.ivLength returns the expected value', () { + final CipherText cipherText = CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + expect(cipherText.ivLength, 1); + }); + + test('get.messageLength returns the expected value', () { + final CipherText cipherText = CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + expect(cipherText.messageLength, 1); + }); + + test('get.tagLength returns the expected value', () { + final CipherText cipherText = CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + expect(cipherText.tagLength, 1); + }); + }); +} diff --git a/packages/native_crypto/test/src/cipher_text_wrapper_test.dart b/packages/native_crypto/test/src/cipher_text_wrapper_test.dart new file mode 100644 index 0000000..7477900 --- /dev/null +++ b/packages/native_crypto/test/src/cipher_text_wrapper_test.dart @@ -0,0 +1,327 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: cipher_text_wrapper_test.dart +// Created Date: 26/05/2022 21:35:41 +// Last Modified: 26/05/2022 22:27:31 +// ----- +// Copyright (c) 2022 + +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_crypto/native_crypto.dart'; + +void main() { + late CipherText single; + late List list; + + setUp(() { + Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk; + single = CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + list = [ + CipherText.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ), + CipherText.fromBytes( + Uint8List.fromList([4, 5, 6]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ), + ]; + }); + + group('single', () { + test('makes isSingle true', () { + final wrapper = CipherTextWrapper.single(single); + expect(wrapper.isSingle, isTrue); + }); + + test('makes isList false', () { + final wrapper = CipherTextWrapper.single(single); + expect(wrapper.isList, isFalse); + }); + + test('makes CipherText the single value', () { + final wrapper = CipherTextWrapper.single(single); + expect(wrapper.single, single); + }); + + test('throws when trying to get list', () { + final wrapper = CipherTextWrapper.single(single); + expect( + () => wrapper.list, + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('is not list'), + ), + ), + ); + }); + + test('makes wrapper returns bytes of CipherText', () { + final wrapper = CipherTextWrapper.single(single); + expect(wrapper.bytes, single.bytes); + }); + + test('makes chunkCount = 1', () { + final wrapper = CipherTextWrapper.single(single); + expect(wrapper.chunkCount, 1); + }); + + test('makes unwrap() returns only CipherText', () { + final wrapper = CipherTextWrapper.single(single); + expect(wrapper.unwrap(), single); + }); + + test('makes unwrap() throws when trying to unwrap List', () { + final wrapper = CipherTextWrapper.single(single); + expect( + () => wrapper.unwrap>(), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('you should use unwrap'), + ), + ), + ); + }); + + test('makes adding is not supported', () { + final wrapper = CipherTextWrapper.single(single); + expect( + () => wrapper.add(single), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('is already single'), + ), + ), + ); + }); + }); + + group('list', () { + test('makes isList true', () { + final wrapper = CipherTextWrapper.list(list); + expect(wrapper.isList, isTrue); + }); + + test('makes isSingle false', () { + final wrapper = CipherTextWrapper.list(list); + expect(wrapper.isSingle, isFalse); + }); + + test('makes List the list value', () { + final wrapper = CipherTextWrapper.list(list); + expect(wrapper.list, list); + }); + + test('throws when trying to get single', () { + final wrapper = CipherTextWrapper.list(list); + expect( + () => wrapper.single, + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('is not single'), + ), + ), + ); + }); + + test('makes wrapper returns bytes of all CipherText joined', () { + final wrapper = CipherTextWrapper.list(list); + expect(wrapper.bytes, Uint8List.fromList([1, 2, 3, 4, 5, 6])); + }); + + test('makes chunkCount = 2', () { + final wrapper = CipherTextWrapper.list(list); + expect(wrapper.chunkCount, 2); + }); + + test('makes unwrap() returns List', () { + final wrapper = CipherTextWrapper.list(list); + expect(wrapper.unwrap>(), list); + }); + + test('makes unwrap() throws when trying to unwrap single', () { + final wrapper = CipherTextWrapper.list(list); + expect( + () => wrapper.unwrap(), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('you should use unwrap'), + ), + ), + ); + }); + + test('makes adding is supported', () { + final originalList = List.from(list); + final wrapper = CipherTextWrapper.list(list)..add(single); + printOnFailure(list.length.toString()); + expect(wrapper.list, [...originalList, single]); + }); + }); + + group('empty', () { + test('makes isList true', () { + final wrapper = CipherTextWrapper.empty(); + expect(wrapper.isList, isTrue); + }); + + test('makes isSingle false', () { + final wrapper = CipherTextWrapper.empty(); + expect(wrapper.isSingle, isFalse); + }); + + test('makes List the list value', () { + final wrapper = CipherTextWrapper.empty(); + expect(wrapper.list, []); + }); + + test('throws when trying to get single', () { + final wrapper = CipherTextWrapper.empty(); + expect( + () => wrapper.single, + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('is not single'), + ), + ), + ); + }); + + test('makes wrapper returns empty bytes', () { + final wrapper = CipherTextWrapper.empty(); + expect(wrapper.bytes, Uint8List.fromList([])); + }); + + test('makes chunkCount = 0', () { + final wrapper = CipherTextWrapper.empty(); + expect(wrapper.chunkCount, 0); + }); + + test('makes unwrap() returns empty List', () { + final wrapper = CipherTextWrapper.empty(); + expect(wrapper.unwrap>(), []); + }); + + test('makes unwrap() throws when trying to unwrap single', () { + final wrapper = CipherTextWrapper.empty(); + expect( + () => wrapper.unwrap(), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_data', + ) + .having( + (e) => e.message, + 'message', + contains('you should use unwrap'), + ), + ), + ); + }); + + test('makes adding is supported', () { + final wrapper = CipherTextWrapper.empty()..add(single); + expect(wrapper.list, [single]); + }); + }); + + group('fromBytes', () { + test('creates single from bytes when no too big', () { + final wrapper = CipherTextWrapper.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + expect(wrapper.isSingle, isTrue); + expect(wrapper.single, single); + }); + + test('creates list from bytes when too big', () { + Cipher.bytesCountPerChunk = 3; + final wrapper = CipherTextWrapper.fromBytes( + Uint8List.fromList([1, 2, 3, 4, 5, 6]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + ); + expect(wrapper.isList, isTrue); + expect(wrapper.list, list); + }); + + test('modifies Cipher.bytesCountPerChunk', () { + expect(Cipher.bytesCountPerChunk, Cipher.defaultBytesCountPerChunk); + CipherTextWrapper.fromBytes( + Uint8List.fromList([1, 2, 3]), + ivLength: 1, + messageLength: 1, + tagLength: 1, + chunkSize: 3, + ); + expect(Cipher.bytesCountPerChunk, 3); + }); + }); +} diff --git a/packages/native_crypto/test/src/hash_algorithm_test.dart b/packages/native_crypto/test/src/hash_algorithm_test.dart new file mode 100644 index 0000000..b7911b1 --- /dev/null +++ b/packages/native_crypto/test/src/hash_algorithm_test.dart @@ -0,0 +1,128 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: hash_algorithm_test.dart +// Created Date: 26/05/2022 22:28:53 +// Last Modified: 26/05/2022 23:03:03 +// ----- +// Copyright (c) 2022 + +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_crypto/src/utils/hash_algorithm.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +import '../mocks/mock_native_crypto_platform.dart'; + +void main() { + final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); + NativeCryptoPlatform.instance = mock; + + group('name', () { + test('is sha256 for HashAlgorithm.sha256', () { + expect(HashAlgorithm.sha256.name, 'sha256'); + }); + test('is sha384 for HashAlgorithm.sha384', () { + expect(HashAlgorithm.sha384.name, 'sha384'); + }); + test('is sha512 for HashAlgorithm.sha512', () { + expect(HashAlgorithm.sha512.name, 'sha512'); + }); + }); + + group('digest', () { + test('handles returning empty list', () async { + mock + ..setDigestExpectations( + data: Uint8List.fromList([1, 2, 3]), + algorithm: 'sha256', + ) + ..setResponse(() => Uint8List(0)); + + await expectLater( + () => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])), + throwsA( + isA().having( + (e) => e.code, + 'code', + 'platform_returned_empty_data', + ), + ), + ); + }); + + test('handles returning null', () async { + mock + ..setDigestExpectations( + data: Uint8List.fromList([1, 2, 3]), + algorithm: 'sha256', + ) + ..setResponse(() => null); + + await expectLater( + () => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])), + throwsA( + isA().having( + (e) => e.code, + 'code', + 'platform_returned_null', + ), + ), + ); + }); + + test('handles throwing PlatformException', () async { + mock + ..setDigestExpectations( + data: Uint8List.fromList([1, 2, 3]), + algorithm: 'sha256', + ) + ..setResponse( + () => throw PlatformException( + code: 'native_crypto', + message: 'dummy error', + ), + ); + + await expectLater( + () => HashAlgorithm.sha256.digest(Uint8List.fromList([1, 2, 3])), + throwsA( + isA() + .having( + (e) => e.message, + 'message', + 'PlatformException(native_crypto, dummy error, null, null)', + ) + .having( + (e) => e.code, + 'code', + 'platform_throws', + ), + ), + ); + }); + + test('returns data on success', () async { + final hash = Uint8List.fromList([4, 5, 6]); + mock + ..setDigestExpectations( + data: Uint8List.fromList([1, 2, 3]), + algorithm: 'sha256', + ) + ..setResponse(() => hash); + + final result = await HashAlgorithm.sha256.digest( + Uint8List.fromList( + [1, 2, 3], + ), + ); + + expect( + result, + hash, + ); + }); + }); +} diff --git a/packages/native_crypto/test/src/pbkdf2_test.dart b/packages/native_crypto/test/src/pbkdf2_test.dart new file mode 100644 index 0000000..f0ae084 --- /dev/null +++ b/packages/native_crypto/test/src/pbkdf2_test.dart @@ -0,0 +1,280 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: pbkdf2_test.dart +// Created Date: 26/05/2022 22:37:27 +// Last Modified: 26/05/2022 23:20:11 +// ----- +// Copyright (c) 2022 + +import 'dart:typed_data'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +import '../mocks/mock_native_crypto_platform.dart'; + +void main() { + final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); + NativeCryptoPlatform.instance = mock; + + group('Constructor', () { + test('throws if keyBytesCount is negative', () { + expect( + () => Pbkdf2(keyBytesCount: -1, iterations: 10000), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('must be positive'), + ), + ), + ); + }); + + test('throws if iterations is negative or 0', () { + expect( + () => Pbkdf2(keyBytesCount: 32, iterations: -1), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('must be strictly positive'), + ), + ), + ); + }); + }); + + group('derive', () { + test('throws if password is null', () async { + final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); + await expectLater( + () => pbkdf2.derive( + salt: 'salt', + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('cannot be null'), + ), + ), + ); + }); + + test('throws if salt is null', () async { + final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); + await expectLater( + () => pbkdf2.derive( + password: 'password', + ), + throwsA( + isA() + .having( + (e) => e.code, + 'code', + 'invalid_argument', + ) + .having( + (e) => e.message, + 'message', + contains('cannot be null'), + ), + ), + ); + }); + + test('handles returning empty list', () async { + mock + ..setPbkdf2Expectations( + password: 'password', + salt: 'salt', + keyBytesCount: 32, + iterations: 10000, + algorithm: 'sha256', + ) + ..setResponse(() => Uint8List(0)); + + final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); + + await expectLater( + () => pbkdf2.derive( + password: 'password', + salt: 'salt', + ), + throwsA( + isA().having( + (e) => e.code, + 'code', + 'platform_returned_empty_data', + ), + ), + ); + }); + + test('handles returning null', () async { + mock + ..setPbkdf2Expectations( + password: 'password', + salt: 'salt', + keyBytesCount: 32, + iterations: 10000, + algorithm: 'sha256', + ) + ..setResponse(() => null); + + final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); + + await expectLater( + () => pbkdf2.derive( + password: 'password', + salt: 'salt', + ), + throwsA( + isA().having( + (e) => e.code, + 'code', + 'platform_returned_null', + ), + ), + ); + }); + + test('handles returning data with wrong length', () async { + mock + ..setPbkdf2Expectations( + password: 'password', + salt: 'salt', + keyBytesCount: 32, + iterations: 10000, + algorithm: 'sha256', + ) + ..setResponse(() => Uint8List(33)); + + final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); + + await expectLater( + () => pbkdf2.derive( + password: 'password', + salt: 'salt', + ), + throwsA( + isA().having( + (e) => e.code, + 'code', + 'platform_returned_invalid_data', + ), + ), + ); + }); + + test('handles throwing PlatformException', () async { + mock + ..setPbkdf2Expectations( + password: 'password', + salt: 'salt', + keyBytesCount: 32, + iterations: 10000, + algorithm: 'sha256', + ) + ..setResponse( + () => throw PlatformException( + code: 'native_crypto', + message: 'dummy error', + ), + ); + + final pbkdf2 = Pbkdf2(keyBytesCount: 32, iterations: 10000); + + await expectLater( + () => pbkdf2.derive( + password: 'password', + salt: 'salt', + ), + throwsA( + isA() + .having( + (e) => e.message, + 'message', + 'PlatformException(native_crypto, dummy error, null, null)', + ) + .having( + (e) => e.code, + 'code', + 'platform_throws', + ), + ), + ); + }); + + test('returns SecretKey on success', () async { + final data = Uint8List.fromList([1, 2, 3, 4, 5, 6]); + final sk = SecretKey(data); + mock + ..setPbkdf2Expectations( + password: 'password', + salt: 'salt', + keyBytesCount: 6, + iterations: 10000, + algorithm: 'sha256', + ) + ..setResponse(() => data); + + final pbkdf = Pbkdf2(keyBytesCount: 6, iterations: 10000); + final result = await pbkdf.derive( + password: 'password', + salt: 'salt', + ); + + expect( + result, + sk, + ); + }); + + test('return empty SecretKey when keyBytesCount is set to 0', () async { + final sk = SecretKey(Uint8List(0)); + mock + ..setPbkdf2Expectations( + password: 'password', + salt: 'salt', + keyBytesCount: 0, + iterations: 10000, + algorithm: 'sha256', + ) + ..setResponse(() => Uint8List(0)); + + final pbkdf = Pbkdf2(keyBytesCount: 0, iterations: 10000); + final result = await pbkdf.derive( + password: 'password', + salt: 'salt', + ); + + expect( + result, + sk, + ); + }); + }); +} diff --git a/packages/native_crypto/test/src/secret_key_test.dart b/packages/native_crypto/test/src/secret_key_test.dart index e8deb2e..df2f159 100644 --- a/packages/native_crypto/test/src/secret_key_test.dart +++ b/packages/native_crypto/test/src/secret_key_test.dart @@ -3,7 +3,7 @@ // ----- // File: secret_key_test.dart // Created Date: 26/05/2022 10:52:41 -// Last Modified: 26/05/2022 19:24:44 +// Last Modified: 26/05/2022 22:38:07 // ----- // Copyright (c) 2022 @@ -20,6 +20,32 @@ void main() { final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); NativeCryptoPlatform.instance = mock; + group('Constructors', () { + test('handles Uint8List', () { + final SecretKey key = SecretKey(Uint8List.fromList([1, 2, 3, 4, 5])); + + expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); + }); + + test('handles base16', () { + final SecretKey key = SecretKey.fromBase16('0102030405'); + + expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); + }); + + test('handles base64', () { + final SecretKey key = SecretKey.fromBase64('AQIDBAU='); + + expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); + }); + + test('handles utf8', () { + final SecretKey key = SecretKey.fromUtf8('ABCDE'); + + expect(key.bytes, Uint8List.fromList([65, 66, 67, 68, 69])); + }); + }); + group('fromSecureRandom', () { test('handles returning random bytes', () async { mock @@ -96,30 +122,4 @@ void main() { ); }); }); - - group('Constructors', () { - test('handles Uint8List', () { - final SecretKey key = SecretKey(Uint8List.fromList([1, 2, 3, 4, 5])); - - expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); - }); - - test('handles base16', () { - final SecretKey key = SecretKey.fromBase16('0102030405'); - - expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); - }); - - test('handles base64', () { - final SecretKey key = SecretKey.fromBase64('AQIDBAU='); - - expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); - }); - - test('handles utf8', () { - final SecretKey key = SecretKey.fromUtf8('ABCDE'); - - expect(key.bytes, Uint8List.fromList([65, 66, 67, 68, 69])); - }); - }); }