encrypter.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  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 Policy = require('scytl-cryptopolicy');
  11. var validator = require('./input-validator');
  12. var codec = require('scytl-codec');
  13. var forge = require('node-forge');
  14. module.exports = AsymmetricEncrypter;
  15. /**
  16. * @class AsymmetricEncrypter
  17. * @classdesc The asymmetric encrypter API. To instantiate this object, use the
  18. * method {@link AsymmetricCryptographyService.newEncrypter}.
  19. * @hideconstructor
  20. * @param {Policy}
  21. * policy The cryptographic policy to use.
  22. * @param {SecureRandomService}
  23. * secureRandomService The secure random service to use.
  24. */
  25. function AsymmetricEncrypter(policy, secureRandomService) {
  26. // PRIVATE ///////////////////////////////////////////////////////////////////
  27. var algorithm_;
  28. var oaepDigester_;
  29. var oeapMaskDigester_;
  30. var kemDeriver_;
  31. var randomGenerator_;
  32. var forgePublicKey_;
  33. function initForgeCipher(algorithm) {
  34. if (algorithm.name ===
  35. Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.name) {
  36. oaepDigester_ = getRsaOaepHash(algorithm.hashAlgorithm);
  37. if (algorithm.maskGenerator.name ===
  38. Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.maskGenerator.MGF1
  39. .name) {
  40. oeapMaskDigester_ =
  41. getRsaOaepMaskHash(algorithm.maskGenerator.hashAlgorithm);
  42. } else {
  43. throw new Error(
  44. 'Asymmetric encrypter RSA-OAEP mask generation function \'' +
  45. algorithm.maskGenerator.name + '\' is not supported.');
  46. }
  47. } else if (
  48. algorithm.name ===
  49. Policy.options.asymmetric.cipher.algorithm.RSA_KEM.name) {
  50. if (algorithm.symmetricCipher !==
  51. Policy.options.asymmetric.cipher.algorithm.RSA_KEM.symmetricCipher
  52. .AES_GCM) {
  53. throw new Error(
  54. 'Asymmetric encrypter RSA-KEM symmetric cipher algorithm \'' +
  55. algorithm.symmetricCipher + '\' is not supported.');
  56. }
  57. kemDeriver_ = createDeriver(
  58. algorithm.keyDeriver.name, algorithm.keyDeriver.hashAlgorithm);
  59. } else {
  60. throw new Error(
  61. 'Asymmetric encryption algorithm \'' + algorithm.name +
  62. '\' is not supported.');
  63. }
  64. }
  65. // CONSTRUCTOR ///////////////////////////////////////////////////////////////
  66. algorithm_ = policy.asymmetric.cipher.algorithm;
  67. randomGenerator_ = secureRandomService.newRandomGenerator();
  68. initForgeCipher(algorithm_);
  69. // PUBLIC ////////////////////////////////////////////////////////////////////
  70. /**
  71. * Initializes the asymmetric encrypter with the provided public key.
  72. *
  73. * @function init
  74. * @memberof AsymmetricEncrypter
  75. * @param {string}
  76. * publicKey The public key with which to initialize the
  77. * asymmetric encrypter, in PEM format.
  78. * @returns {AsymmetricEncrypter} A reference to this object, to facilitate
  79. * method chaining.
  80. * @throws {Error}
  81. * If the input data validation fails.
  82. */
  83. this.init = function(publicKey) {
  84. validator.checkIsNonEmptyString(
  85. publicKey,
  86. 'Public key (PEM encoded) with which to initialize asymmetric encrypter');
  87. forgePublicKey_ = forge.pki.publicKeyFromPem(publicKey);
  88. if (typeof forgePublicKey_.encrypt === 'undefined') {
  89. throw new Error(
  90. 'PEM encoding of public key with which to initialize encrypter is corrupt');
  91. }
  92. return this;
  93. };
  94. /**
  95. * Asymmetrically encrypts the provided data. If the cipher algorithm is set
  96. * to <code>RSA-KEM</code> in the cryptographic policy being used, then
  97. * the output of this function will be in the form
  98. * <code>[Encapsulation][IV][Encrypted Data][Tag]</code>. Before using
  99. * this method, the encrypter must have been initialized with a public key,
  100. * via the method {@link AsymmetricEncrypter.init}.
  101. *
  102. * @function encrypt
  103. * @memberof AsymmetricEncrypter
  104. * @param {Uint8Array|string}
  105. * data The data to encrypt. <b>NOTE:</b> Data of type
  106. * <code>string</code> will be UTF-8 encoded.
  107. * @returns {Uint8Array} The encrypted data.
  108. * @throws {Error}
  109. * If the input data validation fails, the encrypter was not
  110. * initialized or the encryption process fails.
  111. */
  112. this.encrypt = function(data) {
  113. if (typeof forgePublicKey_ === 'undefined') {
  114. throw new Error(
  115. 'Asymmetric encrypter has not been initialized with any public key');
  116. }
  117. if (typeof data === 'string') {
  118. data = codec.utf8Encode(data);
  119. }
  120. validator.checkIsInstanceOf(
  121. data, Uint8Array, 'Uint8Array', 'Data to asymmetrically encrypt');
  122. try {
  123. var output;
  124. // Choose the encryption algorithm from the policy.
  125. switch (algorithm_.name) {
  126. case Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.name:
  127. output = forgePublicKey_.encrypt(
  128. codec.binaryEncode(data), algorithm_.name, {
  129. md: oaepDigester_,
  130. mgf1: {md: oeapMaskDigester_},
  131. seed: codec.binaryEncode(secureRandomService.nextSeed())
  132. });
  133. break;
  134. default:
  135. output = kemEncrypt(
  136. forgePublicKey_, codec.binaryEncode(data), algorithm_,
  137. kemDeriver_, randomGenerator_);
  138. break;
  139. }
  140. return codec.binaryDecode(output);
  141. } catch (error) {
  142. throw new Error('Data could not be encrypted; ' + error);
  143. }
  144. };
  145. }
  146. // Note: at the moment, the options that are being used with the RSA_OAEP
  147. // algorithm are hardcoded (for encrypting and decrypting). This could be
  148. // improved so that these are read from the properties file. Doing this will
  149. // mean any that existing properties files (used by consumers of the
  150. // library) will become invalid (if the consumer uses RSA_OAEP) as they wont
  151. // have the mandatory new properties.
  152. function getRsaOaepHash(hashAlgorithm) {
  153. if (hashAlgorithm ===
  154. Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.hashAlgorithm
  155. .SHA256) {
  156. return forge.md.sha256.create();
  157. } else {
  158. throw new Error(
  159. 'RSA-OAEP cipher hash algorithm \'' + hashAlgorithm +
  160. '\' is not supported.');
  161. }
  162. }
  163. function getRsaOaepMaskHash(hashAlgorithm) {
  164. // For interoperability purposes, the MGF1 hash function must
  165. // remain as SHA-1.
  166. if (hashAlgorithm ===
  167. Policy.options.asymmetric.cipher.algorithm.RSA_OAEP.maskGenerator.MGF1
  168. .hashAlgorithm.SHA1) {
  169. return forge.md.sha1.create();
  170. } else {
  171. throw new Error(
  172. 'RSA-OAEP cipher mask generation function hash algorithm \'' +
  173. hashAlgorithm + '\' is not supported.');
  174. }
  175. }
  176. function createDigester(hashAlgorithm) {
  177. if (hashAlgorithm ===
  178. Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver
  179. .hashAlgorithm.SHA256) {
  180. return forge.md.sha256.create();
  181. } else if (
  182. hashAlgorithm ===
  183. Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver
  184. .hashAlgorithm.SHA512_224) {
  185. return forge.md.sha512.sha224.create();
  186. } else {
  187. throw new Error(
  188. 'RSA-KEM cipher key derivation hash algorithm \'' + hashAlgorithm +
  189. '\' is not supported.');
  190. }
  191. }
  192. function createDeriver(deriverName, hashAlgorithm) {
  193. var hash = createDigester(hashAlgorithm);
  194. switch (deriverName) {
  195. case Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver.name
  196. .KDF1:
  197. return new forge.kem.kdf1(hash);
  198. case Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver.name
  199. .KDF2:
  200. return new forge.kem.kdf2(hash);
  201. case Policy.options.asymmetric.cipher.algorithm.RSA_KEM.keyDeriver.name
  202. .MGF1:
  203. return new forge.mgf.mgf1.create(hash);
  204. default:
  205. throw new Error(
  206. 'RSA-KEM cipher key derivation function \'' + deriverName +
  207. '\' is not supported.');
  208. }
  209. }
  210. /**
  211. * Encrypts some data using the 'RSA-KEM' cipher algorithm.
  212. *
  213. * @function kemEncrypt
  214. * @memberof AsymmetricEncrypter
  215. * @private
  216. * @param {Object}
  217. * publicKey The public key to use for encrypting
  218. * @param {Object}
  219. * data The data to encrypt.
  220. * @param {Object}
  221. * algorithm The cipher algorithm.
  222. * @param {Object}
  223. * randomGenerator The random generator to use.
  224. * @returns {Object} The encrypted data.
  225. */
  226. function kemEncrypt(publicKey, data, algorithm, deriver, randomGenerator) {
  227. // generate and encapsulate secret key
  228. var kem = forge.kem.rsa.create(deriver);
  229. var result = kem.encrypt(publicKey, algorithm.secretKeyLengthBytes);
  230. var iv =
  231. codec.binaryEncode(randomGenerator.nextBytes(algorithm.ivLengthBytes));
  232. var cipher = forge.cipher.createCipher(algorithm.symmetricCipher, result.key);
  233. cipher.start({iv: iv});
  234. cipher.update(forge.util.createBuffer(data));
  235. cipher.finish();
  236. var encryptedData = cipher.output.getBytes();
  237. var tag = cipher.mode.tag.getBytes();
  238. return result.encapsulation.toString() + iv.toString() +
  239. encryptedData.toString() + tag.toString();
  240. }