diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java index 20bf705..32d0d1e 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Api.java @@ -81,6 +81,18 @@ public class Api { } } + public enum CipherAlgorithm { + AES128GCM_NO_PADDING(0), + AES256GCM_NO_PADDING(1), + AES256CBC_PKCS7(2); + + final int index; + + private CipherAlgorithm(final int index) { + this.index = index; + } + } + /** Generated class from Pigeon that represents data sent in messages. */ public static final class NotificationMessageContent { /** The textual body of the message. */ @@ -904,6 +916,86 @@ public class Api { } } + /** Generated class from Pigeon that represents data sent in messages. */ + public static final class CryptographyResult { + private @NonNull byte[] plaintextHash; + + public @NonNull byte[] getPlaintextHash() { + return plaintextHash; + } + + public void setPlaintextHash(@NonNull byte[] setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"plaintextHash\" is null."); + } + this.plaintextHash = setterArg; + } + + private @NonNull byte[] ciphertextHash; + + public @NonNull byte[] getCiphertextHash() { + return ciphertextHash; + } + + public void setCiphertextHash(@NonNull byte[] setterArg) { + if (setterArg == null) { + throw new IllegalStateException("Nonnull field \"ciphertextHash\" is null."); + } + this.ciphertextHash = setterArg; + } + + /** Constructor is non-public to enforce null safety; use Builder. */ + CryptographyResult() {} + + public static final class Builder { + + private @Nullable byte[] plaintextHash; + + public @NonNull Builder setPlaintextHash(@NonNull byte[] setterArg) { + this.plaintextHash = setterArg; + return this; + } + + private @Nullable byte[] ciphertextHash; + + public @NonNull Builder setCiphertextHash(@NonNull byte[] setterArg) { + this.ciphertextHash = setterArg; + return this; + } + + public @NonNull CryptographyResult build() { + CryptographyResult pigeonReturn = new CryptographyResult(); + pigeonReturn.setPlaintextHash(plaintextHash); + pigeonReturn.setCiphertextHash(ciphertextHash); + return pigeonReturn; + } + } + + @NonNull + ArrayList toList() { + ArrayList toListResult = new ArrayList(2); + toListResult.add(plaintextHash); + toListResult.add(ciphertextHash); + return toListResult; + } + + static @NonNull CryptographyResult fromList(@NonNull ArrayList list) { + CryptographyResult pigeonResult = new CryptographyResult(); + Object plaintextHash = list.get(0); + pigeonResult.setPlaintextHash((byte[]) plaintextHash); + Object ciphertextHash = list.get(1); + pigeonResult.setCiphertextHash((byte[]) ciphertextHash); + return pigeonResult; + } + } + + public interface Result { + @SuppressWarnings("UnknownNullness") + void success(T result); + + void error(@NonNull Throwable error); + } + private static class MoxplatformApiCodec extends StandardMessageCodec { public static final MoxplatformApiCodec INSTANCE = new MoxplatformApiCodec(); @@ -913,16 +1005,18 @@ public class Api { protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { switch (type) { case (byte) 128: - return MessagingNotification.fromList((ArrayList) readValue(buffer)); + return CryptographyResult.fromList((ArrayList) readValue(buffer)); case (byte) 129: - return NotificationEvent.fromList((ArrayList) readValue(buffer)); + return MessagingNotification.fromList((ArrayList) readValue(buffer)); case (byte) 130: - return NotificationI18nData.fromList((ArrayList) readValue(buffer)); + return NotificationEvent.fromList((ArrayList) readValue(buffer)); case (byte) 131: - return NotificationMessage.fromList((ArrayList) readValue(buffer)); + return NotificationI18nData.fromList((ArrayList) readValue(buffer)); case (byte) 132: - return NotificationMessageContent.fromList((ArrayList) readValue(buffer)); + return NotificationMessage.fromList((ArrayList) readValue(buffer)); case (byte) 133: + return NotificationMessageContent.fromList((ArrayList) readValue(buffer)); + case (byte) 134: return RegularNotification.fromList((ArrayList) readValue(buffer)); default: return super.readValueOfType(type, buffer); @@ -931,23 +1025,26 @@ public class Api { @Override protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { - if (value instanceof MessagingNotification) { + if (value instanceof CryptographyResult) { stream.write(128); + writeValue(stream, ((CryptographyResult) value).toList()); + } else if (value instanceof MessagingNotification) { + stream.write(129); writeValue(stream, ((MessagingNotification) value).toList()); } else if (value instanceof NotificationEvent) { - stream.write(129); + stream.write(130); writeValue(stream, ((NotificationEvent) value).toList()); } else if (value instanceof NotificationI18nData) { - stream.write(130); + stream.write(131); writeValue(stream, ((NotificationI18nData) value).toList()); } else if (value instanceof NotificationMessage) { - stream.write(131); + stream.write(132); writeValue(stream, ((NotificationMessage) value).toList()); } else if (value instanceof NotificationMessageContent) { - stream.write(132); + stream.write(133); writeValue(stream, ((NotificationMessageContent) value).toList()); } else if (value instanceof RegularNotification) { - stream.write(133); + stream.write(134); writeValue(stream, ((RegularNotification) value).toList()); } else { super.writeValue(stream, value); @@ -957,7 +1054,7 @@ public class Api { /** Generated interface from Pigeon that represents a handler of messages from Flutter. */ public interface MoxplatformApi { - + /** Notification APIs */ void createNotificationChannel(@NonNull String title, @NonNull String description, @NonNull String id, @NonNull Boolean urgent); void showMessagingNotification(@NonNull MessagingNotification notification); @@ -969,13 +1066,19 @@ public class Api { void setNotificationSelfAvatar(@NonNull String path); void setNotificationI18n(@NonNull NotificationI18nData data); - + /** Platform APIs */ @NonNull String getPersistentDataPath(); @NonNull String getCacheDataPath(); + /** Cryptography APIs */ + void encryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result result); + void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result result); + + void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Result result); + /** Stubs */ void eventStub(@NonNull NotificationEvent event); /** The codec used by MoxplatformApi. */ @@ -1175,6 +1278,104 @@ public class Api { channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String sourcePathArg = (String) args.get(0); + String destPathArg = (String) args.get(1); + byte[] keyArg = (byte[]) args.get(2); + byte[] ivArg = (byte[]) args.get(3); + CipherAlgorithm algorithmArg = args.get(4) == null ? null : CipherAlgorithm.values()[(int) args.get(4)]; + String hashSpecArg = (String) args.get(5); + Result resultCallback = + new Result() { + public void success(CryptographyResult result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.encryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String sourcePathArg = (String) args.get(0); + String destPathArg = (String) args.get(1); + byte[] keyArg = (byte[]) args.get(2); + byte[] ivArg = (byte[]) args.get(3); + CipherAlgorithm algorithmArg = args.get(4) == null ? null : CipherAlgorithm.values()[(int) args.get(4)]; + String hashSpecArg = (String) args.get(5); + Result resultCallback = + new Result() { + public void success(CryptographyResult result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.decryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + String sourcePathArg = (String) args.get(0); + String hashSpecArg = (String) args.get(1); + Result resultCallback = + new Result() { + public void success(byte[] result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.hashFile(sourcePathArg, hashSpecArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } { BasicMessageChannel channel = new BasicMessageChannel<>( diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Crypto.kt b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Crypto.kt index 52f011c..63bca9d 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Crypto.kt +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/Crypto.kt @@ -1,6 +1,8 @@ package me.polynom.moxplatform_android import android.util.Log +import me.polynom.moxplatform_android.Api.CipherAlgorithm +import me.polynom.moxplatform_android.Api.CryptographyResult import java.io.FileInputStream import java.io.FileOutputStream @@ -10,6 +12,7 @@ import javax.crypto.Cipher import javax.crypto.CipherOutputStream import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec +import kotlin.concurrent.thread // A FileOutputStream that continuously hashes whatever it writes to the file. private class HashedFileOutputStream(name: String, hashAlgorithm: String) : FileOutputStream(name) { @@ -30,115 +33,126 @@ private class HashedFileOutputStream(name: String, hashAlgorithm: String) : File } } -fun getCipherSpecFromInteger(algorithmType: Int): String { - return when (algorithmType) { - 0 -> "AES_128/GCM/NoPadding" - 1 -> "AES_256/GCM/NoPadding" - 2 -> "AES_256/CBC/PKCS7PADDING" - else -> "" +fun getCipherSpecFromInteger(algorithm: CipherAlgorithm): String { + return when (algorithm) { + CipherAlgorithm.AES128GCM_NO_PADDING -> "AES_128/GCM/NoPadding" + CipherAlgorithm.AES256GCM_NO_PADDING -> "AES_256/GCM/NoPadding" + CipherAlgorithm.AES256CBC_PKCS7 -> "AES_256/CBC/PKCS7PADDING" } } // Compute the hash, specified by @algorithm, of the file at path @srcFile. If an exception // occurs, returns null. If everything went well, returns the raw hash of @srcFile. -fun hashFile(srcFile: String, algorithm: String): ByteArray? { - val buffer = ByteArray(BUFFER_SIZE) - try { - val digest = MessageDigest.getInstance(algorithm) - val fInputStream = FileInputStream(srcFile) - var length: Int +fun hashFile(srcFile: String, algorithm: String, result: Api.Result) { + thread(start = true) { + val buffer = ByteArray(BUFFER_SIZE) + try { + val digest = MessageDigest.getInstance(algorithm) + val fInputStream = FileInputStream(srcFile) + var length: Int - while (true) { - length = fInputStream.read() - if (length <= 0) break + while (true) { + length = fInputStream.read() + if (length <= 0) break - // Only update the digest if we read more than 0 bytes - digest.update(buffer, 0, length) + // Only update the digest if we read more than 0 bytes + digest.update(buffer, 0, length) + } + + fInputStream.close() + + result.success(digest.digest()) + } catch (e: Exception) { + Log.e(TAG, "[hashFile]: " + e.stackTraceToString()) + result.success(null) } - - fInputStream.close() - - return digest.digest() - } catch (e: Exception) { - Log.e(TAG, "[hashFile]: " + e.stackTraceToString()) - return null } } // Encrypt the plaintext file at @src to @dest using the secret key @key and the IV @iv. The algorithm is chosen using @cipherAlgorithm. The file is additionally // hashed before and after encryption using the hash algorithm specified by @hashAlgorithm. -fun encryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: String, hashAlgorithm: String): HashMap? { - val buffer = ByteArray(BUFFER_SIZE) - val secretKey = SecretKeySpec(key, cipherAlgorithm) - try { - val digest = MessageDigest.getInstance(hashAlgorithm) - val cipher = Cipher.getInstance(cipherAlgorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv)) +fun encryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result) { + thread(start = true) { + val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm) + val buffer = ByteArray(BUFFER_SIZE) + val secretKey = SecretKeySpec(key, cipherSpec) + try { + val digest = MessageDigest.getInstance(hashAlgorithm) + val cipher = Cipher.getInstance(cipherSpec) + cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv)) - val fileInputStream = FileInputStream(src) - val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm) - val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher) + val fileInputStream = FileInputStream(src) + val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm) + val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher) - var length: Int - while (true) { - length = fileInputStream.read(buffer) - if (length <= 0) break + var length: Int + while (true) { + length = fileInputStream.read(buffer) + if (length <= 0) break - digest.update(buffer, 0, length) - cipherOutputStream.write(buffer, 0, length) + digest.update(buffer, 0, length) + cipherOutputStream.write(buffer, 0, length) + } + + // Flush and close + cipherOutputStream.flush() + cipherOutputStream.close() + fileInputStream.close() + + result.success( + CryptographyResult().apply { + plaintextHash = digest.digest() + ciphertextHash = fileOutputStream.digest() + } + ) + } catch (e: Exception) { + Log.e(TAG, "[encryptAndHash]: " + e.stackTraceToString()) + result.success(null) } - - // Flush and close - cipherOutputStream.flush() - cipherOutputStream.close() - fileInputStream.close() - - return hashMapOf( - "plaintextHash" to digest.digest(), - "ciphertextHash" to fileOutputStream.digest(), - ) - } catch (e: Exception) { - Log.e(TAG, "[encryptAndHash]: " + e.stackTraceToString()) - return null } } // Decrypt the ciphertext file at @src to @dest using the secret key @key and the IV @iv. The algorithm is chosen using @cipherAlgorithm. The file is additionally // hashed before and after decryption using the hash algorithm specified by @hashAlgorithm. -fun decryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: String, hashAlgorithm: String): HashMap? { - // Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3 - val buffer = ByteArray(BUFFER_SIZE) - val secretKey = SecretKeySpec(key, cipherAlgorithm) - try { - val digest = MessageDigest.getInstance(hashAlgorithm) - val cipher = Cipher.getInstance(cipherAlgorithm) - cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv)) +fun decryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result) { + thread(start = true) { + val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm) + // Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3 + val buffer = ByteArray(BUFFER_SIZE) + val secretKey = SecretKeySpec(key, cipherSpec) + try { + val digest = MessageDigest.getInstance(hashAlgorithm) + val cipher = Cipher.getInstance(cipherSpec) + cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv)) - val fileInputStream = FileInputStream(src) - val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm) - val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher) + val fileInputStream = FileInputStream(src) + val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm) + val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher) - // Read, decrypt, and hash until we read 0 bytes - var length: Int - while (true) { - length = fileInputStream.read(buffer) - if (length <= 0) break + // Read, decrypt, and hash until we read 0 bytes + var length: Int + while (true) { + length = fileInputStream.read(buffer) + if (length <= 0) break - digest.update(buffer, 0, length) - cipherOutputStream.write(buffer, 0, length) + digest.update(buffer, 0, length) + cipherOutputStream.write(buffer, 0, length) + } + + // Flush + cipherOutputStream.flush() + cipherOutputStream.close() + fileInputStream.close() + + result.success( + CryptographyResult().apply { + plaintextHash = digest.digest() + ciphertextHash = fileOutputStream.digest() + } + ) + } catch (e: Exception) { + Log.e(TAG, "[hashAndDecrypt]: " + e.stackTraceToString()) + result.success(null) } - - // Flush - cipherOutputStream.flush() - cipherOutputStream.close() - fileInputStream.close() - - return hashMapOf( - "plaintextHash" to digest.digest(), - "ciphertextHash" to fileOutputStream.digest(), - ) - } catch (e: Exception) { - Log.e(TAG, "[hashAndDecrypt]: " + e.stackTraceToString()) - return null } } \ No newline at end of file diff --git a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java index 85eb878..c9d3ce6 100644 --- a/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java +++ b/packages/moxplatform_android/android/src/main/java/me/polynom/moxplatform_android/MoxplatformAndroidPlugin.java @@ -40,7 +40,7 @@ import io.flutter.plugin.common.JSONMethodCodec; import kotlin.Unit; import kotlin.jvm.functions.Function1; - public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ServiceAware, MoxplatformApi { +public class MoxplatformAndroidPlugin extends BroadcastReceiver implements FlutterPlugin, MethodCallHandler, EventChannel.StreamHandler, ServiceAware, MoxplatformApi { public static final String entrypointKey = "entrypoint_handle"; public static final String extraDataKey = "extra_data"; private static final String autoStartAtBootKey = "auto_start_at_boot"; @@ -166,53 +166,6 @@ import kotlin.jvm.functions.Function1; } result.success(true); break; - case "encryptFile": - Thread encryptionThread = new Thread(new Runnable() { - @Override - public void run() { - ArrayList args = (ArrayList) call.arguments; - String src = (String) args.get(0); - String dest = (String) args.get(1); - byte[] key = (byte[]) args.get(2); - byte[] iv = (byte[]) args.get(3); - int algorithm = (int) args.get(4); - String hashSpec = (String) args.get(5); - - result.success(encryptAndHash(src, dest, key, iv, getCipherSpecFromInteger(algorithm), hashSpec)); - } - }); - encryptionThread.start(); - break; - case "decryptFile": - Thread decryptionThread = new Thread(new Runnable() { - @Override - public void run() { - ArrayList args = (ArrayList) call.arguments; - String src = (String) args.get(0); - String dest = (String) args.get(1); - byte[] key = (byte[]) args.get(2); - byte[] iv = (byte[]) args.get(3); - int algorithm = (int) args.get(4); - String hashSpec = (String) args.get(5); - - result.success(decryptAndHash(src, dest, key, iv, getCipherSpecFromInteger(algorithm), hashSpec)); - } - }); - decryptionThread.start(); - break; - case "hashFile": - Thread hashingThread = new Thread(new Runnable() { - @Override - public void run() { - ArrayList args = (ArrayList) call.arguments; - String src = (String) args.get(0); - String hashSpec = (String) args.get(1); - - result.success(hashFile(src, hashSpec)); - } - }); - hashingThread.start(); - break; case "recordSentMessage": ArrayList rargs = (ArrayList) call.arguments; recordSentMessage(context, (String) rargs.get(0), (String) rargs.get(1), (String) rargs.get(2), (int) rargs.get(3)); @@ -308,6 +261,37 @@ import kotlin.jvm.functions.Function1; return context.getCacheDir().getPath(); } + @Override + public void encryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Api.Result result) { + CryptoKt.encryptAndHash( + sourcePath, + destPath, + key, + iv, + algorithm, + hashSpec, + result + ); + } + + @Override + public void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Api.Result result) { + CryptoKt.decryptAndHash( + sourcePath, + destPath, + key, + iv, + algorithm, + hashSpec, + result + ); + } + + @Override + public void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Api.Result result) { + CryptoKt.hashFile(sourcePath, hashSpec, result); + } + @Override public void eventStub(@NonNull NotificationEvent event) { // Stub to trick pigeon into diff --git a/packages/moxplatform_android/lib/src/crypto_android.dart b/packages/moxplatform_android/lib/src/crypto_android.dart index e203b36..e1b71ba 100644 --- a/packages/moxplatform_android/lib/src/crypto_android.dart +++ b/packages/moxplatform_android/lib/src/crypto_android.dart @@ -2,7 +2,7 @@ import 'package:flutter/services.dart'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart'; class AndroidCryptographyImplementation extends CryptographyImplementation { - final _methodChannel = const MethodChannel('me.polynom.moxplatform_android'); + final MoxplatformApi _api = MoxplatformApi(); @override Future encryptFile( @@ -13,22 +13,13 @@ class AndroidCryptographyImplementation extends CryptographyImplementation { CipherAlgorithm algorithm, String hashSpec, ) async { - final dynamic resultRaw = - await _methodChannel.invokeMethod('encryptFile', [ + return _api.encryptFile( sourcePath, destPath, key, iv, - algorithm.value, + algorithm, hashSpec, - ]); - if (resultRaw == null) return null; - - // ignore: argument_type_not_assignable - final result = Map.from(resultRaw); - return CryptographyResult( - result['plaintextHash']! as Uint8List, - result['ciphertextHash']! as Uint8List, ); } @@ -41,35 +32,18 @@ class AndroidCryptographyImplementation extends CryptographyImplementation { CipherAlgorithm algorithm, String hashSpec, ) async { - final dynamic resultRaw = - await _methodChannel.invokeMethod('decryptFile', [ + return _api.decryptFile( sourcePath, destPath, key, iv, - algorithm.value, + algorithm, hashSpec, - ]); - if (resultRaw == null) return null; - - // ignore: argument_type_not_assignable - final result = Map.from(resultRaw); - return CryptographyResult( - result['plaintextHash']! as Uint8List, - result['ciphertextHash']! as Uint8List, ); } @override - Future hashFile(String path, String hashSpec) async { - final dynamic resultsRaw = - await _methodChannel.invokeMethod('hashFile', [ - path, - hashSpec, - ]); - - if (resultsRaw == null) return null; - - return resultsRaw as Uint8List; + Future hashFile(String sourcePath, String hashSpec) async { + return _api.hashFile(sourcePath, hashSpec); } } diff --git a/packages/moxplatform_platform_interface/lib/src/api.g.dart b/packages/moxplatform_platform_interface/lib/src/api.g.dart index 2055b38..a2fe506 100644 --- a/packages/moxplatform_platform_interface/lib/src/api.g.dart +++ b/packages/moxplatform_platform_interface/lib/src/api.g.dart @@ -20,6 +20,12 @@ enum NotificationEventType { open, } +enum CipherAlgorithm { + aes128GcmNoPadding, + aes256GcmNoPadding, + aes256CbcPkcs7, +} + class NotificationMessageContent { NotificationMessageContent({ this.body, @@ -285,28 +291,57 @@ class NotificationI18nData { } } +class CryptographyResult { + CryptographyResult({ + required this.plaintextHash, + required this.ciphertextHash, + }); + + Uint8List plaintextHash; + + Uint8List ciphertextHash; + + Object encode() { + return [ + plaintextHash, + ciphertextHash, + ]; + } + + static CryptographyResult decode(Object result) { + result as List; + return CryptographyResult( + plaintextHash: result[0]! as Uint8List, + ciphertextHash: result[1]! as Uint8List, + ); + } +} + class _MoxplatformApiCodec extends StandardMessageCodec { const _MoxplatformApiCodec(); @override void writeValue(WriteBuffer buffer, Object? value) { - if (value is MessagingNotification) { + if (value is CryptographyResult) { buffer.putUint8(128); writeValue(buffer, value.encode()); - } else if (value is NotificationEvent) { + } else if (value is MessagingNotification) { buffer.putUint8(129); writeValue(buffer, value.encode()); - } else if (value is NotificationI18nData) { + } else if (value is NotificationEvent) { buffer.putUint8(130); writeValue(buffer, value.encode()); - } else if (value is NotificationMessage) { + } else if (value is NotificationI18nData) { buffer.putUint8(131); writeValue(buffer, value.encode()); - } else if (value is NotificationMessageContent) { + } else if (value is NotificationMessage) { buffer.putUint8(132); writeValue(buffer, value.encode()); - } else if (value is RegularNotification) { + } else if (value is NotificationMessageContent) { buffer.putUint8(133); writeValue(buffer, value.encode()); + } else if (value is RegularNotification) { + buffer.putUint8(134); + writeValue(buffer, value.encode()); } else { super.writeValue(buffer, value); } @@ -316,16 +351,18 @@ class _MoxplatformApiCodec extends StandardMessageCodec { Object? readValueOfType(int type, ReadBuffer buffer) { switch (type) { case 128: - return MessagingNotification.decode(readValue(buffer)!); + return CryptographyResult.decode(readValue(buffer)!); case 129: - return NotificationEvent.decode(readValue(buffer)!); + return MessagingNotification.decode(readValue(buffer)!); case 130: - return NotificationI18nData.decode(readValue(buffer)!); + return NotificationEvent.decode(readValue(buffer)!); case 131: - return NotificationMessage.decode(readValue(buffer)!); + return NotificationI18nData.decode(readValue(buffer)!); case 132: - return NotificationMessageContent.decode(readValue(buffer)!); + return NotificationMessage.decode(readValue(buffer)!); case 133: + return NotificationMessageContent.decode(readValue(buffer)!); + case 134: return RegularNotification.decode(readValue(buffer)!); default: return super.readValueOfType(type, buffer); @@ -343,6 +380,7 @@ class MoxplatformApi { static const MessageCodec codec = _MoxplatformApiCodec(); + /// Notification APIs Future createNotificationChannel(String arg_title, String arg_description, String arg_id, bool arg_urgent) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel', codec, @@ -475,6 +513,7 @@ class MoxplatformApi { } } + /// Platform APIs Future getPersistentDataPath() async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec, @@ -529,6 +568,74 @@ class MoxplatformApi { } } + /// Cryptography APIs + Future encryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) 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 CryptographyResult?); + } + } + + Future decryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) 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 CryptographyResult?); + } + } + + Future hashFile(String arg_sourcePath, String arg_hashSpec) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_sourcePath, arg_hashSpec]) 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?); + } + } + + /// Stubs Future eventStub(NotificationEvent arg_event) async { final BasicMessageChannel channel = BasicMessageChannel( 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.eventStub', codec, diff --git a/packages/moxplatform_platform_interface/lib/src/crypto.dart b/packages/moxplatform_platform_interface/lib/src/crypto.dart index b61b086..b3f7121 100644 --- a/packages/moxplatform_platform_interface/lib/src/crypto.dart +++ b/packages/moxplatform_platform_interface/lib/src/crypto.dart @@ -1,21 +1,5 @@ import 'dart:typed_data'; - -enum CipherAlgorithm { - aes128GcmNoPadding(0), - aes256GcmNoPadding(1), - aes256CbcPkcs7(2); - - const CipherAlgorithm(this.value); - - /// The "id" of the algorithm choice. - final int value; -} - -class CryptographyResult { - const CryptographyResult(this.plaintextHash, this.ciphertextHash); - final Uint8List plaintextHash; - final Uint8List ciphertextHash; -} +import 'package:moxplatform_platform_interface/src/api.g.dart'; /// Wrapper around platform-native cryptography APIs abstract class CryptographyImplementation { @@ -47,9 +31,9 @@ abstract class CryptographyImplementation { String hashSpec, ); - /// Hashes the file at [path] using the Hash function with name [hashSpec]. + /// Hashes the file at [sourcePath] using the Hash function with name [hashSpec]. /// Note that this function runs off-thread as to not block the UI thread. /// /// Returns the hash of the file. - Future hashFile(String path, String hashSpec); + Future hashFile(String sourcePath, String hashSpec); } diff --git a/packages/moxplatform_platform_interface/lib/src/crypto_stub.dart b/packages/moxplatform_platform_interface/lib/src/crypto_stub.dart index b71ff68..b1517f4 100644 --- a/packages/moxplatform_platform_interface/lib/src/crypto_stub.dart +++ b/packages/moxplatform_platform_interface/lib/src/crypto_stub.dart @@ -1,4 +1,5 @@ import 'dart:typed_data'; +import 'package:moxplatform_platform_interface/src/api.g.dart'; import 'package:moxplatform_platform_interface/src/crypto.dart'; class StubCryptographyImplementation extends CryptographyImplementation { @@ -27,7 +28,7 @@ class StubCryptographyImplementation extends CryptographyImplementation { } @override - Future hashFile(String path, String hashSpec) async { + Future hashFile(String sourcePath, String hashSpec) async { return null; } } diff --git a/pigeons/api.dart b/pigeons/api.dart index 7e1bfe9..8dd2ca7 100644 --- a/pigeons/api.dart +++ b/pigeons/api.dart @@ -149,15 +149,37 @@ class NotificationI18nData { final String you; } +enum CipherAlgorithm { + aes128GcmNoPadding, + aes256GcmNoPadding, + aes256CbcPkcs7; +} + +class CryptographyResult { + const CryptographyResult(this.plaintextHash, this.ciphertextHash); + final Uint8List plaintextHash; + final Uint8List ciphertextHash; +} + @HostApi() abstract class MoxplatformApi { + /// Notification APIs void createNotificationChannel(String title, String description, String id, bool urgent); void showMessagingNotification(MessagingNotification notification); void showNotification(RegularNotification notification); void dismissNotification(int id); void setNotificationSelfAvatar(String path); void setNotificationI18n(NotificationI18nData data); + + /// Platform APIs String getPersistentDataPath(); String getCacheDataPath(); + + /// Cryptography APIs + @async CryptographyResult? encryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec); + @async CryptographyResult? decryptFile(String sourcePath, String destPath, Uint8List key, Uint8List iv, CipherAlgorithm algorithm, String hashSpec); + @async Uint8List? hashFile(String sourcePath, String hashSpec); + + /// Stubs void eventStub(NotificationEvent event); }