moxxy/test/xmpp_test.dart

496 lines
16 KiB
Dart

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";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/presence.dart";
import "package:moxxyv2/xmpp/roster.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/managers/attributes.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030.dart";
import "helpers/xmpp.dart";
import "package:test/test.dart";
void main() {
test("Test a successful login attempt with no SM", () 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: "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" },
children: [
XMLNode.xmlns(
tag: "bind",
xmlns: "urn:ietf:params:xml:ns:xmpp-bind",
children: [
XMLNode(
tag: "jid",
text: "polynomdivision@test.server/MU29eEZn"
)
]
)
]
)
),
Expectation(
XMLNode.xmlns(
tag: "presence",
xmlns: "jabber:client",
attributes: { "from": "polynomdivision@test.server/MU29eEZn" },
children: [
XMLNode(
tag: "show",
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=",
"node": "http://moxxy.im",
"hash": "sha-1"
}
)
]
),
XMLNode(
tag: "presence",
)
),
]
);
final XmppConnection conn = XmppConnection(socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: BareJID.fromString("polynomdivision@test.server"),
password: "aaaa",
useDirectTLS: true,
allowPlainAuth: true
));
conn.registerManager(RosterManager());
conn.registerManager(DiscoManager());
conn.registerManager(PresenceManager());
await conn.connect();
await Future.delayed(const Duration(seconds: 3), () {
expect(fakeSocket.getState(), 5);
});
});
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(socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: BareJID.fromString("polynomdivision@test.server"),
password: "aaaa",
useDirectTLS: true,
allowPlainAuth: true
));
conn.registerManager(PresenceManager());
conn.registerManager(RosterManager());
conn.registerManager(DiscoManager());
conn.asBroadcastStream().listen((event) {
if (event is AuthenticationFailedEvent && event.saslError == "not-authorized") {
receivedEvent = true;
}
});
await conn.connect();
await Future.delayed(const Duration(seconds: 3), () {
expect(receivedEvent, true);
});
});
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")
]
)
)
]
);
bool receivedEvent = false;
final XmppConnection conn = XmppConnection(socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: BareJID.fromString("polynomdivision@test.server"),
password: "aaaa",
useDirectTLS: true,
allowPlainAuth: true
));
conn.registerManager(PresenceManager());
conn.registerManager(RosterManager());
conn.registerManager(DiscoManager());
conn.asBroadcastStream().listen((event) {
if (event is AuthenticationFailedEvent && event.saslError == "mechanism-too-weak") {
receivedEvent = true;
}
});
await conn.connect();
await Future.delayed(const Duration(seconds: 3), () {
expect(receivedEvent, true);
});
});
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(socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: BareJID.fromString("polynomdivision@test.server"),
password: "aaaa",
useDirectTLS: true,
allowPlainAuth: false
));
conn.registerManager(RosterManager());
conn.registerManager(DiscoManager());
conn.registerManager(PresenceManager());
await conn.connect();
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(
// ignore: avoid_print
log: (str) => print(str),
sendStanza: (_, { bool addFrom = true, bool addId = true}) async => XMLNode(tag: "hallo"),
sendEvent: (event) {
eventTriggered = true;
},
sendNonza: (_) {},
sendRawXml: (_) {},
getConnectionSettings: () => ConnectionSettings(
jid: BareJID.fromString("some.user@example.server"),
password: "password",
useDirectTLS: true,
allowPlainAuth: false,
),
getManagerById: (_) => null,
isStreamFeatureSupported: (_) => false,
getFullJID: () => FullJID.fromString("some.user@example.server/aaaaa")
));
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
final maliciousStanza = Stanza.fromXMLNode(XMLNode.fromString("<iq type=\"set\" to=\"some.user@example.server/aaaaa\"><query xmlns='jabber:iq:roster'><item subscription=\"both\" jid=\"eve@siacs.eu\" name=\"Bob\" /></query></iq>"));
await Future.forEach(
roster.getStanzaHandlers(),
(StanzaHandler handler) async {
if (handler.matches(maliciousStanza)) {
await handler.callback(maliciousStanza);
}
}
);
expect(eventTriggered, false, reason: "Was able to inject a malicious roster push");
});
});
test("Test bare JIDs", () {
expect(BareJID.fromString("hallo@welt").toString(), "hallo@welt");
expect(BareJID.fromString("@welt").toString(), "@welt");
expect(BareJID.fromString("hallo@").toString(), "hallo@");
expect(BareJID.fromString("hallo@welt/whatever").toString(), "hallo@welt");
});
}