From c2f62e29675029e148be9f6576ee060200f6da30 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Sat, 17 Jun 2023 21:28:54 +0200 Subject: [PATCH] feat(xep): Implement an OMEMO example client --- examples_dart/bin/component.dart | 24 +-- examples_dart/bin/omemo_client.dart | 140 ++++++++++++++++++ examples_dart/pubspec.yaml | 5 + integration_tests/create_users.sh | 2 + packages/moxxmpp/lib/src/connection.dart | 9 ++ packages/moxxmpp/lib/src/events.dart | 4 +- packages/moxxmpp/lib/src/managers/data.dart | 14 +- packages/moxxmpp/lib/src/message.dart | 11 +- packages/moxxmpp/lib/src/stanza.dart | 10 +- packages/moxxmpp/lib/src/util/queue.dart | 6 +- packages/moxxmpp/lib/src/util/typed_map.dart | 2 + .../lib/src/xeps/xep_0030/xep_0030.dart | 6 +- .../lib/src/xeps/xep_0060/xep_0060.dart | 9 ++ .../moxxmpp/lib/src/xeps/xep_0384/types.dart | 4 +- .../lib/src/xeps/xep_0384/xep_0384.dart | 117 ++++++++------- packages/moxxmpp/pubspec.yaml | 5 +- packages/moxxmpp/test/async_queue_test.dart | 4 - .../moxxmpp_socket_tcp/pubspec_overrides.yaml | 2 +- 18 files changed, 285 insertions(+), 89 deletions(-) create mode 100644 examples_dart/bin/omemo_client.dart create mode 100644 integration_tests/create_users.sh diff --git a/examples_dart/bin/component.dart b/examples_dart/bin/component.dart index 051751e..41d60ec 100644 --- a/examples_dart/bin/component.dart +++ b/examples_dart/bin/component.dart @@ -30,24 +30,26 @@ class EchoMessageManager extends XmppManagerBase { StanzaHandlerData state, ) async { final body = stanza.firstTag('body'); - if (body == null) return state.copyWith(done: true); + if (body == null) return state..done = true; final bodyText = body.innerText(); await getAttributes().sendStanza( - Stanza.message( - to: stanza.from, - children: [ - XMLNode( - tag: 'body', - text: 'Hello, ${stanza.from}! You said "$bodyText"', - ), - ], + StanzaDetails( + Stanza.message( + to: stanza.from, + children: [ + XMLNode( + tag: 'body', + text: 'Hello, ${stanza.from}! You said "$bodyText"', + ), + ], + ), + awaitable: false, ), - awaitable: false, ); - return state.copyWith(done: true); + return state..done = true; } } diff --git a/examples_dart/bin/omemo_client.dart b/examples_dart/bin/omemo_client.dart new file mode 100644 index 0000000..183f999 --- /dev/null +++ b/examples_dart/bin/omemo_client.dart @@ -0,0 +1,140 @@ +import 'package:args/args.dart'; +import 'package:chalkdart/chalk.dart'; +import 'package:cli_repl/cli_repl.dart'; +import 'package:logging/logging.dart'; +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; +import 'package:omemo_dart/omemo_dart.dart' as omemo; + +class TestingOmemoManager extends BaseOmemoManager { + TestingOmemoManager(this._encryptToJid); + + final JID _encryptToJid; + + late omemo.OmemoManager manager; + + @override + Future getOmemoManager() async { + return manager; + } + + @override + Future shouldEncryptStanza(JID toJid, Stanza stanza) async { + return toJid.toBare() == _encryptToJid; + } +} + +class TestingTCPSocketWrapper extends TCPSocketWrapper { + @override + bool onBadCertificate(dynamic certificate, String domain) { + return true; + } +} + +void main(List args) async { + // Set up logging + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + // ignore: avoid_print + print( + '[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}', + ); + }); + + final parser = ArgParser() + ..addOption('jid') + ..addOption('password') + ..addOption('host') + ..addOption('port') + ..addOption('to'); + final options = parser.parse(args); + + // Connect + final jid = JID.fromString(options['jid']! as String); + final to = JID.fromString(options['to']! as String).toBare(); + final portString = options['port'] as String?; + final connection = XmppConnection( + TestingReconnectionPolicy(), + AlwaysConnectedConnectivityManager(), + ClientToServerNegotiator(), + TestingTCPSocketWrapper(), + )..connectionSettings = ConnectionSettings( + jid: jid, + password: options['password']! as String, + host: options['host'] as String?, + port: portString != null ? int.parse(portString) : null, + ); + + // Generate OMEMO data + final moxxmppOmemo = TestingOmemoManager(to); + final omemoManager = omemo.OmemoManager( + await omemo.OmemoDevice.generateNewDevice(jid.toString(), opkAmount: 5), + omemo.BlindTrustBeforeVerificationTrustManager(), + moxxmppOmemo.sendEmptyMessageImpl, + moxxmppOmemo.fetchDeviceList, + moxxmppOmemo.fetchDeviceBundle, + moxxmppOmemo.subscribeToDeviceListImpl, + ); + moxxmppOmemo.manager = omemoManager; + final deviceId = await omemoManager.getDeviceId(); + Logger.root.info('Our device id: $deviceId'); + + // Register the managers and negotiators + await connection.registerManagers([ + PresenceManager(), + DiscoManager([]), + PubSubManager(), + MessageManager(), + moxxmppOmemo, + ]); + await connection.registerFeatureNegotiators([ + SaslPlainNegotiator(), + ResourceBindingNegotiator(), + StartTlsNegotiator(), + SaslScramNegotiator(10, '', '', ScramHashType.sha1), + ]); + + // Set up event handlers + connection.asBroadcastStream().listen((event) { + if (event is MessageEvent) { + Logger.root.info(event.id); + Logger.root.info(event.extensions.keys.toList()); + + final body = event.encryptionError != null + ? chalk.red('Failed to decrypt message: ${event.encryptionError}') + : chalk.green(event.get()?.body ?? ''); + print('[${event.from.toString()}] ' + body); + } + }); + + // Connect + Logger.root.info('Connecting...'); + final result = await connection.connect(shouldReconnect: false, waitUntilLogin: true); + if (!result.isType()) { + Logger.root.severe('Authentication failed!'); + return; + } + Logger.root.info('Connected.'); + + // Publish our bundle + Logger.root.info('Publishing bundle'); + final device = await moxxmppOmemo.manager.getDevice(); + final omemoResult = await moxxmppOmemo.publishBundle(await device.toBundle()); + if (!omemoResult.isType()) { + Logger.root.severe('Failed to publish OMEMO bundle: ${omemoResult.get()}'); + return; + } + + final repl = Repl(prompt: '> '); + await for (final line in repl.runAsync()) { + await connection.getManagerById(messageManager)!.sendMessage( + to, + TypedMap.fromList([ + MessageBodyData(line), + ]), + ); + } + + // Disconnect + await connection.disconnect(); +} diff --git a/examples_dart/pubspec.yaml b/examples_dart/pubspec.yaml index 6d95fe3..5f4e285 100644 --- a/examples_dart/pubspec.yaml +++ b/examples_dart/pubspec.yaml @@ -7,6 +7,9 @@ environment: sdk: '>=2.18.0 <3.0.0' dependencies: + args: 2.4.1 + chalkdart: 2.0.9 + cli_repl: 0.2.3 logging: ^1.0.2 moxxmpp: hosted: https://git.polynom.me/api/packages/Moxxy/pub @@ -14,6 +17,8 @@ dependencies: moxxmpp_socket_tcp: hosted: https://git.polynom.me/api/packages/Moxxy/pub version: 0.3.1 + omemo_dart: + path: ../../../Personal/omemo_dart dependency_overrides: moxxmpp: diff --git a/integration_tests/create_users.sh b/integration_tests/create_users.sh new file mode 100644 index 0000000..89b3d9f --- /dev/null +++ b/integration_tests/create_users.sh @@ -0,0 +1,2 @@ +prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123 +prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123 \ No newline at end of file diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 413d8a0..6a0442d 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -479,6 +479,7 @@ class XmppConnection { newStanza, TypedMap(), encrypted: details.encrypted, + shouldEncrypt: details.shouldEncrypt, forceEncryption: details.forceEncryption, ), ); @@ -736,6 +737,13 @@ class XmppConnection { : ''; _log.finest('<== $prefix${incomingPreHandlers.stanza.toXml()}'); + if (incomingPreHandlers.skip) { + _log.fine( + 'Not processing stanza (${incomingPreHandlers.stanza.tag}, ${incomingPreHandlers.stanza.id}) due to skip=true.', + ); + return; + } + final awaited = await _stanzaAwaiter.onData( incomingPreHandlers.stanza, connectionSettings.jid.toBare(), @@ -753,6 +761,7 @@ class XmppConnection { incomingPreHandlers.stanza, incomingPreHandlers.extensions, encrypted: incomingPreHandlers.encrypted, + encryptionError: incomingPreHandlers.encryptionError, cancelReason: incomingPreHandlers.cancelReason, ), ); diff --git a/packages/moxxmpp/lib/src/events.dart b/packages/moxxmpp/lib/src/events.dart index 9c0b8b3..21e12c2 100644 --- a/packages/moxxmpp/lib/src/events.dart +++ b/packages/moxxmpp/lib/src/events.dart @@ -70,9 +70,9 @@ class MessageEvent extends XmppEvent { MessageEvent( this.from, this.to, - this.id, this.encrypted, this.extensions, { + this.id, this.type, this.error, this.encryptionError, @@ -85,7 +85,7 @@ class MessageEvent extends XmppEvent { final JID to; /// The id attribute of the message. - final String id; + final String? id; /// The type attribute of the message. final String? type; diff --git a/packages/moxxmpp/lib/src/managers/data.dart b/packages/moxxmpp/lib/src/managers/data.dart index 6d8a5f2..924f8aa 100644 --- a/packages/moxxmpp/lib/src/managers/data.dart +++ b/packages/moxxmpp/lib/src/managers/data.dart @@ -13,12 +13,20 @@ class StanzaHandlerData { this.encryptionError, this.encrypted = false, this.forceEncryption = false, + this.shouldEncrypt = true, + this.skip = false, }); /// Indicates to the runner that processing is now done. This means that all /// pre-processing is done and no other handlers should be consulted. bool done; + /// Only useful in combination with [done] = true: When [skip] is set to true and + /// this [StanzaHandlerData] object is returned from a IncomingPreStanzaHandler, then + /// moxxmpp will skip checking whether the stanza was awaited and will not run any actual + /// IncomingStanzaHandler callbacks. + bool skip; + /// Indicates to the runner that processing is to be cancelled and no further handlers /// should run. The stanza also will not be sent. bool cancel; @@ -33,7 +41,7 @@ class StanzaHandlerData { /// absolutely necessary, e.g. with Message Carbons or OMEMO. Stanza stanza; - /// Whether the stanza was received encrypted + /// Whether the stanza is already encrypted bool encrypted; // If true, forces the encryption manager to encrypt to the JID, even if it @@ -42,6 +50,10 @@ class StanzaHandlerData { // to the JID anyway. bool forceEncryption; + /// Flag indicating whether a E2EE implementation should encrypt the stanza (true) + /// or not (false). + bool shouldEncrypt; + /// Additional data from other managers. final TypedMap extensions; } diff --git a/packages/moxxmpp/lib/src/message.dart b/packages/moxxmpp/lib/src/message.dart index bae047e..9700ee6 100644 --- a/packages/moxxmpp/lib/src/message.dart +++ b/packages/moxxmpp/lib/src/message.dart @@ -73,16 +73,23 @@ class MessageManager extends XmppManagerBase { Future isSupported() async => true; Future _onMessage( - Stanza _, + Stanza stanza, StanzaHandlerData state, ) async { + final body = stanza.firstTag('body'); + if (body != null) { + state.extensions.set( + MessageBodyData(body.innerText()), + ); + } + getAttributes().sendEvent( MessageEvent( JID.fromString(state.stanza.attributes['from']! as String), JID.fromString(state.stanza.attributes['to']! as String), - state.stanza.attributes['id']! as String, state.encrypted, state.extensions, + id: state.stanza.attributes['id'] as String?, type: state.stanza.attributes['type'] as String?, error: StanzaError.fromStanza(state.stanza), encryptionError: state.encryptionError, diff --git a/packages/moxxmpp/lib/src/stanza.dart b/packages/moxxmpp/lib/src/stanza.dart index 0b07f8d..41db9f6 100644 --- a/packages/moxxmpp/lib/src/stanza.dart +++ b/packages/moxxmpp/lib/src/stanza.dart @@ -7,6 +7,7 @@ class StanzaDetails { this.stanza, { this.addId = true, this.awaitable = true, + this.shouldEncrypt = true, this.encrypted = false, this.forceEncryption = false, this.bypassQueue = false, @@ -22,9 +23,16 @@ class StanzaDetails { /// Track the stanza to allow awaiting its response. final bool awaitable; + final bool forceEncryption; + + /// Flag indicating whether the stanza that is sent is already encrypted (true) + /// or not (false). This is only useful for E2EE implementations that have to + /// send heartbeats that must bypass themselves. final bool encrypted; - final bool forceEncryption; + /// Tells an E2EE implementation, if available, to encrypt the stanza (true) or + /// ignore the stanza (false). + final bool shouldEncrypt; /// Bypasses being put into the queue. Useful for sending stanzas that must go out /// now, where it's okay if it does not get sent. diff --git a/packages/moxxmpp/lib/src/util/queue.dart b/packages/moxxmpp/lib/src/util/queue.dart index b209bc4..b10047b 100644 --- a/packages/moxxmpp/lib/src/util/queue.dart +++ b/packages/moxxmpp/lib/src/util/queue.dart @@ -50,16 +50,12 @@ class AsyncStanzaQueue { @visibleForTesting Queue get queue => _queue; - @visibleForTesting - bool get isRunning => _running; - /// Adds a job [entry] to the queue. Future enqueueStanza(StanzaQueueEntry entry) async { await _lock.synchronized(() async { _queue.add(entry); - if (!_running && _queue.isNotEmpty && await _canSendCallback()) { - _running = true; + if (_queue.isNotEmpty && await _canSendCallback()) { unawaited( _runJob(_queue.removeFirst()), ); diff --git a/packages/moxxmpp/lib/src/util/typed_map.dart b/packages/moxxmpp/lib/src/util/typed_map.dart index f6a096e..b2775b9 100644 --- a/packages/moxxmpp/lib/src/util/typed_map.dart +++ b/packages/moxxmpp/lib/src/util/typed_map.dart @@ -20,4 +20,6 @@ class TypedMap { /// Return the object of type [T] from the map, if it has been stored. T? get() => _data[T] as T?; + + Iterable get keys => _data.keys; } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart index 903304e..98679de 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0030/xep_0030.dart @@ -255,7 +255,7 @@ class DiscoManager extends XmppManagerBase { Future> discoInfoQuery( JID entity, { String? node, - bool shouldEncrypt = true, + bool shouldEncrypt = false, bool shouldCache = true, }) async { DiscoInfo? info; @@ -294,7 +294,7 @@ class DiscoManager extends XmppManagerBase { final stanza = (await getAttributes().sendStanza( StanzaDetails( buildDiscoInfoQueryStanza(entity, node), - encrypted: !shouldEncrypt, + shouldEncrypt: shouldEncrypt, ), ))!; final query = stanza.firstTag('query'); @@ -325,7 +325,7 @@ class DiscoManager extends XmppManagerBase { Future>> discoItemsQuery( JID entity, { String? node, - bool shouldEncrypt = true, + bool shouldEncrypt = false, }) async { final key = DiscoCacheKey(entity, node); final future = await _discoItemsTracker.waitFor(key); diff --git a/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart index c840c37..a6d590d 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0060/xep_0060.dart @@ -202,6 +202,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -245,6 +246,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -329,6 +331,7 @@ class PubSubManager extends XmppManagerBase { ) ], ), + shouldEncrypt: false, ), ))!; if (result.attributes['type'] != 'result') { @@ -419,6 +422,7 @@ class PubSubManager extends XmppManagerBase { ) ], ), + shouldEncrypt: false, ), ))!; @@ -471,6 +475,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -521,6 +526,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; if (form.attributes['type'] != 'result') { @@ -550,6 +556,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; if (submit.attributes['type'] != 'result') { @@ -580,6 +587,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; @@ -624,6 +632,7 @@ class PubSubManager extends XmppManagerBase { ), ], ), + shouldEncrypt: false, ), ))!; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart index 038598c..9a6c4b8 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/types.dart @@ -16,6 +16,6 @@ class OmemoEncryptionError { const OmemoEncryptionError(this.jids, this.devices); /// See omemo_dart's EncryptionResult for info on these fields. - final Map jids; - final Map devices; + final Map jids; + final Map devices; } diff --git a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart index bdf618d..e02a014 100644 --- a/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart +++ b/packages/moxxmpp/lib/src/xeps/xep_0384/xep_0384.dart @@ -24,7 +24,7 @@ import 'package:moxxmpp/src/xeps/xep_0384/crypto.dart'; import 'package:moxxmpp/src/xeps/xep_0384/errors.dart'; import 'package:moxxmpp/src/xeps/xep_0384/helpers.dart'; import 'package:moxxmpp/src/xeps/xep_0384/types.dart'; -import 'package:omemo_dart/omemo_dart.dart'; +import 'package:omemo_dart/omemo_dart.dart' as omemo; import 'package:xml/xml.dart'; const _doNotEncryptList = [ @@ -113,7 +113,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { } // Tell the OmemoManager - (await getOmemoManager()).onDeviceListUpdate(jid.toString(), ids); + await (await getOmemoManager()).onDeviceListUpdate(jid.toString(), ids); // Generate an event getAttributes().sendEvent(OmemoDeviceListUpdatedEvent(jid, ids)); @@ -121,13 +121,13 @@ abstract class BaseOmemoManager extends XmppManagerBase { } @visibleForOverriding - Future getOmemoManager(); + Future getOmemoManager(); /// Wrapper around using getSessionManager and then calling getDeviceId on it. Future _getDeviceId() async => (await getOmemoManager()).getDeviceId(); /// Wrapper around using getSessionManager and then calling getDeviceId on it. - Future _getDeviceBundle() async { + Future _getDeviceBundle() async { final om = await getOmemoManager(); final device = await om.getDevice(); return device.toBundle(); @@ -199,53 +199,43 @@ abstract class BaseOmemoManager extends XmppManagerBase { } XMLNode _buildEncryptedElement( - EncryptionResult result, + omemo.EncryptionResult result, String recipientJid, int deviceId, ) { final keyElements = >{}; - for (final key in result.encryptedKeys) { - final keyElement = XMLNode( + for (final keys in result.encryptedKeys.entries) { + keyElements[keys.key] = keys.value.map((ek) => XMLNode( tag: 'key', - attributes: { - 'rid': '${key.rid}', - 'kex': key.kex ? 'true' : 'false', + attributes: { + 'rid': ek.rid.toString(), + if (ek.kex) + 'kex': 'true', }, - text: key.value, - ); - - if (keyElements.containsKey(key.jid)) { - keyElements[key.jid]!.add(keyElement); - } else { - keyElements[key.jid] = [keyElement]; - } + text: ek.value, + ),).toList(); } final keysElements = keyElements.entries.map((entry) { return XMLNode( tag: 'keys', - attributes: { + attributes: { 'jid': entry.key, }, children: entry.value, ); }).toList(); - var payloadElement = []; - if (result.ciphertext != null) { - payloadElement = [ - XMLNode( - tag: 'payload', - text: base64.encode(result.ciphertext!), - ), - ]; - } - return XMLNode.xmlns( tag: 'encrypted', xmlns: omemoXmlns, children: [ - ...payloadElement, + if (result.ciphertext != null) + XMLNode( + tag: 'payload', + text: base64Encode(result.ciphertext!), + ), + XMLNode( tag: 'header', attributes: { @@ -259,7 +249,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// For usage with omemo_dart's OmemoManager. Future sendEmptyMessageImpl( - EncryptionResult result, + omemo.EncryptionResult result, String toJid, ) async { await getAttributes().sendStanza( @@ -301,17 +291,22 @@ abstract class BaseOmemoManager extends XmppManagerBase { } /// For usage with omemo_dart's OmemoManager - Future fetchDeviceBundle(String jid, int id) async { + Future fetchDeviceBundle(String jid, int id) async { final result = await retrieveDeviceBundle(JID.fromString(jid), id); if (result.isType()) return null; - return result.get(); + return result.get(); } Future _onOutgoingStanza( Stanza stanza, StanzaHandlerData state, ) async { + if (!state.shouldEncrypt) { + logger.finest('Not encrypting since state.shouldEncrypt is false'); + return state; + } + if (state.encrypted) { logger.finest('Not encrypting since state.encrypted is true'); return state; @@ -352,29 +347,31 @@ abstract class BaseOmemoManager extends XmppManagerBase { ?.isEnabled ?? false; final om = await getOmemoManager(); + final encryptToJids = [ + toJid.toString(), + if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(), + ]; final result = await om.onOutgoingStanza( - OmemoOutgoingStanza( - [ - toJid.toString(), - if (carbonsEnabled) getAttributes().getFullJID().toBare().toString(), - ], + omemo.OmemoOutgoingStanza( + encryptToJids, _buildEnvelope(toEncrypt, toJid.toString()), ), ); logger.finest('Encryption done'); - if (!result.isSuccess(2)) { + if (!result.canSend) { return state ..cancel = true // If we have no device list for toJid, then the contact most likely does not // support OMEMO:2 - ..cancelReason = result.jidEncryptionErrors[toJid.toString()] - is NoKeyMaterialAvailableException + ..cancelReason = result.deviceEncryptionErrors[toJid.toString()]!.first.error + is omemo.NoKeyMaterialAvailableError ? OmemoNotSupportedForContactException() : UnknownOmemoError() - ..encryptionError = OmemoEncryptionError( - result.jidEncryptionErrors, - result.deviceEncryptionErrors, + // TODO + ..encryptionError = const OmemoEncryptionError( + {}, + {}, ); } @@ -411,20 +408,19 @@ abstract class BaseOmemoManager extends XmppManagerBase { Stanza stanza, StanzaHandlerData state, ) async { - final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns); - if (encrypted == null) return state; if (stanza.from == null) return state; + final encrypted = stanza.firstTag('encrypted', xmlns: omemoXmlns)!; final fromJid = JID.fromString(stanza.from!).toBare(); final header = encrypted.firstTag('header')!; final payloadElement = encrypted.firstTag('payload'); - final keys = List.empty(growable: true); + // TODO: Only extract our own keys by the JID attribute + final keys = List.empty(growable: true); for (final keysElement in header.findTags('keys')) { final jid = keysElement.attributes['jid']! as String; for (final key in keysElement.findTags('key')) { keys.add( - EncryptedKey( - jid, + omemo.EncryptedKey( int.parse(key.attributes['rid']! as String), key.innerText(), key.attributes['kex'] == 'true', @@ -438,7 +434,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { final om = await getOmemoManager(); final result = await om.onIncomingStanza( - OmemoIncomingStanza( + omemo.OmemoIncomingStanza( fromJid.toString(), sid, state.extensions @@ -448,6 +444,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { DateTime.now().millisecondsSinceEpoch, keys, payloadElement?.innerText(), + false, ), ); @@ -464,6 +461,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { .toList(); } + logger.finest('Got payload: ${result.payload != null}'); if (result.payload != null) { XMLNode envelope; try { @@ -481,6 +479,8 @@ abstract class BaseOmemoManager extends XmppManagerBase { // Do not add forbidden elements from the envelope envelopeChildren.where(shouldEncryptElement), ); + + logger.finest('Adding children: ${envelopeChildren.map((c) => c.tag)}'); } else { logger.warning('Invalid envelope element: No element'); } @@ -490,6 +490,15 @@ abstract class BaseOmemoManager extends XmppManagerBase { } } + // Ignore heartbeat messages + if (stanza.tag == 'message' && encrypted.firstTag('payload') == null) { + logger.finest('Received empty OMEMO message. Ending processing early.'); + return state + ..encrypted = true + ..skip = true + ..done = true; + } + return state ..encrypted = true ..stanza = Stanza( @@ -532,7 +541,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// Retrieve all device bundles for the JID [jid]. /// /// On success, returns a list of devices. On failure, returns am OmemoError. - Future>> retrieveDeviceBundles( + Future>> retrieveDeviceBundles( JID jid, ) async { // TODO(Unknown): Should we query the device list first? @@ -553,7 +562,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// Retrieves a bundle from entity [jid] with the device id [deviceId]. /// /// On success, returns the device bundle. On failure, returns an OmemoError. - Future> retrieveDeviceBundle( + Future> retrieveDeviceBundle( JID jid, int deviceId, ) async { @@ -569,7 +578,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// nodes. /// /// On success, returns true. On failure, returns an OmemoError. - Future> publishBundle(OmemoBundle bundle) async { + Future> publishBundle(omemo.OmemoBundle bundle) async { final attrs = getAttributes(); final pm = attrs.getManagerById(pubsubManager)!; final bareJid = attrs.getFullJID().toBare(); @@ -642,7 +651,7 @@ abstract class BaseOmemoManager extends XmppManagerBase { /// On failure, returns an OmemoError. Future> supportsOmemo(JID jid) async { final dm = getAttributes().getManagerById(discoManager)!; - final items = await dm.discoItemsQuery(jid.toBare()); + final items = await dm.discoItemsQuery(jid.toBare(), shouldEncrypt: false); if (items.isType()) return Result(UnknownOmemoError()); diff --git a/packages/moxxmpp/pubspec.yaml b/packages/moxxmpp/pubspec.yaml index 3198a24..e7aeb68 100644 --- a/packages/moxxmpp/pubspec.yaml +++ b/packages/moxxmpp/pubspec.yaml @@ -18,10 +18,9 @@ dependencies: meta: ^1.7.0 moxlib: hosted: https://git.polynom.me/api/packages/Moxxy/pub - version: ^0.1.5 + version: ^0.2.0 omemo_dart: - hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub - version: ^0.4.3 + path: ../../../../Personal/omemo_dart random_string: ^2.3.1 saslprep: ^1.0.2 synchronized: ^3.0.0+2 diff --git a/packages/moxxmpp/test/async_queue_test.dart b/packages/moxxmpp/test/async_queue_test.dart index 301172b..3b71fb6 100644 --- a/packages/moxxmpp/test/async_queue_test.dart +++ b/packages/moxxmpp/test/async_queue_test.dart @@ -30,7 +30,6 @@ void main() { await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 2); - expect(queue.isRunning, false); }); test('Test sending', () async { @@ -58,7 +57,6 @@ void main() { await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 0); - expect(queue.isRunning, false); }); test('Test partial sending and resuming', () async { @@ -89,12 +87,10 @@ void main() { await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 1); - expect(queue.isRunning, false); canRun = true; await queue.restart(); await Future.delayed(const Duration(seconds: 1)); expect(queue.queue.length, 0); - expect(queue.isRunning, false); }); } diff --git a/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml b/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml index 98626b5..ad07205 100644 --- a/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml +++ b/packages/moxxmpp_socket_tcp/pubspec_overrides.yaml @@ -1,4 +1,4 @@ # melos_managed_dependency_overrides: moxxmpp dependency_overrides: moxxmpp: - path: ../moxxmpp + path: ../moxxmpp \ No newline at end of file