Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 0 additions & 4 deletions .buildkite/commands/build-for-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,6 @@ If (@('1', 'true') -contains $useAzureTrustedSigning) {
$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 "--- :npm: Installing Node dependencies"
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
14 changes: 8 additions & 6 deletions apps/studio/windowsSign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ 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.
// Gated on USE_AZURE_TRUSTED_SIGNING, which the signed-build jobs set (see
// build-for-windows.ps1). Package-only jobs — e.g. the Windows E2E run, which
// uses `electron-forge package` and never signs — leave it unset and get
// undefined, so this config (loaded by Forge on every build) stays inert
// there. The throw fires only for a build that asked to sign but is missing
// its Azure env, where signtool would otherwise fail with an opaque exit code.
function getWindowsSign(): WindowsSignOptions | undefined {
const useAzureSigning = [ '1', 'true' ].includes(
const signWithAzure = [ '1', 'true' ].includes(
( process.env.USE_AZURE_TRUSTED_SIGNING ?? '' ).trim().toLowerCase()
);

if ( ! useAzureSigning ) {
if ( ! signWithAzure ) {
return undefined;
}

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 }` );
}
}