fix(desktop): trust CAs in the windows intermediate store (#3494)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-15 16:48:55 +02:00
1 parent 4f88872a3c
commit 4b8ce8bf63
8 files changed
+290 -9

No files matched your search

@@ -66,6 +66,17 @@ describe('desktop system trust verifier', () => {
assert.equal(buildDesktopTrustAnchors([read('kz_root')]).size, 0);
});
test('anchors on DER roots read from the Windows intermediate store', () => {
const der = (name) => new X509Certificate(read(name)).raw;
const store = buildDesktopTrustAnchors([der('root')]);
const request = {hostname: 'host.example', certificate: chain(['leaf', 'int']), errorCode: AUTHORITY_INVALID};
assert.equal(
verifyAgainstDesktopSystemTrust(request, () => store),
ACCEPT,
);
assert.equal(buildDesktopTrustAnchors([der('kz_root'), Buffer.from('not a certificate')]).size, 0);
});
test('anchors only on admin added CAs that the distro bundle still trusts', () => {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'desktop-trust-'));
const write = (file, content) => {
@@ -5,6 +5,7 @@ import net from 'node:net';
import {createChildLogger} from '@electron/common/Logger';
import {CHROMIUM_SPKI_BLOCKLIST, CHROMIUM_SPKI_BLOCKLIST_SOURCE} from '@electron/main/DesktopChromiumSPKIBlocklist';
import {resolveDesktopLocallyAddedCertificates} from '@electron/main/DesktopTrustedCertificates';
import {readWindowsIntermediateStoreRoots} from '@electron/main/GatewaySocketNativeBoundary';
import type {Certificate, Session} from 'electron';
const logger = createChildLogger('DesktopSystemTrustVerifier');
@@ -398,14 +399,14 @@ function chainsToDesktopTrustAnchor(
}
export function buildDesktopTrustAnchors(
certificates: ReadonlyArray<string>,
certificates: ReadonlyArray<string | Buffer>,
now: number = Date.now(),
): DesktopTrustAnchors {
const anchors = new Map<string, Array<ParsedCertificate>>();
for (const pem of certificates) {
for (const certificate of certificates) {
let anchor: ParsedCertificate;
try {
anchor = parseCertificate(new X509Certificate(pem));
anchor = parseCertificate(new X509Certificate(certificate));
} catch {
continue;
}
@@ -434,12 +435,23 @@ export function verifyAgainstDesktopSystemTrust(
return CHROMIUM_DEFAULT_RESULT;
}
function resolvePlatformAnchorCertificates(): ReadonlyArray<string | Buffer> {
switch (process.platform) {
case 'linux':
return resolveDesktopLocallyAddedCertificates();
case 'win32':
return readWindowsIntermediateStoreRoots();
default:
return [];
}
}
export function installDesktopSystemTrustVerifier(target: Session): void {
if (process.platform !== 'linux') return;
const anchors = buildDesktopTrustAnchors(resolveDesktopLocallyAddedCertificates());
const anchors = buildDesktopTrustAnchors(resolvePlatformAnchorCertificates());
if (anchors.size === 0) return;
logger.info('Trusting locally added CAs for Chromium requests', {
count: anchors.size,
platform: process.platform,
blocklist: CHROMIUM_SPKI_BLOCKLIST_SOURCE,
});
target.setCertificateVerifyProc((request, callback) => {
@@ -27,6 +27,7 @@ export interface GatewaySocketBoundary {
type GatewaySocketNativeModule = {
connect: GatewaySocketConnectBinding | null;
warmup?: (() => void) | null;
windowsIntermediateStoreRoots?: (() => Array<Buffer>) | null;
loadError: Error | null;
};
@@ -34,6 +35,7 @@ const requireModule = createRequire(import.meta.url);
let bindingCache: GatewaySocketConnectBinding | null | undefined;
let warmupCache: (() => void) | null = null;
let windowsIntermediateStoreRootsCache: (() => Array<Buffer>) | null = null;
export function loadGatewaySocketBinding(): GatewaySocketConnectBinding | null {
if (bindingCache !== undefined) return bindingCache;
@@ -47,6 +49,8 @@ export function loadGatewaySocketBinding(): GatewaySocketConnectBinding | null {
const nativeModule = requireModule(GATEWAY_SOCKET_ADDON_PACKAGE) as GatewaySocketNativeModule;
bindingCache = nativeModule.connect ?? null;
warmupCache = bindingCache === null ? null : (nativeModule.warmup ?? null);
windowsIntermediateStoreRootsCache =
bindingCache === null ? null : (nativeModule.windowsIntermediateStoreRoots ?? null);
} catch (error) {
log.warn('[NativeGateway] The native gateway addon is unavailable, using the browser transport', error);
bindingCache = null;
@@ -63,6 +67,16 @@ export function warmGatewaySocketTransport(): void {
}
}
export function readWindowsIntermediateStoreRoots(): ReadonlyArray<Buffer> {
if (loadGatewaySocketBinding() === null || windowsIntermediateStoreRootsCache === null) return [];
try {
return windowsIntermediateStoreRootsCache();
} catch (error) {
log.warn('[NativeGateway] Failed to read the Windows intermediate certificate store', error);
return [];
}
}
export function isNativeGatewayAvailable(): boolean {
return loadGatewaySocketBinding() !== null;
}