Unverified 提交 847f3d4f authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Switch Windows code signing to Azure Trusted Signing (#2429)

#skip-bb ## Summary - Replace DigiCert SSM-based code signing with Azure Trusted Signing for Windows builds - Add new `windowsSign.ts` configuration for Azure signing parameters - Update release workflow to install Azure Trusted Signing CLI and create metadata file - Version bump to 0.36.0-beta.1 for testing the new signing workflow ## Test plan - [ ] Trigger a release build and verify Windows binaries are signed correctly - [ ] Verify the signed executable passes Windows SmartScreen verification 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2429"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Switches Windows code signing from DigiCert SSM to Azure Trusted Signing to simplify CI and reduce secret handling. Updates Forge config and release workflow; bumps version to 0.36.0-beta.1 for testing. - **Refactors** - Added windowsSign.ts with Azure sign params (dlib + metadata), SHA-256, and timestamp server. - Wired windowsSign into Electron Forge packager and MakerSquirrel. - Release workflow installs Azure Trusted Signing via winget and writes signing-metadata.json; removes DigiCert SSM steps. - **Migration** - Add AZURE_CODE_SIGNING_DLIB, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID as CI secrets. The workflow sets AZURE_METADATA_JSON automatically. - Run a release build and verify the signed EXE passes SmartScreen. <sup>Written for commit 0d5b4d58940b59300796ea18e8c403bfcc25b30d. Summary will update on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarClaude Opus 4.5 <noreply@anthropic.com>
上级 9a059eb9
...@@ -45,29 +45,48 @@ jobs: ...@@ -45,29 +45,48 @@ jobs:
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }} MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
run: chmod +x tools/add-macos-cert.sh && . ./tools/add-macos-cert.sh run: chmod +x tools/add-macos-cert.sh && . ./tools/add-macos-cert.sh
# Windows only # Windows only
- name: Set up certificate - name: Install Azure Trusted Signing
if: contains(matrix.os.name, 'windows') if: contains(matrix.os.name, 'windows')
shell: powershell
run: | run: |
echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12 winget install Microsoft.AzureTrustedSigning --accept-source-agreements --accept-package-agreements
shell: bash - name: Create Azure signing metadata
- name: Set variables
if: contains(matrix.os.name, 'windows') if: contains(matrix.os.name, 'windows')
id: variables shell: powershell
run: | run: |
echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV" @'
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV" {
echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV" "Endpoint": "https://eus.codesigning.azure.net/",
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV" "CodeSigningAccountName": "dyad",
shell: bash "CertificateProfileName": "dyad-tech"
- name: Code signing with Software Trust Manager }
if: contains(matrix.os.name, 'windows') '@ | Out-File -Encoding utf8 signing-metadata.json
uses: digicert/ssm-code-signing@v1.1.0
- name: Sync certificate (Windows) echo "AZURE_METADATA_JSON=$PWD\signing-metadata.json" >> $env:GITHUB_ENV
if: contains(matrix.os.name, 'windows')
run: | # - name: Set up certificate
smctl windows certsync --keypair-alias=${{ secrets.DIGICERT_KEYPAIR_ALIAS }} # if: contains(matrix.os.name, 'windows')
shell: bash # run: |
# Publish (all platforms) # echo "${{ secrets.SM_CLIENT_CERT_FILE_B64 }}" | base64 --decode > /d/Certificate_pkcs12.p12
# shell: bash
# - name: Set variables
# if: contains(matrix.os.name, 'windows')
# id: variables
# run: |
# echo "SM_HOST=${{ secrets.SM_HOST }}" >> "$GITHUB_ENV"
# echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> "$GITHUB_ENV"
# echo "SM_CLIENT_CERT_FILE=D:\\Certificate_pkcs12.p12" >> "$GITHUB_ENV"
# echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> "$GITHUB_ENV"
# shell: bash
# - name: Code signing with Software Trust Manager
# if: contains(matrix.os.name, 'windows')
# uses: digicert/ssm-code-signing@v1.1.0
# - name: Sync certificate (Windows)
# if: contains(matrix.os.name, 'windows')
# run: |
# smctl windows certsync --keypair-alias=${{ secrets.DIGICERT_KEYPAIR_ALIAS }}
# shell: bash
# Publish (all platforms)
- name: Publish app - name: Publish app
uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2 uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3.0.2
with: with:
...@@ -77,11 +96,14 @@ jobs: ...@@ -77,11 +96,14 @@ jobs:
env: env:
DEBUG: "@electron/*,electron-forge:*,electron-windows-installer:main" DEBUG: "@electron/*,electron-forge:*,electron-windows-installer:main"
NODE_OPTIONS: "--max-old-space-size=4096" NODE_OPTIONS: "--max-old-space-size=4096"
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }} # SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
verify-assets: verify-assets:
name: Verify Release Assets name: Verify Release Assets
......
import { windowsSign } from "./windowsSign";
import type { ForgeConfig } from "@electron-forge/shared-types"; import type { ForgeConfig } from "@electron-forge/shared-types";
import { MakerSquirrel } from "@electron-forge/maker-squirrel"; import { MakerSquirrel } from "@electron-forge/maker-squirrel";
import { MakerZIP } from "@electron-forge/maker-zip"; import { MakerZIP } from "@electron-forge/maker-zip";
...@@ -8,44 +9,6 @@ import { VitePlugin } from "@electron-forge/plugin-vite"; ...@@ -8,44 +9,6 @@ import { VitePlugin } from "@electron-forge/plugin-vite";
import { FusesPlugin } from "@electron-forge/plugin-fuses"; import { FusesPlugin } from "@electron-forge/plugin-fuses";
import { FuseV1Options, FuseVersion } from "@electron/fuses"; import { FuseV1Options, FuseVersion } from "@electron/fuses";
import { AutoUnpackNativesPlugin } from "@electron-forge/plugin-auto-unpack-natives"; import { AutoUnpackNativesPlugin } from "@electron-forge/plugin-auto-unpack-natives";
import { execSync } from "child_process";
import path from "path";
// Path to signtool.exe bundled with electron-winstaller
// On GitHub Actions, this is the full path:
// D:\a\dyad\dyad\node_modules\electron-winstaller\vendor\signtool.exe
const SIGNTOOL_PATH = path.join(
__dirname,
"node_modules",
"electron-winstaller",
"vendor",
"signtool.exe",
);
/**
* Signs a Windows executable using DigiCert's signtool.
*/
function signWindowsExecutable(filePath: string): void {
const certHash = process.env.SM_CODE_SIGNING_CERT_SHA1_HASH;
if (!certHash) {
console.log(
`[postMake] SM_CODE_SIGNING_CERT_SHA1_HASH not set, skipping signing`,
);
return;
}
console.log(`[postMake] Signing: ${filePath}`);
const signParams = `/sha1 ${certHash} /tr http://timestamp.digicert.com /td SHA256 /fd SHA256`;
const cmd = `"${SIGNTOOL_PATH}" sign ${signParams} "${filePath}"`;
try {
execSync(cmd, { stdio: "inherit" });
console.log(`[postMake] Signing successful: ${filePath}`);
} catch (error) {
console.error(`[postMake] Signing failed for ${filePath}:`, error);
throw error;
}
}
// Based on https://github.com/electron/forge/blob/6b2d547a7216c30fde1e1fddd1118eee5d872945/packages/plugin/vite/src/VitePlugin.ts#L124 // Based on https://github.com/electron/forge/blob/6b2d547a7216c30fde1e1fddd1118eee5d872945/packages/plugin/vite/src/VitePlugin.ts#L124
const ignore = (file: string) => { const ignore = (file: string) => {
...@@ -94,6 +57,7 @@ const isEndToEndTestBuild = process.env.E2E_TEST_BUILD === "true"; ...@@ -94,6 +57,7 @@ const isEndToEndTestBuild = process.env.E2E_TEST_BUILD === "true";
const config: ForgeConfig = { const config: ForgeConfig = {
packagerConfig: { packagerConfig: {
windowsSign,
protocols: [ protocols: [
{ {
name: "Dyad", name: "Dyad",
...@@ -123,30 +87,11 @@ const config: ForgeConfig = { ...@@ -123,30 +87,11 @@ const config: ForgeConfig = {
extraModules: ["better-sqlite3"], extraModules: ["better-sqlite3"],
force: true, force: true,
}, },
hooks: {
postMake: async (_forgeConfig, makeResults) => {
for (const result of makeResults) {
// Only sign Windows artifacts
if (result.platform !== "win32") {
continue;
}
console.log(
`[postMake] Processing Windows artifacts for ${result.arch}`,
);
for (const artifact of result.artifacts) {
const fileName = path.basename(artifact).toLowerCase();
// Sign .exe files (the Squirrel installer and Setup.exe)
if (fileName.endsWith(".exe")) {
signWindowsExecutable(artifact);
}
}
}
return makeResults;
},
},
makers: [ makers: [
new MakerSquirrel({}), new MakerSquirrel({
// @ts-expect-error - incorrect types exported by MakerSquirrel
windowsSign,
}),
new MakerZIP({}, ["darwin"]), new MakerZIP({}, ["darwin"]),
new MakerRpm({}), new MakerRpm({}),
new MakerDeb({ new MakerDeb({
......
{ {
"name": "dyad", "name": "dyad",
"version": "0.35.0", "version": "0.36.0-beta.1",
"description": "Free, local, open-source AI app builder", "description": "Free, local, open-source AI app builder",
"keywords": [], "keywords": [],
"license": "MIT", "license": "MIT",
......
import type { WindowsSignOptions } from "@electron/packager";
import type { HASHES } from "@electron/windows-sign/dist/esm/types";
export const windowsSign: WindowsSignOptions = {
...(process.env.SIGNTOOL_PATH
? { signToolPath: process.env.SIGNTOOL_PATH }
: {}),
signWithParams: `/v /debug /dlib ${process.env.AZURE_CODE_SIGNING_DLIB} /dmdf ${process.env.AZURE_METADATA_JSON}`,
timestampServer: "http://timestamp.acs.microsoft.com",
hashes: ["sha256" as HASHES],
};
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论