/* * 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 DigitalEnvelope = require('./envelope'); var cryptoPolicy = require('scytl-cryptopolicy'); var asymmetric = require('scytl-asymmetric'); var symmetric = require('scytl-symmetric'); var validator = require('./input-validator'); var bitwise = require('scytl-bitwise'); var forge = require('node-forge'); module.exports = DigitalEnvelopeOpener; /** * @class DigitalEnvelopeOpener * @classdesc The digital envelope opener API. To instantiate this object, use * the method {@link DigitalEnvelopeService.newOpener}. * @hideconstructor * @param {Object} * [options] An object containing optional arguments. * @param {Policy} * [options.policy=Default policy] The cryptographic policy to use. * @param {SymmetricCryptographyService} * [options.symmetricCryptographyService=Created internally] The * symmetric cryptography service to use. * @param {AsymmetricCryptographyService} * [options.asymmetricCryptographyService=Created internally] The * asymmetric cryptography service to use. */ function DigitalEnvelopeOpener(options) { options = options || {}; var policy_ = cryptoPolicy.newInstance(); if (options.policy) { policy_.symmetric = options.policy.digitalEnvelope.symmetric; policy_.asymmetric.cipher = options.policy.digitalEnvelope.asymmetric.cipher; } var symmetricService; if (options.symmetricCryptographyService) { symmetricService = options.symmetricCryptographyService; } else { symmetricService = symmetric.newService({policy: policy_}); } var asymmetricService; if (options.asymmetricCryptographyService) { asymmetricService = options.asymmetricCryptographyService; } else { asymmetricService = asymmetric.newService({policy: policy_}); } var symmetricCipher_ = symmetricService.newCipher(); var macHandler_ = symmetricService.newMacHandler(); var asymmetricDecrypter_ = asymmetricService.newDecrypter(); var privateKey_; /** * Initializes the Digital envelope opener with the provided private key. * * @function init * @memberof DigitalEnvelopeOpener * @param {string} * privateKey The private key, in PEM format. * @returns {DigitalEnvelopeOpener} 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) to initialize digital envelope opener'); privateKey_ = privateKey; return this; }; /** * 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. Before using this method, the opener must have * been initialized with a private key, via the method * {@link DigitalEnvelopeOpener.init}. * * @function open * @memberof DigitalEnvelopeOpener * @param {DigitalEnvelope} * envelope The digital envelope to open. * @returns {Uint8Array} The data stored in the digital envelope. * @throws {Error} * If the input data validation fails or the digital envelope * could not be opened. */ this.open = function(envelope) { if (typeof privateKey_ === 'undefined') { throw new Error( 'Could not open digital envelope; Envelope opener has not been initialized with any private key'); } validator.checkIsInstanceOf( envelope, DigitalEnvelope, 'DigitalEnvelope', 'Digital envelope to open'); try { // Retrieve data from digital envelope. var encryptedData = envelope.encryptedData; var mac = envelope.mac; var encryptedSecretKeyPairs = envelope.encryptedSecretKeyPairs; // Retrieve asymmetric encryption of bit-wise concatenation of encryption // and MAC secret keys that can be decrypted with private key provided as // input. var encryptedSecretKeyPair = getEncryptedSecretKeyPair(encryptedSecretKeyPairs, privateKey_); // Asymmetrically decrypt bit-wise concatenation of encryption and MAC // secret keys. var secretKeyPair = asymmetricDecrypter_.init(privateKey_) .decrypt(encryptedSecretKeyPair); // Extract secret keys from bit-wise concatenation. var encryptionSecretKeyLength = policy_.symmetric.secretKey.encryption.lengthBytes; var macSecretKeyLength = policy_.symmetric.secretKey.mac.lengthBytes; var encryptionSecretKey = bitwise.slice(secretKeyPair, 0, encryptionSecretKeyLength); var macSecretKey = bitwise.slice( secretKeyPair, encryptionSecretKeyLength, encryptionSecretKeyLength + macSecretKeyLength); // Check integrity of digital envelope. if (!macHandler_.init(macSecretKey).verify(mac, encryptedData)) { throw new Error('Integrity of digital envelope could not be verified.'); } return symmetricCipher_.init(encryptionSecretKey).decrypt(encryptedData); } catch (error) { throw new Error('Digital envelope could not be opened; ' + error); } }; function getEncryptedSecretKeyPair(encryptedSecretKeyPairs, privateKeyPem) { // Extract modulus and public exponent from private key. var privateKey = forge.pki.privateKeyFromPem(privateKeyPem); var modulus = privateKey.n; var publicExponent = privateKey.e; for (var i = 0; i < encryptedSecretKeyPairs.length; i++) { var publicKeyPem = encryptedSecretKeyPairs[i].publicKey; var publicKey = forge.pki.publicKeyFromPem(publicKeyPem); if (publicKey.n.equals(modulus) && publicKey.e.equals(publicExponent)) { return encryptedSecretKeyPairs[i].encryptedKeyPair; } } throw new Error( 'Could not find an asymmetric encryption of the secret key concatenation that could be decrypted using the provided private key.'); } }