fix(desktop): exclude expired Windows trust roots with real-certificate coverage (#107014)
* fix(desktop): filter expired Windows CAs and deduplicate trust roots * test(desktop): verify CA expiry and deduplication with real certificates * test(desktop): cover Linux alongside macOS CA no-op policy Co-authored-by: Gabriel Stoltemberg <215755014+Stoltemberg@users.noreply.github.com> * chore: map CA trust contributors for release attribution * style(desktop): match CA filtering to lint rules --------- Co-authored-by: wliu-dev <249166551+wliu-dev@users.noreply.github.com> Co-authored-by: Gabriel Stoltemberg <215755014+Stoltemberg@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
// Synthetic self-signed CAs; no private keys are retained. Tests freeze time
|
||||
// between the expired root's 2025 expiry and the other roots' 2035 expiry.
|
||||
export const bundledRoot = `-----BEGIN CERTIFICATE-----
|
||||
MIIBWDCB/6ADAgECAhRU6TyqBZA4z6kjw80UZ4JJcTGTezAKBggqhkjOPQQDAjAi
|
||||
MSAwHgYDVQQDDBdIZXJtZXMgdGVzdCBidW5kbGVkUm9vdDAeFw0yMDAxMDEwMDAw
|
||||
MDBaFw0zNTAxMDEwMDAwMDBaMCIxIDAeBgNVBAMMF0hlcm1lcyB0ZXN0IGJ1bmRs
|
||||
ZWRSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEzYsepxCA6/L5jOtyaK7c
|
||||
tonILmwaEH7S3SaXZkCPMPPn7ciN08fFIQvEK2xmexibsIW/07/y+EQsCsytdXD7
|
||||
TqMTMBEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiAoOvvNIWtl
|
||||
56kb5jeS67rndvpGdvRVfA8hu/d7qPUXHgIhAOVWB0FmymX7/ptbzS9os2DB7S8M
|
||||
bsTP81ca6H4QAVMO
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
export const privateRoot = `-----BEGIN CERTIFICATE-----
|
||||
MIIBWDCB/6ADAgECAhQpNQF0JRsR2+tIXdZrGV71uQLqnjAKBggqhkjOPQQDAjAi
|
||||
MSAwHgYDVQQDDBdIZXJtZXMgdGVzdCBwcml2YXRlUm9vdDAeFw0yMDAxMDEwMDAw
|
||||
MDBaFw0zNTAxMDEwMDAwMDBaMCIxIDAeBgNVBAMMF0hlcm1lcyB0ZXN0IHByaXZh
|
||||
dGVSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEbWaJLnFzm5gFQFK4u1Sg
|
||||
+ZSXFpfKOuKVLiLU9K2xfg9glPZQY+25Eh+7eMkeOWbOOgXbQE9gmQ2Lsjs+G57C
|
||||
fqMTMBEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiAuMyPaHvKJ
|
||||
BTNIxn3kTlG/7rQ8iSBJ/iTYSxC/7sLzcwIhAJZno/XUb+l9XQfNgADj7jHRorx7
|
||||
Hfp7kgJ01+X1LAKU
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
|
||||
export const expiredRoot = `-----BEGIN CERTIFICATE-----
|
||||
MIIBWDCB/6ADAgECAhQpDur4nw/PAq6RuWUbTzDvuICiojAKBggqhkjOPQQDAjAi
|
||||
MSAwHgYDVQQDDBdIZXJtZXMgdGVzdCBleHBpcmVkUm9vdDAeFw0yMDAxMDEwMDAw
|
||||
MDBaFw0yNTAxMDEwMDAwMDBaMCIxIDAeBgNVBAMMF0hlcm1lcyB0ZXN0IGV4cGly
|
||||
ZWRSb290MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEe+50enZvxfDciM9yMPBo
|
||||
BHYtfdV4eSv017W1GgC3OyDQpypA7Usio1jSy6VXGSGTKpKVPTeMhTPtQLSpbf2z
|
||||
eKMTMBEwDwYDVR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNIADBFAiAduJa3QWjS
|
||||
+lF7cmuGdgUezYQLEIUqAPsPhJxgzGNmiwIhAKIyQ1uxv4Mzl0Mou7AiUGpqy8Xr
|
||||
CKxqXEB0ODeqpB0N
|
||||
-----END CERTIFICATE-----
|
||||
`
|
||||
@@ -1,9 +1,36 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { X509Certificate } from 'node:crypto'
|
||||
|
||||
import { test } from 'vitest'
|
||||
import { afterEach, test, vi } from 'vitest'
|
||||
|
||||
import { bundledRoot, expiredRoot, privateRoot } from './fixtures/windows-system-ca'
|
||||
import { installWindowsSystemCaTrust, type NodeTlsCaApi } from './windows-system-ca'
|
||||
|
||||
afterEach(() => vi.restoreAllMocks())
|
||||
|
||||
test('excludes expired roots and deduplicates real certificates with defaults first', () => {
|
||||
vi.spyOn(Date, 'now').mockReturnValue(new Date('2026-01-01T00:00:00Z').getTime())
|
||||
const tlsApi = fakeTlsApi(
|
||||
[expiredRoot, bundledRoot, bundledRoot, 'unparseable-default'],
|
||||
[expiredRoot, bundledRoot.replaceAll('\n', '\r\n'), privateRoot, privateRoot, 'unparseable-system']
|
||||
)
|
||||
|
||||
const result = installWindowsSystemCaTrust(tlsApi, 'win32')
|
||||
|
||||
assert.deepEqual(tlsApi.installed, [[bundledRoot, 'unparseable-default', privateRoot, 'unparseable-system']])
|
||||
assert.deepEqual(result, { applied: true, systemCertificateCount: 2, totalCertificateCount: 4 })
|
||||
})
|
||||
|
||||
test('excludes a root at its exact expiry while retaining valid defaults', () => {
|
||||
vi.spyOn(Date, 'now').mockReturnValue(new X509Certificate(expiredRoot).validToDate.getTime())
|
||||
const tlsApi = fakeTlsApi([bundledRoot], [expiredRoot])
|
||||
|
||||
const result = installWindowsSystemCaTrust(tlsApi, 'win32')
|
||||
|
||||
assert.deepEqual(tlsApi.installed, [[bundledRoot]])
|
||||
assert.deepEqual(result, { applied: true, systemCertificateCount: 0, totalCertificateCount: 1 })
|
||||
})
|
||||
|
||||
function fakeTlsApi(
|
||||
defaults: string[] = ['bundled-ca', 'extra-ca'],
|
||||
system: string[] = ['windows-root-ca']
|
||||
@@ -34,7 +61,7 @@ test('installs Windows system CAs without dropping existing defaults', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('does not inspect or replace CAs outside Windows', () => {
|
||||
test.each(['darwin', 'linux'] as const)('does not inspect or replace CAs on %s', platform => {
|
||||
let reads = 0
|
||||
|
||||
const tlsApi: NodeTlsCaApi = {
|
||||
@@ -48,7 +75,7 @@ test('does not inspect or replace CAs outside Windows', () => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = installWindowsSystemCaTrust(tlsApi, 'darwin')
|
||||
const result = installWindowsSystemCaTrust(tlsApi, platform)
|
||||
|
||||
assert.equal(reads, 0)
|
||||
assert.deepEqual(result, {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { X509Certificate } from 'node:crypto'
|
||||
|
||||
interface NodeTlsCaApi {
|
||||
getCACertificates(type?: 'default' | 'system'): string[]
|
||||
setDefaultCACertificates(certificates: string[]): void
|
||||
@@ -31,12 +33,35 @@ function installWindowsSystemCaTrust(tlsApi: NodeTlsCaApi, platform = process.pl
|
||||
}
|
||||
}
|
||||
|
||||
const certificates = [...defaultCertificates, ...systemCertificates]
|
||||
// Prefer existing defaults. Expired Windows roots can divert OpenSSL onto
|
||||
// an expired chain even when a valid bundled trust path exists.
|
||||
const seen = new Set<string>()
|
||||
const now = Date.now()
|
||||
|
||||
const keepCertificate = (pem: string): boolean => {
|
||||
try {
|
||||
const certificate = new X509Certificate(pem)
|
||||
|
||||
if (certificate.validToDate.getTime() <= now || seen.has(certificate.fingerprint256)) {
|
||||
return false
|
||||
}
|
||||
seen.add(certificate.fingerprint256)
|
||||
} catch {
|
||||
// Leave PEM acceptability to Node if its X.509 parser cannot inspect it.
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
const filteredDefaults = defaultCertificates.filter(keepCertificate)
|
||||
const filteredSystem = systemCertificates.filter(keepCertificate)
|
||||
const certificates = [...filteredDefaults, ...filteredSystem]
|
||||
|
||||
tlsApi.setDefaultCACertificates(certificates)
|
||||
|
||||
return {
|
||||
applied: true,
|
||||
systemCertificateCount: systemCertificates.length,
|
||||
systemCertificateCount: filteredSystem.length,
|
||||
totalCertificateCount: certificates.length
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Stoltemberg
|
||||
@@ -0,0 +1 @@
|
||||
wliu-dev
|
||||
Reference in New Issue
Block a user