Created
October 21, 2015 06:09
-
-
Save vadimtsushko/e915a0f861a190c1ffc6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
part of mongo_dart; | |
class ClientFirst extends SaslStep { | |
String clientFirstMessageBare; | |
UsernamePasswordCredential credential; | |
String rPrefix; | |
ClientFirst(Uint8List bytesToSendToServer, this.credential, | |
this.clientFirstMessageBare, this.rPrefix) { | |
this.bytesToSendToServer = bytesToSendToServer; | |
isComplete = false; | |
} | |
@override | |
SaslStep transition( | |
SaslConversation conversation, List<int> bytesReceivedFromServer) { | |
String serverFirstMessage = UTF8.decode(bytesReceivedFromServer); | |
// serverFirstMessage = 'r=fyko+d2lbbFgONRv9qkxdawLHo+Vgk7qvUOKUwuWLIWg4l/9SraGMHEE,s=rQ9ZY3MntBeuP3E1TDVC4w==,i=10000'; | |
Map decodedMessage = parsePayload(serverFirstMessage); | |
String r = decodedMessage['r']; | |
if (r == null || !r.startsWith(rPrefix)) { | |
throw new MongoDartError("Server sent an invalid nonce."); | |
} | |
var s = decodedMessage['s']; | |
var i = int.parse(decodedMessage['i']); | |
final String gs2Header = 'n,,'; | |
String encodedHeader = BASE64.encode(UTF8.encode(gs2Header)); | |
var channelBinding = 'c=$encodedHeader'; | |
var nonce = 'r=$r'; | |
var clientFinalMessageWithoutProof = '$channelBinding,$nonce'; | |
var passwordDigest = | |
md5DigestPassword(credential.username, credential.password); | |
var salt = BASE64.decode(s); | |
var saltedPassword = hi(passwordDigest, salt, i); | |
var clientKey = computeHMAC(saltedPassword, 'Client Key'); | |
var storedKey = h(clientKey); | |
var authMessage = | |
'$clientFirstMessageBare,$serverFirstMessage,$clientFinalMessageWithoutProof'; | |
var clientSignature = computeHMAC(storedKey, authMessage); | |
var clientProof = xor(clientKey, clientSignature); | |
var serverKey = computeHMAC(saltedPassword, 'Server Key'); | |
var serverSignature = computeHMAC(serverKey, authMessage); | |
var base64clientProof = BASE64.encode(clientProof); | |
var proof = 'p=$base64clientProof'; | |
var clientFinalMessage = '$clientFinalMessageWithoutProof,$proof'; | |
return new ClientLast(UTF8.encode(clientFinalMessage), serverSignature); | |
} | |
static Uint8List computeHMAC(Uint8List data, String key) { | |
var sha1 = new SHA1(); | |
var hmac = new HMAC(sha1, data); | |
hmac.add(UTF8.encode(key)); | |
return new Uint8List.fromList(hmac.close()); | |
} | |
static Uint8List h(Uint8List data) { | |
var sha1 = new SHA1(); | |
sha1.add(data); | |
return new Uint8List.fromList(sha1.close()); | |
} | |
static String md5DigestPassword(username, password) { | |
var md5 = new MD5()..add(UTF8.encode('$username:mongo:$password')); | |
List<int> bytes = md5.close(); | |
return CryptoUtils.bytesToHex(bytes); | |
} | |
static Uint8List xor(Uint8List a, Uint8List b) { | |
var result = []; | |
if (a.length > b.length) { | |
for (var i = 0; i < b.length; i++) { | |
result.add(a[i] ^ b[i]); | |
} | |
} else { | |
for (var i = 0; i < a.length; i++) { | |
result.add(a[i] ^ b[i]); | |
} | |
} | |
return new Uint8List.fromList(result); | |
} | |
static Uint8List hi(String password, Uint8List salt, int iterations) { | |
List<int> passwordDigest = []; | |
var digest = (msg) { | |
var hmac = new HMAC(new SHA1(), password.codeUnits); | |
hmac.add(msg); | |
return new Uint8List.fromList(hmac.close()); | |
}; | |
Uint8List newSalt = new Uint8List.fromList(new List.from(salt)..addAll([0,0,0,1])); | |
var ui = digest(newSalt); | |
var u1 = ui; | |
for (var i = 0; i < iterations - 1; i++) { | |
u1 = digest(u1); | |
ui = xor(ui, u1); | |
} | |
return ui; | |
} | |
} | |
class ClientLast extends SaslStep { | |
Uint8List serverSignature64; | |
ClientLast(Uint8List bytesToSendToServer, this.serverSignature64) { | |
this.bytesToSendToServer = bytesToSendToServer; | |
isComplete = false; | |
} | |
@override | |
SaslStep transition( | |
SaslConversation conversation, List<int> bytesReceivedFromServer) { | |
Map decodedMessage = parsePayload(UTF8.decode(bytesReceivedFromServer)); | |
var serverSignature = BASE64.decode(decodedMessage['v']); | |
if (!const ListEquality().equals(serverSignature64, serverSignature)) { | |
throw new MongoDartError("Server signature was invalid."); | |
} | |
return new CompletedStep(); | |
} | |
} | |
class CompletedStep extends SaslStep { | |
CompletedStep() { | |
this.bytesToSendToServer = null; | |
isComplete = true; | |
} | |
@override | |
SaslStep transition( | |
SaslConversation conversation, List<int> bytesReceivedFromServer) { | |
throw new MongoDartError("Sasl conversation has completed"); | |
} | |
} | |
class ScramSha1Mechanism extends SaslMechanism { | |
final UsernamePasswordCredential credential; | |
final RandomStringGenerator randomStringGenerator; | |
ScramSha1Mechanism(this.credential, this.randomStringGenerator); | |
@override | |
SaslStep initialize(_Connection connection) { | |
if (connection == null) throw new ArgumentError("Connection can't be null"); | |
final String gs2Header = 'n,,'; | |
var username = 'n=${prepUsername(credential.username)}'; | |
var r = randomStringGenerator.generate(20, ""); // TODO Change this | |
// var r = 'fyko+d2lbbFgONRv9qkxdawL'; | |
var nonce = 'r=$r'; | |
var clientFirstMessageBare = '$username,$nonce'; | |
var clientFirstMessage = '$gs2Header$clientFirstMessageBare'; | |
return new ClientFirst( | |
UTF8.encode(clientFirstMessage), credential, clientFirstMessageBare, r); | |
} | |
String prepUsername(String username) => | |
username.replaceAll('=', '=3D').replaceAll(',', '=2C'); | |
String generateRandomString() { | |
const String legalCharacters = | |
'!"#\'\$%&()*+-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; | |
return randomStringGenerator.generate(20, legalCharacters); | |
} | |
@override | |
String get name => ScramSha1Authenticator.SCRAM; | |
} | |
class ScramSha1Authenticator extends SaslAuthenticator { | |
static final String SCRAM = 'SCRAM-SHA-1'; | |
@override | |
String get name => SCRAM; | |
ScramSha1Authenticator(UsernamePasswordCredential credential, Db db) | |
: super(new ScramSha1Mechanism(credential, new WeakRandomStringGenerator()), | |
db) { | |
this.db = db; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment