diff --git a/packages/@n8n/utils/src/format-pem-block.test.ts b/packages/@n8n/utils/src/format-pem-block.test.ts index d873d563b37..e7d48ee1896 100644 --- a/packages/@n8n/utils/src/format-pem-block.test.ts +++ b/packages/@n8n/utils/src/format-pem-block.test.ts @@ -34,6 +34,12 @@ ABC expect(formatPemBlock('')).toBe(''); }); + it('should return non-PEM input unchanged', () => { + expect(formatPemBlock('my secret key')).toBe('my secret key'); + expect(formatPemBlock('pass\\nphrase')).toBe('pass\\nphrase'); + expect(formatPemBlock('-----END CERTIFICATE-----')).toBe('-----END CERTIFICATE-----'); + }); + it('should format compact RSA PRIVATE KEY block', () => { const compactKey = `-----BEGIN RSA PRIVATE KEY-----${'C'.repeat(64)}-----END RSA PRIVATE KEY-----`; diff --git a/packages/@n8n/utils/src/format-pem-block.ts b/packages/@n8n/utils/src/format-pem-block.ts index fee99a7105d..d61732c8603 100644 --- a/packages/@n8n/utils/src/format-pem-block.ts +++ b/packages/@n8n/utils/src/format-pem-block.ts @@ -25,6 +25,9 @@ function formatCompactPem(pem: string, isPublic: boolean): string | undefined { * by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM * chains are returned unchanged. * + * Input that is not a single-line PEM block is returned unchanged, so a plain + * secret (e.g. a key passphrase) passed here by mistake is not corrupted. + * * @param pem - The PEM-encoded block to format. * @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`. * @returns The formatted PEM block. @@ -34,7 +37,9 @@ export function formatPemBlock(pem: string, isPublic = false): string { if (isPublic) { regex = /(PUBLIC KEY)/; } - if (!pem || /\n/.test(pem)) { + // The fallback formatter below would collapse a non-PEM value's whitespace + // into newlines, corrupting plain secrets such as key passphrases. + if (!pem || /\n/.test(pem) || !pem.includes('-----BEGIN ')) { return pem; } const compactPem = formatCompactPem(pem, isPublic); diff --git a/packages/nodes-base/nodes/HttpRequest/GenericFunctions.ts b/packages/nodes-base/nodes/HttpRequest/GenericFunctions.ts index 30926c657bc..c4dbf0dd0ad 100644 --- a/packages/nodes-base/nodes/HttpRequest/GenericFunctions.ts +++ b/packages/nodes-base/nodes/HttpRequest/GenericFunctions.ts @@ -312,8 +312,7 @@ export const setAgentOptions = ( if (sslCertificates.ca) agentOptions.ca = formatPemBlock(sslCertificates.ca); if (sslCertificates.cert) agentOptions.cert = formatPemBlock(sslCertificates.cert); if (sslCertificates.key) agentOptions.key = formatPemBlock(sslCertificates.key); - if (sslCertificates.passphrase) - agentOptions.passphrase = formatPemBlock(sslCertificates.passphrase); + if (sslCertificates.passphrase) agentOptions.passphrase = sslCertificates.passphrase; requestOptions.agentOptions = agentOptions; } }; diff --git a/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts b/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts index e66687d6333..d2642183120 100644 --- a/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts +++ b/packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts @@ -131,6 +131,48 @@ describe('HTTP Node Utils', () => { }, }); }); + + it('should keep a passphrase containing whitespace unchanged', async () => { + const requestOptions: IRequestOptions = { + method: 'GET', + uri: 'https://example.com', + }; + + const sslCertificates = { + passphrase: 'my secret key', + }; + + setAgentOptions(requestOptions, sslCertificates); + + expect(requestOptions).toStrictEqual({ + method: 'GET', + uri: 'https://example.com', + agentOptions: { + passphrase: 'my secret key', + }, + }); + }); + + it('should wrap compact PEM certificates but not the passphrase', async () => { + const requestOptions: IRequestOptions = { + method: 'GET', + uri: 'https://example.com', + }; + + const sslCertificates = { + cert: `-----BEGIN CERTIFICATE-----${'A'.repeat(70)}-----END CERTIFICATE-----`, + key: `-----BEGIN PRIVATE KEY-----${'B'.repeat(70)}-----END PRIVATE KEY-----`, + passphrase: 'my secret key', + }; + + setAgentOptions(requestOptions, sslCertificates); + + expect(requestOptions.agentOptions).toStrictEqual({ + cert: `-----BEGIN CERTIFICATE-----\n${'A'.repeat(64)}\n${'A'.repeat(6)}\n-----END CERTIFICATE-----`, + key: `-----BEGIN PRIVATE KEY-----\n${'B'.repeat(64)}\n${'B'.repeat(6)}\n-----END PRIVATE KEY-----`, + passphrase: 'my secret key', + }); + }); }); describe('sanitizeUiMessage', () => {