auth_41.js 2.6 KB
'use strict';

/*
4.1 authentication: (http://bazaar.launchpad.net/~mysql/mysql-server/5.5/view/head:/sql/password.c)

  SERVER:  public_seed=create_random_string()
           send(public_seed)

  CLIENT:  recv(public_seed)
           hash_stage1=sha1("password")
           hash_stage2=sha1(hash_stage1)
           reply=xor(hash_stage1, sha1(public_seed,hash_stage2)

           // this three steps are done in scramble()

           send(reply)


  SERVER:  recv(reply)
           hash_stage1=xor(reply, sha1(public_seed,hash_stage2))
           candidate_hash2=sha1(hash_stage1)
           check(candidate_hash2==hash_stage2)

server stores sha1(sha1(password)) ( hash_stag2)
*/

const crypto = require('crypto');

function sha1(msg, msg1, msg2) {
  const hash = crypto.createHash('sha1');
  hash.update(msg);
  if (msg1) {
    hash.update(msg1);
  }

  if (msg2) {
    hash.update(msg2);
  }

  return hash.digest();
}

function xor(a, b) {
  if (!Buffer.isBuffer(a)) {
    a = Buffer.from(a, 'binary');
  }

  if (!Buffer.isBuffer(b)) {
    b = Buffer.from(b, 'binary');
  }

  const result = Buffer.allocUnsafe(a.length);

  for (let i = 0; i < a.length; i++) {
    result[i] = a[i] ^ b[i];
  }
  return result;
}

exports.xor = xor;

function token(password, scramble1, scramble2) {
  // TODO: use buffers (not sure why strings here)
  if (!password) {
    return Buffer.alloc(0);
  }
  const stage1 = sha1(password);
  return exports.calculateTokenFromPasswordSha(stage1, scramble1, scramble2);
}

exports.calculateTokenFromPasswordSha = function(
  passwordSha,
  scramble1,
  scramble2
) {
  // we use AUTH 41 here, and we need only the bytes we just need.
  const authPluginData1 = scramble1.slice(0, 8);
  const authPluginData2 = scramble2.slice(0, 12);
  const stage2 = sha1(passwordSha);
  const stage3 = sha1(authPluginData1, authPluginData2, stage2);
  return xor(stage3, passwordSha);
};

exports.calculateToken = token;

exports.verifyToken = function(publicSeed1, publicSeed2, token, doubleSha) {
  const hashStage1 = xor(token, sha1(publicSeed1, publicSeed2, doubleSha));
  const candidateHash2 = sha1(hashStage1);
  return candidateHash2.compare(doubleSha) === 0;
};

exports.doubleSha1 = function(password) {
  return sha1(sha1(password));
};

function xorRotating(a, seed) {
  if (!Buffer.isBuffer(a)) {
    a = Buffer.from(a, 'binary');
  }

  if (!Buffer.isBuffer(seed)) {
    seed = Buffer.from(seed, 'binary');
  }

  const result = Buffer.allocUnsafe(a.length);
  const seedLen = seed.length;

  for (let i = 0; i < a.length; i++) {
    result[i] = a[i] ^ seed[i % seedLen];
  }
  return result;
}
exports.xorRotating = xorRotating;