moxxy/test/xmpp_test.dart

376 lines
15 KiB
Dart
Raw Normal View History

import 'dart:async';
2021-12-30 18:49:01 +00:00
2022-07-17 19:50:48 +00:00
import 'package:moxxyv2/xmpp/connection.dart';
import 'package:moxxyv2/xmpp/events.dart';
import 'package:moxxyv2/xmpp/jid.dart';
import 'package:moxxyv2/xmpp/managers/attributes.dart';
import 'package:moxxyv2/xmpp/managers/data.dart';
import 'package:moxxyv2/xmpp/negotiators/resource_binding.dart';
import 'package:moxxyv2/xmpp/negotiators/sasl/plain.dart';
import 'package:moxxyv2/xmpp/negotiators/sasl/scram.dart';
import 'package:moxxyv2/xmpp/ping.dart';
import 'package:moxxyv2/xmpp/presence.dart';
import 'package:moxxyv2/xmpp/reconnect.dart';
import 'package:moxxyv2/xmpp/roster.dart';
import 'package:moxxyv2/xmpp/settings.dart';
import 'package:moxxyv2/xmpp/stanza.dart';
import 'package:moxxyv2/xmpp/stringxml.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0198/negotiator.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0198/xep_0198.dart';
import 'package:test/test.dart';
2021-12-30 18:49:01 +00:00
import 'helpers/logging.dart';
import 'helpers/xmpp.dart';
2021-12-30 18:49:01 +00:00
2022-02-22 20:21:31 +00:00
/// Returns true if the roster manager triggeres an event for a given stanza
Future<bool> testRosterManager(String bareJid, String resource, String stanzaString) async {
var eventTriggered = false;
2022-02-22 20:21:31 +00:00
final roster = RosterManager();
roster.register(XmppManagerAttributes(
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true }) async => XMLNode(tag: 'hallo'),
2022-02-22 20:21:31 +00:00
sendEvent: (event) {
eventTriggered = true;
},
sendNonza: (_) {},
sendRawXml: (_) {},
getConnectionSettings: () => ConnectionSettings(
jid: JID.fromString(bareJid),
password: 'password',
2022-02-22 20:21:31 +00:00
useDirectTLS: true,
allowPlainAuth: false,
),
2022-07-19 18:47:19 +00:00
getManagerById: getManagerNullStub,
getNegotiatorById: getNegotiatorNullStub,
2022-03-01 19:26:55 +00:00
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('$bareJid/$resource'),
getSocket: () => StubTCPSocket(play: []),
2022-07-16 10:43:21 +00:00
getConnection: () => XmppConnection(TestingReconnectionPolicy()),
),);
2022-02-22 20:21:31 +00:00
final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
2022-03-11 20:39:42 +00:00
for (final handler in roster.getIncomingStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, stanza));
}
2022-02-22 20:21:31 +00:00
return eventTriggered;
}
2021-12-30 18:49:01 +00:00
void main() {
2022-07-15 19:48:32 +00:00
initLogger();
test('Test a successful login attempt with no SM', () async {
2022-01-16 12:33:52 +00:00
final fakeSocket = StubTCPSocket(
play: [
2022-07-19 18:47:19 +00:00
StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
'''
<stream:stream
xmlns="jabber:client"
version="1.0"
xmlns:stream="http://etherx.jabber.org/streams"
from="test.server"
xml:lang="en">
<stream:features xmlns="http://etherx.jabber.org/streams">
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>''',
2022-01-16 12:33:52 +00:00
),
2022-07-19 18:47:19 +00:00
StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />'
2022-01-16 12:33:52 +00:00
),
2022-07-19 18:47:19 +00:00
StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
'''
<stream:stream
xmlns="jabber:client"
version="1.0"
xmlns:stream="http://etherx.jabber.org/streams"
from="test.server"
xml:lang="en">
<stream:features xmlns="http://etherx.jabber.org/streams">
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
<required/>
</bind>
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
<optional/>
</session>
<csi xmlns="urn:xmpp:csi:0"/>
<sm xmlns="urn:xmpp:sm:3"/>
</stream:features>
''',
2022-01-16 12:33:52 +00:00
),
2022-07-19 18:47:19 +00:00
StanzaExpectation(
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
ignoreId: true,
2022-01-16 12:33:52 +00:00
),
2022-07-19 18:47:19 +00:00
/*
2022-01-16 12:33:52 +00:00
Expectation(
XMLNode.xmlns(
tag: 'presence',
xmlns: 'jabber:client',
attributes: { 'from': 'polynomdivision@test.server/MU29eEZn' },
2022-01-16 12:33:52 +00:00
children: [
XMLNode(
tag: 'show',
text: 'chat',
2022-01-21 08:34:38 +00:00
),
XMLNode.xmlns(
tag: 'c',
xmlns: 'http://jabber.org/protocol/caps',
2022-01-21 08:34:38 +00:00
attributes: {
// TODO: Somehow make the test ignore this attribute
'ver': 'QRTBC5cg/oYd+UOTYazSQR4zb/I=',
'node': 'http://moxxy.im',
'hash': 'sha-1'
},
2022-01-16 12:33:52 +00:00
)
],
2022-01-16 12:33:52 +00:00
),
XMLNode(
tag: 'presence',
),
2022-01-16 12:33:52 +00:00
),
2022-07-19 18:47:19 +00:00
*/
],
2022-01-16 12:33:52 +00:00
);
2022-03-11 20:39:42 +00:00
// TODO: This test is broken since we query the server and enable carbons
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
2022-01-16 12:33:52 +00:00
conn.setConnectionSettings(ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa',
2022-01-16 12:33:52 +00:00
useDirectTLS: true,
allowPlainAuth: true,
),);
2022-07-16 16:42:45 +00:00
conn.registerManagers([
2022-07-28 20:49:54 +00:00
PresenceManager(),
RosterManager(),
DiscoManager(),
PingManager(),
StreamManagementManager(),
2022-07-16 16:42:45 +00:00
]);
2022-07-15 18:39:10 +00:00
conn.registerFeatureNegotiators(
[
SaslPlainNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
2022-07-16 11:09:35 +00:00
ResourceBindingNegotiator(),
StreamManagementNegotiator(),
2022-07-15 18:39:10 +00:00
]
);
2022-03-08 20:50:21 +00:00
await conn.connect();
2022-01-21 16:44:48 +00:00
await Future.delayed(const Duration(seconds: 3), () {
2022-07-28 20:49:54 +00:00
expect(fakeSocket.getState(), /*6*/ 5);
2022-01-16 12:33:52 +00:00
});
2021-12-30 18:49:01 +00:00
});
test('Test a failed SASL auth', () async {
final fakeSocket = StubTCPSocket(
play: [
2022-07-19 18:47:19 +00:00
StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
'''
<stream:stream
xmlns="jabber:client"
version="1.0"
xmlns:stream="http://etherx.jabber.org/streams"
from="test.server"
xml:lang="en">
<stream:features xmlns="http://etherx.jabber.org/streams">
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>''',
),
StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><not-authorized /></failure>'
),
],
);
var receivedEvent = false;
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa',
2022-07-16 16:42:45 +00:00
useDirectTLS: true,
allowPlainAuth: true,
),);
2022-07-16 16:42:45 +00:00
conn.registerManagers([
PresenceManager(),
RosterManager(),
DiscoManager(),
PingManager(),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator()
]);
2021-12-30 18:49:01 +00:00
conn.asBroadcastStream().listen((event) {
if (event is AuthenticationFailedEvent && event.saslError == 'not-authorized') {
2022-07-16 16:42:45 +00:00
receivedEvent = true;
}
});
2021-12-30 18:49:01 +00:00
2022-03-08 20:50:21 +00:00
await conn.connect();
2022-01-21 16:44:48 +00:00
await Future.delayed(const Duration(seconds: 3), () {
expect(receivedEvent, true);
});
});
2021-12-30 18:49:01 +00:00
test('Test another failed SASL auth', () async {
final fakeSocket = StubTCPSocket(
play: [
2022-07-19 18:47:19 +00:00
StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
'''
<stream:stream
xmlns="jabber:client"
version="1.0"
xmlns:stream="http://etherx.jabber.org/streams"
from="test.server"
xml:lang="en">
<stream:features xmlns="http://etherx.jabber.org/streams">
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
</mechanisms>
</stream:features>''',
),
StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
'<failure xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><mechanism-too-weak /></failure>',
),
],
2021-12-30 18:49:01 +00:00
);
var receivedEvent = false;
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa',
useDirectTLS: true,
allowPlainAuth: true,
),);
2022-07-16 16:42:45 +00:00
conn.registerManagers([
2022-07-28 20:49:54 +00:00
PresenceManager(),
RosterManager(),
DiscoManager(),
PingManager(),
2022-07-16 16:42:45 +00:00
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator()
]);
conn.asBroadcastStream().listen((event) {
if (event is AuthenticationFailedEvent && event.saslError == 'mechanism-too-weak') {
receivedEvent = true;
}
});
2021-12-30 18:49:01 +00:00
2022-03-08 20:50:21 +00:00
await conn.connect();
2022-01-21 16:44:48 +00:00
await Future.delayed(const Duration(seconds: 3), () {
expect(receivedEvent, true);
});
2021-12-30 18:49:01 +00:00
});
2022-07-19 18:47:19 +00:00
/*test('Test choosing SCRAM-SHA-1', () async {
final fakeSocket = StubTCPSocket(
play: [
2022-07-19 18:47:19 +00:00
StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' xml:lang='en'>",
'''
<stream:stream
xmlns="jabber:client"
version="1.0"
xmlns:stream="http://etherx.jabber.org/streams"
from="test.server"
xml:lang="en">
<stream:features xmlns="http://etherx.jabber.org/streams">
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</mechanisms>
</stream:features>''',
),
2022-07-19 18:47:19 +00:00
// TODO(Unknown): This test is currently broken
StringExpectation(
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
"..."
)
],
);
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
2022-07-28 20:49:54 +00:00
jid: JID.fromString('polynomdivision@test.server'),
password: 'aaaa',
useDirectTLS: true,
allowPlainAuth: false,
),);
2022-07-16 16:42:45 +00:00
conn.registerManagers([
2022-07-28 20:49:54 +00:00
PresenceManager(),
RosterManager(),
DiscoManager(),
PingManager(),
2022-07-16 16:42:45 +00:00
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
2022-07-16 16:42:45 +00:00
]);
2022-03-08 20:50:21 +00:00
await conn.connect();
2022-01-21 16:44:48 +00:00
await Future.delayed(const Duration(seconds: 3), () {
expect(fakeSocket.getState(), 2);
});
2022-07-19 18:47:19 +00:00
});*/
group('Test roster pushes', () {
test('Test for a CVE-2015-8688 style vulnerability', () async {
var eventTriggered = false;
final roster = RosterManager();
roster.register(XmppManagerAttributes(
sendStanza: (_, { StanzaFromType addFrom = StanzaFromType.full, bool addId = true, bool retransmitted = false, bool awaitable = true }) async => XMLNode(tag: 'hallo'),
sendEvent: (event) {
eventTriggered = true;
},
sendNonza: (_) {},
sendRawXml: (_) {},
getConnectionSettings: () => ConnectionSettings(
jid: JID.fromString('some.user@example.server'),
password: 'password',
useDirectTLS: true,
allowPlainAuth: false,
),
2022-07-19 18:47:19 +00:00
getManagerById: getManagerNullStub,
getNegotiatorById: getNegotiatorNullStub,
2022-03-01 19:26:55 +00:00
isFeatureSupported: (_) => false,
getFullJID: () => JID.fromString('some.user@example.server/aaaaa'),
getSocket: () => StubTCPSocket(play: []),
2022-07-16 16:42:45 +00:00
getConnection: () => XmppConnection(TestingReconnectionPolicy()),
),);
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
2022-02-22 15:25:12 +00:00
// NOTE: Added a from attribute as a server would add it itself.
final maliciousStanza = Stanza.fromXMLNode(XMLNode.fromString("<iq type=\"set\" from=\"eve@siacs.eu/bbbbb\" to=\"some.user@example.server/aaaaa\"><query xmlns='jabber:iq:roster'><item subscription=\"both\" jid=\"eve@siacs.eu\" name=\"Bob\" /></query></iq>"));
2022-03-11 20:39:42 +00:00
for (final handler in roster.getIncomingStanzaHandlers()) {
if (handler.matches(maliciousStanza)) await handler.callback(maliciousStanza, StanzaHandlerData(false, maliciousStanza));
}
expect(eventTriggered, false, reason: 'Was able to inject a malicious roster push');
});
test('The manager should accept pushes from our bare jid', () async {
final result = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>");
expect(result, true, reason: 'Roster pushes from our bare JID should be accepted');
2022-02-22 20:21:31 +00:00
});
test('The manager should accept pushes from a jid that, if the resource is stripped, is our bare jid', () async {
final result1 = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example/aaaaa' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>");
expect(result1, true, reason: 'Roster pushes should be accepted if the bare JIDs are the same');
2022-02-22 20:21:31 +00:00
final result2 = await testRosterManager('test.user@server.example', 'aaaaa', "<iq from='test.user@server.example/bbbbb' type='result' id='82c2aa1e-cac3-4f62-9e1f-bbe6b057daf3' to='test.user@server.example/aaaaa' xmlns='jabber:client'><query ver='64' xmlns='jabber:iq:roster'><item jid='some.other.user@server.example' subscription='to' /></query></iq>");
expect(result2, true, reason: 'Roster pushes should be accepted if the bare JIDs are the same');
2022-02-22 20:21:31 +00:00
});
});
2021-12-30 18:49:01 +00:00
}