feat(core): Make SCRAM SASL2 aware
This commit is contained in:
parent
7ab3f4f0d9
commit
478b5b8770
@ -268,7 +268,8 @@ class XmppConnection {
|
|||||||
|
|
||||||
/// Register a list of negotiator with the connection.
|
/// Register a list of negotiator with the connection.
|
||||||
Future<void> registerFeatureNegotiators(
|
Future<void> registerFeatureNegotiators(
|
||||||
List<XmppFeatureNegotiatorBase> negotiators) async {
|
List<XmppFeatureNegotiatorBase> negotiators,
|
||||||
|
) async {
|
||||||
for (final negotiator in negotiators) {
|
for (final negotiator in negotiators) {
|
||||||
_log.finest('Registering ${negotiator.id}');
|
_log.finest('Registering ${negotiator.id}');
|
||||||
negotiator.register(
|
negotiator.register(
|
||||||
|
@ -4,7 +4,6 @@ import 'package:moxxmpp/src/events.dart';
|
|||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/errors.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/sasl/nonza.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/sasl2.dart';
|
import 'package:moxxmpp/src/negotiators/sasl2.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
@ -58,7 +57,6 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
} else {
|
} else {
|
||||||
final tag = nonza.tag;
|
final tag = nonza.tag;
|
||||||
if (tag == 'success') {
|
if (tag == 'success') {
|
||||||
await attributes.sendEvent(AuthenticationSuccessEvent());
|
|
||||||
attributes.setAuthenticated();
|
attributes.setAuthenticated();
|
||||||
return const Result(NegotiatorState.done);
|
return const Result(NegotiatorState.done);
|
||||||
} else {
|
} else {
|
||||||
@ -90,7 +88,8 @@ class SaslPlainNegotiator extends Sasl2AuthenticationNegotiator {
|
|||||||
Future<String> getRawStep(String input) async {
|
Future<String> getRawStep(String input) async {
|
||||||
final settings = attributes.getConnectionSettings();
|
final settings = attributes.getConnectionSettings();
|
||||||
return base64.encode(
|
return base64.encode(
|
||||||
utf8.encode('\u0000${settings.jid.local}\u0000${settings.password}'));
|
utf8.encode('\u0000${settings.jid.local}\u0000${settings.password}'),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -8,8 +8,8 @@ import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
import 'package:moxxmpp/src/negotiators/negotiator.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
|
import 'package:moxxmpp/src/negotiators/sasl/errors.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/sasl/kv.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/sasl/nonza.dart';
|
||||||
|
import 'package:moxxmpp/src/negotiators/sasl2.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
import 'package:random_string/random_string.dart';
|
import 'package:random_string/random_string.dart';
|
||||||
@ -95,7 +95,7 @@ enum ScramState { preSent, initialMessageSent, challengeResponseSent, error }
|
|||||||
|
|
||||||
const gs2Header = 'n,,';
|
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
|
// NOTE: NEVER, and I mean, NEVER set clientNonce or initalMessageNoGS2. They are just there for testing
|
||||||
SaslScramNegotiator(
|
SaslScramNegotiator(
|
||||||
int priority,
|
int priority,
|
||||||
@ -236,20 +236,9 @@ class SaslScramNegotiator extends SaslNegotiator {
|
|||||||
) async {
|
) async {
|
||||||
switch (_scramState) {
|
switch (_scramState) {
|
||||||
case ScramState.preSent:
|
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(
|
attributes.sendNonza(
|
||||||
SaslScramAuthNonza(
|
SaslScramAuthNonza(
|
||||||
body: base64.encode(utf8.encode(gs2Header + initialMessageNoGS2)),
|
body: await getRawStep(''),
|
||||||
type: hashType,
|
type: hashType,
|
||||||
),
|
),
|
||||||
redact: SaslScramAuthNonza(body: '******', type: hashType).toXml(),
|
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(
|
attributes.sendNonza(
|
||||||
SaslScramResponseNonza(body: responseBase64),
|
SaslScramResponseNonza(body: await getRawStep(nonza.innerText())),
|
||||||
redact: SaslScramResponseNonza(body: '******').toXml(),
|
redact: SaslScramResponseNonza(body: '******').toXml(),
|
||||||
);
|
);
|
||||||
return const Result(NegotiatorState.ready);
|
return const Result(NegotiatorState.ready);
|
||||||
@ -314,4 +299,43 @@ class SaslScramNegotiator extends SaslNegotiator {
|
|||||||
|
|
||||||
super.reset();
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:moxxmpp/src/events.dart';
|
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
import 'package:moxxmpp/src/negotiators/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/negotiators/negotiator.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/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.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 {
|
abstract class Sasl2FeatureNegotiator extends XmppFeatureNegotiatorBase {
|
||||||
Sasl2FeatureNegotiator(
|
Sasl2FeatureNegotiator(
|
||||||
int priority,
|
super.priority,
|
||||||
bool sendStreamHeaderWhenDone,
|
super.sendStreamHeaderWhenDone,
|
||||||
String negotiatingXmlns,
|
super.negotiatingXmlns,
|
||||||
String id,
|
super.id,
|
||||||
) : super(priority, sendStreamHeaderWhenDone, negotiatingXmlns, id);
|
);
|
||||||
|
|
||||||
/// Called by the SASL2 negotiator when we received the SASL2 stream features
|
/// 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
|
/// [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);
|
Future<void> onSasl2Success(XMLNode response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A special type of [SaslNegotiator] that is aware of SASL2.
|
||||||
abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator
|
abstract class Sasl2AuthenticationNegotiator extends SaslNegotiator
|
||||||
implements Sasl2FeatureNegotiator {
|
implements Sasl2FeatureNegotiator {
|
||||||
Sasl2AuthenticationNegotiator(int priority, String id, String mechanismName)
|
Sasl2AuthenticationNegotiator(super.priority, super.id, super.mechanismName);
|
||||||
: super(priority, id, mechanismName);
|
|
||||||
|
|
||||||
/// Perform a SASL step with [input] as the already parsed input data. Returns
|
/// Perform a SASL step with [input] as the already parsed input data. Returns
|
||||||
/// the base64-encoded response data.
|
/// the base64-encoded response data.
|
||||||
@ -59,8 +57,10 @@ class UserAgent {
|
|||||||
final String? device;
|
final String? device;
|
||||||
|
|
||||||
XMLNode toXml() {
|
XMLNode toXml() {
|
||||||
assert(id != null || software != null || device != null,
|
assert(
|
||||||
'A completely empty user agent makes no sense');
|
id != null || software != null || device != null,
|
||||||
|
'A completely empty user agent makes no sense',
|
||||||
|
);
|
||||||
return XMLNode(
|
return XMLNode(
|
||||||
tag: 'user-agent',
|
tag: 'user-agent',
|
||||||
attributes: id != null
|
attributes: id != null
|
||||||
@ -130,7 +130,8 @@ class Sasl2Negotiator extends XmppFeatureNegotiatorBase {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
Future<Result<NegotiatorState, NegotiatorError>> negotiate(
|
||||||
XMLNode nonza) async {
|
XMLNode nonza,
|
||||||
|
) async {
|
||||||
switch (_sasl2State) {
|
switch (_sasl2State) {
|
||||||
case Sasl2State.idle:
|
case Sasl2State.idle:
|
||||||
final sasl2 = nonza.firstTag('authentication', xmlns: sasl2Xmlns)!;
|
final sasl2 = nonza.firstTag('authentication', xmlns: sasl2Xmlns)!;
|
||||||
|
Loading…
Reference in New Issue
Block a user