Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

sasl: SCRAM-SHA-1 implementation (incomplete)

  • Loading branch information...
commit 074ee35041ce110af2bc88748d9d66a6cbdd6a5d 1 parent 20a349f
@astro astro authored
Showing with 173 additions and 6 deletions.
  1. +142 −6 lib/xmpp/sasl.js
  2. +31 −0 test/sasl-test.js
View
148 lib/xmpp/sasl.js
@@ -11,6 +11,8 @@ var EventEmitter = require('events').EventEmitter;
function selectMechanism(mechs) {
if (mechs.indexOf("X-FACEBOOK-PLATFORM") >= 0)
return new XFacebookPlatform();
+ else if (mechs.indexOf("SCRAM-SHA-1") >= 0)
+ return new ScramSHA1();
else if (mechs.indexOf("DIGEST-MD5") >= 0)
return new DigestMD5();
else if (mechs.indexOf("PLAIN") >= 0)
@@ -174,6 +176,73 @@ DigestMD5.prototype.response = function(s) {
return true;
};
+function Scram() {
+}
+util.inherits(Scram, Mechanism);
+Scram.prototype.auth = function() {
+ this.clientFirstMessageBare =
+ "n=" + this.authcid +
+ ",r=" + generateRandom();
+ return "n,," + this.clientFirstMessageBare;
+};
+Scram.prototype.challenge = function(s) {
+ this.serverFirstMessage = s;
+ var dict = parseDict(s);
+ var random = dict.r;
+ var salt = dict.s;
+ var iterations = dict.i;
+
+ var saltedPassword = this.hi(this.password, salt, iterations);
+ console.log("saltedPassword", saltedPassword);
+ var clientKey = this.hmac(saltedPassword, "Client Key");
+ console.log("clientKey", clientKey);
+ var storedKey = this.h(clientKey);
+ console.log("storedKey", storedKey);
+ var clientFinalMessageWithoutProof =
+ "c=" + encode64("n,,") +
+ ",r=" + random;
+ var authMessage = this.clientFirstMessageBare + "," +
+ this.serverFirstMessage + "," +
+ clientFinalMessageWithoutProof;
+ var clientSignature = this.hmac(storedKey, authMessage);
+ var clientProof = xor(clientKey, clientSignature);
+
+ return clientFinalMessageWithoutProof +
+ ",p=" + encode64(clientProof);
+};
+Scram.prototype.hi = function(str, salt, iterations) {
+ var u = this.hmac(str, concatBuffers(salt, this.int(1)));
+ var result = u;
+ for(var i = 1; i < iterations; i++) {
+ u = this.hmac(str, u);
+ result = xor(result, u);
+ }
+ return result;
+};
+Scram.prototype.int = function(i) {
+ var result = new Buffer(4);
+ result.writeUInt32BE(i, 0);
+ return result;
+};
+
+/**
+ * http://tools.ietf.org/html/rfc5802
+ */
+function ScramSHA1() {
+}
+util.inherits(ScramSHA1, Scram);
+ScramSHA1.prototype.name = "SCRAM-SHA-1";
+ScramSHA1.prototype.h = function(s) {
+ var hash = crypto.createHash('sha1');
+ hash.update(s);
+ return new Buffer(hash.digest('binary'), 'binary');
+};
+ScramSHA1.prototype.hmac = function(k, s) {
+ var hmac = crypto.createHmac('sha1', k);
+ hmac.update(s);
+ return new Buffer(hmac.digest('binary'), 'binary');
+};
+
/**
* Parse SASL serialization
*/
@@ -194,6 +263,7 @@ function parseDict(s) {
result[m[1]] = m[2];
s = m[3];
} else {
+ /* Nothing matches, done */
s = null;
}
}
@@ -206,11 +276,15 @@ function parseDict(s) {
function encodeDict(dict) {
var s = "";
for(k in dict) {
- var v = dict[k];
- if (v)
- s += ',' + k + '="' + v + '"';
+ var v = dict[k].toString();
+ if (v) {
+ s += ',' + k + '=';
+ s += (v.indexOf(',') < 0) ?
+ v :
+ '"' + v + '"';
+ }
}
- return s.substr(1); // without first ','
+ return s.substr(1); // without first ',' (FIXME)
}
/**
@@ -233,12 +307,54 @@ function md5(s, encoding) {
}
/**
- * Hash a string hexadecimally
+ * Hash a string with hexadecimal result
*/
function md5_hex(s) {
return md5(s, 'hex');
}
+function encode64(decoded) {
+ return (new Buffer(decoded, 'utf8')).toString('base64');
+}
+
+function xor(buf1, buf2) {
+ if (buf1.constructor !== Buffer)
+ buf1 = new Buffer(buf1, 'binary');
+ if (buf2.constructor !== Buffer)
+ buf2 = new Buffer(buf2, 'binary');
+
+ var len = Math.max(buf1.length, buf2.length);
+ var result = new Buffer(len);
+ for(var i = 0; i < len; i++) {
+ var a = (i < buf1.length) ? buf1[i] : 0;
+ var b = (i < buf2.length) ? buf2[i] : 0;
+ result[i] = a ^ b;
+ }
+ return result;
+}
+
+/**
+ * Feel the pain of JavaScript strings
+ */
+function concatBuffers() {
+ var size = 0, i, bufs = [];
+ for(i = 0; i < arguments.length; i++) {
+ var arg = arguments[i], buf;
+ if (arg.constructor === Buffer)
+ buf = arg;
+ else
+ buf = new Buffer(arg);
+ bufs.push(buf);
+ size += buf.length;
+ }
+ var result = new Buffer(size), offset = 0;
+ for(i = 0; i < bufs.length; i++) {
+ bufs[i].copy(result, offset);
+ offset += bufs[i].length;
+ }
+ return result;
+}
+
/**
* Generate a string of 8 digits
* (number used once)
@@ -247,6 +363,26 @@ function generateNonce() {
var result = "";
for(var i = 0; i < 8; i++)
result += String.fromCharCode(48 +
- Math.ceil(Math.random() * 10));
+ Math.floor(Math.random() * 10));
+ return result;
+}
+
+/**
+ * Generate a sequence of random printable ASCII characters
+ */
+function generateRandom() {
+ var result = "";
+ for(var i = 0; i < 24; i++) {
+ var n = Math.floor(Math.random() * (10 + 26 + 26));
+ var c;
+ if (n < 10)
+ c = 48 + n;
+ else if (n < 10 + 26)
+ c = 65 + n - 10;
+ else
+ c = 97 + n - 10 - 26;
+ result += String.fromCharCode(c);
+ }
return result;
+
}
View
31 test/sasl-test.js
@@ -0,0 +1,31 @@
+var vows = require('vows'),
+assert = require('assert'),
+sasl = require('./../lib/xmpp/sasl');
+
+vows.describe('SASL').addBatch({
+
+ 'SCRAM-SHA-1': {
+ 'RFC 5802 example':
+ function() {
+ var mech = sasl.selectMechanism("SCRAM-SHA-1");
+ mech.authcid = "user";
+ mech.password = "pencil";
+
+ assert.equal(mech.name, "SCRAM-SHA-1");
+ var clientFirstMessage = mech.auth();
+ assert.ok(/^n,,n=user,r=/.test(clientFirstMessage));
+
+ /* Attention: we ignore our implementation's r= value but
+ * pass the example one
+ */
+ mech.clientFirstMessageBare = "n=user,r=fyko+d2lbbFgONRv9qkxdawL";
+ var serverFirstMessage = "r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096";
+ var clientFinalMessage = mech.challenge(serverFirstMessage);
+ console.log("clientFinalMessage", clientFinalMessage);
+ assert.ok(/c=biws/.test(clientFinalMessage));
+ assert.ok(/r=fyko\+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j/.test(clientFinalMessage));
+ assert.ok(/p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=/.test(clientFinalMessage));
+ }
+ }
+
+}).export(module);
Please sign in to comment.
Something went wrong with that request. Please try again.