hapheus / n8n-nodes-pgp

n8n-nodes-pgp enables seamless integration of PGP encryption functionalities into n8n workflows. Create keys, encrypt, decrypt, sign, and verify messages effortlessly. Perfect for secure data handling in automated workflows.
MIT License
2 stars 0 forks source link

Binary Mode & Compression in single pass #3

Open erictmnz opened 1 day ago

erictmnz commented 1 day ago

Hi 👋

This is a great community node, thanks for creating it!

Could you kindly add the option to compress as part of the single pass encrypt & sign / verify & decrypt?

We were just about to start our own node to do SFTP file encryption / decryption.

Might look something like the (untested) update below.

Happy to help testing etc, Eric


import {
    IExecuteFunctions,
    INodeExecutionData,
    INodeType,
    INodeTypeDescription,
    NodeOperationError,
} from 'n8n-workflow';

export class PgpNode implements INodeType {
    description: INodeTypeDescription = {
        displayName: 'PGP',
        name: 'pgpNode',
        icon: 'file:key.svg',
        group: ['transform'],
        version: 1,
        description: 'Encrypt, decrypt, sign, and verify data using PGP',
        defaults: {
            name: 'PGP',
        },
        inputs: ['main'],
        outputs: ['main'],
        credentials: [
            {
                name: 'pgpCredentialsApi',
                required: true,
            },
        ],
        properties: [
            {
                displayName: 'Operation',
                name: 'operation',
                type: 'options',
                noDataExpression: true,
                default: 'encrypt',
                required: true,
                options: [
                    {
                        name: 'Encrypt',
                        value: 'encrypt',
                    },
                    {
                        name: 'Encrypt and Sign',
                        value: 'encrypt-and-sign',
                    },
                    {
                        name: 'Decrypt',
                        value: 'decrypt',
                    },
                    {
                        name: 'Verify and Decrypt',
                        value: 'verify-and-decrypt',
                    },
                    {
                        name: 'Sign',
                        value: 'sign',
                    },
                    {
                        name: 'Verify',
                        value: 'verify',
                    },
                ],
            },
            {
                displayName: 'Data Type',
                name: 'dataType',
                type: 'options',
                options: [
                    {
                        name: 'Text',
                        value: 'text',
                    },
                    {
                        name: 'Binary',
                        value: 'binary',
                    },
                ],
                default: 'text',
                description: 'Type of data to process',
            },
            {
                displayName: 'Message',
                name: 'message',
                type: 'string',
                default: '',
                placeholder: 'Message',
                description: 'The message text',
                displayOptions: {
                    show: {
                        dataType: ['text'],
                    },
                },
            },
            {
                displayName: 'Binary Property',
                name: 'binaryProperty',
                type: 'string',
                default: 'data',
                placeholder: '',
                description: 'Name of the binary property containing the data to process',
                displayOptions: {
                    show: {
                        dataType: ['binary'],
                    },
                },
            },
            {
                displayName: 'Compression Algorithm',
                name: 'compression',
                type: 'options',
                options: [
                    {
                        name: 'ZIP',
                        value: 'zip',
                    },
                    {
                        name: 'ZLIB',
                        value: 'zlib',
                    },
                    {
                        name: 'BZip2',
                        value: 'bzip2',
                    },
                    {
                        name: 'Uncompressed',
                        value: 'uncompressed',
                    },
                ],
                default: 'zip',
                description: 'Compression algorithm to use',
            },
            {
                displayName: 'Signature',
                name: 'signature',
                type: 'string',
                default: '',
                placeholder: '-----BEGIN PGP SIGNATURE-----',
                displayOptions: {
                    show: {
                        operation: ['verify', 'verify-and-decrypt'],
                        dataType: ['text'],
                    },
                },
            },
            {
                displayName: 'Signature Binary Property',
                name: 'signatureBinaryProperty',
                type: 'string',
                default: 'signature',
                placeholder: '',
                description: 'Name of the binary property containing the signature',
                displayOptions: {
                    show: {
                        operation: ['verify', 'verify-and-decrypt'],
                        dataType: ['binary'],
                    },
                },
            },
        ],
    };

    async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
        const items = this.getInputData();

        const openpgp = require('openpgp');

        let item: INodeExecutionData;

        // Retrieve credentials
        let credentials;
        let priKey;
        let pubKey;
        try {
            credentials = await this.getCredentials('pgpCredentialsApi');
            if (credentials.passphrase) {
                priKey = await openpgp.decryptKey({
                    privateKey: await openpgp.readPrivateKey({
                        armoredKey: credentials.private_key.trim(),
                    }),
                    passphrase: credentials.passphrase,
                });
            } else {
                priKey = await openpgp.readPrivateKey({
                    armoredKey: credentials.private_key.trim(),
                });
            }

            pubKey = await openpgp.readKey({ armoredKey: credentials.public_key.trim() });
        } catch (e) {
            throw new NodeOperationError(this.getNode(), 'Failed to load PGP credentials.');
        }

