/** * @node crypto * @name Crypto * @category crypto * @version 1.0.0 * @description Hash data, generate HMACs, and perform cryptographic operations for data integrity and message authentication * @icon lock */ const configSchema = { type: 'object', properties: { operation: { type: 'string', title: 'Operation', enum: ['hash', 'hmac', 'random', 'encrypt', 'decrypt'], enumLabels: ['Hash', 'HMAC', 'Random Generation', 'Encrypt (Coming Soon)', 'Decrypt (Coming Soon)'], default: 'hash', description: 'Cryptographic operation to perform' }, algorithm: { type: 'string', title: 'Algorithm', enum: ['md5', 'sha1', 'sha256', 'sha384', 'sha512'], enumLabels: ['MD5', 'SHA-1', 'SHA-256', 'SHA-384', 'SHA-512'], default: 'sha256', description: 'Hash algorithm to use', showWhen: { field: 'operation', value: ['hash', 'hmac'] } }, outputFormat: { type: 'string', title: 'Output Format', enum: ['hex', 'base64'], enumLabels: ['Hexadecimal', 'Base64'], default: 'hex', description: 'Format of the output hash or HMAC', showWhen: { field: 'operation', value: ['hash', 'hmac'] } }, secretKey: { type: 'string', title: 'Secret Key', description: 'Secret key for HMAC generation. Supports {{variable}} interpolation.', showWhen: { field: 'operation', value: 'hmac' } }, randomType: { type: 'string', title: 'Random Type', enum: ['uuid', 'hex', 'alphanumeric', 'password'], enumLabels: ['UUID v4', 'Hex Token', 'Alphanumeric String', 'Secure Password'], default: 'uuid', description: 'Type of random value to generate', showWhen: { field: 'operation', value: 'random' } }, randomLength: { type: 'number', title: 'Length', default: 32, minimum: 1, maximum: 1024, description: 'Length of the generated value (1-1024). For hex, this is number of bytes (output will be 2x characters).', showWhen: { field: 'operation', value: 'random' } }, passwordUppercase: { type: 'boolean', title: 'Include Uppercase', default: true, description: 'Include uppercase letters (A-Z)', showWhen: { field: 'randomType', value: 'password' } }, passwordLowercase: { type: 'boolean', title: 'Include Lowercase', default: true, description: 'Include lowercase letters (a-z)', showWhen: { field: 'randomType', value: 'password' } }, passwordNumbers: { type: 'boolean', title: 'Include Numbers', default: true, description: 'Include numbers (0-9)', showWhen: { field: 'randomType', value: 'password' } }, passwordSymbols: { type: 'boolean', title: 'Include Symbols', default: true, description: 'Include symbols (!@#$%^&*()_+-=[]{}|;:,.<>?)', showWhen: { field: 'randomType', value: 'password' } } }, required: ['operation'] }; const inputSchema = { type: 'object', properties: { data: { type: 'any', description: 'Data to hash or process. Can be a string or will be JSON stringified.' }, secretKey: { type: 'string', description: 'Override secret key for HMAC from input' } } }; const outputSchema = { type: 'object', properties: { result: { type: 'string', description: 'The cryptographic operation result' }, algorithm: { type: 'string', description: 'Algorithm used for the operation' }, operation: { type: 'string', description: 'Operation that was performed' }, format: { type: 'string', description: 'Output format (hex or base64)' }, randomType: { type: 'string', description: 'Type of random value generated (uuid, hex, alphanumeric, password)' }, length: { type: 'number', description: 'Length of the generated random value' }, inputLength: { type: 'number', description: 'Length of the input data in bytes' } } }; function interpolate(template, data) { if (typeof template !== 'string') return template; return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => { const keys = key.trim().split('.'); let value = data; for (const k of keys) { if (value && typeof value === 'object' && k in value) { value = value[k]; } else { return match; } } return value !== undefined ? String(value) : match; }); } function generateRandomString(length, charset) { const charsetLength = charset.length; let result = ''; // Generate enough random bytes to select characters // We need to handle modulo bias by rejecting values that would cause bias const bytesNeeded = length; const randomHex = smartbotic.crypto.randomBytes(bytesNeeded * 2); let pos = 0; while (result.length < length) { if (pos >= randomHex.length) { // Need more random bytes const moreBytes = smartbotic.crypto.randomBytes((length - result.length) * 2); pos = 0; for (let i = 0; i < moreBytes.length && result.length < length; i += 2) { const byte = parseInt(moreBytes.substr(i, 2), 16); // Use rejection sampling to avoid modulo bias const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength; if (byte < maxUnbiased) { result += charset[byte % charsetLength]; } } } else { const byte = parseInt(randomHex.substr(pos, 2), 16); pos += 2; // Use rejection sampling to avoid modulo bias const maxUnbiased = Math.floor(256 / charsetLength) * charsetLength; if (byte < maxUnbiased) { result += charset[byte % charsetLength]; } } } return result; } async function execute(config, input, context) { const operation = config.operation || 'hash'; if (operation === 'encrypt' || operation === 'decrypt') { throw new Error('Encrypt and Decrypt operations are not yet implemented. Coming soon.'); } if (operation === 'random') { const randomType = config.randomType || 'uuid'; const length = config.randomLength || 32; if (randomType !== 'uuid' && (length < 1 || length > 1024)) { throw new Error('Length must be between 1 and 1024'); } if (randomType === 'uuid') { const uuid = smartbotic.crypto.randomUUID(); smartbotic.log.info('Generated random UUID v4'); return { result: uuid, operation: 'random', randomType: 'uuid', length: 36 }; } if (randomType === 'hex') { const randomHex = smartbotic.crypto.randomBytes(length); smartbotic.log.info('Generated ' + length + '-byte hex token (' + randomHex.length + ' characters)'); return { result: randomHex, operation: 'random', randomType: 'hex', length: randomHex.length }; } if (randomType === 'alphanumeric') { const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const result = generateRandomString(length, charset); smartbotic.log.info('Generated ' + length + '-character alphanumeric string'); return { result: result, operation: 'random', randomType: 'alphanumeric', length: length }; } if (randomType === 'password') { const includeUppercase = config.passwordUppercase !== false; const includeLowercase = config.passwordLowercase !== false; const includeNumbers = config.passwordNumbers !== false; const includeSymbols = config.passwordSymbols !== false; let charset = ''; if (includeUppercase) charset += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; if (includeLowercase) charset += 'abcdefghijklmnopqrstuvwxyz'; if (includeNumbers) charset += '0123456789'; if (includeSymbols) charset += '!@#$%^&*()_+-=[]{}|;:,.<>?'; if (charset.length === 0) { throw new Error('At least one character set must be enabled for password generation'); } const result = generateRandomString(length, charset); smartbotic.log.info('Generated ' + length + '-character secure password'); return { result: result, operation: 'random', randomType: 'password', length: length }; } throw new Error('Unknown random type: ' + randomType); } const inputData = input.data !== undefined ? input.data : input; let dataStr; if (typeof inputData === 'string') { dataStr = inputData; } else if (inputData === null || inputData === undefined) { throw new Error('No data provided for ' + operation + ' operation'); } else { dataStr = JSON.stringify(inputData); } const algorithm = config.algorithm || 'sha256'; const outputFormat = config.outputFormat || 'hex'; if (operation === 'hash') { smartbotic.log.info('Hashing ' + dataStr.length + ' bytes with ' + algorithm.toUpperCase()); const result = smartbotic.crypto.hash(dataStr, algorithm, outputFormat); return { result: result, algorithm: algorithm, operation: 'hash', format: outputFormat, inputLength: dataStr.length }; } if (operation === 'hmac') { let secretKey = input.secretKey || config.secretKey; if (secretKey && typeof secretKey === 'string') { secretKey = interpolate(secretKey, input); } if (!secretKey) { throw new Error('Secret key is required for HMAC operation'); } smartbotic.log.info('Generating HMAC-' + algorithm.toUpperCase() + ' for ' + dataStr.length + ' bytes'); const result = smartbotic.crypto.hmac(dataStr, secretKey, algorithm, outputFormat); return { result: result, algorithm: algorithm, operation: 'hmac', format: outputFormat, inputLength: dataStr.length }; } throw new Error('Unknown operation: ' + operation); } module.exports = { configSchema, inputSchema, outputSchema, execute };