feat(core): Make SCRAM SASL2 aware

This commit is contained in:
PapaTutuWawa 2023-03-31 21:09:16 +02:00
parent 7ab3f4f0d9
commit 478b5b8770
4 changed files with 61 additions and 36 deletions

View File

@ -268,7 +268,8 @@ class XmppConnection {
/// Register a list of negotiator with the connection.
Future<void> registerFeatureNegotiators(
List<XmppFeatureNegotiatorBase> negotiators) async {
List<XmppFeatureNegotiatorBase> negotiators,
) async {
for (final negotiator in negotiators) {
_log.finest('Registering ${negotiator.id}');
negotiator.register(

View File

@ -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<String> 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

View File

@ -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<String> 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<List<XMLNode>> onSasl2FeaturesReceived(XMLNode sasl2Features) async {
return [];
}
@override
Future<void> onSasl2Success(XMLNode response) async {
state = NegotiatorState.done;
}
}

View File

@ -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<List<XMLNode>> 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<void> 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<Result<NegotiatorState, NegotiatorError>> negotiate(
XMLNode nonza) async {
XMLNode nonza,
) async {
switch (_sasl2State) {
case Sasl2State.idle:
final sasl2 = nonza.firstTag('authentication', xmlns: sasl2Xmlns)!;