diff --git a/packages/native_crypto/lib/native_crypto.dart b/packages/native_crypto/lib/native_crypto.dart index c42ea91..2b294a4 100644 --- a/packages/native_crypto/lib/native_crypto.dart +++ b/packages/native_crypto/lib/native_crypto.dart @@ -3,7 +3,7 @@ // ----- // File: native_crypto.dart // Created Date: 16/12/2021 16:28:00 -// Last Modified: 25/05/2022 10:48:20 +// Last Modified: 26/05/2022 12:10:42 // ----- // Copyright (c) 2021 @@ -23,7 +23,6 @@ export 'src/kdf/kdf.dart'; export 'src/keys/keys.dart'; // Utils export 'src/utils/cipher_algorithm.dart'; -export 'src/utils/convert.dart'; export 'src/utils/hash_algorithm.dart'; export 'src/utils/kdf_algorithm.dart'; diff --git a/packages/native_crypto/lib/src/core/cipher_text.dart b/packages/native_crypto/lib/src/core/cipher_text.dart index fcf09aa..76a9399 100644 --- a/packages/native_crypto/lib/src/core/cipher_text.dart +++ b/packages/native_crypto/lib/src/core/cipher_text.dart @@ -3,81 +3,144 @@ // ----- // File: cipher_text.dart // Created Date: 16/12/2021 16:59:53 -// Last Modified: 24/05/2022 21:27:44 +// Last Modified: 26/05/2022 16:22:49 // ----- // Copyright (c) 2021 import 'dart:typed_data'; -import 'package:native_crypto/src/interfaces/byte_array.dart'; +import 'package:native_crypto/src/utils/cipher_algorithm.dart'; +import 'package:native_crypto/src/utils/extensions.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; -class CipherText extends ByteArray { +/// Represents a cipher text in Native Crypto. +/// +/// It is represented as a [List] of [Uint8List] like: +/// ```txt +/// [[NONCE], [MESSAGE + TAG]] +/// ``` +/// where: +/// - `[NONCE]` is a [Uint8List] of length [CipherText.ivLength] +/// - `[MESSAGE + TAG]` is a [Uint8List] of length [CipherText.dataLength] +/// +/// To +/// +/// So accessing just the Message or just the Tag is costly and should be +/// done only when needed. +class CipherText { final int _ivLength; - final int _dataLength; + final int _messageLength; final int _tagLength; - final Uint8List _iv; + final CipherAlgorithm? _cipherAlgorithm; - CipherText(Uint8List iv, Uint8List data, Uint8List? tag) - : _ivLength = iv.length, - _dataLength = data.length, - _tagLength = tag?.length ?? 0, - _iv = iv, - super((tag != null) ? Uint8List.fromList(data + tag) : data); + final Uint8List? _iv; + final Uint8List? _data; // Contains the message + tag (if any) - CipherText.fromBytes( - Uint8List bytes, { - required int ivLength, - required int dataLength, - int tagLength = 0, - }) : _ivLength = ivLength, - _dataLength = dataLength, - _tagLength = tagLength, - _iv = bytes.sublist(0, ivLength), - super(bytes.sublist(ivLength, bytes.length - tagLength)); + CipherText._( + this._ivLength, + this._messageLength, + this._tagLength, + this._cipherAlgorithm, + this._iv, + this._data, + ); - const CipherText.fromIvAndBytes( - Uint8List iv, - super.data, { - required int dataLength, - int tagLength = 0, - }) : _ivLength = iv.length, - _dataLength = dataLength, - _tagLength = tagLength, - _iv = iv; + /// Gets the [CipherAlgorithm] used to encrypt the [CipherText]. + CipherAlgorithm get cipherAlgorithm { + if (_cipherAlgorithm.isNotNull) { + return _cipherAlgorithm!; + } else { + throw NativeCryptoException( + message: 'Cipher algorithm is not specified', + code: NativeCryptoExceptionCode.invalid_cipher.code, + ); + } + } - CipherText.fromPairIvAndBytes( - List pair, { - required int dataLength, - int tagLength = 0, - }) : _ivLength = pair.first.length, - _dataLength = dataLength, - _tagLength = tagLength, - _iv = pair.first, - super(pair.last); + /// Gets the [Uint8List] of the [CipherText]'s IV. + Uint8List get iv { + if (_iv.isNotNull) { + return _iv!; + } else { + throw NativeCryptoException( + message: 'IV is not specified', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } + } - /// Gets the CipherText IV. - Uint8List get iv => _iv; - - /// Gets the CipherText data. - Uint8List get data => _tagLength > 0 - ? bytes.sublist(0, _dataLength - _tagLength) - : bytes; - - /// Gets the CipherText tag. - Uint8List get tag => _tagLength > 0 - ? bytes.sublist(_dataLength - _tagLength, _dataLength) - : Uint8List(0); - - /// Gets the CipherText data and tag. - Uint8List get payload => bytes; - - /// Gets the CipherText IV length. + /// Gets the length of the [CipherText]'s IV. int get ivLength => _ivLength; - /// Gets the CipherText data length. - int get dataLength => _dataLength; + /// Gets the [Uint8List] of the [CipherText]'s data. + Uint8List get data { + if (_data.isNotNull) { + return _data!; + } else { + throw NativeCryptoException( + message: 'Data is not specified', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } + } - /// Gets the CipherText tag length. - int get tagLength => _tagLength; + /// Gets the length of the [CipherText]'s data. + int get dataLength => _messageLength + _tagLength; + + // CipherText.fromBytes( + // Uint8List bytes, { + // required int ivLength, + // required int dataLength, + // int tagLength = 0, + // }) : _ivLength = ivLength, + // _dataLength = dataLength, + // _tagLength = tagLength, + // _iv = bytes.sublist(0, ivLength), + // super(bytes.sublist(ivLength, bytes.length - tagLength)); + + // const CipherText.fromIvAndBytes( + // Uint8List iv, + // super.data, { + // required int dataLength, + // int tagLength = 0, + // }) : _ivLength = iv.length, + // _dataLength = dataLength, + // _tagLength = tagLength, + // _iv = iv; + + // CipherText.fromPairIvAndBytes( + // List pair, { + // required int dataLength, + // int tagLength = 0, + // }) : _ivLength = pair.first.length, + // _dataLength = dataLength, + // _tagLength = tagLength, + // _iv = pair.first, + // super(pair.last); + + // /// Gets the CipherText IV. + // Uint8List get iv => _iv; + + // /// Gets the CipherText data. + // Uint8List get data => _tagLength > 0 + // ? bytes.sublist(0, _dataLength - _tagLength) + // : bytes; + + // /// Gets the CipherText tag. + // Uint8List get tag => _tagLength > 0 + // ? bytes.sublist(_dataLength - _tagLength, _dataLength) + // : Uint8List(0); + + // /// Gets the CipherText data and tag. + // Uint8List get payload => bytes; + + // /// Gets the CipherText IV length. + // int get ivLength => _ivLength; + + // /// Gets the CipherText data length. + // int get dataLength => _dataLength; + + // /// Gets the CipherText tag length. + // int get tagLength => _tagLength; } diff --git a/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart b/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart new file mode 100644 index 0000000..a65cc23 --- /dev/null +++ b/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart @@ -0,0 +1,89 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: cipher_text_wrapper.dart +// Created Date: 26/05/2022 14:27:32 +// Last Modified: 26/05/2022 15:53:46 +// ----- +// Copyright (c) 2022 + +import 'dart:typed_data'; + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto/src/utils/extensions.dart'; + +class CipherTextWrapper { + final CipherText? _single; + final List? _list; + + CipherTextWrapper._(this._single, this._list); + + factory CipherTextWrapper.single(CipherText cipherText) => + CipherTextWrapper._(cipherText, null); + + factory CipherTextWrapper.list(List cipherTexts) => + CipherTextWrapper._(null, cipherTexts); + + factory CipherTextWrapper.fromBytes( + // Uint8List bytes, { + // required int ivLength, + // required int dataLength, + // int tagLength = 0, + // int? chunkSize, + // } + ) { + // TODO(hpcl): implement fromBytes + throw UnimplementedError(); + } + + bool get isSingle => _single.isNotNull; + bool get isList => _list.isNotNull; + + /// Gets the [CipherText] if it's a single one. + /// + /// Throws [NativeCryptoException] with + /// [NativeCryptoExceptionCode.invalid_data] if it's not a single one. + CipherText get single { + if (isSingle) { + return _single!; + } else { + throw NativeCryptoException( + message: 'CipherTextWrapper is not single', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } + } + + /// Gets the [List] of [CipherText] if it's a list. + /// + /// Throws [NativeCryptoException] with + /// [NativeCryptoExceptionCode.invalid_data] if it's not a list. + List get list { + if (isList) { + return _list!; + } else { + throw NativeCryptoException( + message: 'CipherTextWrapper is not list', + code: NativeCryptoExceptionCode.invalid_data.code, + ); + } + } + + /// Gets the raw [Uint8List] of the [CipherText] or [List] of [CipherText]. + Uint8List get raw { + if (isSingle) { + return single.bytes; + } else { + return list.map((cipherText) => cipherText.bytes).toList().sum(); + } + } + + int get chunkCount { + _single.isNull; + if (_single.isNotNull) { + return 1; + } else { + return _list?.length ?? 0; + } + } +} diff --git a/packages/native_crypto/lib/src/interfaces/byte_array.dart b/packages/native_crypto/lib/src/interfaces/byte_array.dart index 23d3ba4..d99d115 100644 --- a/packages/native_crypto/lib/src/interfaces/byte_array.dart +++ b/packages/native_crypto/lib/src/interfaces/byte_array.dart @@ -3,47 +3,58 @@ // ----- // File: byte_array.dart // Created Date: 16/12/2021 17:54:16 -// Last Modified: 23/05/2022 23:07:03 +// Last Modified: 26/05/2022 14:25:05 // ----- // Copyright (c) 2021 -import 'dart:convert' as convert; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; -import 'package:native_crypto/src/utils/convert.dart'; +import 'package:native_crypto/src/utils/encoding.dart'; +import 'package:native_crypto/src/utils/extensions.dart'; @immutable abstract class ByteArray { final Uint8List _bytes; + /// Creates a [ByteArray] from a [Uint8List]. const ByteArray(this._bytes); - /// Creates an ByteArray object from a hexdecimal string. + /// Creates a [ByteArray] object from a hexdecimal string. ByteArray.fromBase16(String encoded) - : _bytes = Convert.decodeHexString(encoded); + : _bytes = encoded.toBytes(from: Encoding.base16); - /// Creates an ByteArray object from a Base64 string. + /// Creates a [ByteArray] object from a Base64 string. ByteArray.fromBase64(String encoded) - : _bytes = convert.base64.decode(encoded); + : _bytes = encoded.toBytes(from: Encoding.base64); - /// Creates an ByteArray object from a UTF-8 string. - ByteArray.fromUtf8(String input) - : _bytes = Uint8List.fromList(convert.utf8.encode(input)); + /// Creates a [ByteArray] object from an UTF-8 string. + ByteArray.fromUtf8(String encoded) + : _bytes = encoded.toBytes(from: Encoding.utf8); - /// Creates an ByteArray object from a length. + /// Creates a [ByteArray] object from an UTF-16 string. + ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes(); + + /// Creates an empty [ByteArray] object from a length. ByteArray.fromLength(int length) : _bytes = Uint8List(length); - /// Gets the ByteArray bytes. - // ignore: unnecessary_getters_setters + /// Creates a [ByteArray] object from a [List] of [int]. + ByteArray.fromList(List list) : _bytes = list.toTypedList(); + + /// Gets the [ByteArray] bytes. Uint8List get bytes => _bytes; - /// Gets the ByteArray bytes as a Hexadecimal representation. - String get base16 => - _bytes.map((byte) => byte.toRadixString(16).padLeft(2, '0')).join(); + /// Gets the [ByteArray] bytes as a Hexadecimal representation. + String get base16 => _bytes.toStr(to: Encoding.base16); - /// Gets the ByteArray bytes as a Base64 representation. - String get base64 => convert.base64.encode(_bytes); + /// Gets the [ByteArray] bytes as a Base64 representation. + String get base64 => _bytes.toStr(to: Encoding.base64); + + /// Gets the [ByteArray] bytes as an UTF-8 representation. + String get utf8 => _bytes.toStr(to: Encoding.utf8); + + /// Gets the [ByteArray] bytes as an UTF-16 representation. + String get utf16 => _bytes.toStr(); @override bool operator ==(Object other) { diff --git a/packages/native_crypto/lib/src/keys/secret_key.dart b/packages/native_crypto/lib/src/keys/secret_key.dart index 1aa9031..32f539b 100644 --- a/packages/native_crypto/lib/src/keys/secret_key.dart +++ b/packages/native_crypto/lib/src/keys/secret_key.dart @@ -3,7 +3,7 @@ // ----- // File: secret_key.dart // Created Date: 28/12/2021 13:36:54 -// Last Modified: 25/05/2022 10:45:55 +// Last Modified: 26/05/2022 11:56:06 // ----- // Copyright (c) 2021 @@ -24,13 +24,22 @@ class SecretKey extends Key { static Future fromSecureRandom(int bitsCount) async { try { - final Uint8List _key = - (await platform.generateSecretKey(bitsCount)) ?? Uint8List(0); + final Uint8List? _key = await platform.generateSecretKey(bitsCount); + + if (_key == null || _key.isEmpty) { + throw const KeyException( + message: 'Could not generate secret key, platform returned null', + code: 'platform_returned_null', + ); + } return SecretKey(_key); } catch (e, s) { + if (e is KeyException) { + rethrow; + } throw KeyException( - message: 'Failed to generate a secret key!', + message: '$e', code: 'failed_to_generate_secret_key', stackTrace: s, ); diff --git a/packages/native_crypto/lib/src/utils/convert.dart b/packages/native_crypto/lib/src/utils/convert.dart deleted file mode 100644 index f68bb78..0000000 --- a/packages/native_crypto/lib/src/utils/convert.dart +++ /dev/null @@ -1,23 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: convert.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 23/05/2022 22:39:19 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -abstract class Convert { - static Uint8List decodeHexString(String input) { - assert(input.length.isEven, 'Input needs to be an even length.'); - - return Uint8List.fromList( - List.generate( - input.length ~/ 2, - (i) => int.parse(input.substring(i * 2, (i * 2) + 2), radix: 16), - ).toList(), - ); - } -} diff --git a/packages/native_crypto/lib/src/utils/encoding.dart b/packages/native_crypto/lib/src/utils/encoding.dart new file mode 100644 index 0000000..b7ddd80 --- /dev/null +++ b/packages/native_crypto/lib/src/utils/encoding.dart @@ -0,0 +1,10 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: encoding.dart +// Created Date: 26/05/2022 12:12:34 +// Last Modified: 26/05/2022 12:18:09 +// ----- +// Copyright (c) 2022 + +enum Encoding { utf8, utf16, base64, base16 } diff --git a/packages/native_crypto/lib/src/utils/extensions.dart b/packages/native_crypto/lib/src/utils/extensions.dart new file mode 100644 index 0000000..91b9949 --- /dev/null +++ b/packages/native_crypto/lib/src/utils/extensions.dart @@ -0,0 +1,95 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: extensions.dart +// Created Date: 26/05/2022 12:12:48 +// Last Modified: 26/05/2022 15:49:38 +// ----- +// Copyright (c) 2022 + +import 'dart:convert'; +import 'dart:developer' as developer; +import 'dart:typed_data'; + +import 'package:native_crypto/src/utils/encoding.dart'; + +extension ObjectX on Object? { + /// Returns `true` if the object is `null`. + bool get isNull => this == null; + + /// Returns `true` if the object is **not** `null`. + bool get isNotNull => this != null; + + /// Prints the object to the console. + void log() => developer.log(toString()); +} + +extension ListIntX on List { + /// Converts a [List] of int to a [Uint8List]. + Uint8List toTypedList() => Uint8List.fromList(this); +} + +extension ListUint8ListX on List { + /// Reduce a [List] of [Uint8List] to a [Uint8List]. + + Uint8List sum() { + for (var i = 1; i < length; i++) { + first.addAll(this[i]); + removeAt(i); + } + return first; + } +} + +extension StringX on String { + /// Converts a [String] to a [Uint8List] using the specified [Encoding]. + Uint8List toBytes({final Encoding from = Encoding.utf16}) { + Uint8List bytes; + switch (from) { + case Encoding.utf8: + bytes = utf8.encode(this).toTypedList(); + break; + case Encoding.utf16: + bytes = runes.toList().toTypedList(); + break; + case Encoding.base64: + bytes = base64.decode(this); + break; + case Encoding.base16: + assert(length.isEven, 'String needs to be an even length.'); + bytes = List.generate( + length ~/ 2, + (i) => int.parse(substring(i * 2, (i * 2) + 2), radix: 16), + ).toList().toTypedList(); + } + return bytes; + } +} + +extension Uint8ListX on Uint8List { + /// Converts a [Uint8List] to a [String] using the specified [Encoding]. + String toStr({final Encoding to = Encoding.utf16}) { + String str; + switch (to) { + case Encoding.utf8: + str = utf8.decode(this); + break; + case Encoding.utf16: + str = String.fromCharCodes(this); + break; + case Encoding.base64: + str = base64.encode(this); + break; + case Encoding.base16: + str = List.generate( + length, + (i) => this[i].toRadixString(16).padLeft(2, '0'), + ).join(); + } + return str; + } + + /// Returns a concatenation of this with the other [Uint8List]. + Uint8List operator +(final Uint8List other) => + [...this, ...other].toTypedList(); +} diff --git a/packages/native_crypto_platform_interface/lib/src/utils/exception.dart b/packages/native_crypto_platform_interface/lib/src/utils/exception.dart index 196a2bd..5b571c2 100644 --- a/packages/native_crypto_platform_interface/lib/src/utils/exception.dart +++ b/packages/native_crypto_platform_interface/lib/src/utils/exception.dart @@ -3,21 +3,41 @@ // ----- // File: exception.dart // Created Date: 24/05/2022 18:54:48 -// Last Modified: 25/05/2022 10:43:29 +// Last Modified: 26/05/2022 15:36:56 // ----- // Copyright (c) 2022 +// ignore_for_file: constant_identifier_names + import 'dart:developer'; import 'package:flutter/services.dart'; +enum NativeCryptoExceptionCode { + unknown, + not_implemented, + invalid_argument, + invalid_key, + invalid_key_length, + invalid_algorithm, + invalid_padding, + invalid_mode, + invalid_cipher, + invalid_data, + platform_not_supported, + platform_returned_invalid_data, + platform_returned_empty_data, + platform_returned_null; + + String get code => toString().split('.').last.toLowerCase(); +} + class NativeCryptoException implements Exception { - const NativeCryptoException({ + NativeCryptoException({ this.message, String? code, this.stackTrace, - // ignore: unnecessary_this - }) : this.code = code ?? 'unknown'; + }) : code = code ?? NativeCryptoExceptionCode.unknown.code; /// The long form message of the exception. final String? message; @@ -73,7 +93,7 @@ class NativeCryptoException implements Exception { ) : null; - String code = 'unknown'; + String code = NativeCryptoExceptionCode.unknown.code; String message = platformException.message ?? ''; if (details != null) { @@ -103,51 +123,3 @@ class NativeCryptoException implements Exception { // ignore: avoid_equals_and_hash_code_on_mutable_classes int get hashCode => message.hashCode ^ code.hashCode ^ stackTrace.hashCode; } - -class KeyException extends NativeCryptoException { - const KeyException({ - super.message, - super.code, - super.stackTrace, - }); -} - -class KeyDerivationException extends NativeCryptoException { - const KeyDerivationException({ - super.message, - super.code, - super.stackTrace, - }); -} - -class CipherInitException extends NativeCryptoException { - const CipherInitException({ - super.message, - super.code, - super.stackTrace, - }); -} - -class EncryptionException extends NativeCryptoException { - const EncryptionException({ - super.message, - super.code, - super.stackTrace, - }); -} - -class DecryptionException extends NativeCryptoException { - const DecryptionException({ - super.message, - super.code, - super.stackTrace, - }); -} - -class NotImplementedException extends NativeCryptoException { - const NotImplementedException({ - super.message, - super.code, - super.stackTrace, - }); -}