feat: Add a untest decrypt function

This commit is contained in:
PapaTutuWawa 2022-08-04 14:01:50 +02:00
parent e34e0cc7fb
commit 4d6dbef549
4 changed files with 89 additions and 1 deletions

View File

@ -32,6 +32,19 @@ class HkdfKeyResult {
final List<int> iv;
}
/// cryptography _really_ wants to check the MAC output from AES-256-CBC. Since
/// we don't have it, we need the MAC check to always "pass".
class NoMacSecretBox extends SecretBox {
NoMacSecretBox(super.cipherText, { required super.nonce }) : super(mac: Mac.empty);
@override
Future<void> checkMac({
required MacAlgorithm macAlgorithm,
required SecretKey secretKey,
required List<int> aad,
}) async {}
}
/// OMEMO 0.8.3 often derives the three keys for encryption, authentication and the IV from
/// some input using HKDF-SHA-256. As such, this is a helper function that already provides
/// those three keys from [input] and the info string [info].
@ -65,6 +78,21 @@ Future<List<int>> aes256CbcEncrypt(List<int> plaintext, List<int> key, List<int>
return result.cipherText;
}
/// A small helper function to make AES-256-CBC easier. Decrypt [ciphertext] using [key] as
/// the encryption key and [iv] as the IV. Returns the ciphertext.
Future<List<int>> aes256CbcDecrypt(List<int> ciphertext, List<int> key, List<int> iv) async {
final algorithm = AesCbc.with256bits(
macAlgorithm: MacAlgorithm.empty,
);
return algorithm.decrypt(
NoMacSecretBox(
ciphertext,
nonce: iv,
),
secretKey: SecretKey(key),
);
}
/// OMEMO often uses the output of a HMAC-SHA-256 truncated to its first 16 bytes.
/// Calculate the HMAC-SHA-256 of [input] using the authentication key [key] and
/// truncate the output to 16 bytes.

View File

@ -3,7 +3,8 @@ class InvalidSignatureException implements Exception {
String errMsg() => 'The signature of the SPK does not match the provided signature';
}
/// Triggered by the Double Ratchet if the computet HMAC does not match the attached HMAC.
/// Triggered by the Double Ratchet if the computed HMAC does not match the attached HMAC.
/// Triggered by the Session Manager if the computed HMAC does not match the attached HMAC.
class InvalidMessageHMACException implements Exception {
String errMsg() => 'The computed HMAC does not match the provided HMAC';
}
@ -13,3 +14,13 @@ class InvalidMessageHMACException implements Exception {
class SkippingTooManyMessagesException implements Exception {
String errMsg() => 'Skipping messages would cause a skip bigger than MAXSKIP';
}
/// Triggered by the Session Manager if the message key is not encrypted for the device.
class NotEncryptedForDeviceException implements Exception {
String errMsg() => 'Not encrypted for this device';
}
/// Triggered by the Session Manager when there is no key for decrypting the message.
class NoDecryptionKeyException implements Exception {
String errMsg() => 'No key available for decrypting the message';
}

View File

@ -1,6 +1,9 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:omemo_dart/protobuf/schema.pb.dart';
import 'package:omemo_dart/src/crypto.dart';
import 'package:omemo_dart/src/double_ratchet/double_ratchet.dart';
import 'package:omemo_dart/src/errors.dart';
import 'package:omemo_dart/src/helpers.dart';
import 'package:omemo_dart/src/omemo/device.dart';
import 'package:synchronized/synchronized.dart';
@ -20,6 +23,13 @@ class EncryptionResult {
final Map<String, List<int>> encryptedKeys;
}
class EncryptedKey {
const EncryptedKey(this.rid, this.value);
final String rid;
final String value;
}
class OmemoSessionManager {
OmemoSessionManager(this.device) : _ratchetMap = {}, _deviceMap = {}, _lock = Lock();
@ -92,4 +102,42 @@ class OmemoSessionManager {
encryptedKeys,
);
}
/// Attempt to decrypt [ciphertext]. [keys] refers to the <key /> elements inside the
/// <keys /> element with a "jid" attribute matching our own. [senderJid] refers to the
/// bare Jid of the sender. [senderDeviceId] refers to the "sid" attribute of the
/// <encrypted /> element.
Future<String> decryptMessage(List<int> ciphertext, String senderJid, String senderDeviceId, List<EncryptedKey> keys) async {
// Try to find a session we can decrypt with.
final rawKey = keys.firstWhereOrNull((key) => key.rid == device.id);
if (rawKey == null) {
throw NotEncryptedForDeviceException();
}
final devices = _deviceMap[senderJid];
if (devices == null) {
throw NoDecryptionKeyException();
}
if (!devices.contains(senderDeviceId)) {
throw NoDecryptionKeyException();
}
final decodedRawKey = base64.decode(rawKey.value);
final authMessage = OMEMOAuthenticatedMessage.fromBuffer(decodedRawKey);
final message = OMEMOMessage.fromBuffer(authMessage.message);
final ratchet = _ratchetMap[senderDeviceId]!;
final keyAndHmac = await ratchet.ratchetDecrypt(message, message.ciphertext);
final key = keyAndHmac.sublist(0, 32);
final hmac = keyAndHmac.sublist(32, 48);
final derivedKeys = await deriveEncryptionKeys(key, omemoPayloadInfoString);
final computedHmac = await truncatedHmac(ciphertext, derivedKeys.authenticationKey);
if (!listsEqual(hmac, computedHmac)) {
throw InvalidMessageHMACException();
}
final plaintext = await aes256CbcDecrypt(ciphertext, derivedKeys.encryptionKey, derivedKeys.iv);
return utf8.decode(plaintext);
}
}

View File

@ -6,6 +6,7 @@ environment:
sdk: '>=2.17.0 <3.0.0'
dependencies:
collection: ^1.16.0
cryptography: ^2.0.5
pinenacl: ^0.5.1
protobuf: ^2.1.0