PageRenderTime 58ms CodeModel.GetById 34ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/seif/rng.js

https://gitlab.com/CORP-RESELLER/seif-protocol
JavaScript | 365 lines | 147 code | 77 blank | 141 comment | 13 complexity | d5fc2c121294b6d199340a71e638b355 MD5 | raw file
  1. /** @file rng.js
  2. * @brief File containing the implementation of the isaac RNG service
  3. *
  4. * @author Aashish Sheshadri
  5. * @author Rohit Harchandani
  6. *
  7. * The MIT License (MIT)
  8. *
  9. * Copyright (c) 2016 PayPal
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to
  13. * deal in the Software without restriction, including without limitation the
  14. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  15. * sell copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in
  19. * all copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  26. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
  27. * DEALINGS IN THE SOFTWARE.
  28. */
  29. /*jslint node: true */
  30. function startRNGService() {
  31. 'use strict';
  32. const seifnode = require("seifnode"); // Enable Seifnode for SHA3-256
  33. const net = require("net"); // Enable network services.
  34. const hasher = seifnode.SEIFSHA3(); // Enable SHA3-256 hashing - Seifnode.
  35. const listenPort = 9993; // Port to listen on for incoming connections.
  36. let generatorMap = new Map(); // Map to store/access random generators.
  37. let numConnections = 0; // Number of active connections.
  38. // On process abrupt exit, call exit with error code 1.
  39. process.on('SIGINT', function () {
  40. process.exit(1);
  41. });
  42. // On process exit, destroy all generators initiated so as to update state.
  43. process.on("exit", function () {
  44. generatorMap.forEach(function (generatorObject) {
  45. generatorObject.generator.destroy();
  46. });
  47. });
  48. /**
  49. * @brief Initializes a random generator and informs listeners when ready.
  50. *
  51. * @param generatorObject is a JSON with generator credentials, listeners.
  52. *
  53. */
  54. function startRNG(generatorObject) {
  55. // Get seifnode random generator.
  56. let generator = generatorObject.generator;
  57. /* Check if generator is seeded, i.e, state file is present
  58. * and decryption is successful. If not, seed a new generator.
  59. */
  60. generator.isInitialized(
  61. generatorObject.pwd,
  62. generatorObject.fileName,
  63. function (result) {
  64. /* If not seeded, call initialize to gather entropy
  65. * and seed the random generator.
  66. */
  67. if (result.code !== 0) {
  68. generatorObject.socketArray.forEach(function (item) {
  69. item.socket.end();
  70. item.socket.destroy();
  71. });
  72. generatorMap.delete(generatorObject.id);
  73. return;
  74. }
  75. // Loaded state successfully, set generator as initialized.
  76. generatorObject.isInitialized = true;
  77. /* Inform listeners that the generator is ready to process
  78. * requests.
  79. */
  80. generatorObject.socketArray.forEach(function (item) {
  81. item.socket.write(JSON.stringify(item.response) + "\r\n");
  82. });
  83. // Delete all informed listeners.
  84. delete generatorObject.socketArray;
  85. }
  86. );
  87. }
  88. /**
  89. * @brief Processes a request recived and responds as appropriate.
  90. *
  91. * @param request is a JSON recieved from the random service.
  92. * @param response is a JSON to be sent after being populated.
  93. * @param socket is a reference to the connection socket.
  94. *
  95. * @return boolean true if response should be written on the socket.
  96. *
  97. */
  98. function processData(request, response, socket) {
  99. // Get operation from request.
  100. let op = request.op;
  101. // Define variables to handle request.
  102. let generatorObject, generator, id;
  103. // Check if request is for random bytes.
  104. if (op === "getBytes") {
  105. // Get request byte count.
  106. let numBytes = request.numBytes;
  107. // Get generator id assigned by this service.
  108. id = request.id;
  109. // Get the associated generator.
  110. generatorObject = generatorMap.get(id);
  111. // Unknown id.
  112. if (generatorObject === undefined) {
  113. response.error = true;
  114. return true;
  115. }
  116. // Get the random generator from the generator object.
  117. generator = generatorObject.generator;
  118. /* Check if the request if for an array of random bytes. Call the
  119. * random generator accordingly.
  120. */
  121. if (Array.isArray(numBytes) === true) {
  122. /* Response will contain an array with random bytes,
  123. * each of length as determined by request array numBytes.
  124. */
  125. response.random = [];
  126. numBytes.forEach(function (num) {
  127. response.random.push(
  128. generator.getBytes(num).toString("hex")
  129. );
  130. });
  131. } else {
  132. // Response is a random string of length numBytes.
  133. response.random = generator.getBytes(numBytes).toString("hex");
  134. }
  135. // Save the RNG state if requested to do so.
  136. if (request.saveState === true) {
  137. generator.saveState();
  138. }
  139. // Write the response.
  140. return true;
  141. }
  142. // Check if request is to initialize a random generator.
  143. if (op === "initialize") {
  144. // Parse filename from request.
  145. let fileName = request.fileName;
  146. // Parse credentials of the random generator.
  147. let key = {
  148. pwd: request.pwd,
  149. fileName
  150. };
  151. /* SHA3-256 hash of the key JSON will be used as the key for
  152. * generator look up.
  153. */
  154. let idBuffer = hasher.hash(JSON.stringify(key));
  155. // Key for lookup/storing the generator.
  156. id = idBuffer.toString("hex");
  157. // Lookup for generator object, if initialized previously.
  158. generatorObject = generatorMap.get(id);
  159. // Assign id as the lookup key.
  160. response.id = id;
  161. // If a valid generator was not found, spawn and initialize one.
  162. if (generatorObject === undefined) {
  163. // Decryption key for random generator state.
  164. let pwd = new Buffer(request.pwd, "hex");
  165. // Instantiate a random generator from seifnode.
  166. generator = new seifnode.RNG();
  167. /* Populate generator object with seifnode random generator
  168. * credentials, initialization state and listeners waiting for
  169. * initialization.
  170. */
  171. generatorObject = {
  172. id,
  173. pwd,
  174. fileName,
  175. generator,
  176. isInitialized: false,
  177. socketArray: [{response, socket}]
  178. };
  179. // Store generator.
  180. generatorMap.set(id, generatorObject);
  181. // Initialize and start the generator.
  182. startRNG(generatorObject);
  183. /* Response will be written after initialization. Response
  184. * object is stored in the generator object along with the
  185. * corresponding connection.
  186. */
  187. return false;
  188. }
  189. /* If generator is not yet initialized, add connection to its set
  190. * of listeners.
  191. */
  192. if (generatorObject.isInitialized === false) {
  193. generatorObject.socketArray.push({response, socket});
  194. /* Response will be written after initialization. Response
  195. * object is stored in the generator object along with the
  196. * corresponding connection.
  197. */
  198. return false;
  199. }
  200. /* Generator is initialized and ready to process requests. Write
  201. * to the requestor the look up id for correspondence in the future.
  202. */
  203. return true;
  204. }
  205. // Unknown request; ignored.
  206. return false;
  207. }
  208. // Create server to handle connections from clients requesting random.
  209. let service = net.createServer(function (socket) {
  210. // Increment numConnections to account for this connection.
  211. numConnections = numConnections + 1;
  212. // Set utf8 as the default encoding of the socket.
  213. socket.setEncoding("utf8");
  214. // Buffer data from the socket.
  215. let chunk = "";
  216. /* On recieving data from the random service, buffer data and parse
  217. * when possible.
  218. */
  219. socket.on('data', function (data) {
  220. /**
  221. * @brief Process a JSON recieved on the connection.
  222. *
  223. * @param request is a JSON recieved from the requestor.
  224. *
  225. */
  226. function processRequest(request) {
  227. let response = {}; // Response object.
  228. response.header = request.header; // Add request header.
  229. // Attempt to respond to the request.
  230. try {
  231. // invoke processData to handle response.
  232. let result = processData(request, response, socket);
  233. if (result === false) {
  234. return;
  235. }
  236. } catch (ex) {
  237. console.log(ex);
  238. response = {};
  239. }
  240. // Write response JSON and add "\r\n" as a delimiter.
  241. socket.write(JSON.stringify(response) + "\r\n");
  242. }
  243. chunk += data; // Buffer data on the socket.
  244. // Search for "\r\n" to identify parsable chunk of data.
  245. let nIndex = chunk.indexOf("\r\n");
  246. // While a valid index exists parse such chunks of data.
  247. while (nIndex > -1) {
  248. // Extract parsable chunk.
  249. let currentData = chunk.substring(0, nIndex);
  250. // Parse chunk.
  251. currentData = JSON.parse(currentData);
  252. // Process chunk.
  253. processRequest(currentData);
  254. // Advance over chunk separator.
  255. chunk = chunk.substring(nIndex + 2);
  256. // Search for "\r\n" to identify parsable chunk of data.
  257. nIndex = chunk.indexOf("\r\n");
  258. }
  259. });
  260. // On connection close event, decrement number of connections by one.
  261. socket.on("close", function () {
  262. numConnections = numConnections - 1;
  263. // If service has no active connections, quit service.
  264. if (numConnections === 0) {
  265. process.exit(0);
  266. }
  267. });
  268. });
  269. /* On error in an attempt to listen on listenPort while it is already in
  270. * use, quit service, since a random service is already active of the port
  271. * is in use by some other service.
  272. */
  273. service.on('error', function (error) {
  274. if (error.code === 'EADDRINUSE') {
  275. process.exit(1);
  276. }
  277. });
  278. // Attempt to listen on listenPort for connections to random service.
  279. service.listen(listenPort, function () {
  280. // On listening event inform parent process on success.
  281. let okResponse = {
  282. status: "OK"
  283. };
  284. process.send(okResponse);
  285. });
  286. }
  287. // Attempt to start random service.
  288. startRNGService();