mac-handler.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /*
  2. * Copyright 2018 Scytl Secure Electronic Voting SA
  3. *
  4. * All rights reserved
  5. *
  6. * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
  7. */
  8. /* jshint node:true */
  9. 'use strict';
  10. var validator = require('./input-validator');
  11. var codec = require('scytl-codec');
  12. var forge = require('node-forge');
  13. module.exports = MacHandler;
  14. /**
  15. * @class MacHandler
  16. * @classdesc The MAC handler API. To instantiate this object, use the method
  17. * {@link SymmetricCryptographyService.newMacHandler}.
  18. * @hideconstructor
  19. * @param {Policy}
  20. * policy The cryptographic policy to use.
  21. */
  22. function MacHandler(policy) {
  23. var macHandler_;
  24. var keyByteBuffer_;
  25. var updated_ = false;
  26. /**
  27. * Initializes the MAC handler with the provided secret key.
  28. *
  29. * @function init
  30. * @memberof MacHandler
  31. * @param {Uint8Array}
  32. * key The key with which to initialize the MAC handler.
  33. * @returns {MacHandler} A reference to this object, to facilitate method
  34. * chaining.
  35. * @throws {Error}
  36. * If the input data validation fails or the MAC handler could
  37. * not be initialized.
  38. */
  39. this.init = function(key) {
  40. validator.checkIsInstanceOf(
  41. key, Uint8Array, 'Uint8Array',
  42. 'Secret key with which to initialize MAC handler');
  43. try {
  44. macHandler_ = forge.hmac.create();
  45. keyByteBuffer_ = new forge.util.ByteBuffer(codec.binaryEncode(key));
  46. macHandler_.start(policy.symmetric.mac.hashAlgorithm, keyByteBuffer_);
  47. } catch (error) {
  48. throw new Error('MAC handler could not be initialized: ' + error.message);
  49. }
  50. return this;
  51. };
  52. /**
  53. * Generates a MAC from the provided data. If there were any prior calls to
  54. * the method <code>update</code>, then the provided data will be bitwise
  55. * appended to the data provided to those calls. If no data is provided here
  56. * the MAC will only by generated for the data provided to prior calls to
  57. * the method <code>update</code>. The MAC handler will be automatically
  58. * reinitialized after this method completes. Before using this method, the
  59. * MAC handler must have been initialized with a secret key, via the method
  60. * {@link MacHandler.init}.
  61. *
  62. * @function generate
  63. * @memberof MacHandler
  64. * @param {Uint8Array|string}
  65. * [data] The data from which to generate the MAC. <b>NOTE:</b>
  66. * Data of type <code>string</code> will be UTF-8 encoded.
  67. * @returns {Uint8Array} The generated MAC.
  68. * @throws {Error}
  69. * If the input data validation fails, the MAC handler was not
  70. * initialized, the MAC handler was not updated with any data or
  71. * the MAC generation process fails.
  72. */
  73. this.generate = function(data) {
  74. if (typeof keyByteBuffer_ === 'undefined') {
  75. throw new Error(
  76. 'Could not generate MAC; MAC handler was not initialized with any secret key');
  77. }
  78. if (typeof data !== 'undefined') {
  79. if (typeof data === 'string') {
  80. data = codec.utf8Encode(data);
  81. }
  82. validator.checkIsInstanceOf(
  83. data, Uint8Array, 'Uint8Array', 'Data provided to MAC generator');
  84. this.update(data);
  85. } else if (!updated_) {
  86. throw new Error(
  87. 'Attempt to generate MAC without either providing data as input or having made previous call to method \'update\'');
  88. }
  89. try {
  90. var macBinaryEncoded = macHandler_.digest().getBytes();
  91. macHandler_.start(policy.symmetric.mac.hashAlgorithm, keyByteBuffer_);
  92. updated_ = false;
  93. return codec.binaryDecode(macBinaryEncoded);
  94. } catch (error) {
  95. throw new Error('MAC could not be generated: ' + error.message);
  96. }
  97. };
  98. /**
  99. * Verfies a that MAC was generated from the provided data. If there were
  100. * any prior calls to the method <code>update</code>, then the provided
  101. * data will be bitwise appended to the data provided to those calls. If no
  102. * data is provided here the MAC will only by verified for the data provided
  103. * to prior calls to the method <code>update</code>. The MAC handler will
  104. * be automatically reinitialized after this method completes. Before using
  105. * this method, the MAC handler must have been initialized with a secret
  106. * key, via the method {@link MacHandler.init}.
  107. *
  108. * @function verify
  109. * @memberof MacHandler
  110. * @param {Uint8Array}
  111. * mac The MAC to be verified.
  112. * @param {Uint8Array}
  113. * [data] The data to check against the MAC. <b>NOTE:</b> Data
  114. * of type <code>string</code> will be UTF-8 encoded.
  115. * @returns <code>True</code> if the MAC was verified, <code>false</code>
  116. * otherwise.
  117. * @throws {Error}
  118. * If the input data validation fails, the MAC handler was not
  119. * initialized or the MAC handler was not updated with any data.
  120. */
  121. this.verify = function(mac, data) {
  122. if (typeof keyByteBuffer_ === 'undefined') {
  123. throw new Error(
  124. 'Could not verify MAC; MAC handler was not initialized with any secret key');
  125. }
  126. validator.checkIsInstanceOf(
  127. mac, Uint8Array, 'Uint8Array', 'MAC provided to MAC verifier');
  128. if (typeof data !== 'undefined') {
  129. if (typeof data === 'string') {
  130. data = codec.utf8Encode(data);
  131. }
  132. validator.checkIsInstanceOf(
  133. data, Uint8Array, 'Uint8Array', 'Data provided to MAC verifier');
  134. } else if (!updated_) {
  135. throw new Error(
  136. 'Attempt to verify MAC without either providing data as input or having made previous call to method \'update\'');
  137. }
  138. var macFromData = this.generate(data);
  139. if (macFromData.length !== mac.length) {
  140. return false;
  141. }
  142. var verified = true;
  143. for (var i = 0; i < macFromData.length; i++) {
  144. if (macFromData[i] !== mac[i]) {
  145. verified = false;
  146. }
  147. }
  148. return verified;
  149. };
  150. /**
  151. * Updates the MAC handler with the provided data. The data will be
  152. * internally bitwise concatenated to any data provided via previous calls
  153. * to this method, after the last call to the method <code>generate</code>
  154. * or the method <code>verify</code>. Before using this method, the MAC
  155. * handler must have been initialized with a secret key, via the method
  156. * {@link MacHandler.init}.
  157. *
  158. * @function update
  159. * @memberof MacHandler
  160. * @param {Uint8Array}
  161. * data The data with which to update the MAC handler. <b>NOTE:</b>
  162. * Data of type <code>string</code> will be UTF-8 encoded.
  163. * @returns {MacHandler} A reference to this object, to facilitate method
  164. * chaining.
  165. * @throws {Error}
  166. * If the input data validation fails or the MAC could not be
  167. * updated.
  168. */
  169. this.update = function(data) {
  170. if (typeof keyByteBuffer_ === 'undefined') {
  171. throw new Error(
  172. 'Could not update MAC; MAC handler was not initialized with any secret key');
  173. }
  174. if (typeof data === 'string') {
  175. data = codec.utf8Encode(data);
  176. }
  177. validator.checkIsInstanceOf(
  178. data, Uint8Array, 'Uint8Array',
  179. 'Data with which to update MAC handler');
  180. try {
  181. macHandler_.update(codec.binaryEncode(data));
  182. updated_ = true;
  183. } catch (error) {
  184. throw new Error('MAC handler could not be updated: ' + error.message);
  185. }
  186. return this;
  187. };
  188. }