feat: export new exceptions

This commit is contained in:
Hugo Pointcheval 2022-05-25 10:51:20 +02:00
parent a7affea1e1
commit a1112b5c80
Signed by: hugo
GPG Key ID: A9E8E9615379254F
15 changed files with 313 additions and 212 deletions

28
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,28 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "native_crypto",
"cwd": "packages/native_crypto/example",
"request": "launch",
"type": "dart"
},
{
"name": "native_crypto (profile mode)",
"cwd": "packages/native_crypto/example",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "native_crypto (release mode)",
"cwd": "packages/native_crypto/example",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
]
}

View File

@ -3,7 +3,7 @@
// -----
// File: cipher_page.dart
// Created Date: 28/12/2021 13:33:15
// Last Modified: 28/12/2021 15:20:43
// Last Modified: 25/05/2022 10:49:30
// -----
// Copyright (c) 2021
@ -85,8 +85,8 @@ class CipherPage extends ConsumerWidget {
var bytesToString = plainText.toStr();
decryptionStatus
.print('String successfully decrypted:\n\n$bytesToString');
} on DecryptionException catch (e) {
decryptionStatus.print(e.message);
} on NativeCryptoException catch (e) {
decryptionStatus.print(e.message ?? 'Decryption failed!');
}
}
}

View File

@ -3,7 +3,7 @@
// -----
// File: native_crypto.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 23/05/2022 23:09:10
// Last Modified: 25/05/2022 10:48:20
// -----
// Copyright (c) 2021
@ -13,6 +13,8 @@
/// Author: Hugo Pointcheval
library native_crypto;
export 'package:native_crypto_platform_interface/src/utils/exception.dart';
export 'src/builders/builders.dart';
export 'src/ciphers/ciphers.dart';
export 'src/core/core.dart';

View File