        for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
            try {
                const operation = this.getNodeParameter('operation', itemIndex) as string;
                const dataType = this.getNodeParameter('dataType', itemIndex) as string;
                const compression = this.getNodeParameter('compression', itemIndex) as string;

                item = items[itemIndex];

                let message;
                let options: any = {
                    config: {
                        preferredCompressionAlgorithm: openpgp.enums.compression[compression],
                    },
                    format: 'binary', // Use binary format
                };

                if (dataType === 'text') {
                    const messageText = this.getNodeParameter('message', itemIndex) as string;
                    message = await openpgp.createMessage({ text: messageText });
                } else {
                    const binaryProperty = this.getNodeParameter('binaryProperty', itemIndex) as string;
                    if (!item.binary || !item.binary[binaryProperty]) {
                        throw new NodeOperationError(
                            this.getNode(),
                            `Binary data property "${binaryProperty}" does not exist on item index ${itemIndex}`,
                            { itemIndex },
                        );
                    }
                    const binaryData = await this.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
                    message = await openpgp.createMessage({ binary: binaryData });
                }

                switch (operation) {
                    case 'encrypt':
                        const encrypted = await openpgp.encrypt({
                            message,
                            encryptionKeys: pubKey,
                            ...options,
                        });
                        if (dataType === 'text') {
                            item.json = {
                                encrypted: encrypted, // Encrypted data in binary format
                            };
                        } else {
                            item.binary = item.binary || {};
                            item.binary['data'] = await this.helpers.prepareBinaryData(encrypted);
                        }
                        break;

                    case 'encrypt-and-sign':
                        const encryptedSigned = await openpgp.encrypt({
                            message,
                            encryptionKeys: pubKey,
                            signingKeys: priKey,
                            ...options,
                        });
                        if (dataType === 'text') {
                            item.json = {
                                encrypted: encryptedSigned, // Encrypted data in binary format
                            };
                        } else {
                            item.binary = item.binary || {};
                            item.binary['data'] = await this.helpers.prepareBinaryData(encryptedSigned);
                        }
                        break;

                    case 'decrypt':
                        if (dataType === 'text') {
                            const decryptedMessage = await openpgp.readMessage({
                                armoredMessage: this.getNodeParameter('message', itemIndex) as string,
                            });
                            const decrypted = await openpgp.decrypt({
                                message: decryptedMessage,
                                decryptionKeys: priKey,
                                ...options,
                            });
                            item.json = {
                                decrypted: decrypted.data,
                            };
                        } else {
                            const binaryProperty = this.getNodeParameter('binaryProperty', itemIndex) as string;
                            const encryptedData = await this.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
                            const encryptedMessage = await openpgp.readMessage({
                                binaryMessage: encryptedData,
                            });
                            const decrypted = await openpgp.decrypt({
                                message: encryptedMessage,
                                decryptionKeys: priKey,
                                ...options,
                            });
                            item.binary = item.binary || {};
                            item.binary['data'] = await this.helpers.prepareBinaryData(decrypted.data);
                        }
                        break;

                    case 'verify-and-decrypt':
                        if (dataType === 'text') {
                            const decryptedMessage = await openpgp.readMessage({
                                armoredMessage: this.getNodeParameter('message', itemIndex) as string,
                            });
                            const decrypted = await openpgp.decrypt({
                                message: decryptedMessage,
                                decryptionKeys: priKey,
                                verificationKeys: pubKey,
                                expectSigned: true,
                                ...options,
                            });
                            const verificationResult = decrypted.signatures[0];
                            let isVerified = false;
                            try {
                                await verificationResult.verified;
                                isVerified = true;
                            } catch (error) {
                                isVerified = false;
                            }
                            item.json = {
                                decrypted: decrypted.data,
                                verified: isVerified,
                            };
                        } else {
                            const binaryProperty = this.getNodeParameter('binaryProperty', itemIndex) as string;
                            const encryptedData = await this.helpers.getBinaryDataBuffer(itemIndex, binaryProperty);
                            const encryptedMessage = await openpgp.readMessage({
                                binaryMessage: encryptedData,
                            });
                            const decrypted = await openpgp.decrypt({
                                message: encryptedMessage,
                                decryptionKeys: priKey,
                                verificationKeys: pubKey,
                                expectSigned: true,
                                ...options,
                            });
                            const verificationResult = decrypted.signatures[0];
                            let isVerified = false;
                            try {
                                await verificationResult.verified;
                                isVerified = true;
                            } catch (error) {
                                isVerified = false;
                            }
                            item.binary = item.binary || {};
                            item.binary['data'] = await this.helpers.prepareBinaryData(decrypted.data);
                            item.json = {
                                verified: isVerified,
                            };
                        }
                        break;

                    case 'sign':
                        const signed = await openpgp.sign({
                            message,
                            signingKeys: priKey,
                            detached: true,
                            ...options,
                        });
                        if (dataType === 'text') {
                            item.json = {
                                signature: signed,
                            };
                        } else {
                            item.binary = item.binary || {};
                            item.binary['signature'] = await this.helpers.prepareBinaryData(signed);
                        }
                        break;

                    case 'verify':
                        if (dataType === 'text') {
                            const signature = this.getNodeParameter('signature', itemIndex) as string;
                            const verificationResult = await openpgp.verify({
                                message,
                                signature: await openpgp.readSignature({ armoredSignature: signature }),
                                verificationKeys: pubKey,
                                ...options,
                            });
                            const { verified } = verificationResult.signatures[0];
                            let isVerified = false;
                            try {
                                await verified;
                                isVerified = true;
                            } catch (error) {
                                isVerified = false;
                            }
                            item.json = {
                                verified: isVerified,
                            };
                        } else {
                            const signatureBinaryProperty = this.getNodeParameter('signatureBinaryProperty', itemIndex) as string;
                            const signatureData = await this.helpers.getBinaryDataBuffer(itemIndex, signatureBinaryProperty);
                            const signature = await openpgp.readSignature({
                                binarySignature: signatureData,
                            });
                            const verificationResult = await openpgp.verify({
                                message,
                                signature,
                                verificationKeys: pubKey,
                                ...options,
                            });
                            const { verified } = verificationResult.signatures[0];
                            let isVerified = false;
                            try {
                                await verified;
                                isVerified = true;
                            } catch (error) {
                                isVerified = false;
                            }
                            item.json = {
                                verified: isVerified,
                            };
                        }
                        break;

                    default:
                        throw new NodeOperationError(this.getNode(), `Unsupported operation: ${operation}`, { itemIndex });
                }
            } catch (error) {
                if (this.continueOnFail()) {
                    items.push({ json: this.getInputData(itemIndex)[0].json, error, pairedItem: itemIndex });
                } else {
                    throw new NodeOperationError(this.getNode(), error as Error, {
                        itemIndex,
                    });
                }
            }
        }

        return this.prepareOutputData(items);
    }
}
hapheus commented 10 hours ago

