/* * 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 */ describe('derivation module: ', function() { 'use strict'; it('should exist', function() { cryptolib('primitives.derivation', function(box) { expect(box.primitives.derivation).toBeDefined(); }); }); it('should contain a PBKDF generator factory', function() { cryptolib('primitives.derivation', function(box) { var pbkdfGeneratorFactory = new box.primitives.derivation.factory.SecretKeyGeneratorFactory(); expect(pbkdfGeneratorFactory).toBeDefined(); }); }); describe('PBKDF generator factory', function() { it('should generate a secretKey.', function() { cryptolib( 'primitives.derivation', 'commons.utils', 'primitives.securerandom', function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var secureRandomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); var cryptoRandomBytes = secureRandomFactory.getCryptoRandomBytes(); var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); var salt = cryptoRandomBytes.nextRandom(20); var saltB64 = converters.base64Encode(salt); var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).toBeDefined(); expect(converters.base64Decode(secretKeyB64).length) .toBe(box.policies.primitives.derivation.pbkdf.keyLengthBytes); }); }); it('should generate a secretKey with hash SHA256 and salt length 32 bytes.', function() { cryptolib( 'primitives.derivation', 'commons.utils', 'primitives.securerandom', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var secureRandomFactory = new box.primitives.securerandom .factory.SecureRandomFactory(); var cryptoRandomBytes = secureRandomFactory.getCryptoRandomBytes(); var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); var salt = cryptoRandomBytes.nextRandom(32); var saltB64 = converters.base64Encode(salt); var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).toBeDefined(); expect(converters.base64Decode(secretKeyB64).length) .toBe(box.policies.primitives.derivation.pbkdf .keyLengthBytes); }; f.policies = { primitives: { secureRandom: { provider: Config.primitives.secureRandom.provider.SCYTL }, derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 32, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib( 'primitives.derivation', 'commons.utils', 'primitives.securerandom', f); }); }); it('should generate a secretKey that matches one created with another engine, if the policies and parameters are the same.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var calculatedKey = 'vD80dK3PvPrQkALIY87oi7agHGoOCGGhvkZFuNASgL8='; var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8='; var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).toEqual(calculatedKey); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should generate a secretKey that does not match one created with another engine, if the key lengths are different.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var calculatedKey = 'vD80dK3PvPrQkALIY87oi7agHGoOCGGhvkZFuNASgL8='; var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8='; var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).not.toEqual(calculatedKey); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 16, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should generate a secretKey that does not match one created with another engine, if the number of iterations are different.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var calculatedKey = 'T5dDOwtsmPEnIYuCqUAldZZo4tO86FgcO7GAgDhaxYc='; var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8='; var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).not.toEqual(calculatedKey); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 8000 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should generate a secretKey that does not match one created with another engine, if the password is different.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var calculatedKey = 'T5dDOwtsmPEnIYuCqUAldZZo4tO86FgcO7GAgDhaxYc='; var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('Scytl'); var saltB64 = 'CpHq+38iqF3Ls7j97qBQ2A9Diy8='; var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).not.toEqual(calculatedKey); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should generate a secretKey that does not match one created with another engine, if the salt is different.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var calculatedKey = 'T5dDOwtsmPEnIYuCqUAldZZo4tO86FgcO7GAgDhaxYc='; var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); var saltB64 = 'cpHq+38iqF3Ls7j97qBQ2A9Diy8='; var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).not.toEqual(calculatedKey); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should generate a secretKey that does not match one created with another engine, if the hash algorithm is different.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var saltB64 = 'T4DtI/lcymMpBjJ7Ib9xpyS6xpuB5/+G68X3dT66eJs='; var generatedKeySHA1 = 'Ol8LDensA6B3CNhguSJrcuAnHAaB1NnGL0VfgbzDi34='; var encodedPassword = converters.base64Encode('scytl'); var secretKeyB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); expect(secretKeyB64).not.toEqual(generatedKeySHA1); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 32, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should throw an error when an unsupported provider is specified in the policy.', function() { var f = function(box) { var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); expect(function() { pbkdfGeneratorFactory.createPBKDF(); }).toThrow(); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'WRONG', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should throw an error when an unsupported hash algorithm is specified in the policy.', function() { var f = function(box) { var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); expect(function() { pbkdfGeneratorFactory.createPBKDF(); }).toThrow(); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'WRONG', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should throw an error when an unsupported minimum salt length is specified in the policy.', function() { var f = function(box) { var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); expect(function() { pbkdfGeneratorFactory.createPBKDF(); }).toThrow(); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 21, keyLengthBytes: 32, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should throw an error when an unsupported key length is specified in the policy.', function() { var f = function(box) { var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); expect(function() { pbkdfGeneratorFactory.createPBKDF(); }).toThrow(); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 33, iterations: 1 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should throw an error when an unsupported number of iterations is specified in the policy.', function() { var f = function(box) { var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); expect(function() { pbkdfGeneratorFactory.createPBKDF(); }).toThrow(); }; f.policies = { primitives: { derivation: { pbkdf: { provider: 'Forge', hash: 'SHA256', saltLengthBytes: 20, keyLengthBytes: 32, iterations: 2 } } } }; cryptolib('primitives.derivation', 'commons.utils', f); }); it('should throw an error when a salt with length less than the minimum specified in the policy is used.', function() { var f = function(box) { var converters = new box.commons.utils.Converters(); var secureRandomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); var randomByteGenerator = secureRandomFactory.getCryptoRandomBytes(); var salt = randomByteGenerator.nextRandom( box.policies.primitives.derivation.pbkdf.saltLengthBytes - 1); var saltB64 = converters.base64Encode(salt); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var encodedPassword = converters.base64Encode('scytl'); expect(function() { cryptoPbkdfSecretKeyGenerator.generateSecret( encodedPassword, saltB64); }).toThrow(); }; cryptolib( 'primitives.securerandom', 'primitives.derivation', 'commons.utils', f); }); it('should produce the same results when using SJCL or Forge as the cryptographic service provider.', function() { cryptolib( 'primitives.derivation', 'commons.utils', 'primitives.securerandom', function(box) { var converters = new box.commons.utils.Converters(); var pbkdfGeneratorFactory = new box.primitives.derivation.factory .SecretKeyGeneratorFactory(); var secureRandomFactory = new box.primitives.securerandom.factory .SecureRandomFactory(); var cryptoRandomBytes = secureRandomFactory.getCryptoRandomBytes(); var cryptoPbkdfSecretKeyGenerator = pbkdfGeneratorFactory.createPBKDF(); var password = generateRandomString(Math.floor(Math.random() * 128) + 1); var passwordB64 = converters.base64Encode(password); var salt = cryptoRandomBytes.nextRandom(20); var saltB64 = converters.base64Encode(salt); var secretKeyFromSjclB64 = cryptoPbkdfSecretKeyGenerator.generateSecret( passwordB64, saltB64); var iterations = cryptolibPolicies.primitives.derivation.pbkdf.iterations; var keyLengthBytes = cryptolibPolicies.primitives.derivation.pbkdf.keyLengthBytes; var secretKeyFromForge = forge.pkcs5.pbkdf2( password, salt, iterations, keyLengthBytes, getHashGenerator( cryptolibPolicies.primitives.derivation.pbkdf.hash, Config.primitives.derivation.pbkdf.hash)); var secretKeyFromForgeB64 = converters.base64Encode(secretKeyFromForge); expect(secretKeyFromForgeB64).toBe(secretKeyFromSjclB64); }); }); }); }); function generateRandomString(length) { 'use strict'; var allowedChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&()*+,-./:;<=>?@[\]^_`{|}~'; var str = ''; for (var i = 0; i < length; i++) { str += allowedChars.charAt(Math.floor(Math.random() * allowedChars.length)); } return str; } function getHashGenerator(algorithm, config) { 'use strict'; if (algorithm === config.SHA256) { return forge.sha256.create(); } else { throw 'Hash algorithm \'' + algorithm + '\' is unsupported'; } }