Skip to content
10 changes: 4 additions & 6 deletions apps/cli/lib/certificate-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { domainToASCII } from 'node:url';
import { promisify } from 'node:util';
import { CERT_UNTRUSTED_ROOT, SERVER_AUTH_OID } from '@studio/common/constants';
import { getCertificatesPath } from '@studio/common/lib/well-known-paths';
import sudo from '@vscode/sudo-prompt';
import { __ } from '@wordpress/i18n';
Expand Down Expand Up @@ -159,15 +160,12 @@ export async function isRootCATrusted(): Promise< boolean > {

if ( process.platform === 'win32' ) {
try {
// Execute certutil with more specific validation
const { stdout } = await execFilePromise( 'certutil', [ '-verify', CA_CERT_PATH ] );

const hasValidPolicies =
stdout.includes( 'Verified Application Policies:' ) &&
stdout.includes( 'Server Authentication' );
const isTrusted = ! stdout.includes( CERT_UNTRUSTED_ROOT );
const hasServerAuthPolicy = stdout.includes( SERVER_AUTH_OID );

// Only consider the certificate trusted if it has the Server Authentication policy.
return hasValidPolicies;
return isTrusted && hasServerAuthPolicy;
} catch ( error ) {
return false;
}
Expand Down
10 changes: 4 additions & 6 deletions apps/studio/src/lib/certificate-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fs from 'node:fs';
import path from 'node:path';
import { promisify } from 'node:util';
import * as Sentry from '@sentry/electron/main';
import { CERT_UNTRUSTED_ROOT, SERVER_AUTH_OID } from '@studio/common/constants';
import { getCertificatesPath } from '@studio/common/lib/well-known-paths';
import sudo from '@vscode/sudo-prompt';

Expand All @@ -27,15 +28,12 @@ export async function isRootCATrusted(): Promise< boolean > {

if ( process.platform === 'win32' ) {
try {
// Execute certutil with more specific validation
const { stdout } = await execFilePromise( 'certutil', [ '-verify', CA_CERT_PATH ] );

const hasValidPolicies =
stdout.includes( 'Verified Application Policies:' ) &&
stdout.includes( 'Server Authentication' );
const isTrusted = ! stdout.includes( CERT_UNTRUSTED_ROOT );
const hasServerAuthPolicy = stdout.includes( SERVER_AUTH_OID );

// Only consider the certificate trusted if it has the Server Authentication policy.
return hasValidPolicies;
return isTrusted && hasServerAuthPolicy;
} catch ( error ) {
return false;
}
Expand Down
5 changes: 5 additions & 0 deletions tools/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export const PLAYGROUND_CLI_INACTIVITY_TIMEOUT = 2 * 60 * 1000; // 2 minutes of
export const PLAYGROUND_CLI_MAX_TIMEOUT = 10 * 60 * 1000; // 10 minutes absolute maximum
export const PLAYGROUND_CLI_ACTIVITY_CHECK_INTERVAL = 5 * 1000; // Check for inactivity every 5 seconds

// Certificate validation — we use these instead of human-readable labels
// because Windows localizes certutil output on non-English systems.
export const SERVER_AUTH_OID = '1.3.6.1.5.5.7.3.1'; // RFC 5280 §4.2.1.12 (id-kp-serverAuth)
export const CERT_UNTRUSTED_ROOT = 'CERT_TRUST_IS_UNTRUSTED_ROOT'; // Windows API constant

// Custom domains
export const DEFAULT_CUSTOM_DOMAIN_SUFFIX = '.wp.local';

Expand Down
Loading