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. */ /** Generated class from Pigeon that represents data sent in messages. */
public static final class NotificationMessageContent { public static final class NotificationMessageContent {
/** The textual body of the message. */ /** 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 { private static class MoxplatformApiCodec extends StandardMessageCodec {
public static final MoxplatformApiCodec INSTANCE = new MoxplatformApiCodec(); public static final MoxplatformApiCodec INSTANCE = new MoxplatformApiCodec();
@ -913,16 +1005,18 @@ public class Api {
protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) { protected Object readValueOfType(byte type, @NonNull ByteBuffer buffer) {
switch (type) { switch (type) {
case (byte) 128: case (byte) 128:
return MessagingNotification.fromList((ArrayList<Object>) readValue(buffer)); return CryptographyResult.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 129: case (byte) 129:
return NotificationEvent.fromList((ArrayList<Object>) readValue(buffer)); return MessagingNotification.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 130: case (byte) 130:
return NotificationI18nData.fromList((ArrayList<Object>) readValue(buffer)); return NotificationEvent.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 131: case (byte) 131:
return NotificationMessage.fromList((ArrayList<Object>) readValue(buffer)); return NotificationI18nData.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 132: case (byte) 132:
return NotificationMessageContent.fromList((ArrayList<Object>) readValue(buffer)); return NotificationMessage.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 133: case (byte) 133:
return NotificationMessageContent.fromList((ArrayList<Object>) readValue(buffer));
case (byte) 134:
return RegularNotification.fromList((ArrayList<Object>) readValue(buffer)); return RegularNotification.fromList((ArrayList<Object>) readValue(buffer));
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@ -931,23 +1025,26 @@ public class Api {
@Override @Override
protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) { protected void writeValue(@NonNull ByteArrayOutputStream stream, Object value) {
if (value instanceof MessagingNotification) { if (value instanceof CryptographyResult) {
stream.write(128); stream.write(128);
writeValue(stream, ((CryptographyResult) value).toList());
} else if (value instanceof MessagingNotification) {
stream.write(129);
writeValue(stream, ((MessagingNotification) value).toList()); writeValue(stream, ((MessagingNotification) value).toList());
} else if (value instanceof NotificationEvent) { } else if (value instanceof NotificationEvent) {
stream.write(129); stream.write(130);
writeValue(stream, ((NotificationEvent) value).toList()); writeValue(stream, ((NotificationEvent) value).toList());
} else if (value instanceof NotificationI18nData) { } else if (value instanceof NotificationI18nData) {
stream.write(130); stream.write(131);
writeValue(stream, ((NotificationI18nData) value).toList()); writeValue(stream, ((NotificationI18nData) value).toList());
} else if (value instanceof NotificationMessage) { } else if (value instanceof NotificationMessage) {
stream.write(131); stream.write(132);
writeValue(stream, ((NotificationMessage) value).toList()); writeValue(stream, ((NotificationMessage) value).toList());
} else if (value instanceof NotificationMessageContent) { } else if (value instanceof NotificationMessageContent) {
stream.write(132); stream.write(133);
writeValue(stream, ((NotificationMessageContent) value).toList()); writeValue(stream, ((NotificationMessageContent) value).toList());
} else if (value instanceof RegularNotification) { } else if (value instanceof RegularNotification) {
stream.write(133); stream.write(134);
writeValue(stream, ((RegularNotification) value).toList()); writeValue(stream, ((RegularNotification) value).toList());
} else { } else {
super.writeValue(stream, value); super.writeValue(stream, value);
@ -957,7 +1054,7 @@ public class Api {
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */ /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
public interface MoxplatformApi { public interface MoxplatformApi {
/** Notification APIs */
void createNotificationChannel(@NonNull String title, @NonNull String description, @NonNull String id, @NonNull Boolean urgent); void createNotificationChannel(@NonNull String title, @NonNull String description, @NonNull String id, @NonNull Boolean urgent);
void showMessagingNotification(@NonNull MessagingNotification notification); void showMessagingNotification(@NonNull MessagingNotification notification);
@ -969,13 +1066,19 @@ public class Api {
void setNotificationSelfAvatar(@NonNull String path); void setNotificationSelfAvatar(@NonNull String path);
void setNotificationI18n(@NonNull NotificationI18nData data); void setNotificationI18n(@NonNull NotificationI18nData data);
/** Platform APIs */
@NonNull @NonNull
String getPersistentDataPath(); String getPersistentDataPath();
@NonNull @NonNull
String getCacheDataPath(); 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); void eventStub(@NonNull NotificationEvent event);
/** The codec used by MoxplatformApi. */ /** The codec used by MoxplatformApi. */
@ -1175,6 +1278,104 @@ public class Api {
channel.setMessageHandler(null); 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 = BasicMessageChannel<Object> channel =
new BasicMessageChannel<>( new BasicMessageChannel<>(

View File

@ -1,6 +1,8 @@
package me.polynom.moxplatform_android package me.polynom.moxplatform_android
import android.util.Log 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.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
@ -10,6 +12,7 @@ import javax.crypto.Cipher
import javax.crypto.CipherOutputStream import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import kotlin.concurrent.thread
// A FileOutputStream that continuously hashes whatever it writes to the file. // A FileOutputStream that continuously hashes whatever it writes to the file.
private class HashedFileOutputStream(name: String, hashAlgorithm: String) : FileOutputStream(name) { 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 { fun getCipherSpecFromInteger(algorithm: CipherAlgorithm): String {
return when (algorithmType) { return when (algorithm) {
0 -> "AES_128/GCM/NoPadding" CipherAlgorithm.AES128GCM_NO_PADDING -> "AES_128/GCM/NoPadding"
1 -> "AES_256/GCM/NoPadding" CipherAlgorithm.AES256GCM_NO_PADDING -> "AES_256/GCM/NoPadding"
2 -> "AES_256/CBC/PKCS7PADDING" CipherAlgorithm.AES256CBC_PKCS7 -> "AES_256/CBC/PKCS7PADDING"
else -> ""
} }
} }
// Compute the hash, specified by @algorithm, of the file at path @srcFile. If an exception // 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. // occurs, returns null. If everything went well, returns the raw hash of @srcFile.
fun hashFile(srcFile: String, algorithm: String): ByteArray? { fun hashFile(srcFile: String, algorithm: String, result: Api.Result<ByteArray?>) {
val buffer = ByteArray(BUFFER_SIZE) thread(start = true) {
try { val buffer = ByteArray(BUFFER_SIZE)
val digest = MessageDigest.getInstance(algorithm) try {
val fInputStream = FileInputStream(srcFile) val digest = MessageDigest.getInstance(algorithm)
var length: Int val fInputStream = FileInputStream(srcFile)
var length: Int
while (true) { while (true) {
length = fInputStream.read() length = fInputStream.read()
if (length <= 0) break if (length <= 0) break
// Only update the digest if we read more than 0 bytes // Only update the digest if we read more than 0 bytes
digest.update(buffer, 0, length) 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 // 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. // 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>? { fun encryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result<CryptographyResult?>) {
val buffer = ByteArray(BUFFER_SIZE) thread(start = true) {
val secretKey = SecretKeySpec(key, cipherAlgorithm) val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm)
try { val buffer = ByteArray(BUFFER_SIZE)
val digest = MessageDigest.getInstance(hashAlgorithm) val secretKey = SecretKeySpec(key, cipherSpec)
val cipher = Cipher.getInstance(cipherAlgorithm) try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv)) val digest = MessageDigest.getInstance(hashAlgorithm)
val cipher = Cipher.getInstance(cipherSpec)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
val fileInputStream = FileInputStream(src) val fileInputStream = FileInputStream(src)
val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm) val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm)
val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher) val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher)
var length: Int var length: Int
while (true) { while (true) {
length = fileInputStream.read(buffer) length = fileInputStream.read(buffer)
if (length <= 0) break if (length <= 0) break
digest.update(buffer, 0, length) digest.update(buffer, 0, length)
cipherOutputStream.write(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 // 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. // 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>? { fun decryptAndHash(src: String, dest: String, key: ByteArray, iv: ByteArray, cipherAlgorithm: CipherAlgorithm, hashAlgorithm: String, result: Api.Result<CryptographyResult?>) {
// Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3 thread(start = true) {
val buffer = ByteArray(BUFFER_SIZE) val cipherSpec = getCipherSpecFromInteger(cipherAlgorithm)
val secretKey = SecretKeySpec(key, cipherAlgorithm) // Shamelessly stolen from https://github.com/hugo-pcl/native-crypto-flutter/pull/3
try { val buffer = ByteArray(BUFFER_SIZE)
val digest = MessageDigest.getInstance(hashAlgorithm) val secretKey = SecretKeySpec(key, cipherSpec)
val cipher = Cipher.getInstance(cipherAlgorithm) try {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv)) val digest = MessageDigest.getInstance(hashAlgorithm)
val cipher = Cipher.getInstance(cipherSpec)
cipher.init(Cipher.ENCRYPT_MODE, secretKey, IvParameterSpec(iv))
val fileInputStream = FileInputStream(src) val fileInputStream = FileInputStream(src)
val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm) val fileOutputStream = HashedFileOutputStream(dest, hashAlgorithm)
val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher) val cipherOutputStream = CipherOutputStream(fileOutputStream, cipher)
// Read, decrypt, and hash until we read 0 bytes // Read, decrypt, and hash until we read 0 bytes
var length: Int var length: Int
while (true) { while (true) {
length = fileInputStream.read(buffer) length = fileInputStream.read(buffer)
if (length <= 0) break if (length <= 0) break
digest.update(buffer, 0, length) digest.update(buffer, 0, length)
cipherOutputStream.write(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.Unit;
import kotlin.jvm.functions.Function1; 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 entrypointKey = "entrypoint_handle";
public static final String extraDataKey = "extra_data"; public static final String extraDataKey = "extra_data";
private static final String autoStartAtBootKey = "auto_start_at_boot"; private static final String autoStartAtBootKey = "auto_start_at_boot";
@ -166,53 +166,6 @@ import kotlin.jvm.functions.Function1;
} }
result.success(true); result.success(true);
break; 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": case "recordSentMessage":
ArrayList rargs = (ArrayList) call.arguments; ArrayList rargs = (ArrayList) call.arguments;
recordSentMessage(context, (String) rargs.get(0), (String) rargs.get(1), (String) rargs.get(2), (int) rargs.get(3)); 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(); 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 @Override
public void eventStub(@NonNull NotificationEvent event) { public void eventStub(@NonNull NotificationEvent event) {
// Stub to trick pigeon into // 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'; import 'package:moxplatform_platform_interface/moxplatform_platform_interface.dart';
class AndroidCryptographyImplementation extends CryptographyImplementation { class AndroidCryptographyImplementation extends CryptographyImplementation {
final _methodChannel = const MethodChannel('me.polynom.moxplatform_android'); final MoxplatformApi _api = MoxplatformApi();
@override @override
Future<CryptographyResult?> encryptFile( Future<CryptographyResult?> encryptFile(
@ -13,22 +13,13 @@ class AndroidCryptographyImplementation extends CryptographyImplementation {
CipherAlgorithm algorithm, CipherAlgorithm algorithm,
String hashSpec, String hashSpec,
) async { ) async {
final dynamic resultRaw = return _api.encryptFile(
await _methodChannel.invokeMethod<dynamic>('encryptFile', [
sourcePath, sourcePath,
destPath, destPath,
key, key,
iv, iv,
algorithm.value, algorithm,
hashSpec, 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, CipherAlgorithm algorithm,
String hashSpec, String hashSpec,
) async { ) async {
final dynamic resultRaw = return _api.decryptFile(
await _methodChannel.invokeMethod<dynamic>('decryptFile', [
sourcePath, sourcePath,
destPath, destPath,
key, key,
iv, iv,
algorithm.value, algorithm,
hashSpec, 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 @override
Future<Uint8List?> hashFile(String path, String hashSpec) async { Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async {
final dynamic resultsRaw = return _api.hashFile(sourcePath, hashSpec);
await _methodChannel.invokeMethod<dynamic>('hashFile', [
path,
hashSpec,
]);
if (resultsRaw == null) return null;
return resultsRaw as Uint8List;
} }
} }

View File

@ -20,6 +20,12 @@ enum NotificationEventType {
open, open,
} }
enum CipherAlgorithm {
aes128GcmNoPadding,
aes256GcmNoPadding,
aes256CbcPkcs7,
}
class NotificationMessageContent { class NotificationMessageContent {
NotificationMessageContent({ NotificationMessageContent({
this.body, 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 { class _MoxplatformApiCodec extends StandardMessageCodec {
const _MoxplatformApiCodec(); const _MoxplatformApiCodec();
@override @override
void writeValue(WriteBuffer buffer, Object? value) { void writeValue(WriteBuffer buffer, Object? value) {
if (value is MessagingNotification) { if (value is CryptographyResult) {
buffer.putUint8(128); buffer.putUint8(128);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NotificationEvent) { } else if (value is MessagingNotification) {
buffer.putUint8(129); buffer.putUint8(129);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NotificationI18nData) { } else if (value is NotificationEvent) {
buffer.putUint8(130); buffer.putUint8(130);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NotificationMessage) { } else if (value is NotificationI18nData) {
buffer.putUint8(131); buffer.putUint8(131);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is NotificationMessageContent) { } else if (value is NotificationMessage) {
buffer.putUint8(132); buffer.putUint8(132);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is RegularNotification) { } else if (value is NotificationMessageContent) {
buffer.putUint8(133); buffer.putUint8(133);
writeValue(buffer, value.encode()); writeValue(buffer, value.encode());
} else if (value is RegularNotification) {
buffer.putUint8(134);
writeValue(buffer, value.encode());
} else { } else {
super.writeValue(buffer, value); super.writeValue(buffer, value);
} }
@ -316,16 +351,18 @@ class _MoxplatformApiCodec extends StandardMessageCodec {
Object? readValueOfType(int type, ReadBuffer buffer) { Object? readValueOfType(int type, ReadBuffer buffer) {
switch (type) { switch (type) {
case 128: case 128:
return MessagingNotification.decode(readValue(buffer)!); return CryptographyResult.decode(readValue(buffer)!);
case 129: case 129:
return NotificationEvent.decode(readValue(buffer)!); return MessagingNotification.decode(readValue(buffer)!);
case 130: case 130:
return NotificationI18nData.decode(readValue(buffer)!); return NotificationEvent.decode(readValue(buffer)!);
case 131: case 131:
return NotificationMessage.decode(readValue(buffer)!); return NotificationI18nData.decode(readValue(buffer)!);
case 132: case 132:
return NotificationMessageContent.decode(readValue(buffer)!); return NotificationMessage.decode(readValue(buffer)!);
case 133: case 133:
return NotificationMessageContent.decode(readValue(buffer)!);
case 134:
return RegularNotification.decode(readValue(buffer)!); return RegularNotification.decode(readValue(buffer)!);
default: default:
return super.readValueOfType(type, buffer); return super.readValueOfType(type, buffer);
@ -343,6 +380,7 @@ class MoxplatformApi {
static const MessageCodec<Object?> codec = _MoxplatformApiCodec(); static const MessageCodec<Object?> codec = _MoxplatformApiCodec();
/// Notification APIs
Future<void> createNotificationChannel(String arg_title, String arg_description, String arg_id, bool arg_urgent) async { Future<void> createNotificationChannel(String arg_title, String arg_description, String arg_id, bool arg_urgent) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel', codec, 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.createNotificationChannel', codec,
@ -475,6 +513,7 @@ class MoxplatformApi {
} }
} }
/// Platform APIs
Future<String> getPersistentDataPath() async { Future<String> getPersistentDataPath() async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.getPersistentDataPath', codec, '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 { Future<void> eventStub(NotificationEvent arg_event) async {
final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>(
'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.eventStub', codec, 'dev.flutter.pigeon.moxplatform_platform_interface.MoxplatformApi.eventStub', codec,

View File

@ -1,21 +1,5 @@
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:moxplatform_platform_interface/src/api.g.dart';
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;
}
/// Wrapper around platform-native cryptography APIs /// Wrapper around platform-native cryptography APIs
abstract class CryptographyImplementation { abstract class CryptographyImplementation {
@ -47,9 +31,9 @@ abstract class CryptographyImplementation {
String hashSpec, 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. /// Note that this function runs off-thread as to not block the UI thread.
/// ///
/// Returns the hash of the file. /// 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 'dart:typed_data';
import 'package:moxplatform_platform_interface/src/api.g.dart';
import 'package:moxplatform_platform_interface/src/crypto.dart'; import 'package:moxplatform_platform_interface/src/crypto.dart';
class StubCryptographyImplementation extends CryptographyImplementation { class StubCryptographyImplementation extends CryptographyImplementation {
@ -27,7 +28,7 @@ class StubCryptographyImplementation extends CryptographyImplementation {
} }
@override @override
Future<Uint8List?> hashFile(String path, String hashSpec) async { Future<Uint8List?> hashFile(String sourcePath, String hashSpec) async {
return null; return null;
} }
} }

View File

@ -149,15 +149,37 @@ class NotificationI18nData {
final String you; final String you;
} }
enum CipherAlgorithm {
aes128GcmNoPadding,
aes256GcmNoPadding,
aes256CbcPkcs7;
}
class CryptographyResult {
const CryptographyResult(this.plaintextHash, this.ciphertextHash);
final Uint8List plaintextHash;
final Uint8List ciphertextHash;
}
@HostApi() @HostApi()
abstract class MoxplatformApi { abstract class MoxplatformApi {
/// Notification APIs
void createNotificationChannel(String title, String description, String id, bool urgent); void createNotificationChannel(String title, String description, String id, bool urgent);
void showMessagingNotification(MessagingNotification notification); void showMessagingNotification(MessagingNotification notification);
void showNotification(RegularNotification notification); void showNotification(RegularNotification notification);
void dismissNotification(int id); void dismissNotification(int id);
void setNotificationSelfAvatar(String path); void setNotificationSelfAvatar(String path);
void setNotificationI18n(NotificationI18nData data); void setNotificationI18n(NotificationI18nData data);
/// Platform APIs
String getPersistentDataPath(); String getPersistentDataPath();
String getCacheDataPath(); 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); void eventStub(NotificationEvent event);
} }