/* * Copyright 2018 Scytl Secure Electronic Voting SA * * All rights reserved * * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package */ cryptolib.modules.asymmetric = cryptolib.modules.asymmetric || {}; /** * @namespace asymmetric/cipher */ cryptolib.modules.asymmetric.cipher = function(box) { 'use strict'; box.asymmetric = box.asymmetric || {}; box.asymmetric.cipher = {}; /** * A module that holds asymmetric cipher functionalities. * * @exports asymmetric/cipher/factory */ box.asymmetric.cipher.factory = {}; var policies = { cipher: { algorithm: box.policies.asymmetric.cipher.algorithm, secretKeyLengthBytes: box.policies.asymmetric.cipher.secretKeyLengthBytes, ivLengthBytes: box.policies.asymmetric.cipher.ivLengthBytes, tagLengthBytes: box.policies.asymmetric.cipher.tagLengthBytes, deriver: box.policies.asymmetric.cipher.deriver, hash: box.policies.asymmetric.cipher.hash, symmetricCipher: box.policies.asymmetric.cipher.symmetricCipher, provider: box.policies.asymmetric.cipher.provider } }; var utils, converters, exceptions, randomFactory; var f = function(box) { utils = box.commons.utils; converters = new box.commons.utils.Converters(); exceptions = box.commons.exceptions; randomFactory = new box.primitives.securerandom.factory.SecureRandomFactory(); }; f.policies = { primitives: { secureRandom: {provider: box.policies.asymmetric.cipher.secureRandom.provider} } }; cryptolib('commons', 'primitives.securerandom', f); /** @class */ box.asymmetric.cipher.factory.AsymmetricCipherFactory = function() {}; box.asymmetric.cipher.factory.AsymmetricCipherFactory.prototype = { /** * @function Gets an asymmetric cipher. * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher} */ getCryptoAsymmetricCipher: function() { try { if (policies.cipher.provider === Config.asymmetric.cipher.provider.FORGE) { var secureRandomBytes = randomFactory.getCryptoRandomBytes(); return this.getCryptoForgeAsymmetricCipher(secureRandomBytes); } else { throw new exceptions.CryptoLibException( 'No suitable provider for the asymmetric cipher was provided.'); } } catch (error) { throw new exceptions.CryptoLibException( 'A CryptoAsymmetricCipher could not be obtained.', error); } }, /** * @function Gets a Forge asymmetric cipher. * @param secureRandom * {CryptoScytlRandomBytes} a source of random bytes. * @returns {asymmetric/cipher.CryptoForgeAsymmetricCipher} */ getCryptoForgeAsymmetricCipher: function(secureRandom) { return new CryptoForgeAsymmetricCipher(secureRandom); } }; /** * Defines a Forge asymmetric cipher * * @class * @param secureRandomBytes * {CryptoScytlRandomBytes} a source of random bytes. * @memberof asymmetric/cipher */ function CryptoForgeAsymmetricCipher(secureRandomBytes) { if (!secureRandomBytes) { throw new exceptions.CryptoLibException( 'The received PRNG was not valid'); } this.bitOperators = new utils.BitOperators(); this.secureRandomBytes = secureRandomBytes; // We would like to use the SCYTL PRNG directly in the ciphers that are // created. However it is currently not possible to pass a PRNG to the // Forge library when creating or using a cipher. However, in some cases, // it is possible to pass a seed (created using the SCYTL PRNG) to FORGE // that is then used to seed the FORGE internal PRNG. this.encodingSeedLength = 32; try { if ((policies.cipher.algorithm.name !== Config.asymmetric.cipher.algorithm.RSA_OAEP.name) && (policies.cipher.algorithm.name !== Config.asymmetric.cipher.algorithm.RSA_KEM.name)) { throw new exceptions.CryptoLibException( 'The specified algorithm is not supported.'); } } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeAsymmetricCipher could not be created.', error); } /** * Parses the four parts of the data that is produced by the encrypt * function, and that is received by the decrypt function. *

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

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

* [Encapsulation][IV][Encrypted Data][Tag] * * @function * @param publicKeyPem * {string} public key, as string in PEM format. * @param dataBase64 * {string} data to be encrypted, as string in Base64 encoded * format. * @returns encrypted data, as string in Base 64 encoded format.. */ encrypt: function(publicKeyPem, dataBase64) { this._validateInputs(publicKeyPem, dataBase64); try { var publicKey = box.forge.pki.publicKeyFromPem(publicKeyPem); var data = converters.base64Decode(dataBase64); var output; if (policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_OAEP.name) { var encodingOptions = this._getRsaOaepEncodingOptions(); output = publicKey.encrypt( data, policies.cipher.algorithm.name, encodingOptions); } else if ( policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_KEM.name) { var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes; var ivLengthBytes = policies.cipher.ivLengthBytes; var symmetricCipher = policies.cipher.symmetricCipher; var deriver = this._determineAndCreateDeriver(policies.cipher.deriver); // generate and encapsulate secret key var kem = forge.kem.rsa.create(deriver); var result = kem.encrypt(publicKey, secretKeyLengthBytes); var iv = forge.random.getBytesSync(ivLengthBytes); var cipher = forge.cipher.createCipher(symmetricCipher, result.key); cipher.start({iv: iv}); cipher.update(forge.util.createBuffer(data)); cipher.finish(); var encryptedData = cipher.output.getBytes(); var tag = cipher.mode.tag.getBytes(); output = result.encapsulation.toString() + iv.toString() + encryptedData.toString() + tag.toString(); } else { throw new exceptions.CryptoLibException( 'The specified algorithm is not supported.'); } return converters.base64Encode(output); } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeAsymmetricCipher, data could not be encrypted.', error); } }, /** * Decrypts some encrypted data. * * @function * @param privateKeyPem * {string} private key, as string in PEM format. * @param encryptedDataB64 * {string} encrypted data, as string in Base64 encoded * format. * @returns decrypted data, as string in Base64 encoded format. */ decrypt: function(privateKeyPem, encryptedDataBase64) { this._validateInputs(privateKeyPem, encryptedDataBase64); try { var privateKey = box.forge.pki.privateKeyFromPem(privateKeyPem); var encryptedData = converters.base64Decode(encryptedDataBase64); var decryptedData; if (policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_OAEP.name) { var decodingOptions = this._getRsaOaepDecodingOptions(); decryptedData = privateKey.decrypt( encryptedData, policies.cipher.algorithm.name, decodingOptions); } else if ( policies.cipher.algorithm.name === Config.asymmetric.cipher.algorithm.RSA_KEM.name) { var symmetricCipher = policies.cipher.symmetricCipher; var secretKeyLengthBytes = policies.cipher.secretKeyLengthBytes; var encryptedDataParts = this._parseParts(privateKey, encryptedData); var deriver = this._determineAndCreateDeriver(policies.cipher.deriver); // decrypt encapsulated secret key var kem = forge.kem.rsa.create(deriver); var key = kem.decrypt( privateKey, encryptedDataParts.encapsulation, secretKeyLengthBytes); // decrypt some bytes var decipher = forge.cipher.createDecipher(symmetricCipher, key); decipher.start( {iv: encryptedDataParts.iv, tag: encryptedDataParts.tag}); decipher.update(forge.util.createBuffer(encryptedDataParts.data)); var result = decipher.finish(); // result will be false if there was a failure if (result) { decryptedData = decipher.output.getBytes(); } else { throw new exceptions.CryptoLibException( 'Error while decrypting data.'); } } else { throw new exceptions.CryptoLibException( 'The specified algorithm is not supported.'); } return converters.base64Encode(decryptedData); } catch (error) { throw new exceptions.CryptoLibException( 'CryptoForgeAsymmetricCipher, data could not be decrypted.', error); } } }; };