fix: SASL SCRAM-SHA-{256,512} should now work
This commit is contained in:
parent
6d9010b11c
commit
5dd96f518b
@ -78,6 +78,7 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
CSINegotiator(),
|
CSINegotiator(),
|
||||||
RosterFeatureNegotiator(),
|
RosterFeatureNegotiator(),
|
||||||
SaslPlainNegotiator(),
|
SaslPlainNegotiator(),
|
||||||
|
SaslScramNegotiator(10, '', '', ScramHashType.sha512),
|
||||||
SaslScramNegotiator(9, '', '', ScramHashType.sha256),
|
SaslScramNegotiator(9, '', '', ScramHashType.sha256),
|
||||||
SaslScramNegotiator(8, '', '', ScramHashType.sha1),
|
SaslScramNegotiator(8, '', '', ScramHashType.sha1),
|
||||||
]);
|
]);
|
||||||
|
@ -30,6 +30,17 @@ HashAlgorithm hashFromType(ScramHashType type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int pbkdfBitsFromHash(ScramHashType type) {
|
||||||
|
switch (type) {
|
||||||
|
// NOTE: SHA1 is 20 octets long => 20 octets * 8 bits/octet
|
||||||
|
case ScramHashType.sha1: return 160;
|
||||||
|
// NOTE: SHA256 is 32 octets long => 32 octets * 8 bits/octet
|
||||||
|
case ScramHashType.sha256: return 256;
|
||||||
|
// NOTE: SHA512 is 64 octets long => 64 octets * 8 bits/octet
|
||||||
|
case ScramHashType.sha512: return 512;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const scramSha1Mechanism = 'SCRAM-SHA-1';
|
const scramSha1Mechanism = 'SCRAM-SHA-1';
|
||||||
const scramSha256Mechanism = 'SCRAM-SHA-256';
|
const scramSha256Mechanism = 'SCRAM-SHA-256';
|
||||||
const scramSha512Mechanism = 'SCRAM-SHA-512';
|
const scramSha512Mechanism = 'SCRAM-SHA-512';
|
||||||
@ -106,7 +117,7 @@ class SaslScramNegotiator extends SaslNegotiator {
|
|||||||
final pbkdf2 = Pbkdf2(
|
final pbkdf2 = Pbkdf2(
|
||||||
macAlgorithm: Hmac(_hash),
|
macAlgorithm: Hmac(_hash),
|
||||||
iterations: iterations,
|
iterations: iterations,
|
||||||
bits: 160, // NOTE: RFC says 20 octets => 20 octets * 8 bits/octet
|
bits: pbkdfBitsFromHash(hashType),
|
||||||
);
|
);
|
||||||
|
|
||||||
final saltedPasswordRaw = await pbkdf2.deriveKey(
|
final saltedPasswordRaw = await pbkdf2.deriveKey(
|
||||||
|
27
packages/moxxmpp/test/sasl/kv_test.dart
Normal file
27
packages/moxxmpp/test/sasl/kv_test.dart
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import 'package:moxxmpp/src/negotiators/sasl/kv.dart';
|
||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('Test the Key-Value parser', () {
|
||||||
|
final result1 = parseKeyValue('n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL');
|
||||||
|
expect(result1.length, 2);
|
||||||
|
expect(result1['n']!, 'user');
|
||||||
|
expect(result1['r']!, 'fyko+d2lbbFgONRv9qkxdawL');
|
||||||
|
|
||||||
|
final result2 = parseKeyValue('r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096');
|
||||||
|
expect(result2.length, 3);
|
||||||
|
expect(result2['r']!, 'fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j');
|
||||||
|
expect(result2['s']!, 'QSXCR+Q6sek8bf92');
|
||||||
|
expect(result2['i']!, '4096');
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Test the Key-Value parser with '=' as a value", () {
|
||||||
|
final result = parseKeyValue('c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=,o=123');
|
||||||
|
expect(result.length, 4);
|
||||||
|
expect(result['c']!, 'biws');
|
||||||
|
expect(result['r']!, 'fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j');
|
||||||
|
expect(result['p']!, 'v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=');
|
||||||
|
expect(result['o']!, '123');
|
||||||
|
});
|
||||||
|
}
|
207
packages/moxxmpp/test/sasl/scram_test.dart
Normal file
207
packages/moxxmpp/test/sasl/scram_test.dart
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:hex/hex.dart';
|
||||||
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
|
final scramSha1StreamFeatures = XMLNode(
|
||||||
|
tag: 'stream:features',
|
||||||
|
children: [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'mechanisms',
|
||||||
|
xmlns: saslXmlns,
|
||||||
|
children: [
|
||||||
|
XMLNode(
|
||||||
|
tag: 'mechanism',
|
||||||
|
text: 'SCRAM-SHA-1',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final scramSha256StreamFeatures = XMLNode(
|
||||||
|
tag: 'stream:features',
|
||||||
|
children: [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'mechanisms',
|
||||||
|
xmlns: saslXmlns,
|
||||||
|
children: [
|
||||||
|
XMLNode(
|
||||||
|
tag: 'mechanism',
|
||||||
|
text: 'SCRAM-SHA-256',
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final fakeSocket = StubTCPSocket(play: []);
|
||||||
|
test('Test SASL SCRAM-SHA-1', () async {
|
||||||
|
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1);
|
||||||
|
negotiator.register(
|
||||||
|
NegotiatorAttributes(
|
||||||
|
(XMLNode _, {String? redact}) {},
|
||||||
|
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true, allowPlainAuth: true),
|
||||||
|
(_) async {},
|
||||||
|
getNegotiatorNullStub,
|
||||||
|
getManagerNullStub,
|
||||||
|
() => JID.fromString('user@server'),
|
||||||
|
() => fakeSocket,
|
||||||
|
() => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
HEX.encode(await negotiator.calculateSaltedPassword('QSXCR+Q6sek8bf92', 4096)),
|
||||||
|
'1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
HEX.encode(
|
||||||
|
await negotiator.calculateClientKey(HEX.decode('1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d')),
|
||||||
|
),
|
||||||
|
'e234c47bf6c36696dd6d852b99aaa2ba26555728',
|
||||||
|
);
|
||||||
|
const authMessage = 'n=user,r=fyko+d2lbbFgONRv9qkxdawL,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096,c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j';
|
||||||
|
expect(
|
||||||
|
HEX.encode(
|
||||||
|
await negotiator.calculateClientSignature(authMessage, HEX.decode('e9d94660c39d65c38fbad91c358f14da0eef2bd6')),
|
||||||
|
),
|
||||||
|
'5d7138c486b0bfabdf49e3e2da8bd6e5c79db613',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
HEX.encode(
|
||||||
|
negotiator.calculateClientProof(HEX.decode('e234c47bf6c36696dd6d852b99aaa2ba26555728'), HEX.decode('5d7138c486b0bfabdf49e3e2da8bd6e5c79db613')),
|
||||||
|
),
|
||||||
|
'bf45fcbf7073d93d022466c94321745fe1c8e13b',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
HEX.encode(
|
||||||
|
await negotiator.calculateServerSignature(authMessage, HEX.decode('0fe09258b3ac852ba502cc62ba903eaacdbf7d31')),
|
||||||
|
),
|
||||||
|
'ae617da6a57c4bbb2e0286568dae1d251905b0a4',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
HEX.encode(
|
||||||
|
await negotiator.calculateServerKey(HEX.decode('1d96ee3a529b5a5f9e47c01f229a2cb8a6e15f7d')),
|
||||||
|
),
|
||||||
|
'0fe09258b3ac852ba502cc62ba903eaacdbf7d31',
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
HEX.encode(
|
||||||
|
negotiator.calculateClientProof(
|
||||||
|
HEX.decode('e234c47bf6c36696dd6d852b99aaa2ba26555728'),
|
||||||
|
HEX.decode('5d7138c486b0bfabdf49e3e2da8bd6e5c79db613'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
'bf45fcbf7073d93d022466c94321745fe1c8e13b',
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(await negotiator.calculateChallengeResponse('cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng=='), 'c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test SASL SCRAM-SHA-256', () async {
|
||||||
|
String? lastMessage;
|
||||||
|
final negotiator = SaslScramNegotiator(0, 'n=user,r=rOprNGfwEbeRWgbNEkqO', 'rOprNGfwEbeRWgbNEkqO', ScramHashType.sha256);
|
||||||
|
negotiator.register(
|
||||||
|
NegotiatorAttributes(
|
||||||
|
(XMLNode n, {String? redact}) => lastMessage = n.innerText(),
|
||||||
|
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true, allowPlainAuth: true),
|
||||||
|
(_) async {},
|
||||||
|
getNegotiatorNullStub,
|
||||||
|
getManagerNullStub,
|
||||||
|
() => JID.fromString('user@server'),
|
||||||
|
() => fakeSocket,
|
||||||
|
() => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await negotiator.negotiate(scramSha256StreamFeatures);
|
||||||
|
expect(
|
||||||
|
utf8.decode(base64Decode(lastMessage!)),
|
||||||
|
'n,,n=user,r=rOprNGfwEbeRWgbNEkqO',
|
||||||
|
);
|
||||||
|
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1yT3ByTkdmd0ViZVJXZ2JORWtxTyVodllEcFdVYTJSYVRDQWZ1eEZJbGopaE5sRiRrMCxzPVcyMlphSjBTTlk3c29Fc1VFamI2Z1E9PSxpPTQwOTY=</challenge>"));
|
||||||
|
expect(
|
||||||
|
utf8.decode(base64Decode(lastMessage!)),
|
||||||
|
'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF\$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=',
|
||||||
|
);
|
||||||
|
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj02cnJpVFJCaTIzV3BSUi93dHVwK21NaFVaVW4vZEI1bkxUSlJzamw5NUc0PQ==</success>"));
|
||||||
|
|
||||||
|
expect(negotiator.state, NegotiatorState.done);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test a positive server signature check', () async {
|
||||||
|
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1);
|
||||||
|
negotiator.register(
|
||||||
|
NegotiatorAttributes(
|
||||||
|
(XMLNode _, {String? redact}) {},
|
||||||
|
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true, allowPlainAuth: true),
|
||||||
|
(_) async {},
|
||||||
|
getNegotiatorNullStub,
|
||||||
|
getManagerNullStub,
|
||||||
|
() => JID.fromString('user@server'),
|
||||||
|
() => fakeSocket,
|
||||||
|
() => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await negotiator.negotiate(scramSha1StreamFeatures);
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>"));
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>"));
|
||||||
|
|
||||||
|
expect(negotiator.state, NegotiatorState.done);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test a negative server signature check', () async {
|
||||||
|
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1);
|
||||||
|
negotiator.register(
|
||||||
|
NegotiatorAttributes(
|
||||||
|
(XMLNode _, {String? redact}) {},
|
||||||
|
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true, allowPlainAuth: true),
|
||||||
|
(_) async {},
|
||||||
|
getNegotiatorNullStub,
|
||||||
|
getManagerNullStub,
|
||||||
|
() => JID.fromString('user@server'),
|
||||||
|
() => fakeSocket,
|
||||||
|
() => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await negotiator.negotiate(scramSha1StreamFeatures);
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>"));
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1zbUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>"));
|
||||||
|
|
||||||
|
expect(negotiator.state, NegotiatorState.error);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test a resetting the SCRAM negotiator', () async {
|
||||||
|
final negotiator = SaslScramNegotiator(0, 'n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'fyko+d2lbbFgONRv9qkxdawL', ScramHashType.sha1);
|
||||||
|
negotiator.register(
|
||||||
|
NegotiatorAttributes(
|
||||||
|
(XMLNode _, {String? redact}) {},
|
||||||
|
() => ConnectionSettings(jid: JID.fromString('user@server'), password: 'pencil', useDirectTLS: true, allowPlainAuth: true),
|
||||||
|
(_) async {},
|
||||||
|
getNegotiatorNullStub,
|
||||||
|
getManagerNullStub,
|
||||||
|
() => JID.fromString('user@server'),
|
||||||
|
() => fakeSocket,
|
||||||
|
() => false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await negotiator.negotiate(scramSha1StreamFeatures);
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>"));
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>"));
|
||||||
|
expect(negotiator.state, NegotiatorState.done);
|
||||||
|
|
||||||
|
// Reset and try again
|
||||||
|
negotiator.reset();
|
||||||
|
await negotiator.negotiate(scramSha1StreamFeatures);
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj1meWtvK2QybGJiRmdPTlJ2OXFreGRhd0wzcmZjTkhZSlkxWlZ2V1ZzN2oscz1RU1hDUitRNnNlazhiZjkyLGk9NDA5Ng==</challenge>"));
|
||||||
|
await negotiator.negotiate(XMLNode.fromString("<success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1ybUY5cHFWOFM3c3VBb1pXamE0ZEpSa0ZzS1E9</success>"));
|
||||||
|
expect(negotiator.state, NegotiatorState.done);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user