2025-11-05 17:04:23 -03:00

134 lines
3.5 KiB
JavaScript

var bitops = require('./lib/bitops');
var utils = require('./lib/utils');
var RESP = {};
var CLIENT_KEY = 'Client Key';
var SERVER_KEY = 'Server Key';
function base64decode(s) {
if (atob) {
return Uint8Array.from(atob(s), function(c) { return c.charCodeAt(0); });
} else {
return Uint8Array.from(Buffer.from(s, 'base64'));
}
}
function base64encode(s) {
if (btoa) {
return btoa(s);
} else {
return Buffer.from(s).toString('base64');
}
}
function Mechanism(options) {
options = options || {};
this._genNonce = options.genNonce || utils.genNonce;
this._stage = 'initial';
}
// Conform to the SASL lib's expectations
Mechanism.Mechanism = Mechanism;
Mechanism.prototype.name = 'SCRAM-SHA-1';
Mechanism.prototype.clientFirst = true;
Mechanism.prototype.response = function (cred) {
return RESP[this._stage](this, cred);
};
Mechanism.prototype.challenge = function (chal) {
var values = utils.parse(chal);
this._salt = base64decode(values.s || '');
this._iterationCount = parseInt(values.i, 10);
this._nonce = values.r;
this._verifier = values.v;
this._error = values.e;
this._challenge = chal;
return this;
};
RESP.initial = function (mech, cred) {
mech._cnonce = mech._genNonce();
var authzid = '';
if (cred.authzid) {
authzid = 'a=' + utils.saslname(cred.authzid);
}
mech._gs2Header = 'n,' + authzid + ',';
var nonce = 'r=' + mech._cnonce;
var username = 'n=' + utils.saslname(cred.username || '');
mech._clientFirstMessageBare = username + ',' + nonce;
var result = mech._gs2Header + mech._clientFirstMessageBare;
mech._stage = 'challenge';
return result;
};
RESP.challenge = function (mech, cred) {
var gs2Header = base64encode(mech._gs2Header);
mech._clientFinalMessageWithoutProof = 'c=' + gs2Header + ',r=' + mech._nonce;
var saltedPassword, clientKey, serverKey;
// If our cached salt is the same, we can reuse cached credentials to speed
// up the hashing process.
if (cred.salt && cred.salt.every(function(value, index) { return value === mech._salt[index]; })) {
if (cred.clientKey && cred.serverKey) {
clientKey = cred.clientKey;
serverKey = cred.serverKey;
} else if (cred.saltedPassword) {
saltedPassword = cred.saltedPassword;
clientKey = bitops.HMAC(saltedPassword, CLIENT_KEY);
serverKey = bitops.HMAC(saltedPassword, SERVER_KEY);
}
} else {
saltedPassword = bitops.Hi(cred.password || '', mech._salt, mech._iterationCount);
clientKey = bitops.HMAC(saltedPassword, CLIENT_KEY);
serverKey = bitops.HMAC(saltedPassword, SERVER_KEY);
}
var storedKey = bitops.H(clientKey);
var authMessage = mech._clientFirstMessageBare + ',' +
mech._challenge + ',' +
mech._clientFinalMessageWithoutProof;
var clientSignature = bitops.HMAC(storedKey, authMessage);
var clientProof = base64encode(String.fromCharCode.apply(null, bitops.XOR(clientKey, clientSignature)));
mech._serverSignature = bitops.HMAC(serverKey, authMessage);
var result = mech._clientFinalMessageWithoutProof + ',p=' + clientProof;
mech._stage = 'final';
mech.cache = {
salt: mech._salt,
saltedPassword: saltedPassword,
clientKey: clientKey,
serverKey: serverKey
};
return result;
};
RESP.final = function () {
// TODO: Signal errors
return '';
};
module.exports = Mechanism;