/* * 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 */ cryptolib.modules.primitives = cryptolib.modules.primitives || {}; /** @namespace primitives/derivation */ cryptolib.modules.primitives.derivation = function(box) { 'use strict'; box.primitives = box.primitives || {}; box.primitives.derivation = {}; /** * A module that holds derivation functionalities. * * @exports primitives/derivation */ box.primitives.derivation.factory = {}; var policies = { pbkdf: { provider: box.policies.primitives.derivation.pbkdf.provider, hash: box.policies.primitives.derivation.pbkdf.hash, saltLengthBytes: box.policies.primitives.derivation.pbkdf.saltLengthBytes, keyLengthBytes: box.policies.primitives.derivation.pbkdf.keyLengthBytes, iterations: box.policies.primitives.derivation.pbkdf.iterations } }; var exceptions, converters; cryptolib('rnd', 'commons', function(box) { exceptions = box.commons.exceptions; converters = new box.commons.utils.Converters(); }); /** * A factory class for generating {@link CryptoForgePBKDFSecretKeyGenerator} * objects. * * @class */ box.primitives.derivation.factory.SecretKeyGeneratorFactory = function() { }; box.primitives.derivation.factory.SecretKeyGeneratorFactory.prototype = { /** * Creates a generator for deriving passwords. * * @function * @returns {primitives/derivation.CryptoForgePBKDFSecretKeyGenerator} a * {@link primitives/derivation.CryptoForgePBKDFSecretKeyGenerator} * object. */ createPBKDF: function() { var provider = policies.pbkdf.provider; try { if (provider === Config.primitives.derivation.pbkdf.provider.FORGE) { return new CryptoForgePBKDFSecretKeyGenerator(); } else { throw new exceptions.CryptoLibException( 'The specified provider ' + provider + ' is not supported.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoPBKDFSecretKeyGenerator object could not be created.', error); } } }; /** * * @class * @memberof primitives/derivation */ function CryptoForgePBKDFSecretKeyGenerator() { validateHash(policies.pbkdf.hash); validateKeyLengthBytes(policies.pbkdf.keyLengthBytes); validateSaltLengthBytes(policies.pbkdf.saltLengthBytes); validateIterations(policies.pbkdf.iterations); this.keyLengthBytes = policies.pbkdf.keyLengthBytes; } CryptoForgePBKDFSecretKeyGenerator.prototype = { /** * @param {string} * password the password, as a string of bytes in Base64 * encoded format. * @param {string} * salt the salt, as a string of bytes in Base64 encoded * format. Note that the salt length should coincide with the * one required by the hash algorithm. * * @return {string} the derived key, as a string of bytes in Base64 * encoded format. */ generateSecret: function(passwordB64, saltB64) { var password = converters.base64Decode(passwordB64); var salt = converters.base64Decode(saltB64); if (salt.length < policies.pbkdf.saltLengthBytes) { throw new exceptions.CryptoLibException( 'The salt byte length ' + salt.length + ' is less than the minimum allowed salt length ' + policies.pbkdf.saltLengthBytes + ' set by the cryptographic policy.'); } var derivedKey = sjcl.misc.pbkdf2( password, sjcl.codec.base64.toBits(saltB64), policies.pbkdf.iterations, this.keyLengthBytes * 8, hashCallback(policies.pbkdf.hash)); return sjcl.codec.base64.fromBits(derivedKey); } }; function hashCallback(algorithm) { if (algorithm === Config.primitives.derivation.pbkdf.hash.SHA256) { return null; } else { throw new exceptions.CryptoLibException( 'Hash algorithm \'' + algorithm + '\' is unsupported'); } } function validateHash(hash) { var supportedHashAlgorithms = [Config.primitives.derivation.pbkdf.hash.SHA256]; if (supportedHashAlgorithms.indexOf(hash) < 0) { throw new exceptions.CryptoLibException( 'The specified hash algorithm ' + hash + ' is not one of the supported options: ' + supportedHashAlgorithms); } } function validateSaltLengthBytes(saltLengthBytes) { var supportedSaltLengthBytes = [ Config.primitives.derivation.pbkdf.saltLengthBytes.SL_20, Config.primitives.derivation.pbkdf.saltLengthBytes.SL_32 ]; if (typeof saltLengthBytes !== 'number' || saltLengthBytes % 1 !== 0 || saltLengthBytes < 0) { throw new exceptions.CryptoLibException( 'The specified salt byte length ' + saltLengthBytes + ' is not a positive integer.'); } if (supportedSaltLengthBytes.indexOf(saltLengthBytes) < 0) { throw new exceptions.CryptoLibException( 'The specified salt byte length ' + saltLengthBytes + ' is not one of the supported options: ' + supportedSaltLengthBytes); } } function validateKeyLengthBytes(keyLengthBytes) { var supportedKeyLengthBytes = [ Config.primitives.derivation.pbkdf.keyLengthBytes.KL_16, Config.primitives.derivation.pbkdf.keyLengthBytes.KL_32 ]; if (typeof keyLengthBytes !== 'number' || keyLengthBytes % 1 !== 0 || keyLengthBytes < 0) { throw new exceptions.CryptoLibException( 'The specified key byte length ' + keyLengthBytes + ' is not a positive number.'); } if (supportedKeyLengthBytes.indexOf(keyLengthBytes) < 0) { throw new exceptions.CryptoLibException( 'The specified key byte length ' + keyLengthBytes + ' is not one of the supported options: ' + supportedKeyLengthBytes); } } function validateIterations(iterations) { var supportedIterations = [ Config.primitives.derivation.pbkdf.iterations.I_1, Config.primitives.derivation.pbkdf.iterations.I_8000, Config.primitives.derivation.pbkdf.iterations.I_16000, Config.primitives.derivation.pbkdf.iterations.I_32000, Config.primitives.derivation.pbkdf.iterations.I_64000 ]; if (typeof iterations !== 'number' || iterations % 1 !== 0 || iterations < 0) { throw new exceptions.CryptoLibException( 'The specified number of iterations ' + iterations + ' is not a positive integer.'); } if (supportedIterations.indexOf(iterations) < 0) { throw new exceptions.CryptoLibException( 'The specified number of iterations ' + iterations + ' is not one of the supported options: ' + supportedIterations); } } };