123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266 |
- /*
- * 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 Policy = require('scytl-cryptopolicy');
- var validator = require('./input-validator');
- var codec = require('scytl-codec');
- var forge = require('node-forge');
- module.exports = SignatureVerifier;
- /**
- * @class SignatureVerifier
- * @classdesc The digital signature verifier API. To instantiate this object,
- * use the method
- * {@link AsymmetricCryptographyService.newSignatureVerifier}.
- * @hideconstructor
- * @param {Policy}
- * policy The cryptographic policy to use.
- * @param {SecureRandomService}
- * secureRandomService The secure random service to use.
- */
- function SignatureVerifier(policy, secureRandomService) {
- // PRIVATE ///////////////////////////////////////////////////////////////////
- var digester_;
- var padding_;
- var forgePublicKey_;
- var updated_;
- function initForgeSigner() {
- if (policy.asymmetric.signer.algorithm !==
- Policy.options.asymmetric.signer.algorithm.RSA) {
- throw new Error(
- 'Signature verifier algorithm \'' +
- policy.asymmetric.signer.algorithm + '\' is not supported.');
- }
- if (policy.asymmetric.signer.hashAlgorithm ===
- Policy.options.asymmetric.signer.hashAlgorithm.SHA256) {
- digester_ = forge.md.sha256.create();
- } else if (
- policy.asymmetric.signer.hashAlgorithm ===
- Policy.options.asymmetric.signer.hashAlgorithm.SHA512_224) {
- digester_ = forge.md.sha512.sha224.create();
- } else {
- throw new Error(
- 'Signature verifier hash algorithm \'' +
- policy.asymmetric.signer.hashAlgorithm + '\' is not supported.');
- }
- digester_.start();
- if (typeof policy.asymmetric.signer.padding !== 'undefined') {
- if (policy.asymmetric.signer.padding.name ===
- Policy.options.asymmetric.signer.padding.PSS.name) {
- var paddingMd;
- if (policy.asymmetric.signer.padding.hashAlgorithm ===
- Policy.options.asymmetric.signer.padding.PSS.hashAlgorithm.SHA256) {
- paddingMd = forge.md.sha256.create();
- } else if (
- policy.asymmetric.signer.padding.hashAlgorithm ===
- Policy.options.asymmetric.signer.padding.PSS.hashAlgorithm
- .SHA512_224) {
- paddingMd = forge.md.sha512.sha224.create();
- } else {
- throw new Error(
- 'Signature verifier PSS padding hash algorithm \'' +
- policy.asymmetric.signer.padding.hashAlgorithm +
- '\' is not supported.');
- }
- var paddingMgf;
- if (policy.asymmetric.signer.padding.maskGenerator.name ===
- Policy.options.asymmetric.signer.padding.PSS.maskGenerator.MGF1
- .name) {
- if (policy.asymmetric.signer.padding.maskGenerator.hashAlgorithm ===
- Policy.options.asymmetric.signer.padding.PSS.maskGenerator.MGF1
- .hashAlgorithm.SHA256) {
- paddingMgf = forge.mgf.mgf1.create(forge.md.sha256.create());
- } else if (
- policy.asymmetric.signer.padding.maskGenerator.hashAlgorithm ===
- Policy.options.asymmetric.signer.padding.PSS.maskGenerator.MGF1
- .hashAlgorithm.SHA512_224) {
- paddingMgf = forge.mgf.mgf1.create(forge.md.sha512.sha224.create());
- } else {
- throw new Error(
- 'Signature verifier PSS padding mask generation function hash algorithm \'' +
- policy.asymmetric.signer.padding.maskGenerator.hashAlgorithm +
- '\' is not supported.');
- }
- } else {
- throw new Error(
- 'Signature verifier PSS padding mask generation function \'' +
- policy.asymmetric.signer.padding.maskGenerator.name +
- '\' is not supported.');
- }
- padding_ = forge.pss.create({
- md: paddingMd,
- mgf: paddingMgf,
- saltLength: policy.asymmetric.signer.padding.saltLengthBytes,
- prng: secureRandomService.newRandomGenerator()
- });
- } else {
- throw new Error(
- 'Signature verifier padding \'' +
- policy.asymmetric.signer.padding.name + '\' is not supported.');
- }
- }
- updated_ = false;
- }
- // CONSTRUCTOR ///////////////////////////////////////////////////////////////
- initForgeSigner();
- // PUBLIC ////////////////////////////////////////////////////////////////////
- /**
- * Initializes the signature verifier with the provided public key.
- *
- * @function init
- * @memberof SignatureVerifier
- * @param {string}
- * publicKey The public key with which to initialize the
- * signature verifier, in PEM format.
- * @returns {SignatureVerifier} A reference to this object, to facilitate
- * method chaining.
- * @throws {Error}
- * If the input data validation fails.
- */
- this.init = function(publicKey) {
- validator.checkIsNonEmptyString(
- publicKey,
- 'Public key (PEM encoded) with which to initialize signature verifier');
- forgePublicKey_ = forge.pki.publicKeyFromPem(publicKey);
- if (typeof forgePublicKey_.verify === 'undefined') {
- throw new Error(
- 'PEM encoding of public key with which to initialize signature verifier is corrupt');
- }
- return this;
- };
- /**
- * Verifies the digital signature of the provided data. If there were any
- * prior calls to the method <code>update</code>, then the provided data
- * will be bitwise appended to the data provided to those calls. If no data
- * is provided here the signature will only be verified for the data
- * provided to prior calls to the method <code>update</code>. The
- * signature verifier will be automatically reinitialized after this method
- * completes. Before using this method, the signature verifier must have
- * been initialized with a public key, via the method
- * {@link SignatureVerifier.init}.
- *
- * @function verify
- * @memberof SignatureVerifier
- * @param {Uint8Array}
- * signature The digital signature to verify.
- * @param {Uint8Array|string}
- * [data] Some data that was digitally signed. <b>NOTE:</b> Data
- * of type <code>string</code> will be UTF-8 encoded.
- * @returns {boolean} <code>true</code> if the signature was verified,
- * <code>false</code> otherwise.
- * @throws {Error}
- * If the input data validation fails, the signature verifier
- * was not initialized, the signature verifier was not updated
- * with any data or the signature verification process fails.
- */
- this.verify = function(signature, data) {
- if (typeof forgePublicKey_ === 'undefined') {
- throw new Error(
- 'Could not verify signature; Signature verifier has not been initialized with any public key');
- }
- validator.checkIsInstanceOf(
- signature, Uint8Array, 'Uint8Array', 'Digital signature to verify');
- if (typeof data !== 'undefined') {
- if (typeof data === 'string') {
- data = codec.utf8Encode(data);
- }
- validator.checkIsInstanceOf(
- data, Uint8Array, 'Uint8Array',
- 'Data provided to signature verifier');
- this.update(data);
- } else if (!updated_) {
- throw new Error(
- 'Attempt to verify a signature without either providing data as input or having made a previous call to method \'update\'');
- }
- var publicExponentFound = forgePublicKey_.e.toString();
- var publicExponentExpected =
- policy.asymmetric.signer.publicExponent.toString();
- if (publicExponentFound !== publicExponentExpected) {
- throw new Error(
- 'Expected public key for verifying signature to have same public exponent as crytographic policy: ' +
- publicExponentExpected + ' ; Found ' + publicExponentFound);
- }
- try {
- var verified = forgePublicKey_.verify(
- digester_.digest().getBytes(), codec.binaryEncode(signature),
- padding_);
- digester_.start();
- updated_ = false;
- return verified;
- } catch (error) {
- throw new Error('Signature could not be verified; ' + error);
- }
- };
- /**
- * Updates the signature verifier with the provided data. The data will be
- * internally bitwise concatenated to any data provided during previous
- * calls to this method, after the last call to the method
- * <code>verify</code>. Before using this method, the signature verifier
- * must have been initialized with a public key, via the method
- * {@link SignatureVerifier.init}.
- *
- * @function update
- * @memberof SignatureVerifier
- * @param {Uint8Array|string}
- * data The data with which to update the signature verifier.
- * <b>NOTE:</b> Data of type <code>string</code> will be UTF-8
- * encoded.
- * @returns {SignatureVerifier} A reference to this object, to facilitate
- * method chaining.
- * @throws {Error}
- * If the input data validation fails or the update process
- * fails.
- */
- this.update = function(data) {
- if (typeof forgePublicKey_ === 'undefined') {
- throw new Error(
- 'Could not update; Signature verifier has not been initialized with any public key');
- }
- if (typeof data === 'string') {
- data = codec.utf8Encode(data);
- }
- validator.checkIsInstanceOf(
- data, Uint8Array, 'Uint8Array',
- 'Data with which to update signature verifier');
- try {
- digester_.update(codec.binaryEncode(data));
- updated_ = true;
- } catch (error) {
- throw new Error(
- 'Signature verifier could not be updated: ' + error.message);
- }
- return this;
- };
- }
|