@@ -2,7 +2,6 @@ import { systemCertsAsync } from 'system-ca';
2
2
import type { Options as SystemCAOptions } from 'system-ca' ;
3
3
import { promises as fs } from 'fs' ;
4
4
import { rootCertificates } from 'tls' ;
5
- import { X509Certificate } from 'crypto' ;
6
5
7
6
// A bit more generic than SecureContextOptions['ca'] because of Uint8Array -> Buffer + readonly
8
7
type NodeJSCAOption = string | Uint8Array | readonly ( string | Uint8Array ) [ ] ;
@@ -51,131 +50,6 @@ export function mergeCA(...args: (NodeJSCAOption | undefined)[]): string {
51
50
return [ ...ca ] . join ( '\n' ) ;
52
51
}
53
52
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
-
179
53
// Thin wrapper around system-ca, which merges:
180
54
// - Explicit CA options passed as options
181
55
// - The Node.js TLS root store
@@ -184,8 +58,7 @@ export async function systemCA(
184
58
existingOptions : {
185
59
ca ?: NodeJSCAOption ;
186
60
tlsCAFile ?: string | null | undefined ;
187
- } = { } ,
188
- allowCertificatesWithoutIssuer ?: boolean // defaults to false
61
+ } = { }
189
62
) : Promise < {
190
63
ca : string ;
191
64
systemCACount : number ;
@@ -203,43 +76,21 @@ export async function systemCA(
203
76
204
77
let systemCertsError : Error | undefined ;
205
78
let asyncFallbackError : Error | undefined ;
206
- let systemCerts : ParsedX509Cert [ ] = [ ] ;
79
+ let systemCerts : string [ ] = [ ] ;
207
80
208
81
const messages : string [ ] = [ ] ;
209
82
210
- const _tlsSupportsAllowPartialTrustChainFlag =
211
- tlsSupportsAllowPartialTrustChainFlag ( ) ;
212
83
try {
213
84
const systemCertsResult = await systemCertsCached ( ) ;
214
85
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 ;
223
87
} catch ( err : any ) {
224
88
systemCertsError = err ;
225
89
}
226
90
227
- if (
228
- ! (
229
- allowCertificatesWithoutIssuer ??
230
- ! ! process . env . DEVTOOLS_ALLOW_CERTIFICATES_WITHOUT_ISSUER
231
- ) &&
232
- ! _tlsSupportsAllowPartialTrustChainFlag
233
- ) {
234
- systemCerts = removeCertificatesWithoutIssuer ( systemCerts , messages ) ;
235
- }
236
-
237
91
return {
238
92
ca : mergeCA (
239
- ( _tlsSupportsAllowPartialTrustChainFlag
240
- ? systemCerts
241
- : sortByExpirationDate ( systemCerts )
242
- ) . map ( ( { pem } ) => pem ) ,
93
+ systemCerts ,
243
94
rootCertificates ,
244
95
existingOptions . ca ,
245
96
await readTLSCAFilePromise
0 commit comments