/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
/* jshint node:true */
'use strict';

var Policy = require('scytl-cryptopolicy');
var validator = require('./input-validator');
var codec = require('scytl-codec');
var sjcl = require('sjcl');

module.exports = PbkdfDeriver;

/**
 * @class PbkdfDeriver
 * @classdesc The PBKDF deriver API. To instantiate this object, use the method
 *            {@link PbkdfService.newDeriver}.
 * @hideconstructor
 * @param {Policy}
 *            policy The cryptographic policy to use.
 */
function PbkdfDeriver(policy) {
  var keyLength_ = policy.primitives.keyDerivation.pbkdf.keyLengthBytes;
  var minSaltLength_ = policy.primitives.keyDerivation.pbkdf.minSaltLengthBytes;
  var numIterations_ = policy.primitives.keyDerivation.pbkdf.numIterations;
  var hashAlgorithm_ = policy.primitives.keyDerivation.pbkdf.hashAlgorithm;

  /**
   * Derives a key, using a PBKDF.
   *
   * @function derive
   * @memberof PbkdfDeriver
   * @param {string}
   *            password The password from which to derive the key.
   * @param {Uint8Array|string}
   *            salt The salt from which to derive the key. <b>NOTE:</b> Salt
   *            of type <code>string</code> will be UTF-8 encoded.
   * @returns {Uint8Array} The derived key.
   * @throws {Error}
   *             If the input data validation fails.
   */
  this.derive = function(password, salt) {
    if (typeof salt === 'string') {
      salt = codec.utf8Encode(salt);
    }
    checkData(password, salt);

    var generateHmac = function(key) {
      var generator = new sjcl.misc.hmac(key, getDigester(hashAlgorithm_));
      this.encrypt = function() {
        return generator.encrypt.apply(generator, arguments);
      };
    };

    // NOTE: The following Base64 encoding/decoding should be replaced with the
    // use of 'sjcl.codec.bytes.toBits/fromBits' when/if the latter becomes part
    // of the default sjcl build.
    var derivedKey = sjcl.misc.pbkdf2(
        password, sjcl.codec.base64.toBits(codec.base64Encode(salt)),
        numIterations_, keyLength_ * 8, generateHmac);

    return codec.base64Decode(sjcl.codec.base64.fromBits(derivedKey));
  };

  function getDigester(algorithm) {
    if (algorithm ===
        Policy.options.primitives.keyDerivation.pbkdf.hashAlgorithm.SHA256) {
      return sjcl.hash.sha256;
    } else {
      throw new Error(
          'Hash algorithm \'' + algorithm + '\' is not recognized.');
    }
  }

  function checkData(password, salt) {
    validator.checkIsType(
        password, 'string', 'Password from which to derive key');
    validator.checkIsInstanceOf(
        salt, Uint8Array, 'Uint8Array', 'Salt from which to derive key');

    if (salt.length < minSaltLength_) {
      throw new Error(
          'The salt byte length ' + salt.length +
          ' is less than the minimum allowed salt length ' + minSaltLength_ +
          ' set by the cryptographic policy.');
    }
  }
}
