Skip to content

Commit 9263ba2

Browse files
beyondkmpmmaietta
authored andcommitted
fix: generate stubExecutableExe and sign it (electron-userland#8959)
fix electron-userland#8952 **Root Cause** when createExecutableStubForExe is executed, WriteZipToSetup writes information to the file, essentially creating a new file, which invalidates the original signature. ![Image](https://github.com/user-attachments/assets/9f5b0f4b-8f50-4373-8dad-45b00a730ee1) https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/src/Update/Program.cs#L633-L647 ![Image](https://github.com/user-attachments/assets/f0ea1e22-9727-4599-a836-1fa1f3c77dcc) **How to fix** Apply a patch to the Squirrel Windows source code(Squirrel/Squirrel.Windows#1903). For the existing stub exe files, don't generate them anymore. Then, a new stub exe can be generated in Electron Builder and signed. --------- Co-authored-by: Mike Maietta <[email protected]>
1 parent e82c30c commit 9263ba2

File tree

3 files changed

+56
-18
lines changed

3 files changed

+56
-18
lines changed

.changeset/lovely-brooms-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"electron-builder-squirrel-windows": patch
3+
---
4+
5+
fix: generate stubExecutableExe and sign it for squirrel.windows using new electron-builder-binaries asset

packages/electron-builder-squirrel-windows/src/SquirrelWindowsTarget.ts

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import { InvalidConfigurationError, log, isEmptyOrSpaces } from "builder-util"
2+
import { execWine } from "app-builder-lib/out/wine"
3+
import { getBinFromUrl } from "app-builder-lib/out/binDownload"
24
import { sanitizeFileName } from "builder-util/out/filename"
35
import { Arch, getArchSuffix, SquirrelWindowsOptions, Target, WinPackager } from "app-builder-lib"
46
import * as path from "path"
@@ -20,31 +22,56 @@ export default class SquirrelWindowsTarget extends Target {
2022
}
2123

2224
private async prepareSignedVendorDirectory(): Promise<string> {
23-
// If not specified will use the Squirrel.Windows that is shipped with electron-installer(https://github.com/electron/windows-installer/tree/main/vendor)
24-
// After https://github.com/electron-userland/electron-builder-binaries/pull/56 merged, will add `electron-builder-binaries` to get the latest version of squirrel.
25-
let vendorDirectory = this.options.customSquirrelVendorDir || path.join(require.resolve("electron-winstaller/package.json"), "..", "vendor")
26-
if (isEmptyOrSpaces(vendorDirectory) || !fs.existsSync(vendorDirectory)) {
27-
log.warn({ vendorDirectory }, "unable to access Squirrel.Windows vendor directory, falling back to default electron-winstaller")
28-
vendorDirectory = path.join(require.resolve("electron-winstaller/package.json"), "..", "vendor")
29-
}
30-
25+
const customSquirrelVendorDirectory = this.options.customSquirrelVendorDir
3126
const tmpVendorDirectory = await this.packager.info.tempDirManager.createTempDir({ prefix: "squirrel-windows-vendor" })
32-
// Copy entire vendor directory to temp directory
33-
await fs.promises.cp(vendorDirectory, tmpVendorDirectory, { recursive: true })
34-
log.debug({ from: vendorDirectory, to: tmpVendorDirectory }, "copied vendor directory")
3527

36-
const files = await fs.promises.readdir(tmpVendorDirectory)
37-
for (const file of files) {
38-
if (["Squirrel.exe", "StubExecutable.exe"].includes(file)) {
39-
const filePath = path.join(tmpVendorDirectory, file)
40-
log.debug({ file: filePath }, "signing vendor executable")
41-
await this.packager.sign(filePath)
42-
}
28+
if (isEmptyOrSpaces(customSquirrelVendorDirectory) || !fs.existsSync(customSquirrelVendorDirectory)) {
29+
log.warn({ customSquirrelVendorDirectory: customSquirrelVendorDirectory }, "unable to access custom Squirrel.Windows vendor directory, falling back to default vendor ")
30+
const windowInstallerPackage = require.resolve("electron-winstaller/package.json")
31+
const vendorDirectory = path.join(path.dirname(windowInstallerPackage), "vendor")
32+
33+
const squirrelBin = await getBinFromUrl(
34+
35+
"squirrel.windows-2.0.1-patched.7z",
36+
"DWijIRRElidu/Rq0yegAKqo2g6aVJUPvcRyvkzUoBPbRasIk61P6xY2fBMdXw6wT17md7NzrTI9/zA1wT9vEqg=="
37+
)
38+
39+
await fs.promises.cp(vendorDirectory, tmpVendorDirectory, { recursive: true })
40+
// copy the patched squirrel to tmp vendor directory
41+
await fs.promises.cp(path.join(squirrelBin, "electron-winstaller", "vendor"), tmpVendorDirectory, { recursive: true })
42+
} else {
43+
// copy the custom squirrel vendor directory to tmp vendor directory
44+
await fs.promises.cp(customSquirrelVendorDirectory, tmpVendorDirectory, { recursive: true })
4345
}
4446

47+
const files = await fs.promises.readdir(tmpVendorDirectory)
48+
const squirrelExe = files.find(f => f === "Squirrel.exe")
49+
if (squirrelExe) {
50+
const filePath = path.join(tmpVendorDirectory, squirrelExe)
51+
log.debug({ file: filePath }, "signing vendor executable")
52+
await this.packager.sign(filePath)
53+
} else {
54+
log.warn("Squirrel.exe not found in vendor directory, skipping signing")
55+
}
4556
return tmpVendorDirectory
4657
}
4758

59+
private async generateStubExecutableExe(appOutDir: string, vendorDir: string) {
60+
const files = await fs.promises.readdir(appOutDir, { withFileTypes: true })
61+
const appExe = files.find(f => f.name === `${this.exeName}.exe`)
62+
if (!appExe) {
63+
throw new Error(`App executable not found in app directory: ${appOutDir}`)
64+
}
65+
66+
const filePath = path.join(appOutDir, appExe.name)
67+
const stubExePath = path.join(appOutDir, `${this.exeName}_ExecutionStub.exe`)
68+
await fs.promises.copyFile(path.join(vendorDir, "StubExecutable.exe"), stubExePath)
69+
await execWine(path.join(vendorDir, "WriteZipToSetup.exe"), null, ["--copy-stub-resources", filePath, stubExePath])
70+
await this.packager.sign(stubExePath)
71+
log.debug({ file: filePath }, "signing app executable")
72+
await this.packager.sign(filePath)
73+
}
74+
4875
async build(appOutDir: string, arch: Arch) {
4976
const packager = this.packager
5077
const version = packager.appInfo.version
@@ -62,6 +89,7 @@ export default class SquirrelWindowsTarget extends Target {
6289
arch,
6390
})
6491
const distOptions = await this.computeEffectiveDistOptions(appOutDir, installerOutDir, setupFile)
92+
await this.generateStubExecutableExe(appOutDir, distOptions.vendorDirectory!)
6593
await createWindowsInstaller(distOptions)
6694

6795
await packager.signAndEditResources(artifactPath, arch, installerOutDir)
@@ -120,6 +148,10 @@ export default class SquirrelWindowsTarget extends Target {
120148
return this.options.name || this.packager.appInfo.name
121149
}
122150

151+
private get exeName() {
152+
return this.packager.appInfo.productFilename || this.options.name || this.packager.appInfo.productName
153+
}
154+
123155
private select7zipArch(vendorDirectory: string) {
124156
// https://github.com/electron/windows-installer/blob/main/script/select-7z-arch.js
125157
// Even if we're cross-compiling for a different arch like arm64,

packages/electron-builder-squirrel-windows/template.nuspectemplate

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
<file src="*.pak" target="lib\net45" />
2020
<file src="*.exe.config" target="lib\net45" />
2121
<file src="*.exe.sig" target="lib\net45" />
22+
<file src="*_ExecutionStub.exe" target="lib\net45" />
2223
<file src="icudtl.dat" target="lib\net45\icudtl.dat" />
2324
<file src="Squirrel.exe" target="lib\net45\squirrel.exe" />
2425
<file src="LICENSE.electron.txt" target="lib\net45\LICENSE.electron.txt" />

0 commit comments

Comments
 (0)