crypto.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. /**
  2. * @node crypto
  3. * @name Crypto
  4. * @category crypto
  5. * @version 1.0.0
  6. * @description Hash data, generate HMACs, and perform cryptographic operations for data integrity and message authentication
  7. * @icon lock
  8. */
  9. const configSchema = {
  10. type: 'object',
  11. properties: {
  12. operation: {
  13. type: 'string',
  14. title: 'Operation',
  15. enum: ['hash', 'hmac', 'random', 'encrypt', 'decrypt'],
  16. enumLabels: ['Hash', 'HMAC', 'Random Generation', 'Encrypt (Coming Soon)', 'Decrypt (Coming Soon)'],
  17. default: 'hash',
  18. description: 'Cryptographic operation to perform'
  19. },
  20. algorithm: {
  21. type: 'string',
  22. title: 'Algorithm',
  23. enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512'],
  24. enumLabels: ['MD5', 'SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'],
  25. default: 'sha256',
  26. description: 'Hash algorithm to use',
  27. showWhen: { field: 'operation', value: ['hash', 'hmac'] }
  28. },
  29. outputFormat: {
  30. type: 'string',
  31. title: 'Output Format',
  32. enum: ['hex', 'base64'],
  33. enumLabels: ['Hexadecimal', 'Base64'],
  34. default: 'hex',
  35. description: 'Format of the output hash or HMAC',
  36. showWhen: { field: 'operation', value: ['hash', 'hmac'] }
  37. },
  38. secretKey: {
  39. type: 'string',
  40. title: 'Secret Key',
  41. description: 'Secret key for HMAC generation. Supports {{variable}} interpolation.',
  42. showWhen: { field: 'operation', value: 'hmac' }
  43. },
  44. randomType: {
  45. type: 'string',
  46. title: 'Random Type',
  47. enum: ['uuid', 'hex', 'alphanumeric', 'password'],
  48. enumLabels: ['UUID v4', 'Hex Token', 'Alphanumeric String', 'Secure Password'],
  49. default: 'uuid',
  50. description: 'Type of random value to generate',
  51. showWhen: { field: 'operation', value: 'random' }
  52. },
  53. randomLength: {
  54. type: 'number',
  55. title: 'Length',
  56. default: 32,
  57. minimum: 1,
  58. maximum: 1024,
  59. description: 'Length of the generated value (1-1024). For hex, this is number of bytes (output will be 2x characters).',
  60. showWhen: { field: 'operation', value: 'random' }
  61. },
  62. passwordUppercase: {
  63. type: 'boolean',
  64. title: 'Include Uppercase',
  65. default: true,
  66. description: 'Include uppercase letters (A-Z)',
  67. showWhen: { field: 'randomType', value: 'password' }
  68. },
  69. passwordLowercase: {
  70. type: 'boolean',
  71. title: 'Include Lowercase',
  72. default: true,
  73. description: 'Include lowercase letters (a-z)',
  74. showWhen: { field: 'randomType', value: 'password' }
  75. },
  76. passwordNumbers: {
  77. type: 'boolean',
  78. title: 'Include Numbers',
  79. default: true,
  80. description: 'Include numbers (0-9)',
  81. showWhen: { field: 'randomType', value: 'password' }
  82. },
  83. passwordSymbols: {
  84. type: 'boolean',
  85. title: 'Include Symbols',
  86. default: true,
  87. description: 'Include symbols (!@#$%^&*()_+-=[]{}|;:,.<>?)',
  88. showWhen: { field: 'randomType', value: 'password' }
  89. }
  90. },
  91. required: ['operation']
  92. };
  93. const inputSchema = {
  94. type: 'object',
  95. properties: {
  96. data: {
  97. type: 'any',
  98. description: 'Data to hash or process. Can be a string or will be JSON stringified.'
  99. },
  100. secretKey: {
  101. type: 'string',
  102. description: 'Override secret key for HMAC from input'
  103. }
  104. }
  105. };
  106. const outputSchema = {
  107. type: 'object',
  108. properties: {
  109. result: {
  110. type: 'string',
  111. description: 'The cryptographic operation result'
  112. },
  113. algorithm: {
  114. type: 'string',
  115. description: 'Algorithm used for the operation'
  116. },
  117. operation: {
  118. type: 'string',
  119. description: 'Operation that was performed'
  120. },
  121. format: {
  122. type: 'string',
  123. description: 'Output format (hex or base64)'
  124. },
  125. randomType: {
  126. type: 'string',
  127. description: 'Type of random value generated (uuid, hex, alphanumeric, password)'
  128. },
  129. length: {
  130. type: 'number',
  131. description: 'Length of the generated random value'
  132. },
  133. inputLength: {
  134. type: 'number',
  135. description: 'Length of the input data in bytes'
  136. }
  137. }
  138. };
  139. function interpolate(template, data) {
  140. if (typeof template !== 'string') return template;
  141. return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
  142. const keys = key.trim().split('.');
  143. let value = data;
  144. for (const k of keys) {
  145. if (value && typeof value === 'object' && k in value) {
  146. value = value[k];
  147. } else {
  148. return match;
  149. }
  150. }
  151. return value !== undefined ? String(value) : match;
  152. });
  153. }
  154. function generateRandomString(length, charset) {
  155. const charsetLength = charset.length;
  156. let result = '';
  157. // Generate enough random bytes to select characters
  158. // We need to handle modulo bias by rejecting values that would cause bias
  159. const bytesNeeded = length;
  160. const randomHex = smartbotic.crypto.randomBytes(bytesNeeded * 2);
  161. let pos = 0;
  162. while (result.length < length) {
  163. if (pos >= randomHex.length) {
  164. // Need more random bytes
  165. const moreBytes = smartbotic.crypto.randomBytes((length - result.length) * 2);
  166. pos = 0;
  167. for (let i = 0; i < moreBytes.length && result.length < length; i += 2) {
  168. const byte = parseInt(moreBytes.substr(i, 2), 16);
  169. // Use rejection sampling to avoid modulo bias
  170. const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength;
  171. if (byte < maxUnbiased) {
  172. result += charset[byte % charsetLength];
  173. }
  174. }
  175. } else {
  176. const byte = parseInt(randomHex.substr(pos, 2), 16);
  177. pos += 2;
  178. // Use rejection sampling to avoid modulo bias
  179. const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength;
  180. if (byte < maxUnbiased) {
  181. result += charset[byte % charsetLength];
  182. }
  183. }
  184. }
  185. return result;
  186. }
  187. async function execute(config, input, context) {
  188. const operation = config.operation || 'hash';
  189. if (operation === 'encrypt' || operation === 'decrypt') {
  190. throw new Error('Encrypt and Decrypt operations are not yet implemented. Coming soon.');
  191. }
  192. if (operation === 'random') {
  193. const randomType = config.randomType || 'uuid';
  194. const length = config.randomLength || 32;
  195. if (randomType !== 'uuid' && (length < 1 || length > 1024)) {
  196. throw new Error('Length must be between 1 and 1024');
  197. }
  198. if (randomType === 'uuid') {
  199. const uuid = smartbotic.crypto.randomUUID();
  200. smartbotic.log.info('Generated random UUID v4');
  201. return {
  202. result: uuid,
  203. operation: 'random',
  204. randomType: 'uuid',
  205. length: 36
  206. };
  207. }
  208. if (randomType === 'hex') {
  209. const randomHex = smartbotic.crypto.randomBytes(length);
  210. smartbotic.log.info('Generated ' + length + '-byte hex token (' + randomHex.length + ' characters)');
  211. return {
  212. result: randomHex,
  213. operation: 'random',
  214. randomType: 'hex',
  215. length: randomHex.length
  216. };
  217. }
  218. if (randomType === 'alphanumeric') {
  219. const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
  220. const result = generateRandomString(length, charset);
  221. smartbotic.log.info('Generated ' + length + '-character alphanumeric string');
  222. return {
  223. result: result,
  224. operation: 'random',
  225. randomType: 'alphanumeric',
  226. length: length
  227. };
  228. }
  229. if (randomType === 'password') {
  230. const includeUppercase = config.passwordUppercase !== false;
  231. const includeLowercase = config.passwordLowercase !== false;
  232. const includeNumbers = config.passwordNumbers !== false;
  233. const includeSymbols = config.passwordSymbols !== false;
  234. let charset = '';
  235. if (includeUppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  236. if (includeLowercase) charset += 'abcdefghijklmnopqrstuvwxyz';
  237. if (includeNumbers) charset += '0123456789';
  238. if (includeSymbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?';
  239. if (charset.length === 0) {
  240. throw new Error('At least one character set must be enabled for password generation');
  241. }
  242. const result = generateRandomString(length, charset);
  243. smartbotic.log.info('Generated ' + length + '-character secure password');
  244. return {
  245. result: result,
  246. operation: 'random',
  247. randomType: 'password',
  248. length: length
  249. };
  250. }
  251. throw new Error('Unknown random type: ' + randomType);
  252. }
  253. const inputData = input.data !== undefined ? input.data : input;
  254. let dataStr;
  255. if (typeof inputData === 'string') {
  256. dataStr = inputData;
  257. } else if (inputData === null || inputData === undefined) {
  258. throw new Error('No data provided for ' + operation + ' operation');
  259. } else {
  260. dataStr = JSON.stringify(inputData);
  261. }
  262. const algorithm = config.algorithm || 'sha256';
  263. const outputFormat = config.outputFormat || 'hex';
  264. if (operation === 'hash') {
  265. smartbotic.log.info('Hashing ' + dataStr.length + ' bytes with ' + algorithm.toUpperCase());
  266. const result = smartbotic.crypto.hash(dataStr, algorithm, outputFormat);
  267. return {
  268. result: result,
  269. algorithm: algorithm,
  270. operation: 'hash',
  271. format: outputFormat,
  272. inputLength: dataStr.length
  273. };
  274. }
  275. if (operation === 'hmac') {
  276. let secretKey = input.secretKey || config.secretKey;
  277. if (secretKey && typeof secretKey === 'string') {
  278. secretKey = interpolate(secretKey, input);
  279. }
  280. if (!secretKey) {
  281. throw new Error('Secret key is required for HMAC operation');
  282. }
  283. smartbotic.log.info('Generating HMAC-' + algorithm.toUpperCase() + ' for ' + dataStr.length + ' bytes');
  284. const result = smartbotic.crypto.hmac(dataStr, secretKey, algorithm, outputFormat);
  285. return {
  286. result: result,
  287. algorithm: algorithm,
  288. operation: 'hmac',
  289. format: outputFormat,
  290. inputLength: dataStr.length
  291. };
  292. }
  293. throw new Error('Unknown operation: ' + operation);
  294. }
  295. module.exports = { configSchema, inputSchema, outputSchema, execute };