151 lines
4.2 KiB
Dart
151 lines
4.2 KiB
Dart
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:cryptography/cryptography.dart';
|
|
import 'package:moxxmpp/moxxmpp.dart';
|
|
import 'package:moxxyv2/service/cryptography/types.dart';
|
|
|
|
Future<List<int>> hashFileImpl(HashRequest request) async {
|
|
final data = await File(request.path).readAsBytes();
|
|
|
|
return CryptographicHashManager.hashFromData(data, request.hash);
|
|
}
|
|
|
|
Future<EncryptionResult> encryptFileImpl(EncryptionRequest request) async {
|
|
Cipher algorithm;
|
|
switch (request.encryption) {
|
|
case SFSEncryptionType.aes128GcmNoPadding:
|
|
algorithm = AesGcm.with128bits();
|
|
break;
|
|
case SFSEncryptionType.aes256GcmNoPadding:
|
|
algorithm = AesGcm.with256bits();
|
|
break;
|
|
case SFSEncryptionType.aes256CbcPkcs7:
|
|
// TODO(Unknown): Implement
|
|
throw Exception();
|
|
// ignore: dead_code
|
|
break;
|
|
}
|
|
|
|
// Generate a key and an IV for the file
|
|
final key = await algorithm.newSecretKey();
|
|
final iv = algorithm.newNonce();
|
|
final plaintext = await File(request.source).readAsBytes();
|
|
final secretBox = await algorithm.encrypt(
|
|
plaintext,
|
|
secretKey: key,
|
|
nonce: iv,
|
|
);
|
|
final ciphertext = [
|
|
...secretBox.cipherText,
|
|
...secretBox.mac.bytes,
|
|
];
|
|
|
|
// Write the file
|
|
await File(request.dest).writeAsBytes(ciphertext);
|
|
|
|
return EncryptionResult(
|
|
await key.extractBytes(),
|
|
iv,
|
|
{
|
|
hashSha256: base64Encode(
|
|
await CryptographicHashManager.hashFromData(plaintext, HashFunction.sha256),
|
|
),
|
|
},
|
|
{
|
|
hashSha256: base64Encode(
|
|
await CryptographicHashManager.hashFromData(ciphertext, HashFunction.sha256),
|
|
),
|
|
},
|
|
);
|
|
}
|
|
|
|
// TODO(PapaTutuWawa): Somehow fail when the ciphertext hash is not matching the provided data
|
|
Future<DecryptionResult> decryptFileImpl(DecryptionRequest request) async {
|
|
Cipher algorithm;
|
|
switch (request.encryption) {
|
|
case SFSEncryptionType.aes128GcmNoPadding:
|
|
algorithm = AesGcm.with128bits();
|
|
break;
|
|
case SFSEncryptionType.aes256GcmNoPadding:
|
|
algorithm = AesGcm.with256bits();
|
|
break;
|
|
case SFSEncryptionType.aes256CbcPkcs7:
|
|
// TODO(Unknown): Implement
|
|
throw Exception();
|
|
// ignore: dead_code
|
|
break;
|
|
}
|
|
|
|
final ciphertextRaw = await File(request.source).readAsBytes();
|
|
final mac = List<int>.empty(growable: true);
|
|
final ciphertext = List<int>.empty(growable: true);
|
|
// TODO(PapaTutuWawa): Somehow handle aes256CbcPkcs7
|
|
if (request.encryption == SFSEncryptionType.aes128GcmNoPadding ||
|
|
request.encryption == SFSEncryptionType.aes256GcmNoPadding) {
|
|
mac.addAll(ciphertextRaw.sublist(ciphertextRaw.length - 16));
|
|
ciphertext.addAll(ciphertextRaw.sublist(0, ciphertextRaw.length - 16));
|
|
}
|
|
|
|
var passedCiphertextIntegrityCheck = true;
|
|
var passedPlaintextIntegrityCheck = true;
|
|
// Try to find one hash we can verify
|
|
for (final entry in request.ciphertextHashes.entries) {
|
|
if ([hashSha256, hashSha512, hashBlake2b512].contains(entry.key)) {
|
|
final hash = await CryptographicHashManager.hashFromData(
|
|
ciphertext,
|
|
hashFunctionFromName(entry.key),
|
|
);
|
|
|
|
if (base64Encode(hash) == entry.value) {
|
|
passedCiphertextIntegrityCheck = true;
|
|
} else {
|
|
passedCiphertextIntegrityCheck = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
final secretBox = SecretBox(
|
|
ciphertext,
|
|
nonce: request.iv,
|
|
mac: Mac(mac),
|
|
);
|
|
|
|
try {
|
|
final data = await algorithm.decrypt(
|
|
secretBox,
|
|
secretKey: SecretKey(request.key),
|
|
);
|
|
|
|
for (final entry in request.plaintextHashes.entries) {
|
|
if ([hashSha256, hashSha512, hashBlake2b512].contains(entry.key)) {
|
|
final hash = await CryptographicHashManager.hashFromData(
|
|
data,
|
|
hashFunctionFromName(entry.key),
|
|
);
|
|
|
|
if (base64Encode(hash) == entry.value) {
|
|
passedPlaintextIntegrityCheck = true;
|
|
} else {
|
|
passedPlaintextIntegrityCheck = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
await File(request.dest).writeAsBytes(data);
|
|
} catch (_) {
|
|
return DecryptionResult(
|
|
false,
|
|
passedPlaintextIntegrityCheck,
|
|
passedCiphertextIntegrityCheck,
|
|
);
|
|
}
|
|
|
|
return DecryptionResult(
|
|
true,
|
|
passedPlaintextIntegrityCheck,
|
|
passedCiphertextIntegrityCheck,
|
|
);
|
|
}
|