2
0

signature-verifier.js 9.7 KB

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