/* * 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 update, 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 update. 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. NOTE: Data * of type string will be UTF-8 encoded. * @returns {boolean} true if the signature was verified, * false 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 * verify. 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. * NOTE: Data of type string 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; }; }