feat: Guard against invalid X3DH signatures

This commit is contained in:
PapaTutuWawa 2023-06-15 16:07:23 +02:00
parent c7ded4c824
commit af33ed51d1
5 changed files with 27 additions and 24 deletions

View File

@ -1,10 +1,7 @@
abstract class OmemoError {} abstract class OmemoError {}
/// Triggered during X3DH if the signature if the SPK does verify to the actual SPK. /// Triggered during X3DH if the signature if the SPK does verify to the actual SPK.
class InvalidSignatureException extends OmemoError implements Exception { class InvalidKeyExchangeSignatureError extends OmemoError {}
String errMsg() =>
'The signature of the SPK does not match the provided signature';
}
/// Triggered by the Double Ratchet if the computed HMAC does not match the attached HMAC. /// Triggered by the Double Ratchet if the computed HMAC does not match the attached HMAC.
class InvalidMessageHMACError extends OmemoError {} class InvalidMessageHMACError extends OmemoError {}

View File

@ -509,6 +509,7 @@ class OmemoManager {
ciphertext = []; ciphertext = [];
} }
final encryptionErrors = <String, List<EncryptToJidError>>{};
final addedRatchetKeys = List<RatchetMapKey>.empty(growable: true); final addedRatchetKeys = List<RatchetMapKey>.empty(growable: true);
final kex = <RatchetMapKey, OMEMOKeyExchange>{}; final kex = <RatchetMapKey, OMEMOKeyExchange>{};
for (final jid in stanza.recipientJids) { for (final jid in stanza.recipientJids) {
@ -521,10 +522,24 @@ class OmemoManager {
_log.finest('Building new ratchet $jid:${bundle.id}'); _log.finest('Building new ratchet $jid:${bundle.id}');
final ratchetKey = RatchetMapKey(jid, bundle.id); final ratchetKey = RatchetMapKey(jid, bundle.id);
final ownDevice = await getDevice(); final ownDevice = await getDevice();
final kexResult = await x3dhFromBundle( final kexResultRaw = await x3dhFromBundle(
bundle, bundle,
ownDevice.ik, ownDevice.ik,
); );
// TODO: Track the failure and do not attempt to encrypt to this device
// on every send.
if (kexResultRaw.isType<InvalidKeyExchangeSignatureError>()) {
encryptionErrors.appendOrCreate(
jid,
EncryptToJidError(
bundle.id,
kexResultRaw.get<InvalidKeyExchangeSignatureError>(),
),
);
continue;
}
final kexResult = kexResultRaw.get<X3DHAliceResult>();
final newRatchet = await OmemoDoubleRatchet.initiateNewSession( final newRatchet = await OmemoDoubleRatchet.initiateNewSession(
bundle.spk, bundle.spk,
bundle.spkId, bundle.spkId,
@ -567,7 +582,6 @@ class OmemoManager {
} }
// Encrypt the symmetric key for all devices. // Encrypt the symmetric key for all devices.
final encryptionErrors = <String, List<EncryptToJidError>>{};
final encryptedKeys = <String, List<EncryptedKey>>{}; final encryptedKeys = <String, List<EncryptedKey>>{};
for (final jid in stanza.recipientJids) { for (final jid in stanza.recipientJids) {
// Check if we know about any devices to use // Check if we know about any devices to use

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:cryptography/cryptography.dart'; import 'package:cryptography/cryptography.dart';
import 'package:omemo_dart/src/common/result.dart';
import 'package:omemo_dart/src/crypto.dart'; import 'package:omemo_dart/src/crypto.dart';
import 'package:omemo_dart/src/errors.dart'; import 'package:omemo_dart/src/errors.dart';
import 'package:omemo_dart/src/helpers.dart'; import 'package:omemo_dart/src/helpers.dart';
@ -70,7 +71,7 @@ Future<List<int>> kdf(List<int> km) async {
/// Alice builds a session with Bob using his bundle [bundle] and Alice's identity key /// Alice builds a session with Bob using his bundle [bundle] and Alice's identity key
/// pair [ik]. /// pair [ik].
Future<X3DHAliceResult> x3dhFromBundle( Future<Result<InvalidKeyExchangeSignatureError, X3DHAliceResult>> x3dhFromBundle(
OmemoBundle bundle, OmemoBundle bundle,
OmemoKeyPair ik, OmemoKeyPair ik,
) async { ) async {
@ -84,7 +85,7 @@ Future<X3DHAliceResult> x3dhFromBundle(
); );
if (!signatureValue) { if (!signatureValue) {
throw InvalidSignatureException(); return Result(InvalidKeyExchangeSignatureError());
} }
// Generate EK // Generate EK
@ -106,7 +107,7 @@ Future<X3DHAliceResult> x3dhFromBundle(
await bundle.ik.getBytes(), await bundle.ik.getBytes(),
]); ]);
return X3DHAliceResult(ek, sk, opkId, ad); return Result(X3DHAliceResult(ek, sk, opkId, ad));
} }
/// Bob builds the X3DH shared secret from the inital message [msg], the SPK [spk], the /// Bob builds the X3DH shared secret from the inital message [msg], the SPK [spk], the

View File

@ -28,7 +28,8 @@ void main() {
); );
// Alice does X3DH // Alice does X3DH
final resultAlice = await x3dhFromBundle(bundleBob, ikAlice); final resultAliceRaw = await x3dhFromBundle(bundleBob, ikAlice);
final resultAlice = resultAliceRaw.get<X3DHAliceResult>();
// Alice sends the inital message to Bob // Alice sends the inital message to Bob
// ... // ...

View File

@ -26,7 +26,8 @@ void main() {
); );
// Alice does X3DH // Alice does X3DH
final resultAlice = await x3dhFromBundle(bundleBob, ikAlice); final resultAliceRaw = await x3dhFromBundle(bundleBob, ikAlice);
final resultAlice = resultAliceRaw.get<X3DHAliceResult>();
// Alice sends the inital message to Bob // Alice sends the inital message to Bob
// ... // ...
@ -68,18 +69,7 @@ void main() {
); );
// Alice does X3DH // Alice does X3DH
var exception = false; final result = await x3dhFromBundle(bundleBob, ikAlice);
try { expect(result.isType<InvalidKeyExchangeSignatureError>(), isTrue);
await x3dhFromBundle(bundleBob, ikAlice);
} catch (e) {
exception = true;
expect(
e is InvalidSignatureException,
true,
reason: 'Expected InvalidSignatureException, but got $e',
);
}
expect(exception, true, reason: 'Expected test failure');
}); });
} }