7 Commits

16 changed files with 199 additions and 100 deletions

78
flake.lock generated
View File

@@ -7,11 +7,11 @@
"nixpkgs": "nixpkgs" "nixpkgs": "nixpkgs"
}, },
"locked": { "locked": {
"lastModified": 1694377165, "lastModified": 1727554699,
"narHash": "sha256-NeIlZIElbkbKaNK5SZv6ULcFT/UGIICb3q7GPpkf9jk=", "narHash": "sha256-puBCNL5PW7Pej+6Srmi2YjEgNeE015NFe33hbkkLqeQ=",
"owner": "tadfisher", "owner": "tadfisher",
"repo": "android-nixpkgs", "repo": "android-nixpkgs",
"rev": "b020dc733ee69393841a50cf94d45735d5a5a57a", "rev": "bc34ef1c71fe9eafcfb1d637b431fca83d746625",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -25,15 +25,14 @@
"nixpkgs": [ "nixpkgs": [
"android-nixpkgs", "android-nixpkgs",
"nixpkgs" "nixpkgs"
], ]
"systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1693833206, "lastModified": 1722113426,
"narHash": "sha256-wHOY0nnD6gWj8u9uI85/YlsganYyWRK1hLFZulZwfmY=", "narHash": "sha256-Yo/3loq572A8Su6aY5GP56knpuKYRvM2a1meP9oJZCw=",
"owner": "numtide", "owner": "numtide",
"repo": "devshell", "repo": "devshell",
"rev": "65114ea495a8d3cc1352368bf170d67ef005aa5a", "rev": "67cce7359e4cd3c45296fb4aaf6a19e2a9c757ae",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -43,6 +42,24 @@
} }
}, },
"flake-utils": { "flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1726560853,
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": { "inputs": {
"systems": "systems_2" "systems": "systems_2"
}, },
@@ -60,31 +77,13 @@
"type": "github" "type": "github"
} }
}, },
"flake-utils_2": {
"inputs": {
"systems": "systems_3"
},
"locked": {
"lastModified": 1692799911,
"narHash": "sha256-3eihraek4qL744EvQXsK1Ha6C3CR7nnT8X2qWap4RNk=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "f9e7cf818399d17d347f847525c5a5a8032e4e44",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1694183432, "lastModified": 1727348695,
"narHash": "sha256-YyPGNapgZNNj51ylQMw9lAgvxtM2ai1HZVUu3GS8Fng=", "narHash": "sha256-J+PeFKSDV+pHL7ukkfpVzCOO7mBSrrpJ3svwBFABbhI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "db9208ab987cdeeedf78ad9b4cf3c55f5ebd269b", "rev": "1925c603f17fc89f4c8f6bf6f631a802ad85d784",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -96,11 +95,11 @@
}, },
"nixpkgs_2": { "nixpkgs_2": {
"locked": { "locked": {
"lastModified": 1694343207, "lastModified": 1727586919,
"narHash": "sha256-jWi7OwFxU5Owi4k2JmiL1sa/OuBCQtpaAesuj5LXC8w=", "narHash": "sha256-e/YXG0tO5GWHDS8QQauj8aj4HhXEm602q9swrrlTlKQ=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "78058d810644f5ed276804ce7ea9e82d92bee293", "rev": "2dcd9c55e8914017226f5948ac22c53872a13ee2",
"type": "github" "type": "github"
}, },
"original": { "original": {
@@ -146,21 +145,6 @@
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
},
"systems_3": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@@ -15,20 +15,22 @@
}; };
}; };
# Everything to make Flutter happy # Everything to make Flutter happy
sdk = android-nixpkgs.sdk.${system} (sdkPkgs: with sdkPkgs; [ android = pkgs.androidenv.composeAndroidPackages {
cmdline-tools-latest # TODO: Find a way to pin these
build-tools-30-0-3 #toolsVersion = "26.1.1";
build-tools-33-0-2 #platformToolsVersion = "31.0.3";
build-tools-34-0-0 #buildToolsVersions = [ "31.0.0" ];
platform-tools #includeEmulator = true;
emulator #emulatorVersion = "30.6.3";
patcher-v4 platformVersions = [ "28" ];
platforms-android-28 includeSources = false;
platforms-android-29 includeSystemImages = true;
platforms-android-30 systemImageTypes = [ "default" ];
platforms-android-31 abiVersions = [ "x86_64" ];
platforms-android-33 includeNDK = false;
]); useGoogleAPIs = false;
useGoogleTVAddOns = false;
};
lib = pkgs.lib; lib = pkgs.lib;
pinnedJDK = pkgs.jdk17; pinnedJDK = pkgs.jdk17;
@@ -68,7 +70,7 @@
}; };
in pkgs.mkShell { in pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
flutter pinnedJDK sdk dart # Dart flutter pinnedJDK android.platform-tools dart # Dart
gitlint # Code hygiene gitlint # Code hygiene
ripgrep # General utilities ripgrep # General utilities
@@ -100,13 +102,13 @@
CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include"; CPATH = "${pkgs.xorg.libX11.dev}/include:${pkgs.xorg.xorgproto}/include";
LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ]; LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath [ atk cairo epoxy gdk-pixbuf glib gtk3 harfbuzz pango ];
ANDROID_SDK_ROOT = "${sdk}/share/android-sdk"; ANDROID_SDK_ROOT = "${android.androidsdk}/share/android-sdk";
ANDROID_HOME = "${sdk}/share/android-sdk"; ANDROID_HOME = "${android.androidsdk}/share/android-sdk";
JAVA_HOME = pinnedJDK; JAVA_HOME = pinnedJDK;
# Fix an issue with Flutter using an older version of aapt2, which does not know # Fix an issue with Flutter using an older version of aapt2, which does not know
# an used parameter. # an used parameter.
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${sdk}/share/android-sdk/build-tools/34.0.0/aapt2"; GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${android.androidsdk}/share/android-sdk/build-tools/34.0.0/aapt2";
}; };
apps = { apps = {

View File

@@ -1,2 +1,4 @@
set -ex
prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123 prosodyctl --config ./prosody.cfg.lua register testuser1 localhost abc123
prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123 prosodyctl --config ./prosody.cfg.lua register testuser2 localhost abc123

View File

@@ -3,14 +3,16 @@ description: A sample command-line application.
version: 1.0.0 version: 1.0.0
environment: environment:
sdk: '>=2.18.0 <3.0.0' sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
logging: ^1.0.2 logging: ^1.3.0
moxxmpp: 0.3.0 moxxmpp:
moxxmpp_socket_tcp: 0.3.0 path: ../packages/moxxmpp
moxxmpp_socket_tcp:
path: ../packages/moxxmpp_socket_tcp
dev_dependencies: dev_dependencies:
lints: ^2.0.0 build_runner: ^2.4.13
test: ^1.16.0 test: ^1.25.8
very_good_analysis: ^3.0.1 very_good_analysis: ^6.0.0

View File

@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
class TestingTCPSocketWrapper extends TCPSocketWrapper { class TestingTCPSocketWrapper extends TCPSocketWrapper {
TestingTCPSocketWrapper() : super(true);
@override @override
bool onBadCertificate(dynamic certificate, String domain) { bool onBadCertificate(dynamic certificate, String domain) {
return true; return true;

View File

@@ -4,6 +4,8 @@ import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
class TestingTCPSocketWrapper extends TCPSocketWrapper { class TestingTCPSocketWrapper extends TCPSocketWrapper {
TestingTCPSocketWrapper() : super(true);
@override @override
bool onBadCertificate(dynamic certificate, String domain) { bool onBadCertificate(dynamic certificate, String domain) {
return true; return true;
@@ -27,7 +29,7 @@ void main() {
ClientToServerNegotiator(), ClientToServerNegotiator(),
TestingTCPSocketWrapper(), TestingTCPSocketWrapper(),
)..connectionSettings = ConnectionSettings( )..connectionSettings = ConnectionSettings(
jid: JID.fromString('testuser@localhost'), jid: JID.fromString('testuser1@localhost'),
password: 'abc123', password: 'abc123',
host: '127.0.0.1', host: '127.0.0.1',
port: 5222, port: 5222,
@@ -40,6 +42,8 @@ void main() {
]); ]);
await conn.registerFeatureNegotiators([ await conn.registerFeatureNegotiators([
SaslPlainNegotiator(), SaslPlainNegotiator(),
SaslScramNegotiator(9, '', '', ScramHashType.sha1),
SaslScramNegotiator(10, '', '', ScramHashType.sha256),
ResourceBindingNegotiator(), ResourceBindingNegotiator(),
FASTSaslNegotiator(), FASTSaslNegotiator(),
Bind2Negotiator(), Bind2Negotiator(),

View File

@@ -1,3 +1,6 @@
## 0.4.1
- Moved FAST from staging to xep_0484.dart
## 0.4.0 ## 0.4.0
- **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue. - **BREAKING**: Remove `lastResource` from `XmppConnection`'s `connect` method. Instead, set the `StreamManagementNegotiator`'s `resource` attribute instead. Since the resource can only really be restored by stream management, this is no issue.

View File

@@ -39,7 +39,6 @@ export 'package:moxxmpp/src/socket.dart';
export 'package:moxxmpp/src/stanza.dart'; export 'package:moxxmpp/src/stanza.dart';
export 'package:moxxmpp/src/stringxml.dart'; export 'package:moxxmpp/src/stringxml.dart';
export 'package:moxxmpp/src/util/typed_map.dart'; export 'package:moxxmpp/src/util/typed_map.dart';
export 'package:moxxmpp/src/xeps/staging/fast.dart';
export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart'; export 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
export 'package:moxxmpp/src/xeps/xep_0004.dart'; export 'package:moxxmpp/src/xeps/xep_0004.dart';
export 'package:moxxmpp/src/xeps/xep_0030/errors.dart'; export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
@@ -96,3 +95,4 @@ export 'package:moxxmpp/src/xeps/xep_0447.dart';
export 'package:moxxmpp/src/xeps/xep_0448.dart'; export 'package:moxxmpp/src/xeps/xep_0448.dart';
export 'package:moxxmpp/src/xeps/xep_0449.dart'; export 'package:moxxmpp/src/xeps/xep_0449.dart';
export 'package:moxxmpp/src/xeps/xep_0461.dart'; export 'package:moxxmpp/src/xeps/xep_0461.dart';
export 'package:moxxmpp/src/xeps/xep_0484.dart';

View File

@@ -96,7 +96,9 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
@override @override
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async { Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
state = NegotiatorState.done; if (pickedForSasl2) {
state = NegotiatorState.done;
}
return const Result(true); return const Result(true);
} }

View File

@@ -246,6 +246,9 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
bool _checkSignature(String base64Signature) { bool _checkSignature(String base64Signature) {
final signature = final signature =
parseKeyValue(utf8.decode(base64.decode(base64Signature))); parseKeyValue(utf8.decode(base64.decode(base64Signature)));
_log.finest(
'Expecting signature: "$_serverSignature", got: "${signature["v"]}"',
);
return signature['v']! == _serverSignature; return signature['v']! == _serverSignature;
} }
@@ -360,6 +363,11 @@ class SaslScramNegotiator extends Sasl2AuthenticationNegotiator {
@override @override
Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async { Future<Result<bool, NegotiatorError>> onSasl2Success(XMLNode response) async {
// Don't do anything if we have not been picked for SASL2.
if (!pickedForSasl2) {
return const Result(true);
}
// When we're done with SASL2, check the additional data to verify the server // When we're done with SASL2, check the additional data to verify the server
// signature. // signature.
state = NegotiatorState.done; state = NegotiatorState.done;

View File

@@ -4,6 +4,10 @@ class UnknownOmemoError extends OmemoError {}
class InvalidAffixElementsException implements Exception {} class InvalidAffixElementsException implements Exception {}
/// Internal exception that is returned when the device list cannot be
/// fetched because the returned list is empty.
class EmptyDeviceListException implements OmemoError {}
class OmemoNotSupportedForContactException extends OmemoError {} class OmemoNotSupportedForContactException extends OmemoError {}
class EncryptionFailedException implements Exception {} class EncryptionFailedException implements Exception {}

View File

@@ -535,7 +535,10 @@ class OmemoManager extends XmppManagerBase {
final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!; final pm = getAttributes().getManagerById<PubSubManager>(pubsubManager)!;
final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns); final result = await pm.getItems(jid.toBare(), omemoDevicesXmlns);
if (result.isType<PubSubError>()) return Result(UnknownOmemoError()); if (result.isType<PubSubError>()) return Result(UnknownOmemoError());
return Result(result.get<List<PubSubItem>>().first.payload);
final itemList = result.get<List<PubSubItem>>();
if (itemList.isEmpty) return Result(EmptyDeviceListException());
return Result(itemList.first.payload);
} }
/// Retrieves the OMEMO device list from [jid]. /// Retrieves the OMEMO device list from [jid].

View File

@@ -274,16 +274,16 @@
<xmpp:version>0.2.0</xmpp:version> <xmpp:version>0.2.0</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<!-- Non-Standard (Proto) XEPs -->
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/inbox/xep-fast.html" /> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0484.html" />
<xmpp:status>partial</xmpp:status> <xmpp:status>partial</xmpp:status>
<xmpp:version>0.0.1</xmpp:version> <xmpp:version>0.1.1</xmpp:version>
<xmpp:note xml:lang="en">Invalidation is never requested</xmpp:note> <xmpp:note xml:lang="en">Invalidation is never requested</xmpp:note>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<!-- Non-Standard (Proto) XEPs -->
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md"/> <xmpp:xep rdf:resource="https://codeberg.org/moxxy/custom-xeps/src/branch/master/xep-xxxx-file-upload-notification.md"/>

View File

@@ -5,28 +5,28 @@ homepage: https://codeberg.org/moxxy/moxxmpp
publish_to: https://git.polynom.me/api/packages/Moxxy/pub publish_to: https://git.polynom.me/api/packages/Moxxy/pub
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: ">=3.0.0 <4.0.0"
dependencies: dependencies:
collection: ^1.16.0 collection: ^1.18.0
cryptography: ^2.0.5 cryptography: ^2.7.0
hex: ^0.2.0 hex: ^0.2.0
json_serializable: ^6.3.1 json_serializable: ^6.8.0
logging: ^1.0.2 logging: ^1.2.0
meta: ^1.7.0 meta: ^1.15.0
moxlib: moxlib:
hosted: https://git.polynom.me/api/packages/Moxxy/pub hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: ^0.2.0 version: ^0.2.0
omemo_dart: omemo_dart:
hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub hosted: https://git.polynom.me/api/packages/PapaTutuWawa/pub
version: ^0.5.1 version: ^0.6.0
random_string: ^2.3.1 random_string: ^2.3.1
saslprep: ^1.0.2 saslprep: ^1.0.3
synchronized: ^3.0.0+2 synchronized: ^3.3.0+3
uuid: ^3.0.5 uuid: ^3.0.7
xml: ^6.1.0 xml: ^6.5.0
dev_dependencies: dev_dependencies:
build_runner: ^2.1.11 build_runner: ^2.4.12
test: ^1.18.0 test: ^1.25.8
very_good_analysis: ^3.0.1 very_good_analysis: ^6.0.0

View File

@@ -216,6 +216,89 @@ void main() {
expect(result.isType<XmppError>(), false); expect(result.isType<XmppError>(), false);
}); });
test('Test SCRAM-SHA-1 SASL2 negotiation with a valid signature', () async {
final fakeSocket = StubTCPSocket([
StringExpectation(
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='server' from='user@server' xml:lang='en'>",
'''
<stream:stream
xmlns="jabber:client"
version="1.0"
xmlns:stream="http://etherx.jabber.org/streams"
from="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>
<authentication xmlns='urn:xmpp:sasl:2'>
<mechanism>PLAIN</mechanism>
<mechanism>SCRAM-SHA-1</mechanism>
</authentication>
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
<required/>
</bind>
</stream:features>''',
),
StanzaExpectation(
"<authenticate xmlns='urn:xmpp:sasl:2' mechanism='SCRAM-SHA-1'><user-agent id='d4565fa7-4d72-4749-b3d3-740edbf87770'><software>moxxmpp</software><device>PapaTutuWawa's awesome device</device></user-agent><initial-response>biwsbj11c2VyLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdM</initial-response></authenticate>",
'''
<challenge xmlns='urn:xmpp:sasl:2'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>
''',
),
StanzaExpectation(
'<response xmlns="urn:xmpp:sasl:2">Yz1iaXdzLHI9ZnlrbytkMmxiYkZnT05Sdjlxa3hkYXdMM3JmY05IWUpZMVpWdldWczdqLHA9djBYOHYzQnoyVDBDSkdiSlF5RjBYK0hJNFRzPQ==</response>',
'<success xmlns="urn:xmpp:sasl:2"><additional-data>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</additional-data><authorization-identifier>user@server</authorization-identifier></success>',
),
StanzaExpectation(
"<iq xmlns='jabber:client' type='set' id='aaaa'><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>',
''',
adjustId: true,
ignoreId: true,
),
]);
final conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ClientToServerNegotiator(),
fakeSocket,
)..connectionSettings = ConnectionSettings(
jid: JID.fromString('user@server'),
password: 'pencil',
);
await conn.registerManagers([
PresenceManager(),
RosterManager(TestingRosterStateManager('', [])),
DiscoManager([]),
]);
await conn.registerFeatureNegotiators([
SaslPlainNegotiator(),
SaslScramNegotiator(
10,
'n=user,r=fyko+d2lbbFgONRv9qkxdawL',
'fyko+d2lbbFgONRv9qkxdawL',
ScramHashType.sha1,
),
ResourceBindingNegotiator(),
Sasl2Negotiator()
..userAgent = const UserAgent(
id: 'd4565fa7-4d72-4749-b3d3-740edbf87770',
software: 'moxxmpp',
device: "PapaTutuWawa's awesome device",
),
]);
final result = await conn.connect(
waitUntilLogin: true,
shouldReconnect: false,
enableReconnectOnSuccess: false,
);
expect(result.isType<NegotiatorError>(), false);
});
test('Test SCRAM-SHA-1 SASL2 negotiation with an invalid signature', test('Test SCRAM-SHA-1 SASL2 negotiation with an invalid signature',
() async { () async {
final fakeSocket = StubTCPSocket([ final fakeSocket = StubTCPSocket([