feat: Guard against malformed ciphertext
This commit is contained in:
parent
f1ec8d1793
commit
6c301ab88f
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
NoMacSecretBox(
|
return Result(
|
||||||
ciphertext,
|
await algorithm.decrypt(
|
||||||
nonce: iv,
|
NoMacSecretBox(
|
||||||
),
|
ciphertext,
|
||||||
secretKey: SecretKey(key),
|
nonce: iv,
|
||||||
);
|
),
|
||||||
|
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.
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -152,14 +152,20 @@ class OmemoManager {
|
|||||||
return Result(InvalidMessageHMACError());
|
return Result(InvalidMessageHMACError());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle an exception from the crypto implementation
|
final result = await aes256CbcDecrypt(
|
||||||
|
ciphertext,
|
||||||
|
derivedKeys.encryptionKey,
|
||||||
|
derivedKeys.iv,
|
||||||
|
);
|
||||||
|
if (result.isType<MalformedCiphertextError>()) {
|
||||||
|
return Result(
|
||||||
|
result.get<MalformedCiphertextError>(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return Result(
|
return Result(
|
||||||
utf8.decode(
|
utf8.decode(
|
||||||
await aes256CbcDecrypt(
|
result.get<List<int>>(),
|
||||||
ciphertext,
|
|
||||||
derivedKeys.encryptionKey,
|
|
||||||
derivedKeys.iv,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -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,
|
||||||
|
Loading…
Reference in New Issue
Block a user