From 478b5b87708fe5ef3e1e09082f11f761c613fdc9 Mon Sep 17 00:00:00 2001 From: "Alexander \"PapaTutuWawa" Date: Fri, 31 Mar 2023 21:09:16 +0200 Subject: [PATCH] feat(core): Make SCRAM SASL2 aware --- packages/moxxmpp/lib/src/connection.dart | 3 +- .../lib/src/negotiators/sasl/plain.dart | 5 +- .../lib/src/negotiators/sasl/scram.dart | 62 +++++++++++++------ .../moxxmpp/lib/src/negotiators/sasl2.dart | 27 ++++---- 4 files changed, 61 insertions(+), 36 deletions(-) diff --git a/packages/moxxmpp/lib/src/connection.dart b/packages/moxxmpp/lib/src/connection.dart index 9ed906b..f2ff348 100644 --- a/packages/moxxmpp/lib/src/connection.dart +++ b/packages/moxxmpp/lib/src/connection.dart @@ -268,7 +268,8 @@ class XmppConnection { /// Register a list of negotiator with the connection. Future registerFeatureNegotiators( - List negotiators) async { + List negotiators, + ) async { for (final negotiator in negotiators) { _log.finest('Registering ${negotiator.id}'); negotiator.register( diff --git a/packages/moxxmpp/lib/src/negotiators/sasl/plain.dart b/packages/moxxmpp/lib/src/negotiators/sasl/plain.dart index a74d092..17a54de 100644 --- a/packages/moxxmpp/lib/src/negotiators/sasl/plain.dart +++ b/packages/moxxmpp/lib/src/negotiators/sasl/plain.dart @@ -4,7 +4,6 @@ import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/negotiators/sasl/errors.dart'; -import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart'; import 'package:moxxmpp/src/negotiators/sasl/nonza.dart'; import 'package:moxxmpp/src/negotiators/sasl2.dart'; import 'package:moxxmpp/src/stringxml.dart'; @@ -58,7 +57,6 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator { } else { final tag = nonza.tag; if (tag == 'success') { - await attributes.sendEvent(AuthenticationSuccessEvent()); attributes.setAuthenticated(); return const Result(NegotiatorState.done); } else { @@ -90,7 +88,8 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator { Future getRawStep(String input) async { final settings = attributes.getConnectionSettings(); return base64.encode( - utf8.encode('\u0000${settings.jid.local}\u0000${settings.password}')); + utf8.encode('\u0000${settings.jid.local}\u0000${settings.password}'), + ); } @override diff --git a/packages/moxxmpp/lib/src/negotiators/sasl/scram.dart b/packages/moxxmpp/lib/src/negotiators/sasl/scram.dart index d1be06f..1273366 100644 --- a/packages/moxxmpp/lib/src/negotiators/sasl/scram.dart +++ b/packages/moxxmpp/lib/src/negotiators/sasl/scram.dart @@ -8,8 +8,8 @@ import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; import 'package:moxxmpp/src/negotiators/sasl/errors.dart'; import 'package:moxxmpp/src/negotiators/sasl/kv.dart'; -import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart'; import 'package:moxxmpp/src/negotiators/sasl/nonza.dart'; +import 'package:moxxmpp/src/negotiators/sasl2.dart'; import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/types/result.dart'; import 'package:random_string/random_string.dart'; @@ -95,7 +95,7 @@ enum ScramState { preSent, initialMessageSent, challengeResponseSent, error } const gs2Header = 'n,,'; -class SaslScramNegotiator extends SaslNegotiator { +class SaslScramNegotiator extends Sasl2AuthenticationNegotiator { // NOTE: NEVER, and I mean, NEVER set clientNonce or initalMessageNoGS2. They are just there for testing SaslScramNegotiator( int priority, @@ -236,20 +236,9 @@ class SaslScramNegotiator extends SaslNegotiator { ) async { switch (_scramState) { case ScramState.preSent: - if (clientNonce == null || clientNonce == '') { - clientNonce = randomAlphaNumeric( - 40, - provider: CoreRandomProvider.from(Random.secure()), - ); - } - - initialMessageNoGS2 = - 'n=${attributes.getConnectionSettings().jid.local},r=$clientNonce'; - - _scramState = ScramState.initialMessageSent; attributes.sendNonza( SaslScramAuthNonza( - body: base64.encode(utf8.encode(gs2Header + initialMessageNoGS2)), + body: await getRawStep(''), type: hashType, ), redact: SaslScramAuthNonza(body: '******', type: hashType).toXml(), @@ -266,12 +255,8 @@ class SaslScramNegotiator extends SaslNegotiator { ); } - final challengeBase64 = nonza.innerText(); - final response = await calculateChallengeResponse(challengeBase64); - final responseBase64 = base64.encode(utf8.encode(response)); - _scramState = ScramState.challengeResponseSent; attributes.sendNonza( - SaslScramResponseNonza(body: responseBase64), + SaslScramResponseNonza(body: await getRawStep(nonza.innerText())), redact: SaslScramResponseNonza(body: '******').toXml(), ); return const Result(NegotiatorState.ready); @@ -314,4 +299,43 @@ class SaslScramNegotiator extends SaslNegotiator { super.reset(); } + + @override + Future getRawStep(String input) async { + switch (_scramState) { + case ScramState.preSent: + if (clientNonce == null || clientNonce == '') { + clientNonce = randomAlphaNumeric( + 40, + provider: CoreRandomProvider.from(Random.secure()), + ); + } + + initialMessageNoGS2 = + 'n=${attributes.getConnectionSettings().jid.local},r=$clientNonce'; + + _scramState = ScramState.initialMessageSent; + return base64.encode(utf8.encode(gs2Header + initialMessageNoGS2)); + case ScramState.initialMessageSent: + final challengeBase64 = input; + final response = await calculateChallengeResponse(challengeBase64); + final responseBase64 = base64.encode(utf8.encode(response)); + _scramState = ScramState.challengeResponseSent; + + return responseBase64; + case ScramState.challengeResponseSent: + case ScramState.error: + return ''; + } + } + + @override + Future> onSasl2FeaturesReceived(XMLNode sasl2Features) async { + return []; + } + + @override + Future onSasl2Success(XMLNode response) async { + state = NegotiatorState.done; + } } diff --git a/packages/moxxmpp/lib/src/negotiators/sasl2.dart b/packages/moxxmpp/lib/src/negotiators/sasl2.dart index ef981fd..861b33c 100644 --- a/packages/moxxmpp/lib/src/negotiators/sasl2.dart +++ b/packages/moxxmpp/lib/src/negotiators/sasl2.dart @@ -1,4 +1,3 @@ -import 'package:moxxmpp/src/events.dart'; import 'package:moxxmpp/src/namespaces.dart'; import 'package:moxxmpp/src/negotiators/namespaces.dart'; import 'package:moxxmpp/src/negotiators/negotiator.dart'; @@ -6,15 +5,14 @@ import 'package:moxxmpp/src/negotiators/sasl/negotiator.dart'; import 'package:moxxmpp/src/stringxml.dart'; import 'package:moxxmpp/src/types/result.dart'; -typedef Sasl2FeaturesReceivedCallback = Future> Function(XMLNode); - +/// A special type of [XmppFeatureNegotiatorBase] that is aware of SASL2. abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase { Sasl2FeatureNegotiator( - int priority, - bool sendStreamHeaderWhenDone, - String negotiatingXmlns, - String id, - ) : super(priority, sendStreamHeaderWhenDone, negotiatingXmlns, id); + super.priority, + super.sendStreamHeaderWhenDone, + super.negotiatingXmlns, + super.id, + ); /// Called by the SASL2 negotiator when we received the SASL2 stream features /// [sasl2Features]. The return value is a list of XML elements that should be @@ -26,10 +24,10 @@ abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase { Future onSasl2Success(XMLNode response); } +/// A special type of [SaslNegotiator] that is aware of SASL2. abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator implements Sasl2FeatureNegotiator { - Sasl2AuthenticationNegotiator(int priority, String id, String mechanismName) - : super(priority, id, mechanismName); + Sasl2AuthenticationNegotiator(super.priority, super.id, super.mechanismName); /// Perform a SASL step with [input] as the already parsed input data. Returns /// the base64-encoded response data. @@ -59,8 +57,10 @@ class UserAgent { final String? device; XMLNode toXml() { - assert(id != null || software != null || device != null, - 'A completely empty user agent makes no sense'); + assert( + id != null || software != null || device != null, + 'A completely empty user agent makes no sense', + ); return XMLNode( tag: 'user-agent', attributes: id != null @@ -130,7 +130,8 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase { @override Future> negotiate( - XMLNode nonza) async { + XMLNode nonza, + ) async { switch (_sasl2State) { case Sasl2State.idle: final sasl2 = nonza.firstTag('authentication', xmlns: sasl2Xmlns)!;