Skip to content

Commit 9f2ff40

Browse files
authored
chore(devtools-proxy-support): remove CA transformation hacks COMPASS-8253 (#485)
We now have `allowPartialTrustChain` available in both Compass and mongosh and consequently do not need to keep these hacks around anymore.
1 parent 7aeaac8 commit 9f2ff40

File tree

3 files changed

+5
-290
lines changed

3 files changed

+5
-290
lines changed

packages/devtools-proxy-support/src/agent.spec.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import type { Server as TLSServer } from 'tls';
1111
import { createServer as createTLSServer } from 'tls';
1212
import { promises as fs } from 'fs';
1313
import type { AddressInfo } from 'net';
14-
import { tlsSupportsAllowPartialTrustChainFlag } from './system-ca';
1514

1615
describe('createAgent', function () {
1716
let setup: HTTPServerProxyTestSetup;
@@ -393,11 +392,7 @@ q/I2+0j6dAkOGcK/68z7qQXByeGri3n28a1Kn6o=
393392
});
394393

395394
it('can connect using partial trust chains in the system CA list', async function () {
396-
if (
397-
process.platform !== 'linux' ||
398-
!tlsSupportsAllowPartialTrustChainFlag()
399-
)
400-
return this.skip(); // only really mock-able on Linux
395+
if (process.platform !== 'linux') return this.skip(); // only really mock-able on Linux
401396
resetSystemCACache({
402397
env: {
403398
SSL_CERT_FILE: path.join(fixtures, 'ca.pem'),

packages/devtools-proxy-support/src/system-ca.spec.ts

Lines changed: 0 additions & 131 deletions
This file was deleted.

packages/devtools-proxy-support/src/system-ca.ts

Lines changed: 4 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { systemCertsAsync } from 'system-ca';
22
import type { Options as SystemCAOptions } from 'system-ca';
33
import { promises as fs } from 'fs';
44
import { rootCertificates } from 'tls';
5-
import { X509Certificate } from 'crypto';
65

76
// A bit more generic than SecureContextOptions['ca'] because of Uint8Array -> Buffer + readonly
87
type NodeJSCAOption = string | Uint8Array | readonly (string | Uint8Array)[];
@@ -51,131 +50,6 @@ export function mergeCA(...args: (NodeJSCAOption | undefined)[]): string {
5150
return [...ca].join('\n');
5251
}
5352

54-
export type ParsedX509Cert = { pem: string; parsed: X509Certificate | null };
55-
56-
/**
57-
* Safely parse provided certs, push any encountered errors to the provided
58-
* messages array
59-
*/
60-
export function parseCACerts(
61-
ca: NodeJSCAOption,
62-
messages: string[]
63-
): ParsedX509Cert[] {
64-
ca = Array.isArray(ca) ? ca : [ca];
65-
return ca.map((cert) => {
66-
const pem = certToString(cert);
67-
let parsed: X509Certificate | null = null;
68-
try {
69-
parsed = new X509Certificate(pem);
70-
} catch (err: unknown) {
71-
// Most definitely should happen never or extremely rarely, in case it
72-
// does, if this cert will affect the TLS connection verification, the
73-
// connection will most definitely fail and we'll see it in the logs. For
74-
// that reason we're just logging, but not throwing an error here
75-
messages.push(
76-
`Unable to parse certificate: ${
77-
err && typeof err === 'object' && 'message' in err
78-
? String(err.message)
79-
: String(err)
80-
}`
81-
);
82-
}
83-
return { pem, parsed };
84-
});
85-
}
86-
87-
function certificateHasMatchingIssuer(
88-
cert: X509Certificate,
89-
certs: ParsedX509Cert[]
90-
) {
91-
return (
92-
cert.checkIssued(cert) ||
93-
certs.some(({ parsed: issuer }) => {
94-
return issuer && cert.checkIssued(issuer);
95-
})
96-
);
97-
}
98-
99-
const withRemovedMissingIssuerCache = new WeakMap<
100-
ParsedX509Cert[],
101-
{
102-
ca: ParsedX509Cert[];
103-
messages: string[];
104-
}
105-
>();
106-
107-
// TODO(COMPASS-8253): Remove this in favor of OpenSSL's X509_V_FLAG_PARTIAL_CHAIN
108-
// See linked tickets for details on why we need this (tl;dr: the system certificate
109-
// store may contain intermediate certficiates without the corresponding trusted root,
110-
// and OpenSSL does not seem to accept that)
111-
export function removeCertificatesWithoutIssuer(
112-
ca: ParsedX509Cert[],
113-
messages: string[]
114-
): ParsedX509Cert[] {
115-
const result:
116-
| {
117-
ca: ParsedX509Cert[];
118-
messages: string[];
119-
}
120-
| undefined = withRemovedMissingIssuerCache.get(ca);
121-
122-
if (result) {
123-
messages.push(...result.messages);
124-
return result.ca;
125-
}
126-
127-
const _messages: string[] = [];
128-
const filteredCAlist = ca.filter((cert) => {
129-
// If cert was not parsed, we want to keep it in the list. The case should
130-
// be generally very rare, but in case it happens and this cert will affect
131-
// the TLS handshake, it will show up in the logs as the connection error
132-
// anyway, so it's safe to keep it
133-
const keep = !cert.parsed || certificateHasMatchingIssuer(cert.parsed, ca);
134-
if (!keep && cert.parsed) {
135-
const { parsed } = cert;
136-
_messages.push(
137-
`Removing certificate for '${parsed.subject}' because issuer '${parsed.issuer}' could not be found (serial no '${parsed.serialNumber}')`
138-
);
139-
}
140-
return keep;
141-
});
142-
withRemovedMissingIssuerCache.set(ca, {
143-
ca: filteredCAlist,
144-
messages: _messages,
145-
});
146-
messages.push(..._messages);
147-
return filteredCAlist;
148-
}
149-
150-
/**
151-
* Sorts cerificates by the Not After value. Items that are higher in the list
152-
* get picked up first by the CA issuer finding logic
153-
*
154-
* @see {@link https://jira.mongodb.org/browse/COMPASS-8322}
155-
*/
156-
export function sortByExpirationDate(ca: ParsedX509Cert[]) {
157-
return ca.slice().sort((a, b) => {
158-
if (!a.parsed || !b.parsed) {
159-
return 0;
160-
}
161-
return (
162-
new Date(b.parsed.validTo).getTime() -
163-
new Date(a.parsed.validTo).getTime()
164-
);
165-
});
166-
}
167-
168-
const nodeVersion = process.versions.node.split('.').map(Number);
169-
170-
export function tlsSupportsAllowPartialTrustChainFlag(): boolean {
171-
// TODO: Remove this flag and all X.509 parsing here once all our products
172-
// are at least on these Node.js versions
173-
return (
174-
(nodeVersion[0] >= 22 && nodeVersion[1] >= 9) || // https://github.com/nodejs/node/commit/c2bf0134c
175-
(nodeVersion[0] === 20 && nodeVersion[1] >= 18)
176-
); // https://github.com/nodejs/node/commit/1b3420274
177-
}
178-
17953
// Thin wrapper around system-ca, which merges:
18054
// - Explicit CA options passed as options
18155
// - The Node.js TLS root store
@@ -184,8 +58,7 @@ export async function systemCA(
18458
existingOptions: {
18559
ca?: NodeJSCAOption;
18660
tlsCAFile?: string | null | undefined;
187-
} = {},
188-
allowCertificatesWithoutIssuer?: boolean // defaults to false
61+
} = {}
18962
): Promise<{
19063
ca: string;
19164
systemCACount: number;
@@ -203,43 +76,21 @@ export async function systemCA(
20376

20477
let systemCertsError: Error | undefined;
20578
let asyncFallbackError: Error | undefined;
206-
let systemCerts: ParsedX509Cert[] = [];
79+
let systemCerts: string[] = [];
20780

20881
const messages: string[] = [];
20982

210-
const _tlsSupportsAllowPartialTrustChainFlag =
211-
tlsSupportsAllowPartialTrustChainFlag();
21283
try {
21384
const systemCertsResult = await systemCertsCached();
21485
asyncFallbackError = systemCertsResult.asyncFallbackError;
215-
if (_tlsSupportsAllowPartialTrustChainFlag) {
216-
systemCerts = systemCertsResult.certs.map((pem) => ({
217-
pem,
218-
parsed: null,
219-
}));
220-
} else {
221-
systemCerts = parseCACerts(systemCertsResult.certs, messages);
222-
}
86+
systemCerts = systemCertsResult.certs;
22387
} catch (err: any) {
22488
systemCertsError = err;
22589
}
22690

227-
if (
228-
!(
229-
allowCertificatesWithoutIssuer ??
230-
!!process.env.DEVTOOLS_ALLOW_CERTIFICATES_WITHOUT_ISSUER
231-
) &&
232-
!_tlsSupportsAllowPartialTrustChainFlag
233-
) {
234-
systemCerts = removeCertificatesWithoutIssuer(systemCerts, messages);
235-
}
236-
23791
return {
23892
ca: mergeCA(
239-
(_tlsSupportsAllowPartialTrustChainFlag
240-
? systemCerts
241-
: sortByExpirationDate(systemCerts)
242-
).map(({ pem }) => pem),
93+
systemCerts,
24394
rootCertificates,
24495
existingOptions.ca,
24596
await readTLSCAFilePromise

0 commit comments

Comments
 (0)