/* * 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 */ /* * 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 */ /** * @global * @description This object describes the configuration scheme for this * cryptographic library. The following modules are considered: * * @readonly * @property {object} asymmetric.keypair - Policies for generating keypairs. * @property {object} asymmetric.keypair.encryption - Policies for generating * encryption keypairs. * @property {object} asymmetric.keypair.encryption.algorithm - Algorithm. * @property {string} asymmetric.keypair.encryption.algorithm.RSA - RSA * algorithm. * @property {object} asymmetric.keypair.encryption.provider - Provider. * @property {string} asymmetric.keypair.encryption.provider.FORGE - Forge * provider. * @property {object} asymmetric.keypair.encryption.keyLength - Key length. * @property {number} asymmetric.keypair.encryption.keyLength.KL_2048 - 2048 * bits. * @property {number} asymmetric.keypair.encryption.keyLength.KL_3072 - 3072 * bits. * @property {number} asymmetric.keypair.encryption.keyLength.KL_4096 - 4096 * bits. * @property {object} asymmetric.keypair.encryption.publicExponent - Public * exponent for generating encryption keypairs. * @property {number} asymmetric.keypair.encryption.publicExponent.F4 - 65537. * * @property {object} asymmetric.signer - Policies for signing and verifying * data. * @property {object} asymmetric.signer.algorithm - Algorithm. * @property {string} asymmetric.signer.algorithm.RSA - RSA algorithm. * @property {object} asymmetric.signer.provider - Provider. * @property {string} asymmetric.signer.provider.FORGE - Forge provider. * @property {object} asymmetric.signer.hash - Hash algorithm. * @property {string} asymmetric.signer.hash.SHA256 - SHA-256 algorithm. * @property {object} asymmetric.signer.padding - Padding scheme. * @property {object} asymmetric.signer.padding.PSS - PSS padding scheme. * @property {string} asymmetric.signer.padding.PSS.name - PSS. * @property {object} asymmetric.signer.padding.PSS.hash - Hash for the PSS * padding scheme. * @property {string} asymmetric.signer.padding.PSS.hash.SHA256 - SHA-256 * algorithm. * @property {object} asymmetric.signer.padding.PSS.mask - Mask generating * function for the PSS padding scheme. * @property {object} asymmetric.signer.padding.PSS.mask.MGF1 - MGF1 for the PSS * padding scheme. * @property {string} asymmetric.signer.padding.PSS.mask.MGF1.name - MGF1. * @property {object} asymmetric.signer.padding.PSS.mask.MGF1.hash - Hash * algorithm for the MGF1 for the PSS padding scheme. * @property {string} asymmetric.signer.padding.PSS.mask.MGF1.hash.SHA256 - * SHA-256 algorithm. * @property {object} asymmetric.signer.padding.PSS.saltLengthBytes - Salt * length for the PSS padding scheme. * @property {number} asymmetric.signer.padding.PSS.saltLengthBytes.SL_32 - 32 * bytes. * @property {object} asymmetric.signer.publicExponent - Public exponent. * @property {number} asymmetric.signer.publicExponent.F4 - 65537. * @property {object} primitives.securerandom - Policies for generating * securerandom. * @property {object} primitives.securerandom.provider - Provider. * @property {string} primitives.securerandom.provider.SCYTL - Scytl provider. * @property {object} primitives.messagedigest - Policies for generating message * digests. * @property {object} primitives.messagedigest.algorithm - Algorithm. * @property {string} primitives.messagedigest.algorithm.SHA256 - SHA-256 * algorithm. * @property {object} primitives.messagedigest.provider - Provider. * @property {string} primitives.messagedigest.provider.FORGE - Forge provider. * @property {object} symmetric.secretkey - Policies to generate secret keys. * @property {number} symmetric.secretkey.length - 128 bits. * @property {object} symmetric.cipher - Policies to encrypt and decrypt * symmetrically. * @property {object} symmetric.cipher.algorithm - Algorithm. * @property {object} symmetric.cipher.algorithm.AES128_GCM - AES128-GCM * algorithm. * @property {string} symmetric.cipher.algorithm.AES128_GCM.name - AES128-GCM. * @property {number} symmetric.cipher.algorithm.AES128_GCM.keyLengthBytes - 16 * bytes. * @property {number} symmetric.cipher.algorithm.AES128_GCM.tagLengthBytes - 16 * bytes. * @property {object} symmetric.cipher.provider - Provider. * @property {object} symmetric.cipher.provider.FORGE - Forge provider. * @property {object} symmetric.cipher.initializationVectorLengthBytes - * Initialization vector length. * @property {number} symmetric.cipher.initializationVectorLengthBytes.IV_12 - * 12 bytes. */ var Config = { homomorphic: {cipher: {secureRandom: {provider: {SCYTL: 'Scytl'}}}}, asymmetric: { keypair: { encryption: { algorithm: {RSA: 'RSA'}, provider: {FORGE: 'Forge'}, keyLength: {KL_2048: 2048, KL_3072: 3072, KL_4096: 4096}, publicExponent: {F4: 65537} }, secureRandom: {provider: {SCYTL: 'Scytl'}} }, signer: { algorithm: {RSA: 'RSA'}, provider: {FORGE: 'Forge'}, hash: {SHA256: 'SHA256'}, padding: { PSS: { name: 'PSS', hash: {SHA256: 'SHA256'}, mask: {MGF1: {name: 'MGF1', hash: {SHA256: 'SHA256'}}}, saltLengthBytes: {SL_32: 32} } }, publicExponent: {F4: 65537}, secureRandom: {provider: {SCYTL: 'Scytl'}} }, cipher: { algorithm: { RSA_OAEP: { name: 'RSA-OAEP', hash: 'SHA256', mask: {MGF1: {name: 'MGF1', hash: 'SHA1'}} }, RSA_KEM: { name: 'RSA-KEM', secretKeyLengthBytes: {KL_16: 16, KL_24: 24, KL_32: 32}, ivLengthBytes: {IVL_12: 12, IVL_16: 16, IVL_32: 32, IVL_64: 64}, tagLengthBytes: {TL_12: 12, TL_16: 16, TL_32: 32, TL_64: 64}, deriver: { name: {KDF1: 'kdf1', KDF2: 'kdf2', MGF1: 'mgf1'}, messagedigest: {algorithm: {SHA256: 'SHA256'}} }, cipher: {AESGCM: 'AES-GCM'} } }, provider: {FORGE: 'Forge'}, secureRandom: {provider: {SCYTL: 'Scytl'}} } }, primitives: { secureRandom: {provider: {SCYTL: 'Scytl'}}, messagedigest: { algorithm: {SHA256: 'SHA256', SHA512_224: 'SHA512/224'}, provider: {FORGE: 'Forge'} }, derivation: { pbkdf: { provider: {FORGE: 'Forge'}, hash: {SHA256: 'SHA256'}, saltLengthBytes: {SL_20: 20, SL_32: 32}, keyLengthBytes: {KL_16: 16, KL_32: 32}, iterations: { I_1: 1, I_8000: 8000, I_16000: 16000, I_32000: 32000, I_64000: 64000 } } } }, stores: {}, symmetric: { secretkey: { encryption: {lengthBytes: {SK_16: 16}}, mac: {lengthBytes: {SK_32: 32}}, secureRandom: {provider: {SCYTL: 'Scytl'}} }, cipher: { provider: {FORGE: 'Forge'}, algorithm: { AES128_GCM: {name: 'AES-GCM', keyLengthBytes: 16, tagLengthBytes: 16} }, initializationVectorLengthBytes: {IV_12: 12}, secureRandom: {provider: {SCYTL: 'Scytl'}} }, mac: {hash: {SHA256: 'SHA256'}, provider: {FORGE: 'Forge'}} }, proofs: { secureRandom: {provider: {SCYTL: 'Scytl'}}, messagedigest: { algorithm: {SHA256: 'SHA256', SHA512_224: 'SHA512/224'}, provider: {FORGE: 'Forge'} }, charset: {UTF8: 'UTF-8'} }, digitalenvelope: { symmetric: { secretkey: { encryption: {lengthBytes: {SK_16: 16}}, mac: {lengthBytes: {SK_32: 32}}, secureRandom: {provider: {SCYTL: 'Scytl'}} }, cipher: { provider: {FORGE: 'Forge'}, algorithm: { AES128_GCM: {name: 'AES-GCM', keyLengthBytes: 16, tagLengthBytes: 16} }, initializationVectorLengthBytes: {IV_12: 12}, secureRandom: {provider: {SCYTL: 'Scytl'}} }, mac: {hash: {SHA256: 'SHA256'}, provider: {FORGE: 'Forge'}} }, asymmetric: { cipher: { algorithm: { RSA_OAEP: { name: 'RSA-OAEP', hash: 'SHA256', mask: {MGF1: {name: 'MGF1', hash: 'SHA1'}} }, RSA_KEM: { name: 'RSA-KEM', secretKeyLengthBytes: {KL_16: 16, KL_24: 24, KL_32: 32}, ivLengthBytes: {IVL_12: 12, IVL_16: 16, IVL_32: 32, IVL_64: 64}, tagLengthBytes: {TL_12: 12, TL_16: 16, TL_32: 32, TL_64: 64}, deriver: { name: {KDF1: 'kdf1', KDF2: 'kdf2', MGF1: 'mgf1'}, messagedigest: {algorithm: {SHA256: 'SHA256'}} }, cipher: {AESGCM: 'AES-GCM'} } }, provider: {FORGE: 'Forge'}, secureRandom: {provider: {SCYTL: 'Scytl'}} } } } }; if (Object.freeze) { /** * Freezes an object and all its contents. * @param {Object} target the object to freeze. */ var recursiveFreeze = function(target) { 'use strict'; // Get all property names var propNames = Object.getOwnPropertyNames(target); // Freeze the object's properties. propNames.forEach(function(name) { var property = target[name]; // Freeze the properties' properties. if (typeof property === 'object' && property !== null) { recursiveFreeze(property); } }); // Freeze the object itself. return Object.freeze(target); }; // Lock down the configuration recursiveFreeze(Config); } var cryptolibPolicies; /** * @global * @function cryptolib * * @description Core of the library, this module is used to sandbox the * requested modules. * * @example // how to load all modules cryptolib('*', function(box) { // any * module can be used from here. // * box.examplemodule.service.getExample(); }); * * * @param modules * {...modules} modules to be loaded. '*' or none to load all the * modules. * @param function * {cryptolibSandbox} a closure for the cryptolib object. * @tutorial workers */ function cryptolib() { 'use strict'; /* Class to keep the references to polices and prng. */ function Cryptolib(modules, callback) { function getAllModules(parent) { var modules = []; for (var module in parent) { if (parent.hasOwnProperty(module)) { modules.push(module); } } return modules; } function loadModules(box, modules, parentModule) { if (!modules || modules.toString() === '*'.toString()) { modules = getAllModules(parentModule); } for (var i = 0; i < modules.length; i += 1) { var firstDotIndex = modules[i].indexOf('.'); if (firstDotIndex > -1) { var subModule = modules[i].substr(firstDotIndex + 1); var root = modules[i].substr(0, firstDotIndex); loadModules(box, [subModule], parentModule[root]); } else if (typeof parentModule[modules[i]] === 'function') { parentModule[modules[i]](box); } else if (typeof parentModule[modules[i]] === 'object') { loadModules(box, '*', parentModule[modules[i]]); } } } this.policies = callback.policies || cryptolibPolicies; cryptolibPolicies = cryptolibPolicies || this.policies; this.prng = callback.prng || cryptoPRNG.getPRNG(); loadModules(this, modules, cryptolib.modules); callback(this); } Cryptolib.prototype = { getKeys: function(obj) { var keys = []; for (var key in obj) { if (typeof key !== 'undefined') { keys.push(key); } } return keys; }, getModuleNames: function() { var moduleNames = ''; for (var module in cryptolib.modules) { if (typeof module !== 'undefined') { if (moduleNames.length > 0) { moduleNames += ', '; } moduleNames += module; } } return '[' + moduleNames + ']'; }, forge: forge, // redefines forge global variable sjcl: sjcl, // redefines sjcl global variable // Constants. /** @constant */ NUMBER_OF_BITS_PER_BYTE: 8, /** @constant */ MAXIMUM_LENGTH_TO_GENERATE_DATA: 512, /** @constant */ BASE_2_RADIX: 2, /** @constant */ BASE_10_RADIX: 10, /** @constant */ BASE_16_RADIX: 16, /** @constant */ EPOCH_TIME_LENGTH: 10, /** @constant */ RSA_LINE_LENGTH: 64, /** @constant */ SHA_256_LENGTH: 32, /** @constant */ HMAC_SHA256_KEY_LENGTH: 32, /** @constant */ AES_KEY_LENGTH: 16, /** @constant */ AES_CBC_IV_LENGTH: 16, /** @constant */ DES3_KEY_LENGTH: 24, /** @constant */ DES3_CBC_IV_LENGTH: 8, EQUALS: ' = ', NEWLINE: '\n', CRYPTO_SPEC_DELIM: '/', /** @constant */ SHA_256: 'SHA256', /** @constant */ RSA_ECB_PKCS1PADDING: 'RSA/ECB/PKCS1PADDING', /** @constant */ RSA_ECB_OAEP_WITH_SHA256_AND_MGF1PADDING: 'RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING', /** @constant */ FORGE_RSA_OAEP: 'RSA-OAEP', /** @constant */ FORGE_RSAES_PKCS1_V1_5: 'RSAES-PKCS1-V1_5', /** @constant */ MINIMUM_PRIME_CERTAINTY_LEVEL: 80, /** @constant */ SHORT_EXPONENT_BIT_LENGTH: 256, /** @constant */ EXPONENTIATION_PROOF_AUXILIARY_DATA: 'ExponentiationProof', /** @constant */ PLAINTEXT_PROOF_AUXILIARY_DATA: 'PlaintextProof', /** @constant */ SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA: 'SimplePlaintextEqualityProof', /** @constant */ PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA: 'PlaintextEqualityProof', /** @constant */ PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA: 'PlaintextExponentEqualityProof' }; var args = Array.prototype.slice.call(arguments); var callback = args.pop(); var modules = (args[0] && typeof args[0] === 'string') ? args : args[0]; return new Cryptolib(modules, callback); } cryptolib.modules = {}; /* * 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 */ //////////////////////////////////////////////// // // Functionalities added to JavaScript types // //////////////////////////////////////////////// /** * An equals method for arrays. * * @param array * the array to which this array should be compared. * @param strict * a boolean value which specifies whether or not the elements must * be in the same order in both arrays. This parameter is optional, * if it is not supplied, then a default value of 'true' is assigned * to it. */ Array.prototype.equals = function(array, strict) { 'use strict'; if (!array) { return false; } if (arguments.length === 1) { strict = true; } if (this.length !== array.length) { return false; } for (var i = 0; i < this.length; i++) { if (this[i] instanceof Array && array[i] instanceof Array) { if (!this[i].equals(array[i], strict)) { return false; } } else if (strict && (!this[i].equals(array[i]))) { return false; } else if (!strict) { return this.sort().equals(array.sort(), true); } } return true; }; /** * Allows all of the elements of one array to be added into another array. */ Array.prototype.addAll = function() { 'use strict'; for (var a = 0; a < arguments.length; a++) { var arr = arguments[a]; for (var i = 0; i < arr.length; i++) { this.push(arr[i]); } } }; /* * 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.asymmetric = cryptolib.modules.asymmetric || {}; /** * @namespace asymmetric/cipher */ cryptolib.modules.asymmetric.cipher = function(box) { 'use strict'; box.asymmetric = box.asymmetric || {}; box.asymmetric.cipher = {}; /** * A module that holds asymmetric cipher functionalities. * * @exports asymmetric/cipher/factory */ box.asymmetric.cipher.factory = {}; var policies = { cipher: { algorithm: box.policies.asymmetric.cipher.algorithm, secretKeyLengthBytes: box.policies.asymmetric.cipher.secretKeyLengthBytes, ivLengthBytes: box.policies.asymmetric.cipher.ivLengthBytes, tagLengthBytes: box.policies.asymmetric.cipher.tagLengthBytes, deriver: box.policies.asymmetric.cipher.deriver, hash: box.policies.asymmetric.cipher.hash, symmetricCipher: box.policies.asymmetric.cipher.symmetricCipher, provider: box.policies.asymmetric.cipher.provider } }; var utils, converters, exceptions, randomFactory; var f = function(box) { utils = box.commons.utils; converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.asymmetric.cipher.secureRandom.provider} } }; cryptolib('commons', 'primitives.securerandom', f); /** @class */ box.asymmetric.cipher.factory.AsymmetricCipherFactory = function() {}; box.asymmetric.cipher.factory.AsymmetricCipherFactory.prototype = { /** * @function Gets an asymmetric cipher. * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher} */ getCryptoAsymmetricCipher: function() { try { if (policies.cipher.provider === Config.asymmetric.cipher.provider.FORGE) { var secureRandomBytes = randomFactory.getCryptoRandomBytes(); return this.getCryptoForgeAsymmetricCipher(secureRandomBytes); } else { throw new exceptions.CryptoLibException( 'No suitable provider for the asymmetric cipher was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoAsymmetricCipher could not be obtained.', error); } }, /** * @function Gets a Forge asymmetric cipher. * @param secureRandom * {CryptoScytlRandomBytes} a source of random bytes. * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher} */ getCryptoForgeAsymmetricCipher: function(secureRandom) { return new CryptoForgeAsymmetricCipher(secureRandom); } }; /** * Defines a Forge asymmetric cipher * * @class * @param secureRandomBytes * {CryptoScytlRandomBytes} a source of random bytes. * @memberof asymmetric/cipher */ function CryptoForgeAsymmetricCipher(secureRandomBytes) { if (!secureRandomBytes) { throw new exceptions.CryptoLibException( 'The received PRNG was not valid'); } this.bitOperators = new utils.BitOperators(); this.secureRandomBytes = secureRandomBytes; // We would like to use the SCYTL PRNG directly in the ciphers that are // created. However it is currently not possible to pass a PRNG to the // Forge library when creating or using a cipher. However, in some cases, // it is possible to pass a seed (created using the SCYTL PRNG) to FORGE // that is then used to seed the FORGE internal PRNG. this.encodingSeedLength = 32; try { if ((policies.cipher.algorithm.name !== Config.asymmetric.cipher.algorithm.RSA_OAEP.name) && (policies.cipher.algorithm.name !== Config.asymmetric.cipher.algorithm.RSA_KEM.name)) { throw new exceptions.CryptoLibException( 'The specified algorithm is not supported.'); } } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeAsymmetricCipher could not be created.', error); } /** * Parses the four parts of the data that is produced by the encrypt * function, and that is received by the decrypt function. *

* We know the length of three of these parts, and we know the total * length of the data, therefore we can parse out all four of the parts. */ this._parseParts = function(privateKey, encryptedData) { var ivLengthBytes = policies.cipher.ivLengthBytes; var tagLengthBytes = policies.cipher.tagLengthBytes; var encapsulationLengthBytes = privateKey.n.bitLength() / 8; var totalLength = encryptedData.length; var totalKnownLength = encapsulationLengthBytes + ivLengthBytes + tagLengthBytes; var encryptedDataLength = totalLength - totalKnownLength; var startIndexOfSecondPart = encapsulationLengthBytes; var startIndexOfThirdPart = startIndexOfSecondPart + ivLengthBytes; var startIndexOfFourthPart = startIndexOfThirdPart + encryptedDataLength; var encapsulation = this.bitOperators.extract(encryptedData, 0, startIndexOfSecondPart); var iv = this.bitOperators.extract( encryptedData, startIndexOfSecondPart, startIndexOfThirdPart); var data = this.bitOperators.extract( encryptedData, startIndexOfThirdPart, startIndexOfFourthPart); var tag = this.bitOperators.extract(encryptedData, startIndexOfFourthPart); return {encapsulation: encapsulation, iv: iv, data: data, tag: tag}; }; this._validateInputs = function(keyPem, data) { if (!keyPem) { throw new exceptions.CryptoLibException( 'The received key was not initialized.'); } if (!data) { throw new exceptions.CryptoLibException( 'The received data was not initialized.'); } }; // Note: at the moment, the options that are being used with the RSA_OAEP // algorithm are hardcoded (for encrypting and decrypting). This could be // improved so that these are read from the properties file. Doing this will // mean any that existing properties files (used by consumers of the // library) will become invalid (if the consumer uses RSA_OAEP) as they wont // have the mandatory new properties. this._getRsaOaepHash = function() { return box.forge.md.sha256.create(); }; // this._getRsaOaepMaskHash = function() { // For interoperability purposes, the MGF1 hash function must // remain as SHA-1. return box.forge.md.sha1.create(); }; this._getRsaOaepEncodingOptions = function() { var encodingOptions = { md: this._getRsaOaepHash(), mgf1: {md: this._getRsaOaepMaskHash()}, seed: this.secureRandomBytes.nextRandom(this.encodingSeedLength) }; return encodingOptions; }; this._getRsaOaepDecodingOptions = function() { var decodingOptions = { md: this._getRsaOaepHash(), mgf1: {md: this._getRsaOaepMaskHash()} }; return decodingOptions; }; this._determineAndCreateHash = function(requestedHash) { if (requestedHash === Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.messagedigest .algorithm.SHA256) { return forge.md.sha256.create(); } else { throw new exceptions.CryptoLibException( 'Unsupported hash function specified.'); } }; this._determineAndCreateDeriver = function(requestedDeriver) { var hash = this._determineAndCreateHash(policies.cipher.hash); if (requestedDeriver === Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF1) { return new forge.kem.kdf1(hash); } else if ( requestedDeriver === Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.KDF2) { return new forge.kem.kdf2(hash); } else if ( requestedDeriver === Config.asymmetric.cipher.algorithm.RSA_KEM.deriver.name.MGF1) { return new forge.mgf.mgf1.create(hash); } else { throw new exceptions.CryptoLibException( 'Unsupported deriver function specified.'); } }; } CryptoForgeAsymmetricCipher.prototype = { /** * Encrypts some data. *

* If the algorithm is RSA-KEM, then the output from this function will * be the base64 encoding of the following data: *

* [Encapsulation][IV][Encrypted Data][Tag] * * @function * @param publicKeyPem * {string} public key, as string in PEM format. * @param dataBase64 * {string} data to be encrypted, as string in Base64 encoded * format. * @returns encrypted data, as string in Base 64 encoded format.. */ encrypt: function(publicKeyPem, dataBase64) { this._validateInputs(publicKeyPem, dataBase64); try { var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem); var data = converters.base64Decode(dataBase64); var output; if (policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_OAEP.name) { var encodingOptions = this._getRsaOaepEncodingOptions(); output = publicKey.encrypt( data, policies.cipher.algorithm.name, encodingOptions); } else if ( policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_KEM.name) { var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes; var ivLengthBytes = policies.cipher.ivLengthBytes; var symmetricCipher = policies.cipher.symmetricCipher; var deriver = this._determineAndCreateDeriver(policies.cipher.deriver); // generate and encapsulate secret key var kem = forge.kem.rsa.create(deriver); var result = kem.encrypt(publicKey, secretKeyLengthBytes); var iv = forge.random.getBytesSync(ivLengthBytes); var cipher = forge.cipher.createCipher(symmetricCipher, result.key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(data)); cipher.finish(); var encryptedData = cipher.output.getBytes(); var tag = cipher.mode.tag.getBytes(); output = result.encapsulation.toString() + iv.toString() + encryptedData.toString() + tag.toString(); } else { throw new exceptions.CryptoLibException( 'The specified algorithm is not supported.'); } return converters.base64Encode(output); } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeAsymmetricCipher, data could not be encrypted.', error); } }, /** * Decrypts some encrypted data. * * @function * @param privateKeyPem * {string} private key, as string in PEM format. * @param encryptedDataB64 * {string} encrypted data, as string in Base64 encoded * format. * @returns decrypted data, as string in Base64 encoded format. */ decrypt: function(privateKeyPem, encryptedDataBase64) { this._validateInputs(privateKeyPem, encryptedDataBase64); try { var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem); var encryptedData = converters.base64Decode(encryptedDataBase64); var decryptedData; if (policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_OAEP.name) { var decodingOptions = this._getRsaOaepDecodingOptions(); decryptedData = privateKey.decrypt( encryptedData, policies.cipher.algorithm.name, decodingOptions); } else if ( policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_KEM.name) { var symmetricCipher = policies.cipher.symmetricCipher; var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes; var encryptedDataParts = this._parseParts(privateKey, encryptedData); var deriver = this._determineAndCreateDeriver(policies.cipher.deriver); // decrypt encapsulated secret key var kem = forge.kem.rsa.create(deriver); var key = kem.decrypt( privateKey, encryptedDataParts.encapsulation, secretKeyLengthBytes); // decrypt some bytes var decipher = forge.cipher.createDecipher(symmetricCipher, key); decipher.start( {iv: encryptedDataParts.iv, tag: encryptedDataParts.tag}); decipher.update(forge.util.createBuffer(encryptedDataParts.data)); var result = decipher.finish(); // result will be false if there was a failure if (result) { decryptedData = decipher.output.getBytes(); } else { throw new exceptions.CryptoLibException( 'Error while decrypting data.'); } } else { throw new exceptions.CryptoLibException( 'The specified algorithm is not supported.'); } return converters.base64Encode(decryptedData); } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeAsymmetricCipher, data could not be decrypted.', error); } } }; }; /* * 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.asymmetric = cryptolib.modules.asymmetric || {}; /** @namespace asymmetric/keypair */ cryptolib.modules.asymmetric.keypair = function(box) { 'use strict'; box.asymmetric = box.asymmetric || {}; box.asymmetric.keypair = {}; /** * A module that holds certificates functionalities. * * @exports asymmetric/keypair/factory */ box.asymmetric.keypair.factory = {}; var policies = { encryption: { algorithm: box.policies.asymmetric.keypair.encryption.algorithm, provider: box.policies.asymmetric.keypair.encryption.provider, keyLength: box.policies.asymmetric.keypair.encryption.keyLength, publicExponent: box.policies.asymmetric.keypair.encryption.publicExponent } }; var exceptions, randomFactory; var f = function(box) { exceptions = box.commons.exceptions; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.asymmetric.keypair.secureRandom.provider} } }; cryptolib('commons.exceptions', 'primitives.securerandom', f); /** * A factory class for generating * {@link asymmetric.keypair.CryptoForgeKeyPairGenerator} objects. * * @class */ box.asymmetric.keypair.factory.KeyPairGeneratorFactory = function() {}; box.asymmetric.keypair.factory.KeyPairGeneratorFactory.prototype = { /** * @function * @returns CryptoForgeKeyPairGenerator */ getEncryptionCryptoKeyPairGenerator: function() { var secureRandom; try { if (policies.encryption.provider === Config.asymmetric.keypair.encryption.provider.FORGE) { secureRandom = randomFactory.getCryptoRandomBytes(); return this.getCryptoForgeKeyPairGenerator(secureRandom); } else { throw new exceptions.CryptoLibException( 'No suitable provider was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'An encryption CryptoKeyPairGenerator could not be obtained.', error); } }, getCryptoForgeKeyPairGenerator: function(secureRandom) { return new CryptoForgeKeyPairGenerator(secureRandom); } }; /** * Defines an encryption key pair generator for the FORGE provider. * * @class * @memberof asymmetric/keypair */ function CryptoForgeKeyPairGenerator(secureRandom) { this.secureRandom = secureRandom; } CryptoForgeKeyPairGenerator.prototype = { /** * Generates an encryption key pair. * * @returns {asymmetric/keypair.KeyPair} an encryption key pair. */ generate: function() { try { if (policies.encryption.algorithm === Config.asymmetric.keypair.encryption.algorithm.RSA) { var options = {prng: this.secureRandom}; var keys = forge.pki.rsa.generateKeyPair( policies.encryption.keyLength, policies.encryption.publicExponent, options); var forgePublicKey = keys.publicKey; var forgePrivateKey = keys.privateKey; var publicKeyPem = forge.pki.publicKeyToPem(forgePublicKey); var privateKeyPem = forge.pki.privateKeyToPem(forgePrivateKey); var publicKey = new PublicKey( policies.encryption.algorithm, forgePublicKey.n, forgePublicKey.e, publicKeyPem); var privateKey = new PrivateKey( policies.encryption.algorithm, forgePrivateKey.n, forgePrivateKey.e, forgePrivateKey.d, forgePrivateKey.p, forgePrivateKey.q, forgePrivateKey.dP, forgePrivateKey.dQ, forgePrivateKey.qInv, privateKeyPem); return new KeyPair(publicKey, privateKey); } else { throw new exceptions.CryptoLibException( 'The given algorithm is not supported.'); } } catch (error) { throw new exceptions.CryptoLibException( 'Encryption key pair could not be generated.', error); } } }; /** * Container class for an encryption key pair. * * @class * @param publicKey * public key of the encryption key pair. * @param privateKey * private key of the encryption key pair. * @memberof asymmetric/keypair */ function KeyPair(publicKey, privateKey) { /** * Retrieves the public key of the encryption key pair. * * @function * @returns PrivateKey Public key of encryption key pair. */ var getPublicKey = function() { return publicKey; }; /** * Retrieves the private key of the encryption key pair. * * @function * @returns PrivateKey Private key of encryption key pair. */ var getPrivateKey = function() { return privateKey; }; return {getPublicKey: getPublicKey, getPrivateKey: getPrivateKey}; } /** * @class PrivateKey * @memberof asymmetric/keypair */ function PrivateKey(algorithm, n, e, d, p, q, dP, dQ, qInv, pem) { /** * @function * @returns {string} * @memberof asymmetric/keypair.PrivateKey */ var getAlgorithm = function() { return algorithm; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getModulus = function() { return n; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getPublicExponent = function() { return e; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getPrivateExponent = function() { return d; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getPrime1 = function() { return p; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getPrime2 = function() { return q; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getExponent1 = function() { return dP; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getExponent2 = function() { return dQ; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getCoefficient = function() { return qInv; }; /** * @function * @memberof asymmetric/keypair.PrivateKey */ var getPem = function() { return pem; }; return { getAlgorithm: getAlgorithm, getModulus: getModulus, getPublicExponent: getPublicExponent, getPrivateExponent: getPrivateExponent, getPrime1: getPrime1, getPrime2: getPrime2, getExponent1: getExponent1, getExponent2: getExponent2, getCoefficient: getCoefficient, getPem: getPem }; } /** @class PublicKey */ function PublicKey(algorithm, n, e, pem) { /** * @function * @memberof asymmetric/keypair.PublicKey */ var getAlgorithm = function() { return algorithm; }; /** * @function * @memberof asymmetric/keypair.PublicKey */ var getModulus = function() { return n; }; /** * @function * @memberof asymmetric/keypair.PublicKey */ var getPublicExponent = function() { return e; }; /** * @function * @memberof asymmetric/keypair.PublicKey */ var getPem = function() { return pem; }; return { getAlgorithm: getAlgorithm, getModulus: getModulus, getPublicExponent: getPublicExponent, getPem: getPem }; } }; /* * 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.asymmetric = cryptolib.modules.asymmetric || {}; cryptolib.modules.asymmetric.service = function(box) { 'use strict'; box.asymmetric = box.asymmetric || {}; /** * A module that holds certificates functionalities. * @exports asymmetric/service */ box.asymmetric.service = {}; var keypairGeneratorFactory, signerFactory, xmlSignerFactory, cipherFactory; cryptolib( 'asymmetric.signer', 'asymmetric.xmlsigner', 'asymmetric.keypair', 'asymmetric.cipher', 'commons.exceptions', function(box) { keypairGeneratorFactory = new box.asymmetric.keypair.factory.KeyPairGeneratorFactory(); signerFactory = new box.asymmetric.signer.factory.SignerFactory(); xmlSignerFactory = new box.asymmetric.xmlsigner.factory.XmlSignerFactory(); cipherFactory = new box.asymmetric.cipher.factory.AsymmetricCipherFactory(); }); /** * Generates a {@link asymmetric/keypair.KeyPair} to be used for encrypt data. * @function * @returns {asymmetric/keypair.KeyPair} it can be used for encrypt data. */ box.asymmetric.service.getKeyPairForEncryption = function() { var cryptoKeyPairGenerator = keypairGeneratorFactory.getEncryptionCryptoKeyPairGenerator(); return cryptoKeyPairGenerator.generate(); }; /** * Encrypts the given plain text using the given * public key. * @function * @param publicKeyPem * {String} public key, as string in PEM format. * @param dataBase64 * {String} data to encrypt, as a string in Base64 encoded * format. * @return encrypted data in Base64 encoded format. */ box.asymmetric.service.encrypt = function(publicKeyPem, dataBase64) { var cipher = cipherFactory.getCryptoAsymmetricCipher(); return cipher.encrypt(publicKeyPem, dataBase64); }; /** * Decrypts the given cipher text with the given * private key. * @function * @param privateKeyPem * {String} private key, as string in PEM format. * @param encryptedDataBase64 * {String} data to decrypt, as a string in Base64 encoded * format. * @return a string in Base64 encoded format. */ box.asymmetric.service.decrypt = function( privateKeyPem, encryptedDataBase64) { var cipher = cipherFactory.getCryptoAsymmetricCipher(); return cipher.decrypt(privateKeyPem, encryptedDataBase64); }; /** * Signs the given message using the given private key. * @function * @param privateKeyPem * {String} private key, as string in PEM format. * @param arrayDataBase64 * {String} data to sign, as an array of string in Base64 encoded * format. * @return signature, in Base64 encoded format. */ box.asymmetric.service.sign = function(privateKeyPem, ArrayDataBase64) { var cryptoSigner = signerFactory.getCryptoSigner(); return cryptoSigner.sign(privateKeyPem, ArrayDataBase64); }; /** * Verifies that the given signature is indeed the signature of the given * bytes, using the given public key. * @function * @param signatureB64 * {String} signature, as string in Base64 encoded format. * @param publicKeyPem * {String} public key, as string in PEM format. * @param arrayDataBase64 * {String} data that was signed, as an array of string in Base64 * encoded format. * * @return boolean indicating whether signature verification was successful. */ box.asymmetric.service.verifySignature = function( signatureBase64, publicKeyPem, arrayDataBase64) { var cryptoSigner = signerFactory.getCryptoSigner(); return cryptoSigner.verify(signatureBase64, publicKeyPem, arrayDataBase64); }; /** * Verifies the signature of some data that is in XML format. * @function * @param publicKey * {Object} Public key. * @param signedXml * {string} XML with selfcontained signature, to verify * @param signatureParentNode * {string} Node where the signature is * * @return boolean indicating whether signature verification was successful. */ box.asymmetric.service.verifyXmlSignature = function( publicKey, signedXml, signatureParentNode) { var cryptoXmlSigner = xmlSignerFactory.getCryptoXmlSigner(); return cryptoXmlSigner.verify(publicKey, signedXml, signatureParentNode); }; }; /* * 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.asymmetric = cryptolib.modules.asymmetric || {}; /** @namespace asymmetric/signer */ cryptolib.modules.asymmetric.signer = function(box) { 'use strict'; box.asymmetric = box.asymmetric || {}; box.asymmetric.signer = {}; /** * A module that holds asymmetric signature functionalities. * * @exports asymmetric/signer/factory */ box.asymmetric.signer.factory = {}; var policies = { algorithm: box.policies.asymmetric.signer.algorithm, provider: box.policies.asymmetric.signer.provider, hash: box.policies.asymmetric.signer.hash, padding: box.policies.asymmetric.signer.padding, publicExponent: box.policies.asymmetric.signer.publicExponent }; var converters, exceptions, randomFactory; var f = function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.asymmetric.signer.secureRandom.provider} } }; cryptolib('commons', 'primitives.securerandom', f); /** * A factory class for creating a digital signer. * * @class */ box.asymmetric.signer.factory.SignerFactory = function() {}; box.asymmetric.signer.factory.SignerFactory.prototype = { getCryptoSigner: function() { try { if (policies.provider === Config.asymmetric.signer.provider.FORGE) { var secureRandom = randomFactory.getCryptoRandomBytes(); return this.getCryptoForgeSigner(secureRandom); } else { throw new exceptions.CryptoLibException( 'No suitable provider for the signer was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoSigner could not be obtained.', error); } }, /** * @function * @return {asymmetric/signer.CryptoForgeSigner} */ getCryptoForgeSigner: function(secureRandom) { return new CryptoForgeSigner(secureRandom); } }; /** * A digital signer. * * @class * @memberof asymmetric/signer * @param secureRandom */ function CryptoForgeSigner(secureRandom) { this.digester = null; this.secureRandom = secureRandom; try { if (policies.algorithm !== Config.asymmetric.signer.algorithm.RSA) { throw new exceptions.CryptoLibException( 'The given algorithm is not supported'); } if (policies.hash === Config.asymmetric.signer.hash.SHA256) { this.digester = forge.md.sha256.create(); } else { var errorMessage = 'Message digester type \'' + policies.hash + '\' not recognized by signer.'; throw new exceptions.CryptoLibException(errorMessage); } if (typeof(policies.padding) !== 'undefined' && policies.padding.PSS.name === Config.asymmetric.signer.padding.PSS.name) { var paddingHash; if (policies.padding.PSS.hash === Config.asymmetric.signer.padding.PSS.hash.SHA256) { paddingHash = forge.md.sha256.create(); } var paddingMgf; if (policies.padding.PSS.mask.MGF1.name === Config.asymmetric.signer.padding.PSS.mask.MGF1.name && policies.padding.PSS.mask.MGF1.hash === Config.asymmetric.signer.padding.PSS.mask.MGF1.hash.SHA256) { var mgfHash = forge.md.sha256.create(); paddingMgf = forge.mgf.mgf1.create(mgfHash); } this.padding = forge.pss.create({ md: paddingHash, mgf: paddingMgf, saltLength: policies.padding.PSS.saltLengthBytes, prng: this.secureRandom }); } } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeSigner could not be created.', error); } } CryptoForgeSigner.prototype = { /** * Digitally signs some data. * * @function * @param privateKeyPem * Private key, as string in PEM format. * @param arrayDataBase64 * Data to sign, as an array of string in Base64 encoded * format. * @returns signature, in Base64 encoded format. */ sign: function(privateKeyPem, arrayDataBase64) { try { var privateKey = forge.pki.privateKeyFromPem(privateKeyPem); if (privateKey.e.toString() !== policies.publicExponent.toString()) { throw new exceptions.CryptoLibException( 'Private key has not been generated with the required exponent : ' + policies.publicExponent); } if (!arrayDataBase64 || arrayDataBase64.length < 1) { throw new exceptions.CryptoLibException( 'The array of data in Base64 should contain at least one element.'); } this.digester.start(); var dataB64; var data; for (var i = 0; i < arrayDataBase64.length; i++) { dataB64 = arrayDataBase64[i]; if (dataB64 === undefined || dataB64 === null) { throw new exceptions.CryptoLibException( 'The input data contained an element that was not defined or empty.'); } data = converters.base64Decode(dataB64); this.digester.update(data); } var signature = privateKey.sign(this.digester, this.padding); return converters.base64Encode(signature); } catch (error) { throw new exceptions.CryptoLibException( 'Signature could not be generated.', error); } }, /** * Verifies the digital signature of some data. * * @function * * @param signatureB64 * Signature, as string in Base64 encoded format. * @param publicKeyPem * Public key, as string in PEM format. * @param arrayDataBase64 * Data that was signed, as an array of string in Base64 * encoded format. * * @returns boolean indicating whether signature verification was * successful. */ verify: function(signatureB64, publicKeyPem, arrayDataBase64) { try { var publicKey = forge.pki.publicKeyFromPem(publicKeyPem); if (publicKey.e.toString() !== policies.publicExponent.toString()) { throw new exceptions.CryptoLibException( 'Public key has not been generated with the required exponent : ' + policies.publicExponent); } if (!arrayDataBase64 || arrayDataBase64.length < 1) { throw new exceptions.CryptoLibException( 'The array of data in Base64 should contain at least one element.'); } this.digester.start(); var dataB64; for (var i = 0; i < arrayDataBase64.length; i++) { dataB64 = arrayDataBase64[i]; if (dataB64 === undefined || dataB64 === null) { throw new exceptions.CryptoLibException( 'The input data contained an element that was not defined or empty.'); } var data = converters.base64Decode(dataB64); this.digester.update(data); } var signature = converters.base64Decode(signatureB64); return publicKey.verify( this.digester.digest().getBytes(), signature, this.padding); } catch (error) { throw new exceptions.CryptoLibException( 'Signature could not be verified.', error); } } }; }; /* * 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.asymmetric = cryptolib.modules.asymmetric || {}; /** * Defines the asymmetric signers. * * @namespace asymmetric/xmlsigner */ cryptolib.modules.asymmetric.xmlsigner = function(box) { 'use strict'; box.asymmetric = box.asymmetric || {}; box.asymmetric.xmlsigner = {}; /** * A module that holds certificates functionalities. * * @exports asymmetric/xmlsigner/factory */ box.asymmetric.xmlsigner.factory = {}; var policies = { algorithm: box.policies.asymmetric.signer.algorithm, provider: box.policies.asymmetric.signer.provider, hash: box.policies.asymmetric.signer.hash }; var utils, exceptions, converters, parsers, stringUtils; var f = function(box) { exceptions = box.commons.exceptions; utils = box.commons.utils; converters = new box.commons.utils.Converters(); parsers = new utils.Parsers(); stringUtils = new utils.StringUtils(); }; cryptolib('commons', f); /** * A factory class for creating an XML digital signature verifier. * * @class */ box.asymmetric.xmlsigner.factory.XmlSignerFactory = function() {}; box.asymmetric.xmlsigner.factory.XmlSignerFactory.prototype = { /** * Gets a {@link asymmetric/xmlsigner.CryptoForgeXmlSigner}. * * @function * @return {asymmetric/xmlsigner.CryptoForgeXmlSigner} */ getCryptoXmlSigner: function() { try { if (policies.provider === Config.asymmetric.signer.provider.FORGE) { return new CryptoForgeXmlSigner(); } else { throw new exceptions.CryptoLibException( 'No suitable provider for the signer was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoXmlSigner could not be obtained.', error); } } }; /** * Holds xml signature properties. * * @class * @memberof asymmetric/xmlsigner */ function XmlSignature(signatureNode) { // go through all the nodes to load all relevant information var signedInfoNode; var signatureValueNode; var canonicalizationMethodNode; var signatureMethodNode; var referenceNode; var digestMethodNode; var digestValueNode; var transformsNode; try { signedInfoNode = new utils.XPath(signatureNode.getXml(), 'SignedInfo'); signatureValueNode = new utils.XPath(signatureNode.getXml(), 'SignatureValue'); canonicalizationMethodNode = new utils.XPath(signedInfoNode.getXml(), 'CanonicalizationMethod'); signatureMethodNode = new utils.XPath(signedInfoNode.getXml(), 'SignatureMethod'); referenceNode = new utils.XPath(signedInfoNode.getXml(), 'Reference'); digestMethodNode = new utils.XPath(referenceNode.getXml(), 'DigestMethod'); digestValueNode = new utils.XPath(referenceNode.getXml(), 'DigestValue'); transformsNode = new utils.XPath(referenceNode.getXml(), 'Transforms'); } catch (error) { throw new exceptions.CryptoLibException( 'Could not parse signed XML file', error); } // there are signatures without key, so we ignore them var rsaKeyValueNode; var modulusNode; var exponentNode; try { rsaKeyValueNode = new utils.XPath( signatureNode.getXml(), 'KeyInfo/KeyValue/RSAKeyValue'); modulusNode = new utils.XPath(rsaKeyValueNode.getXml(), 'Modulus'); exponentNode = new utils.XPath(rsaKeyValueNode.getXml(), 'Exponent'); } catch (error) { // ignore if this part fails } // build the object structure /** @property {object} */ this.info = { method: signatureMethodNode.getAttribute('Algorithm'), value: signatureValueNode.getValue(), meta: signatureNode.getAttribute('xmlns') }; /** @property {object} */ this.canonicalization = { method: canonicalizationMethodNode.getAttribute('Algorithm') }; /** @property {object} */ this.reference = { uri: referenceNode.getAttribute('URI'), transforms: [], digest: { method: digestMethodNode.getAttribute('Algorithm'), value: digestValueNode.getValue() } }; /** @property {object} */ this.rsakey = { modulus: modulusNode ? modulusNode.getValue() : '', exponent: exponentNode ? exponentNode.getValue() : '' }; // update the transforms references var transforms = transformsNode.getChildren(); for (var i = 0; i < transforms.length; i++) { if (transforms[i].nodeType === Node.ELEMENT_NODE) { var transformNode = new utils.XPath(transforms[i]); this.reference.transforms.push(transformNode.getAttribute('Algorithm')); } } } /** * @class * @memberof asymmetric/xmlsigner */ function ExclusiveCanonicalization() {} /** * Sorts the attributes * * @function * @param xmlNode * xml node, as a DOM. */ ExclusiveCanonicalization.prototype.sortAttributes = function(xmlNode) { // gather all attributes and remove them var list = []; var attr; if (xmlNode.attributes) { for (var i = 0; i < xmlNode.attributes.length; i++) { attr = xmlNode.attributes[i]; list.push({id: attr.name, val: xmlNode.getAttribute(attr.name)}); xmlNode.removeAttribute(attr.name); i--; } } // sort the attributes list.sort(function(a, b) { if (a.id < b.id) { return -1; } if (a.id > b.id) { return 1; } return 0; }); // reinsert the attributes for (var j = 0; j < list.length; j++) { attr = list[j]; xmlNode.setAttribute(attr.id, attr.val); } }; /** * Inits the process of canonicalization. * * @function * @param xmlNode * xml node, as a DOM. * * @return xml node processed, as DOM. */ ExclusiveCanonicalization.prototype.process = function(xml) { // traverse the tree through all the children for (var i = 0; i < xml.childNodes.length; i++) { var child = xml.childNodes[i]; if (child.nodeType === Node.ELEMENT_NODE) { this.process(child); } // if are comments or other stuff, remove them else if (child.nodeType !== Node.TEXT_NODE) { child.parentNode.removeChild(child); --i; } } // sort the attributes this.sortAttributes(xml); // return the final object return xml; }; /** * An xml signer. * * @class * @memberof asymmetric/xmlsigner */ function CryptoForgeXmlSigner() {} CryptoForgeXmlSigner.prototype = { /** * Verifies the digital signature of a selfsigned xml. * * @function * @param publicKey * Public key, as an object. * @param signedXml * XML data, as a string * @param signatureParentNode * The node where the signature is, as a string. * * @returns Boolean indicating whether signature verification was * successful. */ verify: function(publicKey, signedXml, signatureParentNode) { // clean the comments in the xml signedXml = this._removeXmlStringComments(signedXml); // loads the text to an xml var xml = parsers.xmlFromString(signedXml); // loads the xml signature structure to json var signature = this._loadSignature(xml, signatureParentNode); // check all the algorithms are the expected ones this._verifyAlgorithmsInSignature(signature); // check the public key is the expected one this._verifyPublicKeyInSignature(signature, publicKey); // check the references are well encoded this._validateReferences(signature, signedXml); // validates the signatures are right this._validateSignature(signature, publicKey); // if not exception raised, all went good return true; }, /** * Gets the XML signature node from the main XML node * * @function * @private * @param xml * XML where the signature is, as a Document Object * @param signatureParentNode * The node where the signature is, as a string. * * @returns Object with the DOM node of the signature and and the * signature in json. */ _loadSignature: function(xml, signatureParentNode) { // access to the node where the signature is, and take the methods try { // get the signature node var xmlNode = new utils.XPath(xml, signatureParentNode); // create signature object to load the data from the signature var data = new XmlSignature(xmlNode); // return the main object return {xmlNode: xmlNode, data: data}; } catch (error) { throw new exceptions.CryptoLibException( 'Could not load the signature from the XML', error); } }, /** * Verifies that the method algorithms in the EML and the properties * match. * * @function * @private * @param signature * Signature, as a Javascript Object */ _verifyAlgorithmsInSignature: function(signature) { try { if (signature.data.canonicalization.method !== 'http://www.w3.org/2001/10/xml-exc-c14n#') { throw new exceptions.CryptoLibException( 'Invalid canonicalization algorithm'); } if (!stringUtils.containsSubString( signature.data.info.method, policies.algorithm) && !stringUtils.containsSubString( signature.data.info.method, policies.hash)) { throw new exceptions.CryptoLibException( 'Invalid signature algorithm'); } if (!stringUtils.containsSubString( signature.data.reference.digest.method, policies.hash)) { throw new exceptions.CryptoLibException('Invalid digest algorithm'); } } catch (error) { throw new exceptions.CryptoLibException( 'Could not verify the data in the XML.', error); } }, /** * Verifies the public key is the same than the one in the EML. * * @function * @private * @param signature * Signature, as a Javascript Object * @param publicKey * Public key, as an object. */ _verifyPublicKeyInSignature: function(signature, publicKey) { // it has no key if (!signature.data.rsakey.exponent || !signature.data.rsakey.modulus) { return; } // if it has key, we validate it try { var signatureExponent = converters.base64ToBigInteger(signature.data.rsakey.exponent); var signatureModulus = converters.base64ToBigInteger(signature.data.rsakey.modulus); if (publicKey.e.compareTo(signatureExponent) !== 0 || publicKey.n.compareTo(signatureModulus) !== 0) { throw new exceptions.CryptoLibException('Invalid public key'); } } catch (error) { throw new exceptions.CryptoLibException( 'Could not verify the data in the XML.', error); } }, /** * Verifies the references of a selfsigned xml. * * @function * @private * @param signature * Signature, as a Javascript Object */ _validateReferences: function(signature, xmlString) { try { var xmlCanonString; // The canonicalization transformations are performed here. // Due to differences in the behaviour of Internet Explorer, // these transformations must be applied manually in the case // of that browser. For all other browsers, the transformations // are performed by applying a canonicalization algorithm. if (parsers.isIE()) { xmlCanonString = this._removeXmlStringHeader(xmlString); xmlCanonString = this._removeXmlStringSelfClosingTags(xmlCanonString); xmlCanonString = this._removeXmlStringSignature(xmlCanonString); xmlCanonString = this._removeXmlStringFirstCharacter(xmlCanonString); } else { var xmlCanon = null; // apply all transforms to the xml for (var i = 0; i < signature.data.reference.transforms.length; i++) { // get the algorithm name var algorithm = signature.data.reference.transforms[i]; // consume it if it exists if (algorithm in this._transformAlgorithmMap) { xmlCanon = this._transformAlgorithmMap[algorithm]( signature.xmlNode.getXml()); } } xmlCanon = this._removeXmlHeader(xmlCanon); xmlCanonString = parsers.xmlToString(xmlCanon, true); } var digester = this._getHashMethod(signature.data.reference.digest.method); digester.start(); digester.update( parsers.removeCarriageReturnChars(xmlCanonString), 'utf8'); var messageDigest = digester.digest(); // check if the result is valid var encodedMessage = forge.util.encode64(messageDigest.getBytes()); if (encodedMessage !== signature.data.reference.digest.value) { throw new exceptions.CryptoLibException( 'Invalid reference: \'' + encodedMessage + '\' when was expected \'' + signature.data.reference.digest.value + '\''); } } catch (error) { throw new exceptions.CryptoLibException( 'Could not validate references.', error); } }, /** * Verifies the digital signature of a selfsigned xml. * * @function * @private * @param signature * Signature, as a Javascript Object * @param publicKey * Public key, as an object. */ _validateSignature: function(signature, publicKey) { try { var signedInfoNode = new utils.XPath(signature.xmlNode.getXml(), '/SignedInfo'); var algorithm = signature.data.canonicalization.method; var xmlCanon = null; if (algorithm in this._transformAlgorithmMap) { xmlCanon = this._transformAlgorithmMap[algorithm](signedInfoNode.getXml()); } var digester = this._getHashMethod(signature.data.info.method); digester.start(); digester.update(parsers.xmlToString(xmlCanon, true), 'utf8'); var signatureValue = forge.util.decode64(signature.data.info.value); var result = publicKey.verify(digester.digest().getBytes(), signatureValue); if (!result) { throw new exceptions.CryptoLibException( 'Could not validate the signature.'); } } catch (error) { throw new exceptions.CryptoLibException( 'Could not validate signatures.', error); } }, /** * Creates the hash method * * @function * @private * @param method * method type, as string. * * @return Object, the disgester of that method. */ _getHashMethod: function(method) { if (stringUtils.containsSubString(method, 'sha256')) { return forge.md.sha256.create(); } }, /** * Map with all tarnsform algorithms * * @function * @private */ _transformAlgorithmMap: { /** * Removes the signature from a signed XML * * @param xmlSignature * the xml signature, as DOM. * * @return the XML top document, as DOM */ 'http://www.w3.org/2000/09/xmldsig#enveloped-signature': function( xmlSignature) { var topElement = xmlSignature; // get the top document while (topElement.parentNode) { topElement = topElement.parentNode; } // remove the signature from the node xmlSignature.parentNode.removeChild(xmlSignature); return topElement; }, /** * c14n the XML * * @param xmlNode * the xml node, as DOM. * * @return the XML top document, as DOM */ 'http://www.w3.org/2001/10/xml-exc-c14n#': function(xmlNode) { var transform = new ExclusiveCanonicalization(); return transform.process(xmlNode); } }, /** * Removes the comments in the xml * * @function * @private * @param xml * the xml, as string. * * @return {string} the xml without comments */ _removeXmlStringComments: function(xml) { return xml.replace(//g, ''); }, /** * Removes the header in the xml structure. * * @function * @private * @param {object} * xmlDocument the xml, as DOM. * * @returns {object} the xml, as DOM without the header */ _removeXmlHeader: function(xmlDocument) { return xmlDocument.documentElement ? xmlDocument.documentElement : xmlDocument; }, /** * Removes the header in the XML string. * * @param xmlString * the xml, as a string. * * @return the xml string without the header */ _removeXmlStringHeader: function(xmlString) { return parsers.removeXmlHeaderFromString(xmlString); }, /** * Removes the signature node in the XML string. * * @param xmlString * the xml, as a string. * * @return the xml string without the signature tag */ _removeXmlStringSignature: function(xmlString) { return parsers.removeXmlSignature(xmlString); }, /** * Converts any self-closing tags to opening and closing tags, within an * XML string. * * @param xmlString * the xml, as a string. * * @return the xml string with self-closing tags converted to opening * and closing tags */ _removeXmlStringSelfClosingTags: function(xmlString) { return parsers.xmlRemoveSelfClosingTags(xmlString); }, /** * Removes the first character from an XML string if that character is * not the expected initial character. * * @param xmlString * the xml, as a string. * * @return the xml string with the initial character removed if it is * not wanted */ _removeXmlStringFirstCharacter: function(xmlString) { if (xmlString[0] !== '<') { return xmlString.slice(1, xmlString.length); } else { return xmlString; } } }; }; /* * 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 */ /** @namespace certificates */ cryptolib.modules.certificates = function(box) { 'use strict'; if (box.certificates) { return; } box.certificates = {}; /** * Provides an API that can be used to perform operations with certificates. * * @exports certificates/service */ box.certificates.service = {}; var converters; var exceptions; cryptolib('commons', function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; }); /** * Loads a certificate in pem format. * * @function * @param {string} * certificatePem certificate as string in PEM format. * @returns {certificates.box.certificates.CryptoX509Certificate} the * certificate. */ box.certificates.service.load = function(certificatePem) { return new box.certificates.CryptoX509Certificate(certificatePem); }; /** * Provides methods to access X509 certificate data. * * @class * @param certificatePem * certificate as string in PEM format. * @memberof certificates */ box.certificates.CryptoX509Certificate = function(certificatePem) { /** @property {object} certificate the certificate. * */ this.certificate = forge.pki.certificateFromPem(certificatePem, true); }; box.certificates.CryptoX509Certificate.prototype = { /** * Retrieves the public key of the certificate. * * @function * @returns public key, as string in PEM format. * @memberof certificates.box.certificates.CryptoX509Certificate */ getPublicKey: function() { var publicKey = this.certificate.publicKey; if (publicKey !== null) { return forge.pki.publicKeyToPem(publicKey); } else { throw new exceptions.CryptoLibException( 'Could not find public key in certificate.'); } }, /** * Retrieves the start time of the certificate's validity. * * @function * @returns start time of certificate validity, as Date object. * @memberof certificates.box.certificates.CryptoX509Certificate */ getNotBefore: function() { var notBeforeField = this.certificate.validity.notBefore; if (notBeforeField !== null) { return notBeforeField; } else { throw new exceptions.CryptoLibException( 'Could not find validity start time in certificate.'); } }, /** * Retrieves the end time of the certificate's validity. * * @function * @returns end time of certificate validity, as Date object. * @memberof certificates.box.certificates.CryptoX509Certificate */ getNotAfter: function() { var notAfterField = this.certificate.validity.notAfter; if (notAfterField !== null) { return notAfterField; } else { throw new exceptions.CryptoLibException( 'Could not find validity end time in certificate.'); } }, /** * Retrieves the serial number of the certificate. * * @function * @returns serial number of certificate, as hexadecimal string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSerialNumber: function() { var serialNumber = this.certificate.serialNumber; if (serialNumber !== null) { return serialNumber; } else { return ''; } }, /** * Retrieves the issuer common name of the certificate. * * @function * @returns issuer common name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getIssuerCN: function() { var issuerCNField = this.certificate.issuer.getField({name: 'commonName'}); if (issuerCNField !== null) { return issuerCNField.value; } else { return ''; } }, /** * Retrieves the issuer organizational unit of the certificate. * * @function * @returns issuer organizational unit of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getIssuerOrgUnit: function() { var orgUnitField = this.certificate.issuer.getField({shortName: 'OU'}); if (orgUnitField !== null) { return orgUnitField.value; } else { return ''; } }, /** * Retrieves the issuer organization name of the certificate. * * @function * @returns Issuer organization name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getIssuerOrg: function() { var orgNameField = this.certificate.issuer.getField({name: 'organizationName'}); if (orgNameField !== null) { return orgNameField.value; } else { return ''; } }, /** * Retrieves the issuer locality name of the certificate. * * @function * @returns Locality name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getIssuerLocality: function() { var localityField = this.certificate.issuer.getField({name: 'localityName'}); if (localityField !== null) { return localityField.value; } else { return ''; } }, /** * Retrieves the issuer country name of the certificate. * * @function * @returns Issuer country name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getIssuerCountry: function() { var countryField = this.certificate.issuer.getField({name: 'countryName'}); if (countryField !== null) { return countryField.value; } else { return ''; } }, /** * Retrieves the subject common name of the certificate. * * @function * @returns Subject common name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSubjectCN: function() { var subjectCNField = this.certificate.subject.getField({name: 'commonName'}); if (subjectCNField !== null) { return subjectCNField.value; } else { return ''; } }, /** * Retrieves the subject organizational unit of the certificate. * * @function * @returns Subject organizational unit of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSubjectOrgUnit: function() { var orgUnitField = this.certificate.subject.getField({shortName: 'OU'}); if (orgUnitField !== null) { return orgUnitField.value; } else { return ''; } }, /** * Retrieves the subject organization name of the certificate. * * @function * @return Subject organization name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSubjectOrg: function() { var organizationNameField = this.certificate.subject.getField({name: 'organizationName'}); if (organizationNameField !== null) { return organizationNameField.value; } else { return ''; } }, /** * Retrieves the subject locality name of the certificate. * * @function * @returns Subject locality name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSubjectLocality: function() { var localityField = this.certificate.subject.getField({name: 'localityName'}); if (localityField !== null) { return localityField.value; } else { return ''; } }, /** * Retrieves the subject country name of the certificate. * * @function * @returns Subject country name of certificate, as string. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSubjectCountry: function() { var countryField = this.certificate.subject.getField({name: 'countryName'}); if (countryField !== null) { return countryField.value; } else { return ''; } }, /** * Retrieves the key usage extension of the certificate. * * @function * @return KeyUsageExtension key usage extension, as KeyUsageExtension * object. * @memberof certificates.box.certificates.CryptoX509Certificate */ getKeyUsageExtension: function() { var keyUsageExt = this.certificate.getExtension({name: 'keyUsage'}); if (keyUsageExt !== null) { var keyUsageMap = {}; keyUsageMap.digitalSignature = keyUsageExt.digitalSignature; keyUsageMap.nonRepudiation = keyUsageExt.nonRepudiation; keyUsageMap.keyEncipherment = keyUsageExt.keyEncipherment; keyUsageMap.dataEncipherment = keyUsageExt.dataEncipherment; keyUsageMap.keyAgreement = keyUsageExt.keyAgreement; keyUsageMap.keyCertSign = keyUsageExt.keyCertSign; keyUsageMap.crlSign = keyUsageExt.cRLSign; keyUsageMap.encipherOnly = keyUsageExt.encipherOnly; keyUsageMap.decipherOnly = keyUsageExt.decipherOnly; return new box.certificates.KeyUsageExtension(keyUsageMap); } else { return null; } }, /** * Retrieves the basic constraints of the certificate. * * @function * @returns map containing the basic constraints. * @memberof certificates.box.certificates.CryptoX509Certificate */ getBasicConstraints: function() { var basicConstraints = this.certificate.getExtension({name: 'basicConstraints'}); if (basicConstraints !== null) { var basicConstraintsMap = {}; basicConstraintsMap.ca = basicConstraints.cA; return basicConstraintsMap; } else { return null; } }, /** * Retrieves the digital signature of the certificate. * * @function * @returns digital signature of certificate, as string in Base64 * encoded format. * @memberof certificates.box.certificates.CryptoX509Certificate */ getSignature: function() { var signature = this.certificate.signature; if (signature !== null) { var signatureB64 = converters.base64Encode(signature, box.RSA_LINE_LENGTH); if (signatureB64 !== null) { return signatureB64; } else { throw new exceptions.CryptoLibException( 'Base64 encoding of signature is null.'); } } else { throw new exceptions.CryptoLibException('Signature is null.'); } }, /** * Verifies the digital signature of the certificate provided as input, * using this certificate's public key. * * @function * @param certificatePem * certificate whose signature is to be verified, as string * in PEM format. * @returns boolean indicating whether signature was verified. * @memberof certificates.box.certificates.CryptoX509Certificate */ verify: function(certificatePem) { var certificate = forge.pki.certificateFromPem(certificatePem); if (certificate !== null) { return this.certificate.verify(certificate); } else { throw new exceptions.CryptoLibException('Certificate is null.'); } }, /** * Retrieves the certificate, in PEM format. * * @function * @returns certificate, as string in PEM format * @memberof certificates.box.certificates.CryptoX509Certificate */ toPem: function() { if (this.certificate !== null) { return forge.pki.certificateToPem( this.certificate, box.RSA_LINE_LENGTH); } else { throw new exceptions.CryptoLibException('Certificate is null.'); } } }; /** * Container class for the key usage extension flags of a digital * certificate. * * @class * @param keyUsageMap * map containing name-value pairs of key usage extension flags. * @memberof certificates */ box.certificates.KeyUsageExtension = function(keyUsageMap) { this.keyUsageMap = keyUsageMap; }; box.certificates.KeyUsageExtension.prototype = { /** * Retrieves the digital signature flag of the key usage extension. * * @function * @returns boolean indicating whether digital signature flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ digitalSignature: function() { return this.keyUsageMap.digitalSignature; }, /** * Retrieves the non-repudiation flag of the key usage extension. * * @function * @return boolean indicating whether non-repudiation flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ nonRepudiation: function() { return this.keyUsageMap.nonRepudiation; }, /** * Retrieves the key encipherment flag of the key usage extension. * * @function * @returns boolean indicating whether key encipherment flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ keyEncipherment: function() { return this.keyUsageMap.keyEncipherment; }, /** * Retrieves the data encipherment flag of the key usage extension. * * @function * @returns boolean indicating whether data encipherment flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ dataEncipherment: function() { return this.keyUsageMap.dataEncipherment; }, /** * Retrieves the key agreement flag of the key usage extension. * * @function * @returns boolean indicating whether key agreement flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ keyAgreement: function() { return this.keyUsageMap.keyAgreement; }, /** * Retrieves the key certificate sign flag of the key usage extension. * * @function * @returns Boolean indicating whether key certificate sign flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ keyCertSign: function() { return this.keyUsageMap.keyCertSign; }, /** * Retrieves the CRL sign flag of the key usage extension. * * @function * @returns boolean indicating whether CRL sign flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ crlSign: function() { return this.keyUsageMap.crlSign; }, /** * Retrieves the encipher only flag of the key usage extension. * * @function * @returns boolean indicating whether encipher only flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ encipherOnly: function() { return this.keyUsageMap.encipherOnly; }, /** * Retrieves the decipher only flag of the key usage extension. * * @function * @returns boolean indicating whether decipher only flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ decipherOnly: function() { return this.keyUsageMap.decipherOnly; }, /** * Retrieves the CA flag of the key usage extension. * * @function * @return boolean indicating whether CA flag is set. * @memberof certificates.box.certificates.KeyUsageExtension */ ca: function() { return this.keyUsageMap.ca; } }; /** * Class that validates the content of a CryptoX509Certificate. * * @class * @param validationData * @param {string} * validationData.subject * @param {string} * validationData.subject.commonName * @param {string} * validationData.subject.organization * @param {string} * validationData.subject.organizationUnit * @param {string} * validationData.subject.country * @param {string} * validationData.issuer * @param {string} * validationData.issuer.commonName * @param {string} * validationData.issuer.organization * @param {string} * validationData.issuer.organizationUnit * @param {string} * validationData.issuer.country * @param {string} * validationData.time * @param {string} * validationData.keytype CA, Sign or Encryption * @param {string} * validationData.caCertificatePem * @memberof certificates */ box.certificates.CryptoX509CertificateValidator = function(validationData) { var parseIsoDate = function(isoDate) { var dateChunks = isoDate.split(/\D/); return new Date(Date.UTC( +dateChunks[0], --dateChunks[1], +dateChunks[2], +dateChunks[5], +dateChunks[6], +dateChunks[7], 0)); }; /** * Validates the content of the X.509 certificate. * * @function * @param certificatePem * certificate as string in PEM format. * @return an array containing the errors type, if any. */ this.validate = function(x509CertificatePem) { var cryptoX509Certificate = new box.certificates.CryptoX509Certificate(x509CertificatePem); var failedValidations = []; var validateSubject = function(subject) { if (subject.commonName !== cryptoX509Certificate.getSubjectCN() || subject.organization !== cryptoX509Certificate.getSubjectOrg() || subject.organizationUnit !== cryptoX509Certificate.getSubjectOrgUnit() || subject.country !== cryptoX509Certificate.getSubjectCountry()) { failedValidations.push('SUBJECT'); } }; var validateIssuer = function(issuer) { if (issuer.commonName !== cryptoX509Certificate.getIssuerCN() || issuer.organization !== cryptoX509Certificate.getIssuerOrg() || issuer.organizationUnit !== cryptoX509Certificate.getIssuerOrgUnit() || issuer.country !== cryptoX509Certificate.getIssuerCountry()) { failedValidations.push('ISSUER'); } }; var validateTime = function(isoDate) { var time = parseIsoDate(isoDate); if (time.toString() === 'Invalid Date' || time - cryptoX509Certificate.getNotBefore() < 0 || time - cryptoX509Certificate.getNotAfter() > 0) { failedValidations.push('TIME'); } }; var areBasicConstraintsValid = function(keyType) { var returnValue = true; var basicConstraints = cryptoX509Certificate.getBasicConstraints(); if (keyType === 'CA' && (!basicConstraints || basicConstraints.ca !== true)) { returnValue = false; } return returnValue; }; var isKeyUsageValid = function(keyType) { var returnValue = true; var keyUsageExtension = cryptoX509Certificate.getKeyUsageExtension(); if (!keyUsageExtension) { return false; } switch (keyType) { case 'CA': if (keyUsageExtension.keyCertSign() !== true || keyUsageExtension.crlSign() !== true) { returnValue = false; } break; case 'Sign': if (keyUsageExtension.digitalSignature() !== true || keyUsageExtension.nonRepudiation() !== true) { returnValue = false; } break; case 'Encryption': if (keyUsageExtension.keyEncipherment() !== true || keyUsageExtension.dataEncipherment() !== true) { returnValue = false; } break; default: returnValue = false; } return returnValue; }; var validateKeyType = function(keyType) { if (!areBasicConstraintsValid(keyType) || !isKeyUsageValid(keyType)) { failedValidations.push('KEY_TYPE'); } }; var validateSignature = function(caCertificatePem) { var caCryptoX509Certificate = new box.certificates.CryptoX509Certificate(caCertificatePem); if (caCryptoX509Certificate === null) { failedValidations.push('SIGNATURE'); throw new exceptions.CryptoLibException('CA certificate is null.'); } try { if (!caCryptoX509Certificate.verify(x509CertificatePem)) { failedValidations.push('SIGNATURE'); } } catch (error) { failedValidations.push('SIGNATURE'); throw new exceptions.CryptoLibException( 'Signature verification process failed.'); } }; if (validationData.subject) { validateSubject(validationData.subject); } if (validationData.issuer) { validateIssuer(validationData.issuer); } if (validationData.time) { validateTime(validationData.time); } if (validationData.keyType) { validateKeyType(validationData.keyType); } if (validationData.caCertificatePem) { validateSignature(validationData.caCertificatePem); } return failedValidations; }; }; /** * Class that validates the content of a CryptoX509Certificate. * * @function * @param validationData * {json} * @param {string} * validationData.subject the subject data. * @param {string} * validationData.subject.commonName * @param {string} * validationData.subject.organization * @param {string} * validationData.subject.organizationUnit * @param {string} * validationData.subject.country * @param {string} * validationData.issuer the issuer data. * @param {string} * validationData.issuer.commonName * @param {string} * validationData.issuer.organization * @param {string} * validationData.issuer.organizationUnit * @param {string} * validationData.issuer.country * @param {string} * validationData.time time to be checked. * @param {string} * validationData.keyType CA, Sign or Encryption. * @param {string} * validationData.caCertificatePem * @param certificate * certificate as string in PEM format. * @returns {Array} an array that contains the error types occurred, if any. */ box.certificates.service.validateCertificate = function( validationData, certificate) { if (validationData === null) { throw new exceptions.CryptoLibException('Validation data is null.'); } if (Object.getOwnPropertyNames(validationData).length) { throw new exceptions.CryptoLibException('Validation data is empty.'); } if (certificate === null) { throw new exceptions.CryptoLibException('Certificate is null.'); } return (new box.certificates.CryptoX509CertificateValidator(validationData)) .validate(certificate); }; /** * Validates the certificate information provided to the constructor. The * validation process loops through all certificates, starting with the leaf * certificate, until it reaches the trusted certificate. For each * certificate, except the trusted certificate, it checks that the following * conditions hold: *

* In addition, if a non-null value is provided to the constructor for the * time reference, it will be checked whether this time reference is within * the dates of validity of the leaf certificate. After the validation * process has completed, a list of strings will be returned. If this list * is empty, then the validation was successful. Otherwise, the list will * contain string identifiers for each type of validation that failed. * * @function * @param {json} * chain certificate chain. * @param {json} * chain.leaf leaf certificate. * @param {string} * chain.leaf.pem X.509 certificate. * @param {string} * chain.leaf.keyType keyType. * @param {string} * chain.leaf.subject subject. * @param {string} * chain.leaf.time time reference (Optional). Only mandatory if * the 'Time' validation is going to be performed. * @param {json} * chain.certificates chain of certificates. * @param {Array} * chain.certificates.pems list of X.509 Certificates as string * in pem format. The list starts by the one closer to the root. * @param {Array} * chain.certificates.subjects list of subjects * @param {Array} * chain.root X.509 trusted Certificate. * @returns {Array} a two dimension array where the first dimension is the * index of the element starting by 1 -leaf certificate - and the * second dimension holds the error types. */ box.certificates.service.validateX509CertificateChain = function(chain) { var validateDateRange = function(certificate, failedValidation) { if (certificate.getNotBefore() > certificate.getNotAfter()) { failedValidation.push('VALIDITY_PERIOD'); } }; var validateNotBefore = function( notBefore, previousNotBefore, failedValidation) { if (notBefore < previousNotBefore) { failedValidation.push('NOT_BEFORE'); } }; var validateNotAfter = function( notAfter, previousNotAfter, failedValidation) { if (notAfter > previousNotAfter) { failedValidation.push('NOT_AFTER'); } }; var validateLeafCertificate = function( issuer, signature, previousNotBefore, previousNotAfter) { var validationData = { subject: chain.leaf.subject, issuer: issuer, keyType: chain.leaf.keyType, caCertificatePem: signature }; if (chain.leaf.time) { validationData.time = chain.leaf.time; } var certificateValidator = new box.certificates.CryptoX509CertificateValidator(validationData); var failedValidations = certificateValidator.validate(chain.leaf.pem); var certificate = new box.certificates.CryptoX509Certificate(chain.leaf.pem); validateDateRange(certificate, failedValidations); validateNotBefore( certificate.getNotBefore(), previousNotBefore, failedValidations); validateNotAfter( certificate.getNotAfter(), previousNotAfter, failedValidations); return failedValidations; }; var rootCertificate = new box.certificates.CryptoX509Certificate(chain.root); var issuer = { commonName: rootCertificate.getSubjectCN(), organization: rootCertificate.getSubjectOrg(), organizationUnit: rootCertificate.getSubjectOrgUnit(), country: rootCertificate.getSubjectCountry() }; var signature = chain.root; var failedValidation = []; var previousNotBefore = rootCertificate.getNotBefore(); var previousNotAfter = rootCertificate.getNotAfter(); var certificateValidator; chain.certificates.pems.reverse(); chain.certificates.subjects.reverse(); for (var i = 0; i < chain.certificates.pems.length; i++) { var certificate = new box.certificates.CryptoX509Certificate( chain.certificates.pems[i]); var validationData = { subject: chain.certificates.subjects[i], issuer: issuer, keyType: 'CA', signature: signature }; certificateValidator = new box.certificates.CryptoX509CertificateValidator(validationData); failedValidation[i] = certificateValidator.validate(chain.certificates.pems[i]); validateDateRange(certificate, failedValidation[i]); validateNotBefore( certificate.getNotBefore(), previousNotBefore, failedValidation[i]); validateNotAfter( certificate.getNotAfter(), previousNotAfter, failedValidation[i]); issuer = { commonName: certificate.getSubjectCN(), organization: certificate.getSubjectOrg(), organizationUnit: certificate.getSubjectOrgUnit(), country: certificate.getSubjectCountry() }; signature = chain.certificates.pems[i]; previousNotBefore = certificate.getNotBefore(); previousNotAfter = certificate.getNotAfter(); } failedValidation.push(validateLeafCertificate( issuer, signature, previousNotBefore, previousNotAfter)); failedValidation.reverse(); return failedValidation; }; /** * Flattens failed validations to a one dimensional array to a one * dimensional array. * * @function * @param {array} * failedValidations a two-dimensional failed validations array. * @returns {array} a flat array in which each element contains the error * type message piped with the index of the element, i.e. * ERROR_. */ box.certificates.service.flattenFailedValidations = function( failedValidations) { var flattenFailedValidations = Array(); for (var i = 0; i < failedValidations.length; i++) { if (failedValidations[i] !== undefined) { for (var j = 0; j < failedValidations[i].length; j++) { flattenFailedValidations.push( failedValidations[i][j].toLowerCase() + '_' + (i)); } } } return flattenFailedValidations; }; }; cryptolib.modules.certificates.service = cryptolib.modules.certificates; /* * 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.commons = cryptolib.modules.commons || {}; /** * Defines our exception wrapper. * * @param message. * The string to use in the error description. */ cryptolib.modules.commons.exceptions = function(box) { 'use strict'; box.commons = box.commons || {}; box.commons.exceptions = {}; box.commons.exceptions.CryptoLibException = function( customMessage, originalMessage) { this.customMessage = customMessage; this.originalMessage = originalMessage; }; box.commons.exceptions.CryptoLibException.prototype = { /** * Returns the exception message. * * @return {string} The message. */ toString: function() { var errorMessage = 'ERROR: '; if (typeof this.originalMessage !== 'undefined') { errorMessage += this.customMessage + '. Original Error Message: ' + this.originalMessage; } else { errorMessage += this.customMessage; } return errorMessage; } }; }; /* * 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.commons = cryptolib.modules.commons || {}; /** * @namespace commons/mathematical */ cryptolib.modules.commons.mathematical = function(box) { 'use strict'; box.commons = box.commons || {}; /** * A module the defines mathematical groups, elements, and operations that * can be performed * * @exports commons/mathematical */ box.commons.mathematical = {}; var BigInteger = box.forge.jsbn.BigInteger; var exceptions; var converters; cryptolib('commons.exceptions', 'commons.utils', function(box) { exceptions = box.commons.exceptions; converters = new box.commons.utils.Converters(); }); /** * Given p and Zp representing 'Integers (mod p)' group, a Zp subgroup is a * group where the elements are a subset of elements from Zp. * * The order of the subgroup is q, that is the number of elements. Both Zp * and the ZpSubgroup are finite cyclic groups, it means that all elements * can be generated exponentiating a special group element called generator. * * When the p and q are related with the restriction p = 2q + 1 the subgroup * is also defined as 'Quadratic Residue'. * * @class * @param valueG * {forge.jsbn.BigInteger} The generator of the subgroup, as a * BigInteger. This value must be a group element different from * one. * @param valueP * {forge.jsbn.BigInteger} The modulus, as a BigInteger. * @param valueQ * {forge.jsbn.BigInteger} The order of the subgroup, as a * BigInteger. * * @throws CryptoLibException * if 0 < q < p restriction is not accomplished * @throws CryptoLibException * if subgroup generator is nor between 2 and p-1 * */ box.commons.mathematical.ZpSubgroup = function(valueG, valueP, valueQ) { if ((typeof valueG === 'undefined') || (typeof valueP === 'undefined') || (typeof valueQ === 'undefined')) { throw new exceptions.CryptoLibException( 'The given parameters should be initialized'); } if ((BigInteger.ONE.compareTo(valueG) >= 0) || (valueP.compareTo(valueG) <= 0)) { throw new exceptions.CryptoLibException( 'The generator should be between 2 and p-1'); } if ((BigInteger.ZERO.compareTo(valueQ) >= 0) || (valueP.compareTo(valueQ) <= 0)) { throw new exceptions.CryptoLibException( 'The relationship between the values of the p and q parameters should be such that 0 < q < p'); } var _p = valueP; var _q = valueQ; var generator = new box.commons.mathematical.ZpGroupElement(valueG, _p, _q); var identity = new box.commons.mathematical.ZpGroupElement(BigInteger.ONE, _p, _q); /** * @function * @returns the identity element of the group. */ this.getIdentity = function() { return identity; }; /** * @function * @returns the q parameter (the order of the group). */ this.getQ = function() { return _q; }; /** * @function * @returns the p parameter (the modulus of the group). */ this.getP = function() { return _p; }; /** * @function * @returns {box.commons.mathematical.ZpGroupElement} the generator * element of the group. */ this.getGenerator = function() { return generator; }; }; box.commons.mathematical.ZpSubgroup.prototype = { /** * Checks whether a given element is a member of this MathematicalGroup. * * An element is a member of the group if: * * * @function * @param element * {ZpGroupElement} the element whose group membership should * be checked. * @returns true if the given element is member of the group, otherwise * false. */ isGroupMember: function(element) { if (typeof element !== 'undefined' && element.getP().equals(this.getP())) { var modPow = element.getElementValue().modPow(this.getQ(), this.getP()); return BigInteger.ONE.equals(modPow); } else { return false; } }, /** * Check if this group is equal to the received group. * * @function * @param group * {ZpSubgroup} the group which should be checked against * this group for equality. * @returns true if the given group has the same q, the * same p, and the same generator. */ equals: function(group) { if (typeof group !== 'undefined' && group.getGenerator().equals(this.getGenerator()) && group.getQ().equals(this.getQ()) && group.getP().equals(this.getP())) { return true; } else { return false; } }, /** * Check if this group is a quadratic residue group, which is defined * such that p = 2q + 1. * * @function * @returns true if the given group is a quadratic residue group. */ isQuadraticResidueGroup: function() { return this.getP().equals( new BigInteger('2').multiply(this.getQ()).add(BigInteger.ONE)); }, /** * Displays a string representation of this mathematical group JSON. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function * @returns a string representation of this mathematical group JSON. */ stringify: function() { return JSON.stringify({ zpSubgroup: { p: converters.base64FromBigInteger(this.getP()), q: converters.base64FromBigInteger(this.getQ()), g: converters.base64FromBigInteger( this.getGenerator().getElementValue()) } }); } }; /** * Class representing elements of a Zp group. * *

* Note: This constructor does not check whether the given value is a valid * member of the subgroup. To check subgroup membership the function * zpSubgroup.isGroupMember(groupElement) can be called, * however this is a computationally expensive operation. * * @class * @param value * {forge.jsbn.BigInteger} Value of the element (not null). The * value must be between [1..p-1] * @param groupP * {forge.jsbn.BigInteger} The p parameter, as a BigInteger. * @param groupQ * {forge.jsbn.BigInteger} The q parameter, as a BigInteger. * @throws CryptoLibException * if there are any problems with the inputs. */ box.commons.mathematical.ZpGroupElement = function(value, groupP, groupQ) { if ((typeof value === 'undefined')) { throw new exceptions.CryptoLibException( 'The received value is not the expected object'); } if ((BigInteger.ZERO.compareTo(value) >= 0) || (groupP.compareTo(value) <= 0)) { throw new exceptions.CryptoLibException( 'The value of the element should be between 1 and p-1. P: ' + groupP + ', value: ' + value); } if ((BigInteger.ZERO.compareTo(groupQ) >= 0) || (groupP.compareTo(groupQ) <= 0)) { throw new exceptions.CryptoLibException( 'The relationship between the values of the p and q parameters should hold that 0 < q < p'); } var v = value; var p = groupP; var q = groupQ; /** * Performs a basic check to verify if this element belongs to the same * group as element by checking if the p and q parameters * of both are equal. *

* Note: this function does NOT perform the mathematical operation * needed to actually confirm that a value is an element of a group, * which involves modular exponentiation and is very computationally * costly. Instead, this function simply examines the p and q parameters * that are stored within both elements. * * @function * @param element * {ZpGroupElement} element to by multiplied with this * element. * @throws CryptoLibException * if the p and q parameters of the group that the received * element belongs to are not equal to the p and q * parameters of this element. */ this._validateReceivedElementIsFromSameGroup = function(element) { if (typeof element === 'undefined') { throw new exceptions.CryptoLibException( 'The received value is not the expected object'); } if ((!this.getP().equals(element.getP())) || (!this.getQ().equals(element.getQ()))) { throw new exceptions.CryptoLibException( 'Operations can only be performed on group elements which are members of the same Zp subgroup'); } }; /** * Performs a basic check to verify if this element belongs to the same * group as exponent by checking if the p and q * parameters of both are equal. *

* Note: this function does NOT perform the mathematical operation * needed to actually confirm that an exponent belongs to a particular * group, which would involve confirming the following: *

* 0 >= exponent value <= p-1 * * @function * @param exponent * {Exponent} element to by multiplied with this element. * @throws CryptoLibException * if p and q parameters of the group that the received * element belongs to are not equal to the p and q * parameters of this element. */ this._validateReceivedExponent = function(exponent) { if (typeof exponent === 'undefined') { throw new exceptions.CryptoLibException( 'The received value is not the expected object'); } if (!this.getQ().equals(exponent.getQ())) { throw new exceptions.CryptoLibException( 'The exponent should have the same q as this Zp group element'); } }; /** * @function * @returns The value of this element. */ this.getElementValue = function() { return v; }; /** * @function * @returns The p parameter of the mathematical group to which this * element belongs. */ this.getP = function() { return p; }; /** * @function * @returns The q parameter of the mathematical group to which this * element belongs. */ this.getQ = function() { return q; }; }; box.commons.mathematical.ZpGroupElement.prototype = { /** * Multiple this element by the received element. The operation is * performed mod p. Performs a basic check to confirm that this element * and the received element belong to the same group. * * @function * @param element * {ZpGroupElement} element to by multiplied with this * element. * @returns (this * element) mod p, as a ZpGroupElement. * @throws CryptoLibException * if the p and q parameters of the group that the received * element belongs to are not equal to the p and q * parameters of this element. */ multiply: function(element) { this._validateReceivedElementIsFromSameGroup(element); var result = this.getElementValue() .multiply(element.getElementValue()) .mod(this.getP()); return new box.commons.mathematical.ZpGroupElement( result, this.getP(), this.getQ()); }, /** * Exponentiate this element to the received exponent. * * @function * @param exponent * {Exponent} the exponent used to raise the value of the * group element. * @returns (thisexponent) mod p, as a * ZpGroupElement. * @throws CryptoLibException * if the p and q parameters of the group that the received * exponent belongs to are not equal to the p and q * parameters of this element. */ exponentiate: function(exponent) { this._validateReceivedExponent(exponent); var result = this.getElementValue().modPow(exponent.getValue(), this.getP()); return new box.commons.mathematical.ZpGroupElement( result, this.getP(), this.getQ()); }, /** * Get the inverse of this element, mod p. * * @function * @returns the inverse of this element mod p, as a ZpGroupElement. */ invert: function() { var result = this.getElementValue().modInverse(this.getP()); return new box.commons.mathematical.ZpGroupElement( result, this.getP(), this.getQ()); }, /** * Check if this element is equal to the received element. *

* Elements are considered equal if: *

* * @function * @param element * {ZpGroupElement} the element which should be checked * against this element for equality. * @returns true if the given group element has the same v, * the same p and the same q. */ equals: function(element) { if (typeof element !== 'undefined' && element.getElementValue().equals(this.getElementValue()) && element.getP().equals(this.getP()) && element.getQ().equals(this.getQ())) { return true; } else { return false; } }, /** * Displays a string representation of this Zp group element JSON. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function * @returns a string representation of Zp group element JSON. */ stringify: function() { return JSON.stringify({ zpGroupElement: { p: converters.base64FromBigInteger(this.getP()), q: converters.base64FromBigInteger(this.getQ()), value: converters.base64FromBigInteger(this.getElementValue()) } }); } }; /** * Represents an exponent for a particular mathematical group. *

* The value of an exponent must be within the range [0..q-1]. If the * received value is not within this range, then the value that will be * assigned to the created Exponent will be calculated as follows: *

* value = value mod q * * @class * * @param q * {forge.jsbn.BigInteger} the order of the mathematical group * for which this is an exponent. * @param exponentValue * {forge.jsbn.BigInteger} the value of the exponent. * @throws CryptoLibException * if there are any problems with the inputs. */ box.commons.mathematical.Exponent = function(valueQ, exponentValue) { if (valueQ === 'undefined' || valueQ.compareTo(BigInteger.ZERO) === 0) { throw new exceptions.CryptoLibException( 'Q (the order of the group) cannot be null or zero'); } var _q = valueQ; /** * Gets the value to set for this Exponent. This value has to be a * number between 0 and q-1 (inclusive), * so if it is less than 0 or greater than * q-1, then mod q has to be applied. * * @function * @param {forge.jsbn.BigInteger} * exponentValue the value of the exponent. * @returns the value to set to this exponent. */ function getExponent(exponentValue) { if ((_q.compareTo(exponentValue) > 0) && (BigInteger.ZERO.compareTo(exponentValue) <= 0)) { return exponentValue; } else { return exponentValue.mod(_q); } } var _value = getExponent(exponentValue); /** * Checks if this exponent and the received exponent belong to the same * group. * * @function * @param exponent * {Exponent} the exponent which should be checked to see if * it belongs to the same group as this exponent. * @throws CryptoLibException * if the received exponent is undefined or does not belong * to the same group as this exponent. */ this._confirmSameGroup = function(exponent) { if (typeof exponent === 'undefined') { throw new exceptions.CryptoLibException( 'The received exponent is not the expected object'); } if (!_q.equals(exponent.getQ())) { throw new exceptions.CryptoLibException( 'Operations may only be performed with exponents of the same mathematical group order'); } }; /** * @function * @returns The numeric value of the exponent. */ this.getValue = function() { return _value; }; /** * @function * @returns The numeric value of the exponent. */ this.getQ = function() { return _q; }; }; box.commons.mathematical.Exponent.prototype = { /** * Returns an Exponent whose value is: *

* (this + exponent) mod q * * @param exponent * {Exponent} the exponent to be added to this exponent. * @returns (this + exponent) mod q */ add: function(exponent) { this._confirmSameGroup(exponent); var result = this.getValue().add(exponent.getValue()).mod(this.getQ()); return new box.commons.mathematical.Exponent(this.getQ(), result); }, /** * Returns an Exponent whose value is: *

* (this - exponent) mod q * * @param exponent * {Exponent} the exponent to be subtracted from this * exponent. * @returns (this - exponent) mod q */ subtract: function(exponent) { this._confirmSameGroup(exponent); var result = this.getValue().subtract(exponent.getValue()).mod(this.getQ()); return new box.commons.mathematical.Exponent(this.getQ(), result); }, /** * Returns an Exponent whose value is: *

* (this * exponent) mod q * * @param exponent * {Exponent} the exponent to be multiplied with this * exponent. * @returns (this * exponent) mod q */ multiply: function(exponent) { this._confirmSameGroup(exponent); var result = this.getValue().multiply(exponent.getValue()).mod(this.getQ()); return new box.commons.mathematical.Exponent(this.getQ(), result); }, /** * Returns an Exponent whose value is (-this) mod q * * @returns (-this mod q) */ negate: function() { return new box.commons.mathematical.Exponent( this.getQ(), this.getValue().negate().mod(this.getQ())); }, /** * Check if this exponent is equal to the received exponent. *

* Elements are considered equal if: *

* * @param {Exponent} * exponent the exponent to be checked against this exponent * for equality. * @returns true if the given exponent has the same value * and belongs to the same group as this * exponent. */ equals: function(exponent) { if (exponent.getValue().equals(this.getValue()) && exponent.getQ().equals(this.getQ())) { return true; } else { return false; } }, /** * Displays a string representation of the exponent JSON. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function * @returns a string representation of the exponent JSON. */ stringify: function() { return JSON.stringify({ exponent: { q: converters.base64FromBigInteger(this.getQ()), value: converters.base64FromBigInteger(this.getValue()) } }); } }; /** * Provides utility functionality for working with mathematical groups. * * @class groupUtils * @memberof commons/mathematical */ box.commons.mathematical.groupUtils = (function() { /** * Deserializes a Zp Subgroup string representation to a ZpSubgroup * object. * * @function * @memberof commons/mathematical.groupUtils * @param groupJson * {JSON} a JSON representation of a Zp subgroup. * @returns a new ZpSubgroup, created from the received string. */ function deserializeGroup(groupJson) { var parsed = JSON.parse(groupJson); var p = converters.base64ToBigInteger(parsed.zpSubgroup.p); var q = converters.base64ToBigInteger(parsed.zpSubgroup.q); var g = converters.base64ToBigInteger(parsed.zpSubgroup.g); return new box.commons.mathematical.ZpSubgroup(g, p, q); } /** * Deserializes a Zp Subgroup element string to a ZpGroupElement object. * * @function * @memberof commons/mathematical.groupUtils * @param groupElementJson * {JSON} a JSON representation of a Zp Subgroup element * element. * @returns a new ZpGroupElement, created from the received string. */ function deserializeGroupElement(groupElementJson) { var parsed = JSON.parse(groupElementJson); var value = converters.base64ToBigInteger(parsed.zpGroupElement.value); var p = converters.base64ToBigInteger(parsed.zpGroupElement.p); var q = converters.base64ToBigInteger(parsed.zpGroupElement.q); return new box.commons.mathematical.ZpGroupElement(value, p, q); } /** * Deserializes an exponent string to an Exponent object. * * @function * @memberof commons/mathematical.groupUtils * @param exponentJson * {JSON} a JSON representation of an exponent. * @returns a new Exponent, created from the received string. */ function deserializeExponent(exponentJson) { var parsed = JSON.parse(exponentJson); var q = converters.base64ToBigInteger(parsed.exponent.q); var value = converters.base64ToBigInteger(parsed.exponent.value); return new box.commons.mathematical.Exponent(q, value); } /** * Compress a list of group elements. *

* Note: an exception will be thrown if an array is received which * contains any elements that are not elements of the mathematical * group. * * @function * @memberof commons/mathematical.groupUtils * @param group * {ZpSubgroup} the mathematical group. * @param elementsToBeCompressedArray * {object} the array of group elements to be compressed. * @returns the result of the compression process, as a group element. * @throws CryptoLibException * if there are any problems with the inputs. */ function compressGroupElements(group, elementsToBeCompressedArray) { validateArray(elementsToBeCompressedArray); validateGroup(group); var elem = elementsToBeCompressedArray[0]; for (var i = 1; i < elementsToBeCompressedArray.length; i++) { elem = elem.multiply(elementsToBeCompressedArray[i]); } return elem; } /** * Compress a list of exponents. *

* Note: an exception will be thrown if an array is received which * contains any exponents that are not exponents of the mathematical * group. * * @function * @memberof commons/mathematical.groupUtils * @param group * {ZpSubgroup} the mathematical group * @param exponentsToBeCompressedArray * {object} the array of exponents to be compressed * @returns the result of the compression process, as an exponent. * @throws CryptoLibException * if there are any problems with the inputs. */ function compressExponents(group, exponentsToBeCompressedArray) { validateArray(exponentsToBeCompressedArray); validateGroup(group); validateExponents(group.getQ(), exponentsToBeCompressedArray); var elem = exponentsToBeCompressedArray[0]; for (var i = 1; i < exponentsToBeCompressedArray.length; i++) { elem = elem.add(exponentsToBeCompressedArray[i]); } return elem; } /** * Builds a new array of elements from the input array, where the final * elements from the input array are compressed into a simple element. * * @function * @memberof commons/mathematical.groupUtils * @param group * {ZpSubgroup} the mathematical group that this compressor * operates on. * @param inputArray * {object} the input array of group elements. * @param numElementsRequiredInNewList * {number} the number of elements that should be in the * output array. * @returns a new array of group elements. * @throws CryptoLibException * if there are any problems with the inputs. */ function buildListWithCompressedFinalElement( group, inputArray, numElementsRequiredInNewList) { validateArray(inputArray); validateGroup(group); if (numElementsRequiredInNewList < 1) { throw new exceptions.CryptoLibException( 'The number of elements in the output list must be at least 1'); } var offset = numElementsRequiredInNewList - 1; var outputArray = inputArray.slice(0, offset); var lastElement = compressGroupElements( group, inputArray.slice(offset, inputArray.length)); outputArray.push(lastElement); return outputArray; } /** * Builds a new array of exponents from the input array, where the final * exponents from the input array are compressed into a simple * exponents. * * @function * @memberof commons/mathematical.groupUtils * @param group * {ZpSubgroup} the mathematical group that this compressor * operates on. * @param inputArray * {object} the input array of exponents. * @param numExponentsRequiredInNewList * {number} the number of exponents that should be in the * output array. * @returns a new array of group elements. * @throws CryptoLibException * if there are any problems with the inputs. */ function buildListWithCompressedFinalExponent( group, inputArray, numExponentsRequiredInNewList) { validateArray(inputArray); validateGroup(group); if (numExponentsRequiredInNewList < 1) { throw new exceptions.CryptoLibException( 'The number of exponents in the output list must be at least 1'); } var offset = numExponentsRequiredInNewList - 1; var outputArray = inputArray.slice(0, offset); var lastExponent = compressExponents(group, inputArray.slice(offset, inputArray.length)); outputArray.push(lastExponent); return outputArray; } /** * Given two arrays of group elements, this method produces a new list * of group elements that represents the two input lists combined. *

* The two arrays must only contain values that are elements of the * mathematical group that this divider operates on. If either array * contains any element that is not a group element then an exception * will be thrown. * * @function * @memberof commons/mathematical.groupUtils * @param arrayElements1 * {object} an input array of Zp group elements. * @param arrayElements2 * {object} an input array of Zp group elements. * @returns a new list of group elements. * @throws CryptoLibException * if there are any problems with the inputs. */ function divide(arrayElements1, arrayElements2) { validateArray(arrayElements1); validateArray(arrayElements2); if (arrayElements1.length !== arrayElements2.length) { throw new exceptions.CryptoLibException( 'Both arrays should have the same length'); } var newList = []; var newElement; for (var i = 0; i < arrayElements1.length; i++) { newElement = arrayElements1[i].multiply(arrayElements2[i].invert()); newList.push(newElement); } return newList; } /** * Given an array of group elements and one exponent, this method * produces a new list of group elements that represents the * exponentiation of the group elements to the exponent. *

* The array must contain values that are elements of the mathematical * group that this exponentiation activity operates on. If the array * contains any element that is not a group element then an exception * will be thrown. * * @function * @memberof commons/mathematical.groupUtils * @param {array} arrayElements * an input array of Zp group elements. * @param {object} exponent * An Exponent. * @param {object} group * The mathematical group that all of the elements in * arrayElements belong to. * @param {boolean} validateElements An Exponent. * @return A new list of group elements. * */ function exponentiateArrays( arrayElements, exponent, group, validateElements) { validateArray(arrayElements); validateExponent(exponent); validateGroup(group); // If requested, validate all elements prior to any operations. if (typeof validateElements !== 'undefined' && validateElements) { for (var j = 0; j < arrayElements.length; j++) { if (!group.isGroupMember(arrayElements[j])) { throw new exceptions.CryptoLibException( 'The element does not belong to the group'); } } } var newList = []; var newElement; for (var i = 0; i < arrayElements.length; i++) { newElement = arrayElements[i].exponentiate(exponent); newList.push(newElement); } return newList; } /** * Returns a new random exponent for the received mathematical group. *

* The value of the created exponent will be between 0 * and q-1. * * @function * @memberof commons/mathematical.groupUtils * @param group * {ZpSubgroup} the mathematical group. * @param cryptoRandomInteger * {CryptoScytlRandomInteger} the source of randomness. * @param {boolean} * useShortExponent true if a short exponent is to be * generated. Default value is false. * @returns a new random exponent. * @throws CryptoLibException * if there are any problems with the inputs. */ function generateRandomExponent( group, cryptoRandomInteger, useShortExponent) { validateGroup(group); if (typeof cryptoRandomInteger === 'undefined') { throw new exceptions.CryptoLibException( 'The given parameters should be initialized'); } var _useShortExponent = true; if (!useShortExponent) { _useShortExponent = false; } var q = group.getQ(); var qBitLength = q.bitLength(); var randomExponentLength; if (_useShortExponent) { if (qBitLength < box.SHORT_EXPONENT_BIT_LENGTH) { throw new exceptions.CryptoLibException( 'Zp subgroup order bit length must be greater than or equal to short exponent bit length : ' + box.SHORT_EXPONENT_BIT_LENGTH + '; Found ' + qBitLength); } randomExponentLength = box.SHORT_EXPONENT_BIT_LENGTH; } else { randomExponentLength = qBitLength; } var randomExponentValue; var randomExponentFound = false; while (!randomExponentFound) { randomExponentValue = cryptoRandomInteger.nextRandomByBits(randomExponentLength); if (randomExponentValue.compareTo(q) < 0) { randomExponentFound = true; } } return new box.commons.mathematical.Exponent(q, randomExponentValue); } /** * Returns a group element symmetric key which is obtained by * exponentiating the generator of a given mathematical group to a * random exponent k. * * @function * @memberof commons/mathematical.groupUtils * @param group * {ZpSubgroup} the mathematical group. * @param cryptoRandomInteger * {CryptoScytlRandomInteger} the source of randomness. * @returns the secret key, as a group element. * @throws CryptoLibException * if there are any problems with the inputs. */ function generateGroupElementSecretKey(group, cryptoRandomInteger) { var exponent = generateRandomExponent(group, cryptoRandomInteger); var groupElementSecretKey = group.getGenerator().exponentiate(exponent); return groupElementSecretKey; } /** * Build a ZpSubgroup from the p and g parameters. *

* NOTE: This method builds a particular type of mathematical group with * property that: *

* P = (Q * 2) + 1 *

* This property holds for all groups generated using this method, but * this property does not hold for all mathematical groups. * * @function * @memberof commons/mathematical.groupUtils * @param p * {forge.jsbn.BigInteger} the p parameter of the group. * @param g * {forge.jsbn.BigInteger} the generator of the group. * @returns the generated ZpSubgroup. * @throws CryptoLibException * if there are any problems with the inputs. */ function buildZpSubgroupFromPAndG(p, g) { validateBuildFromPAndGInputs(p, g); var q = p.subtract(BigInteger.ONE).divide(new BigInteger('2')); return new box.commons.mathematical.ZpSubgroup(g, p, q); } /** * Build a random ZpSubgroup whose p parameter will have the specified * bit length. The certainty that both p and q are both prime is * specified by certainty. *

* NOTE: This method builds a particular type of Zp subgroup with * property that: *

* p = (q * 2) + 1 *

* This property holds for all mathematical groups generated using this * method, but this property does not hold for all mathematical groups. *

* Note: the minimum bit length of the p parameter that is permitted by * this method is 2. If a bit length less than 2 is requested then a * CryptoLibException will be thrown. * * @function * @memberof commons/mathematical.groupUtils * @param bitLengthOfP * {number} the bit length that the p parameter of the * generated group should be. * @param cryptoRandomInteger * {CryptoScytlRandomInteger} an instance of * CryptoRandomInteger that can be used as a source of random * integers. * @param certainty * {number} the required level of certainty that the q * parameter of the generated group is prime. * @returns the generated ZpSubgroup. * @throws CryptoLibException * if there are any problems with the inputs. */ function buildRandomZpSubgroupFromLengthOfP( bitLengthOfP, cryptoRandomBytes, certainty) { validateBuildFromLengthPInputs( bitLengthOfP, cryptoRandomBytes, certainty); var p, q; var twoBI = new BigInteger('2'); var randomGroupFound = false; var options = {prng: cryptoRandomBytes}; var callback = function(err, num) { if (err) { throw new exceptions.CryptoLibException( 'Error while generating prime ' + err); } p = num; q = num.subtract(BigInteger.ONE).divide(twoBI); }; while (!randomGroupFound) { forge.prime.generateProbablePrime(bitLengthOfP, options, callback); if (q.isProbablePrime(certainty)) { randomGroupFound = true; } } var generator = findSmallestGenerator(p, q); return new box.commons.mathematical.ZpSubgroup(generator, p, q); } /** * Given an input array containing possible group members, this method * cycles through that array, checking if each element is a group * member. This method attempts to find the specified number of group * members. Once that number has been found, the method will return a * new array containing the specified number of group members. *

* If the required number of group members are not found in the input * array then a CryptoLibException will be thrown. * * @function * @memberof commons/mathematical.groupUtils * @param possibleGroupMembersArray * {object} an array containing possible group members. * @param group * {ZpSubgroup} the Zp subgroup for which the specified * number of group members are required. * @param numMembersRequired * {number} the number of group members of the specified * group that should be added to the output list. * @returns a new array containing the specified number of group * elements, which have been read from the input array. * @throws CryptoLibException * if there are any problems with the inputs. */ function extractNumberOfGroupMembersFromListOfPossibleMembers( possibleGroupMembersArray, group, numMembersRequired) { validateExtractFromListInputs( possibleGroupMembersArray, group, numMembersRequired); var outputArray = []; var membersFound = 0; var candidate; for (var i = 0; i < possibleGroupMembersArray.length; i++) { candidate = new box.commons.mathematical.ZpGroupElement( possibleGroupMembersArray[i], group.getP(), group.getQ()); if (group.isGroupMember(candidate)) { outputArray.push(candidate); membersFound++; } if (membersFound === numMembersRequired) { return outputArray; } } if (membersFound !== numMembersRequired) { throw new exceptions.CryptoLibException( 'Error - did not find the required number of group members in the given list. The required number of was ' + numMembersRequired + ', number of members found was ' + membersFound); } } /** * Find the smallest generator of a mathematical group. *

* Note: starts with a candidate generator value (two is the initial * candidate generator value), and checks if the candidate generator is * a group memeber. If that candidate is a group memeber then return * that value, else increment the candidate by one and again check if * the candidate is a group member. Continue this process until the * candidate value is less than the parameter p. * * @function * @memberof commons/mathematical.groupUtils * @params p {number} the p parameter of the group. * @params q {number} the q parameter of the group. * @throws CryptoLibException */ function findSmallestGenerator(p, q) { var g = new BigInteger('2'); var generatorFound = false; while ((!generatorFound) && (g.compareTo(p) < 0)) { if (g.modPow(q, p).equals(BigInteger.ONE)) { generatorFound = true; } else { g = g.add(BigInteger.ONE); } } if (!generatorFound) { throw new exceptions.CryptoLibException( 'Failed to find a generator, p was: ' + p + ', q was: ' + q); } return g; } /** * Validates a group. * * @function * @memberof commons/mathematical.groupUtils * @params group {ZpSubgroup} the group to be validated. * @throws CryptoLibException * if the received group is undefined. */ function validateGroup(group) { if (typeof group === 'undefined' || null === group) { throw new exceptions.CryptoLibException( 'The given group should be initialized'); } } function validateExponent(exponent) { if (typeof exponent === 'undefined') { throw new exceptions.CryptoLibException( 'The given exponent should be initialized'); } } /** * Validates that an array is initialized and non-empty. * * @function * @memberof commons/mathematical.groupUtils * @params inputArray {object} an array to be validated. * @throws CryptoLibException * if the received array is not initialized or is empty. */ function validateArray(inputArray) { if (typeof inputArray === 'undefined') { throw new exceptions.CryptoLibException( 'The given array should be initialized'); } if (!(inputArray instanceof Array)) { throw new exceptions.CryptoLibException( 'The given array are not from the expected type'); } if (inputArray.length < 1) { throw new exceptions.CryptoLibException( 'The given array cannot be empty'); } } /** * Performs a very basic validation on the received inputs. * * @function * @memberof commons/mathematical.groupUtils * @params p {object} the first parameter. * @params g {object} the second parameter. * @throws CryptoLibException * if either (or both) inputs are undefined. */ function validateBuildFromPAndGInputs(p, g) { if ((typeof p === 'undefined') || (typeof g === 'undefined')) { throw new exceptions.CryptoLibException('p or g are incorrect'); } } /** * Performs a some basic validations on the received inputs. * * @function * @memberof commons/mathematical.groupUtils * @params bitLengthOfP {number} bit length of p parameter. * @params cryptoRandomBytes {CryptoScytlRandomBytes} a secure source of * random bytes. * @params certainty {number} a certainty level. * @throws CryptoLibException * if any of the inputs fail any of the validations. */ function validateBuildFromLengthPInputs( bitLengthOfP, cryptoRandomBytes, certainty) { if (bitLengthOfP < 2) { throw new exceptions.CryptoLibException( 'The bit length should be higher'); } if (typeof cryptoRandomBytes === 'undefined') { throw new exceptions.CryptoLibException( 'The random generator should be initialized'); } if (certainty < box.MINIMUM_PRIME_CERTAINTY_LEVEL) { throw new exceptions.CryptoLibException('Certainty should be higher'); } } /** * Performs some basic validations on the received inputs. * * @function * @memberof commons/mathematical.groupUtils * @params inputArray {object} an array. * @params group {ZpSubgroup} a Zp subgroup. * @params numMembersRequired {number} an integer value. * @throws CryptoLibException * if any of the inputs fail any of the validations. */ function validateExtractFromListInputs( inputArray, group, numMembersRequired) { validateArray(inputArray); validateGroup(group); if (typeof numMembersRequired === 'undefined') { throw new exceptions.CryptoLibException( 'The given objects should be initialized'); } if (typeof numMembersRequired !== 'number') { throw new exceptions.CryptoLibException( 'The given objects are not from the expected type'); } if (numMembersRequired < 1 || numMembersRequired > inputArray.length) { throw new exceptions.CryptoLibException( 'The given number of required elements cannot be higher than the number of elements of the array'); } } /** * Validates that all of the exponents in the received list are * exponents are members of the received group, by checking if the * groups that they contain are equal. * * @function * @memberof commons/mathematical.groupUtils * @params group {ZpSubgroup} a Zp subgroup. * @params exponents {object} a list of exponents. */ function validateExponents(q, exponents) { for (var i = 0; i < exponents.length; i++) { if (!exponents[i].getQ().equals(q)) { throw new exceptions.CryptoLibException( 'The list of exponents contained an exponent which does not belong to the group with the same order.'); } } } return { buildZpSubgroupFromPAndG: buildZpSubgroupFromPAndG, buildRandomZpSubgroupFromLengthOfP: buildRandomZpSubgroupFromLengthOfP, extractNumberOfGroupMembersFromListOfPossibleMembers: extractNumberOfGroupMembersFromListOfPossibleMembers, generateRandomExponent: generateRandomExponent, generateGroupElementSecretKey: generateGroupElementSecretKey, compressGroupElements: compressGroupElements, buildListWithCompressedFinalElement: buildListWithCompressedFinalElement, deserializeGroup: deserializeGroup, deserializeGroupElement: deserializeGroupElement, deserializeExponent: deserializeExponent, compressExponents: compressExponents, buildListWithCompressedFinalExponent: buildListWithCompressedFinalExponent, divide: divide, exponentiateArrays: exponentiateArrays }; })(); }; /* * 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.commons = cryptolib.modules.commons || {}; /** namespace commons/utils */ cryptolib.modules.commons.utils = function(box) { 'use strict'; box.commons = box.commons || {}; /** * A module that holds utility functionalities. * * @exports commons/utils */ box.commons.utils = {}; var exceptions; cryptolib('commons.exceptions', function(box) { exceptions = box.commons.exceptions; }); /** * Defines some useful functions for data manipulation and type conversion. * * @class */ box.commons.utils.Converters = function() {}; box.commons.utils.Converters.prototype = { /** * Base64 encodes some data. * * @function * @param {string} * data data to Base64 encode. * @param {number} * lineLength line length of encoding (No line breaks if not * specified). * @returns {string} base64 encoded data. */ base64Encode: function(data, lineLength) { if (lineLength === undefined || lineLength === null) { return forge.util.encode64(data); } else { return forge.util.encode64(data, lineLength); } }, /** * Base64 decodes some data. * * @function * @param {string} * dataB64 data to Base64 decode. * @returns {string} base64 decoded data. */ base64Decode: function(dataB64) { return forge.util.decode64(dataB64); }, /** * Converts a string to a collection of bytes. * * @function * @param {string} * str string to convert to collection of bytes. * @returns collection of bytes from string. */ bytesFromString: function(str) { var bytes = []; for (var i = 0; i < str.length; i++) { bytes.push(str.charCodeAt(i)); } return bytes; }, /** * Converts a collection of bytes to a string. * * @function * @param {string} * bytes collection of bytes to convert to string. * @return {string} string from collection of bytes. */ bytesToString: function(bytes) { var string = ''; for (var i = 0; i < bytes.length; i++) { string += String.fromCharCode(bytes[i]); } return string; }, /** * Converts a string to a hexadecimal string. * * @function * @param {string} * str String to convert. * @returns string string in hexadecimal format. */ hexFromString: function(str) { return forge.util.bytesToHex(str); }, /** * Converts a hexadecimal string to a string. * * @function * @param {string} * hexStr hexadecimal string to convert. * @returns {string} String from conversion. */ hexToString: function(hexStr) { return forge.util.hexToBytes(hexStr); }, /** * Converts a BigInteger object to a base64 string. * * @function * @param {object} * bigInteger forge.jsbn.BigInteger to convert. * @returns {string} base64 string from conversion. */ base64FromBigInteger: function(bigInteger) { var array = new Uint8Array(bigInteger.toByteArray()); return box.forge.util.binary.base64.encode(array); }, /** * Converts a base64 string to a BigInteger object. * * @function * @param {string} * base64Str base64 string to convert. * @returns {object} forge.jsbn.BigInteger from conversion. */ base64ToBigInteger: function(base64Str) { var buffer = this.base64Decode(base64Str); var hex = this.hexFromString(buffer); return new forge.jsbn.BigInteger(hex, 16); } }; /** * Defines a set of bit operator utilities. * * @class */ box.commons.utils.BitOperators = function() {}; box.commons.utils.BitOperators.prototype = { /** * Bit-wise concatenates some data to some existing data. * * @function * @param {string} * data1 existing data, as string. * @param {string} * data2 data to concatenate, as string. * @returns {string} bit-wise concatenation of data, as string. */ concatenate: function(data1, data2) { try { var converters = new box.commons.utils.Converters(); var data1Bytes = converters.bytesFromString(data1); var data2Bytes = converters.bytesFromString(data2); var concatDataBytes = data1Bytes.concat(data2Bytes); return converters.bytesToString(concatDataBytes); } catch (error) { throw new exceptions.CryptoLibException( 'Data could not be bit-wise concatenated.', error); } }, /** * Unpacks a data subset from a bitwise concatenation of data. * * @function * @param {string} * concatData bit-wise concatenation of data, as string. * @param {number} * offset offset of data subset in concatenation. * @param {number} * size size of data subset. * @returns {string} data subset, as string. */ extract: function(concatData, offset, size) { try { var converters = new box.commons.utils.Converters(); var concatDataBytes = converters.bytesFromString(concatData); var dataSubsetBytes = concatDataBytes.slice(offset, size); return converters.bytesToString(dataSubsetBytes); } catch (error) { throw new exceptions.CryptoLibException( 'Data could not be bit-wise extracted.', error); } } }; /** * Defines a set of time utilities. * * @class */ box.commons.utils.TimeUtils = function() {}; box.commons.utils.TimeUtils.prototype = { /** * Generates a time stamp in Unix time, i.e. the number of seconds that * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1 * January 1970. * * @function * @returns {number} time stamp in seconds, as integer. */ unixTimeStamp: function() { try { return Math.round(new Date().getTime() / 1000); } catch (error) { throw new exceptions.CryptoLibException( 'Could not generate Unix timestamp.', error); } }, /** * Generates a time stamp in Unix time, i.e. the number of seconds that * have elapsed since 00:00:00 Coordinated Universal Time (UTC), 1 * January 1970. The value is returned as a floating point number. * * @function * @returns {number} time stamp in seconds, as floating point number. */ unixTimeStampFloat: function() { try { return new Date().getTime() / 1000; } catch (error) { throw new exceptions.CryptoLibException( 'Could not generate Unix timestamp.', error); } } }; /** * Defines a set of xpath utilities. * * @class * @param {string} * path path in XPathForm notation Eg. *

test
to '/form/name' */ box.commons.utils.XPath = function(xml, path) { this.path = path; this.node = xml; var pathTree = path ? path.split('/') : []; for (var i = 0; i < pathTree.length; i++) { if (pathTree[i] && this.node) { var found = false; for (var j = 0; j < this.node.childNodes.length; j++) { var child = this.node.childNodes[j]; // we found the node if (child.nodeType === Node.ELEMENT_NODE && child.tagName.toLowerCase() === pathTree[i].toLowerCase()) { this.node = child; found = true; break; } } // invalid xpath if (!found) { this.node = null; throw new exceptions.CryptoLibException('Invalid xpath for: ' + path); } } } }; box.commons.utils.XPath.prototype = { /** * Gets the value of a node. For example value returns * value. * * @function * @returns {string} the value, as string. */ getValue: function() { return this.node ? this.node.childNodes[0].nodeValue : ''; }, /** * Returns the value of an attribute of a node. For example node value returns "attr value". * * @function * @param {object} * attribute attribute to get the value. * @returns attribute value, as string. */ getAttribute: function(attribute) { return this.node ? this.node.getAttribute(attribute) : ''; }, /** * Gets the node itself- * * @function * @returns {object} xml document, as a DOM object. */ getXml: function() { return this.node; }, /** * Gets an array of child nodes * * @function * @returns {object} xml document array with all children as DOM * objects. */ getChildren: function() { return this.node ? this.node.childNodes : ''; } }; /** * Defines a set of string utilities. * * @class */ box.commons.utils.StringUtils = function() { }; box.commons.utils.StringUtils.prototype = { /** * Check if a string is within another string * * @function * @param {string} * main text * @param {string} * substring substring to search in the main text * @param caseSensitive * boolean, case sensitive the search * * @returns {boolean} boolean, true if found */ containsSubString: function(string, substring, caseSensitive) { var main = string, sub = substring; if (!caseSensitive) { main = main.toLowerCase(); sub = sub.toLowerCase(); } return main.indexOf(sub) > -1; } }; /** * Defines a set of parser utilities. * * @class */ box.commons.utils.Parsers = function() {}; box.commons.utils.Parsers.prototype = { /** * Converts a string to an XML document. * * @function * @param {string} * str String to convert. * @returns {string} XML document, as string. */ xmlFromString: function(str) { if (this.isIE()) { // Remove null terminating character if exists var strNoNull = this.removeNullTerminatingChar(str); var xmlDoc = new window.ActiveXObject('Microsoft.XMLDOM'); xmlDoc.async = false; xmlDoc.loadXML(strNoNull); return xmlDoc; } else if (typeof window.DOMParser !== 'undefined') { return (new window.DOMParser()).parseFromString(str, 'text/xml'); } else { throw new exceptions.CryptoLibException( 'XML string could not be parsed.'); } }, /** * Converts an XML document to a string. * * @fuction * @param {string} * xmlDoc xml document to convert, as string. * @param {boolean} * removeSelfClosingTags removes self closing tags to * explicit tags. Example: to * @returns {string} string from conversion. */ xmlToString: function(xmlDoc, removeSelfClosingTags) { var result = this.xmlToStringIeCompatible(xmlDoc); if (removeSelfClosingTags) { result = this.xmlRemoveSelfClosingTags(result); } return result; }, xmlRemoveSelfClosingTags: function(data) { var split = data.split('/>'); var newXml = ''; for (var i = 0; i < split.length - 1; i++) { var edsplit = split[i].split('<'); newXml += split[i] + '>'; } return newXml + split[split.length - 1]; }, xmlToStringIeCompatible: function(xmlNode) { if (this.isIE()) { return xmlNode.xml; } else if (typeof window.XMLSerializer !== 'undefined') { return (new window.XMLSerializer()).serializeToString(xmlNode); } else { throw new exceptions.CryptoLibException( 'Error while tring to construct XML from String.'); } }, /** * Removes all newline characters from some data. * * @function * @param {string} * data data from which to remove all newline characters, as * string. * @returns {string} data with all newline characters removed, as * string. */ removeNewLineChars: function(data) { return data.replace(/(\r\n|\n|\r)/gm, ''); }, /** * Removes all carriage return characters from some data. * * @param data * Data from which to remove all carriage return characters, * as string. * @return Data with all carriage return characters removed, as string. */ removeCarriageReturnChars: function(data) { return data.replace(/(\r)/gm, ''); }, /** * Removes the null terminating character from some data. * * @function * @param {string} * data Data from which to remove the null terminating * character, as string. * @returns {string} data with null terminating character removed, as * string. */ removeNullTerminatingChar: function(data) { return data.replace(/\x00+$/, ''); }, /** * Removes the XML header from a string representing an XML. * * @param data * The XML string from which to remove the XML header. * @return XML string with the XML header removed. */ removeXmlHeaderFromString: function(data) { return data.replace('', ''); }, /** * Removes the signature node from a string representing an XML. * * @param data * The XML string from which to remove the signature node. * @return XML string with the signature node removed. */ removeXmlSignature: function(data) { return data.replace(//g, ''); }, /** * Detects if the client agent is Microsoft Internet Explorer. This has * been tested to work with Internet Explorer 9, 10 and 11. If this * software is to be used on other versions, then it should be checked * if those versions are supported by this function and if they are not * then this function should be updated. */ isIE: function() { var ua = window.navigator.userAgent; var msie = ua.indexOf('MSIE '); if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./)) { return true; } else { return false; } } }; /** * Defines a set of connectors. * * @class */ box.commons.utils.Connectors = function() {}; box.commons.utils.Connectors.prototype = { /** * Posts a request message to a destination via HTTP. * * @function * @param {string} * request request message to post, as string. * @param {string} * url url of request destination, as string. * @param {boolean} * isAsynch boolean indicating whether the post is * asynchronous. * @return {string} response retrieved from request destination, as * string. */ postHttpRequestMessage: function(request, url, isAsynch) { // Check if a post destination was provided. if (url === '') { throw new exceptions.CryptoLibException( 'No URL provided for posting HTTP request.'); } // Create HTTP request object. var httpRequest; var done; if (window.XMLHttpRequest) { httpRequest = new XMLHttpRequest(); done = XMLHttpRequest.DONE; } else { throw new exceptions.CryptoLibException( 'Could not create HTTP request object for this browser.'); } // Define post response message handling. var response; httpRequest.onreadystatechange = function() { if (httpRequest.readyState === done) { if (httpRequest.status === 200) { response = httpRequest.responseText; } else { var errorMsg = 'HTTP request message POST was not successful.\n Status: ' + httpRequest.status + ', Message: ' + httpRequest.statusText; throw new exceptions.CryptoLibException(errorMsg); } } }; // Open HTTP request object for synchronous post. httpRequest.open('POST', url, isAsynch); // Post HTTP request message. try { httpRequest.send(request); } catch (error) { var errorMsg = 'Could not post HTTP request message; IP of web application might be incorrect.'; throw new exceptions.CryptoLibException(errorMsg, error); } return response; } }; /** * Defines a progress meter. * * @param {integer} * progressMax Maximum amount of progress to be attained. * @param {integer} * progressCallback Progress callback function. * @param {integer} * progressPercentMinCheckInterval Progress percentage minimum * check interval (optional) (default: 10%). * @class */ box.commons.utils.ProgressMeter = function( progressMax, progressCallback, progressPercentMinCheckInterval) { this.progressMax = progressMax; this.progressCallback = progressCallback; this.progressPercentMinCheckInterval = progressPercentMinCheckInterval || 10; this.lastProgressPercent = 0; }; box.commons.utils.ProgressMeter.prototype = { /** * Calculates the progress as a percentage of the total and provides it * as input to the provided callback function. * * @function * @param {integer} * progress Present amount of progress. */ update: function(progress) { if (typeof this.progressCallback === 'undefined') { return; } var progressPercent = Math.floor((progress / this.progressMax) * 100); progressPercent = Math.min(progressPercent, 100); var progressPercentChange = progressPercent - this.lastProgressPercent; if (progressPercentChange > 0) { var checkProgress = (progressPercentChange >= this.progressPercentMinCheckInterval) || (progressPercent === 100); if (checkProgress) { this.lastProgressPercent = progressPercent; this.progressCallback(progressPercent); } } } }; }; /* * 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 */ var cryptoPRNG = (function() { 'use strict'; return { _collectorsStarted: false, _collectors: [], _events: [], _entropy: '', _entropyCounter: 0, _maxEntropyCounter: 0, _collectorHashUpdater: null, _hashUpdaterInterval: 0, _fullEntropyCallback: null, _tools: null, _seedLength: 0, _prng: null, _privateKeyPem: '', _pinEntropy: 0, _usePrivateKeyEntropy: false, /** * Initializes all the round object fields. * * @function * @param hashUpdaterInterval * the interval, in millis, that the function that replaces the * current entropy content by an hash is called. By default it is * initialized to 5000. * @param maxEntropyCounter * number of maximum bits used to determine when the collectors * should stop collecting entropy. By default it is initialized * to 256. */ _initAndStart: function(hashUpdaterInterval, fullEntropyCallback) { this._collectorsStarted = false; this._collectors = []; this._events = []; this._entropy = ''; this._entropyCounter = 0; this._maxEntropyCounter = 256; this._collectorHashUpdater = null; this._hashUpdaterInterval = (hashUpdaterInterval ? hashUpdaterInterval : 1000); this._fullEntropyCallback = fullEntropyCallback; this._tools = cryptoPRNG.Tools.init(); this._initCollectors(); this._startCollectors(); }, /** * adds a new collector to the list of available connectors * * @param collector * must return an object that implements the startCollector and * stopCollector methods */ _addCollector: function(collector) { this._collectors.push(collector); }, /** * adds a new event to the list of available events * * @param type * event type * @param listener * function that will be executed */ _addEvent: function(type, listener) { this._events.push({'type': type, 'listener': listener}); }, /** * Initializes the random generator with the web kit random collector if * available. * * If it is not, it considers the rest of collectors: ajax calls collector, * JS calls execution collector, navigator information collector, math * random collector, web kit random collector, mouse events collectors, key * events collectors, load event collector, scroll collector) */ _initCollectors: function() { if (this._noWindow()) { this._addNonWindowEntropyCollectors(); return; } var _crypto = this._getCrypto(); if ((_crypto) && (_crypto.getRandomValues)) { this._addCollector(cryptoPRNG.Collectors.getWebKitRandomCollector( _crypto, this._maxEntropyCounter)); } else { this._addNonWindowEntropyCollectors(); // The API detects and delivers accelerometer data 50 times per // second if (window.DeviceOrientationEvent) { // Listen for the device orientation event and handle // DeviceOrientationEvent object this._addEvent( 'deviceorientation', cryptoPRNG.Collectors.getDeviceOrientationCollector); } else if (window.OrientationEvent) { // Listen for the MozOrientation event and handle // OrientationData object this._addEvent( 'MozOrientation', cryptoPRNG.Collectors.getDeviceOrientationCollector); } if (window.DeviceMotionEvent) { this._addEvent( 'devicemotion', cryptoPRNG.Collectors.getDeviceMotionCollector); } } }, _addNonWindowEntropyCollectors: function() { if (this._usePrivateKeyEntropy) { this._addCollector(cryptoPRNG.Collectors.getPrivateKeyCollector( this._privateKeyPem, this._pinEntropy)); } this._addCollector(cryptoPRNG.Collectors.getAjaxCollector()); this._addCollector(cryptoPRNG.Collectors.getJSExecutionCollector()); this._addCollector(cryptoPRNG.Collectors.getNavigatorInfoCollector()); this._addCollector(cryptoPRNG.Collectors.getMathRandomCollector()); // mouse events collectors this._addEvent('mousemove', cryptoPRNG.Collectors.getMouseMoveCollector); this._addEvent( 'mousewheel', cryptoPRNG.Collectors.getMouseWheelCollector); this._addEvent('mouseup', cryptoPRNG.Collectors.getMouseUpCollector); this._addEvent('mousedown', cryptoPRNG.Collectors.getMouseDownCollector); this._addEvent( 'touchstart', cryptoPRNG.Collectors.getTouchStartCollector); this._addEvent('touchmove', cryptoPRNG.Collectors.getTouchMoveCollector); this._addEvent('touchend', cryptoPRNG.Collectors.getTouchEndCollector); this._addEvent( 'gesturestart', cryptoPRNG.Collectors.getGestureStartCollector); this._addEvent( 'gestureend', cryptoPRNG.Collectors.getGestureEndCollector); // keyboard events collectors this._addEvent('keyup', cryptoPRNG.Collectors.getKeyUpCollector); this._addEvent('keydown', cryptoPRNG.Collectors.getKeyDownCollector); // page events collectors this._addEvent('load', cryptoPRNG.Collectors.getLoadCollector); this._addEvent('scroll', cryptoPRNG.Collectors.getScrollCollector); // requests collector collectors this._addEvent('beforeload', cryptoPRNG.Collectors.getRequestsCollector); }, _getCrypto: function() { return window.crypto || window.msCrypto; }, _noWindow: function() { return (typeof window === 'undefined'); }, /** * Start the collectors */ _startCollectors: function() { if (this._collectorsStarted) { return; } var i = 0; // start all the collectors while (i < this._collectors.length) { try { this._collectors[i].startCollector(); } catch (e) { // do not do anything about any exception that is thrown // by the execution of the collectors } i++; } i = 0; // start all the events while (i < this._events.length) { try { if (window.addEventListener) { window.addEventListener( this._events[i].type, this._events[i].listener, false); } else if (document.attachEvent) { document.attachEvent( 'on' + this._events[i].type, this._events[i].listener); } } catch (e) { // do not do anything about any exception that is thrown // by the execution of the events } i++; } this._tools._mdUpdate.start(); this._collectorHashUpdater = setInterval(function() { cryptoPRNG._hashUpdater(); }, this._hashUpdaterInterval); this._collectorsStarted = true; }, /** * Stop the collectors */ _stopCollectors: function() { if (!this._collectorsStarted) { return; } var i = 0; while (i < this._collectors.length) { try { this._collectors[i].stopCollector(); } catch (e) { // do not do anything about any exception that is thrown // by the execution of the collectors } i++; } i = 0; while (i < this._events.length) { try { if (window.removeEventListener) { window.removeEventListener( this._events[i].type, this._events[i].listener, false); } else if (document.detachEvent) { document.detachEvent( 'on' + this._events[i].type, this._events[i].listener); } } catch (e) { // do not do anything about any exception that is thrown // by the remove of the events } i++; } if (this._collectorHashUpdater) { clearInterval(this._collectorHashUpdater); } this._collectorsStarted = false; }, /** * Usually this method is called from the collectors and events. It adds * entropy to the already collected entropy and increments the entropy * counter * * @param data * the entropy data to be added * @param entropyCounter * the entropy counter to be added */ _collectEntropy: function(data, entropyCounter) { this._entropy += data; this._entropyCounter += (entropyCounter ? entropyCounter : 0); // if the entropy counter has reach the limit, update the hash and stop // the collectors if (this._entropyCounter >= this._maxEntropyCounter && this._collectorsStarted) { this._hashUpdater(); } }, /** * Sets the entropy data with the hash that is created using the current * entropy data value. If the entropy counter has reached the max entropy * counter set, it stops the collectors * * @return the generated entropy hash */ _hashUpdater: function() { var entropyHash = this._updateEntropyHash(); if (this._entropyCounter >= this._maxEntropyCounter && this._collectorsStarted) { entropyHash = this._stopCollectEntropy(); var newPRNG = new cryptoPRNG.PRNG(entropyHash, this._tools); if (this._fullEntropyCallback) { this._fullEntropyCallback(newPRNG); } else { this._prng = newPRNG; } } return entropyHash; }, _stopCollectEntropy: function() { // stop collecting entropy this._stopCollectors(); var entropyHash = this._updateEntropyHash(); this._entropy = ''; this._entropyCounter = 0; return entropyHash; }, _updateEntropyHash: function() { var digester = this._tools._mdUpdate; digester.update(this._entropy); var entropyHash = forge.util.bytesToHex(digester.digest().getBytes()); this._entropy = entropyHash; return entropyHash; }, /** * Set Private Key and an Entropy it adds to be used to collect Entropy. The * amount of randomness(entropy) depends on the pin that is used for Private * Key generating. * * @function * @param privateKey * {string} private key, as string in PEM format. * @param pinEntropy * the amount of randomness that can be extracted from the * Private Key. */ setDataForPrivateKeyEntropyCollector: function(privateKeyPem, pinEntropy) { if (privateKeyPem && (!isNaN(pinEntropy)) && (pinEntropy > 0)) { this._privateKeyPem = privateKeyPem; this._pinEntropy = pinEntropy; this._usePrivateKeyEntropy = true; } }, /** * Initializes collectors and starts them. Set Private Key and Entropy * before if this information should be used to collect Entropy. */ startEntropyCollection: function(entropyCollectorCallback) { var hashUpdaterInterval = 1000; this._initAndStart(hashUpdaterInterval, entropyCollectorCallback); }, /** * In the case that the maximum amount of entropy had been reached before * calling this method, it does not do anything, and a false is returned. * Otherwise, this method creates a PRNG if the collectors have not gathered * the maximum amount of entropy. In addition, it stops the collectors. * * This method return a true if the maximum entropy was already reached and * false in other case. */ stopEntropyCollectionAndCreatePRNG: function() { var generatedWithMaximumEntropy = true; if (!this._prng) { var entropyHash = this._stopCollectEntropy(); this._prng = new cryptoPRNG.PRNG(entropyHash, this._tools); generatedWithMaximumEntropy = false; } return generatedWithMaximumEntropy; }, /** * This method creates a PRNG from a given seed, in Hexadecimal format. This * method is to be used from a Worker, which has received the seed from the * main thread. * * @param seed * A string of 64 characters. It represents a 32-byte array in * Hexadecimal. * */ createPRNGFromSeed: function(seed) { this._prng = new cryptoPRNG.PRNG(seed, this._tools); }, /** * Generates a random array of bytes, which is then formatted to hexadecimal * This method is to be called from the main thread, and the given string is * to be passed to a Worker. */ generateRandomSeedInHex: function(lengthSeed) { this._seedLength = (lengthSeed ? lengthSeed : 32); var seedInBytes = this._prng.generate(this._seedLength); return forge.util.bytesToHex(seedInBytes); }, getPRNG: function() { if (!this._prng) { throw new Error('The PRNG has not been initialized yet'); } return this._prng; }, getEntropyCollectedAsPercentage: function() { if (this._entropyCounter >= this._maxEntropyCounter) { return 100; } return this._entropyCounter * 100 / this._maxEntropyCounter; } }; })(); cryptoPRNG.Tools = (function() { 'use strict'; return { _mdUpdate: null, _mdReseed: null, _formatKey: null, _formatSeed: null, _cipher: null, init: function() { this._mdUpdate = forge.md.sha256.create(); this._mdReseed = forge.md.sha256.create(); this._formatKey = function(key) { // convert the key into 32-bit integers var tmp = forge.util.createBuffer(key); key = [ tmp.getInt32(), tmp.getInt32(), tmp.getInt32(), tmp.getInt32(), tmp.getInt32(), tmp.getInt32(), tmp.getInt32(), tmp.getInt32() ]; // return the expanded key return forge.aes._expandKey(key, false); }; this._formatSeed = function(seed) { // convert seed into 32-bit integers var tmp = forge.util.createBuffer(seed); seed = [ tmp.getInt32(), tmp.getInt32(), tmp.getInt32(), tmp.getInt32() ]; return seed; }; this._cipher = function(key, counter) { var aes_output = []; forge.aes._updateBlock( this._formatKey(key), this._formatSeed('' + counter), aes_output, false); var aes_buffer = forge.util.createBuffer(); aes_buffer.putInt32(aes_output[0]); aes_buffer.putInt32(aes_output[1]); aes_buffer.putInt32(aes_output[2]); aes_buffer.putInt32(aes_output[3]); return aes_buffer.getBytes(); }; return this; } }; })(); cryptoPRNG.PRNG = function(entropyHash, tools) { 'use strict'; this._key = ''; this._counter = 0; this._tools = null; if (tools) { this._tools = tools; } else { this._tools = cryptoPRNG.Tools.init(); } this._entropyHash = entropyHash; }; cryptoPRNG.PRNG.prototype = (function() { 'use strict'; return { /** * Generates a random array of bytes using the gathered entropy data. * * @param count * the total number of random bytes to generate */ generate: function(count) { var keyAux; if (this._key === null || this._key === '') { this._reseed(); } // buffer where to store the random bytes var b = forge.util.createBuffer(); var limitReach = 1; while (b.length() < count) { b.putBytes(this._tools._cipher(this._key, this._counter)); this._counter++; if (b.length >= (limitReach * Math.pow(2, 20))) { keyAux = this._key; this._key = ''; for (var i = 0; i < 2; i++) { this._key += this._tools._cipher(keyAux, this._counter); this._counter++; } limitReach++; } } // do it two times to ensure a key with 256 bits keyAux = this._key; this._key = ''; for (var j = 0; j < 2; j++) { this._key += this._tools._cipher(keyAux, this._counter); this._counter++; } return b.getBytes(count); }, /** * Reseeds the generator */ _reseed: function() { var digester = this._tools._mdReseed; digester.start(); digester.update(this._entropyHash); digester.update(this._key); this._key = forge.util.bytesToHex(digester.digest().getBytes()); this._counter++; } }; })(); cryptoPRNG.Collectors = (function() { 'use strict'; var MIN_MATH_ROUND_ENTROPY_FACTOR = 1000000; function _getMathRoundWithEntropy(data) { return Math.round(data * MIN_MATH_ROUND_ENTROPY_FACTOR); } return { getAjaxCollector: function() { return { startCollector: function() { // if jQuery is included if (window.jQuery !== undefined) { $(window) .ajaxStart(function() { cryptoPRNG._collectEntropy((+new Date()), 1); }) .ajaxComplete(function() { cryptoPRNG._collectEntropy((+new Date()), 1); }); } }, stopCollector: function() { // if jQuery is included if (window.jQuery !== undefined) { $(window).unbind('ajaxStart'); $(window).unbind('ajaxComplete'); } return true; } }; }, getJSExecutionCollector: function() { return { _collectTimeout: null, startCollector: function() { var timer_start = (+new Date()), timer_end; this._collectTimeout = (function collect() { timer_end = (+new Date()); var total_time = timer_end - timer_start; // because of browser baseline time checks we limit it if (total_time > 20) { cryptoPRNG._collectEntropy((+new Date()), 1); } timer_start = timer_end; return setTimeout(collect, 0); })(); }, stopCollector: function() { clearTimeout(this._collectTimeout); } }; }, getNavigatorInfoCollector: function() { return { startCollector: function() { if (typeof(navigator) !== 'undefined') { var _navString = ''; for (var key in navigator) { if (typeof key !== 'undefined') { try { if (typeof(navigator[key]) === 'string') { _navString += navigator[key]; } } catch (e) { // ignore any kind of exception } } } cryptoPRNG._collectEntropy(_navString, 1); } }, stopCollector: function() { // just executed once, so no need to execute nothing more return true; } }; }, getMathRandomCollector: function() { return { startCollector: function() { cryptoPRNG._collectEntropy(Math.random(), 0); }, stopCollector: function() { // just executed once, so no need to execute nothing more return true; } }; }, getPrivateKeyCollector: function(privateKeyPem, pinEntropy) { var privateKey = forge.pki.privateKeyFromPem(privateKeyPem); return { pkCollector: true, startCollector: function() { cryptoPRNG._collectEntropy(privateKey.d, pinEntropy); }, stopCollector: function() { // just executed once, so no need to execute nothing more return true; } }; }, getWebKitRandomCollector: function(globalCrypto, entropyQuantity) { return { startCollector: function() { var numPositions = entropyQuantity / 32; // get cryptographically strong entropy in Webkit var ab = new Uint32Array(numPositions); globalCrypto.getRandomValues(ab); var data = ''; for (var i = 0; i < ab.length; i++) { data += '' + (ab[i]); } cryptoPRNG._collectEntropy(data, entropyQuantity); }, stopCollector: function() { // just executed once, so no need to execute nothing more return true; } }; }, getMouseMoveCollector: function(ev) { // to ensure compatibility with IE 8 ev = ev || window.event; cryptoPRNG._collectEntropy( (ev.x || ev.clientX || ev.offsetX || 0) + (ev.y || ev.clientY || ev.offsetY || 0) + (+new Date()), 3); }, getMouseWheelCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy((ev.offsetY || 0) + (+new Date()), 3); }, getMouseDownCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy( (ev.x || ev.clientX || ev.offsetX || 0) + (ev.y || ev.clientY || ev.offsetY || 0) + (+new Date()), 3); }, getMouseUpCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy( (ev.x || ev.clientX || ev.offsetX || 0) + (ev.y || ev.clientY || ev.offsetY || 0) + (+new Date()), 3); }, getTouchStartCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy( (ev.touches[0].pageX || ev.clientX || 0) + (ev.touches[0].pageY || ev.clientY || 0) + (+new Date()), 5); }, getTouchMoveCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy( (ev.touches[0].pageX || ev.clientX || 0) + (ev.touches[0].pageY || ev.clientY || 0) + (+new Date()), 3); }, getTouchEndCollector: function() { cryptoPRNG._collectEntropy((+new Date()), 1); }, getGestureStartCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy( (ev.touches[0].pageX || ev.clientX || 0) + (ev.touches[0].pageY || ev.clientY || 0) + (+new Date()), 5); }, getGestureEndCollector: function() { cryptoPRNG._collectEntropy((+new Date()), 1); }, motionX: null, motionY: null, motionZ: null, getDeviceMotionCollector: function(ev) { ev = ev || window.event; var acceleration = ev.accelerationIncludingGravity; var currentX = (_getMathRoundWithEntropy(acceleration.x) || 0); var currentY = (_getMathRoundWithEntropy(acceleration.y) || 0); var currentZ = (_getMathRoundWithEntropy(acceleration.z) || 0); var rotation = ev.rotationRate; if (rotation !== null) { currentX += _getMathRoundWithEntropy(rotation.alpha); currentY += _getMathRoundWithEntropy(rotation.beta); currentZ += _getMathRoundWithEntropy(rotation.gamma); } // The API detects and delivers accelerometer data 50 times per // second // even if there is any event related, so this is // a way to control if it really changed or not if ((cryptoPRNG.Collectors.motionX === null) || (cryptoPRNG.Collectors.motionY === null) || (cryptoPRNG.Collectors.motionZ === null) || (cryptoPRNG.Collectors.motionX !== currentX) || (cryptoPRNG.Collectors.motionY !== currentY) || (cryptoPRNG.Collectors.motionZ !== currentZ)) { cryptoPRNG.Collectors.motionX = currentX; cryptoPRNG.Collectors.motionY = currentY; cryptoPRNG.Collectors.motionZ = currentZ; cryptoPRNG._collectEntropy( currentX + currentY + currentZ + (+new Date()), 1); } }, deviceOrientationX: null, deviceOrientationY: null, deviceOrientationZ: null, getDeviceOrientationCollector: function(ev) { ev = ev || window.event; var currentX = (_getMathRoundWithEntropy(ev.gamma) || _getMathRoundWithEntropy(ev.x) || 0); var currentY = (_getMathRoundWithEntropy(ev.beta) || _getMathRoundWithEntropy(ev.y) || 0); var currentZ = (_getMathRoundWithEntropy(ev.alpha) || _getMathRoundWithEntropy(ev.z) || 0); // The API detects and delivers accelerometer data 50 times per // second // even if there is any event related, so this is // a way to control if it really changed or not if ((cryptoPRNG.Collectors.deviceOrientationX === null) || (cryptoPRNG.Collectors.deviceOrientationY === null) || (cryptoPRNG.Collectors.deviceOrientationZ === null) || (cryptoPRNG.Collectors.deviceOrientationX !== currentX) || (cryptoPRNG.Collectors.deviceOrientationY !== currentY) || (cryptoPRNG.Collectors.deviceOrientationZ !== currentZ)) { cryptoPRNG.Collectors.deviceOrientationX = currentX; cryptoPRNG.Collectors.deviceOrientationY = currentY; cryptoPRNG.Collectors.deviceOrientationZ = currentZ; cryptoPRNG._collectEntropy( currentX + currentY + currentZ + (+new Date()), 1); } }, getKeyDownCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3); }, getKeyUpCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3); }, getLoadCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3); }, getScrollCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3); }, getRequestsCollector: function(ev) { ev = ev || window.event; cryptoPRNG._collectEntropy((ev.keyCode) + (+new Date()), 3); } }; })(); /* * 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 = cryptolib.modules || {}; /** * Defines the generation and opening of digital envelopes. */ cryptolib.modules.digitalenvelope = function(box) { 'use strict'; box.digitalenvelope = box.digitalenvelope || {}; box.digitalenvelope.factory = {}; var policies = { symmetric: { secretkey: { encryption: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey .encryption.lengthBytes }, mac: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey.mac.lengthBytes } } } }; var exceptions; var converters; var bitOperators; var secretKeyFactory; var macDigester; var symmetricCipher; var asymmetricCipher; var f = function(box) { exceptions = box.commons.exceptions; converters = new box.commons.utils.Converters(); bitOperators = new box.commons.utils.BitOperators(); secretKeyFactory = new box.symmetric.secretkey.factory.SecretKeyFactory(); macDigester = (new box.symmetric.mac.factory.MacFactory()).create(); symmetricCipher = (new box.symmetric.cipher.factory.SymmetricCipherFactory()) .getCryptoSymmetricCipher(); asymmetricCipher = (new box.asymmetric.cipher.factory.AsymmetricCipherFactory()) .getCryptoAsymmetricCipher(); }; f.policies = { symmetric: { secretkey: { encryption: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey .encryption.lengthBytes }, mac: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey.mac.lengthBytes }, secureRandom: { provider: box.policies.digitalenvelope.symmetric.secretkey .secureRandom.provider } }, cipher: { provider: box.policies.digitalenvelope.symmetric.cipher.provider, algorithm: { AES128_GCM: { name: box.policies.digitalenvelope.symmetric.cipher.algorithm .AES128_GCM.name, keyLengthBytes: box.policies.digitalenvelope.symmetric.cipher .algorithm.AES128_GCM.keyLengthBytes, tagLengthBytes: box.policies.digitalenvelope.symmetric.cipher .algorithm.AES128_GCM.tagLengthBytes } }, initializationVectorLengthBytes: box.policies.digitalenvelope.symmetric.cipher .initializationVectorLengthBytes, secureRandom: { provider: box.policies.digitalenvelope.symmetric.cipher.secureRandom .provider } }, mac: { hash: box.policies.digitalenvelope.symmetric.mac.hash, provider: box.policies.digitalenvelope.symmetric.mac.provider } }, asymmetric: { cipher: { algorithm: box.policies.digitalenvelope.asymmetric.cipher.algorithm, secretKeyLengthBytes: box.policies.digitalenvelope.asymmetric.cipher.secretKeyLengthBytes, ivLengthBytes: box.policies.digitalenvelope.asymmetric.cipher.ivLengthBytes, tagLengthBytes: box.policies.digitalenvelope.asymmetric.cipher.tagLengthBytes, deriver: box.policies.digitalenvelope.asymmetric.cipher.deriver, hash: box.policies.digitalenvelope.asymmetric.cipher.hash, symmetricCipher: box.policies.digitalenvelope.asymmetric.cipher.symmetricCipher, provider: box.policies.digitalenvelope.asymmetric.cipher.provider, secureRandom: { provider: box.policies.digitalenvelope.asymmetric.cipher.secureRandom .provider } } } }; cryptolib('commons', 'symmetric', 'asymmetric.cipher', f); /** * Defines a factory for the creation of digital envelope generators and * openers. * * @class DigitalEnvelopeFactory * @memberof digitalenvelope */ box.digitalenvelope.factory.DigitalEnvelopeFactory = function() { }; box.digitalenvelope.factory.DigitalEnvelopeFactory.prototype = { /** * Obtains a new digital envelope generator. * * @function getDigitalEnvelopeGenerator * @return {DigitalEnvelopeGenerator} the digital envelope generator. */ getDigitalEnvelopeGenerator: function() { try { return new box.digitalenvelope.DigitalEnvelopeGenerator(); } catch (error) { throw new exceptions.CryptoLibException( 'Could not obtain a digital envelope generator.', error); } }, /** * Obtains new a digital envelope opener. * * @function getDigitalEnvelopeOpener * @return {DigitalEnvelopeOpener} the digital envelope opener. */ getDigitalEnvelopeOpener: function() { try { return new box.digitalenvelope.DigitalEnvelopeOpener(); } catch (error) { throw new exceptions.CryptoLibException( 'Could not obtain a digital envelope opener.', error); } } }; /** * Defines a digital envelope generator. * * @class DigitalEnvelopeGenerator * @memberof digitalenvelope */ box.digitalenvelope.DigitalEnvelopeGenerator = function() { }; box.digitalenvelope.DigitalEnvelopeGenerator.prototype = { /** * Generates a digital envelope for some data. The envelope can be * generated with one or more public keys, each of whose corresponding * private key can be used to open the envelope. * * @function generate * @param dataBase64 * {string} the data to store in the digital envelope, as a * string in Base64 encoded format. * @param publicKeysPem * {string[]} a list of one or more public keys used to * generate the digital envelope, as an array of strings in * PEM format. * @return {DigitalEnvelope} the generated digital envelope. */ generate: function(dataBase64, publicKeysPem) { try { validateGeneratorInput(dataBase64, publicKeysPem); // Generate encryption secret key and use it to symmetrically encrypt // data. var secretKeyForEncryptionBase64 = secretKeyFactory.getCryptoSecretKeyGeneratorForEncryption() .generate(); var encryptedDataBase64 = symmetricCipher.encrypt(secretKeyForEncryptionBase64, dataBase64); // Generate MAC secret key and use it to generate MAC of symmetrically // Base64 encryption of encrypted data. var secretKeyForMacBase64 = secretKeyFactory.getCryptoSecretKeyGeneratorForMac().generate(); var encryptedDataBase64Base64 = converters.base64Encode(encryptedDataBase64); var macBase64 = macDigester.generate( secretKeyForMacBase64, [encryptedDataBase64Base64]); // Construct bit-wise concatenation of encryption and MAC secret keys. var secretKeyForEncryption = converters.base64Decode(secretKeyForEncryptionBase64); var secretKeyForMac = converters.base64Decode(secretKeyForMacBase64); var secretKeyConcatenation = secretKeyForEncryption.toString() + secretKeyForMac.toString(); var secretKeyConcatenationBase64 = converters.base64Encode(secretKeyConcatenation); // Generate list of asymmetric encryptions of the secret key // concatenation, each encryption created using a different one of the // public keys provided as input for the digital envelope generation. var secretKeyEncryptions = []; for (var i = 0; i < publicKeysPem.length; i++) { var publicKeyPem = publicKeysPem[i]; var encryptedSecretKeyConcatentionBase64 = asymmetricCipher.encrypt( publicKeyPem, secretKeyConcatenationBase64); secretKeyEncryptions.push(new SecretKeyEncryption( encryptedSecretKeyConcatentionBase64, publicKeyPem)); } return new DigitalEnvelope( encryptedDataBase64, macBase64, secretKeyEncryptions); } catch (error) { throw new exceptions.CryptoLibException( 'Digital envelope could not be generated.', error); } } }; /** * Defines a digital envelope opener. * * @class DigitalEnvelopeOpener * @memberof digitalenvelope */ box.digitalenvelope.DigitalEnvelopeOpener = function() { this._getEncryptedSecretKeyConcatenation = function( secretKeyEncryptions, privateKeyPem) { // Extract modulus and public exponent from private key. var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem); var modulus = privateKey.n; var publicExponent = privateKey.e; for (var i = 0; i < secretKeyEncryptions.length; i++) { var publicKeyPem = secretKeyEncryptions[i].getPublicKey(); var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem); if (publicKey.n.equals(modulus) && publicKey.e.equals(publicExponent)) { return secretKeyEncryptions[i].getEncryptedSecretKeyConcatenation(); } } throw new exceptions.CryptoLibException( 'Could not find an asymmetric encryption of the secret key concatenation that could be decrypted using the private key provided.'); }; }; box.digitalenvelope.DigitalEnvelopeOpener.prototype = { /** * Opens a digital envelope and retrieves its data. The envelope can be * opened with any private key corresponding to a public key that was * used to generate the envelope. * * @function open * @param digitalEnvelope * {DigitalEnvelope} the digital envelope to open. * @param privateKeyPem * {string} the private key used to open the digital * envelope, as a string in PEM format. * @return {string} the data stored in the digital envelope, as a string * in Base64 encoded format. */ open: function(digitalEnvelope, privateKeyPem) { try { validateOpenerInput(digitalEnvelope, privateKeyPem); // Retrieve data from digital envelope. var encryptedDataBase64 = digitalEnvelope.getEncryptedData(); var macBase64 = digitalEnvelope.getMac(); var secretKeyEncryptions = digitalEnvelope.getSecretKeyEncryptions(); // Retrieve asymmetric encryption of bit-wise concatenation of // encryption and MAC secret keys that can be decrypted with private // key provided as input. var encryptedSecretKeyConcatenationBase64 = this._getEncryptedSecretKeyConcatenation( secretKeyEncryptions, privateKeyPem); // Asymmetrically decrypt bit-wise concatenation of encryption and MAC // secret keys. var secretKeyConcatenationBase64 = asymmetricCipher.decrypt( privateKeyPem, encryptedSecretKeyConcatenationBase64); var secretKeyConcatenation = converters.base64Decode(secretKeyConcatenationBase64); // Extract secret keys from bit-wise concatenation. var secretKeyForEncryptionLength = policies.symmetric.secretkey.encryption.lengthBytes; var secretKeyForMacLength = policies.symmetric.secretkey.mac.lengthBytes; var secretKeyForEncryption = bitOperators.extract( secretKeyConcatenation, 0, secretKeyForEncryptionLength); var secretKeyForEncryptionBase64 = converters.base64Encode(secretKeyForEncryption); var secretKeyForMac = bitOperators.extract( secretKeyConcatenation, secretKeyForEncryptionLength, secretKeyForEncryptionLength + secretKeyForMacLength); var secretKeyForMacBase64 = converters.base64Encode(secretKeyForMac); // Check integrity of digital envelope. var encryptedDataBase64Base64 = converters.base64Encode(encryptedDataBase64); if (!macDigester.verify( macBase64, secretKeyForMacBase64, [encryptedDataBase64Base64])) { throw new exceptions.CryptoLibException( 'Integrity of digital envelope could not be verified.'); } return symmetricCipher.decrypt( secretKeyForEncryptionBase64, encryptedDataBase64); } catch (error) { throw new exceptions.CryptoLibException( 'Digital envelope could not be opened.', error); } } }; /** * Container class for a digital envelope. * * @class DigitalEnvelope * @param encryptedDataBase64 * {string} the symmetrically encrypted data, as a string in * Base64 encoded format. * @param macBase64 * {string} the MAC of the symmetrically encrypted data, as a * string in Base64 encoded format. * @param secretKeyEncryptions * {SecretKeyEncryption[]} the list of asymmetric encryptions of * the secret key concatenation, each encryption having been * created using a different one of the public keys provided as * input to the digital envelope generator. * @memberof digitalenvelope */ function DigitalEnvelope( encryptedDataBase64, macBase64, secretKeyEncryptions) { this.encryptedDataBase64 = encryptedDataBase64; this.macBase64 = macBase64; this.secretKeyEncryptions = secretKeyEncryptions; var _encryptedSecretKeyConcatsBase64 = []; var _publicKeysPem = []; for (var i = 0; i < secretKeyEncryptions.length; i++) { _encryptedSecretKeyConcatsBase64[i] = secretKeyEncryptions[i].getEncryptedSecretKeyConcatenation(); _publicKeysPem[i] = secretKeyEncryptions[i].getPublicKey(); } this._getEncryptedSecretKeyConcatsBase64 = function() { return _encryptedSecretKeyConcatsBase64; }; this._getPublicKeysPem = function() { return _publicKeysPem; }; } DigitalEnvelope.prototype = { /** * @function getEncryptedData * @return {string} the symmetrically encrypted data, as a string in * Base64 encoded format. */ getEncryptedData: function() { return this.encryptedDataBase64; }, /** * @function getMac * @return {string} the MAC of the symmetrically encrypted data, as a * string in Base64 encoded format. */ getMac: function() { return this.macBase64; }, /** * @function getSecretKeyEncryptions * @return {SecretKeyEncryption[]} the list of asymmetric encryptions of * the secret key concatenation. */ getSecretKeyEncryptions: function() { return this.secretKeyEncryptions; }, /** * Generates a string representation of the digital envelope. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function stringify * @return {string} the string representation of the digital envelope. */ stringify: function() { return JSON.stringify({ digitalEnvelope: { encryptedDataBase64: this.encryptedDataBase64, macBase64: this.macBase64, encryptedSecretKeyConcatsBase64: this._getEncryptedSecretKeyConcatsBase64(), publicKeysPem: this._getPublicKeysPem() } }); } }; /** * Container class for the asymmetric encryption of the bit-wise * concatenation of the secret key used to symmetrically encrypt the data of * a digital envelope and the secret key used to generate an MAC for * checking the integrity of the symmetric encryption. The public key used * to perform the asymmetric encryption of the secret key concatenation is * also included. * * @class SecretKeyEncryption * @param encryptedSecretKeyConcatBase64 * {string} the asymmetric encryption of the secret key * concatenation, using the public key provided as input, as a * string in Base64 encoded format. * @param publicKeyPem * {string} the public key, of type * {@link java.security.PublicKey}, used to asymmetrically * encrypt the secret key concatenation. * @memberof digitalenvelope */ function SecretKeyEncryption(encryptedSecretKeyConcatBase64, publicKeyPem) { /** * @function getEncryptedSecretKeyConcatenationBase64 * @return {string} the asymmetrically encrypted secret key * concatenation, as a string in Base64 encoded format. */ var getEncryptedSecretKeyConcatenation = function() { return encryptedSecretKeyConcatBase64; }; /** * @function getPublicKey * @return {string} the public key used to asymmetrically encrypt the * secret key concatenation, as a string in PEM format. */ var getPublicKey = function() { return publicKeyPem; }; return { getEncryptedSecretKeyConcatenation: getEncryptedSecretKeyConcatenation, getPublicKey: getPublicKey }; } /** * Deserializes a digital envelope. * * @function deserialize * @param serializedDigitalEnvelope * {string} the serialized digital envelope. * @return {DigitalEnvelope} the digital envelope after deserialization. * @memberof digitalenvelope */ box.digitalenvelope.deserialize = function(serializedDigitalEnvelope) { validateDeserializerInput(serializedDigitalEnvelope); var digitalEnvelopeJson = JSON.parse(serializedDigitalEnvelope).digitalEnvelope; var encryptedDataBase64 = digitalEnvelopeJson.encryptedDataBase64; var macBase64 = digitalEnvelopeJson.macBase64; var encryptedSecretKeyConcatsBase64 = digitalEnvelopeJson.encryptedSecretKeyConcatsBase64; var publicKeysPem = digitalEnvelopeJson.publicKeysPem; var secretKeyEncryptions = []; for (var i = 0; i < encryptedSecretKeyConcatsBase64.length; i++) { secretKeyEncryptions.push(new SecretKeyEncryption( encryptedSecretKeyConcatsBase64[i], publicKeysPem[i])); } return new DigitalEnvelope( encryptedDataBase64, macBase64, secretKeyEncryptions); }; function validateGeneratorInput(dataBase64, publicKeysPem) { if (typeof dataBase64 === 'undefined') { throw new exceptions.CryptoLibException( 'Data provided to digital envelope generator is undefined.'); } if (converters.base64Decode(dataBase64) === '') { throw new exceptions.CryptoLibException( 'Data provided to digital envelope generator is empty.'); } if (typeof dataBase64 !== 'string') { throw new exceptions.CryptoLibException( 'Base64 encoded data provided to digital envelope generator is not a String.'); } if (typeof publicKeysPem === 'undefined') { throw new exceptions.CryptoLibException( 'Public key array provided to digital envelope generator is undefined.'); } if (!(publicKeysPem instanceof Array)) { throw new exceptions.CryptoLibException( 'Public key array provided to digital envelope generator is not of type Array.'); } if (publicKeysPem.length === 0) { throw new exceptions.CryptoLibException( 'Public key array provided to digital envelope generator is empty.'); } } function validateOpenerInput(digitalEnvelope, privateKeyPem) { if (typeof digitalEnvelope === 'undefined') { throw new exceptions.CryptoLibException( 'Digital envelope provided to digital envelope opener is undefined.'); } if (!(digitalEnvelope instanceof Object)) { throw new exceptions.CryptoLibException( 'Digital envelope provided to digital envelope generator is not of type Object.'); } if (typeof privateKeyPem === 'undefined') { throw new exceptions.CryptoLibException( 'Private key provided to digital envelope opener is undefined.'); } if (typeof privateKeyPem !== 'string') { throw new exceptions.CryptoLibException( 'Private key PEM provided to digital envelope opener is not of type String.'); } } function validateDeserializerInput(serializedDigitalEnvelope) { if (typeof serializedDigitalEnvelope === 'undefined') { throw new exceptions.CryptoLibException( 'Serialized object provided to digital envelope deserializer is undefined.'); } if (typeof serializedDigitalEnvelope !== 'string') { throw new exceptions.CryptoLibException( 'Serialized object provided to digital envelope deserializer is not of type String.'); } if (serializedDigitalEnvelope.length === 0) { throw new exceptions.CryptoLibException( 'Serialized object provided to digital envelope deserializer is empty.'); } } }; /* * 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.digitalenvelope = cryptolib.modules.digitalenvelope || {}; /** * Defines the digital envelope service. * * @namespace digitalenvelope */ cryptolib.modules.digitalenvelope.service = function(box) { 'use strict'; box.digitalenvelope = box.digitalenvelope || {}; box.digitalenvelope.service = {}; var digitalEnvelopeFactory; cryptolib('digitalenvelope', function(box) { digitalEnvelopeFactory = new box.digitalenvelope.factory.DigitalEnvelopeFactory(); }); /** * Creates a digital envelope, using one or more public keys, each of whose * corresponding private key can be used to open the envelope and retrieve * the data. * * @function createDigitalEnvelope * @param dataBase64 * {string} the data to store in the digital envelope, as a * string in Base64 encoded format. * @param publicKeysPem * {string[]} a list of one or more public keys used to generate * the digital envelope, as an array of strings in PEM format. * @return {DigitalEnvelope} the newly created digital envelope. */ box.digitalenvelope.service.createDigitalEnvelope = function( dataBase64, publicKeysPem) { var generator = digitalEnvelopeFactory.getDigitalEnvelopeGenerator(); return generator.generate(dataBase64, publicKeysPem); }; /** * Opens an existing digital envelope and retrieves the data that it * contains. The envelope can be opened with any private key corresponding * to a public key that was used to generate the envelope. * * @function openDigitalEnvelope * @param digitalEnvelope * {DigitalEnvelope} the digital envelope to open. * @param privateKeyPem * {string} the private key used to open the digital envelope, as * a string in PEM format. * @return {string} the data contained in the digital envelope, as a string * in Base64 encoded format. */ box.digitalenvelope.service.openDigitalEnvelope = function( digitalEnvelope, privateKeyPem) { var opener = digitalEnvelopeFactory.getDigitalEnvelopeOpener(); return opener.open(digitalEnvelope, privateKeyPem); }; }; /* * 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.homomorphic = cryptolib.modules.homomorphic || {}; cryptolib.modules.homomorphic.cipher = function(box) { 'use strict'; box.homomorphic = box.homomorphic || {}; box.homomorphic.cipher = {}; /** * A submodule that holds ElGamal cipher functionalities. * * @exports homomorphic/cipher/factory */ box.homomorphic.cipher.factory = {}; var converters, exceptions, randomFactory, mathematical, keyPairFactory; var f = function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; mathematical = box.commons.mathematical; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); keyPairFactory = new box.homomorphic.keypair.factory.KeyFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.homomorphic.cipher.secureRandom.provider} } }; cryptolib('commons', 'primitives.securerandom', 'homomorphic.keypair', f); /** @class */ box.homomorphic.cipher.factory.ElGamalCipherFactory = function() { }; box.homomorphic.cipher.factory.ElGamalCipherFactory.prototype = { /** * @function createEncrypter * @returns ElGamalEncrypter */ createEncrypter: function(elGamalPublicKey, cryptoRandomInteger) { if (typeof cryptoRandomInteger === 'undefined') { cryptoRandomInteger = randomFactory.getCryptoRandomInteger(); } return new ElGamalEncrypter(elGamalPublicKey, cryptoRandomInteger); }, /** * @function createDecrypter * @returns ElGamalDecrypter */ createDecrypter: function(elGamalPrivateKey) { return new ElGamalDecrypter(elGamalPrivateKey); }, /** * @function createRandomDecrypter * @returns ElGamalRandomDecrypter */ createRandomDecrypter: function(elGamalPublicKey, cryptoRandomInteger) { return new ElGamalRandomDecrypter(elGamalPublicKey, cryptoRandomInteger); } }; function validateArray(inputArray) { if (typeof inputArray === 'undefined') { throw new exceptions.CryptoLibException( 'The given array should be initialized'); } if (!(inputArray instanceof Array)) { throw new exceptions.CryptoLibException( 'The given array are not from the expected type'); } if (inputArray === []) { throw new exceptions.CryptoLibException( 'The given array cannot be empty'); } } /** * Defines an ElGamal encrypter. *

* The methods specified in this object allow data to be encrypted using an * implementation of the ElGamal cryptosystem. * * @class ElGamalEncrypter * @param {ElGamalPublicKey} * elGamalPublicKey The public key. * @param {Object} * cryptoRandomInteger The source of randomness. * @returns {ElGamalEncrypter} */ function ElGamalEncrypter(elGamalPublicKey, cryptoRandomInteger) { var _elGamalPublicKey = elGamalPublicKey; var _cryptoRandomInteger = cryptoRandomInteger; /** * @function getElGamalPublicKey * * @returns {ElGamalPublicKey} */ this.getElGamalPublicKey = function() { return _elGamalPublicKey; }; /** * @function getCryptoRandomInteger * * @returns {ElGamalPublicKey} */ this.getCryptoRandomInteger = function() { return _cryptoRandomInteger; }; // Private methods this._validateMessagesNotLargerThanKeySize = function(length) { if (_elGamalPublicKey.getGroupElementsArray().length < length) { throw new exceptions.CryptoLibException( 'The list of messages to encrypt was larger than the number of public key elements.'); } }; this._validatePrePhisNotLargerThanKeySize = function(phis) { if (_elGamalPublicKey.getGroupElementsArray().length < phis.length) { throw new exceptions.CryptoLibException( 'The list of prePhis was larger than the number of public key elements.'); } }; this._preCompute = function(useShortExponent) { var gamma; var prePhis = []; try { var publicKeyElements = _elGamalPublicKey.getGroupElementsArray(); var group = _elGamalPublicKey.getGroup(); if (useShortExponent && !group.isQuadraticResidueGroup()) { throw new exceptions.CryptoLibException( 'Attempt to ElGamal encrypt using short exponent for Zp subgroup that is not of type quadratic residue.'); } var randomExponent = mathematical.groupUtils.generateRandomExponent( group, _cryptoRandomInteger, useShortExponent); gamma = group.getGenerator().exponentiate(randomExponent); // For each element in the array of public key elements, compute the // following: element: prephi[i] = pubKey[i]^(random) var pubKeyElementRaised; for (var i = 0; i < publicKeyElements.length; i++) { pubKeyElementRaised = publicKeyElements[i].exponentiate(randomExponent); prePhis.push(pubKeyElementRaised); } return new ElGamalEncrypterValues(randomExponent, gamma, prePhis); } catch (error) { throw new exceptions.CryptoLibException( 'There was an error while precomputing values.', error); } }; this._compute = function(messages, computationValues) { var compressedComputationValues = compressComputationValuesIfNecessary( messages.length, computationValues); var phis = []; // For each element in the array of messages, compute the following: // element:phi[i]=message[i]*prePhi[i] for (var i = 0; i < compressedComputationValues.getPhis().length; i++) { phis.push( messages[i].multiply(compressedComputationValues.getPhis()[i])); } return new box.homomorphic.cipher.ElGamalComputationValues( compressedComputationValues.getGamma(), phis); }; this._getListAsQuadraticResidues = function(messages) { var valuesZpSubgroupElements = []; var group = _elGamalPublicKey.getGroup(); var groupElement; for (var i = 0; i < messages.length; i++) { groupElement = new mathematical.ZpGroupElement( new box.forge.jsbn.BigInteger(messages[i]), group.getP(), group.getQ()); valuesZpSubgroupElements.push(groupElement); } return valuesZpSubgroupElements; }; function compressComputationValuesIfNecessary( numMessages, computationValues) { var phis = computationValues.getPhis(); if (phis.length <= numMessages) { return computationValues; } var compressedPhiArray = mathematical.groupUtils.buildListWithCompressedFinalElement( _elGamalPublicKey.getGroup(), phis, numMessages); return new box.homomorphic.cipher.ElGamalComputationValues( computationValues.getGamma(), compressedPhiArray); } } ElGamalEncrypter.prototype = { /** * Encrypt the received list of messages (which are represented as * ZpGroupElements). *

* The length of the received list of messages must be equal to, or less * than, the length of the public key of this encrypter. If this * condition is not met, then an exception will be thrown. * * @function encryptGroupElements * * @param {Array} * messages an array of ZpGroupElements. * @param {Object} [encryptionOption] an optional input parameter. If this option is of type * {boolean}, then the encryption will be pre-computed, using * the value of the boolean to determine whether or not to * use a short random exponent for the pre-computation. If * the option is of type {ElGamalEncrypterValues} then it * will be used as the pre-computation of the encryption. * * @returns {ElGamalEncrypterValues} */ encryptGroupElements: function(messages, encryptionOption) { validateArray(messages); this._validateMessagesNotLargerThanKeySize(messages.length); var useShortExponent; var preComputedValues; if (typeof encryptionOption !== 'undefined' && typeof encryptionOption === 'boolean') { useShortExponent = encryptionOption; } else { preComputedValues = encryptionOption; } if (!preComputedValues) { preComputedValues = this._preCompute(useShortExponent); } else { var prePhis = preComputedValues.getPhis(); validateArray(prePhis); this._validatePrePhisNotLargerThanKeySize(prePhis); } var computationValues = this._compute( messages, preComputedValues.getElGamalComputationValues()); return new ElGamalEncrypterValues( preComputedValues.getR(), computationValues.getGamma(), computationValues.getPhis()); }, /** * Pre-compute the ElGamal encrypter values, based on the ElGamal public * key provided to the encrypter. * * @param {boolean} * [useShortExponent] true if a short exponent is to be used * for the pre-computations. Default value is false. * @returns {ElGamalEncrypterValues} */ preCompute: function(useShortExponent) { return this._preCompute(useShortExponent); }, /** * Encrypt the received list of messages (which are represented as * Strings). *

* The length of the received list of messages must be equal to, or less * than, the length of the public key of this encrypter. If this * condition is not met, then an exception will be thrown. * * @function encryptStrings * * @param {Array} * messages an array of strings. * @param {ElGamalEncrypterValues} * [preComputedValues] an optional parameter. If it is not * provided, then pre-computations are made. * * @returns {ElGamalEncrypterValues} the pre-computed values. */ encryptStrings: function(messages, preComputedValues) { var messagesAsGroupElements = this._getListAsQuadraticResidues(messages); if (!preComputedValues) { return this.encryptGroupElements(messagesAsGroupElements); } else { return this.encryptGroupElements( messagesAsGroupElements, preComputedValues); } } }; /** * Defines an ElGamal decrypter. * * @class ElGamalDecrypter * @param {ElGamalPrivateKey} * elGamalPrivateKey The ElGamal private key. * @returns {ElGamalDecrypter} */ function ElGamalDecrypter(elGamalPrivateKey) { function validateCorrectGroup(elGamalPrivateKey) { var exponentsArray = elGamalPrivateKey.getExponentsArray(); var group = elGamalPrivateKey.getGroup(); for (var i = 0; i < exponentsArray.length; i++) { if (!(group.getQ().equals(exponentsArray[i].getQ()))) { throw new exceptions.CryptoLibException( 'Each Exponent must be of the specified group order.'); } } } validateCorrectGroup(elGamalPrivateKey); var _elGamalPrivateKey = elGamalPrivateKey; this.getElGamalPrivateKey = function() { return _elGamalPrivateKey; }; this._areGroupMembers = function(cipherText) { for (var i = 0; i < cipherText.getPhis().length; i++) { var next = cipherText.getPhis()[i]; if (!(_elGamalPrivateKey.getGroup().isGroupMember(next))) { return false; } } return true; }; this._validateCiphertextSize = function(cipherText) { if (_elGamalPrivateKey.getExponentsArray().length < cipherText.getPhis().length) { throw new exceptions.CryptoLibException( 'The list of ciphertext was larger than the number of private key exponents.'); } }; this._compressKeyIfNecessary = function(numRequired) { var exponents = _elGamalPrivateKey.getExponentsArray(); var group = _elGamalPrivateKey.getGroup(); if (exponents.length <= numRequired) { return _elGamalPrivateKey; } var listWithCompressedFinalExponent = mathematical.groupUtils.buildListWithCompressedFinalExponent( group, exponents, numRequired); return keyPairFactory.createPrivateKey( listWithCompressedFinalExponent, _elGamalPrivateKey.getGroup()); }; } ElGamalDecrypter.prototype = { /** * Decrypts a ciphertext. *

* The encrypted message parameter will be a list of group elements, * encapsulated within an ComputationValues object. *

* The length of the received ciphertext (number of group elements * contained within it) must be equal to, or less than, the length of * the private key of this decrypter. If this condition is not met, then * an exception will be thrown. * * @function decrypt * * @param {Array} * cipherText the encrypted message to be decrypted. * @param {Boolean} * [confirmGroupMembership] if true, a confirmation is made that * each element in * {@code ciphertext} is a member of the mathematical group * over which this decrypter operates. Default value is false. * @returns {Array} the decrypted ciphertext. */ decrypt: function(cipherText, confirmGroupMembership) { if (confirmGroupMembership === true && !this._areGroupMembers(cipherText)) { throw new exceptions.CryptoLibException( 'All values to decrypt must be group elements.'); } this._validateCiphertextSize(cipherText); var privateKeyAfterCompression = this._compressKeyIfNecessary(cipherText.getPhis().length); var plaintext = []; var negatedExponent; var exponentsArray = privateKeyAfterCompression.getExponentsArray(); for (var i = 0; i < cipherText.getPhis().length; i++) { // Compute the e = negate (-) of privKey[i] negatedExponent = exponentsArray[i].negate(); // Compute dm[i]= gamma^(e) * phi[i] plaintext.push(cipherText.getGamma() .exponentiate(negatedExponent) .multiply(cipherText.getPhis()[i])); } return plaintext; } }; /** * Defines an ElGamal decrypter that can be used to decrypt some ciphertext, * given the public key and the source of randomness used to generate the * ciphertext. * * @class ElGamalRandomDecrypter * @param {ElGamalPublicKey} * elGamalPublicKey The ElGamal public key. * @param {Object} cryptoRandomInteger The source of randomness. * @returns {ElGamalDecrypter} The ElGamal decrypter. */ function ElGamalRandomDecrypter(elGamalPublicKey, cryptoRandomInteger) { function validateCorrectGroup(elGamalPublicKey) { var elementsArray = elGamalPublicKey.getGroupElementsArray(); var group = elGamalPublicKey.getGroup(); for (var i = 0; i < elementsArray.length; i++) { if (!(group.getP().equals(elementsArray[i].getP()))) { throw new exceptions.CryptoLibException( 'Each element must be of the specified group modulus.'); } if (!(group.getQ().equals(elementsArray[i].getQ()))) { throw new exceptions.CryptoLibException( 'Each element must be of the specified group order.'); } } } validateCorrectGroup(elGamalPublicKey); var _elGamalPublicKey = elGamalPublicKey; var _cryptoRandomInteger = cryptoRandomInteger; this.getElGamalPublicKey = function() { return _elGamalPublicKey; }; this.getCryptoRandomInteger = function() { return _cryptoRandomInteger; }; this._areGroupMembers = function(cipherText) { for (var i = 0; i < cipherText.getPhis().length; i++) { var next = cipherText.getPhis()[i]; if (!(_elGamalPublicKey.getGroup().isGroupMember(next))) { return false; } } return true; }; this._validateCiphertextSize = function(cipherText) { if (_elGamalPublicKey.getGroupElementsArray().length < cipherText.getPhis().length) { throw new exceptions.CryptoLibException( 'The list of ciphertext was larger than the number of public key elements.'); } }; this._compressKeyIfNecessary = function(numRequired) { var publicKeyElements = _elGamalPublicKey.getGroupElementsArray(); var group = _elGamalPublicKey.getGroup(); if (publicKeyElements.length <= numRequired) { return _elGamalPublicKey; } var listWithCompressedFinalElement = mathematical.groupUtils.buildListWithCompressedFinalElement( group, publicKeyElements, numRequired); return keyPairFactory.createPublicKey( listWithCompressedFinalElement, _elGamalPublicKey.getGroup()); }; } ElGamalRandomDecrypter.prototype = { /** * Decrypts a ciphertext, using the public key and the source of randomness * used to generate the ciphertext.

The encrypted message parameter will * be a list of group elements, encapsulated within an ComputationValues * object.

The length of the received ciphertext (number of group * elements contained within it) must be equal to, or less than, the length * of the public key of this decrypter. If this condition is not met, then * an exception will be thrown. * * @function decrypt * * @param {Array} * cipherText the encrypted message to be decrypted. * @param {Boolean} * [confirmGroupMembership] if true, a confirmation is made that * each element in * {@code ciphertext} is a member of the mathematical group * over which this decrypter operates. Default value is false. * @param {boolean} * [useShortExponent] set to true if a short exponent was used * for the encryption. Default value is false. * @returns {Array} the decrypted messages. */ decrypt: function(cipherText, confirmGroupMembership, useShortExponent) { if (confirmGroupMembership === true && !this._areGroupMembers(cipherText)) { throw new exceptions.CryptoLibException( 'All values to decrypt must be group elements.'); } if (typeof useShortExponent === 'undefined') { useShortExponent = false; } this._validateCiphertextSize(cipherText); var publicKeyAfterCompression = this._compressKeyIfNecessary(cipherText.getPhis().length); var group = publicKeyAfterCompression.getGroup(); var publicKeyElements = publicKeyAfterCompression.getGroupElementsArray(); var plaintext = []; var negatedExponent; var randomExponent = mathematical.groupUtils.generateRandomExponent( group, this.getCryptoRandomInteger(), useShortExponent); negatedExponent = randomExponent.negate(); for (var i = 0; i < cipherText.getPhis().length; i++) { // Compute dm[i]= publicKeyElement^(-e) * phi[i] plaintext.push(publicKeyElements[i] .exponentiate(negatedExponent) .multiply(cipherText.getPhis()[i])); } return plaintext; } }; /** * Class which encapsulates an 'r' value (random exponent) and a set of * ElGamal encryption values (a gamma value and a list of phi values). * * @class ElGamalEncrypterValues * @param {Exponent} * exponent the Exponent to set. * @param {ZpGroupElement} * gamma the gamma (first element) of the ciphertext. * @param {Array} * phis the phi values of the ciphertext. * @returns {ElGamalEncrypterValues} */ function ElGamalEncrypterValues(exponent, gamma, phis) { var _r = exponent; var _gamma = gamma; var _phis = phis; /** * @function * * @returns {Exponent} */ this.getR = function() { return _r; }; /** * @function * * @returns {ZpGroupElement} */ this.getGamma = function() { return _gamma; }; /** * @function * * @returns {Array} */ this.getPhis = function() { return _phis; }; this.getElGamalComputationValues = function() { return new box.homomorphic.cipher.ElGamalComputationValues(_gamma, _phis); }; } box.homomorphic.cipher.ElGamalEncrypterValues = ElGamalEncrypterValues; /** * Encapsulates encryption parameters. *

* These parameters should have been generated as defined below: *

    *
  • For p and q: * http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf *
  • For g: * http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf *
  • For f: * http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf *
*

* Note that p, q, and g are received within the mathematical group. * * * @class EncryptionParameters * @param {ZpSubgroup} * group the group of the encryption parameters. * * @returns {EncryptionParameters} */ box.homomorphic.cipher.EncryptionParameters = function(group) { var _group = group; /** * @function * * @returns {ZpSubgroup} */ this.getEncParamGroup = function() { return _group; }; }; /** * Class which encapsulates a gamma and a set of phi values that are Zp * subgroup elements. * * @class ElGamalComputationValues * @param {ZpGroupElement} * gamma the gamma (first element) of the ciphertext. * @param {Array} * phis the phi values of the ciphertext. */ box.homomorphic.cipher.ElGamalComputationValues = function(gamma, phis) { this._gamma = gamma; this._phis = phis; }; box.homomorphic.cipher.ElGamalComputationValues.prototype = { getGamma: function() { return this._gamma; }, getPhis: function() { return this._phis; }, stringify: function() { var phis = Array(); for (var i = 0; i < this._phis.length; i++) { phis[i] = converters.base64FromBigInteger(this._phis[i].getElementValue()); } return JSON.stringify({ ciphertext: { p: converters.base64FromBigInteger(this._gamma.getP()), q: converters.base64FromBigInteger(this._gamma.getQ()), gamma: converters.base64FromBigInteger(this._gamma.getElementValue()), phis: phis } }); } }; box.homomorphic.cipher.deserializeElGamalComputationValues = function( serializedObject) { var valuesJson = JSON.parse(serializedObject).ciphertext; var p = converters.base64ToBigInteger(valuesJson.p); var q = converters.base64ToBigInteger(valuesJson.q); var gamma = new mathematical.ZpGroupElement( converters.base64ToBigInteger(valuesJson.gamma), p, q); var phis = []; for (var i = 0; i < valuesJson.phis.length; i++) { phis.push(new mathematical.ZpGroupElement( converters.base64ToBigInteger(valuesJson.phis[i]), p, q)); } return new box.homomorphic.cipher.ElGamalComputationValues(gamma, phis); }; }; /* * 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.homomorphic = cryptolib.modules.homomorphic || {}; cryptolib.modules.homomorphic.keypair = function(box) { 'use strict'; box.homomorphic = box.homomorphic || {}; box.homomorphic.keypair = {}; /** * A submodule that holds ElGamal key pair functionalities. * @exports homomorphic/keypair/factory */ box.homomorphic.keypair.factory = {}; var converters; var mathematical; var f = function(box) { converters = new box.commons.utils.Converters(); mathematical = box.commons.mathematical; }; cryptolib('commons.exceptions', 'commons.utils', 'commons.mathematical', f); /** @class */ box.homomorphic.keypair.factory.KeyFactory = function() { }; box.homomorphic.keypair.factory.KeyFactory.prototype = { /** * @function * @returns ElGamalKeyPair */ createKeyPair: function(elGamalPublicKey, elGamalPrivateKey) { return new ElGamalKeyPair(elGamalPublicKey, elGamalPrivateKey); }, /** * @function * @returns ElGamalPublicKey */ createPublicKey: function( serializedElGamalPublicKeyB64OrListOfGroupElements, keyGroup) { return new ElGamalPublicKey( serializedElGamalPublicKeyB64OrListOfGroupElements, keyGroup); }, /** * @function * @returns ElGamalPrivateKey */ createPrivateKey: function( serializedElGamalPrivateKeyB64OrListOfExponents, keyGroup) { return new ElGamalPrivateKey( serializedElGamalPrivateKeyB64OrListOfExponents, keyGroup); } }; /** * Representation of an ElGamal key pair. This key pair is composed of a * private key and a public key. * @class ElGamalKeyPair * @param elGamalPublicKey * ElGamal public key. * @param elGamalPrivateKey * ElGamal private key. * @returns {ElGamalKeyPair} */ function ElGamalKeyPair(elGamalPublicKey, elGamalPrivateKey) { var _elGamalPublicKey = elGamalPublicKey; var _elGamalPrivateKey = elGamalPrivateKey; this.getPublicKey = function() { return _elGamalPublicKey; }; this.getPrivateKey = function() { return _elGamalPrivateKey; }; } /** * Encapsulates an ElGamal public key. *

* This class includes the key itself (which is actually a list of * elements), as well as the mathematical group that this key belongs to. * @class ElGamalPublicKey * @param {String_or_Array} serializedElGamalPublicKeyB64OrListOfGroupElements * this parameter can be either a string containing the * serialized ElGamal public key to JSON string, or an array of * ZpGroupElement. * @param {ZpSubgroup} keyGroup * the ZpSubgroup of the public key. * @returns {ElGamalPublicKey} */ function ElGamalPublicKey( serializedElGamalPublicKeyB64OrListOfGroupElements, keyGroup) { var _group; var _groupElementsArray = []; var _fromJson = function(serializedElGamalPublicKey) { var parsedPublicKey = JSON.parse(serializedElGamalPublicKey).publicKey; var p = converters.base64ToBigInteger( parsedPublicKey.zpSubgroup.p.toString()); var q = converters.base64ToBigInteger( parsedPublicKey.zpSubgroup.q.toString()); var generator = converters.base64ToBigInteger( parsedPublicKey.zpSubgroup.g.toString()); _group = new mathematical.ZpSubgroup(generator, p, q); for (var i = 0; i < parsedPublicKey.elements.length; i++) { var elementValue = converters.base64ToBigInteger(parsedPublicKey.elements[i]); var element = new mathematical.ZpGroupElement( elementValue, _group.getP(), _group.getQ()); _groupElementsArray.push(element); } }; if (typeof keyGroup === 'undefined') { var serializedElGamalPublicKey = converters.base64Decode( serializedElGamalPublicKeyB64OrListOfGroupElements); _fromJson(serializedElGamalPublicKey); } else { _groupElementsArray = serializedElGamalPublicKeyB64OrListOfGroupElements; _group = keyGroup; } this.getGroup = function() { return _group; }; this.getGroupElementsArray = function() { return _groupElementsArray; }; } /** * Encapsulates an ElGamal private key. *

* This class includes the key itself (which is actually a list of * exponents), as well as the mathematical group that this key belongs to. * @class ElGamalPrivateKey * @param {String_or_Array} serializedElGamalPrivateKeyB64OrListOfExponents * this parameter can be either a string containing the * serialized to JSON string ElGamal private key * or an array of Exponents. * @param {ZpSubgroup} keyGroup * the ZpSubgroup of the private key. * @returns {ElGamalPrivateKey} */ function ElGamalPrivateKey( serializedElGamalPrivateKeyB64OrListOfExponents, keyGroup) { var _group; var _exponentsArray = []; var _fromJson = function(serializedElGamalPrivateKey) { var parsedPrivateKey = JSON.parse(serializedElGamalPrivateKey).privateKey; var p = converters.base64ToBigInteger( parsedPrivateKey.zpSubgroup.p.toString()); var q = converters.base64ToBigInteger( parsedPrivateKey.zpSubgroup.q.toString()); var generator = converters.base64ToBigInteger( parsedPrivateKey.zpSubgroup.g.toString()); _group = new mathematical.ZpSubgroup(generator, p, q); for (var i = 0; i < parsedPrivateKey.exponents.length; i++) { var exponentValue = converters.base64ToBigInteger(parsedPrivateKey.exponents[i]); var exponent = new mathematical.Exponent(_group.getQ(), exponentValue); _exponentsArray.push(exponent); } }; if (typeof keyGroup === 'undefined') { var serializedElGamalPrivateKey = converters.base64Decode( serializedElGamalPrivateKeyB64OrListOfExponents); _fromJson(serializedElGamalPrivateKey); } else { _exponentsArray = serializedElGamalPrivateKeyB64OrListOfExponents; _group = keyGroup; } this.getGroup = function() { return _group; }; this.getExponentsArray = function() { return _exponentsArray; }; } }; /* * 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.homomorphic = cryptolib.modules.homomorphic || {}; cryptolib.modules.homomorphic.service = function(box) { 'use strict'; box.homomorphic = box.homomorphic || {}; /** * A module that provides high-level ElGamal cryptographic services. * * @exports homomorphic/service */ box.homomorphic.service = {}; var elGamalFactory; var keyFactory; cryptolib('homomorphic.cipher', 'homomorphic.keypair', function(box) { elGamalFactory = new box.homomorphic.cipher.factory.ElGamalCipherFactory(); keyFactory = new box.homomorphic.keypair.factory.KeyFactory(); }); /** * @function createElGamalKeyPair * @param elGamalPublicKey * {Object} an ElGamal public key. * @param elGamalPrivateKey * {Object} an ElGamal private key. * * @returns {Object} an object of ElGamalKeyPair. */ box.homomorphic.service.createElGamalKeyPair = function( elGamalPublicKey, elGamalPrivateKey) { return keyFactory.createKeyPair(elGamalPublicKey, elGamalPrivateKey); }; /** * @function createElGamalPublicKey * @param serializedElGamalPublicKeyB64 * {String] a serialized ElGamalPublicKey, in base 64 encoded * format. * * @returns {Object} a ElGamalPublicKey, as an object. */ box.homomorphic.service.createElGamalPublicKey = function( serializedElGamalPublicKeyB64) { return keyFactory.createPublicKey(serializedElGamalPublicKeyB64); }; /** * @function createElGamalPrivateKey * @param serializedElGamalPrivateKeyB64 * {String} a serialized ElGamalPrivateKey, in base 64 encoded * format. * * @returns {Object} a ElGamalPrivateKey, as an object. */ box.homomorphic.service.createElGamalPrivateKey = function( serializedElGamalPrivateKeyB64) { return keyFactory.createPrivateKey(serializedElGamalPrivateKeyB64); }; /** * @function createEncrypter * @param elGamalPublicKey * {Object} a public key, as an ElGamalPublicKey object. * @param [cryptoRandomInteger] {Object} an optional parameter to set the * source of randomness for the generation of the ElGamal encryption * exponent. If not provided, the source will be created internally. * * @returns {Object} an ElGamalEncrypter. */ box.homomorphic.service.createEncrypter = function( elGamalPublicKey, cryptoRandomInteger) { return elGamalFactory.createEncrypter( elGamalPublicKey, cryptoRandomInteger); }; /** * @function createDecrypter * @param elGamalPrivateKey * {Object} a private key, as an ElGamalPrivateKey object. * * @returns {Object} an ElGamalDecrypter. */ box.homomorphic.service.createDecrypter = function(elGamalPrivateKey) { return elGamalFactory.createDecrypter(elGamalPrivateKey); }; /** * @function createRandomDecrypter * @param elGamalPublicKey * {Object} a public key, as an ElGamalPublicKey object. * @param cryptoRandomInteger {Object} the * source of randomness for the generation of the ElGamal encryption * exponent. * @returns {Object} an ElGamalRandomDecrypter. */ box.homomorphic.service.createRandomDecrypter = function( elGamalPublicKey, cryptoRandomInteger) { return elGamalFactory.createRandomDecrypter( elGamalPublicKey, cryptoRandomInteger); }; }; /* * 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); } } }; /* * 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/messagedigest */ cryptolib.modules.primitives.messagedigest = function(box) { 'use strict'; box.primitives = box.primitives || {}; box.primitives.messagedigest = box.primitives.messagedigest || {}; /** * A module that holds message digest functionalities. * @exports primitives/messagedigest */ box.primitives.messagedigest.factory = box.primitives.messagedigest.factory || {}; var policies = { messagedigest: { algorithm: box.policies.primitives.messagedigest.algorithm, provider: box.policies.primitives.messagedigest.provider } }; var converters, exceptions; cryptolib('commons', function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; }); /** * A factory class for creating a message digest generator and verifier. * @class */ box.primitives.messagedigest.factory.MessageDigestFactory = function() {}; box.primitives.messagedigest.factory.MessageDigestFactory.prototype = { /** * Gets a message dister object. * @function * @returns {primitives/messagedigest.CryptoForgeMessageDigest} */ getCryptoMessageDigest: function() { try { if (policies.messagedigest.provider === Config.primitives.messagedigest.provider.FORGE) { return this.getCryptoForgeMessageDigest(); } else { throw new exceptions.CryptoLibException( 'No suitable provider was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoSymmetricCipher could not be obtained.', error); } }, getCryptoForgeMessageDigest: function() { return new CryptoForgeMessageDigest(); } }; /** * Defines a message digest hash function. * @class * @memberof primitives/messagedigest */ function CryptoForgeMessageDigest() { this.digester = null; try { if (policies.messagedigest.algorithm === Config.primitives.messagedigest.algorithm.SHA256) { this.digester = forge.md.sha256.create(); } else if ( policies.messagedigest.algorithm === Config.primitives.messagedigest.algorithm.SHA512_224) { this.digester = forge.md.sha512.sha224.create(); } else { var errorMessage = 'Message digester type \'' + policies.messagedigest.algorithm + '\' not recognized by CryptoForgeMessageDigest.'; throw new exceptions.CryptoLibException(errorMessage); } } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeMessageDigest could not be created.', error); } } CryptoForgeMessageDigest.prototype = { /** * Initializes the message digest generator. * @function */ start: function() { try { this.digester.start(); } catch (error) { throw new exceptions.CryptoLibException( 'Message digest could not be initialized.', error); } }, /** * Updates the message digest generator. * @function * @param {Array} arrayDataBase64 * data to compute the hash, as an array of string in Base64 * encoded format. */ update: function(arrayDataBase64) { try { if (!arrayDataBase64 || arrayDataBase64.length < 1) { throw new exceptions.CryptoLibException( 'The array of data in Base64 should contain at least one element.'); } for (var i = 0; i < arrayDataBase64.length; i++) { this.digester.update(converters.base64Decode(arrayDataBase64[i])); } } catch (error) { throw new exceptions.CryptoLibException( 'Message digest could not be updated.', error); } }, /** * Generates the message digest for the updated data. * @function * @returns {string} Message digest, as string in Base64 encoded format. */ digest: function() { try { var messageDigest = this.digester.digest(); var rawOutput = messageDigest.getBytes(); var encodedOutput = converters.base64Encode(rawOutput); return encodedOutput; } catch (error) { throw new exceptions.CryptoLibException( 'Message digest could not be generated.', error); } } }; }; /* * 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/securerandom */ cryptolib.modules.primitives.securerandom = function(box) { 'use strict'; box.primitives = box.primitives || {}; box.primitives.securerandom = {}; /** * A module that holds secure random functionalities. * * @exports primitives/securerandom */ box.primitives.securerandom.factory = {}; var policies = { secureRandom: {provider: box.policies.primitives.secureRandom.provider} }; var converters, exceptions; cryptolib('commons', function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; }); /** * A factory class for creating random generators. * * @class */ box.primitives.securerandom.factory.SecureRandomFactory = function() { }; box.primitives.securerandom.factory.SecureRandomFactory.prototype = { /** * Instantiates a random byte generator. * * @function * @returns {primitives/securerandom.CryptoScytlRandomBytes} */ getCryptoRandomBytes: function() { try { if (policies.secureRandom.provider === Config.primitives.secureRandom.provider.SCYTL) { return this.getCryptoScytlRandomBytes(); } else { throw new exceptions.CryptoLibException( 'No suitable provider was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoRandomBytes could not be obtained.', error); } }, /** * Instantiates a Scytl random byte generator. * * @function * @returns {primitives/securerandom.CryptoScytlRandomBytes} */ getCryptoScytlRandomBytes: function() { return new CryptoScytlRandomBytes(); }, /** * Instantiates a random integer generator. * * @function * @returns {primitives/securerandom.CryptoScytlRandomBytes} */ getCryptoRandomInteger: function() { try { if (policies.secureRandom.provider === Config.primitives.secureRandom.provider.SCYTL) { return this.getCryptoScytlRandomInteger(); } else { throw new exceptions.CryptoLibException( 'No suitable provider was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoRandomBytes could not be obtained.', error); } }, /** * Instantiates a Scytl random integer generator. * * @function * @returns {primitives/securerandom.CryptoScytlRandomBytes} */ getCryptoScytlRandomInteger: function() { return new CryptoScytlRandomInteger(); } }; var validate = function(length) { try { if (typeof length !== 'number') { throw new exceptions.CryptoLibException( 'The given length is not a number'); } if (length < 1) { throw new exceptions.CryptoLibException( 'The given length should be a strictly positive integer.'); } if (length > box.MAXIMUM_LENGTH_TO_GENERATE_DATA) { throw new exceptions.CryptoLibException( 'The given length should be lower than or equal to ' + box.MAXIMUM_LENGTH_TO_GENERATE_DATA); } } catch (error) { throw new exceptions.CryptoLibException( 'Random generation failed.', error); } }; /** * The SCYTL Bytes Random Generator * * @class * @memberof primitives/securerandom */ function CryptoScytlRandomBytes() {} CryptoScytlRandomBytes.prototype = { /** * Generates secure random bytes. * * @function * @param {number} * numBytes number of bytes to be generated.. * @returns {string} secure random string. */ nextRandom: function(length) { validate(length); var randomBytes = box.prng.generate(length); return randomBytes.toString(); }, /** * Method to be compliant with the Forge library. * * @function * @param {number} * numBytes number of bytes to be generated.. * @returns {string} secure random string. */ getBytesSync: function(length) { return this.nextRandom(length); } }; /** * The SCYTL Integer Random Generator * * @class * @memberof primitives/securerandom */ function CryptoScytlRandomInteger() {} CryptoScytlRandomInteger.prototype = { /** * Generates a secure random big integer (Forge). * * @function * @params {number} length the bit length of the random integer. * @returns {object} secure random big integer. */ nextRandom: function(length) { validate(length); var n = ((new box.forge.jsbn.BigInteger('10')).pow(length)) .subtract(box.forge.jsbn.BigInteger.ONE); var bitLength = n.bitLength(); var numberOfBytes = bitLength / box.NUMBER_OF_BITS_PER_BYTE; numberOfBytes = Math.floor(numberOfBytes); var modBytes = bitLength % box.NUMBER_OF_BITS_PER_BYTE; if (modBytes !== 0) { numberOfBytes++; } var generatedInteger; do { var randomBytes = box.prng.generate(numberOfBytes); randomBytes = '\x00' + randomBytes; randomBytes = converters.bytesFromString(randomBytes); generatedInteger = new box.forge.jsbn.BigInteger(randomBytes); } while ((generatedInteger.compareTo(forge.jsbn.BigInteger.ZERO) <= 0) || (generatedInteger.compareTo(n) > 0)); return generatedInteger; }, /** * Generates a secure random big integer according to the specified bit * length. * * @function * @params {number} bitsLength the bit length of the random integer. * @returns {object} Secure random big integer. */ nextRandomByBits: function(bitsLength) { var byteLength = Math.ceil(bitsLength / box.NUMBER_OF_BITS_PER_BYTE); var modBytes = box.NUMBER_OF_BITS_PER_BYTE - (bitsLength % box.NUMBER_OF_BITS_PER_BYTE); var randomBytes = box.prng.generate(byteLength); randomBytes = '\x00' + randomBytes; randomBytes = converters.bytesFromString(randomBytes); var generatedInteger = new box.forge.jsbn.BigInteger(randomBytes); generatedInteger = generatedInteger.shiftRight(modBytes); return generatedInteger; } }; }; /* * 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 || {}; /** * Defines primitives services. * @namespace primitives/service */ cryptolib.modules.primitives.service = function(box) { 'use strict'; box.primitives = box.primitives || {}; /** * A module that holds primitives API. * @exports primitives/service */ box.primitives.service = {}; var securerandomFactory, messageDigestFactory, secretKeyGeneratorFactory; cryptolib( 'primitives.securerandom', 'commons.exceptions', 'primitives.messagedigest', 'primitives.derivation', function(box) { securerandomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); messageDigestFactory = new box.primitives.messagedigest.factory.MessageDigestFactory(); secretKeyGeneratorFactory = new box.primitives.derivation.factory.SecretKeyGeneratorFactory(); }); /** * Provides a random bytes generator. * @function * @returns {primitives/securerandom.CryptoScytlRandomBytes} a CryptoRandomBytes object. */ box.primitives.service.getCryptoRandomBytes = function() { return securerandomFactory.getCryptoRandomBytes(); }; /** * Provides a random integer generator. * @function * @returns {primitives/securerandom.CryptoScytlRandomInteger} a CryptoRandomInteger object. */ box.primitives.service.getCryptoRandomInteger = function() { return securerandomFactory.getCryptoRandomInteger(); }; /** * Generates a hash for the given data. * @function * @parameter {array} ArrayDataBase64 the input data for the hash. * @returns {string} the hash, as a string in Base64 encoded format. */ box.primitives.service.getHash = function(ArrayDataBase64) { var hashFunction = messageDigestFactory.getCryptoMessageDigest(); hashFunction.start(); hashFunction.update(ArrayDataBase64); return hashFunction.digest(); }; /** * Provides a derived secret key generator. * @function * @returns {primitives/derivation.CryptoForgePBKDFSecretKeyGenerator} a the pbkdf Generator. */ box.primitives.service.getPbkdfSecretKeyGenerator = function() { return secretKeyGeneratorFactory.createPBKDF(); }; }; /* * 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 = cryptolib.modules || {}; cryptolib.modules.proofs = cryptolib.modules.proofs || {}; /** * Defines Zero-Knowledge Proofs (ZKPs). */ cryptolib.modules.proofs.implementation = function(box) { 'use strict'; box.proofs = box.proofs || {}; var exceptions, mathematical, utils, converters, secureRandomFactory, messageDigestFactory; var f = function(box) { exceptions = box.commons.exceptions; mathematical = box.commons.mathematical; utils = box.commons.utils; converters = new box.commons.utils.Converters(); secureRandomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); messageDigestFactory = new box.primitives.messagedigest.factory.MessageDigestFactory(); }; f.policies = { primitives: { messagedigest: { algorithm: box.policies.proofs.messagedigest.algorithm, provider: box.policies.proofs.messagedigest.provider }, secureRandom: {provider: box.policies.proofs.secureRandom.provider} } }; cryptolib( 'commons.exceptions', 'commons.mathematical', 'primitives.messagedigest', 'commons.utils', 'primitives.securerandom', f); /** * Schnorr proof generator. */ box.proofs.SchnorrProofGenerator = function(voterID, electionID, group) { checkNotEmpty('voter ID', voterID); checkNotEmpty('election ID', electionID); checkDefined('group', group); var computationRules = [[[1, 1]]]; var _voterID = voterID; var _electionID = electionID; var _progressMeter; var _numOutputs = 1; var buildListOfBaseElements = function() { var baseElements = []; baseElements.push(group.getGenerator()); return baseElements; }; var phiFunctionSchnorr = new box.proofs.PhiFunction( group, 1, _numOutputs, buildListOfBaseElements(), computationRules); var _prover = new box.proofs.Prover(group, phiFunctionSchnorr); this._buildListPublicValues = function(exponentiatedElement) { var publicList = []; publicList.push(exponentiatedElement); return publicList; }; this._getProver = function() { return _prover; }; this._buildListExponents = function(witness) { var exponentList = []; exponentList.push(witness); return exponentList; }; this._buildDataString = function() { return 'SchnorrProof:VoterID=' + _voterID + 'ElectionEventID=' + _electionID; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.SchnorrProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(1, this._getProgressMeter()); }, generate: function(exponentiatedElement, witness, preComputedValues) { checkDefined('exponentiated element', exponentiatedElement); checkDefined('witness', witness); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildListPublicValues(exponentiatedElement), this._buildListExponents(witness), this._buildDataString(), preComputedValues); } }; /** * Exponentiation proof generator. */ box.proofs.ExponentiationProofGenerator = function(baseElements, group) { checkDefined('base elements', baseElements); checkDefined('group', group); var buildComputationRules = function(k) { var rules = []; for (var i = 0; i < k; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _numOutputs = baseElements.length; var phiFunctionExponentiation = new box.proofs.PhiFunction( group, 1, _numOutputs, baseElements, buildComputationRules(_numOutputs)); var _prover = new box.proofs.Prover(group, phiFunctionExponentiation); var _progressMeter; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; this.check = function(exponentiatedElements) { if (exponentiatedElements.length !== _numOutputs) { throw new exceptions.CryptoLibException( 'The number of exponentiated elements must be equal to the number of base elements'); } }; this._getProver = function() { return _prover; }; }; box.proofs.ExponentiationProofGenerator.prototype = { preCompute: function() { var EXPONENT_QUANTITY = 1; return this._getProver().preCompute( EXPONENT_QUANTITY, this._getProgressMeter()); }, generate: function(exponentiatedElements, exponent, preComputedValues) { checkDefined('exponentiatedElements', exponentiatedElements); checkDefined('exponent', exponent); this.check(exponentiatedElements); var exponents = []; exponents.push(exponent); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( exponentiatedElements, exponents, box.EXPONENTIATION_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Plaintext proof generator. */ box.proofs.PlaintextProofGenerator = function(publicKey, group) { checkDefined('public key', publicKey); checkDefined('group', group); var buildListOfBaseElements = function() { var list = []; list.push(group.getGenerator()); list.addAll(publicKey.getGroupElementsArray()); return list; }; var buildComputationRules = function(k) { var rules = []; for (var i = 0; i < (k + 1); i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _publicKeyLength = publicKey.getGroupElementsArray().length; var numOutputs = _publicKeyLength + 1; var phiFunctionPlaintext = new box.proofs.PhiFunction( group, 1, numOutputs, buildListOfBaseElements(), buildComputationRules(_publicKeyLength)); var _prover = new box.proofs.Prover(group, phiFunctionPlaintext); var _progressMeter; this._buildListPublicValues = function(ciphertext, plaintext) { var dividedCiphertext = mathematical.groupUtils.divide(ciphertext.getPhis(), plaintext); var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.addAll(dividedCiphertext); return publicValues; }; this._getPublicKeyLength = function() { return _publicKeyLength; }; this._getProver = function() { return _prover; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _publicKeyLength + 1, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.PlaintextProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(1, this._getProgressMeter()); }, generate: function(ciphertext, plaintext, witness, preComputedValues) { checkDefined('ciphertext', ciphertext); checkDefined('plaintext', plaintext); checkDefined('witness', witness); var publicKeyLength = this._getPublicKeyLength(); var numPhis = ciphertext.getPhis().length; if (publicKeyLength !== numPhis) { throw new exceptions.CryptoLibException( 'The length of the ciphertext must be equal to the length of the public key + 1.'); } if (publicKeyLength !== plaintext.length) { throw new exceptions.CryptoLibException( 'The public key and the plaintext must have the same number of elements'); } if (!preComputedValues) { preComputedValues = this.preCompute(); } var exponents = []; exponents.push(witness); return this._getProver().prove( this._buildListPublicValues(ciphertext, plaintext), exponents, box.PLAINTEXT_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Simple plaintext equality proof generator. */ box.proofs.SimplePlaintextEqualityProofGenerator = function( primaryPublicKey, secondaryPublicKey, group) { checkDefined('primary public key', primaryPublicKey); checkDefined('secondary public key', secondaryPublicKey); checkDefined('group', group); var _primaryPublicKey = primaryPublicKey; var _secondaryPublicKey = secondaryPublicKey; var _group = group; var buildComputationRules = function(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i + 1); rules[i][0].push(1); rules[i][1].push(i + 1 + k); rules[i][1].push(1); } return rules; }; var buildSecondaryInvertedPublicKey = function() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; }; var buildListOfBaseElements = function() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; }; var primaryPublicKeyLength = primaryPublicKey.getGroupElementsArray().length; var secondaryPublicKeyLength = secondaryPublicKey.getGroupElementsArray().length; if (primaryPublicKeyLength !== secondaryPublicKeyLength) { throw new exceptions.CryptoLibException( 'The number of elements in the primary and secondary public keys must be equal'); } var _progressMeter; var _numKeyElements = primaryPublicKey.getGroupElementsArray().length; var numOutputs = _numKeyElements + 1; var rules = buildComputationRules(_numKeyElements); var phiFunctionSimplePlaintextEquality = new box.proofs.PhiFunction( group, 1, numOutputs, buildListOfBaseElements(), rules); var _prover = new box.proofs.Prover(_group, phiFunctionSimplePlaintextEquality); this._buildListExponents = function(witness) { var exponentList = []; exponentList.push(witness); return exponentList; }; this._buildInputList = function(primaryCiphertext, secondaryCiphertext) { var dividedSubCiphertext = mathematical.groupUtils.divide( primaryCiphertext.getPhis(), secondaryCiphertext.getPhis()); var generatedList = []; generatedList.push(secondaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numKeyElements * 2 + 1, progressCallback, progressPercentMinCheckInterval); return this; }; this._getProver = function() { return _prover; }; this._check = function(primaryCiphertextLength) { if (primaryCiphertextLength !== (primaryPublicKeyLength + 1)) { throw new exceptions.CryptoLibException( 'The number of elements in the ciphertext must be equal to the number in the public key + 1'); } }; }; box.proofs.SimplePlaintextEqualityProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(1, this._getProgressMeter()); }, generate: function( primaryCiphertext, secondaryCiphertext, witness, preComputedValues) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('witness', witness); var primaryCiphertextLength = primaryCiphertext.getPhis().length + 1; var secondaryCiphertextLength = secondaryCiphertext.getPhis().length + 1; if (primaryCiphertextLength !== secondaryCiphertextLength) { throw new exceptions.CryptoLibException( 'The number of elements in the primary and secondary ciphertext must be equal'); } this._check(primaryCiphertextLength); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildInputList(primaryCiphertext, secondaryCiphertext), this._buildListExponents(witness), box.SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Plaintext equality proof generator. */ box.proofs.PlaintextEqualityProofGenerator = function( primaryPublicKey, secondaryPublicKey, group) { checkDefined('primary public key', primaryPublicKey); checkDefined('secondary public key', secondaryPublicKey); checkDefined('group', group); var numPrimaryPublicKeyElements = primaryPublicKey.getGroupElementsArray().length; var numSecondaryPublicKeyElements = secondaryPublicKey.getGroupElementsArray().length; if (numPrimaryPublicKeyElements !== numSecondaryPublicKeyElements) { throw new exceptions.CryptoLibException( 'The primary and secondary public keys contain different numbers of elements: ' + numPrimaryPublicKeyElements + ', ' + numSecondaryPublicKeyElements); } var _primaryPublicKey = primaryPublicKey; var _secondaryPublicKey = secondaryPublicKey; var _group = group; var _progressMeter; this._buildListExponents = function(primaryWitness, secondaryWitness) { var exponentList = []; exponentList.push(primaryWitness); exponentList.push(secondaryWitness); return exponentList; }; this._buildInputList = function(primaryCiphertext, secondaryCiphertext) { var dividedSubCiphertext = mathematical.groupUtils.divide( primaryCiphertext.getPhis(), secondaryCiphertext.getPhis()); var generatedList = []; generatedList.push(primaryCiphertext.getGamma()); generatedList.push(secondaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; function buildComputationRules(k) { var firstDimensionLength = k + 2; var rules = []; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); rules[1] = []; rules[1][0] = []; rules[1][0].push(1); rules[1][0].push(2); for (var i = 2; i < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i); rules[i][0].push(1); rules[i][1] = []; rules[i][1].push(i + k); rules[i][1].push(2); } return rules; } function buildListOfBaseElements() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; } function buildSecondaryInvertedPublicKey() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; } var numKeyElements = primaryPublicKey.getGroupElementsArray().length; var _numOutputs = numKeyElements + 2; var rules = buildComputationRules(numKeyElements); var phiFunctionPlaintextEquality = new box.proofs.PhiFunction( group, 2, _numOutputs, buildListOfBaseElements(), rules); var _prover = new box.proofs.Prover(group, phiFunctionPlaintextEquality); this.getPrimaryPublicKey = function() { return _primaryPublicKey; }; this.getSecondaryPublicKey = function() { return _secondaryPublicKey; }; this.getGroup = function() { return _group; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( numKeyElements * 2 + 2, progressCallback, progressPercentMinCheckInterval); return this; }; this._getProver = function() { return _prover; }; this.check = function(numPrimaryCiphertextElements) { if (numPrimaryCiphertextElements !== (numPrimaryPublicKeyElements + 1)) { throw new exceptions.CryptoLibException( 'The number of ciphertext elements is not equal to the number public key elements plus one.'); } }; }; box.proofs.PlaintextEqualityProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(2, this._getProgressMeter()); }, generate: function( primaryCiphertext, primaryWitness, secondaryCiphertext, secondaryWitness, preComputedValues) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('primary witness', primaryWitness); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('secondary witness', secondaryWitness); var numPrimaryCiphertextElements = primaryCiphertext.getPhis().length + 1; var numSecondaryCiphertextElements = secondaryCiphertext.getPhis().length + 1; if (numPrimaryCiphertextElements !== numSecondaryCiphertextElements) { throw new exceptions.CryptoLibException( 'The primary and secondary ciphertexts contain different numbers of elements: ' + numPrimaryCiphertextElements + ', ' + numSecondaryCiphertextElements); } this.check(numPrimaryCiphertextElements); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildInputList(primaryCiphertext, secondaryCiphertext), this._buildListExponents(primaryWitness, secondaryWitness), box.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * Plaintext exponent equality proof generator. */ box.proofs.PlaintextExponentEqualityProofGenerator = function( baseElements, group) { var buildComputationRules = function(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i + 1); rules[i][0].push(1); rules[i][1].push(i + 1 + k); rules[i][1].push(2); } return rules; }; checkDefined('base elements', baseElements); checkDefined('group', group); var _numBaseElements = baseElements.length; var _progressMeter; var k = (_numBaseElements - 1) / 2; var numOutputs = k + 1; var phiFunctionPlaintextExponentEquality = new box.proofs.PhiFunction( group, 2, numOutputs, baseElements, buildComputationRules(k)); var _prover = new box.proofs.Prover(group, phiFunctionPlaintextExponentEquality); this._buildListExponents = function(witness1, witness2) { var exponentList = []; exponentList.push(witness1); exponentList.push(witness2); return exponentList; }; this._buildListPublicValues = function(ciphertext) { var list = []; list.push(ciphertext.getGamma()); list.addAll(ciphertext.getPhis()); return list; }; this._getProver = function() { return _prover; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numBaseElements, progressCallback, progressPercentMinCheckInterval); return this; }; this._checkLengthOf = function(ciphertext) { var k = ciphertext.getPhis().length; var expectedNumBaseElements = (2 * k) + 1; if (_numBaseElements !== expectedNumBaseElements) { throw new exceptions.CryptoLibException( 'The lengths of the ciphertext and the base elements do not have the expected relationship.'); } }; }; box.proofs.PlaintextExponentEqualityProofGenerator.prototype = { preCompute: function() { return this._getProver().preCompute(2, this._getProgressMeter()); }, generate: function(ciphertext, witness1, witness2, preComputedValues) { checkDefined('ciphertext', ciphertext); checkDefined('witness1', witness1); checkDefined('witness2', witness2); this._checkLengthOf(ciphertext); if (!preComputedValues) { preComputedValues = this.preCompute(); } return this._getProver().prove( this._buildListPublicValues(ciphertext), this._buildListExponents(witness1, witness2), box.PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA, preComputedValues); } }; /** * OR-proof generator. */ box.proofs.ORProofGenerator = function(publicKey, numElements, group) { checkPublicKey(publicKey); checkNumElements(numElements); checkGroup(group); var _publicKey = publicKey; var _numElements = numElements; var _group = group; var _numOutputs = _numElements * 2; var _progressMeter; function checkPublicKey(publicKey) { checkDefined('public key', publicKey); checkNotNull('public key', publicKey); var publicKeyLength = publicKey.getGroupElementsArray().length; if (publicKeyLength !== 1) { throw new exceptions.CryptoLibException( 'Expected public key length: 1 ; Found: ' + publicKeyLength); } } function checkNumElements(numElements) { checkDefined('numElements', numElements); checkNotNull('numElements', numElements); if (numElements < 2) { throw new exceptions.CryptoLibException( 'Expected number of elements to be greater than 1 ; Found: ' + numElements); } } function checkGroup(group) { checkDefined('group', group); checkNotNull('group', group); } function buildBaseElements() { var baseElements = []; baseElements.push(_group.getGenerator()); baseElements.push(_publicKey.getGroupElementsArray()[0]); return baseElements; } function buildComputationRules(numOutputs) { var rules = []; var index1; var index2 = 0; for (var i = 0; i < numOutputs; i++) { index1 = 1 + (i % 2); if (i % 2 === 0) { index2++; } rules[i] = []; rules[i][0] = []; rules[i][0].push(index1); rules[i][0].push(index2); } return rules; } var phiFunctionOR = new box.proofs.PhiFunction( group, _numElements, _numOutputs, buildBaseElements(), buildComputationRules(_numOutputs)); var _prover = new box.proofs.Prover(_group, phiFunctionOR); this.getNumElements = function() { return _numElements; }; this.getProver = function() { return _prover; }; this.buildPublicValues = function(ciphertext) { var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.push(ciphertext.getPhis()[0]); return publicValues; }; this.getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.ORProofGenerator.prototype = { preCompute: function() { return this.getProver().preCompute( this.getNumElements(), this.getProgressMeter()); }, generate: function( ciphertext, witness, elements, index, data, preComputedValues) { checkCiphertext(ciphertext); checkWitness(witness); checkElements(elements, this.getNumElements()); checkIndex(index, this.getNumElements()); checkDefined('data', data); function checkCiphertext(ciphertext) { checkDefined('ciphertext', ciphertext); checkNotNull('ciphertext', ciphertext); var ciphertextLength = ciphertext.getPhis().length + 1; if (ciphertextLength !== 2) { throw new exceptions.CryptoLibException( 'Expected ciphertext length: 2 ; Found: ' + ciphertextLength); } } function checkWitness(witness) { checkDefined('witness', witness); checkNotNull('witness', witness); } function checkElements(elements, numElementsExpected) { checkDefined('elements', elements); checkNotNull('elements', elements); var numElementsFound = elements.length; if (numElementsFound !== numElementsExpected) { throw new exceptions.CryptoLibException( 'Expected number of elements: ' + numElementsExpected + ' ; Found: ' + numElementsFound); } } function checkIndex(index, numElements) { checkDefined('index', index); checkNotNull('index', index); if (index < 0) { throw new exceptions.CryptoLibException( 'Expected index of encrypted element to be positive ; Found: ' + index); } if (index >= numElements) { throw new exceptions.CryptoLibException( 'Expected index of encrypted element to be less than number of elements: ' + numElements + ' ; Found: ' + index); } } var publicValues = this.buildPublicValues(ciphertext); var privateValues = []; privateValues.push(witness); if (data === null) { data = ''; } if (!preComputedValues) { preComputedValues = this.preCompute(); } return this.getProver().prove( publicValues, privateValues, data, preComputedValues, elements, index); } }; /** * Schnorr proof verifier. */ box.proofs.SchnorrProofVerifier = function( exponentiatedElement, voterID, electionID, proof, group) { checkDefined('exponentiated element', exponentiatedElement); checkNotEmpty('voter ID', voterID); checkNotEmpty('election ID', electionID); checkDefined('proof', proof); checkDefined('group', group); var _exponentiatedElement = exponentiatedElement; var _voterID = voterID; var _electionID = electionID; var _proof = proof; var _group = group; var _progressMeter; this._buildListOfBaseElements = function() { var baseElements = []; baseElements.push(_group.getGenerator()); return baseElements; }; this._buildExponentiatedElementsList = function() { var exponentiatedElements = []; exponentiatedElements.push(_exponentiatedElement); return exponentiatedElements; }; this._buildDataString = function() { return 'SchnorrProof:VoterID=' + _voterID + 'ElectionEventID=' + _electionID; }; var computationRules = [[[1, 1]]]; var _numOutputs = 1; var phiFunction = new box.proofs.PhiFunction( _group, 1, _numOutputs, this._buildListOfBaseElements(), computationRules); var _verifier = new box.proofs.Verifier(_group, phiFunction); this._getProof = function() { return _proof; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.SchnorrProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildExponentiatedElementsList(), this._getProof(), this._buildDataString(), this._getProgressMeter()); } }; /** * Exponentiation proof verifier. */ box.proofs.ExponentiationProofVerifier = function( exponentiatedElements, baseElements, proof, group) { checkDefined('exponentiated elements', exponentiatedElements); checkDefined('base elements', baseElements); checkDefined('proof', proof); checkDefined('group', group); var _exponentiatedElements = exponentiatedElements; var _proof = proof; var _progressMeter; this._buildComputationRules = function(k) { var rules = []; for (var i = 0; i < k; i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _numOutputs = exponentiatedElements.length; var phiFunctionExponentiation = new box.proofs.PhiFunction( group, 1, _numOutputs, baseElements, this._buildComputationRules(_numOutputs)); var _verifier = new box.proofs.Verifier(group, phiFunctionExponentiation); this._getProof = function() { return _proof; }; this.getExponentiatedElements = function() { return _exponentiatedElements; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.ExponentiationProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this.getExponentiatedElements(), this._getProof(), box.EXPONENTIATION_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Plaintext proof verifier. */ box.proofs.PlaintextProofVerifier = function(publicKey, group) { checkDefined('public key', publicKey); checkDefined('group', group); var _publicKey = publicKey; var _group = group; var _progressMeter; this._buildListOfBaseElements = function() { var list = []; list.push(_group.getGenerator()); list.addAll(_publicKey.getGroupElementsArray()); return list; }; this._buildListPublicValues = function(ciphertext, plaintext) { var dividedCiphertext = mathematical.groupUtils.divide(ciphertext.getPhis(), plaintext); var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.addAll(dividedCiphertext); return publicValues; }; this._buildComputationRules = function(k) { var rules = []; for (var i = 0; i < (k + 1); i++) { rules[i] = []; rules[i][0] = []; rules[i][0].push(i + 1); rules[i][0].push(1); } return rules; }; var _publicKeyLength = _publicKey.getGroupElementsArray().length; var numOutputs = _publicKeyLength + 1; var phiFunctionPlaintext = new box.proofs.PhiFunction( _group, 1, numOutputs, this._buildListOfBaseElements(), this._buildComputationRules(_publicKeyLength)); var _verifier = new box.proofs.Verifier(_group, phiFunctionPlaintext); this.getPublicKey = function() { return _publicKey; }; this.getGroup = function() { return _group; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _publicKeyLength + 1, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.PlaintextProofVerifier.prototype = { verify: function(ciphertext, plaintext, proof) { checkDefined('ciphertext', ciphertext); checkDefined('plaintext', plaintext); checkDefined('proof', proof); return this._getVerifier().verify( this._buildListPublicValues(ciphertext, plaintext), proof, box.PLAINTEXT_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Simple plaintext equality proof verifier. */ box.proofs.SimplePlaintextEqualityProofVerifier = function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('primary public key', primaryPublicKey); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('secondary public key', secondaryPublicKey); checkDefined('proof', proof); checkDefined('group', group); var numPrimaryCiphertextElements = primaryCiphertext.getPhis().length + 1; var numPrimaryPublicKeyElements = primaryPublicKey.getGroupElementsArray().length; var numSecondaryCiphertextElements = secondaryCiphertext.getPhis().length + 1; var numSecondaryPublicKeyElements = secondaryPublicKey.getGroupElementsArray().length; if (numPrimaryCiphertextElements !== numSecondaryCiphertextElements) { throw new exceptions.CryptoLibException( 'The primary and secondary ciphertexts contain different numbers of elements: ' + numPrimaryCiphertextElements + ', ' + numSecondaryCiphertextElements); } if (numPrimaryPublicKeyElements !== numSecondaryPublicKeyElements) { throw new exceptions.CryptoLibException( 'The primary and secondary public keys contain different numbers of elements: ' + numPrimaryPublicKeyElements + ', ' + numSecondaryPublicKeyElements); } if (numPrimaryCiphertextElements !== (numPrimaryPublicKeyElements + 1)) { throw new exceptions.CryptoLibException( 'The number of ciphertext elements is not equal to the number public key elements plus one.'); } var _primaryCiphertext = primaryCiphertext; var _primaryPublicKey = primaryPublicKey; var _secondaryCiphertext = secondaryCiphertext; var _secondaryPublicKey = secondaryPublicKey; var _proof = proof; var _progressMeter; function buildComputationRules(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i + 1); rules[i][0].push(1); rules[i][1].push(i + 1 + k); rules[i][1].push(1); } return rules; } function buildListOfBaseElements() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; } function buildSecondaryInvertedPublicKey() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; } var _primarySubCiphertext = _primaryCiphertext.getPhis(); var _secondarySubCiphertext = _secondaryCiphertext.getPhis(); var _numKeyElements = _primaryPublicKey.getGroupElementsArray().length; var numOutputs = _numKeyElements + 1; var phiFunctionSimplePlaintextEquality = new box.proofs.PhiFunction( group, 1, numOutputs, buildListOfBaseElements(), buildComputationRules(_numKeyElements)); var _verifier = new box.proofs.Verifier(group, phiFunctionSimplePlaintextEquality); this._buildInputList = function() { var dividedSubCiphertext = mathematical.groupUtils.divide( _primarySubCiphertext, _secondarySubCiphertext); var generatedList = []; generatedList.push(_primaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; this._getProof = function() { return _proof; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numKeyElements * 2 + 1, progressCallback, progressPercentMinCheckInterval); return this; }; this._getVerifier = function() { return _verifier; }; }; box.proofs.SimplePlaintextEqualityProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildInputList(), this._getProof(), box.SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Plaintext equality proof verifier. */ box.proofs.PlaintextEqualityProofVerifier = function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { checkDefined('primary ciphertext', primaryCiphertext); checkDefined('primary public key', primaryPublicKey); checkDefined('secondary ciphertext', secondaryCiphertext); checkDefined('secondary public key', secondaryPublicKey); checkDefined('proof', proof); checkDefined('group', group); var _primaryCiphertext = primaryCiphertext; var _primaryPublicKey = primaryPublicKey; var _secondaryCiphertext = secondaryCiphertext; var _secondaryPublicKey = secondaryPublicKey; var _proof = proof; var buildComputationRules = function(k) { var firstDimensionLength = k + 2; var rules = []; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); rules[1] = []; rules[1][0] = []; rules[1][0].push(1); rules[1][0].push(2); for (var i = 2; i < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i); rules[i][0].push(1); rules[i][1].push(i + k); rules[i][1].push(2); } return rules; }; function buildListOfBaseElements() { var invertedSecondaryKeyValues = buildSecondaryInvertedPublicKey(); var baseElements = []; baseElements.push(group.getGenerator()); baseElements.addAll(_primaryPublicKey.getGroupElementsArray()); baseElements.addAll(invertedSecondaryKeyValues); return baseElements; } function buildSecondaryInvertedPublicKey() { var listInvertedElements = []; var keys = _secondaryPublicKey.getGroupElementsArray(); for (var i = 0; i < keys.length; i++) { listInvertedElements.push(keys[i].invert()); } return listInvertedElements; } var _progressMeter; var _primarySubCiphertext = _primaryCiphertext.getPhis(); var _secondarySubCiphertext = _secondaryCiphertext.getPhis(); var _numKeyElements = primaryPublicKey.getGroupElementsArray().length; var numOutputs = _numKeyElements + 2; var phiFunctionPlaintextEquality = new box.proofs.PhiFunction( group, 2, numOutputs, buildListOfBaseElements(), buildComputationRules(_numKeyElements)); var _verifier = new box.proofs.Verifier(group, phiFunctionPlaintextEquality); this._buildInputList = function() { var dividedSubCiphertext = mathematical.groupUtils.divide( _primarySubCiphertext, _secondarySubCiphertext); var generatedList = []; generatedList.push(_primaryCiphertext.getGamma()); generatedList.push(_secondaryCiphertext.getGamma()); generatedList.addAll(dividedSubCiphertext); return generatedList; }; this._getProof = function() { return _proof; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numKeyElements * 2 + 2, progressCallback, progressPercentMinCheckInterval); return this; }; this._getVerifier = function() { return _verifier; }; }; box.proofs.PlaintextEqualityProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildInputList(), this._getProof(), box.PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * Plaintext exponent equality proof verifier. */ box.proofs.PlaintextExponentEqualityProofVerifier = function( ciphertext, baseElements, proof, group) { checkDefined('ciphertext', ciphertext); checkDefined('base elements', baseElements); checkDefined('proof', proof); checkDefined('group', group); var _ciphertext = ciphertext; var _baseElements = baseElements; var _proof = proof; var _group = group; var _progressMeter; this._buildComputationRules = function(k) { var rules = []; var firstDimensionLength = k + 1; rules[0] = []; rules[0][0] = []; rules[0][0].push(1); rules[0][0].push(1); for (var i = 1; i < firstDimensionLength; i++) { rules[i] = []; rules[i][0] = []; rules[i][1] = []; rules[i][0].push(i + 1); rules[i][0].push(1); rules[i][1].push(i + 1 + k); rules[i][1].push(2); } return rules; }; this._buildListPublicValues = function() { var list = []; list.push(_ciphertext.getGamma()); list.addAll(_ciphertext.getPhis()); return list; }; var _k = _ciphertext.getPhis().length; var numOutputs = _k + 1; var phiFunctionPlaintextExponentEquality = new box.proofs.PhiFunction( _group, 2, numOutputs, _baseElements, this._buildComputationRules(_k)); var _verifier = new box.proofs.Verifier(_group, phiFunctionPlaintextExponentEquality); this._getProof = function() { return _proof; }; this._getVerifier = function() { return _verifier; }; this._getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _k * 2 + 1, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.PlaintextExponentEqualityProofVerifier.prototype = { verify: function() { return this._getVerifier().verify( this._buildListPublicValues(), this._getProof(), box.PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA, this._getProgressMeter()); } }; /** * OR-proof verifier. */ box.proofs.ORProofVerifier = function(publicKey, numElements, group) { checkPublicKey(publicKey); checkNumElements(numElements); checkGroup(group); var _publicKey = publicKey; var _numElements = numElements; var _group = group; var _numOutputs = _numElements * 2; var _progressMeter; function checkPublicKey(publicKey) { checkDefined('public key', publicKey); checkNotNull('public key', publicKey); var publicKeyLength = publicKey.getGroupElementsArray().length; if (publicKeyLength !== 1) { throw new exceptions.CryptoLibException( 'Expected public key length: 1 ; Found: ' + publicKeyLength); } } function checkNumElements(numElements) { checkDefined('numElements', numElements); checkNotNull('numElements', numElements); if (numElements < 2) { throw new exceptions.CryptoLibException( 'Expected number of elements to be greater than 1 ; Found: ' + numElements); } } function checkGroup(group) { checkDefined('group', group); checkNotNull('group', group); } function buildBaseElements() { var baseElements = []; baseElements.push(_group.getGenerator()); baseElements.push(_publicKey.getGroupElementsArray()[0]); return baseElements; } function buildComputationRules(numOutputs) { var rules = []; var index1; var index2 = 0; for (var i = 0; i < numOutputs; i++) { index1 = 1 + (i % 2); if (i % 2 === 0) { index2++; } rules[i] = []; rules[i][0] = []; rules[i][0].push(index1); rules[i][0].push(index2); } return rules; } var phiFunctionOR = new box.proofs.PhiFunction( _group, _numElements, _numOutputs, buildBaseElements(), buildComputationRules(_numOutputs)); var _verifier = new box.proofs.Verifier(_group, phiFunctionOR); this.buildPublicValues = function(ciphertext) { var publicValues = []; publicValues.push(ciphertext.getGamma()); publicValues.push(ciphertext.getPhis()[0]); return publicValues; }; this.getNumElements = function() { return _numElements; }; this.getVerifier = function() { return _verifier; }; this.getProgressMeter = function() { return _progressMeter; }; this.initProgressMeter = function( progressCallback, progressPercentMinCheckInterval) { _progressMeter = createProgressMeter( _numOutputs, progressCallback, progressPercentMinCheckInterval); return this; }; }; box.proofs.ORProofVerifier.prototype = { verify: function(ciphertext, elements, data, proof) { checkCiphertext(ciphertext); checkElements(elements, this.getNumElements()); checkDefined('data', data); checkProof(proof); function checkCiphertext(ciphertext) { checkDefined('ciphertext', ciphertext); checkNotNull('ciphertext', ciphertext); var ciphertextLength = ciphertext.getPhis().length + 1; if (ciphertextLength !== 2) { throw new exceptions.CryptoLibException( 'Expected ciphertext length: 2 ; Found: ' + ciphertextLength); } } function checkElements(elements, numElementsExpected) { checkDefined('elements', elements); checkNotNull('elements', elements); var numElementsFound = elements.length; if (numElementsFound !== numElementsExpected) { throw new exceptions.CryptoLibException( 'Expected number of elements: ' + numElementsExpected + ' ; Found: ' + numElementsFound); } } function checkProof(proof) { checkDefined('proof', proof); checkNotNull('proof', proof); } if (data === null) { data = ''; } return this.getVerifier().verify( this.buildPublicValues(ciphertext), proof, data, elements, this.getProgressMeter()); } }; /** * Proofs factory, can be used for creating proof generators and verifiers. */ box.proofs.ProofsFactory = function() { }; box.proofs.ProofsFactory.prototype = { createSchnorrProofGenerator: function(voterID, electionID, group) { return new box.proofs.SchnorrProofGenerator(voterID, electionID, group); }, createExponentiationProofGenerator: function(baseElements, group) { return new box.proofs.ExponentiationProofGenerator(baseElements, group); }, createPlaintextProofGenerator: function(publicKey, group) { return new box.proofs.PlaintextProofGenerator(publicKey, group); }, createSimplePlaintextEqualityProofGenerator: function( primaryPublicKey, secondaryPublicKey, group) { return new box.proofs.SimplePlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group); }, createPlaintextEqualityProofGenerator: function( primaryPublicKey, secondaryPublicKey, group) { return new box.proofs.PlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group); }, createPlaintextExponentEqualityProofGenerator: function( baseElements, group) { return new box.proofs.PlaintextExponentEqualityProofGenerator( baseElements, group); }, createORProofGenerator: function(publicKey, numElements, group) { return new box.proofs.ORProofGenerator(publicKey, numElements, group); }, createSchnorrProofVerifier: function( element, voterID, electionEventID, proof, group) { return new box.proofs.SchnorrProofVerifier( element, voterID, electionEventID, proof, group); }, createExponentiationProofVerifier: function( exponentiatedElements, baseElements, proof, group) { return new box.proofs.ExponentiationProofVerifier( exponentiatedElements, baseElements, proof, group); }, createPlaintextProofVerifier: function( publicKey, ciphertext, plaintext, proof, group) { return new box.proofs.PlaintextProofVerifier( publicKey, ciphertext, plaintext, proof, group); }, createSimplePlaintextEqualityProofVerifier: function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { return new box.proofs.SimplePlaintextEqualityProofVerifier( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group); }, createPlaintextEqualityProofVerifier: function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) { return new box.proofs.PlaintextEqualityProofVerifier( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group); }, createPlaintextExponentEqualityProofVerifier: function( ciphertext, baseElements, proof, group) { return new box.proofs.PlaintextExponentEqualityProofVerifier( ciphertext, baseElements, proof, group); }, createORProofVerifier: function(publicKey, numElements, group) { return new box.proofs.ORProofVerifier(publicKey, numElements, group); } }; /** * Represents the PHI function of Maurer's Unified Zero-Knowledge Proof of * Knowledge (ZK-PoK) scheme. */ box.proofs.PhiFunction = function( group, numInputs, numOutputs, baseElements, computationRules) { var validateComputationRules = function( numInputs, numOutputs, baseElements, computationRules) { // Validate that both of the values in each of the // pairs of rules have values in the correct ranges var numBaseElements = baseElements.length; for (var i = 0; i < numOutputs; i++) { for (var j = 0; j < computationRules[i].length; j++) { // Validate the first value of the pair var pairValue1 = computationRules[i][j][0]; if ((pairValue1 < 1) || (pairValue1 > numBaseElements)) { throw new exceptions.CryptoLibException( 'Invalid first value in index pair - should be in the range 1 to ' + numBaseElements + ', but it was ' + pairValue1); } // Validate the second value of the pair var pairValue2 = computationRules[i][j][1]; if ((pairValue2 < 1) || (pairValue2 > numInputs)) { throw new exceptions.CryptoLibException( 'Invalid second value in index pair - should be in the range 1 to ' + numInputs + ', but it was ' + pairValue2); } } } }; checkDefined('group', group); checkIsPositive('number of inputs', numInputs); checkIsPositive('number of outputs', numOutputs); checkInitializedArray('base elements', baseElements); checkDefined('computation rules', computationRules); validateComputationRules( numInputs, numOutputs, baseElements, computationRules); var _numInputs = numInputs; var _numOutputs = numOutputs; var _baseElements = baseElements; var _computationRules = computationRules; /** * @return The number of inputs */ this.getNumInputs = function() { return _numInputs; }; /** * @return The number of outputs */ this.getNumOutputs = function() { return _numOutputs; }; /** * @return The list of base elements */ this.getBaseElements = function() { return _baseElements; }; /** * @return The computation rules. */ this.getComputationRules = function() { return _computationRules; }; }; box.proofs.PhiFunction.prototype = { calculatePhi: function(inputs, progressMeter) { checkDefined('inputs', inputs); if (progressMeter === undefined) { progressMeter = new utils.ProgressMeter(); } checkDefined('progressMeter', progressMeter); var computationsRules = this.getComputationRules(); var baseElements = this.getBaseElements(); var partialResult; var resultForThisListOfPairs; var result = []; var progress = 0; for (var i = 0; i < computationsRules.length; i++) { var numPairsInList = computationsRules[i].length; resultForThisListOfPairs = baseElements[computationsRules[i][0][0] - 1].exponentiate( inputs[computationsRules[i][0][1] - 1]); progressMeter.update(++progress); for (var j = 1; j < numPairsInList; j++) { var index1 = computationsRules[i][j][0]; index1 = index1 - 1; var index2 = computationsRules[i][j][1]; index2 = index2 - 1; partialResult = baseElements[index1].exponentiate(inputs[index2]); resultForThisListOfPairs = resultForThisListOfPairs.multiply(partialResult); progressMeter.update(++progress); } result.push(resultForThisListOfPairs); } return result; } }; /** * Acts as a prover in a Zero-Knowledge Proof of Knowledge (ZK-PoK) exchange * using Maurer's unified PHI function. */ box.proofs.Prover = function(group, phiFunction) { checkDefined('group', group); checkDefined('phiFunction', phiFunction); var _group = group; var _phiFunction = phiFunction; var _cryptoRandomInteger = secureRandomFactory.getCryptoRandomInteger(); /** * @return Get the group. */ this.getGroup = function() { return _group; }; /** * @return Get the phi function. */ this.getPhiFunction = function() { return _phiFunction; }; this._calculateRandomExponents = function(numExponentsNeeded) { var exponentsArray = []; for (var i = 0; i < numExponentsNeeded; i++) { var randomExponent = mathematical.groupUtils.generateRandomExponent( _group, _cryptoRandomInteger); exponentsArray.push(randomExponent); } return exponentsArray; }; this._generateHash = function(publicValues, phiOutputs, data) { var hashBuilder = new box.proofs.HashBuilder(); var hashB64 = hashBuilder.buildHashForProof(publicValues, phiOutputs, data); var hash = converters.base64Decode(hashB64); var hashBytes = converters.bytesFromString(hash); hashBytes.unshift(0); var value = new box.forge.jsbn.BigInteger(hashBytes); return new mathematical.Exponent(_group.getQ(), value); }; this._generateValuesList = function( privateValues, hash, randomExponents, index) { var proofValues = []; var proofValue; if (typeof index === 'undefined') { for (var i = 0; i < privateValues.length; i++) { proofValue = randomExponents[i].getValue().add( privateValues[i].getValue().multiply(hash.getValue())); proofValues.push( new mathematical.Exponent(_group.getQ(), proofValue)); } } else { var numElements = randomExponents.length / 2; var updatedHash = hash; for (var j = 0; j < numElements; j++) { if (j === index) { for (var k = 0; k < numElements; k++) { if (k !== index) { updatedHash = updatedHash.subtract(randomExponents[k]); } } proofValues.push(updatedHash); } else { proofValues.push(randomExponents[j]); } } for (var m = numElements; m < (numElements * 2); m++) { if (m === (numElements + index)) { proofValue = randomExponents[m].getValue().add( privateValues[0].getValue().multiply(updatedHash.getValue())); proofValues.push( new mathematical.Exponent(_group.getQ(), proofValue)); } else { proofValues.push(randomExponents[m]); } } } return proofValues; }; this._updatePreComputedValues = function( preComputedValues, publicValues, elements, index) { var gamma = publicValues[0]; var phi = publicValues[1]; var numElements = elements.length; var challenges = this._calculateRandomExponents(numElements); var updatedExponents = []; updatedExponents.addAll(challenges); updatedExponents.addAll(preComputedValues.getExponents()); var updatedPhiOutputs = []; var phiOutputs = preComputedValues.getPhiOutputs(); for (var j = 0; j < numElements; j++) { var offset = 2 * j; if (j === index) { updatedPhiOutputs.push(phiOutputs[offset]); updatedPhiOutputs.push(phiOutputs[offset + 1]); } else { var gammaFactor = gamma.invert().exponentiate(challenges[j]); var phiFactor = (phi.invert().multiply(elements[j])).exponentiate(challenges[j]); updatedPhiOutputs.push(phiOutputs[offset].multiply(gammaFactor)); updatedPhiOutputs.push(phiOutputs[offset + 1].multiply(phiFactor)); } } return new box.proofs.ProofPreComputedValues( updatedExponents, updatedPhiOutputs); }; }; box.proofs.Prover.prototype = { /** * Pre-compute time-consuming values, so they can be used later for * method prove(). */ preCompute: function(privateValuesLength, progressMeter) { var randomExponents = this._calculateRandomExponents(privateValuesLength); var phiOutputs = this.getPhiFunction().calculatePhi(randomExponents, progressMeter); return new box.proofs.ProofPreComputedValues(randomExponents, phiOutputs); }, /** * Performs prover steps in a ZK-PoK exchange using Maurer's unified PHI * function. This involves generating a proof, using the received inputs * and this Prover's internal fields. */ prove: function( publicValues, privateValues, data, preComputedValues, elements, index) { checkDefined('public values', publicValues); checkDefined('private values', privateValues); checkDefined('data', data); if (!preComputedValues || typeof preComputedValues.getPhiOutputs() === 'undefined' || typeof preComputedValues.getExponents() === 'undefined') { throw new exceptions.CryptoLibException( 'The precomputed values must be initialized'); } if (typeof elements !== 'undefined') { preComputedValues = this._updatePreComputedValues( preComputedValues, publicValues, elements, index); } var hash = this._generateHash( publicValues, preComputedValues.getPhiOutputs(), data); var proofValues = this._generateValuesList( privateValues, hash, preComputedValues.getExponents(), index); return new box.proofs.Proof(hash, proofValues); } }; /** * Acts as a verifier in a Zero-Knowledge Proof of Knowledge (ZK-PoK) * exchange using Maurer's unified PHI function. */ box.proofs.Verifier = function(group, phiFunction) { checkDefined('group', group); checkDefined('phiFunction', phiFunction); var _group = group; var _phiFunction = phiFunction; /** * @return Get the group. */ this.getGroup = function() { return _group; }; /** * @return Get the phi function. */ this.getPhiFunction = function() { return _phiFunction; }; this._calculateComputedValues = function( publicValues, phiOutputs, proofHashValueOrElements, challenges) { var proofHashValue; var elements; if (!isArray(proofHashValueOrElements)) { proofHashValue = proofHashValueOrElements; } else { elements = proofHashValueOrElements; } var computedValues = []; if (typeof proofHashValue !== 'undefined') { for (var i = 0; i < phiOutputs.length; i++) { computedValues.push(phiOutputs[i].multiply( publicValues[i].exponentiate(proofHashValue.negate()))); } } else { var gamma = publicValues[0]; var phi = publicValues[1]; for (var j = 0; j < challenges.length; j++) { var offset = 2 * j; var gammaFactor = gamma.invert().exponentiate(challenges[j]); var phiFactor = (phi.invert().multiply(elements[j])).exponentiate(challenges[j]); computedValues.push(phiOutputs[offset].multiply(gammaFactor)); computedValues.push(phiOutputs[offset + 1].multiply(phiFactor)); } } return computedValues; }; this._calculateHash = function(publicValues, computedValues, data) { var hashBuilder = new box.proofs.HashBuilder(); var hashB64 = hashBuilder.buildHashForProof(publicValues, computedValues, data); var hash = converters.base64Decode(hashB64); var hashBytes = converters.bytesFromString(hash); hashBytes.unshift(0); var value = new box.forge.jsbn.BigInteger(hashBytes); return new mathematical.Exponent(_group.getQ(), value); }; }; box.proofs.Verifier.prototype = { /** * Verifies the received input as being true or false. */ verify: function( publicValues, proof, data, elementsOrProgressMeter, progressMeter) { checkDefined('public values', publicValues); checkDefined('proof', proof); checkDefined('data', data); var elements; if (!isArray(elementsOrProgressMeter)) { progressMeter = elementsOrProgressMeter; } else { elements = elementsOrProgressMeter; } var proofHashValue = proof.getHash(); var phiOutputs; var computedValues; if (typeof elements === 'undefined') { phiOutputs = this.getPhiFunction().calculatePhi( proof.getValues(), progressMeter); computedValues = this._calculateComputedValues( publicValues, phiOutputs, proofHashValue); } else { var numElements = elements.length; var proofValues = proof.getValues(); var challenges = proofValues.slice(0, numElements); var phiInputs = proofValues.slice(numElements, numElements * 2); phiOutputs = this.getPhiFunction().calculatePhi(phiInputs, progressMeter); computedValues = this._calculateComputedValues( publicValues, phiOutputs, elements, challenges); } var calculatedHash = this._calculateHash(publicValues, computedValues, data); return proofHashValue.equals(calculatedHash); } }; /** * @class ProofPreComputedValues stores randomly created exponents and * outputs of box.proofs.PhiFunction.calculatePhi(). */ box.proofs.ProofPreComputedValues = function(exponents, phiOutputs) { this.className = 'ProofPreComputedValues'; var _exponents = exponents; var _phiOutputs = phiOutputs; /** * @return the exponents. */ this.getExponents = function() { return _exponents; }; /** * @return the phi function output. */ this.getPhiOutputs = function() { return _phiOutputs; }; }; box.proofs.ProofPreComputedValues.prototype = { stringify: function() { var exponentValues = Array(); var phiOutputValues = Array(); for (var i = 0; i < this.getExponents().length; i++) { exponentValues[i] = converters.base64FromBigInteger(this.getExponents()[i].getValue()); } for (var j = 0; j < this.getPhiOutputs().length; j++) { phiOutputValues[j] = converters.base64FromBigInteger( this.getPhiOutputs()[j].getElementValue()); } return JSON.stringify({ preComputed: { p: converters.base64FromBigInteger(this.getPhiOutputs()[0].getP()), q: converters.base64FromBigInteger(this.getPhiOutputs()[0].getQ()), exponents: exponentValues, phiOutputs: phiOutputValues } }); } }; /** * Represents a proof in a ZKP exchange. */ box.proofs.Proof = function(hashValue, values) { checkDefined('hash value', hashValue); checkDefined('values', values); var _hashValue = hashValue; var _values = values; /** * @return Get the hash value of this proof. */ this.getHash = function() { return _hashValue; }; /** * @return Get the list of values of this proof. */ this.getValues = function() { return _values; }; }; box.proofs.Proof.prototype = { /** * @return true if the given proof has the same hash and values. */ equals: function(proof) { return proof.getHash().equals(this.getHash()) && proof.getValues().equals(this.getValues()); }, /** * Displays a string representation of this proof JSON. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function * @returns a string representation of this proof JSON. */ stringify: function() { var values = Array(); for (var i = 0; i < this.getValues().length; i++) { values[i] = converters.base64FromBigInteger(this.getValues()[i].getValue()); } return JSON.stringify({ zkProof: { q: converters.base64FromBigInteger(this.getHash().getQ()), hash: converters.base64FromBigInteger(this.getHash().getValue()), values: values } }); } }; /** * A class which provides utility methods for generating a hash from a set * of inputs. */ box.proofs.HashBuilder = function() { /** * Convert an integer to a byte array */ this._getInt64Bytes = function(x) { var bytes = []; var i = 8; do { bytes[--i] = x & (255); x = x >> 8; } while (i); return bytes; }; /** * Converts an array to a String. */ this._arrayElementValuesToString = function(groupElements) { var str = ''; for (var i = 0; i < groupElements.length; i++) { if (groupElements[i].getElementValue() !== undefined) { str += groupElements[i].getElementValue(); } else { throw new exceptions.CryptoLibException( 'Group Element must implement getElementValue() method.'); } } return str; }; }; box.proofs.HashBuilder.prototype = { /** * Generates a hash value based on the element values of publicValues * and generatedValues and the data. * * @function * @param publicValues * {array} group elements representing public values. * @param generatedValues * {array} group elements representing generated values. * @param data * {string} some data encoded as a String. * @returns a generated hash code. * @throws CryptoLibException * if any of the received objects has unsupported type. */ buildHashForProof: function(publicValues, generatedValues, data) { checkInitializedArray('public values', publicValues); checkInitializedArray('generated values', generatedValues); var messageDigester = messageDigestFactory.getCryptoMessageDigest(); messageDigester.start(); messageDigester = update( messageDigester, this._arrayElementValuesToString(publicValues)); messageDigester = update( messageDigester, this._arrayElementValuesToString(generatedValues)); messageDigester = update(messageDigester, data); return messageDigester.digest(); } }; /** * Utility method that updates messageDigester with the provided string. */ function update(messageDigester, str) { var stringRepresentationB64 = converters.base64Encode(str); var arrayToHash = []; arrayToHash.push(stringRepresentationB64); messageDigester.update(arrayToHash); return messageDigester; } /** * Utility method that checks that value is defined and not null and throws * related exception otherwise. */ function checkDefined(key, value) { if (typeof value === 'undefined') { throw new exceptions.CryptoLibException( 'The ' + key + ' must be defined'); } } /** * Utility method that checks that value is initialized array and throws * related exception otherwise. */ function checkInitializedArray(key, value) { checkDefined(key, value); if (!isArray(value)) { throw new exceptions.CryptoLibException( 'The ' + key + ' must be an initialized array'); } } /** * Utility method that checks that value is not null and throws related * exception otherwise. */ function checkNotNull(key, value) { checkDefined(key, value); if (value === null) { throw new exceptions.CryptoLibException( 'The ' + key + ' must not be null'); } } /** * Utility method that checks that value is not empty and throws related * exception otherwise. */ function checkNotEmpty(key, value) { checkDefined(key, value); if (value.length === 0) { throw new exceptions.CryptoLibException( 'The ' + key + ' must not be empty'); } } /** * Utility method that checks that value is positive and throws related * exception otherwise. */ function checkIsPositive(key, value) { checkDefined(key, value); if (isNaN(value)) { throw new exceptions.CryptoLibException( 'The ' + key + ' must be a number; Found ' + value); } if (value <= 0) { throw new exceptions.CryptoLibException( 'The ' + key + ' must be positive; Found ' + value); } } /** * Utility method that checks that values are defined and throws related * exception otherwise. */ var checkDefinedProgressParams = function( progressCallback, progressPercentMinCheckInterval) { if (typeof progressCallback !== 'undefined' && typeof progressCallback !== 'function') { throw new exceptions.CryptoLibException( 'The progress callback function provided as input is not a function'); } if (typeof progressPercentMinCheckInterval !== 'undefined' && isNaN(progressPercentMinCheckInterval)) { throw new exceptions.CryptoLibException( 'The progress percentage check interval provided as input is not a number'); } }; function isArray(a) { return (!!a) && (a.constructor === Array); } /** * Utility method that creates a new instance of ProgressMeter. */ function createProgressMeter( progressMax, progressCallback, progressPercentMinCheckInterval) { checkDefinedProgressParams( progressCallback, progressPercentMinCheckInterval); return new utils.ProgressMeter( progressMax, progressCallback, progressPercentMinCheckInterval); } /** * Provides utility functionality for working with proofs. * * @class utils * @memberof proofs */ box.proofs.utils = (function() { /** * Deserializes a Proof JSON string representation to a Proof object. * * @function * @memberof proofs.utils * @param jsonAsString * {JSON} a JSON representation of a proof. * @returns a new Proof, created from the received string. */ function deserializeProof(jsonAsString) { var proofJson = JSON.parse(jsonAsString).zkProof; var q = converters.base64ToBigInteger(proofJson.q); var hash = new box.commons.mathematical.Exponent( q, converters.base64ToBigInteger(proofJson.hash)); var values = []; for (var i = 0; i < proofJson.values.length; i++) { values.push(new box.commons.mathematical.Exponent( q, converters.base64ToBigInteger(proofJson.values[i]))); } return new box.proofs.Proof(hash, values); } /** * Deserializes a ProofPreComputedValues JSON string representation to a * ProofPreComputedValues object. * * @function * @memberof proofs.utils * @param jsonAsString * {JSON} a JSON representation of a pre-computed value. * @returns a new ProofPreComputedValues, created from the received * string. */ function deserializeProofPreComputedValues(jsonAsString) { var proofPreComputedValuesJson = JSON.parse(jsonAsString).preComputed; var p = converters.base64ToBigInteger(proofPreComputedValuesJson.p); var q = converters.base64ToBigInteger(proofPreComputedValuesJson.q); var exponentValues = proofPreComputedValuesJson.exponents; var exponents = []; var value; for (var i = 0; i < exponentValues.length; i++) { value = converters.base64ToBigInteger(exponentValues[i]); exponents.push(new box.commons.mathematical.Exponent(q, value)); } var phiOutputValues = proofPreComputedValuesJson.phiOutputs; var phiOutputs = []; for (var j = 0; j < phiOutputValues.length; j++) { value = converters.base64ToBigInteger(phiOutputValues[j]); phiOutputs.push( new box.commons.mathematical.ZpGroupElement(value, p, q)); } return new box.proofs.ProofPreComputedValues(exponents, phiOutputs); } return { deserializeProof: deserializeProof, deserializeProofPreComputedValues: deserializeProofPreComputedValues }; })(); }; /* * 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.proofs = cryptolib.modules.proofs || {}; /** * Allows several cryptographic proofs to be generated and verified. *

*/ cryptolib.modules.proofs.service = function(box) { 'use strict'; box.proofs = box.proofs || {}; box.proofs.service = {}; var proofsFactory; var f = function(box) { proofsFactory = new box.proofs.ProofsFactory(); }; f.policies = { proofs: { messagedigest: { algorithm: box.policies.proofs.messagedigest.algorithm, provider: box.policies.proofs.messagedigest.provider }, secureRandom: {provider: box.policies.proofs.secureRandom.provider}, charset: box.policies.proofs.charset }, primitives: { secureRandom: {provider: box.policies.primitives.secureRandom.provider} } }; cryptolib( 'proofs.implementation', 'primitives.securerandom', 'commons.exceptions', 'commons.mathematical', f); /** * Creates a Schnorr proof. * * @param voterID * The voter ID. This should be a string. * @param electionID * The election event ID. This should be a string. * @param exponentiatedElement * The exponentiated element. This should be an element of the * received group. * @param witness * The witness. This should be an Exponent of the received group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The Schnorr proof. */ box.proofs.service.createSchnorrProof = function( voterID, electionID, exponentiatedElement, witness, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createSchnorrProofGenerator(voterID, electionID, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate(exponentiatedElement, witness); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( exponentiatedElement, witness, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate(exponentiatedElement, witness); } }; /** * Creates an exponentiation proof. * * @param exponentiatedElements * The exponentiated elements. These should consist of the * following base elements, exponentiated with the specified * exponent. * @param baseElements * The base elements used for the exponentiations. These should * correspond to elements of the specified group. * @param exponent * The exponent for which this proof is to be generated. It * should be an exponent of the specified group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The exponentiation proof. */ box.proofs.service.createExponentiationProof = function( exponentiatedElements, baseElements, exponent, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createExponentiationProofGenerator(baseElements, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate(exponentiatedElements, exponent); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( exponentiatedElements, exponent, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate(exponentiatedElements, exponent); } }; /** * Creates a plaintext proof. * * @param publicKey * The primary public key. This should be an ElGamal public key. * @param ciphertext * The ciphertext. This should be the output from ElGamal * encryption. * @param plaintext * The plaintext. This should be an array of elements of the * received group. * @param witness * The witness. This should be an Exponent of the received group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The plaintext proof. */ box.proofs.service.createPlaintextProof = function( publicKey, ciphertext, plaintext, witness, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createPlaintextProofGenerator(publicKey, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate(ciphertext, plaintext, witness); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( ciphertext, plaintext, witness, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate(ciphertext, plaintext, witness); } }; /** * Creates a simple plaintext equality Proof. * * @param primaryCiphertext * The primary ciphertext. This should be the output from ElGamal * encryption. * @param primaryPublicKey * The primary public key. This should be an ElGamal public key. * @param witness * The witness of the proof. This should be an Exponent of the * received group. * @param secondaryCiphertext * The secondary ciphertext. This should be the output from * ElGamal encryption. * @param secondaryPublicKey * The secondary public key. This should be an ElGamal public * key. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The simple plaintext equality proof. */ box.proofs.service.createSimplePlaintextEqualityProof = function( primaryCiphertext, primaryPublicKey, witness, secondaryCiphertext, secondaryPublicKey, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createSimplePlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate( primaryCiphertext, secondaryCiphertext, witness); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( primaryCiphertext, secondaryCiphertext, witness, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate(primaryCiphertext, secondaryCiphertext, witness); } }; /** * Creates a plaintext equality proof. * * @param primaryCiphertext * The primary ciphertext. This should be the output from ElGamal * encryption. * @param primaryPublicKey * The primary public key. This should be an ElGamal public key. * @param primaryWitness * The primary witness of the proof. This should be an Exponent * of the received group. * @param secondaryCiphertext * The secondary ciphertext. This should be the output from * ElGamal encryption. * @param secondaryPublicKey * The secondary public key. This should be an ElGamal public * key. * @param secondaryWitness * The secondary witness of the proof. This should be an Exponent * of the received group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The plaintext equality proof. */ box.proofs.service.createPlaintextEqualityProof = function( primaryCiphertext, primaryPublicKey, primaryWitness, secondaryCiphertext, secondaryPublicKey, secondaryWitness, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createPlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate( primaryCiphertext, primaryWitness, secondaryCiphertext, secondaryWitness); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( primaryCiphertext, primaryWitness, secondaryCiphertext, secondaryWitness, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate( primaryCiphertext, primaryWitness, secondaryCiphertext, secondaryWitness); } }; /** * Creates a plaintext exponent equality proof. * * @param ciphertext * The ciphertext. This should be the output from ElGamal * encryption. * @param plaintext * The plaintext. It should be an array of members of the * received group. * @param primaryWitness * The primary witness. This should be an Exponent of the * received group. * @param secondaryWitness * The secondary witness. This should be an Exponent of the * received group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The plaintext exponent equality proof. */ box.proofs.service.createPlaintextExponentEqualityProof = function( ciphertext, plaintext, primaryWitness, secondaryWitness, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createPlaintextExponentEqualityProofGenerator( plaintext, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate( ciphertext, primaryWitness, secondaryWitness); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( ciphertext, primaryWitness, secondaryWitness, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate(ciphertext, primaryWitness, secondaryWitness); } }; /** * Creates an OR-proof. * * @param publicKey * The public key. This should be an ElGamal public key. * @param ciphertext * The ciphertext of the mathematical group element that was * encrypted. This should be the output from ElGamal encryption. * @param witness * The witness used to generate the ciphertext. This should be an * Exponent of the received group. * @param elements * The mathematical group elements, one of which was encrypted. * This should be an array of elements of the received group. * @param index * The index of the mathematical group element that was * encrypted. This should be an integer. * @param data * Any extra data to use for the proof generation. This should be * a String. NOTE: If this parameter is null, then data will be set * to empty string. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallbackOrPreComputedValues * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The OR-proof. */ box.proofs.service.createORProof = function( publicKey, ciphertext, witness, elements, index, data, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { var proofGenerator = proofsFactory.createORProofGenerator(publicKey, elements.length, group); if (!progressCallbackOrPreComputedValues) { return proofGenerator.generate( ciphertext, witness, elements, index, data); } else if (isPreComputedValues(progressCallbackOrPreComputedValues)) { var preComputedValues = progressCallbackOrPreComputedValues; return proofGenerator.generate( ciphertext, witness, elements, index, data, preComputedValues); } else { var progressCallback = progressCallbackOrPreComputedValues; return proofGenerator .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .generate(ciphertext, witness, elements, index, data); } }; /** * Verifies a Schnorr proof. * * @param exponentiatedElement * The exponentiated element. This should be an element of the * specified group. * @param voterID * The voter IS. This should be a string. * @param electionID * The election event ID. This should be a string. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * * @returns true if the Schnorr Proof can be verified, false otherwise. */ box.proofs.service.verifySchnorrProof = function( element, voterID, electionEventID, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory .createSchnorrProofVerifier( element, voterID, electionEventID, proof, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(); }; /** * Verifies an exponentiation proof. * * @param exponentiatedElements * The exponentiated elements. These should consist of the * following base elements, exponentiated with the specified * exponent. * @param baseElements * The base elements used for the exponentiations. These should * correspond to elements of the specified group. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * * @returns true if the exponentiation proof can be verified, false * otherwise. */ box.proofs.service.verifyExponentiationProof = function( exponentiatedElements, baseElements, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory .createExponentiationProofVerifier( exponentiatedElements, baseElements, proof, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(); }; /** * Verifies a plaintext proof. * * @param publicKey * The primary public key. This should be an ElGamal public key. * @param ciphertext * The ciphertext. This should be the output from ElGamal * encryption. * @param plaintext * The plaintext. This should be an array of elements of the * received group. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * @returns true if the plaintext proof can be verified, false otherwise. */ box.proofs.service.verifyPlaintextProof = function( publicKey, ciphertext, plaintext, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory.createPlaintextProofVerifier(publicKey, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(ciphertext, plaintext, proof); }; /** * Verifies a simple plaintext equality proof. * * @param primaryCiphertext * The primary ciphertext. This should be the output from ElGamal * encryption. * @param primaryPublicKey * The primary public key. This should be an ElGamal public key. * @param secondaryCiphertext * The secondary ciphertext. This should be the output from * ElGamal encryption. * @param secondaryPublicKey * The secondary public key. This should be an ElGamal public * key. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * * @returns true if the simple plaintext equality proof can be verified, * false otherwise. */ box.proofs.service.verifySimplePlaintextEqualityProof = function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory .createSimplePlaintextEqualityProofVerifier( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(); }; /** * Verifies a plaintext equality proof. * * @param primaryCiphertext * The primary ciphertext. This should be the output from ElGamal * encryption. * @param primaryPublicKey * The primary public key. This should be an ElGamal public key. * @param secondaryCiphertext * The secondary ciphertext. This should be the output from * ElGamal encryption. * @param secondaryPublicKey * The secondary public key. This should be an ElGamal public * key. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * * @returns true if the plaintext equality proof can be verified, false * otherwise. */ box.proofs.service.verifyPlaintextEqualityProof = function( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory .createPlaintextEqualityProofVerifier( primaryCiphertext, primaryPublicKey, secondaryCiphertext, secondaryPublicKey, proof, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(); }; /** * Verifies a plaintext exponent equality proof. * * @param ciphertext * The ciphertext. This should be the output from ElGamal * encryption. * @param baseElements * The array of base elements. All of these elements should be * members of the received group. * @param witness1 * The primary witness. This should be an Exponent. * @param witness2 * The secondary witness. This should be an Exponent. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * * @returns true if the plaintext exponent equality proof can be verified, * false otherwise. */ box.proofs.service.verifyPlaintextExponentEqualityProof = function( ciphertext, baseElements, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory .createPlaintextExponentEqualityProofVerifier( ciphertext, baseElements, proof, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(); }; /** * Verifies an OR-proof. * * @param publicKey * The public key. This should be an ElGamal public key. * @param ciphertext * The ciphertext of the mathematical group element that was * encrypted. This should be the output from ElGamal encryption. * @param elements * The mathematical group elements, one of which was encrypted. * This should be an array of elements of the received group. * @param data * Any extra data used for the proof generation. This should be a * String. NOTE: If this parameter is null, then data will be set * to empty string. * @param proof * The proof that is to be verified. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval (optional). * @returns true if the OR-proof can be verified, false otherwise. */ box.proofs.service.verifyORProof = function( publicKey, ciphertext, elements, data, proof, group, progressCallback, progressPercentMinCheckInterval) { return proofsFactory .createORProofVerifier(publicKey, elements.length, group) .initProgressMeter(progressCallback, progressPercentMinCheckInterval) .verify(ciphertext, elements, data, proof); }; /** * Pre-computes a Schnorr proof. IMPORTANT: The same pre-computed values * must not be used twice. * * @param voterID * The voter ID. This should be a string. * @param electionID * The election event ID. This should be a string. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The Schnorr proof pre-computed values. */ box.proofs.service.preComputeSchnorrProof = function( voterID, electionID, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory.createSchnorrProofGenerator(voterID, electionID, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Pre-computes an exponentiation proof. IMPORTANT: The same pre-computed * values must not be used twice. * * @param baseElements * The base elements used for the exponentiations. These should * correspond to elements of the specified group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The exponentiation proof pre-computed values. */ box.proofs.service.preComputeExponentiationProof = function( baseElements, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory.createExponentiationProofGenerator(baseElements, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Pre-computes a plaintext proof. IMPORTANT: The same pre-computed values * must not be used twice. * * @param publicKey * The primary public key. This should be an ElGamal public key. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The plaintext proof pre-computed values. */ box.proofs.service.preComputePlaintextProof = function( publicKey, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory.createPlaintextProofGenerator(publicKey, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Pre-computes values for a simple plaintext equality proof. IMPORTANT: The * same pre-computed values must not be used twice. * * @param primaryPublicKey * The primary public key. This should be an ElGamal public key. * @param secondaryPublicKey * The secondary public key. This should be an ElGamal public * key. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The simple plaintext equality proof pre-computed values. */ box.proofs.service.preComputeSimplePlaintextEqualityProof = function( primaryPublicKey, secondaryPublicKey, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory .createSimplePlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Pre-computes values for a plaintext equality proof. IMPORTANT: The same * pre-computed values must not be used twice. * * @param primaryPublicKey * The primary public key. This should be an ElGamal public key. * @param secondaryPublicKey * The secondary public key. This should be an ElGamal public * key. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The plaintext equality proof pre-computed values. */ box.proofs.service.preComputePlaintextEqualityProof = function( primaryPublicKey, secondaryPublicKey, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory .createPlaintextEqualityProofGenerator( primaryPublicKey, secondaryPublicKey, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Returns pre-computed values for a plaintext exponent equality proof. * IMPORTANT: The same pre-computed values must not be used twice. * * @param plaintext * The plaintext. It should be an array of members of the * received group. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The plaintext exponent equality proof pre-computed values. */ box.proofs.service.preComputePlaintextExponentEqualityProof = function( plaintext, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory .createPlaintextExponentEqualityProofGenerator(plaintext, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Pre-computes an OR-proof. IMPORTANT: The same pre-computed values must * not be used twice. * * @param publicKey * The public key. This should be an ElGamal public key. * @param numElements * The number of elements belonging to the received mathematical * group that will be used for the proof, one of which will be * encrypted. * @param group * The mathematical group. This should be a Zp subgroup. * @param progressCallback * Progress callback function or pre-computed values (optional). * @param progressPercentMinCheckInterval * Progress percentage minimum check interval, if the previous * parameter is a progressCallback function (optional). * * @returns The OR-proof pre-computed values. */ box.proofs.service.preComputeORProof = function( publicKey, numElements, group, progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) { return proofsFactory.createORProofGenerator(publicKey, numElements, group) .initProgressMeter( progressCallbackOrPreComputedValues, progressPercentMinCheckInterval) .preCompute(); }; /** * Utility method that checks if the argument is instance of type * ProofPreComputedValues. * * @param values * argument to check. * @returns {boolean} true if values is instance of ProofPreComputedValues. */ function isPreComputedValues(values) { return !!(values) && values.className === 'ProofPreComputedValues'; } }; /* * 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.stores = cryptolib.modules.stores || {}; /** * Defines a pkcs12 store. */ cryptolib.modules.stores.pkcs12 = function(box) { 'use strict'; box.stores = box.stores || {}; box.stores.pkcs12 = {}; var converters; var exceptions; cryptolib('commons', function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; }); /** * Defines a PKCS12. * * @param pkcs12DerB64 * PKCS12, as string in Base64 encoded DER format. */ box.stores.pkcs12.Pkcs12 = function(pkcs12DerB64) { this.pkcs12Der = converters.base64Decode(pkcs12DerB64); this.pkcs12Asn1 = forge.asn1.fromDer(this.pkcs12Der, false); }; box.stores.pkcs12.Pkcs12.prototype = { /** * Retrieves the private key of the PKCS12. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Private key of PKCS12, as string in PEM format. */ getPrivateKey: function(passwordB64) { var aliases = Array.prototype.slice.call(arguments, 1); function isAliasInList(alias) { var returnValue = false; if (aliases.indexOf(alias) >= 0) { returnValue = true; } return returnValue; } var privateKeyAlias = ''; try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve private key safe bag from PKCS12. var privateKeys = {}; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) { privateKeyAlias = safeBag.attributes.friendlyName[0]; if (aliases.length === 0 || isAliasInList(privateKeyAlias)) { privateKeys[privateKeyAlias] = forge.pki.privateKeyToPem(safeBag.key); } } } } // Check whether any private key safe bags were found. if (Object.getOwnPropertyNames(privateKeys).length === 0) { throw new exceptions.CryptoLibException( 'Could not find any private key safe bags in PKCS12.'); } return privateKeys; } catch (error) { var errorMsg = 'Could not retrieve private key from PKCS12.'; if (privateKeyAlias.length > 0) { errorMsg = 'Could not retrieve private key with alias ' + privateKeyAlias + ' from PKCS12.'; } throw new exceptions.CryptoLibException(errorMsg); } }, /** * Retrieves the private key chain from the PKCS12. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Private key chain of PKCS12, as PrivateKeyChain object. */ getPrivateKeyChain: function(passwordB64) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve private key safe bags from PKCS12. var privateKeyChain = new box.stores.pkcs12.PrivateKeyChain(); for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.pkcs8ShroudedKeyBag) { var alias = safeBag.attributes.friendlyName[0]; var privateKeyPem = forge.pki.privateKeyToPem(safeBag.key); privateKeyChain.add(alias, privateKeyPem); } } } // Check whether any private key safe bags were found. if (privateKeyChain.length === 0) { throw new exceptions.CryptoLibException( 'Could not find any private key safe bags in PKCS12.'); } return privateKeyChain; } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve private key chain from PKCS12.', error); } }, /** * Retrieves the certificate chain of the PKCS12. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Certificate chain of PKCS12, as array of strings in PEM * format. */ getCertificateChain: function(passwordB64) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve certificate safe bags from PKCS12. var certificatePemChain = []; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag) { var certificatePem = forge.pki.certificateToPem(safeBag.cert); certificatePemChain.push(certificatePem); } } } // Check whether any certificate safe bags were found. if (certificatePemChain.length === 0) { throw new exceptions.CryptoLibException( 'Could not find any certificate safe bags in PKCS12.'); } return certificatePemChain; } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate chain from PKCS12.', error); } }, /** * Retrieves the certificate chain of the PKCS12 filtered by alias. * * @param passwordB64 * Password as string in Base64 encoded format. * @param alias * Alias to filter * @return Certificate chain of PKCS12, as array of strings in PEM * format. */ getCertificateChainByAlias: function(passwordB64, alias) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); var discardedBags = {}; var nextBagId = null; // Retrieve certificate safe bags from PKCS12. var certificatePemChain = []; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag) { var bagFound = null; // if not initializated the chain, find the one with alias // or if is the one that goes next into the list take it if ((!nextBagId && safeBag.attributes && safeBag.attributes.friendlyName && safeBag.attributes.friendlyName[0] === alias) || (nextBagId && nextBagId === safeBag.cert.subject.hash)) { bagFound = safeBag; } // otherwise, if not initializated and it's not the alias, save it // for later use else { discardedBags[safeBag.cert.subject.hash] = safeBag; } // add the bag and check if we have the next one in the list we // had while (bagFound) { // transform the current to PEM and save it var certificatePem = forge.pki.certificateToPem(bagFound.cert); certificatePemChain.push(certificatePem); // we arrived to the root so we are done! if (bagFound.cert.subject.hash === bagFound.cert.issuer.hash) { return certificatePemChain; } // save which is the next bag we need nextBagId = bagFound.cert.issuer.hash; // go for the next in the list of discarded ones bagFound = discardedBags[nextBagId]; } } } } // Check whether any certificate safe bags were found. if (certificatePemChain.length === 0) { throw new exceptions.CryptoLibException( 'Could not find any certificates for that alias.'); } return certificatePemChain; } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate chain from PKCS12.', error); } }, /** * Retrieves an issuer certificate from the certificate chain of the * PKCS12, given the issuer common name. * * @param passwordB64 * Password as string in Base64 encoded format. * @param issuerCn * Issuer common name of certificate to retrieve, as string. * @return Certificate with given issuer common name, as string in PEM * format. */ getCertificateByIssuer: function(passwordB64, issuerCn) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve certificate from PKCS12. var certificate = null; var certificateFound = false; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag && safeBag.cert.issuer.getField('CN').value === issuerCn) { certificate = safeBag.cert; certificateFound = true; break; } } if (certificateFound) { break; } } // Check whether certificate was found. if (certificate === null) { throw new exceptions.CryptoLibException( 'Could not find certificate in PKCS12 with issuer common name ' + issuerCn); } return forge.pki.certificateToPem(certificate); } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate with issuer common name ' + issuerCn + ' from PKCS12.', error); } }, /** * Retrieves the subject certificate from the certificate chain of the * PKCS12, given the subject common name. * * @param passwordB64 * Password as string in Base64 encoded format. * @param subjectCn * Subject common name of certificate to retrieve, as string. * @return Certificate with given issuer common name, as string in PEM * format. */ getCertificateBySubject: function(passwordB64, subjectCn) { try { var password = converters.base64Decode(passwordB64); var pkcs12 = forge.pkcs12.pkcs12FromAsn1(this.pkcs12Asn1, false, password); // Retrieve subject certificate from PKCS12. var certificate = null; var certificateFound = false; for (var i = 0; i < pkcs12.safeContents.length; i++) { var safeContents = pkcs12.safeContents[i]; for (var j = 0; j < safeContents.safeBags.length; j++) { var safeBag = safeContents.safeBags[j]; if (safeBag.type === forge.pki.oids.certBag && safeBag.cert.subject.getField('CN').value === subjectCn) { certificate = safeBag.cert; certificateFound = true; break; } } if (certificateFound) { break; } } // Check whether certificate was found. if (certificate === null) { throw new exceptions.CryptoLibException( 'Could not find certificate in PKCS12 with subject common name ' + subjectCn); } return forge.pki.certificateToPem(certificate); } catch (error) { throw new exceptions.CryptoLibException( 'Could not retrieve certificate with subject common name ' + subjectCn + ' from PKCS12.', error); } }, /** * Retrieves the PKCS12 as a Base64 encoded DER string. * * @return PKCS12, as Base64 encoded DER string. */ getBase64Encoded: function() { return converters.base64Encode(this.pkcs12Der); } }; /** * Container class for a chain of private keys to be inserted in a key * store. */ box.stores.pkcs12.PrivateKeyChain = function() { this.map = {}; }; box.stores.pkcs12.PrivateKeyChain.prototype = { /** * Adds a private key to the chain. * * @param alias * Alias of private key, as string. * @param privateKeyPem * Private key to add, as string in PEM format. */ add: function(alias, privateKeyPem) { this.map[alias] = privateKeyPem; }, /** * Retrieves a private key from the chain. * * @return Private key chain, as array of strings in PEM format. */ get: function(alias) { return this.map[alias]; } }; }; /* * 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.stores = cryptolib.modules.stores || {}; /** * A module that provides stores services. *

*/ cryptolib.modules.stores.service = function(box) { 'use strict'; box.stores = box.stores || {}; box.stores.service = {}; var sks; cryptolib('stores.sks', function(box) { sks = box.stores.sks; }); /** * @static * * @returns a Sks object. */ box.stores.service.loadStore = function(store) { return new sks.SksReader(store); }; }; /* * 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 */ /** * Scytl Key Store. It is a custom keystore. */ cryptolib.modules.stores.sks = function(box) { 'use strict'; box.stores = box.stores || {}; box.stores.sks = {}; var converters; var secretKeyGeneratorFactory; var cipher; var pkcs12Module; var exceptions; var homomorphicKeyFactory; var f = function(box) { converters = new box.commons.utils.Converters(); cipher = (new box.symmetric.cipher.factory.SymmetricCipherFactory()) .getCryptoSymmetricCipher(); secretKeyGeneratorFactory = new box.primitives.derivation.factory.SecretKeyGeneratorFactory(); pkcs12Module = box.stores.pkcs12; exceptions = box.commons.exceptions; homomorphicKeyFactory = new box.homomorphic.keypair.factory.KeyFactory(); }; f.policies = { symmetric: { secretkey: { encryption: {length: box.policies.symmetric.secretkey.encryption.length}, mac: {length: box.policies.symmetric.secretkey.mac.length}, secureRandom: {provider: box.policies.symmetric.secretkey.secureRandom.provider} }, cipher: { provider: box.policies.symmetric.cipher.provider, algorithm: box.policies.symmetric.cipher.algorithm, initializationVectorLengthBytes: box.policies.symmetric.cipher.initializationVectorLengthBytes, secureRandom: {provider: box.policies.symmetric.cipher.secureRandom.provider} } }, primitives: { derivation: { 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 } } } }; cryptolib( 'commons', 'primitives.derivation', 'symmetric.cipher', 'stores.pkcs12', 'homomorphic.keypair', f); /** * Defines a SksReader. * * @param data. */ box.stores.sks.SksReader = function(data) { // A cache of keys derived from store passwords, to prevent consecutive // operations on a keystore to derive keys each time. var bigintKeys = {}; var derivedKeys = {}; // Create the PBKDF generator. var cryptoPbkdfSecretKeyGenerator = secretKeyGeneratorFactory.createPBKDF(); try { var loadKeyStore = function(data) { var keyStore; if (data) { if (typeof data === 'string') { keyStore = JSON.parse(data); } else if (typeof data === 'object') { keyStore = data; } } return keyStore; }; var validateKeyStoreStructure = function(keyStore) { if (typeof keyStore.salt === 'undefined') { throw new Error('Missing salt'); } if (typeof keyStore.store === 'undefined') { throw new Error('Missing store'); } }; this.keyStore = loadKeyStore(data); validateKeyStoreStructure(this.keyStore); this.store = new pkcs12Module.Pkcs12(this.keyStore.store); } catch (error) { throw new Error('Invalid keyStore: ' + error.message); } function validateEncryptedKey(encryptedKey, keyType) { if (!encryptedKey) { throw new exceptions.CryptoLibException( 'The keyStore does not contain a key of type ' + keyType); } } /** * Derive a key from the keystore password. * * @param {string} passwordB64 the Base64-encoded keystore password * @returns the Base64-encoded key derived from the password */ this._deriveKey = function(passwordB64) { return cryptoPbkdfSecretKeyGenerator.generateSecret( passwordB64, this.keyStore.salt); }; /** * Get the Base64-encoded key derived from the store password. * * @param {string} passwordB64 the Base64-encoded store password * @return the Base64-encoded key derived from the store password */ this._getDerivedKey = function(passwordB64) { if (!derivedKeys.hasOwnProperty(passwordB64)) { derivedKeys[passwordB64] = this._deriveKey(passwordB64); } return derivedKeys[passwordB64]; }; /** * Get the Base64-encoded BigInteger representation of the key derived from * the store password. * * @param {string} passwordB64 the Base64-encoded store password * @returns the Base64-encoded BigInteger representation of the key derived * from the store password */ this._getBigIntKey = function(passwordB64) { if (!bigintKeys.hasOwnProperty(passwordB64)) { var derivedPasswordBase64 = this._getDerivedKey(passwordB64); var derivedPassword = converters.bytesFromString( converters.base64Decode(derivedPasswordBase64)); var derivedPasswordString = new box.forge.jsbn.BigInteger(derivedPassword); var longPasswordB64 = converters.base64Encode(derivedPasswordString.toString(36)); bigintKeys[passwordB64] = longPasswordB64; } return bigintKeys[passwordB64]; }; this._getKey = function(alias, encryptedKey, passwordB64, keyType) { validateEncryptedKey(encryptedKey, keyType); var aliasAndKeyBase64 = this.decryptKey(encryptedKey, passwordB64); var aliasAndKey = converters.base64Decode(aliasAndKeyBase64); var decryptedAlias = aliasAndKey.slice(0, alias.length); if (decryptedAlias !== alias) { throw new exceptions.CryptoLibException( 'The decrypted ' + keyType + ' key does not correspond to the given alias.'); } var key = aliasAndKey.slice(alias.length, aliasAndKey.length); var keyBase64 = converters.base64Encode(key); return keyBase64; }; }; box.stores.sks.SksReader.prototype = { getPrivateKeys: function(passwordB64) { var updateArguments = function(bigIntKey, args) { var argumentsCopy = Array.prototype.slice.call(args, 1); argumentsCopy.unshift(bigIntKey); return argumentsCopy; }; return this.store.getPrivateKey.apply( this.store, updateArguments(this._getBigIntKey(passwordB64), arguments)); }, getSecretKey: function(alias, passwordB64) { var encryptedKey = this.keyStore.secrets[alias]; return this._getKey(alias, encryptedKey, passwordB64, 'secret'); }, getElGamalPrivateKey: function(alias, passwordB64) { var encryptedKey = this.keyStore.egPrivKeys[alias]; var keyBase64 = this._getKey(alias, encryptedKey, passwordB64, 'ElGamal private'); return homomorphicKeyFactory.createPrivateKey(keyBase64); }, decryptKey: function(encryptedKey, passwordB64) { return cipher.decrypt(this._getDerivedKey(passwordB64), encryptedKey); }, /** * Retrieves the private key chain * * @param passwordB64 * Password as string in Base64 encoded format. * @return Private key chain of as PrivateKeyChain object, which * contains aliases and their related private keys in PEM * format. */ getPrivateKeyChain: function(passwordB64) { return this.store.getPrivateKeyChain(this._getBigIntKey(passwordB64)); }, /** * Retrieves the full certificate chain. * * @param passwordB64 * Password as string in Base64 encoded format. * @return Certificate chain as array of strings in PEM format. */ getCertificateChain: function(passwordB64) { return this.store.getCertificateChain(this._getBigIntKey(passwordB64)); }, /** * Retrieves a certificate chain by private key alias * * @param passwordB64 * Password as string in Base64 encoded format. * @param alias * Alias to filter * @return Certificate chain as array of strings in PEM format. */ getCertificateChainByAlias: function(passwordB64, alias) { return this.store.getCertificateChainByAlias( this._getBigIntKey(passwordB64), alias); }, /** * Retrieves a certificate by subject common name. * * @param passwordB64 * Password as string in Base64 encoded format. * @param subjectCn * Subject common name of certificate to retrieve, as string. * @return Certificate with given subject common name, as string in PEM * format. */ getCertificateBySubject: function(passwordB64, subjectCn) { return this.store.getCertificateBySubject( this._getBigIntKey(passwordB64), subjectCn); } }; }; /* * 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.symmetric = cryptolib.modules.symmetric || {}; /** * Defines the symmetric ciphers. * * @namespace symmetric/cipher */ cryptolib.modules.symmetric.cipher = function(box) { 'use strict'; box.symmetric = box.symmetric || {}; box.symmetric.cipher = {}; /** * Allows a symmetric cipher factory to be obtained. * * @exports symmetric/cipher/factory */ box.symmetric.cipher.factory = {}; var policies = { cipher: { algorithm: box.policies.symmetric.cipher.algorithm, provider: box.policies.symmetric.cipher.provider, initializationVectorLengthBytes: box.policies.symmetric.cipher.initializationVectorLengthBytes } }; for (var prop in policies.cipher.algorithm) { if (typeof prop !== 'undefined') { policies.cipher.algorithm = policies.cipher.algorithm[prop]; } } var utils, exceptions, randomFactory; var f = function(box) { utils = box.commons.utils; exceptions = box.commons.exceptions; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.symmetric.cipher.secureRandom.provider} } }; cryptolib('commons', 'primitives.securerandom', f); /** * Factory class that allows a symmetric cipher to be obtained. * * @class */ box.symmetric.cipher.factory.SymmetricCipherFactory = function() {}; box.symmetric.cipher.factory.SymmetricCipherFactory.prototype = { /** * Create a new symmetric cipher. *

* The particular cipher that is returned depends on the policies. * * @function * @returns a new symmetric cipher. */ getCryptoSymmetricCipher: function() { var secureRandom; try { if (policies.cipher.provider === Config.symmetric.cipher.provider.FORGE) { secureRandom = randomFactory.getCryptoRandomBytes(); return this.getCryptoForgeSymmetricCipher(secureRandom); } else { throw new exceptions.CryptoLibException( 'No suitable provider was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoSymmetricCipher could not be obtained.', error); } }, /** * Create a new CryptoForgeSymmetricCipher. * * @function * @param secureRandom * {CryptoScytlRandomBytes} a secure source of random bytes. * @returns {symmetric/cipher.CryptoForgeSymmetricCipher} a new * CryptoForgeSymmetricCipher. */ getCryptoForgeSymmetricCipher: function(secureRandom) { return new CryptoForgeSymmetricCipher(secureRandom); } }; /** * @class * @param randomGenerator * {CryptoScytlRandomBytes} a secure random bytes generator. * @memberof symmetric/cipher */ function CryptoForgeSymmetricCipher(randomGenerator) { this.converters = new utils.Converters(); this.bitOperators = new utils.BitOperators(); this.randomGenerator = randomGenerator; } CryptoForgeSymmetricCipher.prototype = { /** * Encrypts some data. * * @function * @param data * {String} Data to be encrypted, as string in Base64 encoded * format. * @param keyB64 * {String} Key, as string in Base64 encoded format. * @returns Initialization vector concatenated with the encrypted data, * as string in Base64 encoded format. */ encrypt: function(keyB64, dataB64) { try { var key = this.converters.base64Decode(keyB64); var data = this.converters.base64Decode(dataB64); if (key.length !== policies.cipher.algorithm.keyLengthBytes) { throw new exceptions.CryptolibException( 'Key length does not match the required by the algorithm.'); } var initVec = this.randomGenerator.nextRandom( policies.cipher.initializationVectorLengthBytes); // Create a byte buffer for data. var dataBuffer = new box.forge.util.ByteBuffer(data); var cipher = box.forge.cipher.createCipher(policies.cipher.algorithm.name, key); // Only for the GCM mode var gcmAuthTagByteLength = policies.cipher.algorithm.tagLengthBytes; if (typeof gcmAuthTagBitLength !== 'undefined') { cipher.start({iv: initVec, tagLength: gcmAuthTagByteLength}); } else { cipher.start({iv: initVec}); } cipher.update(dataBuffer); cipher.finish(); var encryptedData = cipher.output.getBytes().toString(); if (typeof gcmAuthTagByteLength !== 'undefined') { var gcmAuthTag = cipher.mode.tag.getBytes(); encryptedData = encryptedData + gcmAuthTag.toString(); } var initVectorAndEncryptedData = initVec + encryptedData; var initVectorAndEncryptedDataBase64 = this.converters.base64Encode(initVectorAndEncryptedData); return initVectorAndEncryptedDataBase64; } catch (error) { throw new exceptions.CryptoLibException( 'Data could not be symmetrically encrypted.', error); } }, /** * Decrypts some encrypted data, using the initialization vector that is * provided * * @function * @param keyB64 * {String} the key as a string in Base64 encoded format. * @param initVectorAndEncryptedDataBase64 * {String} Initialization vector concatenated with the * encrypted data, as string in Base64 encoded format. * @returns Decrypted data, as string in Base64 encoded format. */ decrypt: function(keyB64, initVectorAndEncryptedDataBase64) { try { var key = this.converters.base64Decode(keyB64); if (key.length !== policies.cipher.algorithm.keyLengthBytes) { throw new exceptions.CryptolibException( 'Key length does not match the required by the algorithm.'); } var cipher = box.forge.cipher.createDecipher( policies.cipher.algorithm.name, key); var initVectorAndEncryptedData = this.converters.base64Decode(initVectorAndEncryptedDataBase64); var initVector = this.bitOperators.extract( initVectorAndEncryptedData, 0, policies.cipher.initializationVectorLengthBytes); var encryptedData = this.bitOperators.extract( initVectorAndEncryptedData, policies.cipher.initializationVectorLengthBytes, initVectorAndEncryptedData.length); // Only for the GCM mode var gcmAuthTagByteLength = policies.cipher.algorithm.tagLengthBytes; if (typeof gcmAuthTagByteLength !== 'undefined') { var offset = encryptedData.length - gcmAuthTagByteLength; var gcmAuthTag = this.bitOperators.extract( encryptedData, offset, encryptedData.length); encryptedData = this.bitOperators.extract(encryptedData, 0, offset); var gcmAuthTagBitLength = gcmAuthTagByteLength * box.NUMBER_OF_BITS_PER_BYTE; cipher.start({ iv: initVector, tagLength: gcmAuthTagBitLength, tag: gcmAuthTag }); } else { cipher.start({iv: initVector}); } var encryptedDataBuffer = new box.forge.util.ByteBuffer(encryptedData); cipher.update(encryptedDataBuffer); cipher.finish(); var outputBase64 = this.converters.base64Encode(cipher.output.getBytes().toString()); return outputBase64; } catch (error) { throw new exceptions.CryptoLibException( 'Data could not be symmetrically decrypted.', error); } } }; }; /* * 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.symmetric = cryptolib.modules.symmetric || {}; /** * Defines the MACs of the module. * * @namespace symmetric/mac */ cryptolib.modules.symmetric.mac = function(box) { 'use strict'; box.symmetric = box.symmetric || {}; box.symmetric.mac = {}; /** * Allows a symmetric MAC factory to be obtained. * * @exports symmetric/mac/factory */ box.symmetric.mac.factory = {}; var policies = { mac: { hash: box.policies.symmetric.mac.hash, provider: box.policies.symmetric.mac.provider } }; var converters; var exceptions; cryptolib('commons', function(box) { converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; }); /** * Factory class that allows a MAC to be obtained. * * @class */ box.symmetric.mac.factory.MacFactory = function() {}; box.symmetric.mac.factory.MacFactory.prototype = { /** * Create a new MAC. *

* The particular MAC that is returned depends on the policies. * * @function * @returns a new MAC. */ create: function() { try { if (policies.mac.provider === Config.symmetric.mac.provider.FORGE) { return this.createCryptoForgeMac(); } else { throw new exceptions.CryptoLibException( 'No suitable provider was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoMac could not be obtained.', error); } }, /** * Create a new CryptoForgeMac. * * @function * @returns {symmetric/mac.CryptoForgeMac} a new CryptoForgeMac. */ createCryptoForgeMac: function() { return new CryptoForgeMac(); } }; /** * Represents a MAC. * * @class * @memberof symmetric/mac */ function CryptoForgeMac() { this.hmacDigester = forge.hmac.create(); } CryptoForgeMac.prototype = { /** * Generates MAC for some data. * * @function * @param secretKeyBase64 * {String} Secret key, as string in Base64 encoded format * @param arrayDataBase64 * {String} The input data for the MAC, as an array of string * in Base64 encoded format. It is assumed that the array is * not empty. * @returns The generated MAC, in Base64 encoded format. */ generate: function(secretKeyBase64, arrayDataBase64) { var secretKey = converters.base64Decode(secretKeyBase64); var secretKeyBuffer = new forge.util.ByteBuffer(secretKey); try { this.hmacDigester.start(policies.mac.hash, secretKeyBuffer); } catch (error) { throw new exceptions.CryptoLibException( 'MAC message digester could not be initialized.', error); } try { if (arrayDataBase64.length < 1) { throw new exceptions.CryptoLibException( 'The array of data should have at least one element'); } var dataB64; for (var i = 0; i < arrayDataBase64.length; i++) { dataB64 = arrayDataBase64[i]; var data = converters.base64Decode(dataB64); this.hmacDigester.update(data); } } catch (error) { throw new exceptions.CryptoLibException( 'MAC message digester could not be updated.', error); } try { var hmacMessageDigest = this.hmacDigester.digest(); return converters.base64Encode(hmacMessageDigest.getBytes()); } catch (error) { throw new exceptions.CryptoLibException( 'MAC message digest could not be generated.', error); } }, /** * Verify that a given MAC is indeed the MAC for the given data, using * the given secret key. * * @function * @param macBase64 * {String} The MAC to be verified, as string in Base64 * encoded format. * @param secretKeyBase64 * {String} Secret key, as string in Base64 encoded format * @param arrayDataBase64 * {String} The input data for the MAC, as an array of string * in Base64 encoded format. It is assumed that the array is * not empty. * * @returns True if the MAC is the MAC of the given data and SecretKey, * false otherwise. */ verify: function(macBase64, secretKeyBase64, arrayDataBase64) { if (typeof macBase64 === 'undefined' || macBase64 === null) { return false; } var macBase64ToVerify = this.generate(secretKeyBase64, arrayDataBase64); if (macBase64.length !== macBase64ToVerify.length) { return false; } var equals = true; for (var i = 0; i < macBase64ToVerify.length; i++) { if (macBase64ToVerify.charCodeAt(i) !== macBase64.charCodeAt(i)) { equals = false; } } return equals; } }; }; /* * 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.symmetric = cryptolib.modules.symmetric || {}; /** * Defines a symmetric secret key * * @namespace symmetric/secretkey */ cryptolib.modules.symmetric.secretkey = function(box) { 'use strict'; box.symmetric = box.symmetric || {}; box.symmetric.secretkey = {}; /** * Allows a symmetric secret key factory to be obtained. * * @exports symmetric/secretkey/factory */ box.symmetric.secretkey.factory = {}; var policies = { secretkey: { encryption: { lengthBytes: box.policies.symmetric.secretkey.encryption.lengthBytes }, mac: {lengthBytes: box.policies.symmetric.secretkey.mac.lengthBytes} } }; var utils, randomFactory, exceptions, secureRandom, lengthBytes; var f = function(box) { utils = box.commons.utils; exceptions = box.commons.exceptions; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.symmetric.secretkey.secureRandom.provider} } }; cryptolib('commons', 'primitives.securerandom', f); /** * Validates the received length argument, in bytes. * * Confirms that: *

    *
  • the type of lengthBytes is number.
  • *
  • lengthBytes is an integer with a value not less than * 1.
  • *
* * @function validateParameter * @param lengthBytes * {number} the length, in bytes, to be validated. */ var validateParameter = function(lengthBytes) { if (typeof lengthBytes !== 'number' || lengthBytes % 1 !== 0 || lengthBytes < 1) { throw new exceptions.CryptoLibException( 'The given key length is not valid.'); } }; /** * Represents a secret key factory. * * @class */ box.symmetric.secretkey.factory.SecretKeyFactory = function() { secureRandom = randomFactory.getCryptoRandomBytes(); }; box.symmetric.secretkey.factory.SecretKeyFactory.prototype = { /** * Get a CryptoSecretKeyGenerator for encryption. * * @function * @returns {symmetric/secretkey.CryptoSecretKeyGenerator} a * CryptoSecretKeyGenerator for encryption. */ getCryptoSecretKeyGeneratorForEncryption: function() { lengthBytes = policies.secretkey.encryption.lengthBytes; return this.getCryptoSecretKeyGenerator(); }, /** * Get a CryptoSecretKeyGenerator for MAC. * * @function * @returns {symmetric/secretkey.CryptoSecretKeyGenerator} a * CryptoSecretKeyGenerator for MAC. */ getCryptoSecretKeyGeneratorForMac: function() { lengthBytes = policies.secretkey.mac.lengthBytes; return this.getCryptoSecretKeyGenerator(); }, /** * Create a new CryptoSecretKeyGenerator. * * @function * @returns {symmetric/secretkey.CryptoSecretKeyGenerator} a * CryptoSecretKeyGenerator. */ getCryptoSecretKeyGenerator: function() { return new CryptoSecretKeyGenerator(secureRandom); } }; /** * Defines a Secret Key generator. * * @class * @memberof symmetric/secretkey * @param randomGenerator * {CryptoScytlRandomBytes} a secure random bytes generator. */ function CryptoSecretKeyGenerator(randomGenerator) { this.converters = new utils.Converters(); this.randomGenerator = randomGenerator; } CryptoSecretKeyGenerator.prototype = { /** * Generates a secret key. * * @function * @return A secret key, as string in Base64 encoded format. */ generate: function() { try { validateParameter(lengthBytes); var key = this.randomGenerator.nextRandom(lengthBytes); var keyBase64 = this.converters.base64Encode(key); return keyBase64; } catch (error) { throw new exceptions.CryptoLibException( 'CryptoSecretKey could not be generated.', error); } } }; }; /* * 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.symmetric = cryptolib.modules.symmetric || {}; /** * Defines the symmetric service. * @namespace symmetric/service */ cryptolib.modules.symmetric.service = function(box) { 'use strict'; box.symmetric = box.symmetric || {}; /** * A module that provides symmetric cryptographic services. * @exports symmetric/service */ box.symmetric.service = {}; var cipherFactory, secretKeyFactory, macFactory; cryptolib( 'symmetric.cipher', 'symmetric.secretkey', 'symmetric.mac', 'commons.exceptions', function(box) { cipherFactory = new box.symmetric.cipher.factory.SymmetricCipherFactory(); secretKeyFactory = new box.symmetric.secretkey.factory.SecretKeyFactory(); macFactory = new box.symmetric.mac.factory.MacFactory(); }); /** * @function getSecretKeyForEncryption * @returns {string} a secret key as a string encoded in Base64. */ box.symmetric.service.getSecretKeyForEncryption = function() { var secretKeyGenerator = secretKeyFactory.getCryptoSecretKeyGeneratorForEncryption(); return secretKeyGenerator.generate(); }; /** * @function getSecretKeyForMac * @returns {string} a MAC as a string encoded in Base64. */ box.symmetric.service.getSecretKeyForMac = function() { var secretKeyGenerator = secretKeyFactory.getCryptoSecretKeyGeneratorForMac(); return secretKeyGenerator.generate(); }; /** * @function encrypt * @returns {string} the initialization vector concatenated with the encrypted data, * as string in Base64 encoded format. */ box.symmetric.service.encrypt = function(secretKeyBase64, dataBase64) { var cipher = cipherFactory.getCryptoSymmetricCipher(); return cipher.encrypt(secretKeyBase64, dataBase64); }; /** * @function decrypt * @returns {string} the decrypted data, as a String in Base64 encoded format. */ box.symmetric.service.decrypt = function( secretKeyBase64, initVectorAndEncryptedDataBase64) { var cipher = cipherFactory.getCryptoSymmetricCipher(); return cipher.decrypt(secretKeyBase64, initVectorAndEncryptedDataBase64); }; /** * @function getMac * @returns {string} a string in Base64 encoded format. */ box.symmetric.service.getMac = function(secretKeyBase64, arrayDataBase64) { var cryptoMac = macFactory.create(); return cryptoMac.generate(secretKeyBase64, arrayDataBase64); }; /** * @function verifyMac * @returns {boolean} true if the data has been verified. */ box.symmetric.service.verifyMac = function( macBase64, secretKeyBase64, arrayDataBase64) { var cryptoMac = macFactory.create(); return cryptoMac.verify(macBase64, secretKeyBase64, arrayDataBase64); }; }; if (typeof cryptoPRNG !== 'undefined') { cryptolib.cryptoPRNG = cryptoPRNG; cryptolib.Config = Config; if (typeof module !== 'undefined') { module.exports = cryptolib; }; };