/* * Copyright 2018 Scytl Secure Electronic Voting SA * * All rights reserved * * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package */ cryptolib.modules = cryptolib.modules || {}; /** * Defines the generation and opening of digital envelopes. */ cryptolib.modules.digitalenvelope = function(box) { 'use strict'; box.digitalenvelope = box.digitalenvelope || {}; box.digitalenvelope.factory = {}; var policies = { symmetric: { secretkey: { encryption: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey .encryption.lengthBytes }, mac: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey.mac.lengthBytes } } } }; var exceptions; var converters; var bitOperators; var secretKeyFactory; var macDigester; var symmetricCipher; var asymmetricCipher; var f = function(box) { exceptions = box.commons.exceptions; converters = new box.commons.utils.Converters(); bitOperators = new box.commons.utils.BitOperators(); secretKeyFactory = new box.symmetric.secretkey.factory.SecretKeyFactory(); macDigester = (new box.symmetric.mac.factory.MacFactory()).create(); symmetricCipher = (new box.symmetric.cipher.factory.SymmetricCipherFactory()) .getCryptoSymmetricCipher(); asymmetricCipher = (new box.asymmetric.cipher.factory.AsymmetricCipherFactory()) .getCryptoAsymmetricCipher(); }; f.policies = { symmetric: { secretkey: { encryption: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey .encryption.lengthBytes }, mac: { lengthBytes: box.policies.digitalenvelope.symmetric.secretkey.mac.lengthBytes }, secureRandom: { provider: box.policies.digitalenvelope.symmetric.secretkey .secureRandom.provider } }, cipher: { provider: box.policies.digitalenvelope.symmetric.cipher.provider, algorithm: { AES128_GCM: { name: box.policies.digitalenvelope.symmetric.cipher.algorithm .AES128_GCM.name, keyLengthBytes: box.policies.digitalenvelope.symmetric.cipher .algorithm.AES128_GCM.keyLengthBytes, tagLengthBytes: box.policies.digitalenvelope.symmetric.cipher .algorithm.AES128_GCM.tagLengthBytes } }, initializationVectorLengthBytes: box.policies.digitalenvelope.symmetric.cipher .initializationVectorLengthBytes, secureRandom: { provider: box.policies.digitalenvelope.symmetric.cipher.secureRandom .provider } }, mac: { hash: box.policies.digitalenvelope.symmetric.mac.hash, provider: box.policies.digitalenvelope.symmetric.mac.provider } }, asymmetric: { cipher: { algorithm: box.policies.digitalenvelope.asymmetric.cipher.algorithm, secretKeyLengthBytes: box.policies.digitalenvelope.asymmetric.cipher.secretKeyLengthBytes, ivLengthBytes: box.policies.digitalenvelope.asymmetric.cipher.ivLengthBytes, tagLengthBytes: box.policies.digitalenvelope.asymmetric.cipher.tagLengthBytes, deriver: box.policies.digitalenvelope.asymmetric.cipher.deriver, hash: box.policies.digitalenvelope.asymmetric.cipher.hash, symmetricCipher: box.policies.digitalenvelope.asymmetric.cipher.symmetricCipher, provider: box.policies.digitalenvelope.asymmetric.cipher.provider, secureRandom: { provider: box.policies.digitalenvelope.asymmetric.cipher.secureRandom .provider } } } }; cryptolib('commons', 'symmetric', 'asymmetric.cipher', f); /** * Defines a factory for the creation of digital envelope generators and * openers. * * @class DigitalEnvelopeFactory * @memberof digitalenvelope */ box.digitalenvelope.factory.DigitalEnvelopeFactory = function() { }; box.digitalenvelope.factory.DigitalEnvelopeFactory.prototype = { /** * Obtains a new digital envelope generator. * * @function getDigitalEnvelopeGenerator * @return {DigitalEnvelopeGenerator} the digital envelope generator. */ getDigitalEnvelopeGenerator: function() { try { return new box.digitalenvelope.DigitalEnvelopeGenerator(); } catch (error) { throw new exceptions.CryptoLibException( 'Could not obtain a digital envelope generator.', error); } }, /** * Obtains new a digital envelope opener. * * @function getDigitalEnvelopeOpener * @return {DigitalEnvelopeOpener} the digital envelope opener. */ getDigitalEnvelopeOpener: function() { try { return new box.digitalenvelope.DigitalEnvelopeOpener(); } catch (error) { throw new exceptions.CryptoLibException( 'Could not obtain a digital envelope opener.', error); } } }; /** * Defines a digital envelope generator. * * @class DigitalEnvelopeGenerator * @memberof digitalenvelope */ box.digitalenvelope.DigitalEnvelopeGenerator = function() { }; box.digitalenvelope.DigitalEnvelopeGenerator.prototype = { /** * Generates a digital envelope for some data. The envelope can be * generated with one or more public keys, each of whose corresponding * private key can be used to open the envelope. * * @function generate * @param dataBase64 * {string} the data to store in the digital envelope, as a * string in Base64 encoded format. * @param publicKeysPem * {string[]} a list of one or more public keys used to * generate the digital envelope, as an array of strings in * PEM format. * @return {DigitalEnvelope} the generated digital envelope. */ generate: function(dataBase64, publicKeysPem) { try { validateGeneratorInput(dataBase64, publicKeysPem); // Generate encryption secret key and use it to symmetrically encrypt // data. var secretKeyForEncryptionBase64 = secretKeyFactory.getCryptoSecretKeyGeneratorForEncryption() .generate(); var encryptedDataBase64 = symmetricCipher.encrypt(secretKeyForEncryptionBase64, dataBase64); // Generate MAC secret key and use it to generate MAC of symmetrically // Base64 encryption of encrypted data. var secretKeyForMacBase64 = secretKeyFactory.getCryptoSecretKeyGeneratorForMac().generate(); var encryptedDataBase64Base64 = converters.base64Encode(encryptedDataBase64); var macBase64 = macDigester.generate( secretKeyForMacBase64, [encryptedDataBase64Base64]); // Construct bit-wise concatenation of encryption and MAC secret keys. var secretKeyForEncryption = converters.base64Decode(secretKeyForEncryptionBase64); var secretKeyForMac = converters.base64Decode(secretKeyForMacBase64); var secretKeyConcatenation = secretKeyForEncryption.toString() + secretKeyForMac.toString(); var secretKeyConcatenationBase64 = converters.base64Encode(secretKeyConcatenation); // Generate list of asymmetric encryptions of the secret key // concatenation, each encryption created using a different one of the // public keys provided as input for the digital envelope generation. var secretKeyEncryptions = []; for (var i = 0; i < publicKeysPem.length; i++) { var publicKeyPem = publicKeysPem[i]; var encryptedSecretKeyConcatentionBase64 = asymmetricCipher.encrypt( publicKeyPem, secretKeyConcatenationBase64); secretKeyEncryptions.push(new SecretKeyEncryption( encryptedSecretKeyConcatentionBase64, publicKeyPem)); } return new DigitalEnvelope( encryptedDataBase64, macBase64, secretKeyEncryptions); } catch (error) { throw new exceptions.CryptoLibException( 'Digital envelope could not be generated.', error); } } }; /** * Defines a digital envelope opener. * * @class DigitalEnvelopeOpener * @memberof digitalenvelope */ box.digitalenvelope.DigitalEnvelopeOpener = function() { this._getEncryptedSecretKeyConcatenation = function( secretKeyEncryptions, privateKeyPem) { // Extract modulus and public exponent from private key. var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem); var modulus = privateKey.n; var publicExponent = privateKey.e; for (var i = 0; i < secretKeyEncryptions.length; i++) { var publicKeyPem = secretKeyEncryptions[i].getPublicKey(); var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem); if (publicKey.n.equals(modulus) && publicKey.e.equals(publicExponent)) { return secretKeyEncryptions[i].getEncryptedSecretKeyConcatenation(); } } throw new exceptions.CryptoLibException( 'Could not find an asymmetric encryption of the secret key concatenation that could be decrypted using the private key provided.'); }; }; box.digitalenvelope.DigitalEnvelopeOpener.prototype = { /** * Opens a digital envelope and retrieves its data. The envelope can be * opened with any private key corresponding to a public key that was * used to generate the envelope. * * @function open * @param digitalEnvelope * {DigitalEnvelope} the digital envelope to open. * @param privateKeyPem * {string} the private key used to open the digital * envelope, as a string in PEM format. * @return {string} the data stored in the digital envelope, as a string * in Base64 encoded format. */ open: function(digitalEnvelope, privateKeyPem) { try { validateOpenerInput(digitalEnvelope, privateKeyPem); // Retrieve data from digital envelope. var encryptedDataBase64 = digitalEnvelope.getEncryptedData(); var macBase64 = digitalEnvelope.getMac(); var secretKeyEncryptions = digitalEnvelope.getSecretKeyEncryptions(); // Retrieve asymmetric encryption of bit-wise concatenation of // encryption and MAC secret keys that can be decrypted with private // key provided as input. var encryptedSecretKeyConcatenationBase64 = this._getEncryptedSecretKeyConcatenation( secretKeyEncryptions, privateKeyPem); // Asymmetrically decrypt bit-wise concatenation of encryption and MAC // secret keys. var secretKeyConcatenationBase64 = asymmetricCipher.decrypt( privateKeyPem, encryptedSecretKeyConcatenationBase64); var secretKeyConcatenation = converters.base64Decode(secretKeyConcatenationBase64); // Extract secret keys from bit-wise concatenation. var secretKeyForEncryptionLength = policies.symmetric.secretkey.encryption.lengthBytes; var secretKeyForMacLength = policies.symmetric.secretkey.mac.lengthBytes; var secretKeyForEncryption = bitOperators.extract( secretKeyConcatenation, 0, secretKeyForEncryptionLength); var secretKeyForEncryptionBase64 = converters.base64Encode(secretKeyForEncryption); var secretKeyForMac = bitOperators.extract( secretKeyConcatenation, secretKeyForEncryptionLength, secretKeyForEncryptionLength + secretKeyForMacLength); var secretKeyForMacBase64 = converters.base64Encode(secretKeyForMac); // Check integrity of digital envelope. var encryptedDataBase64Base64 = converters.base64Encode(encryptedDataBase64); if (!macDigester.verify( macBase64, secretKeyForMacBase64, [encryptedDataBase64Base64])) { throw new exceptions.CryptoLibException( 'Integrity of digital envelope could not be verified.'); } return symmetricCipher.decrypt( secretKeyForEncryptionBase64, encryptedDataBase64); } catch (error) { throw new exceptions.CryptoLibException( 'Digital envelope could not be opened.', error); } } }; /** * Container class for a digital envelope. * * @class DigitalEnvelope * @param encryptedDataBase64 * {string} the symmetrically encrypted data, as a string in * Base64 encoded format. * @param macBase64 * {string} the MAC of the symmetrically encrypted data, as a * string in Base64 encoded format. * @param secretKeyEncryptions * {SecretKeyEncryption[]} the list of asymmetric encryptions of * the secret key concatenation, each encryption having been * created using a different one of the public keys provided as * input to the digital envelope generator. * @memberof digitalenvelope */ function DigitalEnvelope( encryptedDataBase64, macBase64, secretKeyEncryptions) { this.encryptedDataBase64 = encryptedDataBase64; this.macBase64 = macBase64; this.secretKeyEncryptions = secretKeyEncryptions; var _encryptedSecretKeyConcatsBase64 = []; var _publicKeysPem = []; for (var i = 0; i < secretKeyEncryptions.length; i++) { _encryptedSecretKeyConcatsBase64[i] = secretKeyEncryptions[i].getEncryptedSecretKeyConcatenation(); _publicKeysPem[i] = secretKeyEncryptions[i].getPublicKey(); } this._getEncryptedSecretKeyConcatsBase64 = function() { return _encryptedSecretKeyConcatsBase64; }; this._getPublicKeysPem = function() { return _publicKeysPem; }; } DigitalEnvelope.prototype = { /** * @function getEncryptedData * @return {string} the symmetrically encrypted data, as a string in * Base64 encoded format. */ getEncryptedData: function() { return this.encryptedDataBase64; }, /** * @function getMac * @return {string} the MAC of the symmetrically encrypted data, as a * string in Base64 encoded format. */ getMac: function() { return this.macBase64; }, /** * @function getSecretKeyEncryptions * @return {SecretKeyEncryption[]} the list of asymmetric encryptions of * the secret key concatenation. */ getSecretKeyEncryptions: function() { return this.secretKeyEncryptions; }, /** * Generates a string representation of the digital envelope. *

* Note: in order to permit interoperability between libraries, this * representation should match the equivalent representation in Java. * * @function stringify * @return {string} the string representation of the digital envelope. */ stringify: function() { return JSON.stringify({ digitalEnvelope: { encryptedDataBase64: this.encryptedDataBase64, macBase64: this.macBase64, encryptedSecretKeyConcatsBase64: this._getEncryptedSecretKeyConcatsBase64(), publicKeysPem: this._getPublicKeysPem() } }); } }; /** * Container class for the asymmetric encryption of the bit-wise * concatenation of the secret key used to symmetrically encrypt the data of * a digital envelope and the secret key used to generate an MAC for * checking the integrity of the symmetric encryption. The public key used * to perform the asymmetric encryption of the secret key concatenation is * also included. * * @class SecretKeyEncryption * @param encryptedSecretKeyConcatBase64 * {string} the asymmetric encryption of the secret key * concatenation, using the public key provided as input, as a * string in Base64 encoded format. * @param publicKeyPem * {string} the public key, of type * {@link java.security.PublicKey}, used to asymmetrically * encrypt the secret key concatenation. * @memberof digitalenvelope */ function SecretKeyEncryption(encryptedSecretKeyConcatBase64, publicKeyPem) { /** * @function getEncryptedSecretKeyConcatenationBase64 * @return {string} the asymmetrically encrypted secret key * concatenation, as a string in Base64 encoded format. */ var getEncryptedSecretKeyConcatenation = function() { return encryptedSecretKeyConcatBase64; }; /** * @function getPublicKey * @return {string} the public key used to asymmetrically encrypt the * secret key concatenation, as a string in PEM format. */ var getPublicKey = function() { return publicKeyPem; }; return { getEncryptedSecretKeyConcatenation: getEncryptedSecretKeyConcatenation, getPublicKey: getPublicKey }; } /** * Deserializes a digital envelope. * * @function deserialize * @param serializedDigitalEnvelope * {string} the serialized digital envelope. * @return {DigitalEnvelope} the digital envelope after deserialization. * @memberof digitalenvelope */ box.digitalenvelope.deserialize = function(serializedDigitalEnvelope) { validateDeserializerInput(serializedDigitalEnvelope); var digitalEnvelopeJson = JSON.parse(serializedDigitalEnvelope).digitalEnvelope; var encryptedDataBase64 = digitalEnvelopeJson.encryptedDataBase64; var macBase64 = digitalEnvelopeJson.macBase64; var encryptedSecretKeyConcatsBase64 = digitalEnvelopeJson.encryptedSecretKeyConcatsBase64; var publicKeysPem = digitalEnvelopeJson.publicKeysPem; var secretKeyEncryptions = []; for (var i = 0; i < encryptedSecretKeyConcatsBase64.length; i++) { secretKeyEncryptions.push(new SecretKeyEncryption( encryptedSecretKeyConcatsBase64[i], publicKeysPem[i])); } return new DigitalEnvelope( encryptedDataBase64, macBase64, secretKeyEncryptions); }; function validateGeneratorInput(dataBase64, publicKeysPem) { if (typeof dataBase64 === 'undefined') { throw new exceptions.CryptoLibException( 'Data provided to digital envelope generator is undefined.'); } if (converters.base64Decode(dataBase64) === '') { throw new exceptions.CryptoLibException( 'Data provided to digital envelope generator is empty.'); } if (typeof dataBase64 !== 'string') { throw new exceptions.CryptoLibException( 'Base64 encoded data provided to digital envelope generator is not a String.'); } if (typeof publicKeysPem === 'undefined') { throw new exceptions.CryptoLibException( 'Public key array provided to digital envelope generator is undefined.'); } if (!(publicKeysPem instanceof Array)) { throw new exceptions.CryptoLibException( 'Public key array provided to digital envelope generator is not of type Array.'); } if (publicKeysPem.length === 0) { throw new exceptions.CryptoLibException( 'Public key array provided to digital envelope generator is empty.'); } } function validateOpenerInput(digitalEnvelope, privateKeyPem) { if (typeof digitalEnvelope === 'undefined') { throw new exceptions.CryptoLibException( 'Digital envelope provided to digital envelope opener is undefined.'); } if (!(digitalEnvelope instanceof Object)) { throw new exceptions.CryptoLibException( 'Digital envelope provided to digital envelope generator is not of type Object.'); } if (typeof privateKeyPem === 'undefined') { throw new exceptions.CryptoLibException( 'Private key provided to digital envelope opener is undefined.'); } if (typeof privateKeyPem !== 'string') { throw new exceptions.CryptoLibException( 'Private key PEM provided to digital envelope opener is not of type String.'); } } function validateDeserializerInput(serializedDigitalEnvelope) { if (typeof serializedDigitalEnvelope === 'undefined') { throw new exceptions.CryptoLibException( 'Serialized object provided to digital envelope deserializer is undefined.'); } if (typeof serializedDigitalEnvelope !== 'string') { throw new exceptions.CryptoLibException( 'Serialized object provided to digital envelope deserializer is not of type String.'); } if (serializedDigitalEnvelope.length === 0) { throw new exceptions.CryptoLibException( 'Serialized object provided to digital envelope deserializer is empty.'); } } };