/* * 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: *
* 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: *
element
has an integer value between
* 1
and p-1
:
* (0 < element < p)
elementq mod p = 1
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: *
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: *
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. *
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* 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: *
* 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: *
lengthBytes
is number.lengthBytes
is an integer with a value not less than
* 1.