diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e1b09e8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,19 @@ +{ + "bloc.newCubitTemplate.type": "equatable", + "psi-header.config": { + "blankLinesAfter": 0, + "forceToTop": true, + }, + "psi-header.templates": [ + { + "language": "*", + "template": [ + "Copyright 2019-<> <>", + "", + "Use of this source code is governed by an MIT-style", + "license that can be found in the LICENSE file or at", + "https://opensource.org/licenses/MIT.", + ] + } + ], +} \ No newline at end of file diff --git a/README.md b/README.md index 507b7ec..c01bf49 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

Fast and powerful cryptographic functions for Flutter.

@@ -35,34 +35,127 @@ For comparison, on a *iPhone 13*, you can encrypt/decrypt a message of **2MiB** In short, NativeCrypto is incomparable with PointyCastle. +## Features + +* Hash functions + - SHA-256 + - SHA-384 + - SHA-512 +* HMAC functions + - HMAC-SHA-256 + - HMAC-SHA-384 + - HMAC-SHA-512 +* Secure random +* PBKDF2 +* AES + - Uint8List encryption/decryption + - File encryption/decryption + +## Quick start + +```dart +import 'package:native_crypto/native_crypto.dart'; + +Future main() async { + // Message to encrypt + final Uint8List message = 'Hello World!'.toBytes(); + + // Ask user for a password + final String password = await getPassword(); + + // Initialize a PBKDF2 object + final Pbkdf2 pbkdf2 = Pbkdf2( + length: 32, // 32 bytes + iterations: 1000, + salt: 'salt'.toBytes(), + hashAlgorithm: HashAlgorithm.sha256, + ); + + // Derive a secret key from the password + final SecretKey secretKey = await pbkdf2(password: password); + + // Initialize an AES cipher + final AES cipher = AES( + key: secretKey, + mode: AESMode.gcm, + padding: AESPadding.none, + ); + + // Encrypt the message + final CipherText cipherText = await cipher.encrypt(message); + + // Decrypt the message + final Uint8List decryptedMessage = await cipher.decrypt(cipherText); + + // Verify and print the decrypted message + assert(listEquals(message, decryptedMessage)); + + print(decryptedMessage.toStr()); +} +``` + +Check the [example](./native_crypto/example) for a complete example. + +Please take a look a the compatibility table below to check if your target is supported. + +> Note: This **Flutter** example must run on a real device or a simulator. + ## Usage +#### Compatibility + First, check compatibility with your targets. | iOS | Android | MacOS | Linux | Windows | Web | | --- | ------- | ----- | ----- | ------- | --- | | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +> Warning: NativeCrypto 0.2.0+ is not compatible with lower NativeCrypto versions. Especially, with NativeCrypto 0.0. X because the cipher mode is not the same. Now, NativeCrypto uses AES-GCM mode instead of AES-CBC mode. (See [Changelog](./CHANGELOG.md)) + +NativeCrypto ciphertexts are formatted as follow: + +``` ++------------------+--------------------+------------------+ +| Nonce (12 bytes) | Cipher text (n-28) | Tag (16 bytes) | ++------------------+--------------------+------------------+ +``` + +> Warning: If your data comes from another source, make sur to use the same format. + #### Hash -To digest a message, you can use the following function: +To digest a message, you'll need to initialize a Hasher object implementing `Hash` . Then, you can digest your message. ```dart -Uint8List hash = await HashAlgorithm.sha256.digest(message); +Hash hasher = Sha256(); +Uint8List digest = await hasher.digest(message); ``` > In NativeCrypto, you can use the following hash functions: SHA-256, SHA-384, SHA-512 -#### Keys +#### HMAC -You can build a `SecretKey` from a utf8, base64, base16 (hex) strings or raw bytes. You can also generate a SecretKey from secure random. +To generate a HMAC, you'll need to initialize a `Hmac` object. Then, you can generate a HMAC from a message and a secret key. ```dart -SecretKey secretKey = SecretKey(Uint8List.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74])); +Hmac hmac = HmacSha256(); +Uint8List hmac = await hmac.digest(message, secretKey); +``` + +> In NativeCrypto, you can use the following HMAC functions: HMAC-SHA-256, HMAC-SHA-384, HMAC-SHA-512 + +#### Keys + +You can build a `SecretKey` from utf8, utf16, base64, base16 (hex) strings, int list or raw bytes. You can also generate a SecretKey from secure random. + +```dart +SecretKey secretKey = SecretKey(bytes); // bytes is a Uint8List SecretKey secretKey = SecretKey.fromUtf8('secret'); +SecretKet secretKey = SecretKey.fromUtf16('secret'); SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0'); SecretKey secretKey = SecretKey.fromBase16('63657274'); -SecretKey secretKey = await SecretKey.fromSecureRandom(256); +SecretKey secretKey = SecretKey.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74]); +SecretKey secretKey = await SecretKey.fromSecureRandom(32); // 32 bytes ``` #### Key derivation @@ -72,20 +165,21 @@ You can derive a `SecretKey` using **PBKDF2**. First, you need to initialize a `Pbkdf2` object. ```dart -Pbkdf2 pbkdf2 = Pbkdf2( - keyBytesCount: 32, +final Pbkdf2 pbkdf2 = Pbkdf2( + length: 32, // 32 bytes iterations: 1000, - algorithm: HashAlgorithm.sha512, + salt: salt.toBytes(), + hashAlgorithm: HashAlgorithm.sha256, ); ``` -Then, you can derive a `SecretKey` from a password and salt. +Then, you can derive a `SecretKey` from a password. ```dart -SecretKey secretKey = await pbkdf2.derive(password: password, salt: 'salt'); +SecretKey secretKey = await pbkdf2(password: password); ``` -> In NativeCrypto, you can use the following key derivation function: PBKDF2 +> Note: Pbkdf2 is a callable class. You can use it like a function. #### Cipher @@ -94,40 +188,93 @@ And now, you can use the `SecretKey` to encrypt/decrypt a message. First, you need to initialize a `Cipher` object. ```dart -AES cipher = AES(secretKey); +final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, +); ``` Then, you can encrypt your message. ```dart -CipherTextWrapper wrapper = await cipher.encrypt(message); - -CipherText cipherText = wrapper.unwrap(); -// same as -CipherText cipherText = wrapper.single; - -// or - -List cipherTexts = wrapper.unwrap>(); -// same as -List cipherTexts = wrapper.list; +final CipherText cipherText = await cipher.encrypt(message); ``` -After an encryption you obtain a `CipherTextWrapper` which contains `CipherText` or `List` depending on the message size. It's up to you to know how to unwrap the `CipherTextWrapper` depending the chunk size you configured. +After an encryption you obtain a `CipherText` which contains chunks. You can get the underlying bytes with `cipherText.bytes` . -Uppon receiving encrypted message, you can decrypt it. -You have to reconstruct the wrapper before decrypting. +Uppon receiving encrypted message `receivedData` , you can decrypt it. +You have to reconstruct the ciphertext and the setup the chunk factory. ```dart -CipherTextWrapper wrapper = CipherTextWrapper.fromBytes( - data, - ivLength: AESMode.gcm.ivLength, - tagLength: AESMode.gcm.tagLength, -); +final CipherText receivedCipherText CipherText( + receivedData, + chunkFactory: (bytes) => AESCipherChunk( + bytes, + ivLength: cipher.mode.ivLength, + tagLength: cipher.mode.tagLength, + ), +), ``` Then, you can decrypt your message. ```dart -Uint8List message = await cipher.decrypt(wrapper); -``` \ No newline at end of file +Uint8List message = await cipher.decrypt(receivedCipherText); +``` + +#### Files + +You can encrypt/decrypt files. + +First, you need to initialize a `Cipher` object. + +```dart +final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, +); +``` + +Then, you can encrypt your file. + +```dart +await cipher.encryptFile(plainText, cipherText); +``` + +> Note: `plainText` and `cipherText` are `File` objects. + +You can decrypt your file. + +```dart +await cipher.decryptFile(cipherText, plainText); +``` + +#### Advanced + +You can force the use of a specific IV. Please note that the IV must be unique for each encryption. + +```dart +final CipherText cipherText = await cipher.encryptWithIV(message, iv); +``` + +⚠️ Use `encrypt(...)` instead of `encryptWithIV(...)` if you don't know what you are doing. + +## Development + +### Android + +> https://docs.flutter.dev/development/packages-and-plugins/developing-packages#step-2b-add-android-platform-code-ktjava + +* Launch Android Studio. +* Select Open an existing Android Studio Project in the Welcome to Android Studio dialog, or select File > Open from the menu, and select the `packages/native_crypto/example/android/build.gradle` file. +* In the Gradle Sync dialog, select OK. +* In the Android Gradle Plugin Update dialog, select Don’t remind me again for this project. + +### iOS + +> https://docs.flutter.dev/development/packages-and-plugins/developing-packages#step-2c-add-ios-platform-code-swifthm + +* Launch Xcode. +* Select File > Open, and select the `packages/native_crypto/example/ios/Runner.xcworkspace` file. diff --git a/packages/native_crypto/LICENSE b/packages/native_crypto/LICENSE index 082d930..67b1d53 100644 --- a/packages/native_crypto/LICENSE +++ b/packages/native_crypto/LICENSE @@ -2,7 +2,7 @@ NativeCrypto MIT License -Copyright (c) 2019 - 2022 Hugo Pointcheval +Copyright (c) 2019 - 2023 Hugo Pointcheval Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/packages/native_crypto/README.md b/packages/native_crypto/README.md index 507b7ec..f412888 100644 --- a/packages/native_crypto/README.md +++ b/packages/native_crypto/README.md @@ -1,133 +1,3 @@ -

- -

Fast and powerful cryptographic functions for Flutter.
-

+# NativeCrypto -

- -Style: Wyatt Analysis - - - -Maintained with Melos - - - -Build Status - -

- ---- - -[[Changelog]](./CHANGELOG.md) | [[License]](./LICENSE) - ---- - -## About - -The goal of this plugin is to provide a fast and powerful cryptographic functions by calling native libraries. On Android, it uses [javax.cypto](https://developer.android.com/reference/javax/crypto/package-summary), and on iOS, it uses [CommonCrypto](https://opensource.apple.com/source/CommonCrypto/) and [CryptoKit](https://developer.apple.com/documentation/cryptokit/) - -I started this projet because I wanted to add cryptographic functions on a Flutter app. But I faced a problem with the well-known [Pointy Castle](https://pub.dev/packages/pointycastle) library: the performance was very poor. Here some benchmarks and comparison: - -![](resources/benchmarks.png) - -For comparison, on a *iPhone 13*, you can encrypt/decrypt a message of **2MiB** in **~5.6s** with PointyCastle and in **~40ms** with NativeCrypto. And on an *OnePlus 5*, you can encrypt/decrypt a message of **50MiB** in **~6min30** with PointyCastle and in less than **~1s** with NativeCrypto. - -In short, NativeCrypto is incomparable with PointyCastle. - -## Usage - -First, check compatibility with your targets. - -| iOS | Android | MacOS | Linux | Windows | Web | -| --- | ------- | ----- | ----- | ------- | --- | -| ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | - -#### Hash - -To digest a message, you can use the following function: - -```dart -Uint8List hash = await HashAlgorithm.sha256.digest(message); -``` - -> In NativeCrypto, you can use the following hash functions: SHA-256, SHA-384, SHA-512 - -#### Keys - -You can build a `SecretKey` from a utf8, base64, base16 (hex) strings or raw bytes. You can also generate a SecretKey from secure random. - -```dart -SecretKey secretKey = SecretKey(Uint8List.fromList([0x73, 0x65, 0x63, 0x72, 0x65, 0x74])); -SecretKey secretKey = SecretKey.fromUtf8('secret'); -SecretKey secretKey = SecretKey.fromBase64('c2VjcmV0'); -SecretKey secretKey = SecretKey.fromBase16('63657274'); -SecretKey secretKey = await SecretKey.fromSecureRandom(256); -``` - -#### Key derivation - -You can derive a `SecretKey` using **PBKDF2**. - -First, you need to initialize a `Pbkdf2` object. - -```dart -Pbkdf2 pbkdf2 = Pbkdf2( - keyBytesCount: 32, - iterations: 1000, - algorithm: HashAlgorithm.sha512, -); -``` - -Then, you can derive a `SecretKey` from a password and salt. - -```dart -SecretKey secretKey = await pbkdf2.derive(password: password, salt: 'salt'); -``` - -> In NativeCrypto, you can use the following key derivation function: PBKDF2 - -#### Cipher - -And now, you can use the `SecretKey` to encrypt/decrypt a message. - -First, you need to initialize a `Cipher` object. - -```dart -AES cipher = AES(secretKey); -``` - -Then, you can encrypt your message. - -```dart -CipherTextWrapper wrapper = await cipher.encrypt(message); - -CipherText cipherText = wrapper.unwrap(); -// same as -CipherText cipherText = wrapper.single; - -// or - -List cipherTexts = wrapper.unwrap>(); -// same as -List cipherTexts = wrapper.list; -``` - -After an encryption you obtain a `CipherTextWrapper` which contains `CipherText` or `List` depending on the message size. It's up to you to know how to unwrap the `CipherTextWrapper` depending the chunk size you configured. - -Uppon receiving encrypted message, you can decrypt it. -You have to reconstruct the wrapper before decrypting. - -```dart -CipherTextWrapper wrapper = CipherTextWrapper.fromBytes( - data, - ivLength: AESMode.gcm.ivLength, - tagLength: AESMode.gcm.tagLength, -); -``` - -Then, you can decrypt your message. - -```dart -Uint8List message = await cipher.decrypt(wrapper); -``` \ No newline at end of file +Readme available at [project root](../../README.md). diff --git a/packages/native_crypto/analysis_options.yaml b/packages/native_crypto/analysis_options.yaml index db48808..82177cd 100644 --- a/packages/native_crypto/analysis_options.yaml +++ b/packages/native_crypto/analysis_options.yaml @@ -1 +1 @@ -include: package:wyatt_analysis/analysis_options.flutter.experimental.yaml \ No newline at end of file +include: package:wyatt_analysis/analysis_options.flutter.yaml \ No newline at end of file diff --git a/packages/native_crypto/example/.metadata b/packages/native_crypto/example/.metadata index ee7f61d..92fd7de 100644 --- a/packages/native_crypto/example/.metadata +++ b/packages/native_crypto/example/.metadata @@ -1,10 +1,30 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: cf4400006550b70f28e4b4af815151d1e74846c6 + revision: cd41fdd495f6944ecd3506c21e94c6567b073278 channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 + base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 + - platform: web + create_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 + base_revision: cd41fdd495f6944ecd3506c21e94c6567b073278 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/packages/native_crypto/example/analysis_options.yaml b/packages/native_crypto/example/analysis_options.yaml index 61b6c4d..82177cd 100644 --- a/packages/native_crypto/example/analysis_options.yaml +++ b/packages/native_crypto/example/analysis_options.yaml @@ -1,29 +1 @@ -# This file configures the analyzer, which statically analyzes Dart code to -# check for errors, warnings, and lints. -# -# The issues identified by the analyzer are surfaced in the UI of Dart-enabled -# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. - -# The following line activates a set of recommended lints for Flutter apps, -# packages, and plugins designed to encourage good coding practices. -include: package:flutter_lints/flutter.yaml - -linter: - # The lint rules applied to this project can be customized in the - # section below to disable rules from the `package:flutter_lints/flutter.yaml` - # included above or to enable additional rules. A list of all available lints - # and their documentation is published at - # https://dart-lang.github.io/linter/lints/index.html. - # - # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code - # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file - # producing the lint. - rules: - # avoid_print: false # Uncomment to disable the `avoid_print` rule - # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule - -# Additional information about this file can be found at -# https://dart.dev/guides/language/analysis-options +include: package:wyatt_analysis/analysis_options.flutter.yaml \ No newline at end of file diff --git a/packages/native_crypto/example/android/app/build.gradle b/packages/native_crypto/example/android/app/build.gradle index 15632c2..237aa05 100644 --- a/packages/native_crypto/example/android/app/build.gradle +++ b/packages/native_crypto/example/android/app/build.gradle @@ -57,6 +57,7 @@ android { signingConfig signingConfigs.debug } } + namespace 'fr.pointcheval.native_crypto_example' } flutter { diff --git a/packages/native_crypto/example/android/app/src/debug/AndroidManifest.xml b/packages/native_crypto/example/android/app/src/debug/AndroidManifest.xml index 94e68f6..f880684 100644 --- a/packages/native_crypto/example/android/app/src/debug/AndroidManifest.xml +++ b/packages/native_crypto/example/android/app/src/debug/AndroidManifest.xml @@ -1,5 +1,4 @@ - + diff --git a/packages/native_crypto/example/android/app/src/main/AndroidManifest.xml b/packages/native_crypto/example/android/app/src/main/AndroidManifest.xml index c55f004..7f62b9c 100644 --- a/packages/native_crypto/example/android/app/src/main/AndroidManifest.xml +++ b/packages/native_crypto/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,4 @@ - + + diff --git a/packages/native_crypto/example/android/build.gradle b/packages/native_crypto/example/android/build.gradle index 3245887..4bcbcdd 100644 --- a/packages/native_crypto/example/android/build.gradle +++ b/packages/native_crypto/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.2.0' + classpath 'com.android.tools.build:gradle:7.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/packages/native_crypto/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/native_crypto/example/android/gradle/wrapper/gradle-wrapper.properties index 562c5e4..cc5527d 100644 --- a/packages/native_crypto/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/native_crypto/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/packages/native_crypto/example/ios/Flutter/AppFrameworkInfo.plist b/packages/native_crypto/example/ios/Flutter/AppFrameworkInfo.plist index 8d4492f..9625e10 100644 --- a/packages/native_crypto/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/native_crypto/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/packages/native_crypto/example/ios/Podfile.lock b/packages/native_crypto/example/ios/Podfile.lock index 3257d42..cad8cae 100644 --- a/packages/native_crypto/example/ios/Podfile.lock +++ b/packages/native_crypto/example/ios/Podfile.lock @@ -1,21 +1,74 @@ PODS: + - DKImagePickerController/Core (4.3.4): + - DKImagePickerController/ImageDataManager + - DKImagePickerController/Resource + - DKImagePickerController/ImageDataManager (4.3.4) + - DKImagePickerController/PhotoGallery (4.3.4): + - DKImagePickerController/Core + - DKPhotoGallery + - DKImagePickerController/Resource (4.3.4) + - DKPhotoGallery (0.0.17): + - DKPhotoGallery/Core (= 0.0.17) + - DKPhotoGallery/Model (= 0.0.17) + - DKPhotoGallery/Preview (= 0.0.17) + - DKPhotoGallery/Resource (= 0.0.17) + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Core (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Preview + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Model (0.0.17): + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Preview (0.0.17): + - DKPhotoGallery/Model + - DKPhotoGallery/Resource + - SDWebImage + - SwiftyGif + - DKPhotoGallery/Resource (0.0.17): + - SDWebImage + - SwiftyGif + - file_picker (0.0.1): + - DKImagePickerController/PhotoGallery + - Flutter - Flutter (1.0.0) - native_crypto_ios (0.0.1): - Flutter + - SDWebImage (5.15.5): + - SDWebImage/Core (= 5.15.5) + - SDWebImage/Core (5.15.5) + - SwiftyGif (5.4.4) DEPENDENCIES: + - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - native_crypto_ios (from `.symlinks/plugins/native_crypto_ios/ios`) +SPEC REPOS: + trunk: + - DKImagePickerController + - DKPhotoGallery + - SDWebImage + - SwiftyGif + EXTERNAL SOURCES: + file_picker: + :path: ".symlinks/plugins/file_picker/ios" Flutter: :path: Flutter native_crypto_ios: :path: ".symlinks/plugins/native_crypto_ios/ios" SPEC CHECKSUMS: - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac + DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 + file_picker: ce3938a0df3cc1ef404671531facef740d03f920 + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 native_crypto_ios: de03ec2f594e8d41bcba2341b7ad57fd926ada5d + SDWebImage: fd7e1a22f00303e058058278639bf6196ee431fe + SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f PODFILE CHECKSUM: cc1f88378b4bfcf93a6ce00d2c587857c6008d3b diff --git a/packages/native_crypto/example/ios/Runner.xcodeproj/project.pbxproj b/packages/native_crypto/example/ios/Runner.xcodeproj/project.pbxproj index 8c503eb..47f5593 100644 --- a/packages/native_crypto/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/native_crypto/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -200,6 +200,7 @@ /* Begin PBXShellScriptBuildPhase section */ 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -214,6 +215,7 @@ }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -340,7 +342,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -418,7 +420,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -467,7 +469,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/packages/native_crypto/example/ios/Runner/Info.plist b/packages/native_crypto/example/ios/Runner/Info.plist index 8293c48..c8a011c 100644 --- a/packages/native_crypto/example/ios/Runner/Info.plist +++ b/packages/native_crypto/example/ios/Runner/Info.plist @@ -45,5 +45,11 @@ CADisableMinimumFrameDurationOnPhone + UIApplicationSupportsIndirectInputEvents + + UISupportsDocumentBrowser + + LSSupportsOpeningDocumentsInPlace + diff --git a/packages/native_crypto/example/lib/core/get_it.dart b/packages/native_crypto/example/lib/core/get_it.dart new file mode 100644 index 0000000..1de732b --- /dev/null +++ b/packages/native_crypto/example/lib/core/get_it.dart @@ -0,0 +1,35 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:get_it/get_it.dart'; +import 'package:native_crypto_example/data/data_sources/logger_data_source_impl.dart'; +import 'package:native_crypto_example/data/data_sources/native_crypto_data_source_impl.dart'; +import 'package:native_crypto_example/data/data_sources/pointy_castle_data_source_impl.dart'; +import 'package:native_crypto_example/data/data_sources/session_data_source_impl.dart'; +import 'package:native_crypto_example/domain/data_sources/logger_data_source.dart'; +import 'package:native_crypto_example/domain/data_sources/session_data_source.dart'; + +final getIt = GetIt.I; + +abstract class GetItInitializer { + static Future init() async { + getIt + ..registerLazySingleton( + SessionDataSourceImpl.new, + ) + ..registerLazySingleton( + LoggerDataSourceImpl.new, + ) + ..registerLazySingleton( + NativeCryptoDataSourceImpl.new, + ) + ..registerLazySingleton( + PointyCastleDataSourceImpl.new, + ); + + await getIt.allReady(); + } +} diff --git a/packages/native_crypto/example/lib/core/typography.dart b/packages/native_crypto/example/lib/core/typography.dart new file mode 100644 index 0000000..3e55d46 --- /dev/null +++ b/packages/native_crypto/example/lib/core/typography.dart @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; + +abstract class AppTypography { + static const title = TextStyle( + color: Colors.black, + fontSize: 24, + ); + + static const body = TextStyle( + fontSize: 16, + ); +} diff --git a/packages/native_crypto/example/lib/data/data_sources/logger_data_source_impl.dart b/packages/native_crypto/example/lib/data/data_sources/logger_data_source_impl.dart new file mode 100644 index 0000000..022d9e6 --- /dev/null +++ b/packages/native_crypto/example/lib/data/data_sources/logger_data_source_impl.dart @@ -0,0 +1,34 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:native_crypto_example/domain/data_sources/logger_data_source.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; + +class LoggerDataSourceImpl extends LoggerDataSource { + final Map _logs = {}; + final StreamController> _streamController = + StreamController.broadcast(); + + @override + Future addLog(LogMessage message) async { + _logs[DateTime.now()] = message; + _streamController.add(Map.from(_logs)); + } + + @override + Future clearLog() async { + _logs.clear(); + _streamController.add(Map.from(_logs)); + } + + @override + Future> getLogs() async => _logs; + + @override + Stream> streamLogs() => _streamController.stream; +} diff --git a/packages/native_crypto/example/lib/data/data_sources/native_crypto_data_source_impl.dart b/packages/native_crypto/example/lib/data/data_sources/native_crypto_data_source_impl.dart new file mode 100644 index 0000000..dd61735 --- /dev/null +++ b/packages/native_crypto/example/lib/data/data_sources/native_crypto_data_source_impl.dart @@ -0,0 +1,150 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart'; + +class NativeCryptoDataSourceImpl extends CryptoDataSource { + @override + Future decrypt(Uint8List data, SecretKey key) async { + final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, + ); + final Uint8List plainText = await cipher.decrypt( + CipherText( + data, + chunkFactory: (bytes) => AESCipherChunk( + bytes, + ivLength: cipher.mode.ivLength, + tagLength: cipher.mode.tagLength, + ), + ), + ); + + return plainText; + } + + @override + Future decryptFile( + File cipherText, + Uri folderResult, + SecretKey key, + ) async { + final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, + ); + + final plainText = File.fromUri( + Uri.parse( + '${folderResult.path}/${cipherText.path.split('/').last.replaceAll('.enc', '')}', + ), + ); + await cipher.decryptFile( + cipherText, + plainText, + ); + } + + @override + Future deriveKeyFromPassword( + String password, { + required String salt, + }) async { + final Pbkdf2 pbkdf2 = Pbkdf2( + length: 32, + iterations: 1000, + salt: salt.toBytes(), + hashAlgorithm: HashAlgorithm.sha256, + ); + return pbkdf2(password: password); + } + + @override + Future hash(Hash hasher, Uint8List data) async { + final Uint8List digestMessage = await hasher.digest(data); + + return digestMessage; + } + + @override + Future hmac(Hmac hmac, Uint8List data, SecretKey key) async { + final Uint8List digestMessage = await hmac.digest(data, key); + + return digestMessage; + } + + @override + Future encrypt(Uint8List data, SecretKey key) async { + final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, + ); + final CipherText cipherText = await cipher.encrypt(data); + + return cipherText.bytes; + } + + @override + Future encryptWithIV( + Uint8List data, + SecretKey key, + Uint8List iv, + ) async { + final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, + ); + + final AESCipherChunk chunk = await cipher.encryptWithIV(data, iv); + final CipherText cipherText = CipherText.fromChunks( + [chunk], + chunkFactory: (bytes) => AESCipherChunk( + bytes, + ivLength: cipher.mode.ivLength, + tagLength: cipher.mode.tagLength, + ), + ); + + return cipherText.bytes; + } + + @override + Future encryptFile( + File plainText, + Uri folderResult, + SecretKey key, + ) async { + final AES cipher = AES( + key: key, + mode: AESMode.gcm, + padding: AESPadding.none, + ); + + final cipherText = File.fromUri( + Uri.parse( + '${folderResult.path}/${plainText.path.split('/').last}.enc', + ), + ); + + await cipher.encryptFile(plainText, cipherText); + } + + @override + Future generateSecureRandom(int length) async { + final SecretKey sk = await SecretKey.fromSecureRandom(length); + + return sk; + } +} diff --git a/packages/native_crypto/example/lib/data/data_sources/pointy_castle_data_source_impl.dart b/packages/native_crypto/example/lib/data/data_sources/pointy_castle_data_source_impl.dart new file mode 100644 index 0000000..e14d14f --- /dev/null +++ b/packages/native_crypto/example/lib/data/data_sources/pointy_castle_data_source_impl.dart @@ -0,0 +1,183 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +// ignore_for_file: implementation_imports + +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart'; +import 'package:pointycastle/export.dart'; +import 'package:pointycastle/src/platform_check/platform_check.dart'; + +class PointyCastleDataSourceImpl extends CryptoDataSource { + FortunaRandom? _secureRandom; + + @override + Future decrypt(Uint8List data, SecretKey key) async { + final iv = Uint8List.fromList(data.sublist(0, 12)); + final cipherTextWithoutIv = Uint8List.fromList( + data.sublist(12), + ); + + final gcm = GCMBlockCipher(AESEngine()) + ..init( + false, + AEADParameters( + KeyParameter(key.bytes), + 16 * 8, + iv, + Uint8List(0), + ), + ); + final paddedPlainText = gcm.process(cipherTextWithoutIv); + + return paddedPlainText; + } + + @override + Future decryptFile( + File cipherText, + Uri folderResult, + SecretKey key, + ) async { + throw UnimplementedError(); + } + + @override + Future deriveKeyFromPassword( + String password, { + required String salt, + }) async { + final derivator = PBKDF2KeyDerivator(HMac(SHA256Digest(), 64)) + ..init( + Pbkdf2Parameters(salt.toBytes(), 1000, 32), + ); + + final Uint8List sk = derivator.process(password.toBytes()); + + return SecretKey(sk); + } + + @override + Future hash(Hash hasher, Uint8List data) async { + final Digest? digest; + + switch (hasher.runtimeType) { + case Sha256: + digest = SHA256Digest(); + break; + case Sha384: + digest = SHA384Digest(); + break; + case Sha512: + digest = SHA512Digest(); + break; + default: + throw UnsupportedError( + 'Unsupported hash algorithm: ${hasher.runtimeType}', + ); + } + + return digest.process(data); + } + + @override + Future hmac(Hmac hmac, Uint8List data, SecretKey key) async { + final HMac? digest; + + switch (hmac.runtimeType) { + case HmacSha256: + digest = HMac.withDigest(SHA256Digest()); + digest.init(KeyParameter(key.bytes)); + break; + case Sha384: + digest = HMac.withDigest(SHA384Digest()); + digest.init(KeyParameter(key.bytes)); + break; + case Sha512: + digest = HMac.withDigest(SHA512Digest()); + digest.init(KeyParameter(key.bytes)); + break; + default: + throw UnsupportedError( + 'Unsupported hmac algorithm: ${hmac.runtimeType}', + ); + } + + return digest.process(data); + } + + @override + Future encrypt(Uint8List data, SecretKey key) async { + final iv = (await generateSecureRandom(12 * 8)).bytes; + + final gcm = GCMBlockCipher(AESEngine()) + ..init( + true, + AEADParameters( + KeyParameter(key.bytes), + 16 * 8, + iv, + Uint8List(0), + ), + ); + + final cipherText = gcm.process(data); + + return Uint8List.fromList( + iv + cipherText, + ); + } + + @override + Future encryptWithIV( + Uint8List data, + SecretKey key, + Uint8List iv, + ) async { + final gcm = GCMBlockCipher(AESEngine()) + ..init( + true, + AEADParameters( + KeyParameter(key.bytes), + 16 * 8, + iv, + Uint8List(0), + ), + ); + + final cipherText = gcm.process(data); + + return Uint8List.fromList( + iv + cipherText, + ); + } + + @override + Future encryptFile( + File plainText, + Uri folderResult, + SecretKey key, + ) async { + throw UnimplementedError(); + } + + @override + Future generateSecureRandom(int length) async { + if (_secureRandom == null) { + _secureRandom = FortunaRandom(); + _secureRandom!.seed( + KeyParameter(Platform.instance.platformEntropySource().getBytes(32)), + ); + } + + final sk = _secureRandom!.nextBytes(length); + + return SecretKey(sk); + } +} diff --git a/packages/native_crypto/example/lib/data/data_sources/session_data_source_impl.dart b/packages/native_crypto/example/lib/data/data_sources/session_data_source_impl.dart new file mode 100644 index 0000000..d7ee01e --- /dev/null +++ b/packages/native_crypto/example/lib/data/data_sources/session_data_source_impl.dart @@ -0,0 +1,45 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/data_sources/session_data_source.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; + +class SessionDataSourceImpl extends SessionDataSource { + SecretKey? _sk; + Mode? _mode = const NativeCryptoMode(); + + @override + Future getSessionKey() async { + if (_sk == null) { + throw Exception('Session key is not ready'); + } + + return _sk!; + } + + @override + Future isSessionKeyReady() async => _sk != null; + + @override + Future setSessionKey(SecretKey key) async { + _sk = key; + } + + @override + Future getCurrentMode() async { + if (_mode == null) { + throw Exception('Mode is not set'); + } + + return _mode!; + } + + @override + Future setCurrentMode(Mode mode) async { + _mode = mode; + } +} diff --git a/packages/native_crypto/example/lib/data/repositories/crypto_repository_impl.dart b/packages/native_crypto/example/lib/data/repositories/crypto_repository_impl.dart new file mode 100644 index 0000000..2781fb3 --- /dev/null +++ b/packages/native_crypto/example/lib/data/repositories/crypto_repository_impl.dart @@ -0,0 +1,147 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class CryptoRepositoryImpl extends CryptoRepository { + CryptoRepositoryImpl({ + required this.cryptoDataSource, + }); + CryptoDataSource cryptoDataSource; + + @override + FutureOrResult decrypt(Uint8List data, SecretKey key) => + Result.tryCatchAsync( + () async => cryptoDataSource.decrypt(data, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult decryptFile( + File cipherText, + Uri folderResult, + SecretKey key, + ) => + Result.tryCatchAsync( + () async => cryptoDataSource.decryptFile(cipherText, folderResult, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult deriveKeyFromPassword( + String password, { + required String salt, + }) => + Result.tryCatchAsync( + () async => cryptoDataSource.deriveKeyFromPassword( + password, + salt: salt, + ), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult hash(Hash hasher, Uint8List data) => + Result.tryCatchAsync( + () async => cryptoDataSource.hash(hasher, data), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult hmac(Hmac hmac, Uint8List data, SecretKey key) => + Result.tryCatchAsync( + () async => cryptoDataSource.hmac(hmac, data, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult encrypt(Uint8List data, SecretKey key) => + Result.tryCatchAsync( + () async => cryptoDataSource.encrypt(data, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult encryptWithIV( + Uint8List data, + SecretKey key, + Uint8List iv, + ) => + Result.tryCatchAsync( + () async => cryptoDataSource.encryptWithIV(data, key, iv), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult encryptFile( + File plainText, + Uri folderResult, + SecretKey key, + ) => + Result.tryCatchAsync( + () async => cryptoDataSource.encryptFile(plainText, folderResult, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult generateSecureRandom(int length) => + Result.tryCatchAsync( + () async => cryptoDataSource.generateSecureRandom(length), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); +} diff --git a/packages/native_crypto/example/lib/data/repositories/crypto_repository_switchable_impl.dart b/packages/native_crypto/example/lib/data/repositories/crypto_repository_switchable_impl.dart new file mode 100644 index 0000000..972ac7b --- /dev/null +++ b/packages/native_crypto/example/lib/data/repositories/crypto_repository_switchable_impl.dart @@ -0,0 +1,167 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/data_sources/crypto_data_source.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class CryptoRepositorySwitchableImpl extends CryptoRepository { + CryptoRepositorySwitchableImpl({ + required this.nativeCryptoDataSource, + required this.pointyCastleDataSource, + required this.currentMode, + }); + + CryptoDataSource nativeCryptoDataSource; + CryptoDataSource pointyCastleDataSource; + Mode currentMode; + + set mode(Mode mode) { + currentMode = mode; + } + + CryptoDataSource get cryptoDataSource { + if (currentMode is NativeCryptoMode) { + return nativeCryptoDataSource; + } else if (currentMode is PointyCastleMode) { + return pointyCastleDataSource; + } else { + throw Exception('Unknown mode'); + } + } + + @override + FutureOrResult decrypt(Uint8List data, SecretKey key) => + Result.tryCatchAsync( + () async => cryptoDataSource.decrypt(data, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult decryptFile( + File cipherText, + Uri folderResult, + SecretKey key, + ) => + Result.tryCatchAsync( + () async => cryptoDataSource.decryptFile(cipherText, folderResult, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult deriveKeyFromPassword( + String password, { + required String salt, + }) => + Result.tryCatchAsync( + () async => cryptoDataSource.deriveKeyFromPassword( + password, + salt: salt, + ), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult hash(Hash hasher, Uint8List data) => + Result.tryCatchAsync( + () async => cryptoDataSource.hash(hasher, data), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult hmac(Hmac hmac, Uint8List data, SecretKey key) => + Result.tryCatchAsync( + () async => cryptoDataSource.hmac(hmac, data, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult encrypt(Uint8List data, SecretKey key) => + Result.tryCatchAsync( + () async => cryptoDataSource.encrypt(data, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult encryptWithIV( + Uint8List data, + SecretKey key, + Uint8List iv, + ) => + Result.tryCatchAsync( + () async => cryptoDataSource.encryptWithIV(data, key, iv), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult encryptFile( + File plainText, + Uri folderResult, + SecretKey key, + ) => + Result.tryCatchAsync( + () async => cryptoDataSource.encryptFile(plainText, folderResult, key), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); + + @override + FutureOrResult generateSecureRandom(int length) => + Result.tryCatchAsync( + () async => cryptoDataSource.generateSecureRandom(length), + (error) { + if (error is NativeCryptoException) { + return ClientException('${error.message}'); + } + return ClientException(error.toString()); + }, + ); +} diff --git a/packages/native_crypto/example/lib/data/repositories/logger_repository_impl.dart b/packages/native_crypto/example/lib/data/repositories/logger_repository_impl.dart new file mode 100644 index 0000000..54ba9be --- /dev/null +++ b/packages/native_crypto/example/lib/data/repositories/logger_repository_impl.dart @@ -0,0 +1,41 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto_example/domain/data_sources/logger_data_source.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class LoggerRepositoryImpl extends LoggerRepository { + LoggerRepositoryImpl({ + required this.loggerDataSource, + }); + + final LoggerDataSource loggerDataSource; + + @override + FutureOrResult addLog(LogMessage message) => Result.tryCatchAsync( + () async => loggerDataSource.addLog(message), + (error) => ClientException(error.toString()), + ); + + @override + FutureOrResult clearLog() => Result.tryCatchAsync( + () async => loggerDataSource.clearLog(), + (error) => ClientException(error.toString()), + ); + + @override + FutureOrResult> getLogs() => Result.tryCatchAsync( + () async => loggerDataSource.getLogs(), + (error) => ClientException(error.toString()), + ); + + @override + StreamResult> streamLogs() => + loggerDataSource.streamLogs().map(Ok.new); +} diff --git a/packages/native_crypto/example/lib/data/repositories/session_repository_impl.dart b/packages/native_crypto/example/lib/data/repositories/session_repository_impl.dart new file mode 100644 index 0000000..5d2d592 --- /dev/null +++ b/packages/native_crypto/example/lib/data/repositories/session_repository_impl.dart @@ -0,0 +1,49 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/data_sources/session_data_source.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +class SessionRepositoryImpl extends SessionRepository { + SessionRepositoryImpl({ + required this.sessionDataSource, + }); + SessionDataSource sessionDataSource; + + @override + FutureOrResult getSessionKey() => Result.tryCatchAsync( + () async => sessionDataSource.getSessionKey(), + (error) => ClientException(error.toString()), + ); + + @override + FutureOrResult isSessionKeyReady() => Result.tryCatchAsync( + () async => sessionDataSource.isSessionKeyReady(), + (error) => ClientException(error.toString()), + ); + + @override + FutureOrResult setSessionKey(SecretKey key) => Result.tryCatchAsync( + () async => sessionDataSource.setSessionKey(key), + (error) => ClientException(error.toString()), + ); + + @override + FutureOrResult getCurrentMode() => Result.tryCatchAsync( + () async => sessionDataSource.getCurrentMode(), + (error) => ClientException(error.toString()), + ); + + @override + FutureOrResult setCurrentMode(Mode mode) => Result.tryCatchAsync( + () async => sessionDataSource.setCurrentMode(mode), + (error) => ClientException(error.toString()), + ); +} diff --git a/packages/native_crypto/example/lib/domain/data_sources/crypto_data_source.dart b/packages/native_crypto/example/lib/domain/data_sources/crypto_data_source.dart new file mode 100644 index 0000000..df7f304 --- /dev/null +++ b/packages/native_crypto/example/lib/domain/data_sources/crypto_data_source.dart @@ -0,0 +1,38 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class CryptoDataSource extends BaseDataSource { + Future generateSecureRandom(int length); + Future deriveKeyFromPassword( + String password, { + required String salt, + }); + Future encrypt(Uint8List data, SecretKey key); + Future encryptFile( + File plainText, + Uri folderResult, + SecretKey key, + ); + Future encryptWithIV( + Uint8List data, + SecretKey key, + Uint8List iv, + ); + Future decrypt(Uint8List data, SecretKey key); + Future decryptFile( + File cipherText, + Uri folderResult, + SecretKey key, + ); + Future hash(Hash hasher, Uint8List data); + Future hmac(Hmac hmac, Uint8List data, SecretKey key); +} diff --git a/packages/native_crypto/example/lib/domain/data_sources/logger_data_source.dart b/packages/native_crypto/example/lib/domain/data_sources/logger_data_source.dart new file mode 100644 index 0000000..fbff97a --- /dev/null +++ b/packages/native_crypto/example/lib/domain/data_sources/logger_data_source.dart @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class LoggerDataSource extends BaseDataSource { + Future addLog(LogMessage message); + Future clearLog(); + Future> getLogs(); + Stream> streamLogs(); +} diff --git a/packages/native_crypto/example/lib/domain/data_sources/session_data_source.dart b/packages/native_crypto/example/lib/domain/data_sources/session_data_source.dart new file mode 100644 index 0000000..8a31feb --- /dev/null +++ b/packages/native_crypto/example/lib/domain/data_sources/session_data_source.dart @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class SessionDataSource extends BaseDataSource { + Future setSessionKey(SecretKey key); + Future isSessionKeyReady(); + Future getSessionKey(); + Future getCurrentMode(); + Future setCurrentMode(Mode mode); +} diff --git a/packages/native_crypto/example/lib/domain/entities/log_message.dart b/packages/native_crypto/example/lib/domain/entities/log_message.dart new file mode 100644 index 0000000..05edf62 --- /dev/null +++ b/packages/native_crypto/example/lib/domain/entities/log_message.dart @@ -0,0 +1,27 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; + +class LogMessage { + const LogMessage(this.prefix, this.color, this.message); + + final String prefix; + final Color color; + final String message; +} + +class LogInfo extends LogMessage { + const LogInfo(String message) : super('info', Colors.black, message); +} + +class LogWarning extends LogMessage { + const LogWarning(String message) : super('warn', Colors.orange, message); +} + +class LogError extends LogMessage { + const LogError(String message) : super('fail', Colors.red, message); +} diff --git a/packages/native_crypto/example/lib/domain/entities/mode.dart b/packages/native_crypto/example/lib/domain/entities/mode.dart new file mode 100644 index 0000000..d35e901 --- /dev/null +++ b/packages/native_crypto/example/lib/domain/entities/mode.dart @@ -0,0 +1,34 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +class Mode extends Entity { + const Mode( + this.primaryColor, + this.secondaryColor, + ); + + final Color primaryColor; + final Color secondaryColor; +} + +class NativeCryptoMode extends Mode { + const NativeCryptoMode() + : super( + Colors.blue, + Colors.black, + ); +} + +class PointyCastleMode extends Mode { + const PointyCastleMode() + : super( + Colors.red, + Colors.white, + ); +} diff --git a/packages/native_crypto/example/lib/domain/entities/states.dart b/packages/native_crypto/example/lib/domain/entities/states.dart new file mode 100644 index 0000000..0e1482c --- /dev/null +++ b/packages/native_crypto/example/lib/domain/entities/states.dart @@ -0,0 +1,7 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +enum State { initial, loading, success, failure } diff --git a/packages/native_crypto/example/lib/domain/entities/test_vector.dart b/packages/native_crypto/example/lib/domain/entities/test_vector.dart new file mode 100644 index 0000000..5682213 --- /dev/null +++ b/packages/native_crypto/example/lib/domain/entities/test_vector.dart @@ -0,0 +1,54 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/foundation.dart'; +import 'package:native_crypto/native_crypto.dart'; + +class TestVector { + TestVector({ + required this.key, + required this.nonce, + required this.plainText, + required this.cipherText, + required this.tag, + }); + + final String key; + final String nonce; + final String plainText; + final String cipherText; + final String tag; + + Uint8List get keyBytes => key.toBytes(from: Encoding.base16); + Uint8List get nonceBytes => nonce.toBytes(from: Encoding.base16); + Uint8List get tagBytes => tag.toBytes(from: Encoding.base16); + Uint8List get plainTextBytes => plainText.toBytes(from: Encoding.base16); + Uint8List get cipherTextBytes { + final iv = nonceBytes; + final data = cipherText.toBytes(from: Encoding.base16); + final tag = tagBytes; + return Uint8List.fromList(iv + data + tag); + } + + bool validateCipherText(Uint8List? testCipherText) { + final result = listEquals(testCipherText, cipherTextBytes); + if (!result) { + debugPrint('Cipher texts differs:\n$cipherTextBytes\n$testCipherText'); + } + return result; + } + + bool validatePlainText(Uint8List? testPlainText) { + final result = listEquals(testPlainText, plainTextBytes); + if (!result) { + debugPrint('Plain texts differs:\n$plainTextBytes\n$testPlainText'); + } + return result; + } + + bool validate(Uint8List? testPlainText, Uint8List? testCipherText) => + validateCipherText(testCipherText) && validatePlainText(testPlainText); +} diff --git a/packages/native_crypto/example/lib/domain/repositories/crypto_repository.dart b/packages/native_crypto/example/lib/domain/repositories/crypto_repository.dart new file mode 100644 index 0000000..6045be6 --- /dev/null +++ b/packages/native_crypto/example/lib/domain/repositories/crypto_repository.dart @@ -0,0 +1,39 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:io'; + +import 'package:flutter/foundation.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class CryptoRepository extends BaseRepository { + FutureOrResult generateSecureRandom(int length); + FutureOrResult deriveKeyFromPassword( + String password, { + required String salt, + }); + FutureOrResult encrypt(Uint8List data, SecretKey key); + FutureOrResult encryptFile( + File plainText, + Uri folderResult, + SecretKey key, + ); + FutureOrResult encryptWithIV( + Uint8List data, + SecretKey key, + Uint8List iv, + ); + FutureOrResult decrypt(Uint8List data, SecretKey key); + FutureOrResult decryptFile( + File cipherText, + Uri folderResult, + SecretKey key, + ); + + FutureOrResult hash(Hash hasher, Uint8List data); + FutureOrResult hmac(Hmac hmac, Uint8List data, SecretKey key); +} diff --git a/packages/native_crypto/example/lib/domain/repositories/logger_repository.dart b/packages/native_crypto/example/lib/domain/repositories/logger_repository.dart new file mode 100644 index 0000000..ed29af4 --- /dev/null +++ b/packages/native_crypto/example/lib/domain/repositories/logger_repository.dart @@ -0,0 +1,18 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: logger_repository.dart +// Created Date: 09/01/2023 22:12:56 +// Last Modified: 09/01/2023 23:03:51 +// ----- +// Copyright (c) 2023 + +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class LoggerRepository extends BaseRepository { + FutureOrResult addLog(LogMessage message); + FutureOrResult clearLog(); + FutureOrResult> getLogs(); + StreamResult> streamLogs(); +} diff --git a/packages/native_crypto/example/lib/domain/repositories/session_repository.dart b/packages/native_crypto/example/lib/domain/repositories/session_repository.dart new file mode 100644 index 0000000..2ecce6f --- /dev/null +++ b/packages/native_crypto/example/lib/domain/repositories/session_repository.dart @@ -0,0 +1,20 @@ +// Author: Hugo Pointcheval +// Email: git@pcl.ovh +// ----- +// File: session_repository.dart +// Created Date: 08/01/2023 17:35:46 +// Last Modified: 09/01/2023 22:13:21 +// ----- +// Copyright (c) 2023 + +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; + +abstract class SessionRepository extends BaseRepository { + FutureOrResult setSessionKey(SecretKey key); + FutureOrResult isSessionKeyReady(); + FutureOrResult getSessionKey(); + FutureOrResult getCurrentMode(); + FutureOrResult setCurrentMode(Mode mode); +} diff --git a/packages/native_crypto/example/lib/home.dart b/packages/native_crypto/example/lib/home.dart deleted file mode 100644 index 8357b7e..0000000 --- a/packages/native_crypto/example/lib/home.dart +++ /dev/null @@ -1,64 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: home.dart -// Created Date: 28/12/2021 13:48:36 -// Last Modified: 28/12/2021 15:18:03 -// ----- -// Copyright (c) 2021 - -import 'package:flutter/material.dart'; -import 'package:native_crypto_example/pages/benchmark_page.dart'; - -import 'pages/cipher_page.dart'; -import 'pages/kdf_page.dart'; - -class Home extends StatefulWidget { - const Home({Key? key}) : super(key: key); - - @override - _HomeState createState() => _HomeState(); -} - -class _HomeState extends State { - int _currentIndex = 0; - final List _children = [KdfPage(), CipherPage(), BenchmarkPage()]; - - void onTabTapped(int index) { - setState(() { - _currentIndex = index; - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - centerTitle: true, - title: const Text('Native Crypto'), - ), - body: _children[_currentIndex], - bottomNavigationBar: BottomNavigationBar( - selectedItemColor: Colors.blue, - unselectedItemColor: Colors.black, - showUnselectedLabels: true, - onTap: onTabTapped, // new - currentIndex: _currentIndex, // new - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.vpn_key), - label: 'Key', - ), - BottomNavigationBarItem( - icon: Icon(Icons.lock), - label: 'Encryption', - ), - BottomNavigationBarItem( - icon: Icon(Icons.timer), - label: 'Benchmark', - ), - ], - ), - ); - } -} diff --git a/packages/native_crypto/example/lib/main.dart b/packages/native_crypto/example/lib/main.dart index 65b60e0..07da36e 100644 --- a/packages/native_crypto/example/lib/main.dart +++ b/packages/native_crypto/example/lib/main.dart @@ -3,23 +3,16 @@ // ----- // File: main.dart // Created Date: 27/12/2021 21:15:12 -// Last Modified: 28/12/2021 13:51:36 +// Last Modified: 10/01/2023 14:59:05 // ----- // Copyright (c) 2021 import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:native_crypto_example/home.dart'; +import 'package:native_crypto_example/core/get_it.dart'; +import 'package:native_crypto_example/presentation/app/app.dart'; -void main() { - runApp(const ProviderScope(child: MyApp())); -} - -class MyApp extends StatelessWidget { - const MyApp({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return const MaterialApp(home: Home()); - } +Future main() async { + await GetItInitializer.init(); + + runApp(App()); } diff --git a/packages/native_crypto/example/lib/pages/benchmark_page.dart b/packages/native_crypto/example/lib/pages/benchmark_page.dart deleted file mode 100644 index 6396152..0000000 --- a/packages/native_crypto/example/lib/pages/benchmark_page.dart +++ /dev/null @@ -1,189 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: benchmark_page.dart -// Created Date: 28/12/2021 15:12:39 -// Last Modified: 26/05/2022 20:19:28 -// ----- -// Copyright (c) 2021 - -import 'dart:math'; -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:native_crypto/native_crypto.dart'; -import 'package:native_crypto_example/pointycastle/aes_gcm.dart'; -import 'package:native_crypto_example/widgets/button.dart'; - -import '../session.dart'; -import '../widgets/output.dart'; - -class BenchmarkPage extends ConsumerWidget { - BenchmarkPage({Key? key}) : super(key: key); - - final Output keyContent = Output(); - final Output benchmarkStatus = Output(large: true); - - Future _benchmark( - WidgetRef ref, - Cipher cipher, { - bool usePc = false, - bool encryptionOnly = false, - }) async { - Session state = ref.read(sessionProvider.state).state; - AesGcm pc = AesGcm(); - - if (state.secretKey.bytes.isEmpty) { - benchmarkStatus - .print('No SecretKey!\nGo in Key tab and generate or derive one.'); - return; - } - - List testedSizes = [2, 4, 8, 16, 32, 64, 128, 256]; - int multiplier = pow(2, 20).toInt(); // MiB - - benchmarkStatus.print("[Benchmark] Sizes: ${testedSizes.join('/')}MiB\n"); - benchmarkStatus.appendln( - "[Benchmark] Engine: " + (usePc ? " PointyCastle" : " NativeCrypto")); - benchmarkStatus.appendln("[Benchmark] Test: " + - (encryptionOnly ? " Encryption Only" : " Encryption & Decryption")); - benchmarkStatus.appendln( - '[Benchmark] bytesCountPerChunk: ${Cipher.bytesCountPerChunk} bytes/chunk'); - - String csv = encryptionOnly - ? "Run;Size (B);Encryption Time (ms)\n" - : "Run;Size (B);Encryption Time (ms);Decryption Time (ms)\n"; - - int run = 0; - var beforeBench = DateTime.now(); - - for (int size in testedSizes) { - run++; - final StringBuffer csvLine = StringBuffer(); - final dummyBytes = Uint8List(size * multiplier); - csvLine.write('$run;${size * multiplier};'); - - // Encryption - Object encryptedBigFile; - var before = DateTime.now(); - if (usePc) { - encryptedBigFile = pc.encrypt(dummyBytes, state.secretKey.bytes); - } else { - encryptedBigFile = await cipher.encrypt(dummyBytes); - } - var after = DateTime.now(); - - var benchmark = - after.millisecondsSinceEpoch - before.millisecondsSinceEpoch; - benchmarkStatus - .appendln('[Benchmark] ${size}MiB => Encryption took $benchmark ms'); - csvLine.write('$benchmark'); - - if (!encryptionOnly) { - // Decryption - before = DateTime.now(); - if (usePc) { - pc.decrypt(encryptedBigFile as Uint8List, state.secretKey.bytes); - } else { - await cipher.decrypt(encryptedBigFile as CipherTextWrapper); - } - after = DateTime.now(); - benchmark = - after.millisecondsSinceEpoch - before.millisecondsSinceEpoch; - benchmarkStatus.appendln( - '[Benchmark] ${size}MiB => Decryption took $benchmark ms'); - csvLine.write(';$benchmark'); - } - csv += csvLine.toString() + '\n'; - } - var afterBench = DateTime.now(); - var benchmark = - afterBench.millisecondsSinceEpoch - beforeBench.millisecondsSinceEpoch; - var sum = testedSizes.reduce((a, b) => a + b); - benchmarkStatus - .appendln('[Benchmark] Finished: ${sum}MiB in $benchmark ms'); - benchmarkStatus.appendln('[Benchmark] Check the console for csv data'); - benchmarkStatus.appendln(csv); - debugPrint(csv); - } - - void _clear() { - benchmarkStatus.clear(); - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - Session state = ref.read(sessionProvider.state).state; - if (state.secretKey.bytes.isEmpty) { - keyContent - .print('No SecretKey!\nGo in Key tab and generate or derive one.'); - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const Align( - child: Text("Secret Key"), - alignment: Alignment.centerLeft, - ), - keyContent, - ], - ), - ), - ); - } - keyContent.print(state.secretKey.bytes.toString()); - - AES cipher = AES(state.secretKey); - - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const Align( - child: Text("Secret Key"), - alignment: Alignment.centerLeft, - ), - keyContent, - Wrap( - children: [ - Button( - () => _benchmark(ref, cipher), - "NativeCrypto", - ), - const SizedBox(width: 8), - Button( - () => _benchmark(ref, cipher, usePc: true), - "PointyCastle", - ), - const SizedBox(width: 8), - Button( - () => _benchmark(ref, cipher, encryptionOnly: true), - "NC Encryption Only", - ), - const SizedBox(width: 8), - Button( - () => _benchmark( - ref, - cipher, - usePc: true, - encryptionOnly: true, - ), - "PC Encryption Only", - ), - const SizedBox(width: 8), - Button( - _clear, - "Clear", - ), - ], - ), - benchmarkStatus, - ], - ), - ), - ); - } -} diff --git a/packages/native_crypto/example/lib/pages/cipher_page.dart b/packages/native_crypto/example/lib/pages/cipher_page.dart deleted file mode 100644 index b0843c7..0000000 --- a/packages/native_crypto/example/lib/pages/cipher_page.dart +++ /dev/null @@ -1,157 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher_page.dart -// Created Date: 28/12/2021 13:33:15 -// Last Modified: 27/05/2022 16:42:10 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:native_crypto/native_crypto.dart'; -import 'package:native_crypto/native_crypto_ext.dart'; -import 'package:native_crypto_example/widgets/button.dart'; - -import '../session.dart'; -import '../widgets/output.dart'; - -// ignore: must_be_immutable -class CipherPage extends ConsumerWidget { - CipherPage({Key? key}) : super(key: key); - - final Output keyContent = Output(); - final Output encryptionStatus = Output(); - final Output decryptionStatus = Output(); - - final TextEditingController _plainTextController = TextEditingController() - ..text = 'PlainText'; - CipherTextWrapper? cipherText; - - Future _encrypt(WidgetRef ref, Cipher cipher) async { - Session state = ref.read(sessionProvider.state).state; - final plainText = _plainTextController.text.trim(); - - if (state.secretKey.bytes.isEmpty) { - encryptionStatus - .print('No SecretKey!\nGo in Key tab and generate or derive one.'); - } else if (plainText.isEmpty) { - encryptionStatus.print('Entry is empty'); - } else { - var stringToBytes = plainText.toBytes(); - cipherText = await cipher.encrypt(stringToBytes); - encryptionStatus.print('String successfully encrypted:\n'); - - CipherText unwrap = cipherText!.unwrap(); - encryptionStatus.append(unwrap.base16); - } - } - - Future _alter() async { - if (cipherText == null) { - decryptionStatus.print('Encrypt before altering CipherText!'); - } else { - // Add 1 to the first byte - Uint8List _altered = cipherText!.unwrap().bytes; - _altered[0] += 1; - // Recreate cipher text with altered data - cipherText = CipherTextWrapper.fromBytes( - _altered, - ivLength: AESMode.gcm.ivLength, - tagLength: AESMode.gcm.tagLength, - ); - encryptionStatus.print('String successfully encrypted:\n'); - - CipherText unwrap = cipherText!.unwrap(); - encryptionStatus.appendln(unwrap.base16); - decryptionStatus.print('CipherText altered!\nDecryption will fail.'); - } - } - - void _decrypt(WidgetRef ref, Cipher cipher) async { - Session state = ref.read(sessionProvider.state).state; - - if (state.secretKey.bytes.isEmpty) { - decryptionStatus - .print('No SecretKey!\nGo in Key tab and generate or derive one.'); - } else if (cipherText == null) { - decryptionStatus.print('Encrypt before decrypting!'); - } else { - try { - Uint8List plainText = await cipher.decrypt(cipherText!); - var bytesToString = plainText.toStr(); - decryptionStatus - .print('String successfully decrypted:\n\n$bytesToString'); - } on NativeCryptoException catch (e) { - decryptionStatus.print(e.message ?? 'Decryption failed!'); - } - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - Session state = ref.read(sessionProvider.state).state; - if (state.secretKey.bytes.isEmpty) { - keyContent - .print('No SecretKey!\nGo in Key tab and generate or derive one.'); - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const Align( - child: Text("Secret Key"), - alignment: Alignment.centerLeft, - ), - keyContent, - ], - ), - ), - ); - } - keyContent.print(state.secretKey.bytes.toString()); - - AES cipher = AES(state.secretKey); - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const Align( - child: Text("Secret Key"), - alignment: Alignment.centerLeft, - ), - keyContent, - TextField( - controller: _plainTextController, - decoration: const InputDecoration( - hintText: 'Plain text', - ), - ), - Button( - () => _encrypt(ref, cipher), - "Encrypt", - ), - encryptionStatus, - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Button( - _alter, - "Alter cipher", - ), - Button( - () => _decrypt(ref, cipher), - "Decrypt", - ), - ], - ), - decryptionStatus, - ], - ), - ), - ); - } -} diff --git a/packages/native_crypto/example/lib/pages/kdf_page.dart b/packages/native_crypto/example/lib/pages/kdf_page.dart deleted file mode 100644 index 371d883..0000000 --- a/packages/native_crypto/example/lib/pages/kdf_page.dart +++ /dev/null @@ -1,136 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: kdf_page.dart -// Created Date: 28/12/2021 13:40:34 -// Last Modified: 26/05/2022 21:09:47 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:native_crypto/native_crypto.dart'; -import 'package:native_crypto/native_crypto_ext.dart'; -import 'package:native_crypto_example/widgets/button.dart'; - -import '../session.dart'; -import '../widgets/output.dart'; - -class KdfPage extends ConsumerWidget { - KdfPage({Key? key}) : super(key: key); - - final Output keyContent = Output(); - final Output keyStatus = Output(); - final Output pbkdf2Status = Output(); - final Output hashStatus = Output(large: true); - - final TextEditingController _pwdTextController = TextEditingController() - ..text = 'Password'; - final TextEditingController _messageTextController = TextEditingController() - ..text = 'Message'; - - Future _generate(WidgetRef ref) async { - Session state = ref.read(sessionProvider.state).state; - try { - SecretKey sk = await SecretKey.fromSecureRandom(256); - state.setKey(sk); - keyStatus.print( - "SecretKey successfully generated.\nLength: ${state.secretKey.bytes.length} bytes"); - keyContent.print(state.secretKey.bytes.toString()); - debugPrint("As hex :\n${sk.base16}"); - } catch (e) { - keyStatus.print(e.toString()); - } - } - - Future _pbkdf2(WidgetRef ref) async { - Session state = ref.read(sessionProvider.state).state; - final password = _pwdTextController.text.trim(); - - if (password.isEmpty) { - pbkdf2Status.print('Password is empty'); - } else { - Pbkdf2 _pbkdf2 = Pbkdf2( - keyBytesCount: 32, - iterations: 1000, - algorithm: HashAlgorithm.sha512, - ); - SecretKey sk = await _pbkdf2.derive(password: password, salt: 'salt'); - state.setKey(sk); - pbkdf2Status.print('Key successfully derived.'); - keyContent.print(state.secretKey.bytes.toString()); - debugPrint("As hex :\n${sk.base16}"); - } - } - - Future _hash(HashAlgorithm hasher) async { - final message = _messageTextController.text.trim(); - if (message.isEmpty) { - hashStatus.print('Message is empty'); - } else { - Uint8List hash = await hasher.digest(message.toBytes()); - hashStatus.print( - 'Message successfully hashed with $hasher :${hash.toStr(to: Encoding.base16)}'); - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Column( - children: [ - const Align( - child: Text("SecretKey"), - alignment: Alignment.centerLeft, - ), - keyContent, - Button( - () => _generate(ref), - "Generate SecretKey", - ), - keyStatus, - TextField( - controller: _pwdTextController, - decoration: const InputDecoration( - hintText: 'Password', - ), - ), - Button( - () => _pbkdf2(ref), - "Apply PBKDF2", - ), - pbkdf2Status, - TextField( - controller: _messageTextController, - decoration: const InputDecoration( - hintText: 'Message', - ), - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Button( - () => _hash(HashAlgorithm.sha256), - "SHA256", - ), - Button( - () => _hash(HashAlgorithm.sha384), - "SHA384", - ), - Button( - () => _hash(HashAlgorithm.sha512), - "SHA512", - ), - ], - ), - hashStatus, - ], - ), - ), - ); - } -} diff --git a/packages/native_crypto/example/lib/pointycastle/aes_gcm.dart b/packages/native_crypto/example/lib/pointycastle/aes_gcm.dart index 967c384..67d0483 100644 --- a/packages/native_crypto/example/lib/pointycastle/aes_gcm.dart +++ b/packages/native_crypto/example/lib/pointycastle/aes_gcm.dart @@ -1,11 +1,8 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes_gcm.dart -// Created Date: 24/05/2022 16:34:54 -// Last Modified: 27/05/2022 17:36:31 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. // ignore_for_file: implementation_imports @@ -69,7 +66,8 @@ class AesGcm { _secureRandom = FortunaRandom(); _secureRandom!.seed( - KeyParameter(Platform.instance.platformEntropySource().getBytes(32))); + KeyParameter(Platform.instance.platformEntropySource().getBytes(32)), + ); } // Use it to generate the random bytes diff --git a/packages/native_crypto/example/lib/presentation/app/app.dart b/packages/native_crypto/example/lib/presentation/app/app.dart new file mode 100644 index 0000000..f9253a4 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/app/app.dart @@ -0,0 +1,65 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto_example/core/get_it.dart'; +import 'package:native_crypto_example/data/data_sources/native_crypto_data_source_impl.dart'; +import 'package:native_crypto_example/data/data_sources/pointy_castle_data_source_impl.dart'; +import 'package:native_crypto_example/data/repositories/crypto_repository_switchable_impl.dart'; +import 'package:native_crypto_example/data/repositories/logger_repository_impl.dart'; +import 'package:native_crypto_example/data/repositories/session_repository_impl.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; +import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart'; +import 'package:native_crypto_example/presentation/home/state_management/home_state_management.dart'; +import 'package:native_crypto_example/presentation/output/blocs/output/output_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class App extends StatelessWidget { + App({super.key}); + + final LoggerRepository _loggerRepository = + LoggerRepositoryImpl(loggerDataSource: getIt()); + + final SessionRepository _sessionRepository = + SessionRepositoryImpl(sessionDataSource: getIt()); + + final CryptoRepository _cryptoRepository = CryptoRepositorySwitchableImpl( + nativeCryptoDataSource: getIt(), + pointyCastleDataSource: getIt(), + currentMode: const NativeCryptoMode(), + ); + + @override + Widget build(BuildContext context) => MultiProvider( + repositoryProviders: [ + RepositoryProvider.value(value: _loggerRepository), + RepositoryProvider.value( + value: _sessionRepository, + ), + RepositoryProvider.value(value: _cryptoRepository), + ], + blocProviders: [ + BlocProvider( + create: (_) => OutputCubit(_loggerRepository), + ), + BlocProvider( + create: (_) => ModeSwitcherCubit( + _sessionRepository, + _cryptoRepository, + ), + ) + ], + child: MaterialApp( + title: 'NativeCrypto', + debugShowCheckedModeBanner: false, + home: HomeStateManagement(), + ), + ); +} diff --git a/packages/native_crypto/example/lib/presentation/benchmark/blocs/benchmark_cubit.dart b/packages/native_crypto/example/lib/presentation/benchmark/blocs/benchmark_cubit.dart new file mode 100644 index 0000000..3c766fe --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/benchmark/blocs/benchmark_cubit.dart @@ -0,0 +1,132 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/entities/states.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; + +part 'benchmark_state.dart'; + +class BenchmarkCubit extends Cubit { + BenchmarkCubit({ + required this.sessionRepository, + required this.loggerRepository, + required this.cryptoRepository, + }) : super(const BenchmarkState.initial()); + final SessionRepository sessionRepository; + final LoggerRepository loggerRepository; + final CryptoRepository cryptoRepository; + + List testedSizes = [ + 2097152, + 6291456, + 10485760, + 14680064, + 18874368, + 23068672, + 27262976, + 31457280, + 35651584, + 39845888, + 44040192, + 48234496, + 52428800, + ]; + + FutureOr launchBenchmark() async { + emit(const BenchmarkState.loading()); + + final sk = await sessionRepository.getSessionKey(); + + if (sk.isErr) { + await loggerRepository.addLog( + const LogError('No SecretKey!\n' + 'Go in Key tab and generate or derive one.'), + ); + emit( + BenchmarkState.failure( + sk.err?.message, + ), + ); + } + + int run = 0; + final csv = StringBuffer( + 'Run;Size (B);Encryption Time (ms);Decryption Time (ms)\n', + ); + for (final size in testedSizes) { + run++; + final StringBuffer csvLine = StringBuffer(); + final dummyBytes = Uint8List(size); + csvLine.write('$run;$size;'); + + // Encryption + final beforeEncryption = DateTime.now(); + + final encryptedBigFileResult = await cryptoRepository.encrypt( + dummyBytes, + sk.ok!, + ); + + final afterEncryption = DateTime.now(); + + final benchmarkEncryption = afterEncryption.millisecondsSinceEpoch - + beforeEncryption.millisecondsSinceEpoch; + + await loggerRepository.addLog( + LogInfo( + '[Benchmark] ${size ~/ 1000000}MB => Encryption took $benchmarkEncryption ms', + ), + ); + + csvLine.write('$benchmarkEncryption'); + + if (encryptedBigFileResult.isErr) { + await loggerRepository.addLog( + LogError( + 'Encryption failed: ${encryptedBigFileResult.err?.message}', + ), + ); + emit( + BenchmarkState.failure( + encryptedBigFileResult.err?.message, + ), + ); + return; + } + + // Decryption + final beforeDecryption = DateTime.now(); + await cryptoRepository.decrypt( + encryptedBigFileResult.ok!, + sk.ok!, + ); + final afterDecryption = DateTime.now(); + final benchmarkDecryption = afterDecryption.millisecondsSinceEpoch - + beforeDecryption.millisecondsSinceEpoch; + await loggerRepository.addLog( + LogInfo( + '[Benchmark] ${size ~/ 1000000}MB => Decryption took $benchmarkDecryption ms', + ), + ); + + csvLine.write(';$benchmarkDecryption'); + csv.writeln(csvLine); + } + debugPrint(csv.toString()); + emit( + const BenchmarkState.success(), + ); + + return; + } +} diff --git a/packages/native_crypto/example/lib/presentation/benchmark/blocs/benchmark_state.dart b/packages/native_crypto/example/lib/presentation/benchmark/blocs/benchmark_state.dart new file mode 100644 index 0000000..8cf7978 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/benchmark/blocs/benchmark_state.dart @@ -0,0 +1,27 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'benchmark_cubit.dart'; + +@immutable +class BenchmarkState { + const BenchmarkState.initial() + : state = State.initial, + error = null; + + const BenchmarkState.loading() + : state = State.loading, + error = null; + + const BenchmarkState.failure(this.error) : state = State.failure; + + const BenchmarkState.success() + : state = State.success, + error = null; + + final State state; + final String? error; +} diff --git a/packages/native_crypto/example/lib/presentation/benchmark/state_management/benchmark_state_management.dart b/packages/native_crypto/example/lib/presentation/benchmark/state_management/benchmark_state_management.dart new file mode 100644 index 0000000..dc2627e --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/benchmark/state_management/benchmark_state_management.dart @@ -0,0 +1,55 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; +import 'package:native_crypto_example/presentation/benchmark/blocs/benchmark_cubit.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart'; +import 'package:native_crypto_example/presentation/output/widgets/logs.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class BenchmarkStateManagement + extends CubitScreen { + const BenchmarkStateManagement({super.key}); + + @override + BenchmarkCubit create(BuildContext context) => BenchmarkCubit( + sessionRepository: repo(context), + loggerRepository: repo(context), + cryptoRepository: repo(context), + ); + + @override + Widget onBuild(BuildContext context, BenchmarkState state) => ListView( + children: [ + const Logs(), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Benchmark', + style: AppTypography.title, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + '''In computer science, a benchmark is a standardized way to measure the performance of a software program or hardware device. A benchmark is typically a set of tests or tasks designed to measure how quickly a program can complete a given set of operations or how efficiently a hardware device can perform a specific task.''', + style: AppTypography.body, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Launch', + onPressed: () => bloc(context).launchBenchmark(), + ), + ), + ], + ); +} diff --git a/packages/native_crypto/example/lib/presentation/cipher/blocs/aes/aes_cubit.dart b/packages/native_crypto/example/lib/presentation/cipher/blocs/aes/aes_cubit.dart new file mode 100644 index 0000000..13b3435 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/cipher/blocs/aes/aes_cubit.dart @@ -0,0 +1,417 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/entities/states.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; + +part 'aes_state.dart'; + +class AESCubit extends Cubit { + AESCubit({ + required this.sessionRepository, + required this.loggerRepository, + required this.cryptoRepository, + }) : super(const AESState.initial()); + final SessionRepository sessionRepository; + final LoggerRepository loggerRepository; + final CryptoRepository cryptoRepository; + + FutureOr encrypt(String message) async { + emit(state.copyWith(state: State.loading)); + + final sk = await sessionRepository.getSessionKey(); + + if (sk.isErr) { + await loggerRepository.addLog( + const LogError('No SecretKey!\n' + 'Go in Key tab and generate or derive one.'), + ); + emit( + state.copyWith( + state: State.failure, + plainText: message.toBytes(), + error: sk.err?.message, + ), + ); + + return; + } + + final result = await cryptoRepository.encrypt(message.toBytes(), sk.ok!); + + emit( + await result.foldAsync( + (cipherText) async { + await loggerRepository.addLog( + LogInfo('String successfully encrypted.\n' + 'Length: ${cipherText.length} bytes.\n' + 'Hex:\n${cipherText.toStr(to: Encoding.base16)}'), + ); + return state.copyWith( + state: State.success, + plainText: message.toBytes(), + cipherText: cipherText, + plainTextFile: '', + cipherTextFile: '', + ); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during encryption.'), + ); + return state.copyWith( + state: State.failure, + plainText: message.toBytes(), + error: error.message, + ); + }, + ), + ); + + return; + } + + FutureOr alterMemory() async { + emit(state.copyWith(state: State.loading)); + + if (state.cipherText?.isEmpty ?? true) { + const error = 'Encrypt before decrypting!'; + await loggerRepository.addLog( + const LogError(error), + ); + emit( + state.copyWith( + state: State.failure, + error: error, + ), + ); + + return; + } + + final altered = state.cipherText!; + altered[0] += 1; + + await loggerRepository.addLog( + const LogWarning('In memory cipher text altered.'), + ); + + emit(state.copyWith(cipherText: altered)); + + return; + } + + FutureOr decryptFromMemory() async { + emit(state.copyWith(state: State.loading)); + + final sk = await sessionRepository.getSessionKey(); + + if (sk.isErr) { + await loggerRepository.addLog( + const LogError('No SecretKey!\n' + 'Go in Key tab and generate or derive one.'), + ); + emit( + state.copyWith( + state: State.failure, + error: sk.err?.message, + ), + ); + + return; + } + + if (state.cipherText?.isEmpty ?? true) { + const error = 'Encrypt before decrypting!'; + await loggerRepository.addLog( + const LogError(error), + ); + emit( + state.copyWith( + state: State.failure, + error: error, + ), + ); + + return; + } + + final result = await cryptoRepository.decrypt(state.cipherText!, sk.ok!); + + emit( + await result.foldAsync( + (plainText) async { + await loggerRepository.addLog( + LogInfo('String successfully decrypted.\n' + 'Text:\n${plainText.toStr()}'), + ); + return state.copyWith( + state: State.success, + plainText: plainText, + plainTextFile: '', + cipherTextFile: '', + ); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during decryption.'), + ); + return state.copyWith( + state: State.failure, + error: error.message, + ); + }, + ), + ); + + return; + } + + FutureOr decryptFromBase16(String message) async { + emit(state.copyWith(state: State.loading)); + + final sk = await sessionRepository.getSessionKey(); + final cipherText = message.toBytes(from: Encoding.base16); + + if (sk.isErr) { + await loggerRepository.addLog( + const LogError('No SecretKey!\n' + 'Go in Key tab and generate or derive one.'), + ); + emit( + state.copyWith( + state: State.failure, + cipherText: cipherText, + error: sk.err?.message, + ), + ); + + return; + } + + final result = await cryptoRepository.decrypt(cipherText, sk.ok!); + + emit( + await result.foldAsync( + (plainText) async { + await loggerRepository.addLog( + LogInfo('String successfully decrypted.\n' + 'Text:\n${plainText.toStr()}'), + ); + return state.copyWith( + state: State.success, + plainText: plainText, + cipherText: cipherText, + plainTextFile: '', + cipherTextFile: '', + ); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during decryption.'), + ); + return state.copyWith( + state: State.failure, + cipherText: cipherText, + error: error.message, + ); + }, + ), + ); + + return; + } + + FutureOr encryptFile() async { + emit(state.copyWith(state: State.loading)); + + final sk = await sessionRepository.getSessionKey(); + + if (sk.isErr) { + await loggerRepository.addLog( + const LogError('No SecretKey!\n' + 'Go in Key tab and generate or derive one.'), + ); + emit( + state.copyWith( + state: State.failure, + error: sk.err?.message, + ), + ); + + return; + } + + // Pick file to encrypt + final pickFileResult = await FilePicker.platform.pickFiles(); + + if (pickFileResult == null) { + await loggerRepository.addLog( + const LogError('No file selected.'), + ); + emit( + state.copyWith( + state: State.failure, + error: 'No file selected.', + ), + ); + + return; + } + + final file = File(pickFileResult.files.single.path!); + + // Pick folder to store the encrypted file + final resultFolder = await FilePicker.platform.getDirectoryPath(); + + if (resultFolder == null) { + await loggerRepository.addLog( + const LogError('No folder selected.'), + ); + emit( + state.copyWith( + state: State.failure, + error: 'No folder selected.', + ), + ); + + return; + } + + final folder = Directory(resultFolder); + + final encryption = await cryptoRepository.encryptFile( + file, + folder.uri, + sk.ok!, + ); + + emit( + await encryption.foldAsync( + (_) async { + await loggerRepository.addLog( + const LogInfo('File successfully encrypted.\n'), + ); + return state.copyWith( + state: State.success, + plainTextFile: '', + cipherTextFile: '', + ); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during encryption.'), + ); + return state.copyWith( + state: State.failure, + error: error.message, + ); + }, + ), + ); + + return; + } + + FutureOr decryptFile() async { + emit(state.copyWith(state: State.loading)); + + final sk = await sessionRepository.getSessionKey(); + + if (sk.isErr) { + await loggerRepository.addLog( + const LogError('No SecretKey!\n' + 'Go in Key tab and generate or derive one.'), + ); + emit( + state.copyWith( + state: State.failure, + error: sk.err?.message, + ), + ); + + return; + } + + await FilePicker.platform.clearTemporaryFiles(); + + final resultPickFile = await FilePicker.platform.pickFiles(); + + if (resultPickFile == null) { + await loggerRepository.addLog( + const LogError('No file selected.'), + ); + emit( + state.copyWith( + state: State.failure, + error: 'No file selected.', + ), + ); + + return; + } + + final file = File(resultPickFile.files.single.path!); + + // Pick folder to store the encrypted file + final resultFolder = await FilePicker.platform.getDirectoryPath(); + + if (resultFolder == null) { + await loggerRepository.addLog( + const LogError('No folder selected.'), + ); + emit( + state.copyWith( + state: State.failure, + error: 'No folder selected.', + ), + ); + + return; + } + + final folder = Directory(resultFolder); + + final decryption = + await cryptoRepository.decryptFile(file, folder.uri, sk.ok!); + + emit( + await decryption.foldAsync( + (_) async { + await loggerRepository.addLog( + const LogInfo('File successfully decrypted.\n'), + ); + return state.copyWith( + state: State.success, + plainTextFile: '', + cipherTextFile: '', + ); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during decryption.'), + ); + return state.copyWith( + state: State.failure, + error: error.message, + ); + }, + ), + ); + + return; + } +} diff --git a/packages/native_crypto/example/lib/presentation/cipher/blocs/aes/aes_state.dart b/packages/native_crypto/example/lib/presentation/cipher/blocs/aes/aes_state.dart new file mode 100644 index 0000000..7f4411d --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/cipher/blocs/aes/aes_state.dart @@ -0,0 +1,51 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'aes_cubit.dart'; + +@immutable +class AESState { + const AESState( + this.state, + this.plainText, + this.cipherText, + this.plainTextFile, + this.cipherTextFile, + this.error, + ); + + const AESState.initial() + : state = State.initial, + plainText = null, + plainTextFile = null, + cipherText = null, + cipherTextFile = null, + error = null; + + final State state; + final Uint8List? plainText; + final Uint8List? cipherText; + final String? plainTextFile; + final String? cipherTextFile; + final String? error; + + AESState copyWith({ + State? state, + Uint8List? plainText, + Uint8List? cipherText, + String? plainTextFile, + String? cipherTextFile, + String? error, + }) => + AESState( + state ?? this.state, + plainText ?? this.plainText, + cipherText ?? this.cipherText, + plainTextFile ?? this.plainTextFile, + cipherTextFile ?? this.cipherTextFile, + error ?? this.error, + ); +} diff --git a/packages/native_crypto/example/lib/presentation/cipher/state_management/aes_state_management.dart b/packages/native_crypto/example/lib/presentation/cipher/state_management/aes_state_management.dart new file mode 100644 index 0000000..4b96dca --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/cipher/state_management/aes_state_management.dart @@ -0,0 +1,129 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; +import 'package:native_crypto_example/presentation/cipher/blocs/aes/aes_cubit.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart'; +import 'package:native_crypto_example/presentation/output/widgets/logs.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class AESStateManagement extends CubitScreen { + AESStateManagement({super.key}); + + final TextEditingController _plainTextTextController = TextEditingController() + ..text = 'abc'; + + final TextEditingController _cipherTextTextController = + TextEditingController(); + + @override + AESCubit create(BuildContext context) => AESCubit( + sessionRepository: repo(context), + loggerRepository: repo(context), + cryptoRepository: repo(context), + ); + + @override + Widget onBuild(BuildContext context, AESState state) => ListView( + children: [ + const Logs(), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'AES256-GCM', + style: AppTypography.title, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + '''AES is a symmetric-key encryption algorithm that is widely used to encrypt sensitive data. It works by encrypting plaintext (the original data) using a secret key and a set of predefined mathematical operations (known as a cipher). The result is ciphertext (the encrypted data) that can only be decrypted and read by someone who has access to the secret key.\nGCM is a mode of operation for AES that provides both confidentiality and authenticity. It works by combining a block cipher (such as AES) with a Galois Field (a mathematical structure used to define a specific type of encryption) and a Counter (a value that is incremented for each block of data that is processed).''', + style: AppTypography.body, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _plainTextTextController, + decoration: const InputDecoration( + hintText: 'PlainText', + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Encrypt', + onPressed: () => + bloc(context).encrypt(_plainTextTextController.text), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Alter', + onPressed: () => bloc(context).alterMemory(), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Decrypt', + onPressed: () => bloc(context).decryptFromMemory(), + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'File', + style: AppTypography.title, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Encrypt file', + onPressed: () => bloc(context).encryptFile(), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Decrypt file', + onPressed: () => bloc(context).decryptFile(), + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'External CipherText', + style: AppTypography.title, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _cipherTextTextController, + decoration: const InputDecoration( + hintText: 'Base16 CipherText', + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Decrypt', + onPressed: () => bloc(context) + .decryptFromBase16(_cipherTextTextController.text), + ), + ), + ], + ); +} diff --git a/packages/native_crypto/example/lib/presentation/hash/blocs/hash/hash_cubit.dart b/packages/native_crypto/example/lib/presentation/hash/blocs/hash/hash_cubit.dart new file mode 100644 index 0000000..f57f58c --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/hash/blocs/hash/hash_cubit.dart @@ -0,0 +1,53 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/entities/states.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; + +part 'hash_state.dart'; + +class HashCubit extends Cubit { + HashCubit({ + required this.loggerRepository, + required this.cryptoRepository, + }) : super(const HashState.initial()); + + final LoggerRepository loggerRepository; + final CryptoRepository cryptoRepository; + + FutureOr hash(Hash hasher, String message) async { + emit(const HashState.loading()); + final result = await cryptoRepository.hash(hasher, message.toBytes()); + + emit( + await result.foldAsync( + (digest) async { + await loggerRepository.addLog( + LogInfo('Hash $message using ${hasher.algorithm.name}.\n' + 'Length: ${digest.length} bytes.\n' + 'Hex:\n${digest.toStr(to: Encoding.base16)}'), + ); + return HashState.success(digest); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during digest.'), + ); + return HashState.failure(error.message); + }, + ), + ); + + return; + } +} diff --git a/packages/native_crypto/example/lib/presentation/hash/blocs/hash/hash_state.dart b/packages/native_crypto/example/lib/presentation/hash/blocs/hash/hash_state.dart new file mode 100644 index 0000000..7eb49e3 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/hash/blocs/hash/hash_state.dart @@ -0,0 +1,32 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'hash_cubit.dart'; + +@immutable +class HashState { + const HashState.initial() + : state = State.initial, + result = null, + error = null; + + const HashState.loading() + : state = State.loading, + result = null, + error = null; + + const HashState.failure(this.error) + : state = State.failure, + result = null; + + const HashState.success(this.result) + : state = State.success, + error = null; + + final State state; + final Uint8List? result; + final String? error; +} diff --git a/packages/native_crypto/example/lib/presentation/hash/state_management/hash_state_management.dart b/packages/native_crypto/example/lib/presentation/hash/state_management/hash_state_management.dart new file mode 100644 index 0000000..c2c5c9a --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/hash/state_management/hash_state_management.dart @@ -0,0 +1,88 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/presentation/hash/blocs/hash/hash_cubit.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart'; +import 'package:native_crypto_example/presentation/output/widgets/logs.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class HashStateManagement extends CubitScreen { + HashStateManagement({super.key}); + + final TextEditingController _hashTextController = TextEditingController() + ..text = 'abc'; + + @override + HashCubit create(BuildContext context) => HashCubit( + loggerRepository: repo(context), + cryptoRepository: repo(context), + ); + + @override + Widget onBuild(BuildContext context, HashState state) => ListView( + children: [ + const Logs(), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Hash', + style: AppTypography.title, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + '''SHA-256, SHA-384, and SHA-512 are cryptographic hash functions. They are used to create a fixed-size output (known as a hash or digest) from an input of any size. The output of a hash function is deterministic, which means that the same input will always produce the same output.''', + style: AppTypography.body, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _hashTextController, + decoration: const InputDecoration( + hintText: 'Message', + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'SHA256', + onPressed: () => bloc(context).hash( + Sha256(), + _hashTextController.text, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'SHA384', + onPressed: () => bloc(context).hash( + Sha384(), + _hashTextController.text, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'SHA512', + onPressed: () => bloc(context).hash( + Sha512(), + _hashTextController.text, + ), + ), + ), + ], + ); +} diff --git a/packages/native_crypto/example/lib/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart b/packages/native_crypto/example/lib/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart new file mode 100644 index 0000000..b007ce4 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart @@ -0,0 +1,53 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto_example/data/repositories/crypto_repository_switchable_impl.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; + +part 'mode_switcher_state.dart'; + +class ModeSwitcherCubit extends Cubit { + ModeSwitcherCubit( + this.sessionRepository, + this.cryptoRepository, + ) : super(const ModeSwitcherState(NativeCryptoMode())); + + SessionRepository sessionRepository; + CryptoRepository cryptoRepository; + + FutureOr switchMode() async { + final currentMode = await sessionRepository.getCurrentMode(); + Mode? newMode; + + if (currentMode.isOk) { + if (currentMode.ok == const NativeCryptoMode()) { + newMode = const PointyCastleMode(); + } else { + newMode = const NativeCryptoMode(); + } + + sessionRepository.setCurrentMode(newMode); + if (cryptoRepository is CryptoRepositorySwitchableImpl) { + (cryptoRepository as CryptoRepositorySwitchableImpl).mode = newMode; + } + } else { + newMode = const NativeCryptoMode(); + sessionRepository.setCurrentMode(newMode); + + if (cryptoRepository is CryptoRepositorySwitchableImpl) { + (cryptoRepository as CryptoRepositorySwitchableImpl).mode = newMode; + } + } + + emit(ModeSwitcherState(newMode)); + } +} diff --git a/packages/native_crypto/example/lib/presentation/home/blocs/mode_switcher/mode_switcher_state.dart b/packages/native_crypto/example/lib/presentation/home/blocs/mode_switcher/mode_switcher_state.dart new file mode 100644 index 0000000..9eae7c3 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/blocs/mode_switcher/mode_switcher_state.dart @@ -0,0 +1,26 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'mode_switcher_cubit.dart'; + +@immutable +class ModeSwitcherState { + const ModeSwitcherState(this.currentMode); + + final Mode currentMode; + + @override + bool operator ==(covariant ModeSwitcherState other) { + if (identical(this, other)) { + return true; + } + + return other.currentMode == currentMode; + } + + @override + int get hashCode => currentMode.hashCode; +} diff --git a/packages/native_crypto/example/lib/presentation/home/blocs/navigation_bar/navigation_bar_cubit.dart b/packages/native_crypto/example/lib/presentation/home/blocs/navigation_bar/navigation_bar_cubit.dart new file mode 100644 index 0000000..ee5263a --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/blocs/navigation_bar/navigation_bar_cubit.dart @@ -0,0 +1,20 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +part 'navigation_bar_state.dart'; + +class NavigationBarCubit extends Cubit { + NavigationBarCubit() : super(const NavigationBarState(0)); + + FutureOr changePage(int page) { + emit(NavigationBarState(page)); + } +} diff --git a/packages/native_crypto/example/lib/presentation/home/blocs/navigation_bar/navigation_bar_state.dart b/packages/native_crypto/example/lib/presentation/home/blocs/navigation_bar/navigation_bar_state.dart new file mode 100644 index 0000000..abec21c --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/blocs/navigation_bar/navigation_bar_state.dart @@ -0,0 +1,14 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'navigation_bar_cubit.dart'; + +@immutable +class NavigationBarState { + const NavigationBarState(this.index); + + final int index; +} diff --git a/packages/native_crypto/example/lib/presentation/home/state_management/home_state_management.dart b/packages/native_crypto/example/lib/presentation/home/state_management/home_state_management.dart new file mode 100644 index 0000000..fbd378d --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/state_management/home_state_management.dart @@ -0,0 +1,67 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/presentation/benchmark/state_management/benchmark_state_management.dart'; +import 'package:native_crypto_example/presentation/cipher/state_management/aes_state_management.dart'; +import 'package:native_crypto_example/presentation/hash/state_management/hash_state_management.dart'; +import 'package:native_crypto_example/presentation/home/blocs/navigation_bar/navigation_bar_cubit.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/app_bar_state_management.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/bottom_navigation_bar_state_management.dart'; +import 'package:native_crypto_example/presentation/kdf/state_management/key_derivation_state_management.dart'; +import 'package:native_crypto_example/presentation/test_vectors/state_management/test_vectors_state_management.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class HomeStateManagement + extends CubitScreen { + HomeStateManagement({super.key}); + + final List _children = [ + KeyDerivationStateManagement(), + HashStateManagement(), + AESStateManagement(), + TestVectorsStateManagement(), + const BenchmarkStateManagement(), + ]; + + @override + NavigationBarCubit create(BuildContext context) => NavigationBarCubit(); + + @override + Widget onBuild(BuildContext context, NavigationBarState state) => Scaffold( + appBar: const PreferredSize( + preferredSize: Size(double.infinity, 60), + child: AppBarStateManagement(), + ), + body: _children[state.index], + bottomNavigationBar: BottomNavigationBarStateManagement( + onTap: (page) => bloc(context).changePage(page), // new + currentIndex: state.index, // new + items: const [ + BottomNavigationBarItem( + icon: Icon(Icons.vpn_key), + label: 'Key', + ), + BottomNavigationBarItem( + icon: Icon(Icons.tag), + label: 'Hash', + ), + BottomNavigationBarItem( + icon: Icon(Icons.lock), + label: 'Encryption', + ), + BottomNavigationBarItem( + icon: Icon(Icons.rule_rounded), + label: 'Tests', + ), + BottomNavigationBarItem( + icon: Icon(Icons.timer), + label: 'Benchmark', + ), + ], + ), + ); +} diff --git a/packages/native_crypto/example/lib/presentation/home/state_management/widgets/app_bar_state_management.dart b/packages/native_crypto/example/lib/presentation/home/state_management/widgets/app_bar_state_management.dart new file mode 100644 index 0000000..6b522a4 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/state_management/widgets/app_bar_state_management.dart @@ -0,0 +1,33 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/domain/entities/mode.dart'; +import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class AppBarStateManagement + extends CubitConsumerScreen { + const AppBarStateManagement({super.key}); + + @override + Widget onBuild(BuildContext context, ModeSwitcherState state) => AppBar( + centerTitle: true, + title: Text( + state.currentMode == const NativeCryptoMode() + ? 'NativeCrypto' + : 'PointyCastle', + ), + backgroundColor: state.currentMode.primaryColor, + actions: [ + Switch( + activeColor: Colors.white, + value: state.currentMode == const NativeCryptoMode(), + onChanged: (_) => bloc(context).switchMode(), + ) + ], + ); +} diff --git a/packages/native_crypto/example/lib/presentation/home/state_management/widgets/bottom_navigation_bar_state_management.dart b/packages/native_crypto/example/lib/presentation/home/state_management/widgets/bottom_navigation_bar_state_management.dart new file mode 100644 index 0000000..5483ad1 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/state_management/widgets/bottom_navigation_bar_state_management.dart @@ -0,0 +1,35 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class BottomNavigationBarStateManagement + extends CubitConsumerScreen { + const BottomNavigationBarStateManagement({ + required this.items, + this.currentIndex = 0, + this.onTap, + super.key, + }); + + final void Function(int)? onTap; + final int currentIndex; + final List items; + + @override + Widget onBuild(BuildContext context, ModeSwitcherState state) => + BottomNavigationBar( + selectedItemColor: state.currentMode.primaryColor, + unselectedItemColor: Colors.black, + showUnselectedLabels: true, + onTap: onTap, + currentIndex: currentIndex, + items: items, + type: BottomNavigationBarType.shifting, + ); +} diff --git a/packages/native_crypto/example/lib/presentation/home/state_management/widgets/button_state_management.dart b/packages/native_crypto/example/lib/presentation/home/state_management/widgets/button_state_management.dart new file mode 100644 index 0000000..97b0f8a --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/state_management/widgets/button_state_management.dart @@ -0,0 +1,38 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/presentation/home/blocs/mode_switcher/mode_switcher_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class ButtonStateManagement + extends CubitConsumerScreen { + const ButtonStateManagement({ + required this.label, + this.onPressed, + super.key, + }); + + final void Function()? onPressed; + final String label; + + @override + Widget onBuild(BuildContext context, ModeSwitcherState state) { + context.watch(); + return ElevatedButton( + onPressed: onPressed, + style: ElevatedButton.styleFrom( + backgroundColor: state.currentMode.primaryColor, + ), + child: Text( + label, + style: AppTypography.body, + ), + ); + } +} diff --git a/packages/native_crypto/example/lib/presentation/home/widgets/blank.dart b/packages/native_crypto/example/lib/presentation/home/widgets/blank.dart new file mode 100644 index 0000000..3a5835e --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/home/widgets/blank.dart @@ -0,0 +1,15 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; + +class Blank extends StatelessWidget { + const Blank({super.key}); + + @override + Widget build(BuildContext context) => + const Center(child: Text('Nothing to show')); +} diff --git a/packages/native_crypto/example/lib/presentation/kdf/blocs/key_derivation/key_derivation_cubit.dart b/packages/native_crypto/example/lib/presentation/kdf/blocs/key_derivation/key_derivation_cubit.dart new file mode 100644 index 0000000..41f9c48 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/kdf/blocs/key_derivation/key_derivation_cubit.dart @@ -0,0 +1,112 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/entities/states.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; + +part 'key_derivation_state.dart'; + +class KeyDerivationCubit extends Cubit { + KeyDerivationCubit({ + required this.sessionRepository, + required this.loggerRepository, + required this.cryptoRepository, + }) : super(const KeyDerivationState.initial()); + final SessionRepository sessionRepository; + final LoggerRepository loggerRepository; + final CryptoRepository cryptoRepository; + + FutureOr generate() async { + emit(const KeyDerivationState.loading()); + final result = await cryptoRepository.generateSecureRandom(32); + + emit( + await result.foldAsync( + (key) async { + await sessionRepository.setSessionKey(key); + await loggerRepository.addLog( + LogInfo('SecretKey successfully generated.\n' + 'Length: ${key.bytes.length} bytes.\n' + 'Hex:\n${key.base16}'), + ); + return KeyDerivationState.success(key.bytes); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during key generation.'), + ); + return KeyDerivationState.failure(error.message); + }, + ), + ); + + return; + } + + FutureOr pbkdf2(String password) async { + emit(const KeyDerivationState.loading()); + if (password.isEmpty) { + const error = 'Password is empty'; + await loggerRepository.addLog( + const LogError(error), + ); + emit(const KeyDerivationState.failure(error)); + } + final result = + await cryptoRepository.deriveKeyFromPassword(password, salt: 'salt'); + + emit( + await result.foldAsync( + (key) async { + await sessionRepository.setSessionKey(key); + await loggerRepository.addLog( + LogInfo('SecretKey successfully derivated from $password.\n' + 'Length: ${key.bytes.length} bytes.\n' + 'Hex:\n${key.base16}'), + ); + return KeyDerivationState.success(key.bytes); + }, + (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during key derivation.'), + ); + return KeyDerivationState.failure(error.message); + }, + ), + ); + + return; + } + + FutureOr getSessionKey() async { + emit(const KeyDerivationState.loading()); + final sk = await sessionRepository.getSessionKey(); + emit( + await sk.foldAsync((key) async { + await loggerRepository.addLog( + LogInfo('Session key successfully retreived.\n' + 'Length: ${key.bytes.length} bytes.\n' + 'Hex:\n${key.base16}'), + ); + return KeyDerivationState.success(key.bytes); + }, (error) async { + await loggerRepository.addLog( + LogError(error.message ?? 'Error during key retrieving.'), + ); + return KeyDerivationState.failure(error.message); + }), + ); + + return; + } +} diff --git a/packages/native_crypto/example/lib/presentation/kdf/blocs/key_derivation/key_derivation_state.dart b/packages/native_crypto/example/lib/presentation/kdf/blocs/key_derivation/key_derivation_state.dart new file mode 100644 index 0000000..c8b7e6d --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/kdf/blocs/key_derivation/key_derivation_state.dart @@ -0,0 +1,32 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'key_derivation_cubit.dart'; + +@immutable +class KeyDerivationState { + const KeyDerivationState.initial() + : state = State.initial, + result = null, + error = null; + + const KeyDerivationState.loading() + : state = State.loading, + result = null, + error = null; + + const KeyDerivationState.failure(this.error) + : state = State.failure, + result = null; + + const KeyDerivationState.success(this.result) + : state = State.success, + error = null; + + final State state; + final Uint8List? result; + final String? error; +} diff --git a/packages/native_crypto/example/lib/presentation/kdf/state_management/key_derivation_state_management.dart b/packages/native_crypto/example/lib/presentation/kdf/state_management/key_derivation_state_management.dart new file mode 100644 index 0000000..fff27cc --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/kdf/state_management/key_derivation_state_management.dart @@ -0,0 +1,102 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/domain/repositories/session_repository.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart'; +import 'package:native_crypto_example/presentation/kdf/blocs/key_derivation/key_derivation_cubit.dart'; +import 'package:native_crypto_example/presentation/output/widgets/logs.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class KeyDerivationStateManagement + extends CubitScreen { + KeyDerivationStateManagement({super.key}); + + final TextEditingController _pwdTextController = TextEditingController() + ..text = 'password'; + + @override + KeyDerivationCubit create(BuildContext context) => KeyDerivationCubit( + sessionRepository: repo(context), + loggerRepository: repo(context), + cryptoRepository: repo(context), + ); + + @override + Widget onBuild(BuildContext context, KeyDerivationState state) => ListView( + children: [ + const Logs(), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Random secret key', + style: AppTypography.title, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + '''A secret key is a piece of information that is used to encrypt and decrypt messages. In symmetric-key encryption, the same secret key is used for both encrypting the original data and for decrypting the encrypted data. The key is kept secret so that only authorized parties can read the encrypted messages.''', + style: AppTypography.body, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Generate secret key', + onPressed: () => bloc(context).generate(), + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'PBKDF2', + style: AppTypography.title, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + '''PBKDF2 (Password-Based Key Derivation Function 2) is a key derivation function that is designed to be computationally expensive and memory-intensive, in order to slow down the process of guessing a password. This makes it more difficult for an attacker to guess a password by using a brute-force attack, which involves trying every possible combination of characters until the correct password is found.''', + style: AppTypography.body, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _pwdTextController, + decoration: const InputDecoration( + hintText: 'Password', + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Compute PBKDF2', + onPressed: () => bloc(context).pbkdf2(_pwdTextController.text), + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Session', + style: AppTypography.title, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Retrieve session key', + onPressed: () => bloc(context).getSessionKey(), + ), + ), + ], + ); +} diff --git a/packages/native_crypto/example/lib/presentation/output/blocs/output/output_cubit.dart b/packages/native_crypto/example/lib/presentation/output/blocs/output/output_cubit.dart new file mode 100644 index 0000000..fd5cd43 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/output/blocs/output/output_cubit.dart @@ -0,0 +1,49 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:wyatt_architecture/wyatt_architecture.dart'; +import 'package:wyatt_type_utils/wyatt_type_utils.dart'; + +part 'output_state.dart'; + +class OutputCubit extends Cubit { + OutputCubit(this.loggerRepository) : super(const OutputState.empty()) { + logSubscription = loggerRepository.streamLogs().listen((message) { + if (message.isOk) { + onMessage(message.ok!); + } + }); + } + + final LoggerRepository loggerRepository; + late StreamSubscription, AppException>> + logSubscription; + + FutureOr add(LogMessage message) { + loggerRepository.addLog(message); + } + + FutureOr clear() { + loggerRepository.clearLog(); + emit(const OutputState.empty()); + } + + FutureOr onMessage(Map entries) { + emit(OutputState(entries: entries)); + } + + @override + Future close() { + logSubscription.cancel(); + return super.close(); + } +} diff --git a/packages/native_crypto/example/lib/presentation/output/blocs/output/output_state.dart b/packages/native_crypto/example/lib/presentation/output/blocs/output/output_state.dart new file mode 100644 index 0000000..6cb82c3 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/output/blocs/output/output_state.dart @@ -0,0 +1,32 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'output_cubit.dart'; + +@immutable +class OutputState { + const OutputState({ + required this.entries, + }); + + const OutputState.empty() : entries = const {}; + + final Map entries; + + @override + String toString() { + final StringBuffer buffer = StringBuffer(); + entries.forEach((key, value) { + buffer + ..write(value.prefix) + ..write(' at ') + ..write(key.toIso8601String()) + ..write(':\t') + ..writeln(value.message); + }); + return buffer.toString(); + } +} diff --git a/packages/native_crypto/example/lib/presentation/output/state_management/output_state_management.dart b/packages/native_crypto/example/lib/presentation/output/state_management/output_state_management.dart new file mode 100644 index 0000000..1512987 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/output/state_management/output_state_management.dart @@ -0,0 +1,76 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/presentation/output/blocs/output/output_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class OutputStateManagement + extends CubitConsumerScreen { + OutputStateManagement({super.key}); + + final ScrollController _scrollController = ScrollController(); + + @override + Widget onBuild(BuildContext context, OutputState state) { + // Auto scroll + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_scrollController.hasClients) { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 100), + curve: Curves.easeOut, + ); + } + }); + + return SizedBox( + height: 250, + child: ListView.separated( + controller: _scrollController, + itemCount: state.entries.length, + separatorBuilder: (context, index) => const Padding( + padding: EdgeInsets.all(8), + child: SizedBox( + height: 1, + child: ColoredBox(color: Colors.black), + ), + ), + itemBuilder: (context, index) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + state.entries.values.elementAt(index).prefix, + style: TextStyle( + color: state.entries.values.elementAt(index).color, + ), + ), + Text( + state.entries.keys.elementAt(index).toIso8601String(), + style: TextStyle( + color: state.entries.values.elementAt(index).color, + ), + ) + ], + ), + Align( + alignment: Alignment.centerLeft, + child: SelectableText( + state.entries.values.elementAt(index).message, + style: TextStyle( + color: state.entries.values.elementAt(index).color, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/packages/native_crypto/example/lib/presentation/output/state_management/widgets/clear_button_state_management.dart b/packages/native_crypto/example/lib/presentation/output/state_management/widgets/clear_button_state_management.dart new file mode 100644 index 0000000..ecbc128 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/output/state_management/widgets/clear_button_state_management.dart @@ -0,0 +1,22 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart'; +import 'package:native_crypto_example/presentation/output/blocs/output/output_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class ClearButtonStateManagement + extends CubitConsumerScreen { + const ClearButtonStateManagement({super.key}); + + @override + Widget onBuild(BuildContext context, OutputState state) => + ButtonStateManagement( + label: 'Clear', + onPressed: () => bloc(context).clear(), + ); +} diff --git a/packages/native_crypto/example/lib/presentation/output/widgets/logs.dart b/packages/native_crypto/example/lib/presentation/output/widgets/logs.dart new file mode 100644 index 0000000..a623e1a --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/output/widgets/logs.dart @@ -0,0 +1,44 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/presentation/output/state_management/output_state_management.dart'; +import 'package:native_crypto_example/presentation/output/state_management/widgets/clear_button_state_management.dart'; + +class Logs extends StatelessWidget { + const Logs({super.key}); + + @override + Widget build(BuildContext context) => Column( + children: [ + Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: const [ + Text( + 'Logs', + style: AppTypography.title, + ), + ClearButtonStateManagement(), + ], + ), + ), + const SizedBox( + height: 2, + width: double.infinity, + child: ColoredBox(color: Colors.black), + ), + OutputStateManagement(), + const SizedBox( + height: 2, + width: double.infinity, + child: ColoredBox(color: Colors.black), + ), + ], + ); +} diff --git a/packages/native_crypto/example/lib/presentation/test_vectors/blocs/test_vectors/test_vectors_cubit.dart b/packages/native_crypto/example/lib/presentation/test_vectors/blocs/test_vectors/test_vectors_cubit.dart new file mode 100644 index 0000000..2d84265 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/test_vectors/blocs/test_vectors/test_vectors_cubit.dart @@ -0,0 +1,204 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:native_crypto/native_crypto.dart'; +import 'package:native_crypto_example/domain/entities/log_message.dart'; +import 'package:native_crypto_example/domain/entities/states.dart'; +import 'package:native_crypto_example/domain/entities/test_vector.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; + +part 'test_vectors_state.dart'; + +class TestVectorsCubit extends Cubit { + TestVectorsCubit({ + required this.loggerRepository, + required this.cryptoRepository, + }) : super(const TestVectorsState.initial()); + final LoggerRepository loggerRepository; + final CryptoRepository cryptoRepository; + + final tests = [ + TestVector( + key: 'b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4', + nonce: '516c33929df5a3284ff463d7', + plainText: '', + cipherText: '', + tag: 'bdc1ac884d332457a1d2664f168c76f0', + ), + TestVector( + key: '5fe0861cdc2690ce69b3658c7f26f8458eec1c9243c5ba0845305d897e96ca0f', + nonce: '770ac1a5a3d476d5d96944a1', + plainText: '', + cipherText: '', + tag: '196d691e1047093ca4b3d2ef4baba216', + ), + TestVector( + key: '7620b79b17b21b06d97019aa70e1ca105e1c03d2a0cf8b20b5a0ce5c3903e548', + nonce: '60f56eb7a4b38d4f03395511', + plainText: '', + cipherText: '', + tag: 'f570c38202d94564bab39f75617bc87a', + ), + TestVector( + key: '31bdadd96698c204aa9ce1448ea94ae1fb4a9a0b3c9d773b51bb1822666b8f22', + nonce: '0d18e06c7c725ac9e362e1ce', + plainText: '2db5168e932556f8089a0622981d017d', + cipherText: 'fa4362189661d163fcd6a56d8bf0405a', + tag: 'd636ac1bbedd5cc3ee727dc2ab4a9489', + ), + TestVector( + key: '460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99', + nonce: '8a4a16b9e210eb68bcb6f58d', + plainText: '99e4e926ffe927f691893fb79a96b067', + cipherText: '133fc15751621b5f325c7ff71ce08324', + tag: 'ec4e87e0cf74a13618d0b68636ba9fa7', + ), + TestVector( + key: 'f78a2ba3c5bd164de134a030ca09e99463ea7e967b92c4b0a0870796480297e5', + nonce: '2bb92fcb726c278a2fa35a88', + plainText: 'f562509ed139a6bbe7ab545ac616250c', + cipherText: 'e2f787996e37d3b47294bf7ebba5ee25', + tag: '00f613eee9bdad6c9ee7765db1cb45c0', + ), + ]; + + FutureOr launchAllTests() async { + emit(const TestVectorsState.loading()); + int i = 0; + for (final test in tests) { + final sk = SecretKey(test.keyBytes); + final iv = test.nonceBytes; + + // Encryption + final encryption = + await cryptoRepository.encryptWithIV(test.plainTextBytes, sk, iv); + final testCipherText = await encryption + .foldAsync((cipherText) async => cipherText, (error) async { + await loggerRepository.addLog( + LogError( + 'TEST $i :: ${error.message ?? 'Error during encryption.'} ❌', + ), + ); + }); + + final encryptionSuccess = test.validateCipherText(testCipherText); + + if (encryptionSuccess) { + await loggerRepository.addLog( + LogInfo('TEST $i :: Encryption test passed ✅'), + ); + } else { + await loggerRepository.addLog( + LogError('TEST $i :: Encryption test failed ❌'), + ); + } + + // Decryption + final decryption = + await cryptoRepository.decrypt(test.cipherTextBytes, sk); + final testPlainText = await decryption + .foldAsync((plainText) async => plainText, (error) async { + await loggerRepository.addLog( + LogError( + 'TEST $i :: ${error.message ?? 'Error during decryption. ❌'}', + ), + ); + }); + + final decryptionSuccess = test.validatePlainText(testPlainText); + + if (decryptionSuccess) { + await loggerRepository.addLog( + LogInfo('TEST $i :: Decryption test passed ✅'), + ); + } else { + await loggerRepository.addLog( + LogError('TEST $i :: Decryption test failed ❌'), + ); + } + i++; + } + + if (i == tests.length) { + emit(const TestVectorsState.success()); + } else { + emit(const TestVectorsState.failure('Error during the test suite')); + } + + return; + } + + FutureOr launchTest(String number) async { + try { + final id = int.parse(number); + + final test = tests.elementAt(id); + + final sk = SecretKey(test.keyBytes); + final iv = test.nonceBytes; + + // Encryption + final encryption = + await cryptoRepository.encryptWithIV(test.plainTextBytes, sk, iv); + final testCipherText = await encryption + .foldAsync((cipherText) async => cipherText, (error) async { + await loggerRepository.addLog( + LogError( + 'TEST $id :: ${error.message ?? 'Error during encryption.'} ❌', + ), + ); + }); + + final encryptionSuccess = test.validateCipherText(testCipherText); + + if (encryptionSuccess) { + await loggerRepository.addLog( + LogInfo('TEST $id :: Encryption test passed ✅'), + ); + } else { + await loggerRepository.addLog( + LogError('TEST $id :: Encryption test failed ❌'), + ); + } + + // Decryption + final decryption = + await cryptoRepository.decrypt(test.cipherTextBytes, sk); + final testPlainText = await decryption + .foldAsync((plainText) async => plainText, (error) async { + await loggerRepository.addLog( + LogError( + 'TEST $id :: ${error.message ?? 'Error during decryption. ❌'}', + ), + ); + }); + + final decryptionSuccess = test.validatePlainText(testPlainText); + + if (decryptionSuccess) { + await loggerRepository.addLog( + LogInfo('TEST $id :: Decryption test passed ✅'), + ); + } else { + await loggerRepository.addLog( + LogError('TEST $id :: Decryption test failed ❌'), + ); + } + emit(const TestVectorsState.success()); + } catch (e) { + final error = 'Invalid vector number: $number'; + await loggerRepository.addLog(LogError(error)); + emit(TestVectorsState.failure(error)); + } + + return; + } +} diff --git a/packages/native_crypto/example/lib/presentation/test_vectors/blocs/test_vectors/test_vectors_state.dart b/packages/native_crypto/example/lib/presentation/test_vectors/blocs/test_vectors/test_vectors_state.dart new file mode 100644 index 0000000..7b06271 --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/test_vectors/blocs/test_vectors/test_vectors_state.dart @@ -0,0 +1,27 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +part of 'test_vectors_cubit.dart'; + +@immutable +class TestVectorsState { + const TestVectorsState.initial() + : state = State.initial, + error = null; + + const TestVectorsState.loading() + : state = State.loading, + error = null; + + const TestVectorsState.failure(this.error) : state = State.failure; + + const TestVectorsState.success() + : state = State.success, + error = null; + + final State state; + final String? error; +} diff --git a/packages/native_crypto/example/lib/presentation/test_vectors/state_management/test_vectors_state_management.dart b/packages/native_crypto/example/lib/presentation/test_vectors/state_management/test_vectors_state_management.dart new file mode 100644 index 0000000..282b52e --- /dev/null +++ b/packages/native_crypto/example/lib/presentation/test_vectors/state_management/test_vectors_state_management.dart @@ -0,0 +1,74 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/material.dart'; +import 'package:native_crypto_example/core/typography.dart'; +import 'package:native_crypto_example/domain/repositories/crypto_repository.dart'; +import 'package:native_crypto_example/domain/repositories/logger_repository.dart'; +import 'package:native_crypto_example/presentation/home/state_management/widgets/button_state_management.dart'; +import 'package:native_crypto_example/presentation/output/widgets/logs.dart'; +import 'package:native_crypto_example/presentation/test_vectors/blocs/test_vectors/test_vectors_cubit.dart'; +import 'package:wyatt_bloc_helper/wyatt_bloc_helper.dart'; + +class TestVectorsStateManagement + extends CubitScreen { + TestVectorsStateManagement({super.key}); + + final TextEditingController _vectorNumberTextController = + TextEditingController(); + + @override + TestVectorsCubit create(BuildContext context) => TestVectorsCubit( + loggerRepository: repo(context), + cryptoRepository: repo(context), + ); + + @override + Widget onBuild(BuildContext context, TestVectorsState state) => ListView( + children: [ + const Logs(), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Test vectors', + style: AppTypography.title, + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + '''In cryptography, a test vector is a set of input values that is used to test the correct implementation and operation of a cryptographic algorithm or system. Test vectors are usually provided by the creators of the algorithm, or by national standardization organizations, and can be used to verify that an implementation of the algorithm is correct and produces the expected output for a given set of inputs.''', + style: AppTypography.body, + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'All Tests', + onPressed: () => bloc(context).launchAllTests(), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: TextField( + controller: _vectorNumberTextController, + keyboardType: TextInputType.number, + decoration: const InputDecoration( + hintText: 'Vector number', + ), + ), + ), + Padding( + padding: const EdgeInsets.all(8), + child: ButtonStateManagement( + label: 'Specific Test', + onPressed: () => + bloc(context).launchTest(_vectorNumberTextController.text), + ), + ), + ], + ); +} diff --git a/packages/native_crypto/example/lib/session.dart b/packages/native_crypto/example/lib/session.dart deleted file mode 100644 index 7636866..0000000 --- a/packages/native_crypto/example/lib/session.dart +++ /dev/null @@ -1,26 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: session.dart -// Created Date: 28/12/2021 13:54:29 -// Last Modified: 28/12/2021 13:58:49 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:native_crypto/native_crypto.dart'; - -class Session { - SecretKey secretKey; - Session() : secretKey = SecretKey(Uint8List(0)); - - void setKey(SecretKey sk) { - secretKey = sk; - } -} - -// Providers - -final sessionProvider = StateProvider((ref) => Session()); diff --git a/packages/native_crypto/example/lib/widgets/button.dart b/packages/native_crypto/example/lib/widgets/button.dart deleted file mode 100644 index 2244bb4..0000000 --- a/packages/native_crypto/example/lib/widgets/button.dart +++ /dev/null @@ -1,31 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: button.dart -// Created Date: 28/12/2021 13:31:17 -// Last Modified: 28/12/2021 13:31:34 -// ----- -// Copyright (c) 2021 - -import 'package:flutter/material.dart'; - -class Button extends StatelessWidget { - final void Function() onPressed; - final String label; - - const Button(this.onPressed, this.label, {Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: onPressed, - style: TextButton.styleFrom( - primary: Colors.blue, - ), - child: Text( - label, - style: const TextStyle(color: Colors.white), - ), - ); - } -} diff --git a/packages/native_crypto/example/lib/widgets/output.dart b/packages/native_crypto/example/lib/widgets/output.dart deleted file mode 100644 index 1dc9b70..0000000 --- a/packages/native_crypto/example/lib/widgets/output.dart +++ /dev/null @@ -1,61 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: output.dart -// Created Date: 28/12/2021 13:31:39 -// Last Modified: 25/05/2022 16:39:39 -// ----- -// Copyright (c) 2021 - -import 'package:flutter/material.dart'; - -// ignore: must_be_immutable -class Output extends StatelessWidget { - late TextEditingController controller; - final bool large; - final bool editable; - - Output({ - Key? key, - TextEditingController? controller, - this.large = false, - this.editable = false, - }) : super(key: key) { - this.controller = controller ?? TextEditingController(); - } - - void print(String message) { - debugPrint(message); - controller.text = message; - } - - void append(String message) { - debugPrint(message); - controller.text += message; - } - - void appendln(String message) { - debugPrint(message); - controller.text += message + "\n"; - } - - void clear() { - controller.clear(); - } - - String read() { - return controller.text; - } - - @override - Widget build(BuildContext context) { - return TextField( - enableInteractiveSelection: true, - readOnly: editable ? false : true, - minLines: large ? 3 : 1, - maxLines: large ? 500 : 5, - decoration: const InputDecoration(border: OutlineInputBorder()), - controller: controller, - ); - } -} diff --git a/packages/native_crypto/example/pubspec.yaml b/packages/native_crypto/example/pubspec.yaml index 1e16cd6..33e895d 100644 --- a/packages/native_crypto/example/pubspec.yaml +++ b/packages/native_crypto/example/pubspec.yaml @@ -3,10 +3,10 @@ description: Demonstrates how to use the native_crypto plugin. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.15.0 <3.0.0" + sdk: ">=2.17.0 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -15,40 +15,37 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: - flutter: - sdk: flutter + flutter: { sdk: flutter } + native_crypto: { path: ../ } + cupertino_icons: ^1.0.5 + flutter_riverpod: ^2.1.3 + pointycastle: ^3.6.2 + flutter_bloc: ^8.1.1 - native_crypto: - # When depending on this package from a real application you should use: - # native_crypto: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. - path: ../ - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - flutter_riverpod: ^1.0.3 - pointycastle: ^3.6.0 + wyatt_architecture: + hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ + version: 0.1.0+1 + wyatt_type_utils: + hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ + version: 0.0.4 + wyatt_bloc_helper: + hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ + version: 2.0.0 + get_it: ^7.2.0 + file_picker: ^5.2.7 dev_dependencies: - flutter_test: - sdk: flutter + flutter_test: { sdk: flutter } - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. - flutter_lints: ^2.0.0 + wyatt_analysis: + hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ + version: 2.4.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec # The following section is specific to Flutter. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. diff --git a/packages/native_crypto/example/pubspec_overrides.yaml b/packages/native_crypto/example/pubspec_overrides.yaml new file mode 100644 index 0000000..f4eef6c --- /dev/null +++ b/packages/native_crypto/example/pubspec_overrides.yaml @@ -0,0 +1,12 @@ +# melos_managed_dependency_overrides: native_crypto,native_crypto_android,native_crypto_ios,native_crypto_platform_interface,native_crypto_web +dependency_overrides: + native_crypto: + path: .. + native_crypto_android: + path: ../../native_crypto_android + native_crypto_ios: + path: ../../native_crypto_ios + native_crypto_platform_interface: + path: ../../native_crypto_platform_interface + native_crypto_web: + path: ../../native_crypto_web diff --git a/packages/native_crypto/example/web/favicon.png b/packages/native_crypto/example/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/packages/native_crypto/example/web/favicon.png differ diff --git a/packages/native_crypto/example/web/icons/Icon-192.png b/packages/native_crypto/example/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/packages/native_crypto/example/web/icons/Icon-192.png differ diff --git a/packages/native_crypto/example/web/icons/Icon-512.png b/packages/native_crypto/example/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/packages/native_crypto/example/web/icons/Icon-512.png differ diff --git a/packages/native_crypto/example/web/icons/Icon-maskable-192.png b/packages/native_crypto/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/packages/native_crypto/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/native_crypto/example/web/icons/Icon-maskable-512.png b/packages/native_crypto/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/packages/native_crypto/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/native_crypto/example/web/index.html b/packages/native_crypto/example/web/index.html new file mode 100644 index 0000000..41b3bc3 --- /dev/null +++ b/packages/native_crypto/example/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/packages/native_crypto/example/web/manifest.json b/packages/native_crypto/example/web/manifest.json new file mode 100644 index 0000000..096edf8 --- /dev/null +++ b/packages/native_crypto/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/native_crypto/example/web/pkg b/packages/native_crypto/example/web/pkg new file mode 120000 index 0000000..033f93b --- /dev/null +++ b/packages/native_crypto/example/web/pkg @@ -0,0 +1 @@ +../../../native-crypto-rust/pkg/ \ No newline at end of file diff --git a/packages/native_crypto/lib/native_crypto.dart b/packages/native_crypto/lib/native_crypto.dart index 2b294a4..2a7dd10 100644 --- a/packages/native_crypto/lib/native_crypto.dart +++ b/packages/native_crypto/lib/native_crypto.dart @@ -1,11 +1,8 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: native_crypto.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 26/05/2022 12:10:42 -// ----- -// Copyright (c) 2021 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. /// Fast and powerful cryptographic functions /// thanks to javax.crypto, CommonCrypto and CryptoKit. @@ -13,18 +10,13 @@ /// Author: Hugo Pointcheval library native_crypto; -export 'package:native_crypto_platform_interface/src/utils/exception.dart'; +export 'package:native_crypto_platform_interface/src/core/exceptions/exception.dart'; export 'src/builders/builders.dart'; export 'src/ciphers/ciphers.dart'; export 'src/core/core.dart'; -export 'src/interfaces/interfaces.dart'; -export 'src/kdf/kdf.dart'; -export 'src/keys/keys.dart'; -// Utils -export 'src/utils/cipher_algorithm.dart'; -export 'src/utils/hash_algorithm.dart'; -export 'src/utils/kdf_algorithm.dart'; - -// ignore: constant_identifier_names -const String AUTHOR = 'Hugo Pointcheval'; +export 'src/digest/digest.dart'; +export 'src/domain/domain.dart'; +export 'src/kdf/pbkdf2.dart'; +export 'src/keys/secret_key.dart'; +export 'src/random/secure_random.dart'; diff --git a/packages/native_crypto/lib/native_crypto_ext.dart b/packages/native_crypto/lib/native_crypto_ext.dart deleted file mode 100644 index 0e9502e..0000000 --- a/packages/native_crypto/lib/native_crypto_ext.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: native_crypto_ext.dart -// Created Date: 26/05/2022 19:36:54 -// Last Modified: 26/05/2022 19:38:44 -// ----- -// Copyright (c) 2022 - -export 'src/utils/encoding.dart'; -export 'src/utils/extensions.dart'; diff --git a/packages/native_crypto/lib/src/builders/builders.dart b/packages/native_crypto/lib/src/builders/builders.dart index d846197..df13d09 100644 --- a/packages/native_crypto/lib/src/builders/builders.dart +++ b/packages/native_crypto/lib/src/builders/builders.dart @@ -1,10 +1,8 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: builders.dart -// Created Date: 23/05/2022 22:56:03 -// Last Modified: 26/05/2022 19:22:19 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. export 'decryption_builder.dart'; +export 'encryption_builder.dart'; diff --git a/packages/native_crypto/lib/src/builders/decryption_builder.dart b/packages/native_crypto/lib/src/builders/decryption_builder.dart index 998fdcf..c9d6dbe 100644 --- a/packages/native_crypto/lib/src/builders/decryption_builder.dart +++ b/packages/native_crypto/lib/src/builders/decryption_builder.dart @@ -1,46 +1,60 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: decryption_builder.dart -// Created Date: 26/05/2022 19:07:52 -// Last Modified: 26/05/2022 19:21:00 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. import 'dart:typed_data'; import 'package:flutter/material.dart'; -import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; -import 'package:native_crypto/src/interfaces/cipher.dart'; +import 'package:native_crypto/src/core/utils/cipher_text.dart'; -class DecryptionBuilder extends StatelessWidget { - final Cipher cipher; - final CipherTextWrapper data; - final Widget Function(BuildContext context) onLoading; - final Widget Function(BuildContext context, Object error) onError; - final Widget Function(BuildContext context, Uint8List plainText) onSuccess; +import 'package:native_crypto/src/domain/cipher.dart'; +import 'package:native_crypto/src/domain/cipher_chunk.dart'; +/// {@template decryption_builder} +/// A [StatelessWidget] that builds a [FutureBuilder] that will decrypt a +/// [CipherText] using a [Cipher]. +/// {@endtemplate} +class DecryptionBuilder extends StatelessWidget { + /// {@macro decryption_builder} const DecryptionBuilder({ - super.key, required this.cipher, - required this.data, + required this.cipherText, required this.onLoading, required this.onError, required this.onSuccess, + super.key, }); + /// The [Cipher] that will be used to decrypt the [CipherText]. + final Cipher cipher; + + /// The [CipherText] that will be decrypted. + final CipherText cipherText; + + /// The [Widget] that will be displayed while the [CipherText] is being + /// decrypted. + final Widget Function(BuildContext context) onLoading; + + /// The [Widget] that will be displayed if an error occurs while decrypting + /// the [CipherText]. + final Widget Function(BuildContext context, Object error) onError; + + /// The [Widget] that will be displayed once the [CipherText] has been + /// decrypted. + final Widget Function(BuildContext context, Uint8List plainText) onSuccess; + @override - Widget build(BuildContext context) { - return FutureBuilder( - future: cipher.decrypt(data), - builder: (context, snapshot) { - if (snapshot.hasData) { - return onSuccess(context, snapshot.data!); - } else if (snapshot.hasError) { - return onError(context, snapshot.error!); - } - return onLoading(context); - }, - ); - } + Widget build(BuildContext context) => FutureBuilder( + future: cipher.decrypt(cipherText), + builder: (context, snapshot) { + if (snapshot.hasData) { + return onSuccess(context, snapshot.data!); + } else if (snapshot.hasError) { + return onError(context, snapshot.error!); + } + return onLoading(context); + }, + ); } diff --git a/packages/native_crypto/lib/src/builders/encryption_builder.dart b/packages/native_crypto/lib/src/builders/encryption_builder.dart new file mode 100644 index 0000000..5782730 --- /dev/null +++ b/packages/native_crypto/lib/src/builders/encryption_builder.dart @@ -0,0 +1,61 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:native_crypto/src/core/utils/cipher_text.dart'; + +import 'package:native_crypto/src/domain/cipher.dart'; +import 'package:native_crypto/src/domain/cipher_chunk.dart'; + +/// {@template encryption_builder} +/// A [StatelessWidget] that builds a [FutureBuilder] that will +/// encrypt a [Uint8List] using a [Cipher]. +/// {@endtemplate} +class EncryptionBuilder extends StatelessWidget { + /// {@macro encryption_builder} + const EncryptionBuilder({ + required this.cipher, + required this.plainText, + required this.onLoading, + required this.onError, + required this.onSuccess, + super.key, + }); + + /// The [Cipher] that will be used to encrypt the [Uint8List]. + final Cipher cipher; + + /// The [Uint8List] that will be encrypted. + final Uint8List plainText; + + /// The [Widget] that will be displayed while the [Uint8List] is being + /// encrypted. + final Widget Function(BuildContext context) onLoading; + + /// The [Widget] that will be displayed if an error occurs while encrypting + /// the [Uint8List]. + final Widget Function(BuildContext context, Object error) onError; + + /// The [Widget] that will be displayed once the [Uint8List] has been + /// encrypted. + final Widget Function(BuildContext context, CipherText cipherText) + onSuccess; + + @override + Widget build(BuildContext context) => FutureBuilder>( + future: cipher.encrypt(plainText), + builder: (context, snapshot) { + if (snapshot.hasData) { + return onSuccess(context, snapshot.data!); + } else if (snapshot.hasError) { + return onError(context, snapshot.error!); + } + return onLoading(context); + }, + ); +} diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes.dart b/packages/native_crypto/lib/src/ciphers/aes/aes.dart index b1113fc..9d995fe 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes.dart @@ -1,181 +1,293 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 27/05/2022 12:13:28 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +import 'dart:io'; import 'dart:typed_data'; +import 'package:native_crypto/src/ciphers/aes/aes_cipher_chunk.dart'; import 'package:native_crypto/src/ciphers/aes/aes_key_size.dart'; 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_wrapper.dart'; -import 'package:native_crypto/src/interfaces/cipher.dart'; +import 'package:native_crypto/src/core/constants/constants.dart'; +import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; +import 'package:native_crypto/src/core/utils/cipher_text.dart'; +import 'package:native_crypto/src/core/utils/platform.dart'; +import 'package:native_crypto/src/domain/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/src/utils/extensions.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; +export 'aes_cipher_chunk.dart'; export 'aes_key_size.dart'; export 'aes_mode.dart'; export 'aes_padding.dart'; -/// An AES cipher. +/// {@template aes} +/// AES cipher. /// -/// [AES] is a [Cipher] that can be used to encrypt or decrypt data. -class AES implements Cipher { - final SecretKey _key; +/// [AES] is a symmetric cipher which means that the same key is used to encrypt +/// and decrypt the data. +/// {@endtemplate} +class AES implements Cipher { + const AES({ + required this.key, + required this.mode, + required this.padding, + this.chunkSize = Constants.defaultChunkSize, + }); + + static const String _algorithm = 'aes'; + + /// The key used to encrypt and decrypt the data. + final SecretKey key; + + /// The [AESMode] used by this [AES]. final AESMode mode; + + /// The [AESPadding] used by this [AES]. final AESPadding padding; - @override - CipherAlgorithm get algorithm => CipherAlgorithm.aes; + /// The size of the cipher text chunks. + final int chunkSize; - AES(SecretKey key, [this.mode = AESMode.gcm, this.padding = AESPadding.none]) - : _key = key { - if (!AESKeySize.supportedSizes.contains(key.bitLength)) { - throw NativeCryptoException( - message: 'Invalid key size! ' - 'Expected: ${AESKeySize.supportedSizes.join(', ')} bits', - code: NativeCryptoExceptionCode.invalid_key_length.code, + @override + Future decrypt(CipherText cipherText) async { + final BytesBuilder plainText = BytesBuilder(copy: false); + final chunks = cipherText.chunks; + + int i = 0; + for (final chunk in chunks) { + plainText.add(await _decryptChunk(chunk.bytes, count: i++)); + } + + return plainText.toBytes(); + } + + @override + Future decryptFile(File cipherTextFile, File plainTextFile) { + if (!cipherTextFile.existsSync()) { + throw ArgumentError.value( + cipherTextFile.path, + 'cipherTextFile.path', + 'File does not exist!', + ); + } + + if (plainTextFile.existsSync()) { + throw ArgumentError.value( + plainTextFile.path, + 'plainTextFile.path', + 'File already exists!', + ); + } + + return platform.decryptFile( + cipherTextPath: cipherTextFile.path, + plainTextPath: plainTextFile.path, + key: key.bytes, + algorithm: _algorithm, + ); + } + + @override + Future> encrypt(Uint8List plainText) async { + final chunks = []; + final chunkedPlainText = plainText.chunked(chunkSize); + + int i = 0; + for (final plainTextChunk in chunkedPlainText) { + final bytes = await _encryptChunk(plainTextChunk, count: i++); + chunks.add( + AESCipherChunk( + bytes, + ivLength: mode.ivLength, + tagLength: mode.tagLength, + ), + ); + } + + return CipherText.fromChunks( + chunks, + chunkFactory: (bytes) => AESCipherChunk( + bytes, + ivLength: mode.ivLength, + tagLength: mode.tagLength, + ), + chunkSize: chunkSize, + ); + } + + @override + Future encryptFile(File plainTextFile, File cipherTextFile) { + if (!plainTextFile.existsSync()) { + throw ArgumentError.value( + plainTextFile.path, + 'plainTextFile.path', + 'File does not exist!', + ); + } + + if (cipherTextFile.existsSync()) { + throw ArgumentError.value( + cipherTextFile.path, + 'cipherTextFile.path', + 'File already exists!', + ); + } + + return platform.encryptFile( + plainTextPath: plainTextFile.path, + cipherTextPath: cipherTextFile.path, + key: key.bytes, + algorithm: _algorithm, + ); + } + + /// Encrypts the [plainText] with the [iv] chosen by the Flutter side. + /// + /// Prefer using [encrypt] instead which will generate a + /// random [iv] on the native side. + /// + /// Note: this method doesn't chunk the data. It can lead to memory issues + /// if the [plainText] is too big. Use [encrypt] instead. + Future encryptWithIV( + Uint8List plainText, + Uint8List iv, + ) async { + // Check if the cipher is correctly initialized + _isCorrectlyInitialized(); + + if (iv.length != mode.ivLength) { + throw ArgumentError.value( + iv.length, + 'iv.length', + 'Invalid iv length! ' + 'Expected: ${mode.ivLength}', + ); + } + + final bytes = await platform.encryptWithIV( + plainText: plainText, + iv: iv, + key: key.bytes, + algorithm: _algorithm, + ); + + // TODO(hpcl): move these checks to the platform interface + if (bytes == null) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes', + ); + } + + if (bytes.isEmpty) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.invalidData, + message: 'Platform returned no data', + ); + } + + return AESCipherChunk( + bytes, + ivLength: mode.ivLength, + tagLength: mode.tagLength, + ); + } + + /// Ensures that the cipher is correctly initialized. + bool _isCorrectlyInitialized() { + final keySize = key.length * 8; + if (!AESKeySize.supportedSizes.contains(keySize)) { + throw ArgumentError.value( + keySize, + 'keySize', + 'Invalid key size! ' + 'Expected: ${AESKeySize.supportedSizes.join(', ')}', ); } if (!mode.supportedPaddings.contains(padding)) { - throw NativeCryptoException( - message: 'Invalid padding! ' + throw ArgumentError.value( + padding, + 'padding', + 'Invalid padding! ' 'Expected: ${mode.supportedPaddings.join(', ')}', - code: NativeCryptoExceptionCode.invalid_padding.code, ); } + + return true; } - Future _decrypt( - CipherText cipherText, { - int chunkCount = 0, + /// Encrypts the plain text chunk. + Future _encryptChunk(Uint8List plainChunk, {int count = 0}) async { + // Check if the cipher is correctly initialized + _isCorrectlyInitialized(); + + Uint8List? bytes; + + try { + bytes = await platform.encrypt( + plainChunk, + key: key.bytes, + algorithm: _algorithm, + ); + } on NativeCryptoException catch (e) { + throw e.copyWith( + message: 'Failed to encrypt chunk #$count: ${e.message}', + ); + } + + // TODO(hpcl): move these checks to the platform interface + if (bytes == null) { + throw NativeCryptoException( + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes on chunk #$count', + ); + } + + if (bytes.isEmpty) { + throw NativeCryptoException( + code: NativeCryptoExceptionCode.invalidData, + message: 'Platform returned no data on chunk #$count', + ); + } + + return bytes; + } + + /// Decrypts the cipher text chunk. + Future _decryptChunk( + Uint8List cipherChunk, { + int count = 0, }) async { - Uint8List? decrypted; + // Check if the cipher is correctly initialized + _isCorrectlyInitialized(); + + Uint8List? bytes; try { - decrypted = await platform.decrypt( - cipherText.bytes, - _key.bytes, - algorithm.name, + bytes = await platform.decrypt( + cipherChunk, + key: key.bytes, + algorithm: _algorithm, ); - } catch (e, s) { + } on NativeCryptoException catch (e) { + throw e.copyWith( + message: 'Failed to decrypt chunk #$count: ${e.message}', + ); + } + + // TODO(hpcl): move these checks to the platform interface + if (bytes == null) { throw NativeCryptoException( - message: '$e', - code: NativeCryptoExceptionCode.platform_throws.code, - stackTrace: s, + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes on chunk #$count', ); } - if (decrypted.isNull) { - throw NativeCryptoException( - message: 'Platform returned null when decrypting chunk #$chunkCount', - code: NativeCryptoExceptionCode.platform_returned_null.code, - ); - } else if (decrypted!.isEmpty) { - throw NativeCryptoException( - message: 'Platform returned no data when decrypting chunk #$chunkCount', - code: NativeCryptoExceptionCode.platform_returned_empty_data.code, - ); - } else { - return decrypted; - } - } - - Future _encrypt(Uint8List data, {int chunkCount = 0}) async { - Uint8List? encrypted; - - try { - encrypted = await platform.encrypt( - data, - _key.bytes, - algorithm.name, - ); - } catch (e, s) { - throw NativeCryptoException( - message: '$e on chunk #$chunkCount', - code: NativeCryptoExceptionCode.platform_throws.code, - stackTrace: s, - ); - } - - if (encrypted.isNull) { - throw NativeCryptoException( - message: 'Platform returned null when encrypting chunk #$chunkCount', - code: NativeCryptoExceptionCode.platform_returned_null.code, - ); - } else if (encrypted!.isEmpty) { - throw NativeCryptoException( - message: 'Platform returned no data when encrypting chunk #$chunkCount', - code: NativeCryptoExceptionCode.platform_returned_empty_data.code, - ); - } else { - try { - return CipherText.fromBytes( - encrypted, - ivLength: 12, - messageLength: encrypted.length - 28, - tagLength: 16, - cipherAlgorithm: CipherAlgorithm.aes, - ); - } on NativeCryptoException catch (e, s) { - throw NativeCryptoException( - message: '${e.message} on chunk #$chunkCount', - code: e.code, - stackTrace: s, - ); - } - } - } - - @override - Future decrypt(CipherTextWrapper cipherText) async { - final BytesBuilder decryptedData = BytesBuilder(copy: false); - - if (cipherText.isList) { - int chunkCount = 0; - for (final CipherText chunk in cipherText.list) { - decryptedData.add(await _decrypt(chunk, chunkCount: chunkCount++)); - } - } else { - decryptedData.add(await _decrypt(cipherText.single)); - } - - return decryptedData.toBytes(); - } - - @override - Future encrypt(Uint8List data) async { - if (data.isEmpty) { - return CipherTextWrapper.empty(); - } - CipherTextWrapper cipherTextWrapper; - Uint8List dataToEncrypt; - final int chunkNb = (data.length / Cipher.bytesCountPerChunk).ceil(); - - if (chunkNb > 1) { - cipherTextWrapper = CipherTextWrapper.empty(); - for (var i = 0; i < chunkNb; i++) { - dataToEncrypt = i < (chunkNb - 1) - ? data.sublist( - i * Cipher.bytesCountPerChunk, - (i + 1) * Cipher.bytesCountPerChunk, - ) - : data.sublist(i * Cipher.bytesCountPerChunk); - cipherTextWrapper.add(await _encrypt(dataToEncrypt, chunkCount: i)); - } - } else { - cipherTextWrapper = CipherTextWrapper.single(await _encrypt(data)); - } - - return cipherTextWrapper; + return bytes; } } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_cipher_chunk.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_cipher_chunk.dart new file mode 100644 index 0000000..7f8bb45 --- /dev/null +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_cipher_chunk.dart @@ -0,0 +1,67 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/domain/cipher_chunk.dart'; + +class AESCipherChunk extends CipherChunk { + const AESCipherChunk( + super.bytes, { + required this.ivLength, + required this.tagLength, + }); + + /// Creates a [AESCipherChunk] from a [List]. + AESCipherChunk.fromList( + super.list, { + required this.ivLength, + required this.tagLength, + }) : super.fromList(); + + /// Creates a [AESCipherChunk] from a [String] encoded in UTF-8. + AESCipherChunk.fromUtf8( + super.encoded, { + required this.ivLength, + required this.tagLength, + }) : super.fromUtf8(); + + /// Creates a [AESCipherChunk] from a [String] encoded in UTF-16. + AESCipherChunk.fromUtf16( + super.encoded, { + required this.ivLength, + required this.tagLength, + }) : super.fromUtf16(); + + /// Creates a [AESCipherChunk] from a [String] encoded in Hexadecimal. + AESCipherChunk.fromBase16( + super.encoded, { + required this.ivLength, + required this.tagLength, + }) : super.fromBase16(); + + /// Creates a [AESCipherChunk] from a [String] encoded in Base64. + AESCipherChunk.fromBase64( + super.encoded, { + required this.ivLength, + required this.tagLength, + }) : super.fromBase64(); + + /// Intialization vector length. + final int ivLength; + + /// Tag length. + final int tagLength; + + /// Returns the initialization vector, or nonce of the [AESCipherChunk]. + Uint8List get iv => bytes.sublist(0, ivLength); + + /// Returns the tag of the [AESCipherChunk]. + Uint8List get tag => bytes.sublist(bytes.length - tagLength, bytes.length); + + /// Returns the message of the [AESCipherChunk]. + Uint8List get message => bytes.sublist(ivLength, bytes.length - tagLength); +} diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart index befa22f..72e81d7 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_key_size.dart @@ -1,18 +1,20 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes_key_size.dart -// Created Date: 23/05/2022 22:10:07 -// Last Modified: 26/05/2022 18:45:01 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. -/// Defines all available key sizes. +/// {@template aes_key_size} +/// Defines the key size of an AES cipher. +/// {@endtemplate} enum AESKeySize { bits128(128), bits192(192), bits256(256); + /// {@macro aes_key_size} + const AESKeySize(this.bits); + /// Returns the number of bits supported by an [AESKeySize]. static final List supportedSizes = [128, 192, 256]; @@ -21,6 +23,4 @@ enum AESKeySize { /// Returns the number of bytes in this [AESKeySize]. int get bytes => bits ~/ 8; - - const AESKeySize(this.bits); } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart index 4bbc7c4..9e34304 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_mode.dart @@ -1,17 +1,29 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes_mode.dart -// Created Date: 23/05/2022 22:09:16 -// Last Modified: 26/05/2022 21:03:26 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. import 'package:native_crypto/src/ciphers/aes/aes_padding.dart'; +import 'package:native_crypto/src/core/constants/constants.dart'; +/// {@template aes_mode} /// Defines the AES modes of operation. +/// {@endtemplate} enum AESMode { - gcm([AESPadding.none], 12, 16); + /// GCM mode. + gcm( + [AESPadding.none], + Constants.aesGcmNonceLength, + Constants.aesGcmTagLength, + ); + + /// {@macro aes_mode} + const AESMode( + this.supportedPaddings, [ + this.ivLength = 16, + this.tagLength = 0, + ]); /// Returns the list of supported [AESPadding] for this [AESMode]. final List supportedPaddings; @@ -21,10 +33,4 @@ enum AESMode { /// Returns the default tag length for this [AESMode]. final int tagLength; - - const AESMode( - this.supportedPaddings, [ - this.ivLength = 16, - this.tagLength = 0, - ]); } diff --git a/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart b/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart index 343ae03..0d26a9f 100644 --- a/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart +++ b/packages/native_crypto/lib/src/ciphers/aes/aes_padding.dart @@ -1,11 +1,11 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes_padding.dart -// Created Date: 23/05/2022 22:10:17 -// Last Modified: 25/05/2022 09:23:49 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. -/// Represents different paddings. -enum AESPadding { none } +/// Padding used for AES encryption. +enum AESPadding { + /// No padding. + none, +} diff --git a/packages/native_crypto/lib/src/ciphers/ciphers.dart b/packages/native_crypto/lib/src/ciphers/ciphers.dart index edae6a4..2b87da6 100644 --- a/packages/native_crypto/lib/src/ciphers/ciphers.dart +++ b/packages/native_crypto/lib/src/ciphers/ciphers.dart @@ -1,10 +1,7 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: ciphers.dart -// Created Date: 23/05/2022 22:56:30 -// Last Modified: 23/05/2022 22:56:47 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. export 'aes/aes.dart'; diff --git a/packages/native_crypto/lib/src/core/cipher_text.dart b/packages/native_crypto/lib/src/core/cipher_text.dart deleted file mode 100644 index 6accd59..0000000 --- a/packages/native_crypto/lib/src/core/cipher_text.dart +++ /dev/null @@ -1,117 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher_text.dart -// Created Date: 16/12/2021 16:59:53 -// Last Modified: 27/05/2022 12:09:47 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; -import 'package:native_crypto/src/interfaces/byte_array.dart'; -import 'package:native_crypto/src/interfaces/cipher.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'; - -/// Represents a cipher text in NativeCrypto. -/// -/// [CipherText] is a [ByteArray] that can be used to store encrypted data. -/// It is represented like: -/// ```txt -/// [IV + MESSAGE + TAG] -/// ``` -/// where: -/// - IV's length is [CipherText.ivLength] bytes. -/// - MESSAGE's length is [CipherText.messageLength] bytes. -/// - TAG's length is [CipherText.tagLength] bytes. -/// -/// Check [CipherTextWrapper] for more information. -class CipherText extends ByteArray { - final int _ivLength; - final int _messageLength; - final int _tagLength; - - final CipherAlgorithm? _cipherAlgorithm; - - const CipherText._( - this._ivLength, - this._messageLength, - this._tagLength, - this._cipherAlgorithm, - super.bytes, - ); - - factory CipherText.fromBytes( - Uint8List bytes, { - required int ivLength, - required int tagLength, - int? messageLength, - CipherAlgorithm? cipherAlgorithm, - }) { - messageLength ??= bytes.length - ivLength - tagLength; - - if (ivLength.isNegative || - messageLength.isNegative || - tagLength.isNegative) { - throw NativeCryptoException( - message: 'Invalid length! Must be positive.', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); - } - - if (bytes.isEmpty) { - throw NativeCryptoException( - message: 'Passed data is empty!', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); - } - - if (bytes.length != ivLength + messageLength + tagLength) { - throw NativeCryptoException( - message: 'Invalid cipher text length! ' - 'Expected: ${ivLength + messageLength + tagLength} bytes ' - 'got: ${bytes.length} bytes.', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); - } - - if (messageLength > Cipher.bytesCountPerChunk) { - throw NativeCryptoException( - message: 'Cipher text is too big! Consider using chunks.', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); - } - - return CipherText._( - ivLength, - messageLength, - tagLength, - cipherAlgorithm, - bytes, - ); - } - - /// 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, - ); - } - } - - /// Gets the length of the [CipherText]'s IV. - int get ivLength => _ivLength; - - /// Gets the length of the [CipherText]'s Message. - int get messageLength => _messageLength; - - /// Gets the length of the [CipherText]'s Tag. - 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 deleted file mode 100644 index 75a9dae..0000000 --- a/packages/native_crypto/lib/src/core/cipher_text_wrapper.dart +++ /dev/null @@ -1,191 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher_text_wrapper.dart -// Created Date: 26/05/2022 14:27:32 -// Last Modified: 27/05/2022 13:43:29 -// ----- -// Copyright (c) 2022 - -import 'dart:typed_data'; - -import 'package:native_crypto/native_crypto.dart'; -import 'package:native_crypto/src/utils/extensions.dart'; - -/// Wrapper for [CipherText] -/// -/// Typically, this object is the result of an encryption operation. -/// For decryption you have to build this before using it. -class CipherTextWrapper { - final CipherText? _single; - final List? _list; - - CipherTextWrapper._(this._single, this._list); - - /// Creates a [CipherTextWrapper] from a [CipherText]. - factory CipherTextWrapper.single(CipherText cipherText) => - CipherTextWrapper._(cipherText, null); - - /// Creates a [CipherTextWrapper] from a [List] of [CipherText]. - factory CipherTextWrapper.list(List cipherTexts) => - CipherTextWrapper._(null, cipherTexts); - - /// Creates an empty [List] in a [CipherTextWrapper]. - /// - /// This is useful when you want to create a [CipherTextWrapper] then - /// fill it with data. - factory CipherTextWrapper.empty() => CipherTextWrapper._(null, []); - - /// Creates a [CipherTextWrapper] from a [Uint8List]. - /// - /// This is a convenience method to create a [CipherTextWrapper] - /// from a [Uint8List]. It tries to detect if the [Uint8List] is a - /// single [CipherText] or a list of [CipherText]. - /// - /// You can customize the chunk size by passing a [chunkSize] parameter. - /// The default chunk size is [Cipher.bytesCountPerChunk]. - /// - /// Throw an [NativeCryptoExceptionCode] with - /// [NativeCryptoExceptionCode.invalid_argument] if the [Uint8List] is - /// not a valid [CipherText] or a [List] of [CipherText]. - factory CipherTextWrapper.fromBytes( - Uint8List bytes, { - required int ivLength, - required int tagLength, - CipherAlgorithm? cipherAlgorithm, - int? chunkSize, - }) { - chunkSize ??= Cipher.bytesCountPerChunk; - Cipher.bytesCountPerChunk = chunkSize; - - final int messageLength = bytes.length - ivLength - tagLength; - - if (messageLength <= chunkSize) { - return CipherTextWrapper.single( - CipherText.fromBytes( - bytes, - ivLength: ivLength, - tagLength: tagLength, - cipherAlgorithm: cipherAlgorithm, - ), - ); - } else { - final cipherTexts = []; - for (var i = 0; i < bytes.length; i += chunkSize + ivLength + tagLength) { - final chunk = bytes.trySublist(i, i + chunkSize + ivLength + tagLength); - - try { - cipherTexts.add( - CipherText.fromBytes( - chunk, - ivLength: ivLength, - tagLength: tagLength, - cipherAlgorithm: cipherAlgorithm, - ), - ); - } on NativeCryptoException catch (e, s) { - throw NativeCryptoException( - message: '${e.message} on chunk #$i', - code: e.code, - stackTrace: s, - ); - } - } - return CipherTextWrapper.list(cipherTexts); - } - } - - /// Checks if the [CipherText] is a single [CipherText]. - bool get isSingle => _single.isNotNull; - - /// Checks if the [CipherText] is a [List] of [CipherText]. - 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 bytes { - if (isSingle) { - return single.bytes; - } else { - return list.map((cipherText) => cipherText.bytes).toList().combine(); - } - } - - /// Gets the number of parts of the [CipherText] or [List] of [CipherText]. - /// - /// Check [Cipher.bytesCountPerChunk] for more information. - int get chunkCount { - _single.isNull; - if (_single.isNotNull) { - return 1; - } else { - return _list?.length ?? 0; - } - } - - /// Gets the [CipherText] or the [List] of [CipherText]. - /// - /// Throws [NativeCryptoException] with - /// [NativeCryptoExceptionCode.invalid_data] if it's not a single or a list or - /// if [T] is not [CipherText] or [List] of [CipherText]. - T unwrap() { - if (isSingle && T == CipherText) { - return single as T; - } else if (isList && T == List) { - return list as T; - } else { - final String type = - isSingle ? 'CipherText' : (isList ? 'List' : 'unknown'); - throw NativeCryptoException( - message: 'CipherTextWrapper is not a $T but a $type, ' - 'you should use unwrap<$type>()', - code: NativeCryptoExceptionCode.invalid_data.code, - ); - } - } - - void add(CipherText cipherText) { - if (isSingle) { - throw NativeCryptoException( - message: 'CipherTextWrapper is already single', - code: NativeCryptoExceptionCode.invalid_data.code, - ); - } else if (isList) { - _list!.add(cipherText); - } else { - throw NativeCryptoException( - message: 'CipherTextWrapper is not single or list', - code: NativeCryptoExceptionCode.invalid_data.code, - ); - } - } -} diff --git a/packages/native_crypto/lib/src/core/constants/constants.dart b/packages/native_crypto/lib/src/core/constants/constants.dart new file mode 100644 index 0000000..37fe0b5 --- /dev/null +++ b/packages/native_crypto/lib/src/core/constants/constants.dart @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +abstract class Constants { + /// The default chunk size in bytes used for encryption and decryption. + /// + /// ~32MB + static const int defaultChunkSize = 33554432; + + /// The length of the initialization vector in bytes used for AES GCM. + static const int aesGcmNonceLength = 12; + + /// The length of the tag in bytes used for AES GCM. + static const int aesGcmTagLength = 16; +} diff --git a/packages/native_crypto/lib/src/core/core.dart b/packages/native_crypto/lib/src/core/core.dart index 32ad783..7a26b2a 100644 --- a/packages/native_crypto/lib/src/core/core.dart +++ b/packages/native_crypto/lib/src/core/core.dart @@ -1,11 +1,11 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: core.dart -// Created Date: 23/05/2022 23:05:26 -// Last Modified: 26/05/2022 17:10:25 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. -export 'cipher_text.dart'; -export 'cipher_text_wrapper.dart'; +export './constants/constants.dart'; +export './enums/enums.dart'; +export './extensions/extensions.dart'; +export './utils/utils.dart'; +export 'utils/platform.dart'; diff --git a/packages/native_crypto/lib/src/core/enums/encoding.dart b/packages/native_crypto/lib/src/core/enums/encoding.dart new file mode 100644 index 0000000..fb459dd --- /dev/null +++ b/packages/native_crypto/lib/src/core/enums/encoding.dart @@ -0,0 +1,20 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +/// An encoding used to convert a byte array to a string and vice-versa. +enum Encoding { + /// UTF-8 encoding, as defined by the Unicode standard. + utf8, + + /// UTF-16 encoding, as defined by the Unicode standard. + utf16, + + /// Base64 encoding, as defined by RFC 4648. + base64, + + /// Hexadecimal encoding. + base16, +} diff --git a/packages/native_crypto/lib/src/core/enums/enums.dart b/packages/native_crypto/lib/src/core/enums/enums.dart new file mode 100644 index 0000000..bc34b6b --- /dev/null +++ b/packages/native_crypto/lib/src/core/enums/enums.dart @@ -0,0 +1,8 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +export 'encoding.dart'; +export 'hash_algorithm.dart'; diff --git a/packages/native_crypto/lib/src/core/enums/hash_algorithm.dart b/packages/native_crypto/lib/src/core/enums/hash_algorithm.dart new file mode 100644 index 0000000..860f609 --- /dev/null +++ b/packages/native_crypto/lib/src/core/enums/hash_algorithm.dart @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +/// The hash algorithm to use in Message Digest and HMAC. +enum HashAlgorithm { + /// The SHA-256 hash algorithm. + sha256, + + /// The SHA-384 hash algorithm. + sha384, + + /// The SHA-512 hash algorithm. + sha512, +} diff --git a/packages/native_crypto/lib/src/core/extensions/extensions.dart b/packages/native_crypto/lib/src/core/extensions/extensions.dart new file mode 100644 index 0000000..811436f --- /dev/null +++ b/packages/native_crypto/lib/src/core/extensions/extensions.dart @@ -0,0 +1,10 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +export 'list_int_extension.dart'; +export 'list_uint8_list_extension.dart'; +export 'string_extension.dart'; +export 'uint8_list_extension.dart'; diff --git a/packages/native_crypto/lib/src/core/extensions/list_int_extension.dart b/packages/native_crypto/lib/src/core/extensions/list_int_extension.dart new file mode 100644 index 0000000..5ae7252 --- /dev/null +++ b/packages/native_crypto/lib/src/core/extensions/list_int_extension.dart @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +extension ListIntExtension on List { + /// Converts a [List] of int to a [Uint8List]. + Uint8List toTypedList() => Uint8List.fromList(this); +} diff --git a/packages/native_crypto/lib/src/core/extensions/list_uint8_list_extension.dart b/packages/native_crypto/lib/src/core/extensions/list_uint8_list_extension.dart new file mode 100644 index 0000000..78dfe75 --- /dev/null +++ b/packages/native_crypto/lib/src/core/extensions/list_uint8_list_extension.dart @@ -0,0 +1,19 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; + +extension ListUint8ListExtension on List { + /// Reduce a [List] of [Uint8List] to a [Uint8List]. + Uint8List combine() { + if (isEmpty) { + return Uint8List(0); + } + return reduce((value, element) => value | element); + } +} diff --git a/packages/native_crypto/lib/src/core/extensions/string_extension.dart b/packages/native_crypto/lib/src/core/extensions/string_extension.dart new file mode 100644 index 0000000..3a342b7 --- /dev/null +++ b/packages/native_crypto/lib/src/core/extensions/string_extension.dart @@ -0,0 +1,36 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/enums/encoding.dart'; +import 'package:native_crypto/src/core/extensions/list_int_extension.dart'; + +extension StringExtension on String { + /// Converts a [String] to a [Uint8List] using the specified [Encoding]. + Uint8List toBytes({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; + } +} diff --git a/packages/native_crypto/lib/src/core/extensions/uint8_list_extension.dart b/packages/native_crypto/lib/src/core/extensions/uint8_list_extension.dart new file mode 100644 index 0000000..daf870a --- /dev/null +++ b/packages/native_crypto/lib/src/core/extensions/uint8_list_extension.dart @@ -0,0 +1,69 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/enums/encoding.dart'; +import 'package:native_crypto/src/core/extensions/list_int_extension.dart'; + +extension Uint8ListExtension on Uint8List { + /// Returns a concatenation of this with the other [Uint8List]. + Uint8List plus(Uint8List other) => [...this, ...other].toTypedList(); + + /// Returns a concatenation of this with the other + /// [Uint8List] using the `|` operator as a shortcut. + Uint8List operator |(Uint8List other) => plus(other); + + /// Returns a sublist of this from the [start] index to the [end] index. + /// If [end] is greater than the length of the list, it is set to the length. + Uint8List trySublist(int start, [int? end]) { + if (isEmpty) { + return this; + } + + int ending = end ?? length; + if (ending > length) { + ending = length; + } + + return sublist(start, ending); + } + + /// Returns a [List] of [Uint8List] of the specified [chunkSize]. + List chunked(int chunkSize) { + if (isEmpty) { + return []; + } + + return List.generate( + (length / chunkSize).ceil(), + (i) => trySublist(i * chunkSize, (i * chunkSize) + chunkSize), + ); + } + + /// Converts a [Uint8List] to a [String] using the specified [Encoding]. + String toStr({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; + } +} diff --git a/packages/native_crypto/lib/src/core/utils/chunk_factory.dart b/packages/native_crypto/lib/src/core/utils/chunk_factory.dart new file mode 100644 index 0000000..afbc6b4 --- /dev/null +++ b/packages/native_crypto/lib/src/core/utils/chunk_factory.dart @@ -0,0 +1,12 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/domain/cipher_chunk.dart'; + +/// A factory that creates a [CipherChunk] of type [T] from a [Uint8List]. +typedef ChunkFactory = T Function(Uint8List chunk); diff --git a/packages/native_crypto/lib/src/core/utils/cipher_text.dart b/packages/native_crypto/lib/src/core/utils/cipher_text.dart new file mode 100644 index 0000000..d9e6977 --- /dev/null +++ b/packages/native_crypto/lib/src/core/utils/cipher_text.dart @@ -0,0 +1,47 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/constants/constants.dart'; +import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; +import 'package:native_crypto/src/core/utils/chunk_factory.dart'; +import 'package:native_crypto/src/domain/byte_array.dart'; +import 'package:native_crypto/src/domain/cipher_chunk.dart'; + +/// {@template cipher_text} +/// A [CipherText] is a [ByteArray] that is used to store a text encrypted by a +/// Cipher. +/// {@endtemplate} +class CipherText extends ByteArray { + /// {@macro cipher_text} + CipherText( + super.bytes, { + required ChunkFactory this.chunkFactory, + this.chunkSize = Constants.defaultChunkSize, + }) : chunks = bytes.chunked(chunkSize).map(chunkFactory).toList(); + + /// Creates a [CipherText] from a [List] of [CipherChunk]. + CipherText.fromChunks( + this.chunks, { + required ChunkFactory this.chunkFactory, + this.chunkSize = Constants.defaultChunkSize, + }) : super( + chunks.fold( + Uint8List(0), + (acc, chunk) => acc | chunk.bytes, + ), + ); + + /// Factory used to create [CipherChunk] from an Uint8List. + final ChunkFactory? chunkFactory; + + /// List of [CipherChunk] that compose the [CipherText]. + final List chunks; + + /// Size of one chunk. + final int chunkSize; +} diff --git a/packages/native_crypto/lib/src/core/utils/platform.dart b/packages/native_crypto/lib/src/core/utils/platform.dart new file mode 100644 index 0000000..0555237 --- /dev/null +++ b/packages/native_crypto/lib/src/core/utils/platform.dart @@ -0,0 +1,9 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +NativeCryptoPlatform platform = NativeCryptoPlatform.instance; diff --git a/packages/native_crypto/lib/src/core/utils/utils.dart b/packages/native_crypto/lib/src/core/utils/utils.dart new file mode 100644 index 0000000..40b6a15 --- /dev/null +++ b/packages/native_crypto/lib/src/core/utils/utils.dart @@ -0,0 +1,8 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +export 'chunk_factory.dart'; +export 'cipher_text.dart'; diff --git a/packages/native_crypto/lib/src/digest/digest.dart b/packages/native_crypto/lib/src/digest/digest.dart new file mode 100644 index 0000000..d5ab308 --- /dev/null +++ b/packages/native_crypto/lib/src/digest/digest.dart @@ -0,0 +1,8 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +export 'hash.dart'; +export 'hmac.dart'; diff --git a/packages/native_crypto/lib/src/digest/hash.dart b/packages/native_crypto/lib/src/digest/hash.dart new file mode 100644 index 0000000..30f33c7 --- /dev/null +++ b/packages/native_crypto/lib/src/digest/hash.dart @@ -0,0 +1,62 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; +import 'package:native_crypto/src/core/utils/platform.dart'; +import 'package:native_crypto/src/domain/hash.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +class _Hash extends Hash { + const _Hash(this.algorithm); + + @override + Future digest(Uint8List data) async { + final hash = await platform.hash(data, algorithm: algorithm.name); + + // TODO(hpcl): move these checks to the platform interface + if (hash == null) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes', + ); + } + + if (hash.isEmpty) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.invalidData, + message: 'Platform returned no data.', + ); + } + + return hash; + } + + @override + final HashAlgorithm algorithm; +} + +/// A [Hash] that uses the SHA-256 algorithm. +class Sha256 extends _Hash { + factory Sha256() => _instance ??= Sha256._(); + Sha256._() : super(HashAlgorithm.sha256); + static Sha256? _instance; +} + +/// A [Hash] that uses the SHA-384 algorithm. +class Sha384 extends _Hash { + factory Sha384() => _instance ??= Sha384._(); + Sha384._() : super(HashAlgorithm.sha384); + static Sha384? _instance; +} + +/// A [Hash] that uses the SHA-512 algorithm. +class Sha512 extends _Hash { + factory Sha512() => _instance ??= Sha512._(); + Sha512._() : super(HashAlgorithm.sha512); + static Sha512? _instance; +} diff --git a/packages/native_crypto/lib/src/digest/hmac.dart b/packages/native_crypto/lib/src/digest/hmac.dart new file mode 100644 index 0000000..69419b0 --- /dev/null +++ b/packages/native_crypto/lib/src/digest/hmac.dart @@ -0,0 +1,67 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; +import 'package:native_crypto/src/core/utils/platform.dart'; +import 'package:native_crypto/src/domain/hmac.dart'; +import 'package:native_crypto/src/keys/secret_key.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +class _Hmac extends Hmac { + const _Hmac(this.algorithm); + + @override + Future digest(Uint8List data, SecretKey key) async { + final hmac = await platform.hmac( + data, + key: key.bytes, + algorithm: algorithm.name, + ); + + // TODO(hpcl): move these checks to the platform interface + if (hmac == null) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes', + ); + } + + if (hmac.isEmpty) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.invalidData, + message: 'Platform returned no data.', + ); + } + + return hmac; + } + + @override + final HashAlgorithm algorithm; +} + +/// A [Hmac] that uses the SHA-256 algorithm. +class HmacSha256 extends _Hmac { + factory HmacSha256() => _instance ??= HmacSha256._(); + HmacSha256._() : super(HashAlgorithm.sha256); + static HmacSha256? _instance; +} + +/// A [Hmac] that uses the SHA-384 algorithm. +class HmacSha384 extends _Hmac { + factory HmacSha384() => _instance ??= HmacSha384._(); + HmacSha384._() : super(HashAlgorithm.sha384); + static HmacSha384? _instance; +} + +/// A [Hmac] that uses the SHA-512 algorithm. +class HmacSha512 extends _Hmac { + factory HmacSha512() => _instance ??= HmacSha512._(); + HmacSha512._() : super(HashAlgorithm.sha512); + static HmacSha512? _instance; +} diff --git a/packages/native_crypto/lib/src/domain/base_key.dart b/packages/native_crypto/lib/src/domain/base_key.dart new file mode 100644 index 0000000..be6e135 --- /dev/null +++ b/packages/native_crypto/lib/src/domain/base_key.dart @@ -0,0 +1,37 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto/src/domain/byte_array.dart'; + +/// {@template base_key} +/// Represents a key in NativeCrypto. +/// +/// [BaseKey] is a [ByteArray] that can be used to store keys. +/// +/// This interface is implemented by all the key classes. +/// +/// Note: [BaseKey] is named [BaseKey] instead of Key to avoid conflicts with +/// the Key class from Flutter. +/// {@endtemplate} +abstract class BaseKey extends ByteArray { + /// {@macro base_key} + const BaseKey(super.bytes); + + /// Creates a [BaseKey] from a [List]. + BaseKey.fromList(super.list) : super.fromList(); + + /// Creates a [BaseKey] from a [String] encoded in UTF-8. + BaseKey.fromUtf8(super.encoded) : super.fromUtf8(); + + /// Creates a [BaseKey] from a [String] encoded in UTF-16. + BaseKey.fromUtf16(super.encoded) : super.fromUtf16(); + + /// Creates a [BaseKey] from a [String] encoded in Hexadecimal. + BaseKey.fromBase16(super.encoded) : super.fromBase16(); + + /// Creates a [BaseKey] from a [String] encoded in Base64. + BaseKey.fromBase64(super.encoded) : super.fromBase64(); +} diff --git a/packages/native_crypto/lib/src/interfaces/byte_array.dart b/packages/native_crypto/lib/src/domain/byte_array.dart similarity index 61% rename from packages/native_crypto/lib/src/interfaces/byte_array.dart rename to packages/native_crypto/lib/src/domain/byte_array.dart index 7cdef3a..46bae1c 100644 --- a/packages/native_crypto/lib/src/interfaces/byte_array.dart +++ b/packages/native_crypto/lib/src/domain/byte_array.dart @@ -1,48 +1,49 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: byte_array.dart -// Created Date: 16/12/2021 17:54:16 -// Last Modified: 26/05/2022 17:13:27 -// ----- -// Copyright (c) 2021 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. import 'dart:typed_data'; -import 'package:flutter/foundation.dart'; -import 'package:native_crypto/src/utils/encoding.dart'; -import 'package:native_crypto/src/utils/extensions.dart'; +import 'package:equatable/equatable.dart'; +import 'package:native_crypto/src/core/enums/encoding.dart'; +import 'package:native_crypto/src/core/extensions/list_int_extension.dart'; +import 'package:native_crypto/src/core/extensions/string_extension.dart'; +import 'package:native_crypto/src/core/extensions/uint8_list_extension.dart'; +/// {@template byte_array} /// Represents a byte array. /// /// [ByteArray] wraps a [Uint8List] and provides some useful conversion methods. -@immutable -abstract class ByteArray { - final Uint8List _bytes; - - /// Creates a [ByteArray] from a [Uint8List]. +/// {@endtemplate} +abstract class ByteArray extends Equatable { + /// {@macro byte_array} const ByteArray(this._bytes); - /// Creates a [ByteArray] object from a hexdecimal string. - ByteArray.fromBase16(String encoded) - : _bytes = encoded.toBytes(from: Encoding.base16); + /// Creates a [ByteArray] object from a [List] of [int]. + ByteArray.fromList(List list) : _bytes = list.toTypedList(); - /// Creates a [ByteArray] object from a Base64 string. - ByteArray.fromBase64(String encoded) - : _bytes = encoded.toBytes(from: Encoding.base64); + /// Creates an empty [ByteArray] object from a length. + ByteArray.fromLength(int length, {int fill = 0}) + : _bytes = Uint8List(length)..fillRange(0, length, fill); + + /// Creates a [ByteArray] object from an UTF-16 string. + ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes(); /// Creates a [ByteArray] object from an UTF-8 string. ByteArray.fromUtf8(String encoded) : _bytes = encoded.toBytes(from: Encoding.utf8); - /// Creates a [ByteArray] object from an UTF-16 string. - ByteArray.fromUtf16(String encoded) : _bytes = encoded.toBytes(); + /// Creates a [ByteArray] object from a Base64 string. + ByteArray.fromBase64(String encoded) + : _bytes = encoded.toBytes(from: Encoding.base64); - /// Creates an empty [ByteArray] object from a length. - ByteArray.fromLength(int length) : _bytes = Uint8List(length); + /// Creates a [ByteArray] object from a hexdecimal string. + ByteArray.fromBase16(String encoded) + : _bytes = encoded.toBytes(from: Encoding.base16); - /// Creates a [ByteArray] object from a [List] of [int]. - ByteArray.fromList(List list) : _bytes = list.toTypedList(); + final Uint8List _bytes; /// Gets the [ByteArray] bytes. Uint8List get bytes => _bytes; @@ -62,31 +63,6 @@ abstract class ByteArray { /// Gets the [ByteArray] length in bytes. int get length => _bytes.length; - /// Gets the [ByteArray] length in bits. - int get bitLength => _bytes.length * 8; - @override - bool operator ==(Object other) { - if (other is ByteArray) { - for (int i = 0; i < _bytes.length; i++) { - if (_bytes[i] != other._bytes[i]) { - return false; - } - } - - return true; - } - - return false; - } - - @override - int get hashCode { - int hash = 0; - for (int i = 0; i < _bytes.length; i++) { - hash = _bytes[i] + (hash << 6) + (hash << 16) - hash; - } - - return hash; - } + List get props => [_bytes]; } diff --git a/packages/native_crypto/lib/src/domain/cipher.dart b/packages/native_crypto/lib/src/domain/cipher.dart new file mode 100644 index 0000000..ee9a475 --- /dev/null +++ b/packages/native_crypto/lib/src/domain/cipher.dart @@ -0,0 +1,33 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/utils/cipher_text.dart'; +import 'package:native_crypto/src/domain/cipher_chunk.dart'; + +/// {@template cipher} +/// Abstract class that defines the behavior of a Cipher. +/// {@endtemplate} +abstract class Cipher { + /// {@macro cipher} + const Cipher(); + + /// Encrypts a [Uint8List] and returns a [CipherText]. + Future> encrypt(Uint8List plainText); + + /// Decrypts a [CipherText] and returns a [Uint8List]. + Future decrypt(CipherText cipherText); + + /// Encrypts a File located at [plainTextFile] and saves the result + /// at [cipherTextFile]. + Future encryptFile(File plainTextFile, File cipherTextFile); + + /// Decrypts a File located at [cipherTextFile] and saves the result + /// at [plainTextFile]. + Future decryptFile(File cipherTextFile, File plainTextFile); +} diff --git a/packages/native_crypto/lib/src/domain/cipher_chunk.dart b/packages/native_crypto/lib/src/domain/cipher_chunk.dart new file mode 100644 index 0000000..2af6666 --- /dev/null +++ b/packages/native_crypto/lib/src/domain/cipher_chunk.dart @@ -0,0 +1,31 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto/src/domain/byte_array.dart'; + +/// {@template cipher_chunk} +/// A [CipherChunk] is a [ByteArray] that is used to store a chunk of data +/// encrypted by a Cipher. +/// {@endtemplate} +abstract class CipherChunk extends ByteArray { + /// {@macro cipher_chunk} + const CipherChunk(super.bytes); + + /// Creates a [CipherChunk] from a [List]. + CipherChunk.fromList(super.list) : super.fromList(); + + /// Creates a [CipherChunk] from a [String] encoded in UTF-8. + CipherChunk.fromUtf8(super.encoded) : super.fromUtf8(); + + /// Creates a [CipherChunk] from a [String] encoded in UTF-16. + CipherChunk.fromUtf16(super.encoded) : super.fromUtf16(); + + /// Creates a [CipherChunk] from a [String] encoded in Hexadecimal. + CipherChunk.fromBase16(super.encoded) : super.fromBase16(); + + /// Creates a [CipherChunk] from a [String] encoded in Base64. + CipherChunk.fromBase64(super.encoded) : super.fromBase64(); +} diff --git a/packages/native_crypto/lib/src/domain/domain.dart b/packages/native_crypto/lib/src/domain/domain.dart new file mode 100644 index 0000000..a22b2cc --- /dev/null +++ b/packages/native_crypto/lib/src/domain/domain.dart @@ -0,0 +1,14 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +export 'base_key.dart'; +export 'byte_array.dart'; +export 'cipher.dart'; +export 'cipher_chunk.dart'; +export 'hash.dart'; +export 'hmac.dart'; +export 'key_derivation_function.dart'; +export 'random.dart'; diff --git a/packages/native_crypto/lib/src/domain/hash.dart b/packages/native_crypto/lib/src/domain/hash.dart new file mode 100644 index 0000000..f1e181a --- /dev/null +++ b/packages/native_crypto/lib/src/domain/hash.dart @@ -0,0 +1,24 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; + +/// {@template hash} +/// A [Hash] is a one-way function that takes arbitrary-sized data and +/// outputs a fixed-sized hash value. +/// {@endtemplate} +abstract class Hash { + /// {@macro hash} + const Hash(); + + /// The [HashAlgorithm] used by this [Hash]. + HashAlgorithm get algorithm; + + /// Digests the given [data] and returns the hash. + Future digest(Uint8List data); +} diff --git a/packages/native_crypto/lib/src/domain/hmac.dart b/packages/native_crypto/lib/src/domain/hmac.dart new file mode 100644 index 0000000..45b1923 --- /dev/null +++ b/packages/native_crypto/lib/src/domain/hmac.dart @@ -0,0 +1,25 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/enums/hash_algorithm.dart'; +import 'package:native_crypto/src/keys/secret_key.dart'; + +/// {@template hmac} +/// A HMAC is a cryptographic hash that uses a key to sign a message. +/// The receiver verifies the hash by recomputing it using the same key. +/// {@endtemplate} +abstract class Hmac { + /// {@macro hmac} + const Hmac(); + + /// The [HashAlgorithm] used by this [Hmac]. + HashAlgorithm get algorithm; + + /// Digests the given [data] and returns the hmac. + Future digest(Uint8List data, SecretKey key); +} diff --git a/packages/native_crypto/lib/src/domain/key_derivation_function.dart b/packages/native_crypto/lib/src/domain/key_derivation_function.dart new file mode 100644 index 0000000..d337a6b --- /dev/null +++ b/packages/native_crypto/lib/src/domain/key_derivation_function.dart @@ -0,0 +1,27 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +/// {@template key_derivation_function} +/// A [KeyDerivationFunction] is a function that derives a key from an +/// [Uint8List] key material. +/// {@endtemplate} +abstract class KeyDerivationFunction { + /// {@macro key_derivation_function} + const KeyDerivationFunction(); + + /// Derives a key from a [keyMaterial]. + Future derive( + Uint8List keyMaterial, + ); + + /// Verifies a [keyMaterial] against an [expected] value. + Future verify( + Uint8List keyMaterial, + Uint8List expected, + ); +} diff --git a/packages/native_crypto/lib/src/domain/random.dart b/packages/native_crypto/lib/src/domain/random.dart new file mode 100644 index 0000000..7592e21 --- /dev/null +++ b/packages/native_crypto/lib/src/domain/random.dart @@ -0,0 +1,18 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +/// {@template random} +/// A [Random] is a source of random bytes. +/// {@endtemplate} +abstract class Random { + /// {@macro random} + const Random(); + + /// Generates a random [Uint8List] of [length] bytes. + Future generate(int length); +} diff --git a/packages/native_crypto/lib/src/interfaces/base_key.dart b/packages/native_crypto/lib/src/interfaces/base_key.dart deleted file mode 100644 index e8ac27e..0000000 --- a/packages/native_crypto/lib/src/interfaces/base_key.dart +++ /dev/null @@ -1,22 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: base_key.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 26/05/2022 17:40:38 -// ----- -// Copyright (c) 2021 - -import 'package:native_crypto/src/interfaces/byte_array.dart'; - -/// Represents a key in NativeCrypto. -/// -/// [BaseKey] is a [ByteArray] that can be used to store keys. -/// -/// This interface is implemented by all the key classes. -abstract class BaseKey extends ByteArray { - const BaseKey(super.bytes); - BaseKey.fromBase16(super.encoded) : super.fromBase16(); - BaseKey.fromBase64(super.encoded) : super.fromBase64(); - BaseKey.fromUtf8(super.input) : super.fromUtf8(); -} diff --git a/packages/native_crypto/lib/src/interfaces/builder.dart b/packages/native_crypto/lib/src/interfaces/builder.dart deleted file mode 100644 index a1b39aa..0000000 --- a/packages/native_crypto/lib/src/interfaces/builder.dart +++ /dev/null @@ -1,14 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: builder.dart -// Created Date: 28/12/2021 12:02:34 -// Last Modified: 23/05/2022 22:38:44 -// ----- -// Copyright (c) 2021 - -// ignore_for_file: one_member_abstracts - -abstract class Builder { - Future build(); -} diff --git a/packages/native_crypto/lib/src/interfaces/cipher.dart b/packages/native_crypto/lib/src/interfaces/cipher.dart deleted file mode 100644 index 58b0d4a..0000000 --- a/packages/native_crypto/lib/src/interfaces/cipher.dart +++ /dev/null @@ -1,53 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher.dart -// Created Date: 16/12/2021 16:28:00 -// Last Modified: 26/05/2022 21:21:07 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:native_crypto/src/core/cipher_text_wrapper.dart'; -import 'package:native_crypto/src/utils/cipher_algorithm.dart'; - -/// Represents a cipher in NativeCrypto. -/// -/// In cryptography, a [Cipher] is an algorithm for performing encryption -/// or decryption - a series of well-defined steps that can -/// be followed as a procedure. -/// -/// This interface is implemented by all the ciphers in NativeCrypto. -abstract class Cipher { - static const int _bytesCountPerChunkDefault = 33554432; - static int _bytesCountPerChunk = _bytesCountPerChunkDefault; - - /// Returns the default number of bytes per chunk. - static int get defaultBytesCountPerChunk => _bytesCountPerChunkDefault; - - /// Returns the size of a chunk of data - /// that can be processed by the [Cipher]. - static int get bytesCountPerChunk => Cipher._bytesCountPerChunk; - - /// Sets the size of a chunk of data - /// that can be processed by the [Cipher]. - static set bytesCountPerChunk(int bytesCount) { - _bytesCountPerChunk = bytesCount; - } - - /// Returns the standard algorithm for this [Cipher]. - CipherAlgorithm get algorithm; - - /// Encrypts the [data]. - /// - /// Takes [Uint8List] data as parameter. - /// Returns a [CipherTextWrapper]. - Future encrypt(Uint8List data); - - /// Decrypts the [cipherText] - /// - /// Takes [CipherTextWrapper] as parameter. - /// And returns plain text data as [Uint8List]. - Future decrypt(CipherTextWrapper cipherText); -} diff --git a/packages/native_crypto/lib/src/interfaces/interfaces.dart b/packages/native_crypto/lib/src/interfaces/interfaces.dart deleted file mode 100644 index ef47be3..0000000 --- a/packages/native_crypto/lib/src/interfaces/interfaces.dart +++ /dev/null @@ -1,14 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: interfaces.dart -// Created Date: 23/05/2022 23:03:47 -// Last Modified: 26/05/2022 17:41:06 -// ----- -// Copyright (c) 2022 - -export 'base_key.dart'; -export 'builder.dart'; -export 'byte_array.dart'; -export 'cipher.dart'; -export 'keyderivation.dart'; diff --git a/packages/native_crypto/lib/src/interfaces/keyderivation.dart b/packages/native_crypto/lib/src/interfaces/keyderivation.dart deleted file mode 100644 index ccdbb4b..0000000 --- a/packages/native_crypto/lib/src/interfaces/keyderivation.dart +++ /dev/null @@ -1,23 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: kdf.dart -// Created Date: 18/12/2021 11:56:43 -// Last Modified: 26/05/2022 18:47:15 -// ----- -// Copyright (c) 2021 - -import 'package:native_crypto/src/keys/secret_key.dart'; -import 'package:native_crypto/src/utils/kdf_algorithm.dart'; - -/// Represents a Key Derivation Function (KDF) in NativeCrypto. -/// -/// [KeyDerivation] function is a function that takes some -/// parameters and returns a [SecretKey]. -abstract class KeyDerivation { - /// Returns the standard algorithm for this key derivation function - KdfAlgorithm get algorithm; - - /// Derive a [SecretKey]. - Future derive(); -} diff --git a/packages/native_crypto/lib/src/kdf/kdf.dart b/packages/native_crypto/lib/src/kdf/kdf.dart deleted file mode 100644 index cb7d609..0000000 --- a/packages/native_crypto/lib/src/kdf/kdf.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: kdf.dart -// Created Date: 23/05/2022 22:57:11 -// Last Modified: 23/05/2022 23:04:15 -// ----- -// Copyright (c) 2022 - -export 'pbkdf2.dart'; diff --git a/packages/native_crypto/lib/src/kdf/pbkdf2.dart b/packages/native_crypto/lib/src/kdf/pbkdf2.dart index 8ccdadd..21373af 100644 --- a/packages/native_crypto/lib/src/kdf/pbkdf2.dart +++ b/packages/native_crypto/lib/src/kdf/pbkdf2.dart @@ -1,115 +1,93 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: pbkdf2.dart -// Created Date: 17/12/2021 14:50:42 -// Last Modified: 26/05/2022 23:19:46 -// ----- -// Copyright (c) 2021 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. -import 'dart:typed_data'; - -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/extensions.dart'; -import 'package:native_crypto/src/utils/hash_algorithm.dart'; -import 'package:native_crypto/src/utils/kdf_algorithm.dart'; +import 'package:flutter/foundation.dart'; +import 'package:native_crypto/native_crypto.dart'; import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; -/// Represent a PBKDF2 Key Derivation Function (KDF) in NativeCrypto. -/// -/// [Pbkdf2] is a function that takes password, salt, iteration count and -/// derive a [SecretKey] of specified length. -class Pbkdf2 extends KeyDerivation { - final int _keyBytesCount; - final int _iterations; - final HashAlgorithm _hash; +/// {@template pbkdf2} +/// A PBKDF2 is a password-based key derivation function. +/// {@endtemplate} +class Pbkdf2 extends KeyDerivationFunction { + /// {@macro pbkdf2} + const Pbkdf2({ + required this.hashAlgorithm, + required this.iterations, + required this.salt, + required this.length, + }); + + /// The [HashAlgorithm] used by this [Pbkdf2]. + final HashAlgorithm hashAlgorithm; + + /// The number of iterations. + final int iterations; + + /// The salt. + final Uint8List salt; + + /// The length of the derived key in bytes. + final int length; @override - KdfAlgorithm get algorithm => KdfAlgorithm.pbkdf2; + Future derive(Uint8List keyMaterial) async { + if (length == 0) { + // If the length is 0, return an empty list + return Uint8List(0); + } - Pbkdf2({ - required int keyBytesCount, - required int iterations, - HashAlgorithm algorithm = HashAlgorithm.sha256, - }) : _keyBytesCount = keyBytesCount, - _iterations = iterations, - _hash = algorithm { - if (keyBytesCount < 0) { - throw NativeCryptoException( - message: 'keyBytesCount must be positive.', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); + if (length < 0) { + throw ArgumentError.value(length, 'length', 'must be positive'); } if (iterations <= 0) { - throw NativeCryptoException( - message: 'iterations must be strictly positive.', - code: NativeCryptoExceptionCode.invalid_argument.code, + throw ArgumentError.value( + iterations, + 'iterations', + 'must greater than 0', ); } + + // Call the platform interface to derive the key + final bytes = await platform.pbkdf2( + password: keyMaterial, + salt: salt, + iterations: iterations, + length: length, + hashAlgorithm: hashAlgorithm.name, + ); + + // TODO(hpcl): move these checks to the platform interface + if (bytes == null) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes', + ); + } + + if (bytes.length != length) { + throw NativeCryptoException( + code: NativeCryptoExceptionCode.invalidData, + message: 'Platform returned bytes of wrong length: ' + 'expected $length, got ${bytes.length}', + ); + } + + return bytes; } @override - Future derive({String? password, String? salt}) async { - Uint8List? derivation; + Future verify(Uint8List keyMaterial, Uint8List expected) => + derive(keyMaterial).then((actual) { + if (actual.length != expected.length) { + return false; + } + return listEquals(actual, expected); + }); - if (_keyBytesCount == 0) { - return SecretKey(Uint8List(0)); - } - if (password.isNull) { - throw NativeCryptoException( - message: 'Password cannot be null.', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); - } - - if (salt.isNull) { - throw NativeCryptoException( - message: 'Salt cannot be null.', - code: NativeCryptoExceptionCode.invalid_argument.code, - ); - } - - try { - derivation = await platform.pbkdf2( - password!, - salt!, - _keyBytesCount, - _iterations, - _hash.name, - ); - } catch (e, s) { - throw NativeCryptoException( - message: '$e', - code: NativeCryptoExceptionCode.platform_throws.code, - stackTrace: s, - ); - } - - if (derivation.isNull) { - throw NativeCryptoException( - message: 'Failed to derive a key! Platform returned null.', - code: NativeCryptoExceptionCode.platform_returned_null.code, - ); - } - - if (derivation!.isEmpty) { - throw NativeCryptoException( - message: 'Failed to derive a key! Platform returned no data.', - code: NativeCryptoExceptionCode.platform_returned_empty_data.code, - ); - } - - if (derivation.length != _keyBytesCount) { - throw NativeCryptoException( - message: 'Failed to derive a key! Platform returned ' - '${derivation.length} bytes, but expected $_keyBytesCount bytes.', - code: NativeCryptoExceptionCode.platform_returned_invalid_data.code, - ); - } - - return SecretKey(derivation); - } + Future call({required String password}) => + derive(password.toBytes()).then(SecretKey.new); } diff --git a/packages/native_crypto/lib/src/keys/keys.dart b/packages/native_crypto/lib/src/keys/keys.dart deleted file mode 100644 index 912bb39..0000000 --- a/packages/native_crypto/lib/src/keys/keys.dart +++ /dev/null @@ -1,10 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: keys.dart -// Created Date: 23/05/2022 23:04:04 -// Last Modified: 23/05/2022 23:04:07 -// ----- -// Copyright (c) 2022 - -export 'secret_key.dart'; diff --git a/packages/native_crypto/lib/src/keys/secret_key.dart b/packages/native_crypto/lib/src/keys/secret_key.dart index e30b87b..3c6b0c5 100644 --- a/packages/native_crypto/lib/src/keys/secret_key.dart +++ b/packages/native_crypto/lib/src/keys/secret_key.dart @@ -1,60 +1,43 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: secret_key.dart -// Created Date: 28/12/2021 13:36:54 -// Last Modified: 26/05/2022 23:13:10 -// ----- -// Copyright (c) 2021 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. -import 'dart:typed_data'; - -import 'package:native_crypto/src/interfaces/base_key.dart'; -import 'package:native_crypto/src/interfaces/cipher.dart'; -import 'package:native_crypto/src/platform.dart'; -import 'package:native_crypto/src/utils/extensions.dart'; -import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; +import 'package:native_crypto/src/domain/base_key.dart'; +import 'package:native_crypto/src/random/secure_random.dart'; +/// {@template secret_key} /// Represents a secret key in NativeCrypto. /// /// [SecretKey] is a [BaseKey] that can be used to store secret keys. /// A [SecretKey] is a key that can be used to encrypt or decrypt data with -/// a symmetric [Cipher]. +/// a symmetric Cipher. +/// {@endtemplate} class SecretKey extends BaseKey { + /// {@macro secret_key} const SecretKey(super.bytes); + + /// Creates a [SecretKey] from a [List]. + SecretKey.fromList(super.list) : super.fromList(); + + /// Creates a [SecretKey] from a [String] encoded in UTF-8. + SecretKey.fromUtf8(super.encoded) : super.fromUtf8(); + + /// Creates a [SecretKey] from a [String] encoded in UTF-16. + SecretKey.fromUtf16(super.encoded) : super.fromUtf16(); + + /// Creates a [SecretKey] from a [String] encoded in Hexadecimal. SecretKey.fromBase16(super.encoded) : super.fromBase16(); + + /// Creates a [SecretKey] from a [String] encoded in Base64. SecretKey.fromBase64(super.encoded) : super.fromBase64(); - SecretKey.fromUtf8(super.input) : super.fromUtf8(); - static Future fromSecureRandom(int bitsCount) async { - Uint8List? key; - if (bitsCount == 0) { - return SecretKey(Uint8List(0)); - } + /// Generates a random [SecretKey] of the given [length] in bytes. + static Future fromSecureRandom(int length) async { + const random = SecureRandom(); + final bytes = await random.generate(length); - try { - key = await platform.generateSecretKey(bitsCount); - } catch (e, s) { - throw NativeCryptoException( - message: '$e', - code: NativeCryptoExceptionCode.platform_throws.code, - stackTrace: s, - ); - } - if (key.isNull) { - throw NativeCryptoException( - message: 'Failed to generate a secret key! Platform returned null.', - code: NativeCryptoExceptionCode.platform_returned_null.code, - ); - } - - if (key!.isEmpty) { - throw NativeCryptoException( - message: 'Failed to generate a secret key! ' - 'Platform returned no data.', - code: NativeCryptoExceptionCode.platform_returned_empty_data.code, - ); - } - return SecretKey(key); + return SecretKey(bytes); } } diff --git a/packages/native_crypto/lib/src/platform.dart b/packages/native_crypto/lib/src/platform.dart deleted file mode 100644 index 5d62b5e..0000000 --- a/packages/native_crypto/lib/src/platform.dart +++ /dev/null @@ -1,12 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: platform.dart -// Created Date: 27/12/2021 22:03:58 -// Last Modified: 25/05/2022 10:09:18 -// ----- -// Copyright (c) 2021 - -import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; - -NativeCryptoPlatform platform = NativeCryptoPlatform.instance; diff --git a/packages/native_crypto/lib/src/random/secure_random.dart b/packages/native_crypto/lib/src/random/secure_random.dart new file mode 100644 index 0000000..c769e29 --- /dev/null +++ b/packages/native_crypto/lib/src/random/secure_random.dart @@ -0,0 +1,50 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto/src/core/utils/platform.dart'; +import 'package:native_crypto/src/domain/random.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +/// {@template secure_random} +/// A [SecureRandom] is a source of secure random bytes. +/// {@endtemplate} +class SecureRandom extends Random { + /// {@macro secure_random} + const SecureRandom(); + + @override + Future generate(int length) async { + if (length < 0) { + throw ArgumentError.value(length, 'length', 'must be positive'); + } + if (length == 0) { + // If the length is 0, return an empty list + return Uint8List(0); + } + // Call the platform interface to generate the secure random bytes + final bytes = await platform.generateSecureRandom(length); + + // TODO(hpcl): move these checks to the platform interface + if (bytes == null) { + throw const NativeCryptoException( + code: NativeCryptoExceptionCode.nullError, + message: 'Platform returned null bytes', + ); + } + + if (bytes.length != length) { + throw NativeCryptoException( + code: NativeCryptoExceptionCode.invalidData, + message: 'Platform returned bytes of wrong length: ' + 'expected $length, got ${bytes.length}', + ); + } + + return bytes; + } +} diff --git a/packages/native_crypto/lib/src/utils/cipher_algorithm.dart b/packages/native_crypto/lib/src/utils/cipher_algorithm.dart deleted file mode 100644 index 2ba968c..0000000 --- a/packages/native_crypto/lib/src/utils/cipher_algorithm.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher_algorithm.dart -// Created Date: 23/05/2022 22:07:54 -// Last Modified: 26/05/2022 18:52:32 -// ----- -// Copyright (c) 2022 - -/// Represents different cipher algorithms -enum CipherAlgorithm { aes } diff --git a/packages/native_crypto/lib/src/utils/encoding.dart b/packages/native_crypto/lib/src/utils/encoding.dart deleted file mode 100644 index b7ddd80..0000000 --- a/packages/native_crypto/lib/src/utils/encoding.dart +++ /dev/null @@ -1,10 +0,0 @@ -// 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 deleted file mode 100644 index fc2a799..0000000 --- a/packages/native_crypto/lib/src/utils/extensions.dart +++ /dev/null @@ -1,101 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: extensions.dart -// Created Date: 26/05/2022 12:12:48 -// Last Modified: 27/05/2022 12:26:55 -// ----- -// 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 combine() { - if (isEmpty) return Uint8List(0); - return reduce((value, element) => value.plus(element)); - } -} - -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 plus(final Uint8List other) => [...this, ...other].toTypedList(); - - /// Returns a sublist of this from the [start] index to the [end] index. - /// If [end] is greater than the length of the list, it is set to the length - Uint8List trySublist(int start, [int? end]) { - if (isEmpty) return this; - - int ending = end ?? length; - if (ending > length) ending = length; - - return sublist(start, ending); - } -} diff --git a/packages/native_crypto/lib/src/utils/hash_algorithm.dart b/packages/native_crypto/lib/src/utils/hash_algorithm.dart deleted file mode 100644 index 4538ef5..0000000 --- a/packages/native_crypto/lib/src/utils/hash_algorithm.dart +++ /dev/null @@ -1,51 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: hash_algorithm.dart -// Created Date: 23/05/2022 22:01:59 -// Last Modified: 26/05/2022 22:59:04 -// ----- -// Copyright (c) 2022 - -import 'dart:typed_data'; - -import 'package:native_crypto/src/platform.dart'; -import 'package:native_crypto/src/utils/extensions.dart'; -import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; - -/// Defines the hash algorithms. -enum HashAlgorithm { - sha256, - sha384, - sha512; - - /// Digest the [data] using this [HashAlgorithm]. - Future digest(Uint8List data) async { - Uint8List? hash; - try { - hash = await platform.digest(data, name); - } catch (e, s) { - throw NativeCryptoException( - message: '$e', - code: NativeCryptoExceptionCode.platform_throws.code, - stackTrace: s, - ); - } - - if (hash.isNull) { - throw NativeCryptoException( - message: 'Failed to digest data! Platform returned null.', - code: NativeCryptoExceptionCode.platform_returned_null.code, - ); - } - - if (hash!.isEmpty) { - throw NativeCryptoException( - message: 'Failed to digest data! Platform returned no data.', - code: NativeCryptoExceptionCode.platform_returned_empty_data.code, - ); - } - - return hash; - } -} diff --git a/packages/native_crypto/lib/src/utils/kdf_algorithm.dart b/packages/native_crypto/lib/src/utils/kdf_algorithm.dart deleted file mode 100644 index 68d6a76..0000000 --- a/packages/native_crypto/lib/src/utils/kdf_algorithm.dart +++ /dev/null @@ -1,11 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: kdf_algorithm.dart -// Created Date: 23/05/2022 22:36:24 -// Last Modified: 26/05/2022 18:53:50 -// ----- -// Copyright (c) 2022 - -/// Represents different key derivation functions -enum KdfAlgorithm { pbkdf2 } diff --git a/packages/native_crypto/pubspec.yaml b/packages/native_crypto/pubspec.yaml index 72b0d59..f5b311c 100644 --- a/packages/native_crypto/pubspec.yaml +++ b/packages/native_crypto/pubspec.yaml @@ -2,22 +2,21 @@ name: native_crypto description: Fast and secure cryptography for Flutter. version: 0.1.1 -publish_to: 'none' +publish_to: "none" environment: sdk: ">=2.17.0 <3.0.0" flutter: ">=2.5.0" dependencies: - flutter: - sdk: flutter - + flutter: { sdk: flutter } + native_crypto_android: git: url: https://github.com/hugo-pcl/native-crypto-flutter.git ref: native_crypto_android-v0.1.1 path: packages/native_crypto_android - + native_crypto_ios: git: url: https://github.com/hugo-pcl/native-crypto-flutter.git @@ -29,19 +28,19 @@ dependencies: url: https://github.com/hugo-pcl/native-crypto-flutter.git ref: native_crypto_platform_interface-v0.1.1 path: packages/native_crypto_platform_interface + equatable: ^2.0.5 dev_dependencies: - flutter_test: - sdk: flutter + flutter_test: { sdk: flutter } - mockito: ^5.2.0 - plugin_platform_interface: ^2.1.2 + mockito: ^5.4.0 + plugin_platform_interface: ^2.1.4 wyatt_analysis: - git: - url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages - ref: wyatt_analysis-v2.1.0 - path: packages/wyatt_analysis + hosted: + url: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ + name: wyatt_analysis + version: 2.4.1 flutter: plugin: @@ -49,4 +48,4 @@ flutter: android: default_package: native_crypto_android ios: - default_package: native_crypto_ios \ No newline at end of file + default_package: native_crypto_ios diff --git a/packages/native_crypto/pubspec_overrides.yaml b/packages/native_crypto/pubspec_overrides.yaml new file mode 100644 index 0000000..53d57bc --- /dev/null +++ b/packages/native_crypto/pubspec_overrides.yaml @@ -0,0 +1,8 @@ +# melos_managed_dependency_overrides: native_crypto_android,native_crypto_ios,native_crypto_platform_interface,native_crypto_web +dependency_overrides: + native_crypto_android: + path: ../native_crypto_android + native_crypto_ios: + path: ../native_crypto_ios + native_crypto_platform_interface: + path: ../native_crypto_platform_interface diff --git a/packages/native_crypto/test/mocks/mock_native_crypto_api.dart b/packages/native_crypto/test/mocks/mock_native_crypto_api.dart new file mode 100644 index 0000000..a1b2e1b --- /dev/null +++ b/packages/native_crypto/test/mocks/mock_native_crypto_api.dart @@ -0,0 +1,231 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto_platform_interface/native_crypto_platform_interface_gen.dart'; + +class MockNativeCryptoAPI implements NativeCryptoAPI { + static Uint8List? Function(int length)? generateSecureRandomFn; + + static Uint8List? Function( + Uint8List data, + String algorithm, + )? hashFn; + + static Uint8List? Function( + Uint8List data, + Uint8List key, + String algorithm, + )? hmacFn; + + static Uint8List? Function( + Uint8List cipherText, + Uint8List key, + String algorithm, + )? decryptFn; + + static bool? Function( + String cipherTextPath, + String plainTextPath, + Uint8List key, + String algorithm, + )? decryptFileFn; + + static Uint8List? Function( + Uint8List plainText, + Uint8List key, + String algorithm, + )? encryptFn; + + static Uint8List? Function( + Uint8List plainText, + Uint8List key, + Uint8List iv, + String algorithm, + )? encryptWithIVFn; + + static bool? Function( + String plainTextPath, + String cipherTextPath, + Uint8List key, + String algorithm, + )? encryptFileFn; + + static bool? Function( + String plainTextPath, + String cipherTextPath, + Uint8List key, + Uint8List iv, + String algorithm, + )? encryptFileWithIVFn; + + static Uint8List? Function( + Uint8List password, + Uint8List salt, + int iterations, + int length, + String algorithm, + )? pbkdf2Fn; + + @override + Future decrypt( + Uint8List argCiphertext, + Uint8List argKey, + CipherAlgorithm argAlgorithm, + ) async { + if (decryptFn != null) { + return decryptFn!(argCiphertext, argKey, argAlgorithm.toString()); + } else { + return Uint8List.fromList([1, 2, 3]); + } + } + + @override + Future decryptFile( + String argCiphertextpath, + String argPlaintextpath, + Uint8List argKey, + CipherAlgorithm argAlgorithm, + ) async { + if (decryptFileFn != null) { + return decryptFileFn!( + argCiphertextpath, + argPlaintextpath, + argKey, + argAlgorithm.toString(), + ); + } else { + return Future.value(true); + } + } + + @override + Future encrypt( + Uint8List argPlaintext, + Uint8List argKey, + CipherAlgorithm argAlgorithm, + ) async { + if (encryptFn != null) { + return encryptFn!(argPlaintext, argKey, argAlgorithm.toString()); + } else { + return Uint8List.fromList([1, 2, 3]); + } + } + + @override + Future encryptFile( + String argPlaintextpath, + String argCiphertextpath, + Uint8List argKey, + CipherAlgorithm argAlgorithm, + ) async { + if (encryptFileFn != null) { + return encryptFileFn!( + argPlaintextpath, + argCiphertextpath, + argKey, + argAlgorithm.toString(), + ); + } else { + return Future.value(true); + } + } + + @override + Future encryptFileWithIV( + String argPlaintextpath, + String argCiphertextpath, + Uint8List argIv, + Uint8List argKey, + CipherAlgorithm argAlgorithm, + ) async { + if (encryptFileWithIVFn != null) { + return encryptFileWithIVFn!( + argPlaintextpath, + argCiphertextpath, + argKey, + argIv, + argAlgorithm.toString(), + ); + } else { + return Future.value(true); + } + } + + @override + Future encryptWithIV( + Uint8List argPlaintext, + Uint8List argIv, + Uint8List argKey, + CipherAlgorithm argAlgorithm, + ) async { + if (encryptWithIVFn != null) { + return encryptWithIVFn!( + argPlaintext, + argKey, + argIv, + argAlgorithm.toString(), + ); + } else { + return Future.value(Uint8List.fromList([1, 2, 3])); + } + } + + @override + Future generateSecureRandom(int argLength) { + if (generateSecureRandomFn != null) { + return Future.value(generateSecureRandomFn!(argLength)); + } else { + return Future.value(Uint8List.fromList([1, 2, 3])); + } + } + + @override + Future hash(Uint8List argData, HashAlgorithm argAlgorithm) { + if (hashFn != null) { + return Future.value(hashFn!(argData, argAlgorithm.toString())); + } else { + return Future.value(Uint8List.fromList([1, 2, 3])); + } + } + + @override + Future hmac( + Uint8List argData, + Uint8List argKey, + HashAlgorithm argAlgorithm, + ) { + if (hmacFn != null) { + return Future.value(hmacFn!(argData, argKey, argAlgorithm.toString())); + } else { + return Future.value(Uint8List.fromList([1, 2, 3])); + } + } + + @override + Future pbkdf2( + Uint8List argPassword, + Uint8List argSalt, + int argLength, + int argIterations, + HashAlgorithm argAlgorithm, + ) { + if (pbkdf2Fn != null) { + return Future.value( + pbkdf2Fn!( + argPassword, + argSalt, + argIterations, + argLength, + argAlgorithm.toString(), + ), + ); + } else { + return Future.value(Uint8List.fromList([1, 2, 3])); + } + } +} diff --git a/packages/native_crypto/test/mocks/mock_native_crypto_platform.dart b/packages/native_crypto/test/mocks/mock_native_crypto_platform.dart deleted file mode 100644 index 6e69cb9..0000000 --- a/packages/native_crypto/test/mocks/mock_native_crypto_platform.dart +++ /dev/null @@ -1,192 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: mock_native_crypto_platform.dart -// Created Date: 25/05/2022 23:34:34 -// Last Modified: 26/05/2022 11:40:24 -// ----- -// Copyright (c) 2022 - -import 'dart:typed_data'; - -import 'package:flutter_test/flutter_test.dart'; -import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -class MockNativeCryptoPlatform extends Fake - with MockPlatformInterfaceMixin - implements NativeCryptoPlatform { - Uint8List? data; - List? dataAsList; - Uint8List? key; - String? algorithm; - int? bitsCount; - String? password; - String? salt; - int? keyBytesCount; - int? iterations; - - Uint8List? Function()? response; - List? Function()? responseAsList; - - // ignore: use_setters_to_change_properties - void setResponse(Uint8List? Function()? response) { - this.response = response; - } - - // ignore: use_setters_to_change_properties - void setResponseAsList(List? Function()? responseAsList) { - this.responseAsList = responseAsList; - } - - void setDecryptExpectations({ - required Uint8List data, - required Uint8List key, - required String algorithm, - }) { - this.data = data; - this.key = key; - this.algorithm = algorithm; - } - - @override - Future decrypt( - Uint8List data, - Uint8List key, - String algorithm, - ) async { - expect(data, this.data); - expect(key, this.key); - expect(algorithm, this.algorithm); - return response?.call(); - } - - void setDecryptAsListExpectations({ - required List data, - required Uint8List key, - required String algorithm, - }) { - dataAsList = data; - this.key = key; - this.algorithm = algorithm; - } - - @override - Future decryptAsList( - List data, - Uint8List key, - String algorithm, - ) async { - expect(data, dataAsList); - expect(key, this.key); - expect(algorithm, this.algorithm); - - return response?.call(); - } - - void setDigestExpectations({ - required Uint8List data, - required String algorithm, - }) { - this.data = data; - this.algorithm = algorithm; - } - - @override - Future digest(Uint8List data, String algorithm) async { - expect(data, this.data); - expect(algorithm, this.algorithm); - - return response?.call(); - } - - void setEncryptExpectations({ - required Uint8List data, - required Uint8List key, - required String algorithm, - }) { - this.data = data; - this.key = key; - this.algorithm = algorithm; - } - - @override - Future encrypt( - Uint8List data, - Uint8List key, - String algorithm, - ) async { - expect(data, this.data); - expect(key, this.key); - expect(algorithm, this.algorithm); - - return response?.call(); - } - - void setEncryptAsListExpectations({ - required Uint8List data, - required Uint8List key, - required String algorithm, - }) => - setEncryptExpectations( - data: data, - key: key, - algorithm: algorithm, - ); - - @override - Future?> encryptAsList( - Uint8List data, - Uint8List key, - String algorithm, - ) async { - expect(data, this.data); - expect(key, this.key); - expect(algorithm, this.algorithm); - - return responseAsList?.call(); - } - - // ignore: use_setters_to_change_properties - void setGenerateKeyExpectations({required int bitsCount}) { - this.bitsCount = bitsCount; - } - - @override - Future generateSecretKey(int bitsCount) async { - expect(bitsCount, this.bitsCount); - - return response?.call(); - } - - void setPbkdf2Expectations({ - required String password, - required String salt, - required int keyBytesCount, - required int iterations, - required String algorithm, - }) { - this.password = password; - this.salt = salt; - this.iterations = iterations; - this.keyBytesCount = keyBytesCount; - this.algorithm = algorithm; - } - - @override - Future pbkdf2( - String password, - String salt, - int keyBytesCount, - int iterations, - String algorithm, - ) async { - expect(password, this.password); - expect(salt, this.salt); - expect(keyBytesCount, this.keyBytesCount); - expect(iterations, this.iterations); - expect(algorithm, this.algorithm); - - return response?.call(); - } -} diff --git a/packages/native_crypto/test/src/aes_cipher_test.dart b/packages/native_crypto/test/src/aes_cipher_test.dart deleted file mode 100644 index f1d0f39..0000000 --- a/packages/native_crypto/test/src/aes_cipher_test.dart +++ /dev/null @@ -1,323 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: aes_cipher_test.dart -// Created Date: 26/05/2022 23:20:53 -// Last Modified: 27/05/2022 16:39:44 -// ----- -// 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; - - setUp(() { - Cipher.bytesCountPerChunk = Cipher.defaultBytesCountPerChunk; - }); - - group('Constructor', () { - test('throws on invalid key length', () { - expect( - () => AES(SecretKey(Uint8List(0))), - throwsA( - isA() - .having( - (e) => e.code, - 'code', - 'invalid_key_length', - ) - .having( - (e) => e.message, - 'message', - contains('Invalid key'), - ), - ), - ); - }); - - test('creates a valid instance', () { - expect( - AES( - SecretKey(Uint8List(16)), - ), - isA(), - ); - }); - }); - - group('encrypt', () { - test('returns a valid cipher text wrapper', () async { - mock - ..setEncryptExpectations( - data: Uint8List(16), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => Uint8List(16 + 28)); - - final aes = AES(SecretKey(Uint8List(16))); - - expect( - await aes.encrypt(Uint8List(16)), - isA().having((e) => e.isSingle, 'is single', isTrue), - ); - }); - - test('returns a valid cipher text with multiple chunks', () async { - mock - ..setEncryptExpectations( - data: Uint8List(16), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => Uint8List(16 + 28)); // Returns 1 encrypted chunk - Cipher.bytesCountPerChunk = 16; - final aes = AES(SecretKey(Uint8List(16))); - - expect( - await aes.encrypt(Uint8List(16 * 3)), - isA().having((e) => e.isList, 'is list', isTrue), - ); - }); - - test('handles returning empty list', () async { - mock - ..setEncryptExpectations( - data: Uint8List(16), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => Uint8List(0)); - - final aes = AES(SecretKey(Uint8List(16))); - - await expectLater( - () => aes.encrypt(Uint8List(16)), - throwsA( - isA().having( - (e) => e.code, - 'code', - 'platform_returned_empty_data', - ), - ), - ); - }); - - test('handles returning null', () async { - mock - ..setEncryptExpectations( - data: Uint8List(16), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => null); - - final aes = AES(SecretKey(Uint8List(16))); - - await expectLater( - () => aes.encrypt(Uint8List(16)), - throwsA( - isA().having( - (e) => e.code, - 'code', - 'platform_returned_null', - ), - ), - ); - }); - - test('handles throwing PlatformException', () async { - mock - ..setEncryptExpectations( - data: Uint8List(16), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse( - () => throw PlatformException( - code: 'native_crypto', - message: 'dummy error', - ), - ); - - final aes = AES(SecretKey(Uint8List(16))); - - await expectLater( - () => aes.encrypt(Uint8List(16)), - throwsA( - isA() - .having( - (e) => e.message, - 'message', - contains( - 'PlatformException(native_crypto, dummy error, null, null)', - ), - ) - .having( - (e) => e.code, - 'code', - 'platform_throws', - ), - ), - ); - }); - }); - - group('decrypt', () { - test('returns a valid Uint8List', () async { - mock - ..setDecryptExpectations( - data: Uint8List(16 + 28), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => Uint8List(16)); - - final aes = AES(SecretKey(Uint8List(16))); - final bytes = Uint8List(16 + 28); - final wrapper = CipherTextWrapper.fromBytes( - bytes, - ivLength: 12, - tagLength: 16, - ); - - expect( - await aes.decrypt(wrapper), - isA().having((e) => e.length, 'length', 16), - ); - }); - - test('returns a valid Uint8List on decrypting multiple chunks', () async { - const int chunkSize = 8; - mock - ..setDecryptExpectations( - data: Uint8List(chunkSize + 28), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => Uint8List(chunkSize)); - Cipher.bytesCountPerChunk = chunkSize; - final aes = AES(SecretKey(Uint8List(16))); - final bytes = Uint8List((chunkSize + 28) * 3); - final wrapper = CipherTextWrapper.fromBytes( - bytes, - ivLength: 12, - tagLength: 16, - ); - - expect( - await aes.decrypt(wrapper), - isA().having((e) => e.length, 'length', chunkSize * 3), - ); - }); - - test('handles returning empty list', () async { - mock - ..setDecryptExpectations( - data: Uint8List(16 + 28), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => Uint8List(0)); - - final aes = AES(SecretKey(Uint8List(16))); - final bytes = Uint8List(16 + 28); - final wrapper = CipherTextWrapper.fromBytes( - bytes, - ivLength: 12, - tagLength: 16, - ); - - await expectLater( - () => aes.decrypt(wrapper), - throwsA( - isA().having( - (e) => e.code, - 'code', - 'platform_returned_empty_data', - ), - ), - ); - }); - - test('handles returning null', () async { - mock - ..setDecryptExpectations( - data: Uint8List(16 + 28), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse(() => null); - - final aes = AES(SecretKey(Uint8List(16))); - final bytes = Uint8List(16 + 28); - final wrapper = CipherTextWrapper.fromBytes( - bytes, - ivLength: 12, - tagLength: 16, - ); - - await expectLater( - () => aes.decrypt(wrapper), - throwsA( - isA().having( - (e) => e.code, - 'code', - 'platform_returned_null', - ), - ), - ); - }); - - test('handles throwing PlatformException', () async { - mock - ..setDecryptExpectations( - data: Uint8List(16 + 28), - key: Uint8List(16), - algorithm: 'aes', - ) - ..setResponse( - () => throw PlatformException( - code: 'native_crypto', - message: 'dummy error', - ), - ); - - final aes = AES(SecretKey(Uint8List(16))); - final bytes = Uint8List(16 + 28); - final wrapper = CipherTextWrapper.fromBytes( - bytes, - ivLength: 12, - tagLength: 16, - ); - - await expectLater( - () => aes.decrypt(wrapper), - throwsA( - isA() - .having( - (e) => e.message, - 'message', - contains( - 'PlatformException(native_crypto, dummy error, null, null)', - ), - ) - .having( - (e) => e.code, - 'code', - 'platform_throws', - ), - ), - ); - }); - }); -} diff --git a/packages/native_crypto/test/src/cipher_text_test.dart b/packages/native_crypto/test/src/cipher_text_test.dart deleted file mode 100644 index 16d98a5..0000000 --- a/packages/native_crypto/test/src/cipher_text_test.dart +++ /dev/null @@ -1,192 +0,0 @@ -// 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 deleted file mode 100644 index 8dc0116..0000000 --- a/packages/native_crypto/test/src/cipher_text_wrapper_test.dart +++ /dev/null @@ -1,349 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: cipher_text_wrapper_test.dart -// Created Date: 26/05/2022 21:35:41 -// Last Modified: 27/05/2022 13:46:54 -// ----- -// 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, - tagLength: 1, - ); - expect(wrapper.isSingle, isTrue); - expect(wrapper.single, single); - }); - - test('creates list from bytes when too big', () { - Cipher.bytesCountPerChunk = 1; - final wrapper = CipherTextWrapper.fromBytes( - Uint8List.fromList([1, 2, 3, 4, 5, 6]), - ivLength: 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, - tagLength: 1, - chunkSize: 3, - ); - expect(Cipher.bytesCountPerChunk, 3); - }); - - test('throws if trying to build list with bad parameters', () { - Cipher.bytesCountPerChunk = 1; // length of a message - - expect( - () => CipherTextWrapper.fromBytes( - Uint8List.fromList([1, 2, 3, 4, 5, 6]), - ivLength: 2, - tagLength: 1, - ), - throwsA( - isA() - .having( - (e) => e.code, - 'code', - 'invalid_argument', - ) - .having( - (e) => e.message, - 'message', - contains('on chunk #'), - ), - ), - ); - }); - }); -} diff --git a/packages/native_crypto/test/src/digest_test.dart b/packages/native_crypto/test/src/digest_test.dart new file mode 100644 index 0000000..1927ea5 --- /dev/null +++ b/packages/native_crypto/test/src/digest_test.dart @@ -0,0 +1,116 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +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_api.dart'; + +void main() { + setUp(() { + // Mock the platform interface API + NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( + api: MockNativeCryptoAPI(), + ); + }); + + group('Hash', () { + test('$Sha256 digest correctly', () async { + final hash = await Sha256().digest('abc'.toBytes()); + expect(hash, isNotNull); + expect( + hash, + Uint8List.fromList([1, 2, 3]), + ); + }); + + test('$Sha256 digest throws if platform returns null', () async { + MockNativeCryptoAPI.hashFn = (input, algorithm) => null; + expect( + () async => Sha256().digest('abc'.toBytes()), + throwsA(isA()), + ); + }); + + test('$Sha256 digest throws if platform returns invalid data', () async { + MockNativeCryptoAPI.hashFn = (input, algorithm) => Uint8List(0); + expect( + () async => Sha256().digest('abcd'.toBytes()), + throwsA(isA()), + ); + }); + + test('$Sha256 returns correct $HashAlgorithm', () async { + final hash = Sha256(); + + expect(hash.algorithm, HashAlgorithm.sha256); + }); + + test('$Sha384 returns correct $HashAlgorithm', () async { + final hash = Sha384(); + + expect(hash.algorithm, HashAlgorithm.sha384); + }); + + test('$Sha512 returns correct $HashAlgorithm', () async { + final hash = Sha512(); + + expect(hash.algorithm, HashAlgorithm.sha512); + }); + }); + + group('Hmac', () { + test('$HmacSha256 digest correctly', () async { + final hash = await HmacSha256() + .digest('abc'.toBytes(), SecretKey.fromUtf16('key')); + expect(hash, isNotNull); + expect( + hash, + Uint8List.fromList([1, 2, 3]), + ); + }); + + test('$HmacSha256 digest throws if platform returns null', () async { + MockNativeCryptoAPI.hmacFn = (input, key, algorithm) => null; + expect( + () async => + HmacSha256().digest('abc'.toBytes(), SecretKey.fromUtf16('key')), + throwsA(isA()), + ); + }); + + test('$HmacSha256 digest throws if platform returns invalid data', + () async { + MockNativeCryptoAPI.hmacFn = (input, key, algorithm) => Uint8List(0); + expect( + () async => + HmacSha256().digest('abc'.toBytes(), SecretKey.fromUtf16('key')), + throwsA(isA()), + ); + }); + + test('$HmacSha256 returns correct $HashAlgorithm', () async { + final hash = HmacSha256(); + + expect(hash.algorithm, HashAlgorithm.sha256); + }); + + test('$HmacSha384 returns correct $HashAlgorithm', () async { + final hash = HmacSha384(); + + expect(hash.algorithm, HashAlgorithm.sha384); + }); + + test('$HmacSha512 returns correct $HashAlgorithm', () async { + final hash = HmacSha512(); + + expect(hash.algorithm, HashAlgorithm.sha512); + }); + }); +} diff --git a/packages/native_crypto/test/src/hash_algorithm_test.dart b/packages/native_crypto/test/src/hash_algorithm_test.dart deleted file mode 100644 index b7911b1..0000000 --- a/packages/native_crypto/test/src/hash_algorithm_test.dart +++ /dev/null @@ -1,128 +0,0 @@ -// 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 index f0ae084..9732018 100644 --- a/packages/native_crypto/test/src/pbkdf2_test.dart +++ b/packages/native_crypto/test/src/pbkdf2_test.dart @@ -1,280 +1,155 @@ -// 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 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. 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'; +import '../mocks/mock_native_crypto_api.dart'; void main() { - final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); - NativeCryptoPlatform.instance = mock; + setUp(() { + // Mock the platform interface API + NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( + api: MockNativeCryptoAPI(), + ); - 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'), - ), - ), - ); - }); + MockNativeCryptoAPI.pbkdf2Fn = null; }); - 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', + group('$Pbkdf2', () { + test('derive key correctly', () async { + final key = await Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 3, + hashAlgorithm: HashAlgorithm.sha256, + ).derive( + Uint8List.fromList([1, 2, 3]), ); + expect(key, isNotNull); + expect(key.length, 3); expect( - result, - sk, + key, + Uint8List.fromList([1, 2, 3]), ); }); - 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', - ); - + test('derive key with invalid length throws', () async { expect( - result, - sk, + () => Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: -1, + hashAlgorithm: HashAlgorithm.sha256, + ).derive( + Uint8List.fromList([1, 2, 3]), + ), + throwsA(isA()), ); }); + + test('derive key with invalid iterations throws', () async { + expect( + () => Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 0, + length: 3, + hashAlgorithm: HashAlgorithm.sha256, + ).derive( + Uint8List.fromList([1, 2, 3]), + ), + throwsA(isA()), + ); + }); + + test('derive key with 0 length returns empty list', () async { + final key = await Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 0, + hashAlgorithm: HashAlgorithm.sha256, + ).derive( + Uint8List.fromList([1, 2, 3]), + ); + expect(key, isNotNull); + expect(key.length, 0); + }); + + test('derive key throws if platform returns null', () async { + MockNativeCryptoAPI.pbkdf2Fn = + (password, salt, iterations, length, hashAlgorithm) => null; + expect( + () => Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 3, + hashAlgorithm: HashAlgorithm.sha256, + ).derive( + Uint8List.fromList([1, 2, 3]), + ), + throwsA(isA()), + ); + }); + + test('derive key throws if platform returns invalid data', () async { + expect( + () async => Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 4, + hashAlgorithm: HashAlgorithm.sha256, + ).derive( + Uint8List.fromList([1, 2, 3]), + ), + throwsA(isA()), + ); + }); + + test('call returns $SecretKey', () async { + final pbkdf = Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 3, + hashAlgorithm: HashAlgorithm.sha256, + ); + + final key = await pbkdf(password: 'password'); + + expect(key, isNotNull); + expect(key, isA()); + }); + + test('verify key returns true on the same password', () async { + final pbkdf = Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 3, + hashAlgorithm: HashAlgorithm.sha256, + ); + final pwd = Uint8List.fromList([1, 2, 3]); + final key = await pbkdf.derive(pwd); + final sucess = await pbkdf.verify(pwd, key); + expect(sucess, true); + }); + + test('verify key returns true on the same password', () async { + final pbkdf = Pbkdf2( + salt: Uint8List.fromList([1, 2, 3]), + iterations: 1, + length: 3, + hashAlgorithm: HashAlgorithm.sha256, + ); + final pwd = Uint8List.fromList([1, 2, 3]); + final key = Uint8List.fromList([1, 2, 3, 4, 5, 6]); + final sucess = await pbkdf.verify(pwd, key); + expect(sucess, false); + }); }); } diff --git a/packages/native_crypto/test/src/random_test.dart b/packages/native_crypto/test/src/random_test.dart new file mode 100644 index 0000000..71fb794 --- /dev/null +++ b/packages/native_crypto/test/src/random_test.dart @@ -0,0 +1,63 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +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_api.dart'; + +void main() { + setUp(() { + // Mock the platform interface API + NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( + api: MockNativeCryptoAPI(), + ); + }); + + group('$SecureRandom', () { + test('generate random bytes correctly', () async { + final random = await const SecureRandom().generate(3); + expect(random, isNotNull); + expect(random.length, 3); + expect( + random, + Uint8List.fromList([1, 2, 3]), + ); + }); + + test('generate random bytes with invalid length throws', () async { + expect( + () => const SecureRandom().generate(-1), + throwsA(isA()), + ); + }); + + test('generate random bytes with 0 length returns empty list', () async { + final random = await const SecureRandom().generate(0); + expect(random, isNotNull); + expect(random.length, 0); + }); + + test('generate random bytes throws if platform returns null', () async { + MockNativeCryptoAPI.generateSecureRandomFn = (length) => null; + expect( + () async => const SecureRandom().generate(3), + throwsA(isA()), + ); + }); + + test('generate random bytes throws if platform returns invalid data', + () async { + expect( + () async => const SecureRandom().generate(4), + throwsA(isA()), + ); + }); + }); +} diff --git a/packages/native_crypto/test/src/secret_key_test.dart b/packages/native_crypto/test/src/secret_key_test.dart index df2f159..29716fe 100644 --- a/packages/native_crypto/test/src/secret_key_test.dart +++ b/packages/native_crypto/test/src/secret_key_test.dart @@ -1,125 +1,56 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: secret_key_test.dart -// Created Date: 26/05/2022 10:52:41 -// Last Modified: 26/05/2022 22:38:07 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. import 'dart:typed_data'; -import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:native_crypto/src/keys/secret_key.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'; +import '../mocks/mock_native_crypto_api.dart'; void main() { - final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); - NativeCryptoPlatform.instance = mock; + setUp(() { + // Mock the platform interface API + NativeCryptoPlatform.instance = BasicMessageChannelNativeCrypto( + api: MockNativeCryptoAPI(), + ); + }); - group('Constructors', () { - test('handles Uint8List', () { + group('$SecretKey', () { + test('can be create from 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', () { + test('can be create from base16', () { final SecretKey key = SecretKey.fromBase16('0102030405'); expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); }); - test('handles base64', () { + test('can be create from base64', () { final SecretKey key = SecretKey.fromBase64('AQIDBAU='); expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); }); - test('handles utf8', () { + test('can be create from 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 - ..setGenerateKeyExpectations(bitsCount: 5) - ..setResponse(() => Uint8List.fromList([1, 2, 3, 4, 5])); + test('can be create from secure random', () async { + MockNativeCryptoAPI.generateSecureRandomFn = + (length) => Uint8List.fromList([1, 2, 3, 4, 5]); + final SecretKey key = await SecretKey.fromSecureRandom(5); - final SecretKey secretKey = await SecretKey.fromSecureRandom(5); - - expect( - secretKey.bytes, - Uint8List.fromList([1, 2, 3, 4, 5]), - ); - }); - - test('handles returning empty list', () async { - mock - ..setGenerateKeyExpectations(bitsCount: 5) - ..setResponse(() => Uint8List(0)); - - await expectLater( - () => SecretKey.fromSecureRandom(5), - throwsA( - isA().having( - (e) => e.code, - 'code', - 'platform_returned_empty_data', - ), - ), - ); - }); - - test('handles returning null', () async { - mock - ..setGenerateKeyExpectations(bitsCount: 5) - ..setResponse(() => null); - - await expectLater( - () => SecretKey.fromSecureRandom(5), - throwsA( - isA().having( - (e) => e.code, - 'code', - 'platform_returned_null', - ), - ), - ); - }); - - test('handles throwing PlatformException', () async { - mock - ..setGenerateKeyExpectations(bitsCount: 5) - ..setResponse( - () => throw PlatformException( - code: 'native_crypto', - message: 'dummy error', - ), - ); - - await expectLater( - () => SecretKey.fromSecureRandom(5), - throwsA( - isA() - .having( - (e) => e.message, - 'message', - 'PlatformException(native_crypto, dummy error, null, null)', - ) - .having( - (e) => e.code, - 'code', - 'platform_throws', - ), - ), - ); + expect(key.bytes, Uint8List.fromList([1, 2, 3, 4, 5])); }); }); } diff --git a/packages/native_crypto_android/LICENSE b/packages/native_crypto_android/LICENSE index dd5d33b..84320e5 100644 --- a/packages/native_crypto_android/LICENSE +++ b/packages/native_crypto_android/LICENSE @@ -2,7 +2,7 @@ NativeCrypto - Android Implementation MIT License -Copyright (c) 2019 - 2022 Hugo Pointcheval +Copyright (c) 2019 - 2023 Hugo Pointcheval Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/packages/native_crypto_android/README.md b/packages/native_crypto_android/README.md index 48b93a5..0c65720 100644 --- a/packages/native_crypto_android/README.md +++ b/packages/native_crypto_android/README.md @@ -1,15 +1,13 @@ -# native_crypto_android +# NativeCrypto - Android Implementation -A new flutter plugin project. +Android Implementation of [NativeCrypto][1] Plugin. ## Getting Started -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. +This project is a starting point for a Flutter [plug-in package][2], a specialized package that includes platform-specific implementation code for Android and/or iOS. -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +For help getting started with Flutter, view our [online documentation][3], which offers tutorials, samples, guidance on mobile development, and a full API reference. +[1]: ../../README.md +[2]: https://flutter.dev/developing-packages/ +[3]: https://flutter.dev/docs diff --git a/packages/native_crypto_android/android/build.gradle b/packages/native_crypto_android/android/build.gradle index 301735f..a30b841 100644 --- a/packages/native_crypto_android/android/build.gradle +++ b/packages/native_crypto_android/android/build.gradle @@ -47,4 +47,5 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.documentfile:documentfile:1.0.1' } diff --git a/packages/native_crypto_android/android/src/main/java/fr/pointcheval/native_crypto_android/GeneratedAndroidNativeCrypto.java b/packages/native_crypto_android/android/src/main/java/fr/pointcheval/native_crypto_android/GeneratedAndroidNativeCrypto.java new file mode 100644 index 0000000..8b10a22 --- /dev/null +++ b/packages/native_crypto_android/android/src/main/java/fr/pointcheval/native_crypto_android/GeneratedAndroidNativeCrypto.java @@ -0,0 +1,1577 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// -- +// Autogenerated from Pigeon (v9.2.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package fr.pointcheval.native_crypto_android; + +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MessageCodec; +import io.flutter.plugin.common.StandardMessageCodec; +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** Generated class from Pigeon. */ +@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression", "serial"}) +public class GeneratedAndroidNativeCrypto { + + /** Error class for passing custom error details to Flutter via a thrown PlatformException. */ + public static class FlutterError extends RuntimeException { + + /** The error code. */ + public final String code; + + /** The error details. Must be a datatype supported by the api codec. */ + public final Object details; + + public FlutterError(@NonNull String code, @Nullable String message, @Nullable Object details) + { + super(message); + this.code = code; + this.details = details; + } + } + + @NonNull + private static ArrayList wrapError(@NonNull Throwable exception) { + ArrayList errorList = new ArrayList(3); + if (exception instanceof FlutterError) { + FlutterError error = (FlutterError) exception; + errorList.add(error.code); + errorList.add(error.getMessage()); + errorList.add(error.details); + } else { + errorList.add(exception.toString()); + errorList.add(exception.getClass().getSimpleName()); + errorList.add( + "Cause: " + exception.getCause() + ", Stacktrace: " + Log.getStackTraceString(exception)); + } + return errorList; + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class HashRequest { + private @Nullable byte[] data; + + public @Nullable byte[] getData() { + return data; + } + + public void setData(@Nullable byte[] setterArg) { + this.data = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] data; + + public @NonNull Builder setData(@Nullable byte[] setterArg) { + this.data = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull HashRequest build() { + HashRequest pigeonReturn = new HashRequest(); + pigeonReturn.setData(data); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(data); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull HashRequest fromList(@NonNull ArrayList list) { + HashRequest pigeonResult = new HashRequest(); + Object data = list.get(0); + pigeonResult.setData((byte[]) data); + Object algorithm = list.get(1); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class HashResponse { + private @Nullable byte[] hash; + + public @Nullable byte[] getHash() { + return hash; + } + + public void setHash(@Nullable byte[] setterArg) { + this.hash = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] hash; + + public @NonNull Builder setHash(@Nullable byte[] setterArg) { + this.hash = setterArg; + return this; + } + + public @NonNull HashResponse build() { + HashResponse pigeonReturn = new HashResponse(); + pigeonReturn.setHash(hash); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(hash); + return toListResult; + } + + static @NonNull HashResponse fromList(@NonNull ArrayList list) { + HashResponse pigeonResult = new HashResponse(); + Object hash = list.get(0); + pigeonResult.setHash((byte[]) hash); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class HmacRequest { + private @Nullable byte[] data; + + public @Nullable byte[] getData() { + return data; + } + + public void setData(@Nullable byte[] setterArg) { + this.data = setterArg; + } + + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] data; + + public @NonNull Builder setData(@Nullable byte[] setterArg) { + this.data = setterArg; + return this; + } + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull HmacRequest build() { + HmacRequest pigeonReturn = new HmacRequest(); + pigeonReturn.setData(data); + pigeonReturn.setKey(key); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(3); + toListResult.add(data); + toListResult.add(key); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull HmacRequest fromList(@NonNull ArrayList list) { + HmacRequest pigeonResult = new HmacRequest(); + Object data = list.get(0); + pigeonResult.setData((byte[]) data); + Object key = list.get(1); + pigeonResult.setKey((byte[]) key); + Object algorithm = list.get(2); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class HmacResponse { + private @Nullable byte[] hmac; + + public @Nullable byte[] getHmac() { + return hmac; + } + + public void setHmac(@Nullable byte[] setterArg) { + this.hmac = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] hmac; + + public @NonNull Builder setHmac(@Nullable byte[] setterArg) { + this.hmac = setterArg; + return this; + } + + public @NonNull HmacResponse build() { + HmacResponse pigeonReturn = new HmacResponse(); + pigeonReturn.setHmac(hmac); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(hmac); + return toListResult; + } + + static @NonNull HmacResponse fromList(@NonNull ArrayList list) { + HmacResponse pigeonResult = new HmacResponse(); + Object hmac = list.get(0); + pigeonResult.setHmac((byte[]) hmac); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class GenerateSecureRandomRequest { + private @Nullable Long length; + + public @Nullable Long getLength() { + return length; + } + + public void setLength(@Nullable Long setterArg) { + this.length = setterArg; + } + + public static final class Builder { + + private @Nullable Long length; + + public @NonNull Builder setLength(@Nullable Long setterArg) { + this.length = setterArg; + return this; + } + + public @NonNull GenerateSecureRandomRequest build() { + GenerateSecureRandomRequest pigeonReturn = new GenerateSecureRandomRequest(); + pigeonReturn.setLength(length); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(length); + return toListResult; + } + + static @NonNull GenerateSecureRandomRequest fromList(@NonNull ArrayList list) { + GenerateSecureRandomRequest pigeonResult = new GenerateSecureRandomRequest(); + Object length = list.get(0); + pigeonResult.setLength((length == null) ? null : ((length instanceof Integer) ? (Integer) length : (Long) length)); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class GenerateSecureRandomResponse { + private @Nullable byte[] random; + + public @Nullable byte[] getRandom() { + return random; + } + + public void setRandom(@Nullable byte[] setterArg) { + this.random = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] random; + + public @NonNull Builder setRandom(@Nullable byte[] setterArg) { + this.random = setterArg; + return this; + } + + public @NonNull GenerateSecureRandomResponse build() { + GenerateSecureRandomResponse pigeonReturn = new GenerateSecureRandomResponse(); + pigeonReturn.setRandom(random); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(random); + return toListResult; + } + + static @NonNull GenerateSecureRandomResponse fromList(@NonNull ArrayList list) { + GenerateSecureRandomResponse pigeonResult = new GenerateSecureRandomResponse(); + Object random = list.get(0); + pigeonResult.setRandom((byte[]) random); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class Pbkdf2Request { + private @Nullable byte[] password; + + public @Nullable byte[] getPassword() { + return password; + } + + public void setPassword(@Nullable byte[] setterArg) { + this.password = setterArg; + } + + private @Nullable byte[] salt; + + public @Nullable byte[] getSalt() { + return salt; + } + + public void setSalt(@Nullable byte[] setterArg) { + this.salt = setterArg; + } + + private @Nullable Long length; + + public @Nullable Long getLength() { + return length; + } + + public void setLength(@Nullable Long setterArg) { + this.length = setterArg; + } + + private @Nullable Long iterations; + + public @Nullable Long getIterations() { + return iterations; + } + + public void setIterations(@Nullable Long setterArg) { + this.iterations = setterArg; + } + + private @Nullable String hashAlgorithm; + + public @Nullable String getHashAlgorithm() { + return hashAlgorithm; + } + + public void setHashAlgorithm(@Nullable String setterArg) { + this.hashAlgorithm = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] password; + + public @NonNull Builder setPassword(@Nullable byte[] setterArg) { + this.password = setterArg; + return this; + } + + private @Nullable byte[] salt; + + public @NonNull Builder setSalt(@Nullable byte[] setterArg) { + this.salt = setterArg; + return this; + } + + private @Nullable Long length; + + public @NonNull Builder setLength(@Nullable Long setterArg) { + this.length = setterArg; + return this; + } + + private @Nullable Long iterations; + + public @NonNull Builder setIterations(@Nullable Long setterArg) { + this.iterations = setterArg; + return this; + } + + private @Nullable String hashAlgorithm; + + public @NonNull Builder setHashAlgorithm(@Nullable String setterArg) { + this.hashAlgorithm = setterArg; + return this; + } + + public @NonNull Pbkdf2Request build() { + Pbkdf2Request pigeonReturn = new Pbkdf2Request(); + pigeonReturn.setPassword(password); + pigeonReturn.setSalt(salt); + pigeonReturn.setLength(length); + pigeonReturn.setIterations(iterations); + pigeonReturn.setHashAlgorithm(hashAlgorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(5); + toListResult.add(password); + toListResult.add(salt); + toListResult.add(length); + toListResult.add(iterations); + toListResult.add(hashAlgorithm); + return toListResult; + } + + static @NonNull Pbkdf2Request fromList(@NonNull ArrayList list) { + Pbkdf2Request pigeonResult = new Pbkdf2Request(); + Object password = list.get(0); + pigeonResult.setPassword((byte[]) password); + Object salt = list.get(1); + pigeonResult.setSalt((byte[]) salt); + Object length = list.get(2); + pigeonResult.setLength((length == null) ? null : ((length instanceof Integer) ? (Integer) length : (Long) length)); + Object iterations = list.get(3); + pigeonResult.setIterations((iterations == null) ? null : ((iterations instanceof Integer) ? (Integer) iterations : (Long) iterations)); + Object hashAlgorithm = list.get(4); + pigeonResult.setHashAlgorithm((String) hashAlgorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class Pbkdf2Response { + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + public @NonNull Pbkdf2Response build() { + Pbkdf2Response pigeonReturn = new Pbkdf2Response(); + pigeonReturn.setKey(key); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(key); + return toListResult; + } + + static @NonNull Pbkdf2Response fromList(@NonNull ArrayList list) { + Pbkdf2Response pigeonResult = new Pbkdf2Response(); + Object key = list.get(0); + pigeonResult.setKey((byte[]) key); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class EncryptRequest { + private @Nullable byte[] plainText; + + public @Nullable byte[] getPlainText() { + return plainText; + } + + public void setPlainText(@Nullable byte[] setterArg) { + this.plainText = setterArg; + } + + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] plainText; + + public @NonNull Builder setPlainText(@Nullable byte[] setterArg) { + this.plainText = setterArg; + return this; + } + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull EncryptRequest build() { + EncryptRequest pigeonReturn = new EncryptRequest(); + pigeonReturn.setPlainText(plainText); + pigeonReturn.setKey(key); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(3); + toListResult.add(plainText); + toListResult.add(key); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull EncryptRequest fromList(@NonNull ArrayList list) { + EncryptRequest pigeonResult = new EncryptRequest(); + Object plainText = list.get(0); + pigeonResult.setPlainText((byte[]) plainText); + Object key = list.get(1); + pigeonResult.setKey((byte[]) key); + Object algorithm = list.get(2); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class EncryptResponse { + private @Nullable byte[] cipherText; + + public @Nullable byte[] getCipherText() { + return cipherText; + } + + public void setCipherText(@Nullable byte[] setterArg) { + this.cipherText = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] cipherText; + + public @NonNull Builder setCipherText(@Nullable byte[] setterArg) { + this.cipherText = setterArg; + return this; + } + + public @NonNull EncryptResponse build() { + EncryptResponse pigeonReturn = new EncryptResponse(); + pigeonReturn.setCipherText(cipherText); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(cipherText); + return toListResult; + } + + static @NonNull EncryptResponse fromList(@NonNull ArrayList list) { + EncryptResponse pigeonResult = new EncryptResponse(); + Object cipherText = list.get(0); + pigeonResult.setCipherText((byte[]) cipherText); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class DecryptRequest { + private @Nullable byte[] cipherText; + + public @Nullable byte[] getCipherText() { + return cipherText; + } + + public void setCipherText(@Nullable byte[] setterArg) { + this.cipherText = setterArg; + } + + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] cipherText; + + public @NonNull Builder setCipherText(@Nullable byte[] setterArg) { + this.cipherText = setterArg; + return this; + } + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull DecryptRequest build() { + DecryptRequest pigeonReturn = new DecryptRequest(); + pigeonReturn.setCipherText(cipherText); + pigeonReturn.setKey(key); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(3); + toListResult.add(cipherText); + toListResult.add(key); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull DecryptRequest fromList(@NonNull ArrayList list) { + DecryptRequest pigeonResult = new DecryptRequest(); + Object cipherText = list.get(0); + pigeonResult.setCipherText((byte[]) cipherText); + Object key = list.get(1); + pigeonResult.setKey((byte[]) key); + Object algorithm = list.get(2); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class DecryptResponse { + private @Nullable byte[] plainText; + + public @Nullable byte[] getPlainText() { + return plainText; + } + + public void setPlainText(@Nullable byte[] setterArg) { + this.plainText = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] plainText; + + public @NonNull Builder setPlainText(@Nullable byte[] setterArg) { + this.plainText = setterArg; + return this; + } + + public @NonNull DecryptResponse build() { + DecryptResponse pigeonReturn = new DecryptResponse(); + pigeonReturn.setPlainText(plainText); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(plainText); + return toListResult; + } + + static @NonNull DecryptResponse fromList(@NonNull ArrayList list) { + DecryptResponse pigeonResult = new DecryptResponse(); + Object plainText = list.get(0); + pigeonResult.setPlainText((byte[]) plainText); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class EncryptFileRequest { + private @Nullable String plainTextPath; + + public @Nullable String getPlainTextPath() { + return plainTextPath; + } + + public void setPlainTextPath(@Nullable String setterArg) { + this.plainTextPath = setterArg; + } + + private @Nullable String cipherTextPath; + + public @Nullable String getCipherTextPath() { + return cipherTextPath; + } + + public void setCipherTextPath(@Nullable String setterArg) { + this.cipherTextPath = setterArg; + } + + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable String plainTextPath; + + public @NonNull Builder setPlainTextPath(@Nullable String setterArg) { + this.plainTextPath = setterArg; + return this; + } + + private @Nullable String cipherTextPath; + + public @NonNull Builder setCipherTextPath(@Nullable String setterArg) { + this.cipherTextPath = setterArg; + return this; + } + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull EncryptFileRequest build() { + EncryptFileRequest pigeonReturn = new EncryptFileRequest(); + pigeonReturn.setPlainTextPath(plainTextPath); + pigeonReturn.setCipherTextPath(cipherTextPath); + pigeonReturn.setKey(key); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(plainTextPath); + toListResult.add(cipherTextPath); + toListResult.add(key); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull EncryptFileRequest fromList(@NonNull ArrayList list) { + EncryptFileRequest pigeonResult = new EncryptFileRequest(); + Object plainTextPath = list.get(0); + pigeonResult.setPlainTextPath((String) plainTextPath); + Object cipherTextPath = list.get(1); + pigeonResult.setCipherTextPath((String) cipherTextPath); + Object key = list.get(2); + pigeonResult.setKey((byte[]) key); + Object algorithm = list.get(3); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class EncryptFileResponse { + private @Nullable Boolean success; + + public @Nullable Boolean getSuccess() { + return success; + } + + public void setSuccess(@Nullable Boolean setterArg) { + this.success = setterArg; + } + + public static final class Builder { + + private @Nullable Boolean success; + + public @NonNull Builder setSuccess(@Nullable Boolean setterArg) { + this.success = setterArg; + return this; + } + + public @NonNull EncryptFileResponse build() { + EncryptFileResponse pigeonReturn = new EncryptFileResponse(); + pigeonReturn.setSuccess(success); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(success); + return toListResult; + } + + static @NonNull EncryptFileResponse fromList(@NonNull ArrayList list) { + EncryptFileResponse pigeonResult = new EncryptFileResponse(); + Object success = list.get(0); + pigeonResult.setSuccess((Boolean) success); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class DecryptFileRequest { + private @Nullable String cipherTextPath; + + public @Nullable String getCipherTextPath() { + return cipherTextPath; + } + + public void setCipherTextPath(@Nullable String setterArg) { + this.cipherTextPath = setterArg; + } + + private @Nullable String plainTextPath; + + public @Nullable String getPlainTextPath() { + return plainTextPath; + } + + public void setPlainTextPath(@Nullable String setterArg) { + this.plainTextPath = setterArg; + } + + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable String cipherTextPath; + + public @NonNull Builder setCipherTextPath(@Nullable String setterArg) { + this.cipherTextPath = setterArg; + return this; + } + + private @Nullable String plainTextPath; + + public @NonNull Builder setPlainTextPath(@Nullable String setterArg) { + this.plainTextPath = setterArg; + return this; + } + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull DecryptFileRequest build() { + DecryptFileRequest pigeonReturn = new DecryptFileRequest(); + pigeonReturn.setCipherTextPath(cipherTextPath); + pigeonReturn.setPlainTextPath(plainTextPath); + pigeonReturn.setKey(key); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(cipherTextPath); + toListResult.add(plainTextPath); + toListResult.add(key); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull DecryptFileRequest fromList(@NonNull ArrayList list) { + DecryptFileRequest pigeonResult = new DecryptFileRequest(); + Object cipherTextPath = list.get(0); + pigeonResult.setCipherTextPath((String) cipherTextPath); + Object plainTextPath = list.get(1); + pigeonResult.setPlainTextPath((String) plainTextPath); + Object key = list.get(2); + pigeonResult.setKey((byte[]) key); + Object algorithm = list.get(3); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class DecryptFileResponse { + private @Nullable Boolean success; + + public @Nullable Boolean getSuccess() { + return success; + } + + public void setSuccess(@Nullable Boolean setterArg) { + this.success = setterArg; + } + + public static final class Builder { + + private @Nullable Boolean success; + + public @NonNull Builder setSuccess(@Nullable Boolean setterArg) { + this.success = setterArg; + return this; + } + + public @NonNull DecryptFileResponse build() { + DecryptFileResponse pigeonReturn = new DecryptFileResponse(); + pigeonReturn.setSuccess(success); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(1); + toListResult.add(success); + return toListResult; + } + + static @NonNull DecryptFileResponse fromList(@NonNull ArrayList list) { + DecryptFileResponse pigeonResult = new DecryptFileResponse(); + Object success = list.get(0); + pigeonResult.setSuccess((Boolean) success); + return pigeonResult; + } + } + + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class EncryptWithIVRequest { + private @Nullable byte[] plainText; + + public @Nullable byte[] getPlainText() { + return plainText; + } + + public void setPlainText(@Nullable byte[] setterArg) { + this.plainText = setterArg; + } + + private @Nullable byte[] iv; + + public @Nullable byte[] getIv() { + return iv; + } + + public void setIv(@Nullable byte[] setterArg) { + this.iv = setterArg; + } + + private @Nullable byte[] key; + + public @Nullable byte[] getKey() { + return key; + } + + public void setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + } + + private @Nullable String algorithm; + + public @Nullable String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + } + + public static final class Builder { + + private @Nullable byte[] plainText; + + public @NonNull Builder setPlainText(@Nullable byte[] setterArg) { + this.plainText = setterArg; + return this; + } + + private @Nullable byte[] iv; + + public @NonNull Builder setIv(@Nullable byte[] setterArg) { + this.iv = setterArg; + return this; + } + + private @Nullable byte[] key; + + public @NonNull Builder setKey(@Nullable byte[] setterArg) { + this.key = setterArg; + return this; + } + + private @Nullable String algorithm; + + public @NonNull Builder setAlgorithm(@Nullable String setterArg) { + this.algorithm = setterArg; + return this; + } + + public @NonNull EncryptWithIVRequest build() { + EncryptWithIVRequest pigeonReturn = new EncryptWithIVRequest(); + pigeonReturn.setPlainText(plainText); + pigeonReturn.setIv(iv); + pigeonReturn.setKey(key); + pigeonReturn.setAlgorithm(algorithm); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(4); + toListResult.add(plainText); + toListResult.add(iv); + toListResult.add(key); + toListResult.add(algorithm); + return toListResult; + } + + static @NonNull EncryptWithIVRequest fromList(@NonNull ArrayList list) { + EncryptWithIVRequest pigeonResult = new EncryptWithIVRequest(); + Object plainText = list.get(0); + pigeonResult.setPlainText((byte[]) plainText); + Object iv = list.get(1); + pigeonResult.setIv((byte[]) iv); + Object key = list.get(2); + pigeonResult.setKey((byte[]) key); + Object algorithm = list.get(3); + pigeonResult.setAlgorithm((String) algorithm); + return pigeonResult; + } + } + + private static class NativeCryptoAPICodec extends StandardMessageCodec { + public static final NativeCryptoAPICodec INSTANCE = new NativeCryptoAPICodec(); + + private NativeCryptoAPICodec() {} + + @Override + protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { + switch (type) { + case (byte) 128: + return DecryptFileRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 129: + return DecryptFileResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 130: + return DecryptRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 131: + return DecryptResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 132: + return EncryptFileRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 133: + return EncryptFileResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 134: + return EncryptRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 135: + return EncryptResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 136: + return EncryptWithIVRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 137: + return GenerateSecureRandomRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 138: + return GenerateSecureRandomResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 139: + return HashRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 140: + return HashResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 141: + return HmacRequest.fromList((ArrayList) readValue(buffer)); + case (byte) 142: + return HmacResponse.fromList((ArrayList) readValue(buffer)); + case (byte) 143: + return Pbkdf2Request.fromList((ArrayList) readValue(buffer)); + case (byte) 144: + return Pbkdf2Response.fromList((ArrayList) readValue(buffer)); + default: + return super.readValueOfType(type, buffer); + } + } + + @Override + protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { + if (value instanceof DecryptFileRequest) { + stream.write(128); + writeValue(stream, ((DecryptFileRequest) value).toList()); + } else if (value instanceof DecryptFileResponse) { + stream.write(129); + writeValue(stream, ((DecryptFileResponse) value).toList()); + } else if (value instanceof DecryptRequest) { + stream.write(130); + writeValue(stream, ((DecryptRequest) value).toList()); + } else if (value instanceof DecryptResponse) { + stream.write(131); + writeValue(stream, ((DecryptResponse) value).toList()); + } else if (value instanceof EncryptFileRequest) { + stream.write(132); + writeValue(stream, ((EncryptFileRequest) value).toList()); + } else if (value instanceof EncryptFileResponse) { + stream.write(133); + writeValue(stream, ((EncryptFileResponse) value).toList()); + } else if (value instanceof EncryptRequest) { + stream.write(134); + writeValue(stream, ((EncryptRequest) value).toList()); + } else if (value instanceof EncryptResponse) { + stream.write(135); + writeValue(stream, ((EncryptResponse) value).toList()); + } else if (value instanceof EncryptWithIVRequest) { + stream.write(136); + writeValue(stream, ((EncryptWithIVRequest) value).toList()); + } else if (value instanceof GenerateSecureRandomRequest) { + stream.write(137); + writeValue(stream, ((GenerateSecureRandomRequest) value).toList()); + } else if (value instanceof GenerateSecureRandomResponse) { + stream.write(138); + writeValue(stream, ((GenerateSecureRandomResponse) value).toList()); + } else if (value instanceof HashRequest) { + stream.write(139); + writeValue(stream, ((HashRequest) value).toList()); + } else if (value instanceof HashResponse) { + stream.write(140); + writeValue(stream, ((HashResponse) value).toList()); + } else if (value instanceof HmacRequest) { + stream.write(141); + writeValue(stream, ((HmacRequest) value).toList()); + } else if (value instanceof HmacResponse) { + stream.write(142); + writeValue(stream, ((HmacResponse) value).toList()); + } else if (value instanceof Pbkdf2Request) { + stream.write(143); + writeValue(stream, ((Pbkdf2Request) value).toList()); + } else if (value instanceof Pbkdf2Response) { + stream.write(144); + writeValue(stream, ((Pbkdf2Response) value).toList()); + } else { + super.writeValue(stream, value); + } + } + } + + /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ + public interface NativeCryptoAPI { + + @NonNull + HashResponse hash(@NonNull HashRequest request); + + @NonNull + HmacResponse hmac(@NonNull HmacRequest request); + + @NonNull + GenerateSecureRandomResponse generateSecureRandom(@NonNull GenerateSecureRandomRequest request); + + @NonNull + Pbkdf2Response pbkdf2(@NonNull Pbkdf2Request request); + + @NonNull + EncryptResponse encrypt(@NonNull EncryptRequest request); + + @NonNull + DecryptResponse decrypt(@NonNull DecryptRequest request); + + @NonNull + EncryptFileResponse encryptFile(@NonNull EncryptFileRequest request); + + @NonNull + DecryptFileResponse decryptFile(@NonNull DecryptFileRequest request); + + @NonNull + EncryptResponse encryptWithIV(@NonNull EncryptWithIVRequest request); + + /** The codec used by NativeCryptoAPI. */ + static MessageCodec getCodec() { + return NativeCryptoAPICodec.INSTANCE; + } + /**Sets up an instance of `NativeCryptoAPI` to handle messages through the `binaryMessenger`. */ + static void setup(BinaryMessenger binaryMessenger, NativeCryptoAPI api) { + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.hash", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + HashRequest requestArg = (HashRequest) args.get(0); + try { + HashResponse output = api.hash(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.hmac", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + HmacRequest requestArg = (HmacRequest) args.get(0); + try { + HmacResponse output = api.hmac(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + GenerateSecureRandomRequest requestArg = (GenerateSecureRandomRequest) args.get(0); + try { + GenerateSecureRandomResponse output = api.generateSecureRandom(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.pbkdf2", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Pbkdf2Request requestArg = (Pbkdf2Request) args.get(0); + try { + Pbkdf2Response output = api.pbkdf2(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encrypt", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + EncryptRequest requestArg = (EncryptRequest) args.get(0); + try { + EncryptResponse output = api.encrypt(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.decrypt", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + DecryptRequest requestArg = (DecryptRequest) args.get(0); + try { + DecryptResponse output = api.decrypt(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptFile", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + EncryptFileRequest requestArg = (EncryptFileRequest) args.get(0); + try { + EncryptFileResponse output = api.encryptFile(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.decryptFile", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + DecryptFileRequest requestArg = (DecryptFileRequest) args.get(0); + try { + DecryptFileResponse output = api.decryptFile(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + EncryptWithIVRequest requestArg = (EncryptWithIVRequest) args.get(0); + try { + EncryptResponse output = api.encryptWithIV(requestArg); + wrapped.add(0, output); + } + catch (Throwable exception) { + ArrayList wrappedError = wrapError(exception); + wrapped = wrappedError; + } + reply.reply(wrapped); + }); + } else { + channel.setMessageHandler(null); + } + } + } + } +} diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCrypto.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCrypto.kt new file mode 100644 index 0000000..6e29361 --- /dev/null +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCrypto.kt @@ -0,0 +1,118 @@ +package fr.pointcheval.native_crypto_android + +import android.content.Context +import fr.pointcheval.native_crypto_android.ciphers.AES +import fr.pointcheval.native_crypto_android.kdf.Pbkdf2 +import fr.pointcheval.native_crypto_android.utils.FileParameters +import fr.pointcheval.native_crypto_android.utils.HashAlgorithmParser +import java.security.SecureRandom + +class NativeCrypto(private val context: Context) : NativeCryptoAPI { + override fun hash(data: ByteArray, algorithm: HashAlgorithm): ByteArray? { + val md = HashAlgorithmParser.getMessageDigest(algorithm) + + return md.digest(data) + } + + override fun hmac(data: ByteArray, key: ByteArray, algorithm: HashAlgorithm): ByteArray? { + val mac = HashAlgorithmParser.getMac(algorithm) + val secretKey = javax.crypto.spec.SecretKeySpec(key, mac.algorithm) + mac.init(secretKey) + + return mac.doFinal(data) + } + + override fun generateSecureRandom(length: Long): ByteArray { + val bytes = ByteArray(length.toInt()) + SecureRandom.getInstanceStrong().nextBytes(bytes) + + return bytes + } + + override fun pbkdf2( + password: ByteArray, + salt: ByteArray, + length: Long, + iterations: Long, + algorithm: HashAlgorithm + ): ByteArray? { + val pbkdf2 = Pbkdf2(length.toInt(), iterations.toInt(), algorithm) + pbkdf2.init(password, salt) + + return pbkdf2.derive() + } + + override fun encrypt( + plainText: ByteArray, + key: ByteArray, + algorithm: CipherAlgorithm + ): ByteArray { + // For now, only AES is supported + val aes = AES() + + return aes.encrypt(plainText, key, null) + } + + override fun encryptWithIV( + plainText: ByteArray, + iv: ByteArray, + key: ByteArray, + algorithm: CipherAlgorithm + ): ByteArray { + // For now, only AES is supported + val aes = AES() + + return aes.encrypt(plainText, key, iv) + } + + override fun decrypt( + cipherText: ByteArray, + key: ByteArray, + algorithm: CipherAlgorithm + ): ByteArray { + // For now, only AES is supported + val aes = AES() + + return aes.decrypt(cipherText, key) + } + + override fun encryptFile( + plainTextPath: String, + cipherTextPath: String, + key: ByteArray, + algorithm: CipherAlgorithm + ): Boolean { + // For now, only AES is supported + val aes = AES() + val params = FileParameters(context, plainTextPath, cipherTextPath) + + return aes.encryptFile(params, key, null) + } + + override fun encryptFileWithIV( + plainTextPath: String, + cipherTextPath: String, + iv: ByteArray, + key: ByteArray, + algorithm: CipherAlgorithm + ): Boolean { + // For now, only AES is supported + val aes = AES() + val params = FileParameters(context, plainTextPath, cipherTextPath) + + return aes.encryptFile(params, key, iv) + } + + override fun decryptFile( + cipherTextPath: String, + plainTextPath: String, + key: ByteArray, + algorithm: CipherAlgorithm + ): Boolean { + // For now, only AES is supported + val aes = AES() + val params = FileParameters(context, cipherTextPath, plainTextPath) + + return aes.decryptFile(params, key) + } +} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt index 36cd412..4c8b235 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/NativeCryptoAndroidPlugin.kt @@ -1,160 +1,19 @@ package fr.pointcheval.native_crypto_android -import androidx.annotation.NonNull -import fr.pointcheval.native_crypto_android.interfaces.Cipher -import fr.pointcheval.native_crypto_android.kdf.Pbkdf2 -import fr.pointcheval.native_crypto_android.keys.SecretKey -import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm -import fr.pointcheval.native_crypto_android.utils.Constants -import fr.pointcheval.native_crypto_android.utils.HashAlgorithm -import fr.pointcheval.native_crypto_android.utils.Task import io.flutter.embedding.engine.plugins.FlutterPlugin -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import io.flutter.plugin.common.MethodChannel.MethodCallHandler -import io.flutter.plugin.common.MethodChannel.Result -import java.util.* /** NativeCryptoAndroidPlugin */ -class NativeCryptoAndroidPlugin : FlutterPlugin, MethodCallHandler { - /// The MethodChannel that will the communication between Flutter and native Android - /// - /// This local reference serves to register the plugin with the Flutter Engine and unregister it - /// when the Flutter Engine is detached from the Activity - private lateinit var channel: MethodChannel - private val name = "plugins.hugop.cl/native_crypto" +class NativeCryptoAndroidPlugin : FlutterPlugin { + private var nativeCrypto: NativeCrypto? = null - private var cipherInstance: Cipher? = null - - override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { - channel = MethodChannel(flutterPluginBinding.binaryMessenger, name) - channel.setMethodCallHandler(this) + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + val context = flutterPluginBinding.applicationContext + nativeCrypto = NativeCrypto(context) + NativeCryptoAPI.setUp(flutterPluginBinding.binaryMessenger, nativeCrypto) } - override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) { - channel.setMethodCallHandler(null) - } - - override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) { - lateinit var methodCallTask: Task<*> - - when (call.method) { - "digest" -> methodCallTask = handleDigest(call.arguments()) - "generateSecretKey" -> methodCallTask = handleGenerateSecretKey(call.arguments()) - "pbkdf2" -> methodCallTask = handlePbkdf2(call.arguments()) - "encryptAsList" -> methodCallTask = handleEncryptAsList(call.arguments()) - "decryptAsList" -> methodCallTask = handleDecryptAsList(call.arguments()) - "encrypt" -> methodCallTask = handleCrypt(call.arguments(), true) - "decrypt" -> methodCallTask = handleCrypt(call.arguments(), false) - else -> result.notImplemented() - } - - methodCallTask.call() - - methodCallTask.finalize { task -> - if (task.isSuccessful()) { - result.success(task.getResult()) - } else { - val exception: Exception = task.getException() - val message = exception.message - result.error("native_crypto", message, null) - } - } - } - - private fun handleDigest(arguments: Map?): Task { - return Task { - val data: ByteArray = - Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray - val algorithm: String = - Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String - HashAlgorithm.digest(data, algorithm) - } - } - - private fun handleGenerateSecretKey(arguments: Map?): Task { - return Task { - val bitsCount: Int = Objects.requireNonNull(arguments?.get(Constants.BITS_COUNT)) as Int - SecretKey.fromSecureRandom(bitsCount).bytes - } - } - - private fun handlePbkdf2(arguments: Map?): Task { - return Task { - val password: String = - Objects.requireNonNull(arguments?.get(Constants.PASSWORD)) as String - val salt: String = Objects.requireNonNull(arguments?.get(Constants.SALT)) as String - val keyBytesCount: Int = - Objects.requireNonNull(arguments?.get(Constants.KEY_BYTES_COUNT)) as Int - val iterations: Int = - Objects.requireNonNull(arguments?.get(Constants.ITERATIONS)) as Int - val algorithm: String = - Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String - - val pbkdf2: Pbkdf2 = Pbkdf2(keyBytesCount, iterations, HashAlgorithm.valueOf(algorithm)) - pbkdf2.init(password, salt) - - pbkdf2.derive().bytes - } - } - - private fun lazyLoadCipher(cipherAlgorithm: CipherAlgorithm) { - if (cipherInstance == null) { - cipherInstance = cipherAlgorithm.getCipher() - } else { - if (cipherInstance!!.algorithm != cipherAlgorithm) { - cipherInstance = cipherAlgorithm.getCipher() - } - } - } - - private fun handleEncryptAsList(arguments: Map?): Task> { - return Task { - val data: ByteArray = - Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray - val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray - val algorithm: String = - Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String - - val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) - lazyLoadCipher(cipherAlgorithm) - - cipherInstance!!.encryptAsList(data, key) - } - } - - private fun handleDecryptAsList(arguments: Map?): Task { - return Task { - val data: List = - Objects.requireNonNull(arguments?.get(Constants.DATA)) as List - val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray - val algorithm: String = - Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String - - val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) - lazyLoadCipher(cipherAlgorithm) - - cipherInstance!!.decryptAsList(data, key) - } - } - - // **EN**Crypt and **DE**Crypt - private fun handleCrypt(arguments: Map?, forEncryption: Boolean): Task { - return Task { - val data: ByteArray = - Objects.requireNonNull(arguments?.get(Constants.DATA)) as ByteArray - val key: ByteArray = Objects.requireNonNull(arguments?.get(Constants.KEY)) as ByteArray - val algorithm: String = - Objects.requireNonNull(arguments?.get(Constants.ALGORITHM)) as String - - val cipherAlgorithm: CipherAlgorithm = CipherAlgorithm.valueOf(algorithm) - lazyLoadCipher(cipherAlgorithm) - - if (forEncryption) { - cipherInstance!!.encrypt(data, key) - } else { - cipherInstance!!.decrypt(data, key) - } - } + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + NativeCryptoAPI.setUp(binding.binaryMessenger, null) + nativeCrypto = null } } diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/Pigeon.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/Pigeon.kt new file mode 100644 index 0000000..0383075 --- /dev/null +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/Pigeon.kt @@ -0,0 +1,300 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// -- +// Autogenerated from Pigeon (v9.2.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +package fr.pointcheval.native_crypto_android + +import android.util.Log +import io.flutter.plugin.common.BasicMessageChannel +import io.flutter.plugin.common.BinaryMessenger +import io.flutter.plugin.common.MessageCodec +import io.flutter.plugin.common.StandardMessageCodec +import java.io.ByteArrayOutputStream +import java.nio.ByteBuffer + +private fun wrapResult(result: Any?): List { + return listOf(result) +} + +private fun wrapError(exception: Throwable): List { + if (exception is FlutterError) { + return listOf( + exception.code, + exception.message, + exception.details + ) + } else { + return listOf( + exception.javaClass.simpleName, + exception.toString(), + "Cause: " + exception.cause + ", Stacktrace: " + Log.getStackTraceString(exception) + ) + } +} + +/** + * Error class for passing custom error details to Flutter via a thrown PlatformException. + * @property code The error code. + * @property message The error message. + * @property details The error details. Must be a datatype supported by the api codec. + */ +class FlutterError ( + val code: String, + override val message: String? = null, + val details: Any? = null +) : Throwable() + +enum class HashAlgorithm(val raw: Int) { + SHA256(0), + SHA384(1), + SHA512(2); + + companion object { + fun ofRaw(raw: Int): HashAlgorithm? { + return values().firstOrNull { it.raw == raw } + } + } +} + +enum class CipherAlgorithm(val raw: Int) { + AES(0); + + companion object { + fun ofRaw(raw: Int): CipherAlgorithm? { + return values().firstOrNull { it.raw == raw } + } + } +} +/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ +interface NativeCryptoAPI { + fun hash(data: ByteArray, algorithm: HashAlgorithm): ByteArray? + fun hmac(data: ByteArray, key: ByteArray, algorithm: HashAlgorithm): ByteArray? + fun generateSecureRandom(length: Long): ByteArray? + fun pbkdf2(password: ByteArray, salt: ByteArray, length: Long, iterations: Long, algorithm: HashAlgorithm): ByteArray? + fun encrypt(plainText: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): ByteArray? + fun encryptWithIV(plainText: ByteArray, iv: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): ByteArray? + fun decrypt(cipherText: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): ByteArray? + fun encryptFile(plainTextPath: String, cipherTextPath: String, key: ByteArray, algorithm: CipherAlgorithm): Boolean? + fun encryptFileWithIV(plainTextPath: String, cipherTextPath: String, iv: ByteArray, key: ByteArray, algorithm: CipherAlgorithm): Boolean? + fun decryptFile(cipherTextPath: String, plainTextPath: String, key: ByteArray, algorithm: CipherAlgorithm): Boolean? + + companion object { + /** The codec used by NativeCryptoAPI. */ + val codec: MessageCodec by lazy { + StandardMessageCodec() + } + /** Sets up an instance of `NativeCryptoAPI` to handle messages through the `binaryMessenger`. */ + @Suppress("UNCHECKED_CAST") + fun setUp(binaryMessenger: BinaryMessenger, api: NativeCryptoAPI?) { + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.hash", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val dataArg = args[0] as ByteArray + val algorithmArg = HashAlgorithm.ofRaw(args[1] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.hash(dataArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.hmac", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val dataArg = args[0] as ByteArray + val keyArg = args[1] as ByteArray + val algorithmArg = HashAlgorithm.ofRaw(args[2] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.hmac(dataArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val lengthArg = args[0].let { if (it is Int) it.toLong() else it as Long } + var wrapped: List + try { + wrapped = listOf(api.generateSecureRandom(lengthArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.pbkdf2", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val passwordArg = args[0] as ByteArray + val saltArg = args[1] as ByteArray + val lengthArg = args[2].let { if (it is Int) it.toLong() else it as Long } + val iterationsArg = args[3].let { if (it is Int) it.toLong() else it as Long } + val algorithmArg = HashAlgorithm.ofRaw(args[4] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.pbkdf2(passwordArg, saltArg, lengthArg, iterationsArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encrypt", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val plainTextArg = args[0] as ByteArray + val keyArg = args[1] as ByteArray + val algorithmArg = CipherAlgorithm.ofRaw(args[2] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.encrypt(plainTextArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val plainTextArg = args[0] as ByteArray + val ivArg = args[1] as ByteArray + val keyArg = args[2] as ByteArray + val algorithmArg = CipherAlgorithm.ofRaw(args[3] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.encryptWithIV(plainTextArg, ivArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.decrypt", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val cipherTextArg = args[0] as ByteArray + val keyArg = args[1] as ByteArray + val algorithmArg = CipherAlgorithm.ofRaw(args[2] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.decrypt(cipherTextArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptFile", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val plainTextPathArg = args[0] as String + val cipherTextPathArg = args[1] as String + val keyArg = args[2] as ByteArray + val algorithmArg = CipherAlgorithm.ofRaw(args[3] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.encryptFile(plainTextPathArg, cipherTextPathArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val plainTextPathArg = args[0] as String + val cipherTextPathArg = args[1] as String + val ivArg = args[2] as ByteArray + val keyArg = args[3] as ByteArray + val algorithmArg = CipherAlgorithm.ofRaw(args[4] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.encryptFileWithIV(plainTextPathArg, cipherTextPathArg, ivArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + run { + val channel = BasicMessageChannel(binaryMessenger, "dev.flutter.pigeon.NativeCryptoAPI.decryptFile", codec) + if (api != null) { + channel.setMessageHandler { message, reply -> + val args = message as List + val cipherTextPathArg = args[0] as String + val plainTextPathArg = args[1] as String + val keyArg = args[2] as ByteArray + val algorithmArg = CipherAlgorithm.ofRaw(args[3] as Int)!! + var wrapped: List + try { + wrapped = listOf(api.decryptFile(cipherTextPathArg, plainTextPathArg, keyArg, algorithmArg)) + } catch (exception: Throwable) { + wrapped = wrapError(exception) + } + reply.reply(wrapped) + } + } else { + channel.setMessageHandler(null) + } + } + } + } +} diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt index 0b4c86c..e8a5145 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/ciphers/AES.kt @@ -1,55 +1,149 @@ package fr.pointcheval.native_crypto_android.ciphers import fr.pointcheval.native_crypto_android.interfaces.Cipher -import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm -import javax.crypto.SecretKey +import fr.pointcheval.native_crypto_android.utils.FileParameters +import java.io.BufferedInputStream +import java.io.BufferedOutputStream +import javax.crypto.CipherInputStream +import javax.crypto.CipherOutputStream import javax.crypto.spec.GCMParameterSpec -import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec class AES : Cipher { - override val algorithm: CipherAlgorithm - get() = CipherAlgorithm.aes + private var cipherInstance: javax.crypto.Cipher? = null - var cipherInstance: javax.crypto.Cipher? = null; - - fun lazyLoadCipher() { + private fun lazyLoadCipher() { if (cipherInstance == null) { cipherInstance = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding") } } - // 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 = 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 { + // native.crypto cipherText representation = [NONCE(12) || CIPHERTEXT(n-28) || TAG(16)] + override fun encrypt(data: ByteArray, key: ByteArray, predefinedIV: ByteArray?): ByteArray { + // Initialize secret key spec val sk = SecretKeySpec(key, "AES") + + // Initialize cipher (if not already done) lazyLoadCipher() - cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) + + // If predefinedIV is not null, use it + if (predefinedIV != null && predefinedIV.isNotEmpty()) { + // Here we use the predefinedIV as the nonce (12 bytes) + // And we set the tag length to 16 bytes (128 bits) + val gcmParameterSpec = GCMParameterSpec(16*8, predefinedIV) + cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk, gcmParameterSpec) + } else { + // If predefinedIV is null, we generate a new one + cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) + } + + // Encrypt data val bytes: ByteArray = cipherInstance!!.doFinal(data) val iv: ByteArray = cipherInstance!!.iv - return listOf(iv, bytes) + + return iv.plus(bytes) + } + + override fun encryptFile(fileParameters: FileParameters, key: ByteArray, predefinedIV: ByteArray?): Boolean { + // Initialize secret key spec + val sk = SecretKeySpec(key, "AES") + + // Initialize cipher (if not already done) + lazyLoadCipher() + + // If predefinedIV is not null, use it + if (predefinedIV != null && predefinedIV.isNotEmpty()) { + // Here we use the predefinedIV as the nonce (12 bytes) + // And we set the tag length to 16 bytes (128 bits) + val gcmParameterSpec = GCMParameterSpec(16*8, predefinedIV) + cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk, gcmParameterSpec) + } else { + // If predefinedIV is null, we generate a new one + cipherInstance!!.init(javax.crypto.Cipher.ENCRYPT_MODE, sk) + } + + val input = BufferedInputStream(fileParameters.getFileInputStream()) + + var outputBuffered = BufferedOutputStream(fileParameters.getFileOutputStream(false)) + val iv: ByteArray = cipherInstance!!.iv + + // Prepend the IV to the cipherText file + outputBuffered.write(iv) + outputBuffered.flush() + outputBuffered.close() + + // Reopen the file and append the cipherText + outputBuffered = BufferedOutputStream(fileParameters.getFileOutputStream(true)) + val output = CipherOutputStream(outputBuffered, cipherInstance) + + var i: Int + do { + i = input.read() + if (i != -1) output.write(i) + } while (i != -1) + + output.flush() + output.close() + + input.close() + outputBuffered.close() + + + return fileParameters.outputExists() } override fun decrypt(data: ByteArray, key: ByteArray): ByteArray { + // Extract the IV from the cipherText 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, key: ByteArray): ByteArray { + // Initialize secret key spec val sk = SecretKeySpec(key, "AES") - val payload: ByteArray = data.last() - val iv: ByteArray = data.first() - val gcmSpec = GCMParameterSpec(16 * 8, iv) + + // Initialize GCMParameterSpec + val gcmParameterSpec = GCMParameterSpec(16 * 8, iv) + + // Initialize cipher (if not already done) lazyLoadCipher() - cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmSpec) + cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmParameterSpec) + + // Decrypt data return cipherInstance!!.doFinal(payload) } + + override fun decryptFile(fileParameters: FileParameters, key: ByteArray): Boolean { + val iv = ByteArray(12) + val inputFile = fileParameters.getFileInputStream() ?: throw Exception("Error while reading IV") + + // Read the first 12 bytes from the file + inputFile.read(iv) + + // Initialize secret key spec + val sk = SecretKeySpec(key, "AES") + + // Initialize GCMParameterSpec + val gcmParameterSpec = GCMParameterSpec(16 * 8, iv) + + // Initialize cipher (if not already done) + lazyLoadCipher() + + cipherInstance!!.init(javax.crypto.Cipher.DECRYPT_MODE, sk, gcmParameterSpec) + + val input = CipherInputStream(BufferedInputStream(inputFile), cipherInstance) + val output = BufferedOutputStream(fileParameters.getFileOutputStream(false)) + + var i: Int + do { + i = input.read() + if (i != -1) output.write(i) + } while (i != -1) + + output.flush() + output.close() + + input.close() + + return fileParameters.outputExists() + } } \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt index 5893d25..6d8e63f 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Cipher.kt @@ -1,12 +1,10 @@ package fr.pointcheval.native_crypto_android.interfaces -import fr.pointcheval.native_crypto_android.utils.CipherAlgorithm +import fr.pointcheval.native_crypto_android.utils.FileParameters interface Cipher { - val algorithm: CipherAlgorithm - - fun encrypt(data: ByteArray, key: ByteArray): ByteArray + fun encrypt(data: ByteArray, key: ByteArray, predefinedIV: ByteArray?): ByteArray fun decrypt(data: ByteArray, key: ByteArray): ByteArray - fun encryptAsList(data: ByteArray, key: ByteArray): List - fun decryptAsList(data: List, key: ByteArray): ByteArray + fun encryptFile(fileParameters: FileParameters, key: ByteArray, predefinedIV: ByteArray?): Boolean + fun decryptFile(fileParameters: FileParameters, key: ByteArray): Boolean } \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Key.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Key.kt deleted file mode 100644 index 4df212b..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/Key.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fr.pointcheval.native_crypto_android.interfaces - -interface Key { - val bytes: ByteArray -} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/KeyDerivation.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/KeyDerivation.kt index 6bd9216..17db04e 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/KeyDerivation.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/interfaces/KeyDerivation.kt @@ -1,10 +1,5 @@ package fr.pointcheval.native_crypto_android.interfaces -import fr.pointcheval.native_crypto_android.keys.SecretKey -import fr.pointcheval.native_crypto_android.utils.KdfAlgorithm - interface KeyDerivation { - val algorithm: KdfAlgorithm - - fun derive(): SecretKey + fun derive(): ByteArray? } \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/kdf/Pbkdf2.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/kdf/Pbkdf2.kt index 17d8c5e..0daa8ae 100644 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/kdf/Pbkdf2.kt +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/kdf/Pbkdf2.kt @@ -1,39 +1,43 @@ package fr.pointcheval.native_crypto_android.kdf +import fr.pointcheval.native_crypto_android.HashAlgorithm import fr.pointcheval.native_crypto_android.interfaces.KeyDerivation -import fr.pointcheval.native_crypto_android.keys.SecretKey -import fr.pointcheval.native_crypto_android.utils.HashAlgorithm -import fr.pointcheval.native_crypto_android.utils.KdfAlgorithm +import fr.pointcheval.native_crypto_android.utils.HashAlgorithmParser import javax.crypto.SecretKeyFactory import javax.crypto.spec.PBEKeySpec class Pbkdf2( - private val keyBytesCount: Int, private val iterations: Int, - private val hash: HashAlgorithm = HashAlgorithm.sha256 + private val length: Int, private val iterations: Int, + private val hashAlgorithm: HashAlgorithm ) : KeyDerivation { - private var password: String? = null - private var salt: String? = null + private var password: CharArray? = null + private var salt: ByteArray? = null - fun init(password: String, salt: String) { - this.password = password + fun init(password: ByteArray, salt: ByteArray) { + // Transform the password to a char array + val passwordCharArray = CharArray(password.size) + for (i in password.indices) { + passwordCharArray[i] = password[i].toInt().toChar() + } + + this.password = passwordCharArray this.salt = salt } - override val algorithm: KdfAlgorithm - get() = KdfAlgorithm.pbkdf2 - - override fun derive(): SecretKey { + override fun derive(): ByteArray? { if (password == null || salt == null) { throw Exception("Password and Salt must be initialized.") } val spec = PBEKeySpec( - password!!.toCharArray(), - salt!!.toByteArray(), + password!!, + salt!!, iterations, - keyBytesCount * 8 + length * 8 ) - val skf: SecretKeyFactory = SecretKeyFactory.getInstance(hash.pbkdf2String()) - return SecretKey(skf.generateSecret(spec).encoded) + val skf: SecretKeyFactory = + SecretKeyFactory.getInstance(HashAlgorithmParser.getPbkdf2String(hashAlgorithm)) + + return skf.generateSecret(spec).encoded } } \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/keys/SecretKey.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/keys/SecretKey.kt deleted file mode 100644 index 07e9833..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/keys/SecretKey.kt +++ /dev/null @@ -1,14 +0,0 @@ -package fr.pointcheval.native_crypto_android.keys - -import fr.pointcheval.native_crypto_android.interfaces.Key -import java.security.SecureRandom - -class SecretKey(override val bytes: ByteArray) : Key { - companion object { - fun fromSecureRandom(bitsCount: Int): SecretKey { - val bytes = ByteArray(bitsCount / 8) - SecureRandom.getInstanceStrong().nextBytes(bytes) - return SecretKey(bytes) - } - } -} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/CipherAlgorithm.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/CipherAlgorithm.kt deleted file mode 100644 index a95185f..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/CipherAlgorithm.kt +++ /dev/null @@ -1,14 +0,0 @@ -package fr.pointcheval.native_crypto_android.utils - -import fr.pointcheval.native_crypto_android.ciphers.AES -import fr.pointcheval.native_crypto_android.interfaces.Cipher - -enum class CipherAlgorithm { - aes; - - fun getCipher(): Cipher { - return when (this) { - aes -> AES() - } - } -} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/Constants.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/Constants.kt deleted file mode 100644 index 1cb359a..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/Constants.kt +++ /dev/null @@ -1,14 +0,0 @@ -package fr.pointcheval.native_crypto_android.utils - -object Constants { - const val ALGORITHM = "algorithm" - const val BITS_COUNT = "bitsCount" - const val DATA = "data" - const val PASSWORD = "password" - const val SALT = "salt" - const val KEY = "key" - const val KEY_BYTES_COUNT = "keyBytesCount" - const val ITERATIONS = "iterations" - const val EPHEMERAL_PRIVATE_KEY = "ephemeralPrivateKey" - const val OTHER_PUBLIC_KEY = "otherPublicKey" -} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/FileParameters.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/FileParameters.kt new file mode 100644 index 0000000..8a5651e --- /dev/null +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/FileParameters.kt @@ -0,0 +1,72 @@ +package fr.pointcheval.native_crypto_android.utils + +import android.content.Context +import android.content.res.Resources +import android.net.Uri +import androidx.documentfile.provider.DocumentFile +import java.io.* + +class FileParameters(ctx: Context, input: String, output: String) { + private var context: Context + + private var inputPath: String + private var outputPath: String + + init { + this.context = ctx + this.inputPath = input + this.outputPath = output + } + + private fun getUri(): Uri? { + val persistedUriPermissions = context.contentResolver.persistedUriPermissions + if (persistedUriPermissions.size > 0) { + val uriPermission = persistedUriPermissions[0] + return uriPermission.uri + } + return null + } + + private fun getDocumentFileByPath(path: String): DocumentFile { + var doc = DocumentFile.fromTreeUri(context, getUri()!!) + val parts = path.split("/") + for (i in parts.indices) { + val nextFile = doc?.findFile(parts[i]) + if(nextFile != null){ + doc = nextFile + } + } + if (doc != null){ + return doc + } else { + throw Resources.NotFoundException("File not found") + } + } + + fun getFileOutputStream(append: Boolean): OutputStream? { + val path = outputPath + return try{ + FileOutputStream(path, append) + } catch(e: IOException){ + val documentFile: DocumentFile = this.getDocumentFileByPath(path) + val documentUri = documentFile.uri + val mode = if(append) "wa" else "w" + context.contentResolver.openOutputStream(documentUri, mode) + } + } + + fun getFileInputStream(): InputStream? { + val path = inputPath + return try{ + FileInputStream(path) + } catch(e: IOException){ + val documentFile: DocumentFile = this.getDocumentFileByPath(path) + val documentUri = documentFile.uri + context.contentResolver.openInputStream(documentUri) + } + } + + fun outputExists(): Boolean { + return File(outputPath).exists() + } +} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/HashAlgorithm.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/HashAlgorithm.kt deleted file mode 100644 index 904f7ce..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/HashAlgorithm.kt +++ /dev/null @@ -1,50 +0,0 @@ -package fr.pointcheval.native_crypto_android.utils - -import java.security.MessageDigest - -@Suppress("EnumEntryName") -enum class HashAlgorithm(val bitsCount: Int) { - sha256(256), - sha384(384), - sha512(512); - - fun messageDigestString(): String { - return when (this) { - sha256 -> "SHA-256" - sha384 -> "SHA-384" - sha512 -> "SHA-512" - } - } - - fun hmacString(): String { - return when (this) { - sha256 -> "HmacSHA256" - sha384 -> "HmacSHA384" - sha512 -> "HmacSHA512" - } - } - - fun pbkdf2String(): String { - return when (this) { - sha256 -> "PBKDF2WithHmacSHA256" - sha384 -> "PBKDF2WithHmacSHA384" - sha512 -> "PBKDF2WithHmacSHA512" - } - } - - fun digest(data: ByteArray): ByteArray { - val md = MessageDigest.getInstance(messageDigestString()) - return md.digest(data) - } - - companion object { - fun digest(data: ByteArray, algorithm: String): ByteArray { - for (h in values()) { - if (h.name == algorithm) { - return h.digest(data) - } - } - throw Exception("Unknown HashAlgorithm: $algorithm") - } - } -} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/HashAlgorithmParser.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/HashAlgorithmParser.kt new file mode 100644 index 0000000..0114acb --- /dev/null +++ b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/HashAlgorithmParser.kt @@ -0,0 +1,31 @@ +package fr.pointcheval.native_crypto_android.utils + +import fr.pointcheval.native_crypto_android.HashAlgorithm +import java.security.MessageDigest +import javax.crypto.Mac + +object HashAlgorithmParser { + fun getMessageDigest(algorithm: HashAlgorithm): MessageDigest { + return when (algorithm) { + HashAlgorithm.SHA256 -> MessageDigest.getInstance("SHA-256") + HashAlgorithm.SHA384 -> MessageDigest.getInstance("SHA-384") + HashAlgorithm.SHA512 -> MessageDigest.getInstance("SHA-512") + } + } + + fun getMac(algorithm: HashAlgorithm): Mac { + return when (algorithm) { + HashAlgorithm.SHA256 -> Mac.getInstance("HmacSHA256") + HashAlgorithm.SHA384 -> Mac.getInstance("HmacSHA384") + HashAlgorithm.SHA512 -> Mac.getInstance("HmacSHA512") + } + } + + fun getPbkdf2String(algorithm: HashAlgorithm): String { + return when (algorithm) { + HashAlgorithm.SHA256 -> "PBKDF2WithHmacSHA256" + HashAlgorithm.SHA384 -> "PBKDF2WithHmacSHA384" + HashAlgorithm.SHA512 -> "PBKDF2WithHmacSHA512" + } + } +} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/KdfAlgorithm.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/KdfAlgorithm.kt deleted file mode 100644 index 4ef9f69..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/KdfAlgorithm.kt +++ /dev/null @@ -1,5 +0,0 @@ -package fr.pointcheval.native_crypto_android.utils - -enum class KdfAlgorithm { - pbkdf2 -} \ No newline at end of file diff --git a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/Task.kt b/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/Task.kt deleted file mode 100644 index d4832de..0000000 --- a/packages/native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/utils/Task.kt +++ /dev/null @@ -1,44 +0,0 @@ -package fr.pointcheval.native_crypto_android.utils - -class Task(private var task: () -> T) { - - private var successful = false - private var result: T? = null - private var exception: Exception? = null - - fun isSuccessful(): Boolean { - return successful - } - - fun getResult(): T { - if (successful && result != null) { - return result!! - } else { - throw Exception("No result found!") - } - } - - fun getException(): Exception { - if (exception != null) { - return exception!! - } else { - throw Exception("No exception found!") - } - } - - fun call() { - try { - result = task() - exception = null - successful = true - } catch (e: Exception) { - exception = e - result = null - successful = false - } - } - - fun finalize(callback: (task: Task) -> Unit) { - callback(this) - } -} \ No newline at end of file diff --git a/packages/native_crypto_ios/LICENSE b/packages/native_crypto_ios/LICENSE index 8c5c1f7..d243b55 100644 --- a/packages/native_crypto_ios/LICENSE +++ b/packages/native_crypto_ios/LICENSE @@ -2,7 +2,7 @@ NativeCrypto - iOS Implementation MIT License -Copyright (c) 2019 - 2022 Hugo Pointcheval +Copyright (c) 2019 - 2023 Hugo Pointcheval Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/packages/native_crypto_ios/README.md b/packages/native_crypto_ios/README.md index a60dd49..beb7c0e 100644 --- a/packages/native_crypto_ios/README.md +++ b/packages/native_crypto_ios/README.md @@ -1,15 +1,13 @@ # NativeCrypto - iOS Implementation -iOS Implementation of NativeCrypto Plugin. +iOS Implementation of [NativeCrypto][1] Plugin. ## Getting Started -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. +This project is a starting point for a Flutter [plug-in package][2], a specialized package that includes platform-specific implementation code for Android and/or iOS. -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +For help getting started with Flutter, view our [online documentation][3], which offers tutorials, samples, guidance on mobile development, and a full API reference. +[1]: ../../README.md +[2]: https://flutter.dev/developing-packages/ +[3]: https://flutter.dev/docs diff --git a/packages/native_crypto_ios/ios/Classes/NativeCrypto.swift b/packages/native_crypto_ios/ios/Classes/NativeCrypto.swift new file mode 100644 index 0000000..ac94c52 --- /dev/null +++ b/packages/native_crypto_ios/ios/Classes/NativeCrypto.swift @@ -0,0 +1,113 @@ +// +// NativeCrypto.swift +// native_crypto_ios +// +// Created by Hugo Pointcheval on 04/04/2023. +// + +import Foundation +import CryptoKit + +public class NativeCrypto: NSObject, NativeCryptoAPI { + func hash(data: FlutterStandardTypedData, algorithm: HashAlgorithm) throws -> FlutterStandardTypedData? { + var md = HashAlgorithmParser.getMessageDigest(algorithm: algorithm) + md.update(data: data.data) + + let bytes = Data(md.finalize()) + + return FlutterStandardTypedData(bytes: bytes) + } + + func hmac(data: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: HashAlgorithm) throws -> FlutterStandardTypedData? { + let symmetricKey : SymmetricKey = SymmetricKey.init(data: key.data) + + switch algorithm { + case .sha256: return FlutterStandardTypedData(bytes: Data(HMAC(key: symmetricKey).finalize())) + case .sha384: return FlutterStandardTypedData(bytes: Data(HMAC(key: symmetricKey).finalize())) + case .sha512: return FlutterStandardTypedData(bytes: Data(HMAC(key: symmetricKey).finalize())) + } + } + + func generateSecureRandom(length: Int64) throws -> FlutterStandardTypedData? { + let lengthInt = Int(truncatingIfNeeded: length) + let bitCount = lengthInt * 8 + let symmetricKey = SymmetricKey.init(size: SymmetricKeySize(bitCount: bitCount)) + let bytes = symmetricKey.withUnsafeBytes + { + return Data(Array($0)) + } + + return FlutterStandardTypedData(bytes: bytes) + } + + func pbkdf2(password: FlutterStandardTypedData, salt: FlutterStandardTypedData, length: Int64, iterations: Int64, algorithm: HashAlgorithm) throws -> FlutterStandardTypedData? { + let lengthInt = Int(truncatingIfNeeded: length) + let iterationsInt = Int(truncatingIfNeeded: iterations) + let pbkdf2 = Pbkdf2(length: lengthInt, iterations: iterationsInt, hashAlgorithm: algorithm) + pbkdf2.initialize(password: password.data, salt: salt.data) + let data = try? pbkdf2.derive() + + if (data == nil) { + return nil + } + + return FlutterStandardTypedData(bytes: data!) + } + + func encrypt(plainText: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> FlutterStandardTypedData? { + let aes = AESCipher() + let bytes = try? aes.encrypt(data: plainText.data, key: key.data, predefinedIV: nil) + + if (bytes == nil) { + return nil + } + + return FlutterStandardTypedData(bytes: bytes!) + } + + func encryptWithIV(plainText: FlutterStandardTypedData, iv: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> FlutterStandardTypedData? { + let aes = AESCipher() + let bytes = try? aes.encrypt(data: plainText.data, key: key.data, predefinedIV: iv.data) + + if (bytes == nil) { + return nil + } + + return FlutterStandardTypedData(bytes: bytes!) + } + + func decrypt(cipherText: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> FlutterStandardTypedData? { + let aes = AESCipher() + let bytes = try? aes.decrypt(data: cipherText.data, key: key.data) + + if (bytes == nil) { + return nil + } + + return FlutterStandardTypedData(bytes: bytes!) + } + + func encryptFile(plainTextPath: String, cipherTextPath: String, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> Bool? { + let aes = AESCipher() + let params = FileParameters(input: plainTextPath, output: cipherTextPath) + let success = try? aes.encryptFile(fileParameters: params, key: key.data, predefinedIV: nil) + + return success + } + + func encryptFileWithIV(plainTextPath: String, cipherTextPath: String, iv: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> Bool? { + let aes = AESCipher() + let params = FileParameters(input: plainTextPath, output: cipherTextPath) + let success = try? aes.encryptFile(fileParameters: params, key: key.data, predefinedIV: iv.data) + + return success + } + + func decryptFile(cipherTextPath: String, plainTextPath: String, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> Bool? { + let aes = AESCipher() + let params = FileParameters(input: cipherTextPath, output: plainTextPath) + let success = try? aes.decryptFile(fileParameters: params, key: key.data) + + return success + } +} diff --git a/packages/native_crypto_ios/ios/Classes/SwiftNativeCryptoIosPlugin.swift b/packages/native_crypto_ios/ios/Classes/SwiftNativeCryptoIosPlugin.swift index c9b1a84..8a6a342 100644 --- a/packages/native_crypto_ios/ios/Classes/SwiftNativeCryptoIosPlugin.swift +++ b/packages/native_crypto_ios/ios/Classes/SwiftNativeCryptoIosPlugin.swift @@ -3,144 +3,9 @@ import UIKit @available(iOS 13.0, *) public class SwiftNativeCryptoIosPlugin: NSObject, FlutterPlugin { - static let name: String = "plugins.hugop.cl/native_crypto" - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: name, binaryMessenger: registrar.messenger()) - let instance = SwiftNativeCryptoIosPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) + let messenger : FlutterBinaryMessenger = registrar.messenger() + let api : NativeCryptoAPI & NSObjectProtocol = NativeCrypto.init() + NativeCryptoAPISetup.setUp(binaryMessenger: messenger, api: api); } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "digest": _call(task: handleDigest(call: call), result: result) - case "generateSecretKey": _call(task: handleGenerateSecretKey(call: call), result: result) - case "pbkdf2": _call(task: handlePbkdf2(call: call), result: result) - case "encryptAsList": _call(task: handleEncryptAsList(call: call), result: result) - case "decryptAsList": _call(task: handleDecryptAsList(call: call), result: result) - case "encrypt": _call(task: handleCrypt(call: call, forEncryption: true), result: result) - case "decrypt": _call(task: handleCrypt(call: call, forEncryption: false), result: result) - default: result(FlutterMethodNotImplemented) - } - } - - private func _call(task: Task, result: @escaping FlutterResult) { - task.call() - task.finalize(callback: {(task: Task) in - if (task.isSuccessful()) { - result(task.getResult()!) - } else { - let exception: Error = task.getException() - let message = exception.localizedDescription - result(FlutterError(code: "native_crypto", message: message, details: nil)) - } - }) - } - - private func handleDigest(call: FlutterMethodCall) -> Task { - return Task(task: { - let args : NSDictionary = call.arguments as! NSDictionary - - let data : Data = (args["data"] as! FlutterStandardTypedData).data - let algorithm : String = args["algorithm"] as! String - - return FlutterStandardTypedData.init(bytes: try HashAlgorithm.digest(data: data, algorithm: algorithm)) - }) - } - - private func handleGenerateSecretKey(call: FlutterMethodCall) -> Task { - return Task(task: { - let args : NSDictionary = call.arguments as! NSDictionary - - let bitsCount : NSNumber = args["bitsCount"] as! NSNumber - - return FlutterStandardTypedData.init(bytes: SecretKey(fromSecureRandom: bitsCount.intValue).bytes) - }) - } - - private func handlePbkdf2(call: FlutterMethodCall) -> Task { - return Task(task: { - let args : NSDictionary = call.arguments as! NSDictionary - - let password : String = args["password"] as! String - let salt : String = args["salt"] as! String - let keyBytesCount : NSNumber = args["keyBytesCount"] as! NSNumber - let iterations : NSNumber = args["iterations"] as! NSNumber - let algorithm : String = args["algorithm"] as! String - - let pbkdf2 : Pbkdf2 = Pbkdf2(keyBytesCount: keyBytesCount.intValue, iterations: iterations.intValue) - pbkdf2.hash = HashAlgorithm.init(rawValue: algorithm) ?? pbkdf2.hash - pbkdf2.initialize(password: password, salt: salt) - - return FlutterStandardTypedData.init(bytes: try pbkdf2.derive().bytes) - }) - } - - private func handleEncryptAsList(call: FlutterMethodCall) -> Task> { - return Task(task: { - let args : NSDictionary = call.arguments as! NSDictionary - - let data : Data = (args["data"] as! FlutterStandardTypedData).data - let key : Data = (args["key"] as! FlutterStandardTypedData).data - let algorithm : String = args["algorithm"] as! String - - let cipherAlgorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algorithm) - var cipher : Cipher - if (cipherAlgorithm != nil) { - cipher = cipherAlgorithm!.getCipher - } else { - throw NativeCryptoError.cipherError - } - - return try cipher.encryptAsList(data: data, key: key) - }) - } - - private func handleDecryptAsList(call: FlutterMethodCall) -> Task { - return Task(task: { - let args : NSDictionary = call.arguments as! NSDictionary - - let data = args["data"] as! NSArray - let key : Data = (args["key"] as! FlutterStandardTypedData).data - let algorithm : String = args["algorithm"] as! String - - let iv = (data[0] as! FlutterStandardTypedData).data - let encrypted = (data[1] as! FlutterStandardTypedData).data - - let cipherAlgorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algorithm) - var cipher : Cipher - if (cipherAlgorithm != nil) { - cipher = cipherAlgorithm!.getCipher - } else { - throw NativeCryptoError.cipherError - } - - return FlutterStandardTypedData.init(bytes: try cipher.decryptAsList(data: [iv, encrypted], key: key)) - }) - } - - private func handleCrypt(call: FlutterMethodCall, forEncryption: Bool) -> Task { - return Task(task: { - let args : NSDictionary = call.arguments as! NSDictionary - - let data : Data = (args["data"] as! FlutterStandardTypedData).data - let key : Data = (args["key"] as! FlutterStandardTypedData).data - let algorithm : String = args["algorithm"] as! String - - let cipherAlgorithm : CipherAlgorithm? = CipherAlgorithm.init(rawValue: algorithm) - var cipher : Cipher - if (cipherAlgorithm != nil) { - cipher = cipherAlgorithm!.getCipher - } else { - throw NativeCryptoError.cipherError - } - - if (forEncryption) { - return FlutterStandardTypedData.init(bytes: try cipher.encrypt(data: data, key: key)) - } else { - return FlutterStandardTypedData.init(bytes: try cipher.decrypt(data: data, key: key)) - } - }) - } - } diff --git a/packages/native_crypto_ios/ios/Classes/ciphers/AESCipher.swift b/packages/native_crypto_ios/ios/Classes/ciphers/AESCipher.swift index 2110ff5..20c48ff 100644 --- a/packages/native_crypto_ios/ios/Classes/ciphers/AESCipher.swift +++ b/packages/native_crypto_ios/ios/Classes/ciphers/AESCipher.swift @@ -9,17 +9,33 @@ import Foundation import CryptoKit class AESCipher : Cipher { - var algorithm: CipherAlgorithm = CipherAlgorithm.aes - /// Encrypts plaintext with key using AES GCM @available(iOS 13.0, *) - func encrypt(data: Data, key: Data) throws -> Data { + func encrypt(data: Data, key: Data, predefinedIV: Data?) throws -> Data { let symmetricKey : SymmetricKey = SymmetricKey.init(data: key) - let encrypted : AES.GCM.SealedBox? = try? AES.GCM.seal(data, using: symmetricKey) - let encryptedData : Data? = encrypted?.combined + // Encryption + var encrypted : AES.GCM.SealedBox + do { + // If predefinedIV is not null use it + if (predefinedIV != nil) { + let nonce = try AES.GCM.Nonce(data: predefinedIV!) + encrypted = try AES.GCM.seal(data, using: symmetricKey, nonce: nonce) + } else { + encrypted = try AES.GCM.seal(data, using: symmetricKey) + } + } catch CryptoKitError.incorrectKeySize { + throw NativeCryptoError.invalidKeySize() + } catch CryptoKitError.invalidParameter, CryptoKitError.incorrectParameterSize { + throw NativeCryptoError.invalidParameter() + } catch { + throw NativeCryptoError.unknownError(reason: "An error occured during encryption.") + } + + // NONCE[12] || CIPHERTEXT[n] || TAG[16] + let encryptedData : Data? = encrypted.combined if (encryptedData == nil) { - throw NativeCryptoError.encryptionError + throw NativeCryptoError.unknownError(reason: "An error occured during ciphertext combination.") } return encryptedData! } @@ -28,30 +44,65 @@ class AESCipher : Cipher { @available(iOS 13.0, *) func decrypt(data: Data, key: Data) throws -> Data { let symmetricKey = SymmetricKey.init(data: key) - let sealedBox = try? AES.GCM.SealedBox(combined: data) - if (sealedBox == nil) { return Data.init() } - let decryptedData = try? AES.GCM.open(sealedBox!, using: symmetricKey) - if (decryptedData == nil) { - throw NativeCryptoError.decryptionError + + // SealedBox initialization + var encrypted : AES.GCM.SealedBox + do { + encrypted = try AES.GCM.SealedBox(combined: data) + } catch { + throw NativeCryptoError.unknownError(reason: "An error occured during sealedbox initialization.") } - return decryptedData! - } - - func encryptAsList(data: Data, key: Data) throws -> [Data] { - let encryptedData = try encrypt(data: data, key: key) - let iv = encryptedData.prefix(12) - let data = encryptedData.suffix(from: 12) + // Decryption + var decryptedData : Data + do { + decryptedData = try AES.GCM.open(encrypted, using: symmetricKey) + } catch CryptoKitError.incorrectKeySize { + throw NativeCryptoError.invalidKeySize() + } catch CryptoKitError.invalidParameter, CryptoKitError.incorrectParameterSize { + throw NativeCryptoError.invalidParameter() + } catch CryptoKitError.authenticationFailure { + throw NativeCryptoError.authenticationError() + } catch { + throw NativeCryptoError.unknownError(reason: "An error occured during decryption.") + } - return [iv, data] - } - - func decryptAsList(data: [Data], key: Data) throws -> Data { - var encryptedData = data.first! - let data = data.last! - encryptedData.append(data) - - let decryptedData = try decrypt(data: encryptedData, key: key) return decryptedData } + + /// Encrypts plaintext file with key using AES GCM + func encryptFile(fileParameters: FileParameters, key: Data, predefinedIV: Data?) throws -> Bool { + let fileManager = FileManager.default + let inputFile = URL(fileURLWithPath: fileParameters.inputPath) + + guard let data = fileManager.contents(atPath: inputFile.path) else { + throw NativeCryptoError.ioError(reason: "Error while reading input file.") + } + + let encryptedData = try encrypt(data: data, key: key, predefinedIV: predefinedIV) + + guard fileManager.createFile(atPath: fileParameters.outputPath, contents: encryptedData, attributes: nil) else { + throw NativeCryptoError.ioError(reason: "Error while writing output file.") + } + + return true + } + + /// Decrypts ciphertext file with key using AES GCM + func decryptFile(fileParameters: FileParameters, key: Data) throws -> Bool { + let fileManager = FileManager.default + let inputFile = URL(fileURLWithPath: fileParameters.inputPath) + + guard let data = fileManager.contents(atPath: inputFile.path) else { + throw NativeCryptoError.ioError(reason: "Error while reading input file.") + } + + let decryptedData = try decrypt(data: data, key: key) + + guard fileManager.createFile(atPath: fileParameters.outputPath, contents: decryptedData, attributes: nil) else { + throw NativeCryptoError.ioError(reason: "Error while writing output file.") + } + + return true + } } diff --git a/packages/native_crypto_ios/ios/Classes/kdf/Pbkdf2.swift b/packages/native_crypto_ios/ios/Classes/kdf/Pbkdf2.swift index 8a34a1e..0377904 100644 --- a/packages/native_crypto_ios/ios/Classes/kdf/Pbkdf2.swift +++ b/packages/native_crypto_ios/ios/Classes/kdf/Pbkdf2.swift @@ -9,45 +9,42 @@ import Foundation import CommonCrypto class Pbkdf2 : KeyDerivation { - var algorithm: KdfAlgorithm = KdfAlgorithm.pbkdf2 - - var keyBytesCount: Int + var length: Int var iterations: Int - var hash: HashAlgorithm = HashAlgorithm.HashSHA256 + var hashAlgorithm: HashAlgorithm - var password: String? = nil - var salt: String? = nil + var password: Data? = nil + var salt: Data? = nil - init(keyBytesCount: Int, iterations: Int) { - self.keyBytesCount = keyBytesCount + init(length: Int, iterations: Int, hashAlgorithm: HashAlgorithm) { + self.length = length self.iterations = iterations + self.hashAlgorithm = hashAlgorithm } - func initialize(password: String, salt: String) { + func initialize(password: Data, salt: Data) { self.password = password self.salt = salt } - func derive() throws -> SecretKey { + func derive() throws -> Data? { if (password == nil || salt == nil) { - throw NativeCryptoError.pbkdf2Error + throw NativeCryptoError.kdfError(reason: "Password and salt cannot be null.") } - let passwordData = password!.data(using: .utf8)! - let saltData = salt!.data(using: .utf8)! - - var derivedKeyData = Data(repeating: 0, count: keyBytesCount) + var derivedKeyData = Data(repeating: 0, count: length) let localDerivedKeyData = derivedKeyData + let identifier = HashAlgorithmParser.getPbkdf2Identifier(algorithm: hashAlgorithm) let status = derivedKeyData.withUnsafeMutableBytes { (derivedKeyBytes: UnsafeMutableRawBufferPointer) in - saltData.withUnsafeBytes { (saltBytes: UnsafeRawBufferPointer) in + salt!.withUnsafeBytes { (saltBytes: UnsafeRawBufferPointer) in CCKeyDerivationPBKDF( CCPBKDFAlgorithm(kCCPBKDF2), - password, - passwordData.count, + (password! as NSData).bytes, + password!.count, saltBytes.bindMemory(to: UInt8.self).baseAddress, - saltData.count, - hash.pbkdf2identifier, + salt!.count, + identifier, UInt32(iterations), derivedKeyBytes.bindMemory(to: UInt8.self).baseAddress, localDerivedKeyData.count) @@ -55,9 +52,9 @@ class Pbkdf2 : KeyDerivation { } if (status != kCCSuccess) { - throw NativeCryptoError.pbkdf2Error + throw NativeCryptoError.kdfError() } - return SecretKey(derivedKeyData) + return derivedKeyData } } diff --git a/packages/native_crypto_ios/ios/Classes/keys/SecretKey.swift b/packages/native_crypto_ios/ios/Classes/keys/SecretKey.swift deleted file mode 100644 index 6325143..0000000 --- a/packages/native_crypto_ios/ios/Classes/keys/SecretKey.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SecretKey.swift -// native_crypto_ios -// -// Created by Hugo Pointcheval on 25/05/2022. -// - -import Foundation -import CryptoKit - -class SecretKey : Key { - var bytes: Data - - init(_ bytes: Data) { - self.bytes = bytes - } - - init(fromSecureRandom bitsCount: Int) { - let symmetricKey = SymmetricKey.init(size: SymmetricKeySize(bitCount: bitsCount)) - bytes = symmetricKey.withUnsafeBytes - { - return Data(Array($0)) - } - } -} diff --git a/packages/native_crypto_ios/ios/Classes/messages.g.swift b/packages/native_crypto_ios/ios/Classes/messages.g.swift new file mode 100644 index 0000000..f9c5a5d --- /dev/null +++ b/packages/native_crypto_ios/ios/Classes/messages.g.swift @@ -0,0 +1,243 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// -- +// Autogenerated from Pigeon (v9.2.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon + +import Foundation +#if os(iOS) +import Flutter +#elseif os(macOS) +import FlutterMacOS +#else +#error("Unsupported platform.") +#endif + + + +private func wrapResult(_ result: Any?) -> [Any?] { + return [result] +} + +private func wrapError(_ error: Any) -> [Any?] { + if let flutterError = error as? FlutterError { + return [ + flutterError.code, + flutterError.message, + flutterError.details + ] + } + return [ + "\(error)", + "\(type(of: error))", + "Stacktrace: \(Thread.callStackSymbols)" + ] +} + +enum HashAlgorithm: Int { + case sha256 = 0 + case sha384 = 1 + case sha512 = 2 +} + +enum CipherAlgorithm: Int { + case aes = 0 +} +/// Generated protocol from Pigeon that represents a handler of messages from Flutter. +protocol NativeCryptoAPI { + func hash(data: FlutterStandardTypedData, algorithm: HashAlgorithm) throws -> FlutterStandardTypedData? + func hmac(data: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: HashAlgorithm) throws -> FlutterStandardTypedData? + func generateSecureRandom(length: Int64) throws -> FlutterStandardTypedData? + func pbkdf2(password: FlutterStandardTypedData, salt: FlutterStandardTypedData, length: Int64, iterations: Int64, algorithm: HashAlgorithm) throws -> FlutterStandardTypedData? + func encrypt(plainText: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> FlutterStandardTypedData? + func encryptWithIV(plainText: FlutterStandardTypedData, iv: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> FlutterStandardTypedData? + func decrypt(cipherText: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> FlutterStandardTypedData? + func encryptFile(plainTextPath: String, cipherTextPath: String, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> Bool? + func encryptFileWithIV(plainTextPath: String, cipherTextPath: String, iv: FlutterStandardTypedData, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> Bool? + func decryptFile(cipherTextPath: String, plainTextPath: String, key: FlutterStandardTypedData, algorithm: CipherAlgorithm) throws -> Bool? +} + +/// Generated setup class from Pigeon to handle messages through the `binaryMessenger`. +class NativeCryptoAPISetup { + /// The codec used by NativeCryptoAPI. + /// Sets up an instance of `NativeCryptoAPI` to handle messages through the `binaryMessenger`. + static func setUp(binaryMessenger: FlutterBinaryMessenger, api: NativeCryptoAPI?) { + let hashChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.hash", binaryMessenger: binaryMessenger) + if let api = api { + hashChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let dataArg = args[0] as! FlutterStandardTypedData + let algorithmArg = HashAlgorithm(rawValue: args[1] as! Int)! + do { + let result = try api.hash(data: dataArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + hashChannel.setMessageHandler(nil) + } + let hmacChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.hmac", binaryMessenger: binaryMessenger) + if let api = api { + hmacChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let dataArg = args[0] as! FlutterStandardTypedData + let keyArg = args[1] as! FlutterStandardTypedData + let algorithmArg = HashAlgorithm(rawValue: args[2] as! Int)! + do { + let result = try api.hmac(data: dataArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + hmacChannel.setMessageHandler(nil) + } + let generateSecureRandomChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom", binaryMessenger: binaryMessenger) + if let api = api { + generateSecureRandomChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let lengthArg = (args[0] is Int) ? Int64(args[0] as! Int) : args[0] as! Int64 + do { + let result = try api.generateSecureRandom(length: lengthArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + generateSecureRandomChannel.setMessageHandler(nil) + } + let pbkdf2Channel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.pbkdf2", binaryMessenger: binaryMessenger) + if let api = api { + pbkdf2Channel.setMessageHandler { message, reply in + let args = message as! [Any] + let passwordArg = args[0] as! FlutterStandardTypedData + let saltArg = args[1] as! FlutterStandardTypedData + let lengthArg = (args[2] is Int) ? Int64(args[2] as! Int) : args[2] as! Int64 + let iterationsArg = (args[3] is Int) ? Int64(args[3] as! Int) : args[3] as! Int64 + let algorithmArg = HashAlgorithm(rawValue: args[4] as! Int)! + do { + let result = try api.pbkdf2(password: passwordArg, salt: saltArg, length: lengthArg, iterations: iterationsArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + pbkdf2Channel.setMessageHandler(nil) + } + let encryptChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.encrypt", binaryMessenger: binaryMessenger) + if let api = api { + encryptChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let plainTextArg = args[0] as! FlutterStandardTypedData + let keyArg = args[1] as! FlutterStandardTypedData + let algorithmArg = CipherAlgorithm(rawValue: args[2] as! Int)! + do { + let result = try api.encrypt(plainText: plainTextArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + encryptChannel.setMessageHandler(nil) + } + let encryptWithIVChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV", binaryMessenger: binaryMessenger) + if let api = api { + encryptWithIVChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let plainTextArg = args[0] as! FlutterStandardTypedData + let ivArg = args[1] as! FlutterStandardTypedData + let keyArg = args[2] as! FlutterStandardTypedData + let algorithmArg = CipherAlgorithm(rawValue: args[3] as! Int)! + do { + let result = try api.encryptWithIV(plainText: plainTextArg, iv: ivArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + encryptWithIVChannel.setMessageHandler(nil) + } + let decryptChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.decrypt", binaryMessenger: binaryMessenger) + if let api = api { + decryptChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let cipherTextArg = args[0] as! FlutterStandardTypedData + let keyArg = args[1] as! FlutterStandardTypedData + let algorithmArg = CipherAlgorithm(rawValue: args[2] as! Int)! + do { + let result = try api.decrypt(cipherText: cipherTextArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + decryptChannel.setMessageHandler(nil) + } + let encryptFileChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.encryptFile", binaryMessenger: binaryMessenger) + if let api = api { + encryptFileChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let plainTextPathArg = args[0] as! String + let cipherTextPathArg = args[1] as! String + let keyArg = args[2] as! FlutterStandardTypedData + let algorithmArg = CipherAlgorithm(rawValue: args[3] as! Int)! + do { + let result = try api.encryptFile(plainTextPath: plainTextPathArg, cipherTextPath: cipherTextPathArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + encryptFileChannel.setMessageHandler(nil) + } + let encryptFileWithIVChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV", binaryMessenger: binaryMessenger) + if let api = api { + encryptFileWithIVChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let plainTextPathArg = args[0] as! String + let cipherTextPathArg = args[1] as! String + let ivArg = args[2] as! FlutterStandardTypedData + let keyArg = args[3] as! FlutterStandardTypedData + let algorithmArg = CipherAlgorithm(rawValue: args[4] as! Int)! + do { + let result = try api.encryptFileWithIV(plainTextPath: plainTextPathArg, cipherTextPath: cipherTextPathArg, iv: ivArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + encryptFileWithIVChannel.setMessageHandler(nil) + } + let decryptFileChannel = FlutterBasicMessageChannel(name: "dev.flutter.pigeon.NativeCryptoAPI.decryptFile", binaryMessenger: binaryMessenger) + if let api = api { + decryptFileChannel.setMessageHandler { message, reply in + let args = message as! [Any] + let cipherTextPathArg = args[0] as! String + let plainTextPathArg = args[1] as! String + let keyArg = args[2] as! FlutterStandardTypedData + let algorithmArg = CipherAlgorithm(rawValue: args[3] as! Int)! + do { + let result = try api.decryptFile(cipherTextPath: cipherTextPathArg, plainTextPath: plainTextPathArg, key: keyArg, algorithm: algorithmArg) + reply(wrapResult(result)) + } catch { + reply(wrapError(error)) + } + } + } else { + decryptFileChannel.setMessageHandler(nil) + } + } +} diff --git a/packages/native_crypto_ios/ios/Classes/protocols/Cipher.swift b/packages/native_crypto_ios/ios/Classes/protocols/Cipher.swift index f1291f9..024c5f8 100644 --- a/packages/native_crypto_ios/ios/Classes/protocols/Cipher.swift +++ b/packages/native_crypto_ios/ios/Classes/protocols/Cipher.swift @@ -8,9 +8,8 @@ import Foundation protocol Cipher { - var algorithm: CipherAlgorithm { get } - func encrypt(data: Data, key: Data) throws -> Data + func encrypt(data: Data, key: Data, predefinedIV: Data?) throws -> Data func decrypt(data: Data, key: Data) throws -> Data - func encryptAsList(data: Data, key: Data) throws -> [Data] - func decryptAsList(data: [Data], key: Data) throws-> Data + func encryptFile(fileParameters: FileParameters, key: Data, predefinedIV: Data?) throws -> Bool + func decryptFile(fileParameters: FileParameters, key: Data) throws -> Bool } diff --git a/packages/native_crypto_ios/ios/Classes/protocols/Key.swift b/packages/native_crypto_ios/ios/Classes/protocols/Key.swift deleted file mode 100644 index 9fc9199..0000000 --- a/packages/native_crypto_ios/ios/Classes/protocols/Key.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Key.swift -// native_crypto_ios -// -// Created by Hugo Pointcheval on 25/05/2022. -// - -import Foundation - -protocol Key { - var bytes: Data { get set } -} diff --git a/packages/native_crypto_ios/ios/Classes/protocols/KeyDerivation.swift b/packages/native_crypto_ios/ios/Classes/protocols/KeyDerivation.swift index df3a0d5..f5ff3fb 100644 --- a/packages/native_crypto_ios/ios/Classes/protocols/KeyDerivation.swift +++ b/packages/native_crypto_ios/ios/Classes/protocols/KeyDerivation.swift @@ -8,6 +8,5 @@ import Foundation protocol KeyDerivation { - var algorithm : KdfAlgorithm { get } - func derive() throws -> SecretKey + func derive() throws -> Data? } diff --git a/packages/native_crypto_ios/ios/Classes/utils/CipherAlgorithm.swift b/packages/native_crypto_ios/ios/Classes/utils/CipherAlgorithm.swift deleted file mode 100644 index afb9094..0000000 --- a/packages/native_crypto_ios/ios/Classes/utils/CipherAlgorithm.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// CipherAlgorithm.swift -// native_crypto_ios -// -// Created by Hugo Pointcheval on 25/05/2022. -// - -import Foundation - -enum CipherAlgorithm : String { - case aes = "aes" - - var getCipher: Cipher { - switch self { - case .aes: return AESCipher() - } - } -} diff --git a/packages/native_crypto_ios/ios/Classes/utils/FileParameters.swift b/packages/native_crypto_ios/ios/Classes/utils/FileParameters.swift new file mode 100644 index 0000000..dbdbf29 --- /dev/null +++ b/packages/native_crypto_ios/ios/Classes/utils/FileParameters.swift @@ -0,0 +1,20 @@ +// +// FileParameters.swift +// native_crypto_ios +// +// Created by Hugo Pointcheval on 07/01/2023. +// + +import Foundation + +class FileParameters { + var inputPath: String + var outputPath: String + + init(input: String, output: String) { + self.inputPath = input + self.outputPath = output + } + + +} diff --git a/packages/native_crypto_ios/ios/Classes/utils/HashAlgorithm.swift b/packages/native_crypto_ios/ios/Classes/utils/HashAlgorithm.swift deleted file mode 100644 index 38b7740..0000000 --- a/packages/native_crypto_ios/ios/Classes/utils/HashAlgorithm.swift +++ /dev/null @@ -1,45 +0,0 @@ -// -// HashAlgorithm.swift -// native_crypto_ios -// -// Created by Hugo Pointcheval on 25/05/2022. -// - -import Foundation -import CommonCrypto -import CryptoKit - -enum HashAlgorithm: String { - case HashSHA256 = "sha256" - case HashSHA384 = "sha384" - case HashSHA512 = "sha512" - - var pbkdf2identifier: UInt32 { - switch self { - case .HashSHA256: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256) - case .HashSHA384: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA384) - case .HashSHA512: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512) - } - } - - @available(iOS 13.0, *) - func digest(data: Data) -> Data { - switch self { - case .HashSHA256: - return Data(SHA256.hash(data: data)) - case .HashSHA384: - return Data(SHA384.hash(data: data)) - case .HashSHA512: - return Data(SHA512.hash(data: data)) - } - } - - @available(iOS 13.0, *) - static func digest(data: Data, algorithm: String) throws -> Data { - let algo = HashAlgorithm.init(rawValue: algorithm) - if (algo == nil) { - throw NativeCryptoError.messageDigestError - } - return algo!.digest(data: data) - } -} diff --git a/packages/native_crypto_ios/ios/Classes/utils/HashAlgorithmParser.swift b/packages/native_crypto_ios/ios/Classes/utils/HashAlgorithmParser.swift new file mode 100644 index 0000000..d097a56 --- /dev/null +++ b/packages/native_crypto_ios/ios/Classes/utils/HashAlgorithmParser.swift @@ -0,0 +1,29 @@ +// +// HashAlgorithmParser.swift +// native_crypto_ios +// +// Created by Hugo Pointcheval on 04/04/2023. +// + +import Foundation +import CommonCrypto +import CryptoKit + +public class HashAlgorithmParser { + static func getMessageDigest(algorithm: HashAlgorithm) -> any HashFunction { + switch algorithm { + case .sha256: return SHA256.init() + case .sha384: return SHA384.init() + case .sha512: return SHA512.init() + } + } + + static func getPbkdf2Identifier(algorithm: HashAlgorithm) -> UInt32 { + switch algorithm { + case .sha256: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256) + case .sha384: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA384) + case .sha512: return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512) + } + } + +} diff --git a/packages/native_crypto_ios/ios/Classes/utils/KdfAlgorithm.swift b/packages/native_crypto_ios/ios/Classes/utils/KdfAlgorithm.swift deleted file mode 100644 index d6af3c1..0000000 --- a/packages/native_crypto_ios/ios/Classes/utils/KdfAlgorithm.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// KdfAlgorithm.swift -// native_crypto_ios -// -// Created by Hugo Pointcheval on 25/05/2022. -// - -import Foundation - -enum KdfAlgorithm { - case pbkdf2 -} diff --git a/packages/native_crypto_ios/ios/Classes/utils/NativeCryptoError.swift b/packages/native_crypto_ios/ios/Classes/utils/NativeCryptoError.swift index a2ac159..0a10b00 100644 --- a/packages/native_crypto_ios/ios/Classes/utils/NativeCryptoError.swift +++ b/packages/native_crypto_ios/ios/Classes/utils/NativeCryptoError.swift @@ -7,12 +7,51 @@ import Foundation -enum NativeCryptoError : Error { -case decryptionError -case encryptionError -case messageDigestError -case pbkdf2Error -case cipherError -case resultError -case exceptionError +enum NativeCryptoError : LocalizedError { + /// When an operation is not supported + case notSupported(reason: String? = nil) + /// When the key is invalid + case invalidKey(reason: String? = nil) + /// When the key length is invalid + case invalidKeySize(reason: String? = nil) + /// When operation parameter is invalid + case invalidParameter(reason: String? = nil) + /// When an authentication process fails + case authenticationError(reason: String? = nil) + /// When a input/output process (like file manipulation) fails + case ioError(reason: String? = nil) + /// When cipher initialization fails + case cipherError(reason: String? = nil) + /// When a digest process fails + case digestError(reason: String? = nil) + /// When a key derivation process fails + case kdfError(reason: String? = nil) + /// When any other expection is thrown + case unknownError(reason: String? = nil) + + var errorDescription: String? { + switch self { + case .notSupported(reason: let reason): + return reason ?? "This operation is not supported." + case .invalidKey(reason: let reason): + return reason ?? "Invalid key." + case .invalidKeySize(reason: let reason): + return reason ?? "Invalid key size." + case .invalidParameter(reason: let reason): + return reason ?? "Invalid parameter." + case .authenticationError(reason: let reason): + return reason ?? "Authentication fails." + case .ioError(reason: let reason): + return reason ?? "IO operation fails." + case .cipherError(reason: let reason): + return reason ?? "Cipher initialization fails." + case .digestError(reason: let reason): + return reason ?? "Digest fails." + case .kdfError(reason: let reason): + return reason ?? "Key derivation fails." + case .unknownError(reason: let reason): + return reason ?? "An unknown error occurred during the operation." + } + } + } diff --git a/packages/native_crypto_ios/ios/Classes/utils/Task.swift b/packages/native_crypto_ios/ios/Classes/utils/Task.swift deleted file mode 100644 index 4d11070..0000000 --- a/packages/native_crypto_ios/ios/Classes/utils/Task.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Task.swift -// native_crypto_ios -// -// Created by Hugo Pointcheval on 25/05/2022. -// - -import Foundation - -class Task { - - var task: () throws -> T - private var successful: Bool = false - private var result: T? = nil - private var exception: Error? = nil - - init(task: @escaping () throws -> T) { - self.task = task - } - - func isSuccessful() -> Bool { - return successful - } - - func getResult() -> T? { - return result - } - - func getException() -> Error { - if (exception != nil) { - return exception! - } else { - return NativeCryptoError.exceptionError - } - } - - func call() { - do { - result = try task() - exception = nil - successful = true - } catch { - exception = error - result = nil - successful = false - } - } - - func finalize(callback: (_ task: Task) -> Void) { - callback(self) - } -} diff --git a/packages/native_crypto_platform_interface/LICENSE b/packages/native_crypto_platform_interface/LICENSE index 68bb0c6..5d49a94 100644 --- a/packages/native_crypto_platform_interface/LICENSE +++ b/packages/native_crypto_platform_interface/LICENSE @@ -2,7 +2,7 @@ NativeCrypto - Platform Interface MIT License -Copyright (c) 2019 - 2022 Hugo Pointcheval +Copyright (c) 2019 - 2023 Hugo Pointcheval Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -20,4 +20,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file +SOFTWARE. diff --git a/packages/native_crypto_platform_interface/README.md b/packages/native_crypto_platform_interface/README.md index d1cde17..07bd2a9 100644 --- a/packages/native_crypto_platform_interface/README.md +++ b/packages/native_crypto_platform_interface/README.md @@ -1,12 +1,20 @@ # NativeCrypto - Platform Interface -A common platform interface for the [`native_crypto`][1] plugin. +A common platform interface for the [NativeCrypto][1] plugin. This interface allows platform-specific implementations of the `native_crypto` plugin, as well as the plugin itself, to ensure they are supporting the same interface. ## Usage -To implement a new platform-specific implementation of `native_crypto`, extend [`NativeCryptoPlatform`][2] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `NativeCryptoPlatform` by calling `NativeCryptoPlatform.instance = MyNativeCryptoPlatform()`. +To implement a new platform-specific implementation of `native_crypto`, extend [`NativeCryptoPlatform`][1] with an implementation that performs the platform-specific behavior, and when you register your plugin, set the default `NativeCryptoPlatform` by calling `NativeCryptoPlatform.instance = MyNativeCryptoPlatform()`. -[1]: ../native_crypto -[2]: lib/native_crypto_platform_interface.dart \ No newline at end of file +## Pigeon + +This package uses [Pigeon](https://pub.dev/packages/pigeon) to generate the platform interface code. + +Run generator with `flutter pub run pigeon --input pigeons/messages.dart`. + +> Note: Make sure the `lib/src/gen` folder exists before running the generator. + +[1]: ../../README.md +[2]: lib/native_crypto_platform_interface.dart diff --git a/packages/native_crypto_platform_interface/analysis_options.yaml b/packages/native_crypto_platform_interface/analysis_options.yaml index 82177cd..8c9daa4 100644 --- a/packages/native_crypto_platform_interface/analysis_options.yaml +++ b/packages/native_crypto_platform_interface/analysis_options.yaml @@ -1 +1 @@ -include: package:wyatt_analysis/analysis_options.flutter.yaml \ No newline at end of file +include: package:wyatt_analysis/analysis_options.flutter.yaml diff --git a/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart b/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart index c85bda1..cc6c0ce 100644 --- a/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart +++ b/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface.dart @@ -1,14 +1,15 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: native_crypto_platform_interface.dart -// Created Date: 24/05/2022 19:39:11 -// Last Modified: 24/05/2022 19:39:58 -// ----- -// Copyright (c) 2022 +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +/// The interface that implementations of native_crypto must implement. library native_crypto_platform_interface; -export 'src/method_channel/method_channel_native_crypto.dart'; -export 'src/platform_interface/native_crypto_platform.dart'; -export 'src/utils/exception.dart'; +export 'src/core/enums/exception_code.dart'; +export 'src/core/enums/methods.dart'; +export 'src/core/exceptions/exception.dart'; +export 'src/gen/test.g.dart'; +export 'src/implementations/basic_message_channel_native_crypto.dart'; +export 'src/interface/native_crypto_platform.dart'; diff --git a/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface_gen.dart b/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface_gen.dart new file mode 100644 index 0000000..f3f4a34 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/native_crypto_platform_interface_gen.dart @@ -0,0 +1,11 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +/// The interface that implementations of native_crypto must implement. +library native_crypto_platform_interface; + +export 'src/gen/messages.g.dart'; +export 'src/gen/test.g.dart'; diff --git a/packages/native_crypto_platform_interface/lib/src/core/enums/exception_code.dart b/packages/native_crypto_platform_interface/lib/src/core/enums/exception_code.dart new file mode 100644 index 0000000..910b9d9 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/core/enums/exception_code.dart @@ -0,0 +1,84 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +enum NativeCryptoExceptionCode { + /// The method is not implemented on the platform side. + platformMethodNotImplemented, + + /// Platform returned invalid data. + /// Can be null, empty, or not the expected type. + platformReturnedInvalidData, // TODO(hpcl): remove this + + /// The platforms returned null. + nullError, + + /// The algorithm is not supported. + algorithmNotSupported, + + /// The key is not valid. Like a bad length or format. + invalidKey, + + /// The data is not valid. + invalidData, + + /// The parameters are not valid. Like an invalid IV. + invalidParameters, + + /// Authentication failed. Like a bad MAC or tag. + authenticationError, + + /// An I/O error occurred. + ioError, + + /// Key derivation failed. + keyDerivationError, + + /// Channel error. Like a bad channel or a bad message. + channelError, + + /// An unknown error occurred. + unknownError; + + /// Returns code of the [NativeCryptoExceptionCode]. + /// ```dart + /// print(NativeCryptoExceptionCode.platformMethodNotImplemented.code) + /// // => platform_method_not_implemented + /// ``` + String get code { + switch (name.length) { + case 0: + return name; + case 1: + return name.toLowerCase(); + default: + return name + .splitMapJoin( + RegExp('[A-Z]'), + onMatch: (m) => ' ${m[0]}', + onNonMatch: (n) => n, + ) + .trim() + .splitMapJoin( + RegExp(r'\s+|-+|_+|\.+'), + onMatch: (m) => '_', + onNonMatch: (n) => n.toLowerCase(), + ); + } + } + + /// Returns the [NativeCryptoExceptionCode] from the given [code]. + static NativeCryptoExceptionCode from(String code) { + for (final value in values) { + if (value.code == code) { + return value; + } + } + return unknownError; + } + + @override + String toString() => code; +} diff --git a/packages/native_crypto_platform_interface/lib/src/core/enums/methods.dart b/packages/native_crypto_platform_interface/lib/src/core/enums/methods.dart new file mode 100644 index 0000000..024b575 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/core/enums/methods.dart @@ -0,0 +1,17 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +enum NativeCryptoMethod { + hash, + hmac, + generateSecureRandom, + pbkdf2, + encrypt, + decrypt, + encryptFile, + decryptFile, + encryptWithIV +} diff --git a/packages/native_crypto_platform_interface/lib/src/core/exceptions/exception.dart b/packages/native_crypto_platform_interface/lib/src/core/exceptions/exception.dart new file mode 100644 index 0000000..9143b13 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/core/exceptions/exception.dart @@ -0,0 +1,100 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:equatable/equatable.dart'; +import 'package:flutter/services.dart'; +import 'package:native_crypto_platform_interface/src/core/enums/exception_code.dart'; + +/// An exception thrown by the native crypto plugin. +class NativeCryptoException extends Equatable implements Exception { + /// Creates a new [NativeCryptoException]. + const NativeCryptoException({ + required this.code, + this.message, + this.stackTrace, + }); + + /// Creates a new [NativeCryptoException] from a [PlatformException]. + factory NativeCryptoException.fromPlatformException( + PlatformException platformException, + StackTrace stackTrace, + ) { + final Map? details = platformException.details != null + ? Map.from( + platformException.details as Map, + ) + : null; + + String code = platformException.code.split('(').first; + String message = platformException.message ?? ''; + + if (details != null) { + code = details['code'] ?? code; + message = details['message'] ?? message; + } + + return NativeCryptoException( + code: NativeCryptoExceptionCode.from(code), + message: message, + stackTrace: stackTrace, + ); + } + + /// The standardised error code. + final NativeCryptoExceptionCode code; + + /// The long form message of the exception. + final String? message; + + /// The stack trace which provides information to the user about the call + /// sequence that triggered an exception + final StackTrace? stackTrace; + + static Never convertPlatformException( + Object exception, + StackTrace stackTrace, + ) { + // If the exception is not a PlatformException, throw it as is. + if (exception is! Exception || exception is! PlatformException) { + Error.throwWithStackTrace(exception, stackTrace); + } + + // Otherwise, throw a NativeCryptoException. + Error.throwWithStackTrace( + NativeCryptoException.fromPlatformException(exception, stackTrace), + stackTrace, + ); + } + + NativeCryptoException copyWith({ + NativeCryptoExceptionCode? code, + String? message, + StackTrace? stackTrace, + }) => + NativeCryptoException( + code: code ?? this.code, + message: message ?? this.message, + stackTrace: stackTrace ?? this.stackTrace, + ); + + @override + String toString() { + final output = StringBuffer('[NativeCrypto/$code]'); + + if (message != null) { + output.write(' $message'); + } + + if (stackTrace != null) { + output.write('\n\n$stackTrace'); + } + + return output.toString(); + } + + @override + List get props => [code, message, stackTrace]; +} diff --git a/packages/native_crypto_platform_interface/lib/src/core/utils/enum_parser.dart b/packages/native_crypto_platform_interface/lib/src/core/utils/enum_parser.dart new file mode 100644 index 0000000..e565f2d --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/core/utils/enum_parser.dart @@ -0,0 +1,27 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:native_crypto_platform_interface/src/gen/messages.g.dart'; + +abstract class EnumParser { + static HashAlgorithm hashAlgorithmFromString(String value) { + for (final algorithm in HashAlgorithm.values) { + if (algorithm.name == value) { + return algorithm; + } + } + throw ArgumentError('Invalid algorithm: $value'); + } + + static CipherAlgorithm cipherAlgorithmFromString(String value) { + for (final algorithm in CipherAlgorithm.values) { + if (algorithm.name == value) { + return algorithm; + } + } + throw ArgumentError('Invalid algorithm: $value'); + } +} diff --git a/packages/native_crypto_platform_interface/lib/src/gen/messages.g.dart b/packages/native_crypto_platform_interface/lib/src/gen/messages.g.dart new file mode 100644 index 0000000..b2061ff --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/gen/messages.g.dart @@ -0,0 +1,256 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// -- +// Autogenerated from Pigeon (v9.2.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, prefer_null_aware_operators, omit_local_variable_types, unused_shown_name, unnecessary_import + +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; + +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; + +enum HashAlgorithm { + sha256, + sha384, + sha512, +} + +enum CipherAlgorithm { + aes, +} + +class NativeCryptoAPI { + /// Constructor for [NativeCryptoAPI]. The [binaryMessenger] named argument is + /// available for dependency injection. If it is left null, the default + /// BinaryMessenger will be used which routes to the host platform. + NativeCryptoAPI({BinaryMessenger? binaryMessenger}) + : _binaryMessenger = binaryMessenger; + final BinaryMessenger? _binaryMessenger; + + static const MessageCodec codec = StandardMessageCodec(); + + Future hash(Uint8List arg_data, HashAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.hash', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_data, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future hmac(Uint8List arg_data, Uint8List arg_key, HashAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.hmac', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_data, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future generateSecureRandom(int arg_length) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_length]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future pbkdf2(Uint8List arg_password, Uint8List arg_salt, int arg_length, int arg_iterations, HashAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.pbkdf2', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_password, arg_salt, arg_length, arg_iterations, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future encrypt(Uint8List arg_plainText, Uint8List arg_key, CipherAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encrypt', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_plainText, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future encryptWithIV(Uint8List arg_plainText, Uint8List arg_iv, Uint8List arg_key, CipherAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_plainText, arg_iv, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future decrypt(Uint8List arg_cipherText, Uint8List arg_key, CipherAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.decrypt', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_cipherText, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as Uint8List?); + } + } + + Future encryptFile(String arg_plainTextPath, String arg_cipherTextPath, Uint8List arg_key, CipherAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encryptFile', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_plainTextPath, arg_cipherTextPath, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as bool?); + } + } + + Future encryptFileWithIV(String arg_plainTextPath, String arg_cipherTextPath, Uint8List arg_iv, Uint8List arg_key, CipherAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_plainTextPath, arg_cipherTextPath, arg_iv, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as bool?); + } + } + + Future decryptFile(String arg_cipherTextPath, String arg_plainTextPath, Uint8List arg_key, CipherAlgorithm arg_algorithm) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.decryptFile', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_cipherTextPath, arg_plainTextPath, arg_key, arg_algorithm.index]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return (replyList[0] as bool?); + } + } +} diff --git a/packages/native_crypto_platform_interface/lib/src/gen/test.g.dart b/packages/native_crypto_platform_interface/lib/src/gen/test.g.dart new file mode 100644 index 0000000..14c7da7 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/gen/test.g.dart @@ -0,0 +1,307 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// -- +// Autogenerated from Pigeon (v9.2.0), do not edit directly. +// See also: https://pub.dev/packages/pigeon +// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import, unnecessary_parenthesis, unnecessary_import +// ignore_for_file: avoid_relative_lib_imports +import 'dart:async'; +import 'dart:typed_data' show Float64List, Int32List, Int64List, Uint8List; +import 'package:flutter/foundation.dart' show ReadBuffer, WriteBuffer; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'messages.g.dart'; + +abstract class TestNativeCryptoAPI { + static TestDefaultBinaryMessengerBinding? get _testBinaryMessengerBinding => TestDefaultBinaryMessengerBinding.instance; + static const MessageCodec codec = StandardMessageCodec(); + + Uint8List? hash(Uint8List data, HashAlgorithm algorithm); + + Uint8List? hmac(Uint8List data, Uint8List key, HashAlgorithm algorithm); + + Uint8List? generateSecureRandom(int length); + + Uint8List? pbkdf2(Uint8List password, Uint8List salt, int length, int iterations, HashAlgorithm algorithm); + + Uint8List? encrypt(Uint8List plainText, Uint8List key, CipherAlgorithm algorithm); + + Uint8List? encryptWithIV(Uint8List plainText, Uint8List iv, Uint8List key, CipherAlgorithm algorithm); + + Uint8List? decrypt(Uint8List cipherText, Uint8List key, CipherAlgorithm algorithm); + + bool? encryptFile(String plainTextPath, String cipherTextPath, Uint8List key, CipherAlgorithm algorithm); + + bool? encryptFileWithIV(String plainTextPath, String cipherTextPath, Uint8List iv, Uint8List key, CipherAlgorithm algorithm); + + bool? decryptFile(String cipherTextPath, String plainTextPath, Uint8List key, CipherAlgorithm algorithm); + + static void setup(TestNativeCryptoAPI? api, {BinaryMessenger? binaryMessenger}) { + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.hash', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hash was null.'); + final List args = (message as List?)!; + final Uint8List? arg_data = (args[0] as Uint8List?); + assert(arg_data != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hash was null, expected non-null Uint8List.'); + final HashAlgorithm? arg_algorithm = args[1] == null ? null : HashAlgorithm.values[args[1] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hash was null, expected non-null HashAlgorithm.'); + final Uint8List? output = api.hash(arg_data!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.hmac', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hmac was null.'); + final List args = (message as List?)!; + final Uint8List? arg_data = (args[0] as Uint8List?); + assert(arg_data != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hmac was null, expected non-null Uint8List.'); + final Uint8List? arg_key = (args[1] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hmac was null, expected non-null Uint8List.'); + final HashAlgorithm? arg_algorithm = args[2] == null ? null : HashAlgorithm.values[args[2] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.hmac was null, expected non-null HashAlgorithm.'); + final Uint8List? output = api.hmac(arg_data!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom was null.'); + final List args = (message as List?)!; + final int? arg_length = (args[0] as int?); + assert(arg_length != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.generateSecureRandom was null, expected non-null int.'); + final Uint8List? output = api.generateSecureRandom(arg_length!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.pbkdf2', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.pbkdf2 was null.'); + final List args = (message as List?)!; + final Uint8List? arg_password = (args[0] as Uint8List?); + assert(arg_password != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.pbkdf2 was null, expected non-null Uint8List.'); + final Uint8List? arg_salt = (args[1] as Uint8List?); + assert(arg_salt != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.pbkdf2 was null, expected non-null Uint8List.'); + final int? arg_length = (args[2] as int?); + assert(arg_length != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.pbkdf2 was null, expected non-null int.'); + final int? arg_iterations = (args[3] as int?); + assert(arg_iterations != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.pbkdf2 was null, expected non-null int.'); + final HashAlgorithm? arg_algorithm = args[4] == null ? null : HashAlgorithm.values[args[4] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.pbkdf2 was null, expected non-null HashAlgorithm.'); + final Uint8List? output = api.pbkdf2(arg_password!, arg_salt!, arg_length!, arg_iterations!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encrypt', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encrypt was null.'); + final List args = (message as List?)!; + final Uint8List? arg_plainText = (args[0] as Uint8List?); + assert(arg_plainText != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encrypt was null, expected non-null Uint8List.'); + final Uint8List? arg_key = (args[1] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encrypt was null, expected non-null Uint8List.'); + final CipherAlgorithm? arg_algorithm = args[2] == null ? null : CipherAlgorithm.values[args[2] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encrypt was null, expected non-null CipherAlgorithm.'); + final Uint8List? output = api.encrypt(arg_plainText!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV was null.'); + final List args = (message as List?)!; + final Uint8List? arg_plainText = (args[0] as Uint8List?); + assert(arg_plainText != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV was null, expected non-null Uint8List.'); + final Uint8List? arg_iv = (args[1] as Uint8List?); + assert(arg_iv != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV was null, expected non-null Uint8List.'); + final Uint8List? arg_key = (args[2] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV was null, expected non-null Uint8List.'); + final CipherAlgorithm? arg_algorithm = args[3] == null ? null : CipherAlgorithm.values[args[3] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptWithIV was null, expected non-null CipherAlgorithm.'); + final Uint8List? output = api.encryptWithIV(arg_plainText!, arg_iv!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.decrypt', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decrypt was null.'); + final List args = (message as List?)!; + final Uint8List? arg_cipherText = (args[0] as Uint8List?); + assert(arg_cipherText != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decrypt was null, expected non-null Uint8List.'); + final Uint8List? arg_key = (args[1] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decrypt was null, expected non-null Uint8List.'); + final CipherAlgorithm? arg_algorithm = args[2] == null ? null : CipherAlgorithm.values[args[2] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decrypt was null, expected non-null CipherAlgorithm.'); + final Uint8List? output = api.decrypt(arg_cipherText!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encryptFile', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFile was null.'); + final List args = (message as List?)!; + final String? arg_plainTextPath = (args[0] as String?); + assert(arg_plainTextPath != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFile was null, expected non-null String.'); + final String? arg_cipherTextPath = (args[1] as String?); + assert(arg_cipherTextPath != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFile was null, expected non-null String.'); + final Uint8List? arg_key = (args[2] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFile was null, expected non-null Uint8List.'); + final CipherAlgorithm? arg_algorithm = args[3] == null ? null : CipherAlgorithm.values[args[3] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFile was null, expected non-null CipherAlgorithm.'); + final bool? output = api.encryptFile(arg_plainTextPath!, arg_cipherTextPath!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV was null.'); + final List args = (message as List?)!; + final String? arg_plainTextPath = (args[0] as String?); + assert(arg_plainTextPath != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV was null, expected non-null String.'); + final String? arg_cipherTextPath = (args[1] as String?); + assert(arg_cipherTextPath != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV was null, expected non-null String.'); + final Uint8List? arg_iv = (args[2] as Uint8List?); + assert(arg_iv != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV was null, expected non-null Uint8List.'); + final Uint8List? arg_key = (args[3] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV was null, expected non-null Uint8List.'); + final CipherAlgorithm? arg_algorithm = args[4] == null ? null : CipherAlgorithm.values[args[4] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.encryptFileWithIV was null, expected non-null CipherAlgorithm.'); + final bool? output = api.encryptFileWithIV(arg_plainTextPath!, arg_cipherTextPath!, arg_iv!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.NativeCryptoAPI.decryptFile', codec, + binaryMessenger: binaryMessenger); + if (api == null) { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, null); + } else { + _testBinaryMessengerBinding!.defaultBinaryMessenger.setMockDecodedMessageHandler(channel, (Object? message) async { + assert(message != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decryptFile was null.'); + final List args = (message as List?)!; + final String? arg_cipherTextPath = (args[0] as String?); + assert(arg_cipherTextPath != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decryptFile was null, expected non-null String.'); + final String? arg_plainTextPath = (args[1] as String?); + assert(arg_plainTextPath != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decryptFile was null, expected non-null String.'); + final Uint8List? arg_key = (args[2] as Uint8List?); + assert(arg_key != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decryptFile was null, expected non-null Uint8List.'); + final CipherAlgorithm? arg_algorithm = args[3] == null ? null : CipherAlgorithm.values[args[3] as int]; + assert(arg_algorithm != null, + 'Argument for dev.flutter.pigeon.NativeCryptoAPI.decryptFile was null, expected non-null CipherAlgorithm.'); + final bool? output = api.decryptFile(arg_cipherTextPath!, arg_plainTextPath!, arg_key!, arg_algorithm!); + return [output]; + }); + } + } + } +} diff --git a/packages/native_crypto_platform_interface/lib/src/implementations/basic_message_channel_native_crypto.dart b/packages/native_crypto_platform_interface/lib/src/implementations/basic_message_channel_native_crypto.dart new file mode 100644 index 0000000..2ecc4ff --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/implementations/basic_message_channel_native_crypto.dart @@ -0,0 +1,194 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/foundation.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; +import 'package:native_crypto_platform_interface/src/core/utils/enum_parser.dart'; +import 'package:native_crypto_platform_interface/src/gen/messages.g.dart'; + +/// An implementation of [NativeCryptoPlatform] that uses Pigeon generated code. +class BasicMessageChannelNativeCrypto extends NativeCryptoPlatform { + /// Creates a new instance of [BasicMessageChannelNativeCrypto]. + /// + /// The [api] parameter permits to override the default Pigeon API used to + /// interact with the native platform. This is useful for testing. + BasicMessageChannelNativeCrypto({NativeCryptoAPI? api}) + : api = api ?? NativeCryptoAPI(); + + /// The Pigeon API used to interact with the native platform. + final NativeCryptoAPI api; + + @override + Future hash(Uint8List data, {required String algorithm}) async { + try { + return api.hash( + data, + EnumParser.hashAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future hmac( + Uint8List data, { + required Uint8List key, + required String algorithm, + }) async { + try { + return api.hmac( + data, + key, + EnumParser.hashAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future generateSecureRandom(int length) async { + try { + return api.generateSecureRandom(length); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future pbkdf2({ + required Uint8List password, + required Uint8List salt, + required int length, + required int iterations, + required String hashAlgorithm, + }) async { + try { + return api.pbkdf2( + password, + salt, + length, + iterations, + EnumParser.hashAlgorithmFromString(hashAlgorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future encrypt( + Uint8List plainText, { + required Uint8List key, + required String algorithm, + }) async { + try { + return api.encrypt( + plainText, + key, + EnumParser.cipherAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future encryptWithIV({ + required Uint8List plainText, + required Uint8List iv, + required Uint8List key, + required String algorithm, + }) async { + try { + return api.encryptWithIV( + plainText, + iv, + key, + EnumParser.cipherAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future decrypt( + Uint8List cipherText, { + required Uint8List key, + required String algorithm, + }) async { + try { + return api.decrypt( + cipherText, + key, + EnumParser.cipherAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future encryptFile({ + required String plainTextPath, + required String cipherTextPath, + required Uint8List key, + required String algorithm, + }) async { + try { + return api.encryptFile( + plainTextPath, + cipherTextPath, + key, + EnumParser.cipherAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future encryptFileWithIV({ + required String plainTextPath, + required String cipherTextPath, + required Uint8List iv, + required Uint8List key, + required String algorithm, + }) async { + try { + return api.encryptFileWithIV( + plainTextPath, + cipherTextPath, + iv, + key, + EnumParser.cipherAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future decryptFile({ + required String cipherTextPath, + required String plainTextPath, + required Uint8List key, + required String algorithm, + }) async { + try { + return api.decryptFile( + cipherTextPath, + plainTextPath, + key, + EnumParser.cipherAlgorithmFromString(algorithm), + ); + } catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } +} diff --git a/packages/native_crypto_platform_interface/lib/src/implementations/method_channel_native_crypto.dart b/packages/native_crypto_platform_interface/lib/src/implementations/method_channel_native_crypto.dart new file mode 100644 index 0000000..d51f79e --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/implementations/method_channel_native_crypto.dart @@ -0,0 +1,161 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; + +/// An implementation of [NativeCryptoPlatform] that uses method channels. +class MethodChannelNativeCrypto extends NativeCryptoPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + MethodChannel channel = const MethodChannel('plugins.hugop.cl/native_crypto'); + + Future _invokeMethod( + NativeCryptoMethod method, [ + Map? arguments, + ]) async { + try { + return await channel.invokeMethod(method.name, arguments); + } on PlatformException catch (e, s) { + NativeCryptoException.convertPlatformException(e, s); + } + } + + @override + Future hash(Uint8List data, {required String algorithm}) => + _invokeMethod( + NativeCryptoMethod.hash, + { + 'data': data, + 'algorithm': algorithm, + }, + ); + + @override + Future hmac( + Uint8List data, { + required Uint8List key, + required String algorithm, + }) => + _invokeMethod( + NativeCryptoMethod.hmac, + { + 'data': data, + 'key': key, + 'algorithm': algorithm, + }, + ); + + @override + Future generateSecureRandom(int length) => + _invokeMethod( + NativeCryptoMethod.generateSecureRandom, + { + 'length': length, + }, + ); + + @override + Future pbkdf2({ + required Uint8List password, + required Uint8List salt, + required int length, + required int iterations, + required String hashAlgorithm, + }) => + _invokeMethod( + NativeCryptoMethod.pbkdf2, + { + 'password': password, + 'salt': salt, + 'length': length, + 'iterations': iterations, + 'hashAlgorithm': hashAlgorithm, + }, + ); + + @override + Future encrypt( + Uint8List plainText, { + required Uint8List key, + required String algorithm, + }) => + _invokeMethod( + NativeCryptoMethod.encrypt, + { + 'plainText': plainText, + 'key': key, + 'algorithm': algorithm, + }, + ); + + @override + Future decrypt( + Uint8List cipherText, { + required Uint8List key, + required String algorithm, + }) => + _invokeMethod( + NativeCryptoMethod.decrypt, + { + 'cipherText': cipherText, + 'key': key, + 'algorithm': algorithm, + }, + ); + + @override + Future encryptFile({ + required String plainTextPath, + required String cipherTextPath, + required Uint8List key, + required String algorithm, + }) => + _invokeMethod( + NativeCryptoMethod.encryptFile, + { + 'plainTextPath': plainTextPath, + 'cipherTextPath': cipherTextPath, + 'key': key, + 'algorithm': algorithm, + }, + ); + + @override + Future decryptFile({ + required String cipherTextPath, + required String plainTextPath, + required Uint8List key, + required String algorithm, + }) => + _invokeMethod( + NativeCryptoMethod.decryptFile, + { + 'cipherTextPath': cipherTextPath, + 'plainTextPath': plainTextPath, + 'key': key, + 'algorithm': algorithm, + }, + ); + + @override + Future encryptWithIV({ + required Uint8List plainText, + required Uint8List iv, + required Uint8List key, + required String algorithm, + }) => + _invokeMethod( + NativeCryptoMethod.encryptWithIV, + { + 'plainText': plainText, + 'iv': iv, + 'key': key, + 'algorithm': algorithm, + }, + ); +} diff --git a/packages/native_crypto_platform_interface/lib/src/interface/native_crypto_platform.dart b/packages/native_crypto_platform_interface/lib/src/interface/native_crypto_platform.dart new file mode 100644 index 0000000..684cf39 --- /dev/null +++ b/packages/native_crypto_platform_interface/lib/src/interface/native_crypto_platform.dart @@ -0,0 +1,133 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'dart:typed_data'; + +import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +/// The interface that implementations of native_crypto must implement. +/// +/// Platform implementations should extend this class rather than implement +/// it as `NativeCrypto` does not consider newly added methods to be +/// breaking changes. Extending this class (using `extends`) ensures +/// that the subclass will get the default implementation, while platform +/// implementations that `implements` this interface will be +/// broken by newly added [NativeCryptoPlatform] methods. +abstract class NativeCryptoPlatform extends PlatformInterface { + /// Constructs a NativeCryptoPlatform. + NativeCryptoPlatform() : super(token: _token); + + static final Object _token = Object(); + + static NativeCryptoPlatform _instance = BasicMessageChannelNativeCrypto(); + + /// The default instance of [NativeCryptoPlatform] to use. + /// + /// Defaults to [BasicMessageChannelNativeCrypto]. + static NativeCryptoPlatform get instance => _instance; + + /// Platform-specific plugins should set this with their own platform-specific + /// class that extends [NativeCryptoPlatform] when they register themselves. + static set instance(NativeCryptoPlatform instance) { + PlatformInterface.verify(instance, _token); + _instance = instance; + } + + /// Returns the hash of the given data. + Future hash(Uint8List data, {required String algorithm}) { + throw UnimplementedError('hash is not implemented'); + } + + /// Returns the hmac of the given data using the given key. + Future hmac( + Uint8List data, { + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('hmac is not implemented'); + } + + /// Generates a secure random of the given length in bytes. + Future generateSecureRandom(int length) { + throw UnimplementedError('generateSecureRandom is not implemented'); + } + + /// Derives a key from the given password and salt using pbkdf2. + Future pbkdf2({ + required Uint8List password, + required Uint8List salt, + required int length, + required int iterations, + required String hashAlgorithm, + }) { + throw UnimplementedError('pbkdf2 is not implemented'); + } + + /// Encrypts the given data using the given key and algorithm. + Future encrypt( + Uint8List plainText, { + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('encrypt is not implemented'); + } + + /// Encrypts the given data using the given key, algorithm and iv. + /// + /// Users should use [encrypt] instead if they don't need to specify the iv. + Future encryptWithIV({ + required Uint8List plainText, + required Uint8List iv, + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('encryptWithIV is not implemented'); + } + + /// Decrypts the given data using the given key and algorithm. + Future decrypt( + Uint8List cipherText, { + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('decrypt is not implemented'); + } + + /// Encrypts the given file using the given key and algorithm. + Future encryptFile({ + required String plainTextPath, + required String cipherTextPath, + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('encryptFile is not implemented'); + } + + /// Encrypts the given file using the given key, algorithm and iv. + /// + /// Users should use [encryptFile] instead if they don't need to specify + /// the iv. + Future encryptFileWithIV({ + required String plainTextPath, + required String cipherTextPath, + required Uint8List iv, + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('encryptFileWithIV is not implemented'); + } + + /// Decrypts the given file using the given key and algorithm. + Future decryptFile({ + required String cipherTextPath, + required String plainTextPath, + required Uint8List key, + required String algorithm, + }) { + throw UnimplementedError('decryptFile is not implemented'); + } +} diff --git a/packages/native_crypto_platform_interface/lib/src/method_channel/method_channel_native_crypto.dart b/packages/native_crypto_platform_interface/lib/src/method_channel/method_channel_native_crypto.dart deleted file mode 100644 index 0559ed4..0000000 --- a/packages/native_crypto_platform_interface/lib/src/method_channel/method_channel_native_crypto.dart +++ /dev/null @@ -1,154 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: native_crypto_method_channel.dart -// Created Date: 25/12/2021 16:58:04 -// Last Modified: 25/05/2022 10:40:29 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; -import 'package:native_crypto_platform_interface/native_crypto_platform_interface.dart'; - -/// An implementation of [NativeCryptoPlatform] that uses method channels. -class MethodChannelNativeCrypto extends NativeCryptoPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - MethodChannel channel = const MethodChannel('plugins.hugop.cl/native_crypto'); - - @override - Future digest(Uint8List data, String algorithm) async { - try { - return await channel.invokeMethod( - 'digest', - { - 'data': data, - 'algorithm': algorithm, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } - - @override - Future generateSecretKey(int bitsCount) async { - try { - return await channel.invokeMethod( - 'generateSecretKey', - { - 'bitsCount': bitsCount, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } - - @override - Future pbkdf2( - String password, - String salt, - int keyBytesCount, - int iterations, - String algorithm, - ) async { - try { - return await channel.invokeMethod( - 'pbkdf2', - { - 'password': password, - 'salt': salt, - 'keyBytesCount': keyBytesCount, - 'iterations': iterations, - 'algorithm': algorithm, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } - - @override - Future?> encryptAsList( - Uint8List data, - Uint8List key, - String algorithm, - ) async { - try { - return await channel.invokeListMethod( - 'encryptAsList', - { - 'data': data, - 'key': key, - 'algorithm': algorithm, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } - - @override - Future decryptAsList( - List data, - Uint8List key, - String algorithm, - ) async { - try { - return await channel.invokeMethod( - 'decryptAsList', - { - 'data': data, - 'key': key, - 'algorithm': algorithm, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } - - @override - Future encrypt( - Uint8List data, - Uint8List key, - String algorithm, - ) async { - try { - return await channel.invokeMethod( - 'encrypt', - { - 'data': data, - 'key': key, - 'algorithm': algorithm, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } - - @override - Future decrypt( - Uint8List data, - Uint8List key, - String algorithm, - ) async { - try { - return await channel.invokeMethod( - 'decrypt', - { - 'data': data, - 'key': key, - 'algorithm': algorithm, - }, - ); - } catch (e, s) { - NativeCryptoException.convertPlatformException(e, s); - } - } -} diff --git a/packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart b/packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart deleted file mode 100644 index b09c0d3..0000000 --- a/packages/native_crypto_platform_interface/lib/src/platform_interface/native_crypto_platform.dart +++ /dev/null @@ -1,92 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: native_crypto_platform_interface.dart -// Created Date: 25/12/2021 16:43:49 -// Last Modified: 25/05/2022 22:11:02 -// ----- -// Copyright (c) 2021 - -import 'dart:typed_data'; - -import 'package:native_crypto_platform_interface/src/method_channel/method_channel_native_crypto.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -/// The interface that implementations of path_provider must implement. -/// -/// Platform implementations should extend this class rather than implement -/// it as `NativeCrypto` does not consider newly added methods to be -/// breaking changes. Extending this class (using `extends`) ensures -/// that the subclass will get the default implementation, while platform -/// implementations that `implements` this interface will be -/// broken by newly added [NativeCryptoPlatform] methods. -abstract class NativeCryptoPlatform extends PlatformInterface { - /// Constructs a NativeCryptoPlatform. - NativeCryptoPlatform() : super(token: _token); - - static final Object _token = Object(); - - static NativeCryptoPlatform _instance = MethodChannelNativeCrypto(); - - /// The default instance of [NativeCryptoPlatform] to use. - /// - /// Defaults to [MethodChannelNativeCrypto]. - static NativeCryptoPlatform get instance => _instance; - - /// Platform-specific plugins should set this with their own platform-specific - /// class that extends [NativeCryptoPlatform] when they register themselves. - static set instance(NativeCryptoPlatform instance) { - PlatformInterface.verify(instance, _token); - _instance = instance; - } - - Future digest(Uint8List data, String algorithm) { - throw UnimplementedError('digest is not implemented'); - } - - Future generateSecretKey(int bitsCount) { - throw UnimplementedError('generateSecretKey is not implemented'); - } - - Future pbkdf2( - String password, - String salt, - int keyBytesCount, - int iterations, - String algorithm, - ) { - throw UnimplementedError('pbkdf2 is not implemented'); - } - - Future?> encryptAsList( - Uint8List data, - Uint8List key, - String algorithm, - ) { - throw UnimplementedError('encryptAsList is not implemented'); - } - - Future decryptAsList( - List data, - Uint8List key, - String algorithm, - ) { - throw UnimplementedError('decryptAsList is not implemented'); - } - - Future encrypt( - Uint8List data, - Uint8List key, - String algorithm, - ) { - throw UnimplementedError('encrypt is not implemented'); - } - - Future decrypt( - Uint8List data, - Uint8List key, - String algorithm, - ) { - throw UnimplementedError('decrypt is not implemented'); - } -} diff --git a/packages/native_crypto_platform_interface/lib/src/utils/exception.dart b/packages/native_crypto_platform_interface/lib/src/utils/exception.dart deleted file mode 100644 index b46bb4a..0000000 --- a/packages/native_crypto_platform_interface/lib/src/utils/exception.dart +++ /dev/null @@ -1,126 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: exception.dart -// Created Date: 24/05/2022 18:54:48 -// Last Modified: 26/05/2022 20:36:04 -// ----- -// 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_throws, - platform_returned_invalid_data, - platform_returned_empty_data, - platform_returned_null; - - String get code => toString().split('.').last.toLowerCase(); -} - -class NativeCryptoException implements Exception { - NativeCryptoException({ - this.message, - String? code, - this.stackTrace, - }) : code = code ?? NativeCryptoExceptionCode.unknown.code; - - /// 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 = '[NativeCryptoException/$code] $message'; - - if (stackTrace != null) { - output += '\n\n${stackTrace.toString()}'; - } - - return output; - } - - /// 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); - } - - Error.throwWithStackTrace( - NativeCryptoException.fromPlatformException(exception, stackTrace), - stackTrace, - ); - } - - /// 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? details = platformException.details != null - ? Map.from( - platformException.details as Map, - ) - : null; - - String code = NativeCryptoExceptionCode.unknown.code; - 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; -} diff --git a/packages/native_crypto_platform_interface/pigeons/copyright_header.txt b/packages/native_crypto_platform_interface/pigeons/copyright_header.txt new file mode 100644 index 0000000..2701758 --- /dev/null +++ b/packages/native_crypto_platform_interface/pigeons/copyright_header.txt @@ -0,0 +1,6 @@ +Copyright 2019-2023 Hugo Pointcheval + +Use of this source code is governed by an MIT-style +license that can be found in the LICENSE file or at +https://opensource.org/licenses/MIT. +-- \ No newline at end of file diff --git a/packages/native_crypto_platform_interface/pigeons/messages.dart b/packages/native_crypto_platform_interface/pigeons/messages.dart new file mode 100644 index 0000000..3f00fe2 --- /dev/null +++ b/packages/native_crypto_platform_interface/pigeons/messages.dart @@ -0,0 +1,88 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:pigeon/pigeon.dart'; + +@ConfigurePigeon( + PigeonOptions( + copyrightHeader: 'pigeons/copyright_header.txt', + dartOut: 'lib/src/gen/messages.g.dart', + // We export in the lib folder to expose the class to other packages. + dartTestOut: 'lib/src/gen/test.g.dart', + kotlinOut: + '../native_crypto_android/android/src/main/kotlin/fr/pointcheval/native_crypto_android/Pigeon.kt', + kotlinOptions: KotlinOptions( + package: 'fr.pointcheval.native_crypto_android', + ), + swiftOut: '../native_crypto_ios/ios/Classes/messages.g.swift', + ), +) +enum HashAlgorithm { + sha256, + sha384, + sha512; +} + +enum CipherAlgorithm { + aes; +} + +@HostApi(dartHostTestHandler: 'TestNativeCryptoAPI') +abstract class NativeCryptoAPI { + Uint8List? hash(Uint8List data, HashAlgorithm algorithm); + Uint8List? hmac(Uint8List data, Uint8List key, HashAlgorithm algorithm); + Uint8List? generateSecureRandom(int length); + + Uint8List? pbkdf2( + Uint8List password, + Uint8List salt, + int length, + int iterations, + HashAlgorithm algorithm, + ); + + Uint8List? encrypt( + Uint8List plainText, + Uint8List key, + CipherAlgorithm algorithm, + ); + + Uint8List? encryptWithIV( + Uint8List plainText, + Uint8List iv, + Uint8List key, + CipherAlgorithm algorithm, + ); + + Uint8List? decrypt( + Uint8List cipherText, + Uint8List key, + CipherAlgorithm algorithm, + ); + + bool? encryptFile( + String plainTextPath, + String cipherTextPath, + Uint8List key, + CipherAlgorithm algorithm, + ); + + bool? encryptFileWithIV( + String plainTextPath, + String cipherTextPath, + Uint8List iv, + Uint8List key, + CipherAlgorithm algorithm, + ); + + bool? decryptFile( + String cipherTextPath, + String plainTextPath, + Uint8List key, + CipherAlgorithm algorithm, + ); + +} diff --git a/packages/native_crypto_platform_interface/pubspec.yaml b/packages/native_crypto_platform_interface/pubspec.yaml index 21ec587..320e4ba 100644 --- a/packages/native_crypto_platform_interface/pubspec.yaml +++ b/packages/native_crypto_platform_interface/pubspec.yaml @@ -7,19 +7,19 @@ environment: flutter: ">=2.5.0" dependencies: - flutter: - sdk: flutter + equatable: ^2.0.5 + flutter: { sdk: flutter } - plugin_platform_interface: ^2.1.3 + plugin_platform_interface: ^2.1.4 dev_dependencies: - flutter_test: - sdk: flutter + flutter_test: { sdk: flutter } - mockito: ^5.3.2 + mockito: ^5.4.0 + pigeon: ^9.2.0 wyatt_analysis: hosted: url: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ name: wyatt_analysis - version: 2.3.0 \ No newline at end of file + version: 2.4.1 diff --git a/packages/native_crypto_platform_interface/test/method_channel/method_channel_native_crypto_test.dart b/packages/native_crypto_platform_interface/test/method_channel/method_channel_native_crypto_test.dart deleted file mode 100644 index 28c01a1..0000000 --- a/packages/native_crypto_platform_interface/test/method_channel/method_channel_native_crypto_test.dart +++ /dev/null @@ -1,175 +0,0 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: method_channel_native_crypto_test.dart -// Created Date: 25/05/2022 22:47:41 -// Last Modified: 25/05/2022 23:22:44 -// ----- -// Copyright (c) 2022 - -import 'dart:typed_data'; - -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:native_crypto_platform_interface/src/method_channel/method_channel_native_crypto.dart'; - -void main() { - TestWidgetsFlutterBinding - .ensureInitialized(); // Required for setMockMethodCallHandler - - group('$MethodChannelNativeCrypto', () { - const MethodChannel channel = - MethodChannel('plugins.hugop.cl/native_crypto'); - final List log = []; - final MethodChannelNativeCrypto nativeCrypto = MethodChannelNativeCrypto(); - - TestDefaultBinaryMessengerBinding.instance?.defaultBinaryMessenger - .setMockMethodCallHandler(channel, (MethodCall call) async { - log.add(call); - return null; - }); - - // Run after each test. - tearDown(log.clear); - - test('digest', () async { - await nativeCrypto.digest(Uint8List(0), 'sha256'); - expect( - log, - [ - isMethodCall( - 'digest', - arguments: { - 'data': Uint8List(0), - 'algorithm': 'sha256', - }, - ), - ], - ); - }); - - test('generateSecretKey', () async { - await nativeCrypto.generateSecretKey(256); - expect( - log, - [ - isMethodCall( - 'generateSecretKey', - arguments: { - 'bitsCount': 256, - }, - ), - ], - ); - }); - - test('pbkdf2', () async { - await nativeCrypto.pbkdf2( - 'password', - 'salt', - 32, - 10000, - 'sha256', - ); - expect( - log, - [ - isMethodCall( - 'pbkdf2', - arguments: { - 'password': 'password', - 'salt': 'salt', - 'keyBytesCount': 32, - 'iterations': 10000, - 'algorithm': 'sha256', - }, - ), - ], - ); - }); - - test('encryptAsList', () async { - await nativeCrypto.encryptAsList( - Uint8List(0), - Uint8List(0), - 'aes', - ); - expect( - log, - [ - isMethodCall( - 'encryptAsList', - arguments: { - 'data': Uint8List(0), - 'key': Uint8List(0), - 'algorithm': 'aes', - }, - ), - ], - ); - }); - - test('decryptAsList', () async { - await nativeCrypto.decryptAsList( - [Uint8List(0)], - Uint8List(0), - 'aes', - ); - expect( - log, - [ - isMethodCall( - 'decryptAsList', - arguments: { - 'data': [Uint8List(0)], - 'key': Uint8List(0), - 'algorithm': 'aes', - }, - ), - ], - ); - }); - - test('encrypt', () async { - await nativeCrypto.encrypt( - Uint8List(0), - Uint8List(0), - 'aes', - ); - expect( - log, - [ - isMethodCall( - 'encrypt', - arguments: { - 'data': Uint8List(0), - 'key': Uint8List(0), - 'algorithm': 'aes', - }, - ), - ], - ); - }); - - test('decrypt', () async { - await nativeCrypto.decrypt( - Uint8List(0), - Uint8List(0), - 'aes', - ); - expect( - log, - [ - isMethodCall( - 'decrypt', - arguments: { - 'data': Uint8List(0), - 'key': Uint8List(0), - 'algorithm': 'aes', - }, - ), - ], - ); - }); - }); -} diff --git a/packages/native_crypto_platform_interface/test/native_crypto_exception_test.dart b/packages/native_crypto_platform_interface/test/native_crypto_exception_test.dart new file mode 100644 index 0000000..0e8cbb0 --- /dev/null +++ b/packages/native_crypto_platform_interface/test/native_crypto_exception_test.dart @@ -0,0 +1,72 @@ +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_crypto_platform_interface/src/core/enums/exception_code.dart'; +import 'package:native_crypto_platform_interface/src/core/exceptions/exception.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$NativeCryptoException', () { + test('should return a formatted message with only code', () async { + const e = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + ); + + expect(e.toString(), '[NativeCrypto/unknown_error]'); + }); + + test('should return a formatted message', () async { + const e = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + message: 'foo', + ); + + expect(e.toString(), '[NativeCrypto/unknown_error] foo'); + }); + + test('should return a formatted message with a stack trace', () async { + const e = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + message: 'foo', + ); + + expect(e.toString(), '[NativeCrypto/unknown_error] foo'); + }); + + test('should return a formatted message with a stack trace', () async { + final e = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + message: 'foo', + stackTrace: StackTrace.current, + ); + + // Anything with a stack trace adds 2 blanks lines following the message. + expect(e.toString(), startsWith('[NativeCrypto/unknown_error] foo\n\n')); + }); + + test('should override the == operator', () async { + const e1 = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + message: 'foo', + ); + + const e2 = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + message: 'foo', + ); + + const e3 = NativeCryptoException( + code: NativeCryptoExceptionCode.unknownError, + message: 'foo', + ); + + expect(e1 == e2, true); + expect(e1 != e3, false); + }); + }); +} diff --git a/packages/native_crypto_platform_interface/test/platform_interface/native_crypto_platform_test.dart b/packages/native_crypto_platform_interface/test/platform_interface/native_crypto_platform_test.dart index acd0f9d..8d06d88 100644 --- a/packages/native_crypto_platform_interface/test/platform_interface/native_crypto_platform_test.dart +++ b/packages/native_crypto_platform_interface/test/platform_interface/native_crypto_platform_test.dart @@ -1,43 +1,38 @@ -// Author: Hugo Pointcheval -// Email: git@pcl.ovh -// ----- -// File: native_crypto_platform_test.dart -// Created Date: 25/05/2022 21:43:25 -// Last Modified: 25/05/2022 23:26:18 -// ----- -// Copyright (c) 2022 - -import 'dart:typed_data'; +// Copyright 2019-2023 Hugo Pointcheval +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/mockito.dart'; -import 'package:native_crypto_platform_interface/src/platform_interface/native_crypto_platform.dart'; +import 'package:native_crypto_platform_interface/src/implementations/basic_message_channel_native_crypto.dart'; +import 'package:native_crypto_platform_interface/src/interface/native_crypto_platform.dart'; import 'package:plugin_platform_interface/plugin_platform_interface.dart'; +class ImplementsNativeCryptoPlatform + // ignore: prefer_mixin + with + Mock + implements + NativeCryptoPlatform {} + +class ExtendsNativeCryptoPlatform extends NativeCryptoPlatform {} + +class NativeCryptoMockPlatform extends Mock + with + // ignore: prefer_mixin, plugin_platform_interface needs to migrate to use `mixin` + MockPlatformInterfaceMixin + implements + NativeCryptoPlatform {} + void main() { - late ExtendsNativeCryptoPlatform nativeCryptoPlatform; + TestWidgetsFlutterBinding.ensureInitialized(); group('$NativeCryptoPlatform', () { - setUpAll(() { - nativeCryptoPlatform = ExtendsNativeCryptoPlatform(); - }); - test('Constructor', () { - expect(nativeCryptoPlatform, isA()); - expect(nativeCryptoPlatform, isA()); - }); - - test('get.instance', () { - expect( - NativeCryptoPlatform.instance, - isA(), - ); - }); - test('set.instance', () { + // should allow read of default app from native + test('Can be extended', () { NativeCryptoPlatform.instance = ExtendsNativeCryptoPlatform(); - expect( - NativeCryptoPlatform.instance, - isA(), - ); }); test('Cannot be implemented with `implements`', () { @@ -45,122 +40,19 @@ void main() { () { NativeCryptoPlatform.instance = ImplementsNativeCryptoPlatform(); }, - throwsA(isInstanceOf()), + throwsA(anything), ); }); test('Can be mocked with `implements`', () { - final MockNativeCryptoPlatform mock = MockNativeCryptoPlatform(); + final NativeCryptoMockPlatform mock = NativeCryptoMockPlatform(); NativeCryptoPlatform.instance = mock; }); - test('Can be extended', () { - NativeCryptoPlatform.instance = ExtendsNativeCryptoPlatform(); - }); - - test('throws if .digest() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform.digest(Uint8List(0), 'sha256'), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'digest is not implemented', - ), - ), - ); - }); - - test('throws if .generateSecretKey() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform.generateSecretKey(256), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'generateSecretKey is not implemented', - ), - ), - ); - }); - - test('throws if .pbkdf2() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform.pbkdf2('password', 'salt', 0, 0, 'sha256'), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'pbkdf2 is not implemented', - ), - ), - ); - }); - - test('throws if .encryptAsList() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform.encryptAsList( - Uint8List(0), - Uint8List(0), - 'aes', - ), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'encryptAsList is not implemented', - ), - ), - ); - }); - - test('throws if .decryptAsList() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform - .decryptAsList([Uint8List(0)], Uint8List(0), 'aes'), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'decryptAsList is not implemented', - ), - ), - ); - }); - - test('throws if .encrypt() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform.encrypt(Uint8List(0), Uint8List(0), 'aes'), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'encrypt is not implemented', - ), - ), - ); - }); - - test('throws if .decrypt() not implemented', () async { - await expectLater( - () => nativeCryptoPlatform.decrypt(Uint8List(0), Uint8List(0), 'aes'), - throwsA( - isA().having( - (e) => e.message, - 'message', - 'decrypt is not implemented', - ), - ), - ); + test('Can set with $BasicMessageChannelNativeCrypto', () { + final BasicMessageChannelNativeCrypto pigeon = + BasicMessageChannelNativeCrypto(); + NativeCryptoPlatform.instance = pigeon; }); }); } - -class ExtendsNativeCryptoPlatform extends NativeCryptoPlatform {} - -class ImplementsNativeCryptoPlatform extends Mock - implements NativeCryptoPlatform {} - -class MockNativeCryptoPlatform extends Mock - with MockPlatformInterfaceMixin - implements NativeCryptoPlatform {} diff --git a/resources/models/aes_classes.puml b/resources/models/aes_classes.puml new file mode 100644 index 0000000..fa0ae97 --- /dev/null +++ b/resources/models/aes_classes.puml @@ -0,0 +1,25 @@ +@startuml aes_classes + +abstract class Cipher { + encrypt(plainText: Uint8List): CipherText + decrypt(cipherText: CipherText): Uint8List + encryptFile(plainTextFile: Path, cipherTextFile: Path) + decryptFile(cipherTextFile: Path, plainTextFile: Path) +} + +class AES extends Cipher { + key: SecretKey + mode: AESMode + padding: Padding + chunkSize: int + + encrypt(plainText: Uint8List): CipherText + decrypt(cipherText: CipherText): Uint8List + encryptFile(plainTextFile: Path, cipherTextFile: Path) + decryptFile(cipherTextFile: Path, plainTextFile: Path) + + encryptWithIV(plainText: Uint8List, iv: Uint8List): AESCipherChunk + decryptWithIV(cipherChunk: AESCipherChunk, iv: Uint8List): Uint8List +} + +@enduml \ No newline at end of file diff --git a/resources/models/aes_dss.puml b/resources/models/aes_dss.puml new file mode 100644 index 0000000..dd2d7d3 --- /dev/null +++ b/resources/models/aes_dss.puml @@ -0,0 +1,44 @@ +@startuml aes_dss + +actor user +participant AES as aes +participant CipherText as ct +participant CipherChunk as cc +participant NativeCrypto as nc + +user -> aes : new(key: SecretKey, mode: Mode, padding: Padding, chunkSize: int) +activate aes +aes --> user : AES +user -> aes : encrypt(plainText: Uint8List) + +loop for each chunk in plainText + aes -> nc : encrypt(chunk: Uint8List, key: Uint8List, "aes/gcm/NoPadding") + nc --> aes : Uint8List + aes -> cc : new(chunk: Uint8List) + cc --> aes : CipherChunk +end + +aes -> ct : new(chunks: List) +ct --> aes : CipherText +aes --> user : CipherText + +user -> aes : decrypt(cipherText: CipherText) +loop for each chunk in cipherText.chunks + aes -> nc : decrypt(chunk: Uint8List, key: Uint8List, "aes/gcm/NoPadding") + nc --> aes : Uint8List + aes --> aes : concat Uint8List +end + +aes --> user : Uint8List + +user -> aes : encryptFile(plainTextFile: File, cipherTextFile: File) +aes -> nc : encryptFile(plainTextFile: File, cipherTextFile: File, key: Uint8List, "aes/gcm/NoPadding") +nc --> aes : void +aes --> user : void + +user -> aes : decryptFile(cipherTextFile: File, plainTextFile: File) +aes -> nc : decryptFile(cipherTextFile: File, plainTextFile: File, key: Uint8List, "aes/gcm/NoPadding") +nc --> aes : void +aes --> user : void + +@enduml \ No newline at end of file diff --git a/resources/models/cipher_text_classes.puml b/resources/models/cipher_text_classes.puml new file mode 100644 index 0000000..ee96cb1 --- /dev/null +++ b/resources/models/cipher_text_classes.puml @@ -0,0 +1,63 @@ +@startuml cipher_text_classes + +abstract class ByteArray { + bytes : Uint8List + length : int + + ByteArray(bytes: Uint8List) + + fromList(list: List) + fromLength(length: int, {fill: int = 0}) + fromUtf16(encoded: String) + fromUtf8(encoded: String) + fromBase64(encoded: String) + fromBase16(encoded: String) + + toList() : List + toUtf16() : String + toUtf8() : String + toBase64() : String + toBase16() : String +} + +class CipherChunk extends ByteArray { + CipherChunk(bytes: Uint8List) + + fromList(list: List) + fromUtf16(encoded: String) + fromUtf8(encoded: String) + fromBase64(encoded: String) + fromBase16(encoded: String) +} + +class CipherText extends ByteArray { + chunkSize : int + chunks : List + + CipherText(bytes: Uint8List, {chunkSize: int = 33554432}) + + fromList(list: List) + fromUtf16(encoded: String) + fromUtf8(encoded: String) + fromBase64(encoded: String) + fromBase16(encoded: String) + + toList() : List + toUtf16() : String + toUtf8() : String + toBase64() : String + toBase16() : String + + fromChunks(chunks: List) + toChunks() : List + toBytes() : Uint8List +} + +class AESCipherChunk extends CipherChunk { + iv : Uint8List + message : Uint8List + tag : Uint8List +} + + +@enduml \ No newline at end of file diff --git a/resources/models/cipher_text_dss.puml b/resources/models/cipher_text_dss.puml new file mode 100644 index 0000000..653102d --- /dev/null +++ b/resources/models/cipher_text_dss.puml @@ -0,0 +1,19 @@ +@startuml cipher_text_dss + +actor user +participant CipherText as ct +participant CipherChunk as cc + +user -> ct : new(bytes) +loop for each chunk + ct -> cc : new(bytes) + cc --> ct +end +ct --> user : CipherText + +user -> ct : new(bytes, chunkSize: bytes.length) +ct -> cc : new(bytes) +cc --> ct +ct --> user : CipherText + +@enduml \ No newline at end of file diff --git a/resources/models/digest_classes.puml b/resources/models/digest_classes.puml new file mode 100644 index 0000000..f1bd9fc --- /dev/null +++ b/resources/models/digest_classes.puml @@ -0,0 +1,25 @@ +@startuml digest_classes + +abstract class Hash { + digest(data: Uint8List): Uint8List +} + +abstract class Hmac { + digest(data: Uint8List, key: Uint8List): Uint8List +} + +Hmac o-- Hash + +class Sha256 extends Hash { + static instance: Sha256 +} + +class Sha512 extends Hash { + static instance: Sha512 +} + +class HmacSha256 extends Hmac { + static instance: HmacSha256 +} + +@enduml \ No newline at end of file diff --git a/resources/models/digest_dss.puml b/resources/models/digest_dss.puml new file mode 100644 index 0000000..d4d21ce --- /dev/null +++ b/resources/models/digest_dss.puml @@ -0,0 +1,29 @@ +@startuml digest_dss + +actor user +participant Flutter as flt +participant Sha256 as sha +participant HmacSha256 as hmac +participant NativeCrypto as nc + +user -> flt : getDigest("sha256") +flt -> sha : getInstance() +sha --> flt : Sha256 +flt --> user : Sha256 + +user -> sha : digest(data) +sha --> nc : hash(data, "sha256") +nc --> sha : digest +sha --> user : digest + +user -> flt : getDigest("hmacSha256") +flt -> hmac : getInstance() +hmac --> flt : HmacSha256 +flt --> user : HmacSha256 + +user -> hmac : digest(data) +hmac --> nc : hmac(data, key, "sha256") +nc --> hmac : digest +hmac --> user : digest + +@enduml \ No newline at end of file diff --git a/resources/models/generator_classes.puml b/resources/models/generator_classes.puml new file mode 100644 index 0000000..a5cfad1 --- /dev/null +++ b/resources/models/generator_classes.puml @@ -0,0 +1,9 @@ +@startuml generator_classes + +abstract class Random { + generate(bytes: int): Uint8List +} + +class SecureRandom extends Random {} + +@enduml \ No newline at end of file diff --git a/resources/models/generator_dss.puml b/resources/models/generator_dss.puml new file mode 100644 index 0000000..97a877c --- /dev/null +++ b/resources/models/generator_dss.puml @@ -0,0 +1,15 @@ +@startuml generator_dss + +actor user +participant SecureRandom as rand +participant NativeCrypto as nc + +user -> rand : new() +rand --> user : SecureRandom + +user -> rand : generate(32) +rand -> nc : generateRandomBytes(32) +nc --> rand : Uint8List(32) +rand --> user : Uint8List(32) + +@enduml \ No newline at end of file diff --git a/resources/models/kdf_classes.puml b/resources/models/kdf_classes.puml new file mode 100644 index 0000000..6af06ae --- /dev/null +++ b/resources/models/kdf_classes.puml @@ -0,0 +1,17 @@ +@startuml kdf_classes + +abstract class KeyDerivationFunction { + derive(keyMaterial: Uint8List) : Uint8List + verify(keyMaterial: Uint8List, expected: Uint8List) : bool +} + +class PBKDF2 extends KeyDerivationFunction { + hashAlgorithm: Hash + iterations: int + salt: Uint8List + length: int + + call({password: String}) : SecretKey +} + +@enduml \ No newline at end of file diff --git a/resources/models/kdf_dss.puml b/resources/models/kdf_dss.puml new file mode 100644 index 0000000..3850996 --- /dev/null +++ b/resources/models/kdf_dss.puml @@ -0,0 +1,20 @@ +@startuml kdf_dss + +actor user +participant Pbkdf2 as kdf +participant NativeCrypto as nc + +user -> kdf: new(hash, iterations, salt, length) +kdf--> user : Pbkdf2 + +user -> kdf: derive(password) +kdf--> nc : pbkdf2(password, hash, iterations, salt, length) +nc --> kdf: Uint8List(length) +kdf--> user : SecretKey + +user -> kdf : verify(password, key) +kdf--> nc : pbkdf2(password, hash, iterations, salt, length) +nc --> kdf: Uint8List(length) +kdf--> user : bool + +@enduml \ No newline at end of file diff --git a/resources/models/key_classes.puml b/resources/models/key_classes.puml new file mode 100644 index 0000000..07fbdc7 --- /dev/null +++ b/resources/models/key_classes.puml @@ -0,0 +1,40 @@ +@startuml key_classes + +abstract class ByteArray { + bytes : Uint8List + length : int + + fromList(list: List ) + fromLength(length: int, {fill: int = 0}) + fromUtf16(encoded: String) + fromUtf8(encoded: String) + fromBase64(encoded: String) + fromBase16(encoded: String) + + toList() : List + toUtf16() : String + toUtf8() : String + toBase64() : String + toBase16() : String + +} + +abstract class Key extends ByteArray { + fromList(list: List ) + fromUtf16(encoded: String) + fromUtf8(encoded: String) + fromBase64(encoded: String) + fromBase16(encoded: String) +} + +class SecretKey extends Key { + fromList(list: List) + fromUtf16(encoded: String) + fromUtf8(encoded: String) + fromBase64(encoded: String) + fromBase16(encoded: String) + + async fromSecureRandom(bytes: int) +} + +@enduml \ No newline at end of file diff --git a/resources/models/key_dss.puml b/resources/models/key_dss.puml new file mode 100644 index 0000000..135a850 --- /dev/null +++ b/resources/models/key_dss.puml @@ -0,0 +1,17 @@ +@startuml key_dss + +actor user +participant SecretKey as sk +participant SecureRandom as rand +participant NativeCrypto as nc + +user -> sk : fromSecureRandom(32) +sk -> rand : new() +rand --> sk : SecureRandom +sk -> rand : generate(32) +rand -> nc : generateRandomBytes(32) +nc --> rand : Uint8List(32) +rand --> sk : Uint8List(32) +sk --> user : SecretKey + +@enduml \ No newline at end of file