diff --git a/lib/omemo_dart.dart b/lib/omemo_dart.dart index ae10e34..1db0f88 100644 --- a/lib/omemo_dart.dart +++ b/lib/omemo_dart.dart @@ -5,4 +5,5 @@ export 'src/errors.dart'; export 'src/helpers.dart'; export 'src/keys.dart'; export 'src/omemo/bundle.dart'; +export 'src/omemo/sessionmanager.dart'; export 'src/x3dh/x3dh.dart'; diff --git a/lib/src/omemo/sessionmanager.dart b/lib/src/omemo/sessionmanager.dart index c83c5d7..93f2913 100644 --- a/lib/src/omemo/sessionmanager.dart +++ b/lib/src/omemo/sessionmanager.dart @@ -5,6 +5,7 @@ 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/bundle.dart'; import 'package:omemo_dart/src/omemo/device.dart'; import 'package:synchronized/synchronized.dart'; @@ -36,6 +37,7 @@ class OmemoSessionManager { /// Generate a new cryptographic identity. static Future generateNewIdentity({ int opkAmount = 100 }) async { + assert(opkAmount > 0, 'opkAmount must be bigger than 0.'); final device = await Device.generateNewDevice(opkAmount: opkAmount); return OmemoSessionManager(device); @@ -72,6 +74,10 @@ class OmemoSessionManager { } }); } + + Future addSessionFromBundle(String jid, String deviceId, OmemoBundle bundle) async { + // TODO(PapaTutuWawa): Do + } /// Encrypt the key [plaintext] for all known bundles of [jid]. Returns a map that /// maps the Bundle Id to the ciphertext of [plaintext]. @@ -87,8 +93,8 @@ class OmemoSessionManager { keys.iv, ); final hmac = await truncatedHmac(ciphertext, keys.authenticationKey); - final concatKey = concat([keys.encryptionKey, hmac]); - + final concatKey = concat([key, hmac]); + await _lock.synchronized(() async { // We assume that the user already checked if the session exists for (final deviceId in _deviceMap[jid]!) { @@ -121,13 +127,13 @@ class OmemoSessionManager { 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 keyAndHmac = await ratchet.ratchetDecrypt(message, decodedRawKey); final key = keyAndHmac.sublist(0, 32); final hmac = keyAndHmac.sublist(32, 48); final derivedKeys = await deriveEncryptionKeys(key, omemoPayloadInfoString); diff --git a/test/omemo_test.dart b/test/omemo_test.dart new file mode 100644 index 0000000..f60a7cf --- /dev/null +++ b/test/omemo_test.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; +import 'package:omemo_dart/omemo_dart.dart'; +import 'package:test/test.dart'; + +void main() { + test('Test using OMEMO sessions with only one device per user', () async { + const aliceJid = 'alice@server.example'; + const bobJid = 'bob@other.server.example'; + + // Alice and Bob generate their sessions + final aliceSession = await OmemoSessionManager.generateNewIdentity(opkAmount: 1); + final bobSession = await OmemoSessionManager.generateNewIdentity(opkAmount: 1); + + // Perform the X3DH + final x3dhAliceResult = await x3dhFromBundle( + await bobSession.device.toBundle(), + aliceSession.device.ik, + ); + final x3dhBobResult = await x3dhFromInitialMessage( + X3DHMessage( + aliceSession.device.ik.pk, + x3dhAliceResult.ek.pk, + '2', + ), + bobSession.device.spk, + bobSession.device.opks.values.elementAt(0), + bobSession.device.ik, + ); + + // Build the ratchets + final aliceRatchet = await OmemoDoubleRatchet.initiateNewSession( + bobSession.device.spk.pk, + x3dhAliceResult.sk, + x3dhAliceResult.ad, + ); + final bobRatchet = await OmemoDoubleRatchet.acceptNewSession( + bobSession.device.spk, + x3dhBobResult.sk, + x3dhBobResult.ad, + ); + + // Add the ratchets to the session managers + await aliceSession.addSession(bobJid, bobSession.device.id, aliceRatchet); + await bobSession.addSession(aliceJid, aliceSession.device.id, bobRatchet); + + // Alice encrypts a message for Bob + const messagePlaintext = 'Hello Bob!'; + final aliceMessage = await aliceSession.encryptToJid(bobJid, messagePlaintext); + expect(aliceMessage.encryptedKeys.length, 1); + + // Alice sends the message to Bob + // ... + + // Bob decrypts it + final bobMessage = await bobSession.decryptMessage( + aliceMessage.ciphertext, + aliceJid, + aliceSession.device.id, + [ + EncryptedKey( + bobSession.device.id, + base64.encode(aliceMessage.encryptedKeys[bobSession.device.id]!), + ), + ], + ); + + expect(messagePlaintext, bobMessage); + }); +}