feat: Move the crypto APIs to pigeon

This commit is contained in:
PapaTutuWawa 2023-08-03 21:19:11 +02:00
parent 271428219a
commit 61de3cd565
8 changed files with 496 additions and 209 deletions

View File

@ -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<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<Object>(2);
toListResult.add(plaintextHash);
toListResult.add(ciphertextHash);
return toListResult;
}
static @NonNull CryptographyResult fromList(@NonNull ArrayList<Object> 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<T> {
@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<Object>) readValue(buffer));
return CryptographyResult.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 129:
return NotificationEvent.fromList((ArrayList<Object>) readValue(buffer));
return MessagingNotification.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 130:
return NotificationI18nData.fromList((ArrayList<Object>) readValue(buffer));
return NotificationEvent.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 131:
return NotificationMessage.fromList((ArrayList<Object>) readValue(buffer));
return NotificationI18nData.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 132:
return NotificationMessageContent.fromList((ArrayList<Object>) readValue(buffer));
return NotificationMessage.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 133:
return NotificationMessageContent.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 134:
return RegularNotification.fromList((ArrayList<Object>) 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<CryptographyResult> result);
void decryptFile(@NonNull String sourcePath, @NonNull String destPath, @NonNull byte[] key, @NonNull byte[] iv, @NonNull CipherAlgorithm algorithm, @NonNull String hashSpec, @NonNull Result<CryptographyResult> result);
void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Result<byte[]> result);
/** Stubs */
void eventStub(@NonNull NotificationEvent event);
/** The codec used by MoxplatformApi. */
@ -1175,6 +1278,104 @@ public class Api {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) 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<CryptographyResult> resultCallback =
new Result<CryptographyResult>() {
public void success(CryptographyResult result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.encryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) 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<CryptographyResult> resultCallback =
new Result<CryptographyResult>() {
public void success(CryptographyResult result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.decryptFile(sourcePathArg, destPathArg, keyArg, ivArg, algorithmArg, hashSpecArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(
binaryMessenger, "dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile", getCodec());
if (api != null) {
channel.setMessageHandler(
(message, reply) -> {
ArrayList<Object> wrapped = new ArrayList<Object>();
ArrayList<Object> args = (ArrayList<Object>) message;
String sourcePathArg = (String) args.get(0);
String hashSpecArg = (String) args.get(1);
Result<byte[]> resultCallback =
new Result<byte[]>() {
public void success(byte[] result) {
wrapped.add(0, result);
reply.reply(wrapped);
}
public void error(Throwable error) {
ArrayList<Object> wrappedError = wrapError(error);
reply.reply(wrappedError);
}
};
api.hashFile(sourcePathArg, hashSpecArg, resultCallback);
});
} else {
channel.setMessageHandler(null);
}
}
{
BasicMessageChannel<Object> channel =
new BasicMessageChannel<>(

View File

@ -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<ByteArray?>) {
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<String, ByteArray>? {
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<CryptographyResult?>) {
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<String, ByteArray>? {
// 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<CryptographyResult?>) {
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
}
}

View File

@ -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<CryptographyResult> 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<CryptographyResult> result) {
CryptoKt.decryptAndHash(
sourcePath,
destPath,
key,
iv,
algorithm,
hashSpec,
result
);
}
@Override
public void hashFile(@NonNull String sourcePath, @NonNull String hashSpec, @NonNull Api.Result<byte[]> result) {
CryptoKt.hashFile(sourcePath, hashSpec, result);
}
@Override
public void eventStub(@NonNull NotificationEvent event) {
// Stub to trick pigeon into

View File

@ -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<CryptographyResult?> encryptFile(
@ -13,22 +13,13 @@ class AndroidCryptographyImplementation extends CryptographyImplementation {
CipherAlgorithm algorithm,
String hashSpec,
) async {
final dynamic resultRaw =
await _methodChannel.invokeMethod<dynamic>('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<String, dynamic>.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<dynamic>('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<String, dynamic>.from(resultRaw);
return CryptographyResult(
result['plaintextHash']! as Uint8List,
result['ciphertextHash']! as Uint8List,
);
}
@override
Future<Uint8List?> hashFile(String path, String hashSpec) async {
final dynamic resultsRaw =
await _methodChannel.invokeMethod<dynamic>('hashFile', [
path,
hashSpec,
]);
if (resultsRaw == null) return null;
return resultsRaw as Uint8List;
Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async {
return _api.hashFile(sourcePath, hashSpec);
}
}

View File

@ -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 <Object?>[
plaintextHash,
ciphertextHash,
];
}
static CryptographyResult decode(Object result) {
result as List<Object?>;
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<Object?> codec = _MoxplatformApiCodec();
/// Notification APIs
Future<void> createNotificationChannel(String arg_title, String arg_description, String arg_id, bool arg_urgent) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel', codec,
@ -475,6 +513,7 @@ class MoxplatformApi {
}
}
/// Platform APIs
Future<String> getPersistentDataPath() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec,
@ -529,6 +568,74 @@ class MoxplatformApi {
}
}
/// Cryptography APIs
Future<CryptographyResult?> encryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.encryptFile', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List<Object?>?;
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<CryptographyResult?> decryptFile(String arg_sourcePath, String arg_destPath, Uint8List arg_key, Uint8List arg_iv, CipherAlgorithm arg_algorithm, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.decryptFile', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_sourcePath, arg_destPath, arg_key, arg_iv, arg_algorithm.index, arg_hashSpec]) as List<Object?>?;
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<Uint8List?> hashFile(String arg_sourcePath, String arg_hashSpec) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.hashFile', codec,
binaryMessenger: _binaryMessenger);
final List<Object?>? replyList =
await channel.send(<Object?>[arg_sourcePath, arg_hashSpec]) as List<Object?>?;
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<void> eventStub(NotificationEvent arg_event) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.eventStub', codec,

View File

@ -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<Uint8List?> hashFile(String path, String hashSpec);
Future<Uint8List?> hashFile(String sourcePath, String hashSpec);
}

View File

@ -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<Uint8List?> hashFile(String path, String hashSpec) async {
Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async {
return null;
}
}

View File

@ -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);
}