/*
* 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;
};
}