opener.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. /*
  2. * Copyright 2018 Scytl Secure Electronic Voting SA
  3. *
  4. * All rights reserved
  5. *
  6. * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
  7. */
  8. /* jshint node:true */
  9. 'use strict';
  10. var DigitalEnvelope = require('./envelope');
  11. var cryptoPolicy = require('scytl-cryptopolicy');
  12. var asymmetric = require('scytl-asymmetric');
  13. var symmetric = require('scytl-symmetric');
  14. var validator = require('./input-validator');
  15. var bitwise = require('scytl-bitwise');
  16. var forge = require('node-forge');
  17. module.exports = DigitalEnvelopeOpener;
  18. /**
  19. * @class DigitalEnvelopeOpener
  20. * @classdesc The digital envelope opener API. To instantiate this object, use
  21. * the method {@link DigitalEnvelopeService.newOpener}.
  22. * @hideconstructor
  23. * @param {Object}
  24. * [options] An object containing optional arguments.
  25. * @param {Policy}
  26. * [options.policy=Default policy] The cryptographic policy to use.
  27. * @param {SymmetricCryptographyService}
  28. * [options.symmetricCryptographyService=Created internally] The
  29. * symmetric cryptography service to use.
  30. * @param {AsymmetricCryptographyService}
  31. * [options.asymmetricCryptographyService=Created internally] The
  32. * asymmetric cryptography service to use.
  33. */
  34. function DigitalEnvelopeOpener(options) {
  35. options = options || {};
  36. var policy_ = cryptoPolicy.newInstance();
  37. if (options.policy) {
  38. policy_.symmetric = options.policy.digitalEnvelope.symmetric;
  39. policy_.asymmetric.cipher =
  40. options.policy.digitalEnvelope.asymmetric.cipher;
  41. }
  42. var symmetricService;
  43. if (options.symmetricCryptographyService) {
  44. symmetricService = options.symmetricCryptographyService;
  45. } else {
  46. symmetricService = symmetric.newService({policy: policy_});
  47. }
  48. var asymmetricService;
  49. if (options.asymmetricCryptographyService) {
  50. asymmetricService = options.asymmetricCryptographyService;
  51. } else {
  52. asymmetricService = asymmetric.newService({policy: policy_});
  53. }
  54. var symmetricCipher_ = symmetricService.newCipher();
  55. var macHandler_ = symmetricService.newMacHandler();
  56. var asymmetricDecrypter_ = asymmetricService.newDecrypter();
  57. var privateKey_;
  58. /**
  59. * Initializes the Digital envelope opener with the provided private key.
  60. *
  61. * @function init
  62. * @memberof DigitalEnvelopeOpener
  63. * @param {string}
  64. * privateKey The private key, in PEM format.
  65. * @returns {DigitalEnvelopeOpener} A reference to this object, to
  66. * facilitate method chaining.
  67. * @throws {Error}
  68. * If the input data validation fails.
  69. */
  70. this.init = function(privateKey) {
  71. validator.checkIsNonEmptyString(
  72. privateKey,
  73. 'private key (PEM encoded) to initialize digital envelope opener');
  74. privateKey_ = privateKey;
  75. return this;
  76. };
  77. /**
  78. * Opens a digital envelope and retrieves its data. The envelope can be
  79. * opened with any private key corresponding to a public key that was used
  80. * to generate the envelope. Before using this method, the opener must have
  81. * been initialized with a private key, via the method
  82. * {@link DigitalEnvelopeOpener.init}.
  83. *
  84. * @function open
  85. * @memberof DigitalEnvelopeOpener
  86. * @param {DigitalEnvelope}
  87. * envelope The digital envelope to open.
  88. * @returns {Uint8Array} The data stored in the digital envelope.
  89. * @throws {Error}
  90. * If the input data validation fails or the digital envelope
  91. * could not be opened.
  92. */
  93. this.open = function(envelope) {
  94. if (typeof privateKey_ === 'undefined') {
  95. throw new Error(
  96. 'Could not open digital envelope; Envelope opener has not been initialized with any private key');
  97. }
  98. validator.checkIsInstanceOf(
  99. envelope, DigitalEnvelope, 'DigitalEnvelope',
  100. 'Digital envelope to open');
  101. try {
  102. // Retrieve data from digital envelope.
  103. var encryptedData = envelope.encryptedData;
  104. var mac = envelope.mac;
  105. var encryptedSecretKeyPairs = envelope.encryptedSecretKeyPairs;
  106. // Retrieve asymmetric encryption of bit-wise concatenation of encryption
  107. // and MAC secret keys that can be decrypted with private key provided as
  108. // input.
  109. var encryptedSecretKeyPair =
  110. getEncryptedSecretKeyPair(encryptedSecretKeyPairs, privateKey_);
  111. // Asymmetrically decrypt bit-wise concatenation of encryption and MAC
  112. // secret keys.
  113. var secretKeyPair = asymmetricDecrypter_.init(privateKey_)
  114. .decrypt(encryptedSecretKeyPair);
  115. // Extract secret keys from bit-wise concatenation.
  116. var encryptionSecretKeyLength =
  117. policy_.symmetric.secretKey.encryption.lengthBytes;
  118. var macSecretKeyLength = policy_.symmetric.secretKey.mac.lengthBytes;
  119. var encryptionSecretKey =
  120. bitwise.slice(secretKeyPair, 0, encryptionSecretKeyLength);
  121. var macSecretKey = bitwise.slice(
  122. secretKeyPair, encryptionSecretKeyLength,
  123. encryptionSecretKeyLength + macSecretKeyLength);
  124. // Check integrity of digital envelope.
  125. if (!macHandler_.init(macSecretKey).verify(mac, encryptedData)) {
  126. throw new Error('Integrity of digital envelope could not be verified.');
  127. }
  128. return symmetricCipher_.init(encryptionSecretKey).decrypt(encryptedData);
  129. } catch (error) {
  130. throw new Error('Digital envelope could not be opened; ' + error);
  131. }
  132. };
  133. function getEncryptedSecretKeyPair(encryptedSecretKeyPairs, privateKeyPem) {
  134. // Extract modulus and public exponent from private key.
  135. var privateKey = forge.pki.privateKeyFromPem(privateKeyPem);
  136. var modulus = privateKey.n;
  137. var publicExponent = privateKey.e;
  138. for (var i = 0; i < encryptedSecretKeyPairs.length; i++) {
  139. var publicKeyPem = encryptedSecretKeyPairs[i].publicKey;
  140. var publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
  141. if (publicKey.n.equals(modulus) && publicKey.e.equals(publicExponent)) {
  142. return encryptedSecretKeyPairs[i].encryptedKeyPair;
  143. }
  144. }
  145. throw new Error(
  146. 'Could not find an asymmetric encryption of the secret key concatenation that could be decrypted using the provided private key.');
  147. }
  148. }