feat: Guard against malformed ciphertext

This commit is contained in:
PapaTutuWawa 2023-06-15 16:42:17 +02:00
parent f1ec8d1793
commit 6c301ab88f
5 changed files with 42 additions and 30 deletions

View File

@ -3,7 +3,7 @@ class Result<T, V> {
const Result(this._data) const Result(this._data)
: assert( : assert(
_data is T || _data is V, _data is T || _data is V,
'Invalid data type: Must be either $T or $V', 'Invalid data type $_data: Must be either $T or $V',
); );
final dynamic _data; final dynamic _data;

View File

@ -1,5 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:omemo_dart/src/common/result.dart';
import 'package:omemo_dart/src/errors.dart';
import 'package:omemo_dart/src/keys.dart'; import 'package:omemo_dart/src/keys.dart';
/// Performs X25519 with [kp] and [pk]. If [identityKey] is set, then /// Performs X25519 with [kp] and [pk]. If [identityKey] is set, then
@ -92,7 +94,7 @@ Future<List<int>> aes256CbcEncrypt(
/// A small helper function to make AES-256-CBC easier. Decrypt [ciphertext] using [key] as /// 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. /// the encryption key and [iv] as the IV. Returns the ciphertext.
Future<List<int>> aes256CbcDecrypt( Future<Result<MalformedCiphertextError, List<int>>> aes256CbcDecrypt(
List<int> ciphertext, List<int> ciphertext,
List<int> key, List<int> key,
List<int> iv, List<int> iv,
@ -100,13 +102,19 @@ Future<List<int>> aes256CbcDecrypt(
final algorithm = AesCbc.with256bits( final algorithm = AesCbc.with256bits(
macAlgorithm: MacAlgorithm.empty, macAlgorithm: MacAlgorithm.empty,
); );
return algorithm.decrypt( try {
return Result(
await algorithm.decrypt(
NoMacSecretBox( NoMacSecretBox(
ciphertext, ciphertext,
nonce: iv, nonce: iv,
), ),
secretKey: SecretKey(key), secretKey: SecretKey(key),
),
); );
} catch (ex) {
return Result(MalformedCiphertextError(ex));
}
} }
/// OMEMO often uses the output of a HMAC-SHA-256 truncated to its first 16 bytes. /// OMEMO often uses the output of a HMAC-SHA-256 truncated to its first 16 bytes.

View File

@ -261,7 +261,11 @@ class OmemoDoubleRatchet {
} }
final plaintext = await aes256CbcDecrypt(ciphertext, keys.encryptionKey, keys.iv); final plaintext = await aes256CbcDecrypt(ciphertext, keys.encryptionKey, keys.iv);
return Result(plaintext); if (plaintext.isType<MalformedCiphertextError>()) {
return Result(plaintext.get<MalformedCiphertextError>());
}
return Result(plaintext.get<List<int>>());
} }
/// Checks whether we could decrypt the payload in [header] with a skipped key. If yes, /// Checks whether we could decrypt the payload in [header] with a skipped key. If yes,

View File

@ -13,22 +13,10 @@ class SkippingTooManyKeysError extends OmemoError {}
/// Triggered by the Session Manager if the message key is not encrypted for the device. /// Triggered by the Session Manager if the message key is not encrypted for the device.
class NotEncryptedForDeviceError extends OmemoError {} class NotEncryptedForDeviceError extends OmemoError {}
/// Triggered by the Session Manager when there is no key for decrypting the message.
class NoDecryptionKeyException extends OmemoError implements Exception {
String errMsg() => 'No key available for decrypting the message';
}
/// Triggered by the Session Manager when the identifier of the used Signed Prekey /// Triggered by the Session Manager when the identifier of the used Signed Prekey
/// is neither the current SPK's identifier nor the old one's. /// is neither the current SPK's identifier nor the old one's.
class UnknownSignedPrekeyError extends OmemoError {} class UnknownSignedPrekeyError extends OmemoError {}
/// Triggered by the Session Manager when the received Key Exchange message does not meet
/// the requirement that a key exchange, given that the ratchet already exists, must be
/// sent after its creation.
class InvalidKeyExchangeException extends OmemoError implements Exception {
String errMsg() => 'The key exchange was sent before the last kex finished';
}
/// Triggered by the OmemoManager when we could not encrypt a message as we have /// Triggered by the OmemoManager when we could not encrypt a message as we have
/// no key material available. That happens, for example, when we want to create a /// no key material available. That happens, for example, when we want to create a
/// ratchet session with a JID we had no session with but fetching the device bundle /// ratchet session with a JID we had no session with but fetching the device bundle
@ -38,3 +26,11 @@ class NoKeyMaterialAvailableError extends OmemoError {}
/// A non-key-exchange message was received that was encrypted for our device, but we have no ratchet with /// A non-key-exchange message was received that was encrypted for our device, but we have no ratchet with
/// the device that sent the message. /// the device that sent the message.
class NoSessionWithDeviceError extends OmemoError {} class NoSessionWithDeviceError extends OmemoError {}
/// Caused when the AES-256 CBC decryption failed.
class MalformedCiphertextError extends OmemoError {
MalformedCiphertextError(this.ex);
/// The exception that was raised while decryption.
final Object ex;
}

View File

@ -152,14 +152,20 @@ class OmemoManager {
return Result(InvalidMessageHMACError()); return Result(InvalidMessageHMACError());
} }
// TODO: Handle an exception from the crypto implementation final result = await aes256CbcDecrypt(
return Result(
utf8.decode(
await aes256CbcDecrypt(
ciphertext, ciphertext,
derivedKeys.encryptionKey, derivedKeys.encryptionKey,
derivedKeys.iv, derivedKeys.iv,
), );
if (result.isType<MalformedCiphertextError>()) {
return Result(
result.get<MalformedCiphertextError>(),
);
}
return Result(
utf8.decode(
result.get<List<int>>(),
), ),
); );
} }
@ -316,8 +322,6 @@ class OmemoManager {
kexMessage.ek, kexMessage.ek,
KeyPairType.x25519, KeyPairType.x25519,
); );
// TODO: Guard against invalid signatures
final kex = await x3dhFromInitialMessage( final kex = await x3dhFromInitialMessage(
X3DHMessage( X3DHMessage(
kexIk, kexIk,