123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311 |
- /*
- * 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. <b>NOTE:</b> To retrieve data
- * of type <code>string</code>, apply method
- * <code>codec.utf8Decode</code> 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.
- * <p>
- * 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)
- };
- }
|