Hi Eric 👋

Thanks for the feedback and kind words! I’ll review your request this week. Just to confirm:

You’d like a "return as binary" option. If enabled, it should allow choosing a compression algorithm. Sounds like a great addition! I’ll keep you updated and would appreciate your help testing once it’s ready.

Cheers,

erictmnz commented 8 hours ago

Hi Franz,

Awesome, that will make it the most complete encryption node in the n8n community.

Yes I basically want to input and output a file that gets compressed, encrypted and signed in a single pass and vice versa (verified, unencrypted and decompressed).

Hence a few more selections on the node :)

Cheers Eric

Eric Troebner Chief Technology Officer +64 9 557 0650| +64 21 1991 850 tmnz.co.nz Level 5, Citigroup Centre, 23 Customs St East, Auckland 1010 ​PO Box 105 435, Auckland 1143 IMPORTANT: The information contained in this message may be legally privileged or confidential. If you appear to have received this in error or are not the named recipient, you are notified that any use, ​copying or distribution of this email message or its attachments is prohibited. Please notify the sender immediately. Thank you. Any views expressed or implied within the email or its attachments are not ​necessarily the views of Tax Management NZ. Any issues can be addressed to the author or @.*** ​ Please consider the environment before printing this email.


From: Franz Haberfellner @.> Sent: Monday, November 25, 2024 6:26:38 AM To: hapheus/n8n-nodes-pgp @.> Cc: Eric Troebner @.>; Author @.> Subject: Re: [hapheus/n8n-nodes-pgp] Binary Mode & Compression in single pass (Issue #3)

Hi Eric 👋

Thanks for the feedback and kind words! I’ll review your request this week. Just to confirm:

You’d like a "return as binary" option. If enabled, it should allow choosing a compression algorithm. Sounds like a great addition! I’ll keep you updated and would appreciate your help testing once it’s ready.

Cheers,

— Reply to this email directly, view it on GitHubhttps://github.com/hapheus/n8n-nodes-pgp/issues/3#issuecomment-2496121282, or unsubscribehttps://github.com/notifications/unsubscribe-auth/BKXD3E7W4QSIJVBZJRVTIED2CID45AVCNFSM6AAAAABSKXWR4GVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDIOJWGEZDCMRYGI. You are receiving this because you authored the thread.Message ID: @.***>