feat(Crypto Node): Add support for hash and hmac on binary data (#6359)

This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2023-06-02 09:22:21 +00:00 committed by GitHub
parent 9dfc11037b
commit 406a405dd1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 168 additions and 57 deletions

View file

@ -9,13 +9,17 @@ import type {
INodeTypeDescription,
JsonObject,
} from 'n8n-workflow';
import { deepCopy } from 'n8n-workflow';
import { deepCopy, BINARY_ENCODING } from 'n8n-workflow';
import type { BinaryToTextEncoding } from 'crypto';
import { createHash, createHmac, createSign, getHashes, randomBytes } from 'crypto';
import stream from 'stream';
import { promisify } from 'util';
import { v4 as uuid } from 'uuid';
const pipeline = promisify(stream.pipeline);
export class Crypto implements INodeType {
description: INodeTypeDescription = {
displayName: 'Crypto',
@ -45,15 +49,15 @@ export class Crypto implements INodeType {
},
{
name: 'Hash',
description: 'Hash a text in a specified format',
description: 'Hash a text or file in a specified format',
value: 'hash',
action: 'Hash a text in a specified format',
action: 'Hash a text or file in a specified format',
},
{
name: 'Hmac',
description: 'Hmac a text in a specified format',
description: 'Hmac a text or file in a specified format',
value: 'hmac',
action: 'HMAC a text in a specified format',
action: 'HMAC a text or file in a specified format',
},
{
name: 'Sign',
@ -107,12 +111,40 @@ export class Crypto implements INodeType {
description: 'The hash type to use',
required: true,
},
{
displayName: 'Binary Data',
name: 'binaryData',
type: 'boolean',
default: false,
required: true,
displayOptions: {
show: {
action: ['hash', 'hmac'],
},
},
description: 'Whether the data to hashed should be taken from binary field',
},
{
displayName: 'Binary Property Name',
name: 'binaryPropertyName',
displayOptions: {
show: {
action: ['hash', 'hmac'],
binaryData: [true],
},
},
type: 'string',
default: 'data',
description: 'Name of the binary property which contains the input data',
required: true,
},
{
displayName: 'Value',
name: 'value',
displayOptions: {
show: {
action: ['hash'],
binaryData: [false],
},
},
type: 'string',
@ -204,6 +236,7 @@ export class Crypto implements INodeType {
displayOptions: {
show: {
action: ['hmac'],
binaryData: [false],
},
},
type: 'string',
@ -264,6 +297,7 @@ export class Crypto implements INodeType {
displayOptions: {
show: {
action: ['sign'],
binaryData: [false],
},
},
type: 'string',
@ -430,6 +464,7 @@ export class Crypto implements INodeType {
const dataPropertyName = this.getNodeParameter('dataPropertyName', i);
const value = this.getNodeParameter('value', i, '') as string;
let newValue;
let binaryProcessed = false;
if (action === 'generate') {
const encodingType = this.getNodeParameter('encodingType', i);
@ -449,17 +484,33 @@ export class Crypto implements INodeType {
}
}
}
if (action === 'hash') {
if (action === 'hash' || action === 'hmac') {
const type = this.getNodeParameter('type', i) as string;
const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding;
newValue = createHash(type).update(value).digest(encoding);
}
if (action === 'hmac') {
const type = this.getNodeParameter('type', i) as string;
const secret = this.getNodeParameter('secret', i) as string;
const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding;
newValue = createHmac(type, secret).update(value).digest(encoding);
const hashOrHmac =
action === 'hash'
? createHash(type)
: createHmac(type, this.getNodeParameter('secret', i) as string);
if (this.getNodeParameter('binaryData', i)) {
const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i);
const binaryData = this.helpers.assertBinaryData(i, binaryPropertyName);
if (binaryData.id) {
const binaryStream = this.helpers.getBinaryStream(binaryData.id);
hashOrHmac.setEncoding(encoding);
await pipeline(binaryStream, hashOrHmac);
newValue = hashOrHmac.read();
} else {
newValue = hashOrHmac
.update(Buffer.from(binaryData.data, BINARY_ENCODING))
.digest(encoding);
}
binaryProcessed = true;
} else {
newValue = hashOrHmac.update(value).digest(encoding);
}
}
if (action === 'sign') {
const algorithm = this.getNodeParameter('algorithm', i) as string;
const encoding = this.getNodeParameter('encoding', i) as BinaryToTextEncoding;
@ -489,7 +540,7 @@ export class Crypto implements INodeType {
};
}
if (item.binary !== undefined) {
if (item.binary !== undefined && !binaryProcessed) {
newItem.binary = item.binary;
}

View file

@ -1,5 +1,24 @@
import { testWorkflows, getWorkflowFilenames } from '../../../test/nodes/Helpers';
import fs from 'fs';
import fsPromises from 'fs/promises';
import { Readable } from 'stream';
import {
testWorkflows,
getWorkflowFilenames,
initBinaryDataManager,
} from '../../../test/nodes/Helpers';
const workflows = getWorkflowFilenames(__dirname);
describe('Test Crypto Node', () => testWorkflows(workflows));
describe('Test Crypto Node', () => {
jest.mock('fast-glob', () => async () => ['/test/binary.data']);
jest.mock('fs/promises');
fsPromises.access = async () => {};
jest.mock('fs');
fs.createReadStream = () => Readable.from(Buffer.from('test')) as fs.ReadStream;
beforeEach(async () => {
await initBinaryDataManager();
});
testWorkflows(workflows);
});

View file

@ -1,5 +1,5 @@
{
"name": "review node unit tests",
"name": "Crypto Test",
"nodes": [
{
"parameters": {},
@ -7,41 +7,32 @@
"name": "When clicking \"Execute Workflow\"",
"type": "n8n-nodes-base.manualTrigger",
"typeVersion": 1,
"position": [
-2640,
4140
]
"position": [-480, 460]
},
{
"parameters": {
"value": "=test"
"value": "test"
},
"id": "90831322-8a73-40ac-ae52-84a6504d3d95",
"name": "Crypto Hash into Hex",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
-2120,
4140
]
"position": [360, 660]
},
{
"parameters": {
"value": "=test"
"value": "test"
},
"id": "9836f128-6798-498e-8752-ba447218ce21",
"name": "Crypto Hash into MD5",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
-2120,
3980
]
"position": [360, 500]
},
{
"parameters": {
"action": "sign",
"value": "=test",
"value": "test",
"algorithm": "RSA-MD4",
"encoding": "base64",
"privateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8Qu\nKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEm\no3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2k\nTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp7\n9mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uy\nv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs\n/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00\n-----END RSA PRIVATE KEY-----"
@ -50,15 +41,12 @@
"name": "Crypto Sign data with RSA-MDA4",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
-2120,
3800
]
"position": [80, 860]
},
{
"parameters": {
"action": "hmac",
"value": "=test",
"value": "test",
"secret": "-----BEGIN RSA PRIVATE KEY-----|MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8QuKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEmo3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2kTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp79mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uyv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00-----END RSA PRIVATE KEY-----",
"encoding": "base64"
},
@ -66,10 +54,7 @@
"name": "Crypto Hmac data with MD5",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
-2120,
3620
]
"position": [360, 320]
},
{
"parameters": {
@ -79,10 +64,7 @@
"name": "Crypto Generate UUID",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [
-2120,
4320
]
"position": [-160, 1060]
},
{
"parameters": {
@ -100,10 +82,7 @@
"name": "IF",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
-1900,
4320
]
"position": [80, 1060]
},
{
"parameters": {},
@ -111,10 +90,7 @@
"name": "No Operation, do nothing",
"type": "n8n-nodes-base.noOp",
"typeVersion": 1,
"position": [
-1680,
4300
]
"position": [420, 1040]
},
{
"parameters": {
@ -124,10 +100,40 @@
"name": "Stop and Error",
"type": "n8n-nodes-base.stopAndError",
"typeVersion": 1,
"position": [
-1680,
4500
]
"position": [260, 1180]
},
{
"parameters": {
"fileSelector": "/test/binary.data"
},
"id": "09bbc611-c2ca-4750-94a7-d1bb4fc53a57",
"name": "Read Binary Files",
"type": "n8n-nodes-base.readBinaryFiles",
"typeVersion": 1,
"position": [-160, 40]
},
{
"parameters": {
"binaryData": true
},
"id": "9f23080a-402d-4a82-821c-b74388cc9a26",
"name": "Crypto Hash Binary Data",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [200, -80]
},
{
"parameters": {
"action": "hmac",
"binaryData": true,
"secret": "-----BEGIN RSA PRIVATE KEY-----|MIIBOgIBAAJBAKj34GkxFhD90vcNLYLInFEX6Ppy1tPf9Cnzj4p4WGeKLs1Pt8QuKUpRKfFLfRYC9AIKjbJTWit+CqvjWYzvQwECAwEAAQJAIJLixBy2qpFoS4DSmoEmo3qGy0t6z09AIJtH+5OeRV1be+N4cDYJKffGzDa88vQENZiRm0GRq6a+HPGQMd2kTQIhAKMSvzIBnni7ot/OSie2TmJLY4SwTQAevXysE2RbFDYdAiEBCUEaRQnMnbp79mxDXDf6AU0cN/RPBjb9qSHDcWZHGzUCIG2Es59z8ugGrDY+pxLQnwfotadxd+Uyv/Ow5T0q5gIJAiEAyS4RaI9YG8EWx/2w0T67ZUVAw8eOMB6BIUg0Xcu+3okCIBOs/5OiPgoTdSy7bcF9IGpSE8ZgGKzgYQVZeN97YE00-----END RSA PRIVATE KEY-----",
"encoding": "base64"
},
"id": "43d5ffa2-9c95-4287-b582-b912071a05c1",
"name": "Crypto Hmac Binary Data",
"type": "n8n-nodes-base.crypto",
"typeVersion": 1,
"position": [200, 100]
}
],
"pinData": {
@ -158,6 +164,20 @@
"data": "BBXLTeT2o/R6oy5H69Yh7w=="
}
}
],
"Crypto Hash Binary Data": [
{
"json": {
"data": "098f6bcd4621d373cade4e832627b4f6"
}
}
],
"Crypto Hmac Binary Data": [
{
"json": {
"data": "BBXLTeT2o/R6oy5H69Yh7w=="
}
}
]
},
"connections": {
@ -188,6 +208,11 @@
"node": "Crypto Generate UUID",
"type": "main",
"index": 0
},
{
"node": "Read Binary Files",
"type": "main",
"index": 0
}
]
]
@ -225,6 +250,22 @@
"main": [
[]
]
},
"Read Binary Files": {
"main": [
[
{
"node": "Crypto Hash Binary Data",
"type": "main",
"index": 0
},
{
"node": "Crypto Hmac Binary Data",
"type": "main",
"index": 0
}
]
]
}
},
"active": false,