/* * Copyright 2018 Scytl Secure Electronic Voting SA * * All rights reserved * * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package */ /* jshint node:true */ 'use strict'; var Policy = require('scytl-cryptopolicy'); var validator = require('./input-validator'); var bitwise = require('scytl-bitwise'); var codec = require('scytl-codec'); var forge = require('node-forge'); module.exports = AsymmetricDecrypter; /** * @class AsymmetricDecrypter * @classdesc The asymmetric decrypter API. To instantiate this object, use the * method {@link AsymmetricCryptographyService.newDecrypter}. * @hideconstructor * @param {Policy} * policy The cryptographic policy to use. */ function AsymmetricDecrypter(policy) { // PRIVATE /////////////////////////////////////////////////////////////////// var algorithm_; var oaepDigester_; var oeapMaskDigester_; var kemDeriver_; var forgePrivateKey_; function initForgeCipher(algorithm) { if (algorithm.name === Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.name) { oaepDigester_ = getRsaOaepHash(algorithm.hashAlgorithm); if (algorithm.maskGenerator.name === Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.maskGenerator.MGF1 .name) { oeapMaskDigester_ = getRsaOaepMaskHash(algorithm.maskGenerator.hashAlgorithm); } else { throw new Error( 'RSA-OAEP mask generation function \'' + algorithm.maskGenerator.name + '\' is not supported.'); } } else if ( algorithm.name === Policy.options.asymmetric.cipher.algorithm.RSA_KEM.name) { if (algorithm.symmetricCipher !== Policy.options.asymmetric.cipher.algorithm.RSA_KEM.symmetricCipher .AES_GCM) { throw new Error( 'RSA-KEM symmetric cipher algorithm \'' + algorithm.symmetricCipher + '\' is not supported.'); } kemDeriver_ = createDeriver( algorithm.keyDeriver.name, algorithm.keyDeriver.hashAlgorithm); } else { throw new Error( 'Asymmetric decryption algorithm \'' + algorithm.name + '\' is not supported.'); } } // CONSTRUCTOR /////////////////////////////////////////////////////////////// algorithm_ = policy.asymmetric.cipher.algorithm; initForgeCipher(algorithm_); // PUBLIC //////////////////////////////////////////////////////////////////// /** * Initializes the asymmetric decrypter with the provided private key. * * @function init * @memberof AsymmetricDecrypter * @param {string} * privateKey The private key with which to initialize the * asymmetric decrypter, in PEM format. * @returns {AsymmetricDecrypter} A reference to this object, to facilitate * method chaining. * @throws {Error} * If the input data validation fails. */ this.init = function(privateKey) { validator.checkIsNonEmptyString( privateKey, 'Private key (PEM encoded) with which to initialize asymmetric decrypter'); forgePrivateKey_ = forge.pki.privateKeyFromPem(privateKey); if (typeof forgePrivateKey_.decrypt === 'undefined') { throw new Error( 'PEM encoding of private key with which to initialize asymmetric decrypter is corrupt'); } return this; }; /** * Asymmetrically decrypts the provided data. Before using this method, the * decrypter must have been initialized with a private key, via the method * {@link AsymmetricDecrypter.init}. * * @function decrypt * @memberof AsymmetricDecrypter * @param {Uint8Array} * encryptedData The encrypted data to decrypt. * @returns {Uint8Array} The decrypted data. NOTE: To retrieve data * of type string, apply method * codec.utf8Decode to result. * @throws {Error} * If the input data validation fails, the decrypter was not * initialized or the decryption process fails. */ this.decrypt = function(encryptedData) { if (typeof forgePrivateKey_ === 'undefined') { throw new Error( 'Asymmetric decrypter has not been initialized with any private key'); } validator.checkIsInstanceOf( encryptedData, Uint8Array, 'Uint8Array', 'Encrypted data to asymmetrically decrypt'); try { var decryptedData; // Choose a decryption algorithm from the policy. switch (algorithm_.name) { case Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.name: decryptedData = forgePrivateKey_.decrypt( encryptedData, algorithm_.name, {md: oaepDigester_, mgf1: {md: oeapMaskDigester_}}); break; default: decryptedData = kemDecrypt( forgePrivateKey_, encryptedData, algorithm_, kemDeriver_); } return codec.binaryDecode(decryptedData); } catch (error) { throw new Error('Data could not be decrypted; ' + error); } }; } // 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. function getRsaOaepHash(hashAlgorithm) { if (hashAlgorithm === Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.hashAlgorithm .SHA256) { return forge.md.sha256.create(); } else { throw new Error( 'RSA-OAEP cipher hash algorithm \'' + hashAlgorithm + '\' is not supported.'); } } function getRsaOaepMaskHash(hashAlgorithm) { // For interoperability purposes, the MGF1 hash function must // remain as SHA-1. if (hashAlgorithm === Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.maskGenerator.MGF1 .hashAlgorithm.SHA1) { return forge.md.sha1.create(); } else { throw new Error( 'RSA-OAEP cipher mask generation function hash algorithm \'' + hashAlgorithm + '\' is not supported.'); } } function createDigester(hashAlgorithm) { if (hashAlgorithm === Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver .hashAlgorithm.SHA256) { return forge.md.sha256.create(); } else if ( hashAlgorithm === Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver .hashAlgorithm.SHA512_224) { return forge.md.sha512.sha224.create(); } else { throw new Error( 'RSA-KEM cipher key derivation hash algorithm \'' + hashAlgorithm + '\' is not supported.'); } } function createDeriver(deriverName, hashAlgorithm) { var hash = createDigester(hashAlgorithm); switch (deriverName) { case Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver.name .KDF1: return new forge.kem.kdf1(hash); case Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver.name .KDF2: return new forge.kem.kdf2(hash); case Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver.name .MGF1: return new forge.mgf.mgf1.create(hash); default: throw new Error( 'RSA-KEM cipher key derivation function \'' + deriverName + '\' is not supported.'); } } /** * Decrypt some encrypted data using the 'RSA-KEM' cipher algorithm. * * @function kemDecrypt * @memberof AsymmetricDecrypter * @private * @param {Object} * privateKey The private key used for decrypting. * @param {Object} * encryptedData The encrypted data to decrypt. * @param {Object} * algorithm The cipher algorithm. * @returns {Object} The decrypted data. */ function kemDecrypt(privateKey, encryptedData, algorithm, deriver) { var encryptedDataParts = parseParts(privateKey, encryptedData, algorithm); // var deriver = createDeriver( // algorithm.keyDeriver.name, algorithm.keyDeriver.hashAlgorithm); // decrypt encapsulated secret key var kem = forge.kem.rsa.create(deriver); var key = kem.decrypt( privateKey, encryptedDataParts.encapsulation, algorithm.secretKeyLengthBytes); // decrypt some bytes var decipher = forge.cipher.createDecipher(algorithm.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) { return decipher.output.getBytes(); } else { throw new Error('Data could not be KEM decrypted.'); } } /** * Parses the four parts of the data that are produced by the encrypt function, * and that are 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 all four of the parts. * * @function parseParts * @memberof AsymmetricDecrypter * @private * @param {Object} * privateKey The private key used for decrypting. * @param {Object} * encryptedData The encrypted data to decrypt. * @param {Object} * algorithm The cipher algorithm. returns {Object} The object * encapsulating the parsing results. */ function parseParts(privateKey, encryptedData, algorithm) { var ivLengthBytes = algorithm.ivLengthBytes; var tagLengthBytes = algorithm.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 = bitwise.slice(encryptedData, 0, startIndexOfSecondPart); var iv = bitwise.slice( encryptedData, startIndexOfSecondPart, startIndexOfThirdPart); var data = bitwise.slice( encryptedData, startIndexOfThirdPart, startIndexOfFourthPart); var tag = bitwise.slice(encryptedData, startIndexOfFourthPart); return { encapsulation: codec.binaryEncode(encapsulation), iv: codec.binaryEncode(iv), data: codec.binaryEncode(data), tag: codec.binaryEncode(tag) }; }