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