@ -3,14 +3,14 @@
// -----
// File: aes_builder.dart
// Created Date: 28/12/2021 12:03:11
// Last Modified: 23/05/2022 23:05:19
// Last Modified: 25/05/2022 10:47:11
// -----
// Copyright (c) 2021
import 'package:native_crypto/src/ciphers/aes/aes.dart';
import 'package:native_crypto/src/core/exceptions.dart';
import 'package:native_crypto/src/interfaces/builder.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
class AESBuilder implements Builder<AES> {
SecretKey? _sk;
@ -36,7 +36,10 @@ class AESBuilder implements Builder<AES> {
Future<AES> build() async {
if (_sk == null) {
if (_fsk == null) {
throw CipherInitException('You must specify or generate a secret key.');
throw const CipherInitException(
message: 'You must specify or generate a secret key.',
code: 'missing_key',
);
} else {
_sk = await _fsk;
}

View File

@ -3,7 +3,7 @@
// -----
// File: aes.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 24/05/2022 23:29:42
// Last Modified: 25/05/2022 10:44:25
// -----
// Copyright (c) 2022
@ -14,11 +14,11 @@ import 'package:native_crypto/src/ciphers/aes/aes_mode.dart';
import 'package:native_crypto/src/ciphers/aes/aes_padding.dart';
import 'package:native_crypto/src/core/cipher_text.dart';
import 'package:native_crypto/src/core/cipher_text_list.dart';
import 'package:native_crypto/src/core/exceptions.dart';
import 'package:native_crypto/src/interfaces/cipher.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto/src/utils/cipher_algorithm.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
export 'package:native_crypto/src/ciphers/aes/aes_key_size.dart';
export 'package:native_crypto/src/ciphers/aes/aes_mode.dart';
@ -34,16 +34,21 @@ class AES implements Cipher {
AES(this.key, this.mode, {this.padding = AESPadding.none}) {
if (!AESKeySize.supportedSizes.contains(key.bytes.length * 8)) {
throw CipherInitException('Invalid key length!');
throw const CipherInitException(
message: 'Invalid key length!',
code: 'invalid_key_length',
);
}
final Map<AESMode, List<AESPadding>> _supported = {
AESMode.gcm: [AESPadding.none],
AESMode.cbc: [AESPadding.pkcs5],
};
if (!_supported[mode]!.contains(padding)) {
throw CipherInitException('Invalid padding!');
throw const CipherInitException(
message: 'Invalid padding!',
code: 'invalid_padding',
);
}
}

View File

@ -3,9 +3,9 @@
// -----
// File: aes_mode.dart
// Created Date: 23/05/2022 22:09:16
// Last Modified: 24/05/2022 23:17:01
// Last Modified: 25/05/2022 09:23:54
// -----
// Copyright (c) 2022
/// Defines the AES modes of operation.
enum AESMode { gcm, cbc }
enum AESMode { gcm }

View File

@ -3,9 +3,9 @@
// -----
// File: aes_padding.dart
// Created Date: 23/05/2022 22:10:17
// Last Modified: 24/05/2022 23:17:25
// Last Modified: 25/05/2022 09:23:49
// -----
// Copyright (c) 2022
/// Represents different paddings.
enum AESPadding { none, pkcs5 }
enum AESPadding { none }

View File

@ -3,10 +3,9 @@
// -----
// File: core.dart
// Created Date: 23/05/2022 23:05:26
// Last Modified: 23/05/2022 23:05:30
// Last Modified: 25/05/2022 10:44:32
// -----
// Copyright (c) 2022
export 'cipher_text.dart';
export 'cipher_text_list.dart';
export 'exceptions.dart';

View File

@ -1,41 +0,0 @@
// Author: Hugo Pointcheval
// Email: git@pcl.ovh
// -----
// File: exceptions.dart
// Created Date: 16/12/2021 16:28:00
// Last Modified: 23/05/2022 22:30:27
// -----
// Copyright (c) 2021
class NativeCryptoException implements Exception {
final String message;
const NativeCryptoException(this.message);
}
class UtilsException extends NativeCryptoException {
UtilsException(super.message);
}
class KeyException extends NativeCryptoException {
KeyException(super.message);
}
class KeyDerivationException extends NativeCryptoException {
KeyDerivationException(super.message);
}
class CipherInitException extends NativeCryptoException {
CipherInitException(super.message);
}
class EncryptionException extends NativeCryptoException {
EncryptionException(super.message);
}
class DecryptionException extends NativeCryptoException {
DecryptionException(super.message);
}
class NotImplementedException extends NativeCryptoException {
NotImplementedException(super.message);
}

View File

@ -3,18 +3,18 @@
// -----
// File: pbkdf2.dart
// Created Date: 17/12/2021 14:50:42
// Last Modified: 23/05/2022 23:07:19
// Last Modified: 25/05/2022 10:45:00
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:native_crypto/src/core/exceptions.dart';
import 'package:native_crypto/src/interfaces/keyderivation.dart';
import 'package:native_crypto/src/keys/secret_key.dart';
import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto/src/utils/hash_algorithm.dart';
import 'package:native_crypto/src/utils/kdf_algorithm.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
class Pbkdf2 extends KeyDerivation {
final int _keyBytesCount;
@ -35,7 +35,10 @@ class Pbkdf2 extends KeyDerivation {
@override
Future<SecretKey> derive({String? password, String? salt}) async {
if (password == null || salt == null) {
throw KeyDerivationException("Password or Salt can't be null!");
throw const KeyDerivationException(
message: "Password or Salt can't be null!",
code: 'invalid_password_or_salt',
);
}
final Uint8List derivation = (await platform.pbkdf2(

View File

@ -3,16 +3,15 @@
// -----
// File: secret_key.dart
// Created Date: 28/12/2021 13:36:54
// Last Modified: 23/05/2022 23:07:28
// Last Modified: 25/05/2022 10:45:55
// -----
// Copyright (c) 2021
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:native_crypto/src/core/exceptions.dart';
import 'package:native_crypto/src/interfaces/key.dart';
import 'package:native_crypto/src/platform.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
/// A class representing a secret key.
/// A secret key is a key that is not accessible by anyone else.
@ -29,8 +28,12 @@ class SecretKey extends Key {
(await platform.generateSecretKey(bitsCount)) ?? Uint8List(0);
return SecretKey(_key);
} on PlatformException catch (e) {
throw KeyException(e.toString());
} catch (e, s) {
throw KeyException(
message: 'Failed to generate a secret key!',
code: 'failed_to_generate_secret_key',
stackTrace: s,
);
}
}
}

View File

@ -3,7 +3,7 @@
// -----
// File: platform.dart
// Created Date: 27/12/2021 22:03:58
// Last Modified: 27/12/2021 22:04:30
// Last Modified: 25/05/2022 10:09:18
// -----
// Copyright (c) 2021

View File

@ -11,74 +11,45 @@ class AES : Cipher {
override val algorithm: CipherAlgorithm
get() = CipherAlgorithm.aes
var forEncryption: Boolean = true
var cipherInstance: javax.crypto.Cipher? = null;
var secretKey: SecretKeySpec? = null;
/* override fun encrypt(data: ByteArray, key: ByteArray): ByteArray {
val sk: SecretKey = SecretKeySpec(key, "AES")
val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, sk)
// javax.crypto representation = [CIPHERTEXT(n-16) || TAG(16)]
val bytes = cipher.doFinal(data)
val iv = cipher.iv.copyOf() // 12 bytes nonce
// native.crypto representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)]
return iv.plus(bytes)
fun lazyLoadCipher() {
if (cipherInstance == null) {
cipherInstance = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")
}
}
override fun decrypt(data: ByteArray, key: ByteArray): ByteArray {
val sk: SecretKey = SecretKeySpec(key, "AES")
// native.crypto representation = [NONCE(12) || CIPHERTEXT(n-16) || TAG(16)]
val iv: ByteArray = data.take(12).toByteArray()
// javax.crypto representation = [CIPHERTEXT(n-28) || TAG(16)]
val payload: ByteArray = data.drop(12).toByteArray()
val spec = GCMParameterSpec(16 * 8, iv)
val cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding")
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, sk, spec)
return cipher.doFinal(payload)
}*/
// native.crypto cipherText representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)]
// javax.crypto cipherText representation = [NONCE(12)] + [CIPHERTEXT(n-16) || TAG(16)]
override fun encrypt(data: ByteArray, key: ByteArray): ByteArray {
val list : List<ByteArray> = encryptAsList(data, key)
return list.first().plus(list.last())
}
// native.crypto cipherText representation = [NONCE(12)] + [CIPHERTEXT(n-16) || TAG(16)]
// javax.crypto cipherText representation = [NONCE(12)] + [CIPHERTEXT(n-16) || TAG(16)]
override fun encryptAsList(data: ByteArray, key: ByteArray): List<ByteArray> {
val sk = SecretKeySpec(key, "AES")
if (cipherInstance == null || !forEncryption || secretKey != sk) {
secretKey = sk
forEncryption = true
// native.crypto representation = [IV(16) || CIPHERTEXT(n-16)]
// javax.crypto representation = [CIPHERTEXT(n-16)]
cipherInstance = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding")
cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk)
}
// javax.crypto representation = [CIPHERTEXT(n-16)]
lazyLoadCipher()
cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk)
val bytes: ByteArray = cipherInstance!!.doFinal(data)
val iv: ByteArray = cipherInstance!!.iv
// native.crypto representation = [IV(16) || CIPHERTEXT(n-16)]
return listOf(iv, bytes)
}
override fun decrypt(data: ByteArray, key: ByteArray): ByteArray {
// javax.crypto representation = [CIPHERTEXT(n-16)]
val iv: ByteArray = data.take(16).toByteArray()
val payload: ByteArray = data.drop(16).toByteArray()
val iv: ByteArray = data.take(12).toByteArray()
val payload: ByteArray = data.drop(12).toByteArray()
return decryptAsList(listOf(iv, payload), key)
}
override fun decryptAsList(data: List<ByteArray>, key: ByteArray): ByteArray {
if (cipherInstance == null) {
// native.crypto representation = [IV(16) || CIPHERTEXT(n-16)]
// javax.crypto representation = [CIPHERTEXT(n-16)]
cipherInstance = javax.crypto.Cipher.getInstance("AES/CBC/PKCS5Padding")
}
val sk = SecretKeySpec(key, "AES")
val iv: ByteArray = data.first()
val ivSpec = IvParameterSpec(iv)
cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, ivSpec)
forEncryption = false
val payload: ByteArray = data.last()
val iv: ByteArray = data.first()
val gcmSpec = GCMParameterSpec(16 * 8, iv)
lazyLoadCipher()
cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmSpec)
return cipherInstance!!.doFinal(payload)
}
}

View File

@ -3,7 +3,7 @@
// -----
// File: native_crypto_method_channel.dart
// Created Date: 25/12/2021 16:58:04
// Last Modified: 24/05/2022 22:59:32
// Last Modified: 25/05/2022 10:40:29
// -----
// Copyright (c) 2021
@ -11,7 +11,7 @@ import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:native_crypto_platform_interface/src/platform_interface/native_crypto_platform.dart';
import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart';
/// An implementation of [NativeCryptoPlatform] that uses method channels.
class MethodChannelNativeCrypto extends NativeCryptoPlatform {
@ -20,24 +20,32 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
MethodChannel channel = const MethodChannel('plugins.hugop.cl/native_crypto');
@override
Future<Uint8List?> digest(Uint8List data, String algorithm) {
return channel.invokeMethod<Uint8List>(
'digest',
<String, dynamic>{
'data': data,
'algorithm': algorithm,
},
);
Future<Uint8List?> digest(Uint8List data, String algorithm) async {
try {
return await channel.invokeMethod<Uint8List>(
'digest',
<String, dynamic>{
'data': data,
'algorithm': algorithm,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
@override
Future<Uint8List?> generateSecretKey(int bitsCount) {
return channel.invokeMethod<Uint8List>(
'generateSecretKey',
<String, dynamic>{
'bitsCount': bitsCount,
},
);
Future<Uint8List?> generateSecretKey(int bitsCount) async {
try {
return await channel.invokeMethod<Uint8List>(
'generateSecretKey',
<String, dynamic>{
'bitsCount': bitsCount,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
@override
@ -47,17 +55,21 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
int keyBytesCount,
int iterations,
String algorithm,
) {
return channel.invokeMethod<Uint8List>(
'pbkdf2',
<String, dynamic>{
'password': password,
'salt': salt,
'keyBytesCount': keyBytesCount,
'iterations': iterations,
'algorithm': algorithm,
},
);
) async {
try {
return await channel.invokeMethod<Uint8List>(
'pbkdf2',
<String, dynamic>{
'password': password,
'salt': salt,
'keyBytesCount': keyBytesCount,
'iterations': iterations,
'algorithm': algorithm,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
@override
@ -65,15 +77,19 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
Uint8List data,
Uint8List key,
String algorithm,
) {
return channel.invokeListMethod(
'encryptAsList',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
) async {
try {
return await channel.invokeListMethod(
'encryptAsList',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
@override
@ -81,15 +97,19 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
List<Uint8List> data,
Uint8List key,
String algorithm,
) {
return channel.invokeMethod<Uint8List>(
'decryptAsList',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
) async {
try {
return await channel.invokeMethod<Uint8List>(
'decryptAsList',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
@override
@ -97,15 +117,19 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
Uint8List data,
Uint8List key,
String algorithm,
) {
return channel.invokeMethod<Uint8List>(
'encrypt',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
) async {
try {
return await channel.invokeMethod<Uint8List>(
'encrypt',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
@override
@ -113,14 +137,18 @@ class MethodChannelNativeCrypto extends NativeCryptoPlatform {
Uint8List data,
Uint8List key,
String algorithm,
) {
return channel.invokeMethod<Uint8List>(
'decrypt',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
) async {
try {
return await channel.invokeMethod<Uint8List>(
'decrypt',
<String, dynamic>{
'data': data,
'key': key,
'algorithm': algorithm,
},
);
} catch (e, s) {
NativeCryptoException.convertPlatformException(e, s);
}
}
}

View File

@ -3,51 +3,151 @@
// -----
// File: exception.dart
// Created Date: 24/05/2022 18:54:48
// Last Modified: 24/05/2022 18:58:39
// Last Modified: 25/05/2022 10:43:29
// -----
// Copyright (c) 2022
import 'dart:developer';
import 'package:flutter/services.dart';
class NativeCryptoException implements Exception {
final String message;
const NativeCryptoException(this.message);
}
const NativeCryptoException({
this.message,
String? code,
this.stackTrace,
// ignore: unnecessary_this
}) : this.code = code ?? 'unknown';
/// Catches a [PlatformException] and returns an [Exception].
///
/// If the [Exception] is a [PlatformException],
/// a [NativeCryptoException] is returned.
Never convertPlatformException(Object exception, StackTrace stackTrace) {
if (exception is! Exception || exception is! PlatformException) {
Error.throwWithStackTrace(exception, stackTrace);
/// The long form message of the exception.
final String? message;
/// The optional code to accommodate the message.
final String code;
/// The stack trace which provides information to the user about the call
/// sequence that triggered an exception
final StackTrace? stackTrace;
@override
String toString() {
String output = '[NativeException/$code] $message';
if (stackTrace != null) {
output += '\n\n${stackTrace.toString()}';
}
return output;
}
Error.throwWithStackTrace(
platformExceptionToNativeCryptoException(exception, stackTrace),
stackTrace,
);
}
/// Catches a [PlatformException] and returns an [Exception].
///
/// If the [Exception] is a [PlatformException],
/// a [NativeCryptoException] is returned.
static Never convertPlatformException(
Object exception,
StackTrace stackTrace,
) {
log(exception.toString());
if (exception is! Exception || exception is! PlatformException) {
Error.throwWithStackTrace(exception, stackTrace);
}
/// Converts a [PlatformException] into a [NativeCryptoException].
///
/// A [PlatformException] can only be converted to a [NativeCryptoException]
/// if the `details` of the exception exist.
NativeCryptoException platformExceptionToNativeCryptoException(
PlatformException platformException,
StackTrace stackTrace,
) {
final Map<String, String>? details = platformException.details != null
? Map<String, String>.from(
platformException.details as Map<String, String>,
)
: null;
String message = platformException.message ?? '';
if (details != null) {
message = details['message'] ?? message;
Error.throwWithStackTrace(
NativeCryptoException.fromPlatformException(exception, stackTrace),
stackTrace,
);
}
return NativeCryptoException(message);
/// Converts a [PlatformException] into a [NativeCryptoException].
///
/// A [PlatformException] can only be converted to a [NativeCryptoException]
/// if the `details` of the exception exist.
factory NativeCryptoException.fromPlatformException(
PlatformException platformException,
StackTrace stackTrace,
) {
final Map<String, String>? details = platformException.details != null
? Map<String, String>.from(
platformException.details as Map<String, String>,
)
: null;
String code = 'unknown';
String message = platformException.message ?? '';
if (details != null) {
code = details['code'] ?? code;
message = details['message'] ?? message;
}
return NativeCryptoException(
message: message,
code: code,
stackTrace: stackTrace,
);
}
@override
// ignore: avoid_equals_and_hash_code_on_mutable_classes
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is NativeCryptoException &&
other.message == message &&
other.code == code &&
other.stackTrace == stackTrace;
}
@override
// 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,
});
}