signer.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  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 = Signer;
  15. /**
  16. * @class Signer
  17. * @classdesc The digital signer API. To instantiate this object, use the method
  18. * {@link AsymmetricCryptographyService.newSigner}.
  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 Signer(policy, secureRandomService) {
  26. // PRIVATE ///////////////////////////////////////////////////////////////////
  27. var digester_;
  28. var padding_;
  29. var forgePrivateKey_;
  30. var updated_;
  31. function initForgeSigner() {
  32. if (policy.asymmetric.signer.algorithm !==
  33. Policy.options.asymmetric.signer.algorithm.RSA) {
  34. throw new Error(
  35. 'Signer algorithm \'' + policy.asymmetric.signer.algorithm +
  36. '\' is not supported.');
  37. }
  38. if (policy.asymmetric.signer.hashAlgorithm ===
  39. Policy.options.asymmetric.signer.hashAlgorithm.SHA256) {
  40. digester_ = forge.md.sha256.create();
  41. } else if (
  42. policy.asymmetric.signer.hashAlgorithm ===
  43. Policy.options.asymmetric.signer.hashAlgorithm.SHA512_224) {
  44. digester_ = forge.md.sha512.sha224.create();
  45. } else {
  46. throw new Error(
  47. 'Signer hash algorithm \'' + policy.asymmetric.signer.hashAlgorithm +
  48. '\' is not supported.');
  49. }
  50. digester_.start();
  51. if (typeof policy.asymmetric.signer.padding !== 'undefined') {
  52. if (policy.asymmetric.signer.padding.name ===
  53. Policy.options.asymmetric.signer.padding.PSS.name) {
  54. var paddingMd;
  55. if (policy.asymmetric.signer.padding.hashAlgorithm ===
  56. Policy.options.asymmetric.signer.padding.PSS.hashAlgorithm.SHA256) {
  57. paddingMd = forge.md.sha256.create();
  58. } else if (
  59. policy.asymmetric.signer.padding.hashAlgorithm ===
  60. Policy.options.asymmetric.signer.padding.PSS.hashAlgorithm
  61. .SHA512_224) {
  62. paddingMd = forge.md.sha512.sha224.create();
  63. } else {
  64. throw new Error(
  65. 'Signer PSS padding hash algorithm \'' +
  66. policy.asymmetric.signer.padding.hashAlgorithm +
  67. '\' is not supported.');
  68. }
  69. var paddingMgf;
  70. if (policy.asymmetric.signer.padding.maskGenerator.name ===
  71. Policy.options.asymmetric.signer.padding.PSS.maskGenerator.MGF1
  72. .name) {
  73. if (policy.asymmetric.signer.padding.maskGenerator.hashAlgorithm ===
  74. Policy.options.asymmetric.signer.padding.PSS.maskGenerator.MGF1
  75. .hashAlgorithm.SHA256) {
  76. paddingMgf = forge.mgf.mgf1.create(forge.md.sha256.create());
  77. } else if (
  78. policy.asymmetric.signer.padding.maskGenerator.hashAlgorithm ===
  79. Policy.options.asymmetric.signer.padding.PSS.maskGenerator.MGF1
  80. .hashAlgorithm.SHA512_224) {
  81. paddingMgf = forge.mgf.mgf1.create(forge.md.sha512.sha224.create());
  82. } else {
  83. throw new Error(
  84. 'Signer PSS padding mask generation function hash algorithm \'' +
  85. policy.asymmetric.signer.padding.maskGenerator.hashAlgorithm +
  86. '\' is not supported.');
  87. }
  88. } else {
  89. throw new Error(
  90. 'Signer PSS padding mask generation function \'' +
  91. policy.asymmetric.signer.padding.maskGenerator.name +
  92. '\' is not supported.');
  93. }
  94. padding_ = forge.pss.create({
  95. md: paddingMd,
  96. mgf: paddingMgf,
  97. saltLength: policy.asymmetric.signer.padding.saltLengthBytes,
  98. prng: secureRandomService.newRandomGenerator()
  99. });
  100. } else {
  101. throw new Error(
  102. 'Signer padding \'' + policy.asymmetric.signer.padding.name +
  103. '\' is not supported.');
  104. }
  105. }
  106. updated_ = false;
  107. }
  108. // CONSTRUCTOR ///////////////////////////////////////////////////////////////
  109. initForgeSigner();
  110. // PUBLIC ////////////////////////////////////////////////////////////////////
  111. /**
  112. * Initializes the digital signer with the provided private key.
  113. *
  114. * @function init
  115. * @memberof Signer
  116. * @param {string}
  117. * privateKeyPem The private key with which to initialize the
  118. * digital signer, in PEM format.
  119. * @returns {Signer} A reference to this object, to facilitate method
  120. * chaining.
  121. * @throws {Error}
  122. * If the input data validation fails.
  123. */
  124. this.init = function(privateKeyPem) {
  125. validator.checkIsNonEmptyString(
  126. privateKeyPem,
  127. 'Private key (PEM encoded) with which to initialize digital signer');
  128. forgePrivateKey_ = forge.pki.privateKeyFromPem(privateKeyPem);
  129. if (typeof forgePrivateKey_.sign === 'undefined') {
  130. throw new Error(
  131. 'PEM encoding of private key with which to initialize digital signer is corrupt');
  132. }
  133. return this;
  134. };
  135. /**
  136. * Digitally signs the provided data. If there were any prior calls to the
  137. * method <code>update</code>, then the provided data will be bitwise
  138. * appended to the data provided to those calls. If no data is provided here
  139. * the signature will only be generated for the data provided to prior calls
  140. * to the method <code>update</code>. The signer will be automatically
  141. * reinitialized after this method completes. Before
  142. * using this method, the signer must have been initialized with a private
  143. * key, via the method {@link Signer.init}.
  144. *
  145. * @function sign
  146. * @memberof Signer
  147. * @param {Uint8Array|string}
  148. * [data] Some data to sign. <b>NOTE:</b> Data of type
  149. * <code>string</code> will be UTF-8 encoded.
  150. * @returns {Uint8Array} The digital signature.
  151. * @throws {Error}
  152. * If the input data validation fails, the signer was not
  153. * initialized, the signer was not updated with any data or the
  154. * digital signing process fails.
  155. */
  156. this.sign = function(data) {
  157. if (typeof forgePrivateKey_ === 'undefined') {
  158. throw new Error(
  159. 'Could not sign; Digital signer has not been initialized with any private key');
  160. }
  161. if (typeof data !== 'undefined') {
  162. if (typeof data === 'string') {
  163. data = codec.utf8Encode(data);
  164. }
  165. validator.checkIsInstanceOf(
  166. data, Uint8Array, 'Uint8Array', 'Data provided to digital signer');
  167. this.update(data);
  168. } else if (!updated_) {
  169. throw new Error(
  170. 'Attempt to digitally sign without either providing data as input or having made a previous call to method \'update\'');
  171. }
  172. var publicExponentFound = forgePrivateKey_.e.toString();
  173. var publicExponentExpected =
  174. policy.asymmetric.signer.publicExponent.toString();
  175. if (publicExponentFound !== publicExponentExpected) {
  176. throw new Error(
  177. 'Expected private key for signing data to have same public exponent as crytographic policy: ' +
  178. publicExponentExpected + ' ; Found ' + publicExponentFound);
  179. }
  180. try {
  181. var signature = forgePrivateKey_.sign(digester_, padding_);
  182. digester_.start();
  183. updated_ = false;
  184. return codec.binaryDecode(signature);
  185. } catch (error) {
  186. throw new Error('Signature could not be generated; ' + error);
  187. }
  188. };
  189. /**
  190. * Updates the signer with the provided data. The data will be internally
  191. * bitwise concatenated to any data provided during previous calls to this
  192. * method, after the last call to the method <code>sign</code>. Before
  193. * using this method, the signer must have been initialized with a private
  194. * key, via the method {@link Signer.init}.
  195. *
  196. * @function update
  197. * @memberof Signer
  198. * @param {Uint8Array|string}
  199. * data The data with which to update the signer. <b>NOTE:</b>
  200. * Data of type <code>string</code> will be UTF-8 encoded.
  201. * @returns {Signer} A reference to this object, to facilitate method
  202. * chaining.
  203. * @throws {Error}
  204. * If the input data validation fails or the update process
  205. * fails.
  206. */
  207. this.update = function(data) {
  208. if (typeof forgePrivateKey_ === 'undefined') {
  209. throw new Error(
  210. 'Could not update; Digital signer has not been initialized with any private key');
  211. }
  212. if (typeof data === 'string') {
  213. data = codec.utf8Encode(data);
  214. }
  215. validator.checkIsInstanceOf(
  216. data, Uint8Array, 'Uint8Array',
  217. 'Data with which to update digital signer');
  218. try {
  219. digester_.update(codec.binaryEncode(data));
  220. updated_ = true;
  221. } catch (error) {
  222. throw new Error('Signer could not be updated: ' + error.message);
  223. }
  224. return this;
  225. };
  226. }