diff --git a/analysis_options.yaml b/analysis_options.yaml index f434b72..6b2506f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -12,3 +12,4 @@ analyzer: - "**/*.g.dart" - "**/*.freezed.dart" - "test/" + - "integration_test/" diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index b54326f..e8dc77e 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -796,6 +796,15 @@ class XmppConnection { _updateRoutingState(RoutingState.handleStanzas); await _onNegotiationsDone(); + } else if (_currentNegotiator!.state == NegotiatorState.error) { + _log.severe('Negotiator returned an error'); + + _updateRoutingState(RoutingState.error); + await _setConnectionState(XmppConnectionState.error); + _connectionCompleter?.complete(const XmppConnectionResult(false)); + _connectionCompleter = null; + + _closeSocket(); } } diff --git a/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart b/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart new file mode 100644 index 0000000..0417b7a --- /dev/null +++ b/packages/moxxmpp_socket_tcp/integration_test/badxmpp_certificate_test.dart @@ -0,0 +1,64 @@ +import 'package:logging/logging.dart'; +import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart'; +import 'package:test/test.dart'; + +Future _runTest(String domain) async { + var gotTLSException = false; + final socket = TCPSocketWrapper(false); + final log = Logger('TestLogger'); + socket.getEventStream().listen((event) { + if (event is XmppSocketTLSFailedEvent) { + log.info('Got XmppSocketTLSFailedEvent from socket'); + gotTLSException = true; + } + }); + + final connection = XmppConnection( + ExponentialBackoffReconnectionPolicy(), + socket, + ); + connection.registerFeatureNegotiators([ + StartTlsNegotiator(), + ]); + connection.registerManagers([ + DiscoManager(), + RosterManager(), + PingManager(), + MessageManager(), + PresenceManager('http://moxxmpp.example'), + ]); + + connection.setConnectionSettings( + ConnectionSettings( + jid: JID.fromString('testuser@$domain'), + password: 'abc123', + useDirectTLS: true, + allowPlainAuth: true, + ), + ); + + final result = await connection.connectAwaitable(); + expect(result.success, false); + expect(gotTLSException, true); +} + +void main() { + Logger.root.level = Level.ALL; + Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); + + for (final domain in [ + 'self-signed.badxmpp.eu', + 'expired.badxmpp.eu', + 'wrong-name.badxmpp.eu', + 'missing-chain.badxmpp.eu', + // TODO(Unknown): Technically, this one should not fail + //'ecdsa.badxmpp.eu', + ]) { + test('$domain with connectAwaitable', () async { + await _runTest(domain); + }); + } +} diff --git a/packages/moxxmpp_socket_tcp/lib/moxxmpp_socket_tcp.dart b/packages/moxxmpp_socket_tcp/lib/moxxmpp_socket_tcp.dart index 94c8361..5be4d12 100644 --- a/packages/moxxmpp_socket_tcp/lib/moxxmpp_socket_tcp.dart +++ b/packages/moxxmpp_socket_tcp/lib/moxxmpp_socket_tcp.dart @@ -1,4 +1,5 @@ library moxxmpp_socket_tcp; +export 'src/events.dart'; export 'src/record.dart'; export 'src/socket.dart'; diff --git a/packages/moxxmpp_socket_tcp/lib/src/events.dart b/packages/moxxmpp_socket_tcp/lib/src/events.dart new file mode 100644 index 0000000..0408845 --- /dev/null +++ b/packages/moxxmpp_socket_tcp/lib/src/events.dart @@ -0,0 +1,4 @@ +import 'package:moxxmpp/moxxmpp.dart'; + +/// Triggered when TLS errors occur +class XmppSocketTLSFailedEvent extends XmppSocketEvent {} diff --git a/packages/moxxmpp_socket_tcp/lib/src/socket.dart b/packages/moxxmpp_socket_tcp/lib/src/socket.dart index 4113eeb..7c6b17f 100644 --- a/packages/moxxmpp_socket_tcp/lib/src/socket.dart +++ b/packages/moxxmpp_socket_tcp/lib/src/socket.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:moxxmpp/moxxmpp.dart'; +import 'package:moxxmpp_socket_tcp/src/events.dart'; import 'package:moxxmpp_socket_tcp/src/record.dart'; import 'package:moxxmpp_socket_tcp/src/rfc_2782.dart'; @@ -66,6 +67,7 @@ class TCPSocketWrapper extends BaseSocketWrapper { return false; } + var failedDueToTLS = false; results.sort(srvRecordSortComparator); for (final srv in results) { try { @@ -91,12 +93,20 @@ class TCPSocketWrapper extends BaseSocketWrapper { _secure = true; _log.finest('Success!'); return true; - } on SocketException catch(e) { + } on Exception catch(e) { _log.finest('Failure! $e'); _ignoreSocketClosure = false; + + if (e is HandshakeException) { + failedDueToTLS = true; + } } } + if (failedDueToTLS) { + _eventStream.add(XmppSocketTLSFailedEvent()); + } + return false; } @@ -118,7 +128,7 @@ class TCPSocketWrapper extends BaseSocketWrapper { _ignoreSocketClosure = false; _log.finest('Success!'); return true; - } on SocketException catch(e) { + } on Exception catch(e) { _log.finest('Failure! $e'); _ignoreSocketClosure = false; continue; @@ -142,7 +152,7 @@ class TCPSocketWrapper extends BaseSocketWrapper { ); _log.finest('Success!'); return true; - } on SocketException catch(e) { + } on Exception catch(e) { _log.finest('Failure! $e'); _ignoreSocketClosure = false; return false; @@ -183,8 +193,14 @@ class TCPSocketWrapper extends BaseSocketWrapper { _ignoreSocketClosure = false; _setupStreams(); return true; - } on SocketException { + } on Exception catch (e) { + _log.severe('Failed to secure socket: $e'); _ignoreSocketClosure = false; + + if (e is HandshakeException) { + _eventStream.add(XmppSocketTLSFailedEvent()); + } + return false; } } @@ -293,7 +309,7 @@ class TCPSocketWrapper extends BaseSocketWrapper { try { _socket!.write(data); - } on SocketException catch (e) { + } on Exception catch (e) { _log.severe(e); _eventStream.add(XmppSocketErrorEvent(e)); }