/*
* 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 validator = require('./input-validator');
var codec = require('scytl-codec');
var forge = require('node-forge');
module.exports = MacHandler;
/**
* @class MacHandler
* @classdesc The MAC handler API. To instantiate this object, use the method
* {@link SymmetricCryptographyService.newMacHandler}.
* @hideconstructor
* @param {Policy}
* policy The cryptographic policy to use.
*/
function MacHandler(policy) {
var macHandler_;
var keyByteBuffer_;
var updated_ = false;
/**
* Initializes the MAC handler with the provided secret key.
*
* @function init
* @memberof MacHandler
* @param {Uint8Array}
* key The key with which to initialize the MAC handler.
* @returns {MacHandler} A reference to this object, to facilitate method
* chaining.
* @throws {Error}
* If the input data validation fails or the MAC handler could
* not be initialized.
*/
this.init = function(key) {
validator.checkIsInstanceOf(
key, Uint8Array, 'Uint8Array',
'Secret key with which to initialize MAC handler');
try {
macHandler_ = forge.hmac.create();
keyByteBuffer_ = new forge.util.ByteBuffer(codec.binaryEncode(key));
macHandler_.start(policy.symmetric.mac.hashAlgorithm, keyByteBuffer_);
} catch (error) {
throw new Error('MAC handler could not be initialized: ' + error.message);
}
return this;
};
/**
* Generates a MAC from 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 MAC will only by generated for the data provided to prior calls to
* the method update
. The MAC handler will be automatically
* reinitialized after this method completes. Before using this method, the
* MAC handler must have been initialized with a secret key, via the method
* {@link MacHandler.init}.
*
* @function generate
* @memberof MacHandler
* @param {Uint8Array|string}
* [data] The data from which to generate the MAC. NOTE:
* Data of type string
will be UTF-8 encoded.
* @returns {Uint8Array} The generated MAC.
* @throws {Error}
* If the input data validation fails, the MAC handler was not
* initialized, the MAC handler was not updated with any data or
* the MAC generation process fails.
*/
this.generate = function(data) {
if (typeof keyByteBuffer_ === 'undefined') {
throw new Error(
'Could not generate MAC; MAC handler was not initialized with any secret key');
}
if (typeof data !== 'undefined') {
if (typeof data === 'string') {
data = codec.utf8Encode(data);
}
validator.checkIsInstanceOf(
data, Uint8Array, 'Uint8Array', 'Data provided to MAC generator');
this.update(data);
} else if (!updated_) {
throw new Error(
'Attempt to generate MAC without either providing data as input or having made previous call to method \'update\'');
}
try {
var macBinaryEncoded = macHandler_.digest().getBytes();
macHandler_.start(policy.symmetric.mac.hashAlgorithm, keyByteBuffer_);
updated_ = false;
return codec.binaryDecode(macBinaryEncoded);
} catch (error) {
throw new Error('MAC could not be generated: ' + error.message);
}
};
/**
* Verfies a that MAC was generated from 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 MAC will only by verified for the data provided
* to prior calls to the method update
. The MAC handler will
* be automatically reinitialized after this method completes. Before using
* this method, the MAC handler must have been initialized with a secret
* key, via the method {@link MacHandler.init}.
*
* @function verify
* @memberof MacHandler
* @param {Uint8Array}
* mac The MAC to be verified.
* @param {Uint8Array}
* [data] The data to check against the MAC. NOTE: Data
* of type string
will be UTF-8 encoded.
* @returns True
if the MAC was verified, false
* otherwise.
* @throws {Error}
* If the input data validation fails, the MAC handler was not
* initialized or the MAC handler was not updated with any data.
*/
this.verify = function(mac, data) {
if (typeof keyByteBuffer_ === 'undefined') {
throw new Error(
'Could not verify MAC; MAC handler was not initialized with any secret key');
}
validator.checkIsInstanceOf(
mac, Uint8Array, 'Uint8Array', 'MAC provided to MAC verifier');
if (typeof data !== 'undefined') {
if (typeof data === 'string') {
data = codec.utf8Encode(data);
}
validator.checkIsInstanceOf(
data, Uint8Array, 'Uint8Array', 'Data provided to MAC verifier');
} else if (!updated_) {
throw new Error(
'Attempt to verify MAC without either providing data as input or having made previous call to method \'update\'');
}
var macFromData = this.generate(data);
if (macFromData.length !== mac.length) {
return false;
}
var verified = true;
for (var i = 0; i < macFromData.length; i++) {
if (macFromData[i] !== mac[i]) {
verified = false;
}
}
return verified;
};
/**
* Updates the MAC handler with the provided data. The data will be
* internally bitwise concatenated to any data provided via previous calls
* to this method, after the last call to the method generate
* or the method verify
. Before using this method, the MAC
* handler must have been initialized with a secret key, via the method
* {@link MacHandler.init}.
*
* @function update
* @memberof MacHandler
* @param {Uint8Array}
* data The data with which to update the MAC handler. NOTE:
* Data of type string
will be UTF-8 encoded.
* @returns {MacHandler} A reference to this object, to facilitate method
* chaining.
* @throws {Error}
* If the input data validation fails or the MAC could not be
* updated.
*/
this.update = function(data) {
if (typeof keyByteBuffer_ === 'undefined') {
throw new Error(
'Could not update MAC; MAC handler was not initialized with any secret key');
}
if (typeof data === 'string') {
data = codec.utf8Encode(data);
}
validator.checkIsInstanceOf(
data, Uint8Array, 'Uint8Array',
'Data with which to update MAC handler');
try {
macHandler_.update(codec.binaryEncode(data));
updated_ = true;
} catch (error) {
throw new Error('MAC handler could not be updated: ' + error.message);
}
return this;
};
}