moxxy/test/xmpp_test.dart

573 lines
20 KiB
Dart
Raw Normal View History

2021-12-30 18:49:01 +00:00
import "dart:async";
import "package:moxxyv2/xmpp/connection.dart";
import "package:moxxyv2/xmpp/settings.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/jid.dart";
2022-01-25 14:50:25 +00:00
import "package:moxxyv2/xmpp/stanza.dart";
2022-01-21 08:34:38 +00:00
import "package:moxxyv2/xmpp/presence.dart";
import "package:moxxyv2/xmpp/roster.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/ping.dart";
import "package:moxxyv2/xmpp/reconnect.dart";
import "package:moxxyv2/xmpp/managers/attributes.dart";
2022-03-11 20:39:42 +00:00
import "package:moxxyv2/xmpp/managers/data.dart";
2022-07-16 11:09:35 +00:00
import "package:moxxyv2/xmpp/negotiators/resource_binding.dart";
import "package:moxxyv2/xmpp/negotiators/sasl/plain.dart";
import "package:moxxyv2/xmpp/negotiators/sasl/scram.dart";
2022-02-17 11:41:45 +00:00
import "package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart";
2022-03-11 20:39:42 +00:00
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
2021-12-30 18:49:01 +00:00
2022-07-15 19:48:32 +00:00
import "helpers/logging.dart";
2022-01-16 12:33:52 +00:00
import "helpers/xmpp.dart";
2021-12-30 18:49:01 +00:00
import "package:test/test.dart";
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 {
bool eventTriggered = false;
final roster = RosterManager();
roster.register(XmppManagerAttributes(
2022-04-10 20:57:04 +00:00
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",
useDirectTLS: true,
allowPlainAuth: false,
),
getManagerById: (_) => null,
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()),
// TODO
getNegotiatorById: (id) => null,
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();
2022-01-21 08:34:38 +00:00
test("Test a successful login attempt with no SM", () async {
2022-01-16 12:33:52 +00:00
final fakeSocket = StubTCPSocket(
play: [
Expectation(
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"to": "test.server",
"xml:lang": "en"
},
closeTag: false
),
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"from": "test.server",
"xml:lang": "en"
},
closeTag: false,
children: [
XMLNode.xmlns(
tag: "stream:features",
xmlns: "http://etherx.jabber.org/streams",
children: [
XMLNode.xmlns(
tag: "mechanisms",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
children: [
XMLNode(tag: "mechanism", text: "PLAIN")
]
)
]
)
]
)
),
2022-01-21 08:34:38 +00:00
Expectation(XMLNode.xmlns(
2022-01-16 12:33:52 +00:00
tag: "auth",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
attributes: {
"mechanism": "PLAIN"
},
text: "AHBvbHlub21kaXZpc2lvbgBhYWFh"
),
XMLNode.xmlns(
tag: "success",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl"
)
),
Expectation(
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"to": "test.server",
"xml:lang": "en"
},
closeTag: false
),
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"from": "test.server",
"xml:lang": "en"
},
closeTag: false,
children: [
XMLNode.xmlns(
tag: "stream:features",
xmlns: "http://etherx.jabber.org/streams",
children: [
XMLNode.xmlns(
tag: "bind",
xmlns: "urn:ietf:params:xml:ns:xmpp-bind",
children: [
XMLNode(tag: "required")
]
),
XMLNode.xmlns(
tag: "session",
xmlns: "urn:ietf:params:xml:ns:xmpp-session",
children: [
XMLNode(tag: "optional")
]
),
XMLNode.xmlns(
tag: "csi",
xmlns: "urn:xmpp:csi:0",
)
]
)
]
),
),
Expectation(
XMLNode.xmlns(
tag: "iq",
xmlns: "jabber:client",
attributes: { "type": "set", "id": "a" },
children: [
XMLNode.xmlns(
tag: "bind",
xmlns: "urn:ietf:params:xml:ns:xmpp-bind"
)
]
),
XMLNode.xmlns(
tag: "iq",
xmlns: "jabber:client",
attributes: { "type": "result" },
2022-01-16 12:33:52 +00:00
children: [
XMLNode.xmlns(
tag: "bind",
xmlns: "urn:ietf:params:xml:ns:xmpp-bind",
children: [
XMLNode(
tag: "jid",
text: "polynomdivision@test.server/MU29eEZn"
)
]
2022-01-16 12:33:52 +00:00
)
]
)
),
Expectation(
XMLNode.xmlns(
tag: "presence",
xmlns: "jabber:client",
attributes: { "from": "polynomdivision@test.server/MU29eEZn" },
children: [
XMLNode(
tag: "show",
2022-01-21 08:34:38 +00:00
text: "chat"
),
XMLNode.xmlns(
tag: "c",
xmlns: "http://jabber.org/protocol/caps",
attributes: {
// TODO: Somehow make the test ignore this attribute
"ver": "QRTBC5cg/oYd+UOTYazSQR4zb/I=",
2022-01-21 08:34:38 +00:00
"node": "http://moxxy.im",
"hash": "sha-1"
}
2022-01-16 12:33:52 +00:00
)
]
),
XMLNode(
tag: "presence",
)
),
]
);
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(
2022-02-22 20:21:31 +00:00
jid: JID.fromString("polynomdivision@test.server"),
2022-01-16 12:33:52 +00:00
password: "aaaa",
useDirectTLS: true,
2022-01-20 12:18:34 +00:00
allowPlainAuth: true
2022-01-16 12:33:52 +00:00
));
2022-07-16 16:42:45 +00:00
conn.registerManagers([
PresenceManager(),
RosterManager(),
DiscoManager(),
DiscoCacheManager(),
PingManager(),
]);
2022-07-15 18:39:10 +00:00
conn.registerFeatureNegotiators(
[
SaslPlainNegotiator(),
2022-07-16 11:09:35 +00:00
SaslScramNegotiator(10, "", "", ScramHashType.sha512),
ResourceBindingNegotiator(),
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-01-21 08:34:38 +00:00
expect(fakeSocket.getState(), 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: [
Expectation(
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"to": "test.server",
"xml:lang": "en"
},
closeTag: false
),
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"from": "test.server",
"xml:lang": "en"
},
closeTag: false,
children: [
XMLNode.xmlns(
tag: "stream:features",
xmlns: "http://etherx.jabber.org/streams",
children: [
XMLNode.xmlns(
tag: "mechanisms",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
children: [
XMLNode(tag: "mechanism", text: "PLAIN")
]
)
]
)
]
)
),
Expectation(XMLNode.xmlns(
tag: "auth",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
attributes: {
"mechanism": "PLAIN"
},
text: "AHBvbHlub21kaXZpc2lvbgBhYWFh"
),
XMLNode.xmlns(
tag: "failure",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
children: [
XMLNode(tag: "not-authorized")
]
)
)
]
);
bool receivedEvent = false;
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
2022-07-16 16:42:45 +00:00
jid: JID.fromString("polynomdivision@test.server"),
password: "aaaa",
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) {
2022-07-16 16:42:45 +00:00
if (event is AuthenticationFailedEvent && event.saslError == "not-authorized") {
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: [
Expectation(
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"to": "test.server",
"xml:lang": "en"
},
closeTag: false
),
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"from": "test.server",
"xml:lang": "en"
},
closeTag: false,
children: [
XMLNode.xmlns(
tag: "stream:features",
xmlns: "http://etherx.jabber.org/streams",
children: [
XMLNode.xmlns(
tag: "mechanisms",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
children: [
XMLNode(tag: "mechanism", text: "PLAIN")
]
)
]
)
]
)
),
Expectation(XMLNode.xmlns(
tag: "auth",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
attributes: {
"mechanism": "PLAIN"
},
text: "AHBvbHlub21kaXZpc2lvbgBhYWFh"
),
XMLNode.xmlns(
tag: "failure",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
children: [
XMLNode(tag: "mechanism-too-weak")
]
)
2021-12-30 18:49:01 +00:00
)
]
2021-12-30 18:49:01 +00:00
);
bool receivedEvent = false;
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
2022-02-22 20:21:31 +00:00
jid: JID.fromString("polynomdivision@test.server"),
password: "aaaa",
useDirectTLS: true,
allowPlainAuth: true
));
2022-07-16 16:42:45 +00:00
conn.registerManagers([
PresenceManager(),
RosterManager(),
DiscoManager(),
PingManager(),
]);
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
});
test("Test choosing SCRAM-SHA-1", () async {
final fakeSocket = StubTCPSocket(
play: [
Expectation(
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"to": "test.server",
"xml:lang": "en"
},
closeTag: false
),
XMLNode(
tag: "stream:stream",
attributes: {
"xmlns": "jabber:client",
"version": "1.0",
"xmlns:stream": "http://etherx.jabber.org/streams",
"from": "test.server",
"xml:lang": "en"
},
closeTag: false,
children: [
XMLNode.xmlns(
tag: "stream:features",
xmlns: "http://etherx.jabber.org/streams",
children: [
XMLNode.xmlns(
tag: "mechanisms",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
children: [
XMLNode(tag: "mechanism", text: "PLAIN"),
XMLNode(tag: "mechanism", text: "SCRAM-SHA-1")
]
)
]
)
]
)
),
Expectation(XMLNode.xmlns(
tag: "auth",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
attributes: {
"mechanism": "SCRAM-SHA-1"
},
text: "..."
),
XMLNode.xmlns(
tag: "challenge",
xmlns: "urn:ietf:params:xml:ns:xmpp-sasl",
attributes: {
"mechanism": "SCRAM-SHA-1"
},
text: "cj02ZDQ0MmI1ZDllNTFhNzQwZjM2OWUzZGNlY2YzMTc4ZWMxMmIzOTg1YmJkNGE4ZTZmODE0YjQyMmFiNzY2NTczLHM9UVNYQ1IrUTZzZWs4YmY5MixpPTQwOTY="
),
justCheckAttributes: {
"mechanism": "SCRAM-SHA-1"
}
)
]
);
final XmppConnection conn = XmppConnection(TestingReconnectionPolicy(), socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
2022-02-22 20:21:31 +00:00
jid: JID.fromString("polynomdivision@test.server"),
password: "aaaa",
useDirectTLS: true,
allowPlainAuth: false
));
2022-07-16 16:42:45 +00:00
conn.registerManagers([
PresenceManager(),
RosterManager(),
DiscoManager(),
PingManager(),
]);
conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
SaslScramNegotiator(10, "", "", ScramHashType.sha1),
]);
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);
});
});
group("Test roster pushes", () {
test("Test for a CVE-2015-8688 style vulnerability", () async {
bool eventTriggered = false;
final roster = RosterManager();
roster.register(XmppManagerAttributes(
2022-04-10 20:57:04 +00:00
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(
2022-02-22 20:21:31 +00:00
jid: JID.fromString("some.user@example.server"),
password: "password",
useDirectTLS: true,
allowPlainAuth: false,
),
getManagerById: (_) => null,
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()),
getNegotiatorById: (_) => null,
));
// 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");
});
2022-02-22 20:21:31 +00:00
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");
});
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");
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");
});
});
2021-12-30 18:49:01 +00:00
}