/* * 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, jasmine:true */ 'use strict'; var secureRandom = require('../lib/index'); var constants = require('../lib/constants'); var codec = require('scytl-codec'); var forge = require('node-forge'); var BigInteger = forge.jsbn.BigInteger; describe('The secure random module should be able to ...', function() { var TEST_SEED = new Uint8Array([18, 58, 188]); var PRIVATE_KEY_PEM = '-----BEGIN RSA PRIVATE KEY-----MIIEowIBAAKCAQEAglypZU45bnf2opnRI+Y51VqouBpvDT33xIB/OtbwKzwVpi+JrjBFtfk33tE9t/dSRs79CK94HRhCWcOiLa2qWPrjeZ9SBiEyScrhIvRZVBF41zBgwQNuRvJCsKmAqlZaFNJDZxEP4repmlBn1CfVFmfrXmOKqwP5F7l9ZtucveRzsfmF1yVPFkW8TMuB3YqMiyymyqHlS8ujCsu5I8tpgPbwuxdMOY94fNhSXrYkY8IuX1g1zdq/Z1jluOaR/UqK4UpnbuJaH/F0VgDNiWh6cTD0DFGEk0b70i5wU4Q3L/S6XZQRvSuADoCbhwBKuFL5pW5n865oLVb5S3wuVdWaGwIDAQABAoIBAC/tn34Wf3kE9BGeGc1oFLVDaqqdVVz5/oEpeR2J7q0GnzMFYUpAhzC7WvY52cYsUPyll1Q9Jx0TUTmteo/uvKWQQFfz4nVMeS+2PoXabolBDzuWlsv/1eiRo0FOYHa/3siu8YcQN9X0DpAkpbfTmT1uoZOHZ3EuucMmOFu7vGn38Grw8bSxpR0uvTtnb8ygC+aB51y38RMyhzQQanrM8FMeAfDAy6IB0Yo7b0c50Cxa6Ax4nqn9LXyGakr5WeAMkgTIOA/GId9SZD4e5eRpq+628pOeR4O9datFltgl6r1+A4ii2VrJsDqeatGtODlX6KRKqwFHoGIa2TjgSZLuorECgYEAxeSZDOOgFsI5mB7RkRzZaQ9znJ15sgdyZiAFZAOUah4hSGdAXNAnZTlrdacduXEu3EfkpuPToX7xZSv5FRYwfBwMwCLeytlGLPjQzWejZGbo4+KqgzWb9fECDYVtDPlJ/+yLih9nt67BHweJKxYydl18rVigdVyy22X86NijSykCgYEAqKPUrXZAo+TJvmTw4tgsibJgvXBYBhmsej8mGNQw+Nyp2gV28sgm61ifIeXKS8teq+MFwGA6cHQedbsCqhMHokdhESZmlbWxhSFLihQcewBxwvrBwbaxI23yXRzwMewznZFL032PpcbqrmwFmcSSEZ3nmbvTH6ShqLW+pzDNp6MCgYBQLzdgxJ7qedqSa/JohTMG4e7rh9d2rpPJE7J7ewPZF8pOpx+qO+Gqn2COdJ+Ts2vUcAETKn9nEaPIZc/wnmQY9dioxbhWo0FPGaaphBPtq9Ez/XUv4zoFppk5V1X/isdUPsmvttf00oeIBiqrXbwmv+yz5JRn2Z7TTXjz9Ev+OQKBgQCUuoCMRzl1EgcXIqEL/0kwW6BUEqufHa9u1Ri9Vw6lvL8T6DPipMEmWK9nzuid9gtVns/ovTVtDgv7GuabplLaPQePf4WDzY11c0rSyS/hDyBFrK+LL5uEOqhAlJAGB2HyOj1clWVF+GvrTpuV5LZKUS/79pmZU7G7QCaX/0Ow7wKBgC/kDH7cmWQnWvvJ5izrx/7PogQVPOLELeUIGLu/hjsSdDKiFCxCUZ948+9NuG+DnpXDWzw//r8mPBRRGGsqFws5Aipp7yjQ3kRDCCzGelPCVhHyfmKqA+8ewXPulKS3/wIyHIvaXmsuAtTfurHtpRyzjKmCBK1Y6WQ3trIXvo7s-----END RSA PRIVATE KEY-----'; var PIN_ENTROPY = 100; var service_; var randomGenerator_; beforeEach(function() { service_ = secureRandom.newService(); randomGenerator_ = service_.newRandomGenerator(); }); describe('create a secure random service that should be able to ..', function() { describe( 'create a secure random generator that should be able to', function() { it('generate 10 random bytes', function() { var numBytes = 10; var bytes = randomGenerator_.nextBytes(numBytes); expect(bytes).toBeDefined(); expect(bytes.length).not.toBeGreaterThan(numBytes); }); it('generate a specified number of random bytes', function() { var numBytes = 16; var bytes = randomGenerator_.nextBytes(numBytes); expect(bytes).toBeDefined(); expect(bytes.numBytes).not.toBeGreaterThan(numBytes); }); it('generate a random BigInteger of a specified maximum bit length', function() { for (var maxNumBits = 1; maxNumBits <= 20; maxNumBits++) { var randomBigInteger = randomGenerator_.nextBigInteger(maxNumBits); expect(randomBigInteger).not.toBeLessThan(0); expect(randomBigInteger.bitLength()) .not.toBeGreaterThan(maxNumBits); } }); it('generate a random BigInteger of a specified number of digits', function() { var maxValue; for (var numDigits = 1; numDigits <= 10; numDigits++) { maxValue = Math.pow(10, numDigits); var randomBigInteger = randomGenerator_.nextBigIntegerByDigits(numDigits); expect(randomBigInteger.compareTo(BigInteger.ZERO)) .toBeGreaterThan(0); expect(randomBigInteger.compareTo( new BigInteger(maxValue.toString()))) .toBeLessThan(0); } }); it('be created by specifying a maximum allowed number of bytes', function() { var maxNumBytes = constants.SECURE_RANDOM_MAX_NUM_BYTES + 1; var randomGenerator_ = service_.newRandomGenerator({maxNumBytes: maxNumBytes}); var randomBytes = randomGenerator_.nextBytes(maxNumBytes); expect(randomBytes).toBeDefined(); expect(randomBytes.length).not.toBeGreaterThan(maxNumBytes); expect(function() { randomGenerator_.nextBytes(maxNumBytes + 1); }).toThrow(); }); it('be created by specifying maximum allowed number of digits', function() { var maxNumDigits = constants.SECURE_RANDOM_MAX_NUM_BYTES + 1; var randomGenerator_ = service_.newRandomGenerator({maxNumDigits: maxNumDigits}); var randomBigInteger = randomGenerator_.nextBigIntegerByDigits(maxNumDigits); expect(randomBigInteger).toBeDefined(); expect(randomBigInteger.compareTo(BigInteger.ZERO)) .toBeGreaterThan(0); expect(randomBigInteger.compareTo( new BigInteger('10').pow(maxNumDigits))) .toBeLessThan(0); expect(function() { randomGenerator_.nextBigIntegerByDigits(maxNumDigits + 1); }).toThrow(); }); it('throw an exception when a negative input argument is passed', function() { expect(function() { randomGenerator_.nextBytes(-1); }).toThrow(); expect(function() { randomGenerator_.nextBigInteger(-1); }).toThrow(); expect(function() { randomGenerator_.nextBigIntegerByDigits(-1); }).toThrow(); }); it('throw an exception when a non-numeric input argument is passed', function() { expect(function() { randomGenerator_.nextBytes('abc'); }).toThrow(); expect(function() { randomGenerator_.nextBigInteger('abc'); }).toThrow(); expect(function() { randomGenerator_.nextBigIntegerByDigits('abc'); }).toThrow(); }); it('throw an exception when a too-large input argument is passed', function() { expect(function() { randomGenerator_.nextBytes( constants.SECURE_RANDOM_MAX_NUM_BYTES + 1); }).toThrow(); expect(function() { randomGenerator_.nextBigIntegerByDigits( constants.SECURE_RANDOM_MAX_NUM_DIGITS + 1); }).toThrow(); }); }); it('initialize its PRNG from a provided seed', function() { var service = secureRandom.newService( {prngSeed: TEST_SEED, noDefaultCollectors: true}); var randomGenerator = service.newRandomGenerator(); expect(randomGenerator.nextBigIntegerByDigits(2).toString()) .toEqual('21'); expect(randomGenerator.nextBigIntegerByDigits(4).toString()) .toEqual('2950'); expect(randomGenerator.nextBigIntegerByDigits(16).toString()) .toEqual('5574593524320719'); }); it('generate a random seed to instantiate other secure random services', function() { var seed = service_.nextSeed(); expect(seed.length) .toBe(constants.SECURE_RANDOM_DEFAULT_NUM_SEED_BYTES); seed = service_.nextSeed(16); expect(seed.length).toBe(16); var service1 = secureRandom.newService( {prngSeed: seed, noDefaultCollectors: true}); var randomGenerator1 = service1.newRandomGenerator(); var randomInt1 = randomGenerator1.nextBigIntegerByDigits(2); var service2 = secureRandom.newService( {prngSeed: seed, noDefaultCollectors: true}); var randomGenerator2 = service2.newRandomGenerator(); var randomInt2 = randomGenerator2.nextBigIntegerByDigits(2); expect(randomInt1).toEqual(randomInt2); }); it('create two different secure random generators that use the same PRNG', function() { var service = secureRandom.newService( {prngSeed: TEST_SEED, noDefaultCollectors: true}); var randomGenerator1 = service.newRandomGenerator(); var randomGenerator2 = service.newRandomGenerator(); expect(randomGenerator1.nextBigIntegerByDigits(2).toString()) .toEqual('21'); expect(randomGenerator1.nextBigIntegerByDigits(4).toString()) .toEqual('2950'); expect(randomGenerator1.nextBigIntegerByDigits(16).toString()) .toEqual('5574593524320719'); expect(randomGenerator2.nextBigIntegerByDigits(2).toString()) .toEqual('12'); expect(randomGenerator2.nextBigIntegerByDigits(4).toString()) .toEqual('6576'); expect(randomGenerator2.nextBigIntegerByDigits(16).toString()) .toEqual('2672997009079990'); }); it('start and stop the entropy collection manually', function() { var callbackCalled = false; var callback = function() { callbackCalled = true; }; service_.startEntropyCollection(callback); var maxEntropyCollected = service_.stopEntropyCollection(); expect(callbackCalled).toBeTruthy(); expect(typeof maxEntropyCollected).toBe('boolean'); }); it('measure the entropy collected as a percentage of the maximum entropy required', function() { var service = secureRandom.newService({noDefaultCollectors: true}); service.startEntropyCollection(); service.stopEntropyCollection(); expect(service.getEntropyPercentage()).toBe(0); service = secureRandom.newService(); service.startEntropyCollection(); service.stopEntropyCollection(); expect(service.getEntropyPercentage()).toBeGreaterThan(0); expect(service.getEntropyPercentage()).toBeLessThan(101); }); it('collect some entropy with the maximum entropy counter option set', function() { var service = secureRandom.newService({maxEntropyCounter: 150}); expect(service.getEntropyPercentage()).toBeGreaterThan(0); expect(service.getEntropyPercentage()).toBeLessThan(101); }); it('collect some entropy with the hash updater interval option set', function() { var service = secureRandom.newService({hashUpdaterInterval: 500}); expect(service.getEntropyPercentage()).toBeGreaterThan(0); expect(service.getEntropyPercentage()).toBeLessThan(101); }); it('collect some entropy using private key data', function() { var privateKeyData = { privateKey: PRIVATE_KEY_PEM, pinEntropy: PIN_ENTROPY }; var service = secureRandom.newService( {noDefaultCollectors: true, privateKeyData: privateKeyData}); expect(service.getEntropyPercentage()).toBeGreaterThan(0); expect(service.getEntropyPercentage()).toBeLessThan(101); }); it('generate bytes with the Forge-compliant bytes generation method', function() { var service = secureRandom.newService( {prngSeed: TEST_SEED, noDefaultCollectors: true}); var randomGenerator = service.newRandomGenerator(); expect(codec.base64Encode( codec.binaryDecode(randomGenerator.getBytesSync(2)))) .toEqual('k5U='); expect(codec.base64Encode( codec.binaryDecode(randomGenerator.getBytesSync(4)))) .toEqual('FQJ+/Q=='); expect(codec.base64Encode( codec.binaryDecode(randomGenerator.getBytesSync(6)))) .toEqual('o9f6kc4U'); }); }); });