Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions .buildkite/commands/build-for-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,10 @@ if ($Architecture -notin $VALID_ARCHITECTURES) {
Exit 1
}

$useAzureTrustedSigning = "$($env:USE_AZURE_TRUSTED_SIGNING)".Trim().ToLower()
If (@('1', 'true') -contains $useAzureTrustedSigning) {
Write-Host "--- :lock: Setting up Azure Trusted Signing"
$setupScript = (Get-Command setup_azure_trusted_signing.ps1 -ErrorAction Stop).Source
& $setupScript
If ($LastExitCode -ne 0) { Exit $LastExitCode }
} Else {
# setup_windows_code_signing.ps1 comes from CI Toolkit Plugin
& "setup_windows_code_signing.ps1"
If ($LastExitCode -ne 0) { Exit $LastExitCode }
}
Write-Host "--- :lock: Setting up Azure Trusted Signing"
$setupScript = (Get-Command setup_azure_trusted_signing.ps1 -ErrorAction Stop).Source
& $setupScript
If ($LastExitCode -ne 0) { Exit $LastExitCode }

Write-Host "--- :npm: Installing Node dependencies"
bash .buildkite/commands/install-node-dependencies.sh
Expand Down
2 changes: 0 additions & 2 deletions .buildkite/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,6 @@ steps:
- label: 🔨 Windows Dev Build - {{matrix}}
agents:
queue: windows
env:
USE_AZURE_TRUSTED_SIGNING: 1
command: powershell -File .buildkite/commands/build-for-windows.ps1 -BuildType dev -Architecture {{matrix}}
artifact_paths:
- apps\studio\out\**\studio-setup.exe
Expand Down
2 changes: 0 additions & 2 deletions .buildkite/release-build-and-distribute.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ steps:
- label: 🔨 Windows Release Build - {{matrix}}
agents:
queue: windows
env:
USE_AZURE_TRUSTED_SIGNING: 1
command: |
bash .buildkite/commands/checkout-release-branch.sh "${RELEASE_VERSION}"
powershell -File .buildkite/commands/build-for-windows.ps1 -BuildType release -Architecture {{matrix}}
Expand Down
12 changes: 3 additions & 9 deletions apps/studio/forge.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,9 @@ const config: ForgeConfig = {

setupExe: 'studio-setup.exe',

// Azure mode: use the custom signing hook that calls signtool
// with Azure Trusted Signing parameters.
// PFX mode: use the local certificate file and password.
...( windowsSign
? { windowsSign }
: {
certificateFile: path.join( repoRoot, 'certificate.pfx' ),
certificatePassword: process.env.WINDOWS_CODE_SIGNING_CERT_PASSWORD,
} ),
// Sign via the custom Azure Trusted Signing hook (signtool, SHA256-only).
// Undefined off Windows CI, where the build is left unsigned.
...( windowsSign ? { windowsSign } : {} ),
},
[ 'win32' ]
),
Expand Down
18 changes: 7 additions & 11 deletions apps/studio/windowsSign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,19 @@ import type { WindowsSignOptions } from '@electron/packager';
// dual-signs (SHA1 + SHA256), but Azure only supports SHA256.
// The hook calls signtool directly with SHA256-only parameters.
//
// Controlled by the USE_AZURE_TRUSTED_SIGNING env var:
// - Unset or not '1'/'true': returns undefined, letting Forge use PFX certificate signing.
// - '1' or 'true': returns the Azure signing hook config, or throws if the
// required Azure env vars are missing.
// Only signs on Windows CI. This config is loaded by Forge on every platform,
// so non-Windows builds (and local Windows builds) get undefined — returning
// the Azure config unconditionally would throw on Mac/Linux CI, where the
// Azure env vars are absent.
function getWindowsSign(): WindowsSignOptions | undefined {
const useAzureSigning = [ '1', 'true' ].includes(
( process.env.USE_AZURE_TRUSTED_SIGNING ?? '' ).trim().toLowerCase()
);

if ( ! useAzureSigning ) {
if ( ! process.env.CI || process.platform !== 'win32' ) {
return undefined;
}

if ( ! process.env.AZURE_CODE_SIGNING_DLIB || ! process.env.AZURE_METADATA_JSON || ! process.env.SIGNTOOL_PATH ) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — confirmed. The Windows E2E job (run-e2e-tests.sh) runs electron-forge package for an intentionally-unsigned bundle and never runs setup_azure_trusted_signing.ps1, so a CI && win32 gate would have thrown at config import there.

Digging in, USE_AZURE_TRUSTED_SIGNING turned out to be load-bearing rather than redundant: it's the "sign this build" signal that the make jobs set and E2E leaves unset. So I've kept it as the gate (in windowsSign.ts and the pipelines) and removed only the dead PFX fallback branches. Addressed in 7dea0c6.

Posted by Claude (Opus 4.8) on behalf of @mokagio with approval.

throw new Error(
'USE_AZURE_TRUSTED_SIGNING is set but Azure signing env vars ' +
'(AZURE_CODE_SIGNING_DLIB, AZURE_METADATA_JSON, SIGNTOOL_PATH) are missing. ' +
'Windows CI build is missing Azure Trusted Signing env vars ' +
'(AZURE_CODE_SIGNING_DLIB, AZURE_METADATA_JSON, SIGNTOOL_PATH). ' +
'Did setup_azure_trusted_signing.ps1 run?'
);
}
Expand Down
108 changes: 42 additions & 66 deletions scripts/package-appx.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,18 @@ import path from 'path';
import convertToWindowsStore from 'electron2appx';
import packageJson from '../apps/studio/package.json' with { type: 'json' };

const useAzureSigning = [ '1', 'true' ].includes(
( process.env.USE_AZURE_TRUSTED_SIGNING ?? '' ).trim().toLowerCase()
);

console.log( '--- :electron: Packaging AppX' );

if ( useAzureSigning ) {
const azureSigning = await import( './azure-signing.cjs' );
const { assertAzureSigningEnv } = azureSigning.default;
console.log( '~~~ Verifying Azure Trusted Signing env vars...' );
try {
assertAzureSigningEnv();
} catch ( error ) {
console.error( error instanceof Error ? error.message : error );
process.exit( 1 );
}
} else {
console.log( '~~~ Verifying WINDOWS_CODE_SIGNING_CERT_PASSWORD env var...' );
if ( ! process.env.WINDOWS_CODE_SIGNING_CERT_PASSWORD ) {
console.error( 'Required env var WINDOWS_CODE_SIGNING_CERT_PASSWORD is not set!' );
process.exit( 1 );
}
const { assertAzureSigningEnv, getAzureSignArgs, getAzureSigningConfig } = (
await import( './azure-signing.cjs' )
).default;

console.log( '~~~ Verifying Azure Trusted Signing env vars...' );
try {
assertAzureSigningEnv();
} catch ( error ) {
console.error( error instanceof Error ? error.message : error );
process.exit( 1 );
}

// Get architecture from environment variable, default to x64 for backward compatibility
Expand Down Expand Up @@ -189,54 +179,40 @@ await convertToWindowsStore( {
const appxOutputPathSigned = path.resolve( outPath, `${ appxName }-${ architecture }-signed` );
console.log( `~~~ Creating signed .appx for local testing at ${ appxOutputPathSigned }...` );

if ( useAzureSigning ) {
const sideloadPublisher =
'CN=Automattic Inc., O=Automattic Inc., L=San Francisco, S=California, C=US';
const sideloadPublisher =
'CN=Automattic Inc., O=Automattic Inc., L=San Francisco, S=California, C=US';

// Build unsigned, then sign with Azure Trusted Signing via signtool.
await convertToWindowsStore( {
...sharedOptions,
publisher: sideloadPublisher,
devCert: 'nil',
outputDirectory: appxOutputPathSigned,
} );
// Build unsigned, then sign with Azure Trusted Signing via signtool.
await convertToWindowsStore( {
...sharedOptions,
publisher: sideloadPublisher,
devCert: 'nil',
outputDirectory: appxOutputPathSigned,
} );

console.log( '~~~ Signing sideload .appx with Azure Trusted Signing...' );
const azureSigning = await import( './azure-signing.cjs' );
const { getAzureSignArgs, getAzureSigningConfig } = azureSigning.default;
const appxFiles = ( await fs.readdir( appxOutputPathSigned ) ).filter( ( f ) =>
f.endsWith( '.appx' )
);
if ( appxFiles.length === 0 ) {
console.error( 'No .appx file found to sign!' );
process.exit( 1 );
}
console.log( '~~~ Signing sideload .appx with Azure Trusted Signing...' );
const appxFiles = ( await fs.readdir( appxOutputPathSigned ) ).filter( ( f ) =>
f.endsWith( '.appx' )
);
if ( appxFiles.length === 0 ) {
console.error( 'No .appx file found to sign!' );
process.exit( 1 );
}

for ( const appxFile of appxFiles ) {
const appxPath = path.join( appxOutputPathSigned, appxFile );
console.log( `Signing ${ appxPath }...` );
const { signtoolPath } = getAzureSigningConfig();
execFileSync( signtoolPath, getAzureSignArgs( appxPath ), {
stdio: 'inherit',
} );
console.log( `Signed ${ appxFile } successfully.` );

// Rename to remove misleading "unsigned" from the filename
const renamedFile = appxFile.replace( ' unsigned', '' );
if ( renamedFile !== appxFile ) {
const renamedPath = path.join( appxOutputPathSigned, renamedFile );
await fs.rename( appxPath, renamedPath );
console.log( `Renamed to ${ renamedFile }` );
}
}
} else {
// PFX certificate signing
await convertToWindowsStore( {
...sharedOptions,
publisher:
'CN="Automattic, Inc.", O="Automattic, Inc.", S=California, C=US',
devCert: 'certificate.pfx',
certPass: process.env.WINDOWS_CODE_SIGNING_CERT_PASSWORD,
outputDirectory: appxOutputPathSigned,
for ( const appxFile of appxFiles ) {
const appxPath = path.join( appxOutputPathSigned, appxFile );
console.log( `Signing ${ appxPath }...` );
const { signtoolPath } = getAzureSigningConfig();
execFileSync( signtoolPath, getAzureSignArgs( appxPath ), {
stdio: 'inherit',
} );
console.log( `Signed ${ appxFile } successfully.` );

// Rename to remove misleading "unsigned" from the filename
const renamedFile = appxFile.replace( ' unsigned', '' );
if ( renamedFile !== appxFile ) {
const renamedPath = path.join( appxOutputPathSigned, renamedFile );
await fs.rename( appxPath, renamedPath );
console.log( `Renamed to ${ renamedFile }` );
}
}