Skip to content

Commit bcd240f

Browse files
fix(HTTP Request Node): Keep SSL passphrases with whitespace intact (#37020)
1 parent e2afe16 commit bcd240f

4 files changed

Lines changed: 55 additions & 3 deletions

File tree

packages/@n8n/utils/src/format-pem-block.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ ABC
3434
expect(formatPemBlock('')).toBe('');
3535
});
3636

37+
it('should return non-PEM input unchanged', () => {
38+
expect(formatPemBlock('my secret key')).toBe('my secret key');
39+
expect(formatPemBlock('pass\\nphrase')).toBe('pass\\nphrase');
40+
expect(formatPemBlock('-----END CERTIFICATE-----')).toBe('-----END CERTIFICATE-----');
41+
});
42+
3743
it('should format compact RSA PRIVATE KEY block', () => {
3844
const compactKey = `-----BEGIN RSA PRIVATE KEY-----${'C'.repeat(64)}-----END RSA PRIVATE KEY-----`;
3945

packages/@n8n/utils/src/format-pem-block.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ function formatCompactPem(pem: string, isPublic: boolean): string | undefined {
2525
* by collapsing whitespace and wrapping the body at 64 chars. Multi-block PEM
2626
* chains are returned unchanged.
2727
*
28+
* Input that is not a single-line PEM block is returned unchanged, so a plain
29+
* secret (e.g. a key passphrase) passed here by mistake is not corrupted.
30+
*
2831
* @param pem - The PEM-encoded block to format.
2932
* @param isPublic - When true, match `PUBLIC KEY` labels instead of the default `PRIVATE KEY` / `CERTIFICATE`.
3033
* @returns The formatted PEM block.
@@ -34,7 +37,9 @@ export function formatPemBlock(pem: string, isPublic = false): string {
3437
if (isPublic) {
3538
regex = /(PUBLIC KEY)/;
3639
}
37-
if (!pem || /\n/.test(pem)) {
40+
// The fallback formatter below would collapse a non-PEM value's whitespace
41+
// into newlines, corrupting plain secrets such as key passphrases.
42+
if (!pem || /\n/.test(pem) || !pem.includes('-----BEGIN ')) {
3843
return pem;
3944
}
4045
const compactPem = formatCompactPem(pem, isPublic);

packages/nodes-base/nodes/HttpRequest/GenericFunctions.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,7 @@ export const setAgentOptions = (
312312
if (sslCertificates.ca) agentOptions.ca = formatPemBlock(sslCertificates.ca);
313313
if (sslCertificates.cert) agentOptions.cert = formatPemBlock(sslCertificates.cert);
314314
if (sslCertificates.key) agentOptions.key = formatPemBlock(sslCertificates.key);
315-
if (sslCertificates.passphrase)
316-
agentOptions.passphrase = formatPemBlock(sslCertificates.passphrase);
315+
if (sslCertificates.passphrase) agentOptions.passphrase = sslCertificates.passphrase;
317316
requestOptions.agentOptions = agentOptions;
318317
}
319318
};

packages/nodes-base/nodes/HttpRequest/test/utils/utils.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,48 @@ describe('HTTP Node Utils', () => {
131131
},
132132
});
133133
});
134+
135+
it('should keep a passphrase containing whitespace unchanged', async () => {
136+
const requestOptions: IRequestOptions = {
137+
method: 'GET',
138+
uri: 'https://example.com',
139+
};
140+
141+
const sslCertificates = {
142+
passphrase: 'my secret key',
143+
};
144+
145+
setAgentOptions(requestOptions, sslCertificates);
146+
147+
expect(requestOptions).toStrictEqual({
148+
method: 'GET',
149+
uri: 'https://example.com',
150+
agentOptions: {
151+
passphrase: 'my secret key',
152+
},
153+
});
154+
});
155+
156+
it('should wrap compact PEM certificates but not the passphrase', async () => {
157+
const requestOptions: IRequestOptions = {
158+
method: 'GET',
159+
uri: 'https://example.com',
160+
};
161+
162+
const sslCertificates = {
163+
cert: `-----BEGIN CERTIFICATE-----${'A'.repeat(70)}-----END CERTIFICATE-----`,
164+
key: `-----BEGIN PRIVATE KEY-----${'B'.repeat(70)}-----END PRIVATE KEY-----`,
165+
passphrase: 'my secret key',
166+
};
167+
168+
setAgentOptions(requestOptions, sslCertificates);
169+
170+
expect(requestOptions.agentOptions).toStrictEqual({
171+
cert: `-----BEGIN CERTIFICATE-----\n${'A'.repeat(64)}\n${'A'.repeat(6)}\n-----END CERTIFICATE-----`,
172+
key: `-----BEGIN PRIVATE KEY-----\n${'B'.repeat(64)}\n${'B'.repeat(6)}\n-----END PRIVATE KEY-----`,
173+
passphrase: 'my secret key',
174+
});
175+
});
134176
});
135177

136178
describe('sanitizeUiMessage', () => {

0 commit comments

Comments
 (0)