Skip to content

Commit

Permalink
fix: ASAR files in extraResources are not included in integrity cal…
Browse files Browse the repository at this point in the history
…culations (#8805)
  • Loading branch information
mmaietta authored Jan 25, 2025
1 parent 4a68fd2 commit c6d6b6e
Show file tree
Hide file tree
Showing 9 changed files with 220 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/nine-cats-eat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"app-builder-lib": patch
---

fix: ASAR files in extraResources are not included in integrity calculations
60 changes: 51 additions & 9 deletions packages/app-builder-lib/src/asar/integrity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { createReadStream } from "fs"
import { readdir } from "fs/promises"
import * as path from "path"
import { readAsarHeader, NodeIntegrity } from "./asar"
import { FileMatcher } from "../fileMatcher"
import { statOrNull, walk, FilterStats, log } from "builder-util"

export interface AsarIntegrityOptions {
readonly resourcesPath: string
readonly resourcesRelativePath: string
readonly resourcesDestinationPath: string
readonly extraResourceMatchers: Array<FileMatcher> | null
}

export interface HeaderHash {
Expand All @@ -19,16 +23,54 @@ export interface AsarIntegrity {
[key: string]: HeaderHash
}

export async function computeData({ resourcesPath, resourcesRelativePath }: AsarIntegrityOptions): Promise<AsarIntegrity> {
// sort to produce constant result
const names = (await readdir(resourcesPath)).filter(it => it.endsWith(".asar")).sort()
const checksums = await BluebirdPromise.map(names, it => hashHeader(path.join(resourcesPath, it)))

const result: AsarIntegrity = {}
for (let i = 0; i < names.length; i++) {
result[path.join(resourcesRelativePath, names[i])] = checksums[i]
export async function computeData({ resourcesPath, resourcesRelativePath, resourcesDestinationPath, extraResourceMatchers }: AsarIntegrityOptions): Promise<AsarIntegrity> {
type Match = Pick<FileMatcher, "to" | "from">
type IntegrityMap = {
[filepath: string]: string
}
return result
const isAsar = (filepath: string) => filepath.endsWith(".asar")

const resources = await readdir(resourcesPath)
const resourceAsars = resources.filter(isAsar).reduce<IntegrityMap>(
(prev, filename) => ({
...prev,
[path.join(resourcesRelativePath, filename)]: path.join(resourcesPath, filename),
}),
{}
)

const extraResources = await BluebirdPromise.map(extraResourceMatchers ?? [], async (matcher: FileMatcher): Promise<Match[]> => {
const { from, to } = matcher
const stat = await statOrNull(from)
if (stat == null) {
log.warn({ from }, `file source doesn't exist`)
return []
}
if (stat.isFile()) {
return [{ from, to }]
}

if (matcher.isEmpty() || matcher.containsOnlyIgnore()) {
matcher.prependPattern("**/*")
}
const matcherFilter = matcher.createFilter()
const extraResourceMatches = await walk(matcher.from, (file: string, stats: FilterStats) => matcherFilter(file, stats) || stats.isDirectory())
return extraResourceMatches.map(from => ({ from, to: matcher.to }))
})
const extraResourceAsars = extraResources
.flat(1)
.filter(match => isAsar(match.from))
.reduce<IntegrityMap>((prev, { to, from }) => {
const prefix = path.relative(resourcesDestinationPath, to)
return {
...prev,
[path.join(resourcesRelativePath, prefix, path.basename(from))]: from,
}
}, {})

// sort to produce constant result
const allAsars = [...Object.entries(resourceAsars), ...Object.entries(extraResourceAsars)].sort(([name1], [name2]) => name1.localeCompare(name2))
return BluebirdPromise.reduce(allAsars, async (prev, [relativePathKey, from]) => ({ ...prev, [relativePathKey]: await hashHeader(from) }), {} as AsarIntegrity)
}

async function hashHeader(file: string): Promise<HeaderHash> {
Expand Down
2 changes: 1 addition & 1 deletion packages/app-builder-lib/src/platformPackager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ export abstract class PlatformPackager<DC extends PlatformSpecificBuildOptions>

let asarIntegrity: AsarIntegrity | null = null
if (!(asarOptions == null || options?.disableAsarIntegrity)) {
asarIntegrity = await computeData({ resourcesPath, resourcesRelativePath })
asarIntegrity = await computeData({ resourcesPath, resourcesRelativePath, resourcesDestinationPath: this.getResourcesDir(appOutDir), extraResourceMatchers })
}

await framework.beforeCopyExtraFiles({
Expand Down
Binary file added test/fixtures/test-app-one/build/extraAsar.asar
Binary file not shown.
88 changes: 88 additions & 0 deletions test/snapshots/mac/macPackagerTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,94 @@

exports[`electronDist 1`] = `"corrupted Electron dist"`;

exports[`multiple asar resources 1`] = `
{
"mac": [
{
"arch": "x64",
"file": "Test App ßW-1.1.0-mac.zip",
"safeArtifactName": "TestApp-1.1.0-mac.zip",
"updateInfo": {
"sha512": "@sha512",
"size": "@size",
},
},
{
"file": "Test App ßW-1.1.0-mac.zip.blockmap",
"safeArtifactName": "Test App ßW-1.1.0-mac.zip.blockmap",
"updateInfo": {
"sha512": "@sha512",
"size": "@size",
},
},
],
}
`;

exports[`multiple asar resources 2`] = `
{
"CFBundleDisplayName": "Test App ßW",
"CFBundleExecutable": "Test App ßW",
"CFBundleIconFile": "icon.icns",
"CFBundleIdentifier": "org.electron-builder.testApp",
"CFBundleInfoDictionaryVersion": "6.0",
"CFBundleName": "Test App ßW",
"CFBundlePackageType": "APPL",
"CFBundleShortVersionString": "1.1.0",
"ElectronAsarIntegrity": {
"Resources/app.asar": {
"algorithm": "SHA256",
"hash": "hash",
},
"Resources/extraAsar.asar": {
"algorithm": "SHA256",
"hash": "hash",
},
"Resources/subdir/extraAsar2.asar": {
"algorithm": "SHA256",
"hash": "hash",
},
},
"LSApplicationCategoryType": "your.app.category.type",
"LSEnvironment": {
"MallocNanoZone": "0",
},
"NSAppTransportSecurity": {
"NSAllowsLocalNetworking": true,
"NSExceptionDomains": {
"127.0.0.1": {
"NSIncludesSubdomains": false,
"NSTemporaryExceptionAllowsInsecureHTTPLoads": true,
"NSTemporaryExceptionAllowsInsecureHTTPSLoads": false,
"NSTemporaryExceptionMinimumTLSVersion": "1.0",
"NSTemporaryExceptionRequiresForwardSecrecy": false,
},
"localhost": {
"NSIncludesSubdomains": false,
"NSTemporaryExceptionAllowsInsecureHTTPLoads": true,
"NSTemporaryExceptionAllowsInsecureHTTPSLoads": false,
"NSTemporaryExceptionMinimumTLSVersion": "1.0",
"NSTemporaryExceptionRequiresForwardSecrecy": false,
},
},
},
"NSBluetoothAlwaysUsageDescription": "This app needs access to Bluetooth",
"NSBluetoothPeripheralUsageDescription": "This app needs access to Bluetooth",
"NSHighResolutionCapable": true,
"NSPrincipalClass": "AtomApplication",
"NSSupportsAutomaticGraphicsSwitching": true,
}
`;

exports[`multiple asar resources 3`] = `
[
"app.asar",
"extraAsar.asar",
"icon.icns",
"subdir/extraAsar2.asar",
]
`;

exports[`one-package 1`] = `
{
"mac": [
Expand Down
26 changes: 26 additions & 0 deletions test/snapshots/windows/winPackagerTest.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ exports[`win zip 2`] = `
"locales/",
"resources/",
"resources/app.asar",
"resources/extraAsar.asar",
"resources/subdir/",
"resources/subdir/extraAsar2.asar",
"resources.pak",
"snapshot_blob.bin",
"Test App ßW.exe",
Expand All @@ -92,6 +95,16 @@ exports[`win zip 3`] = `
"file": "resources\\app.asar",
"value": "hash",
},
{
"alg": "SHA256",
"file": "resources\\extraAsar.asar",
"value": "hash",
},
{
"alg": "SHA256",
"file": "resources\\subdir\\extraAsar2.asar",
"value": "hash",
},
]
`;

Expand All @@ -108,6 +121,9 @@ exports[`win zip 4`] = `
"locales/",
"resources/",
"resources/app.asar",
"resources/extraAsar.asar",
"resources/subdir/",
"resources/subdir/extraAsar2.asar",
"resources.pak",
"snapshot_blob.bin",
"Test App ßW.exe",
Expand All @@ -125,6 +141,16 @@ exports[`win zip 5`] = `
"file": "resources\\app.asar",
"value": "hash",
},
{
"alg": "SHA256",
"file": "resources\\extraAsar.asar",
"value": "hash",
},
{
"alg": "SHA256",
"file": "resources\\subdir\\extraAsar2.asar",
"value": "hash",
},
]
`;

Expand Down
1 change: 0 additions & 1 deletion test/src/helpers/packTester.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,6 @@ async function checkWindowsResult(packager: Packager, checkOptions: AssertPackOp
} else if (hasTarget("zip") && !(checkOptions.signed || checkOptions.signedWin)) {
return checkZipResult()
}

}

const checkResult = async (artifacts: Array<ArtifactCreated>, extension: string) => {
Expand Down
28 changes: 27 additions & 1 deletion test/src/mac/macPackagerTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Arch, createTargets, DIR_TARGET, Platform } from "electron-builder"
import * as fs from "fs/promises"
import * as path from "path"
import { assertThat } from "../helpers/fileAssert"
import { app, appThrows, assertPack, platform } from "../helpers/packTester"
import { app, appThrows, assertPack, checkDirContents, platform } from "../helpers/packTester"
import { verifySmartUnpack } from "../helpers/verifySmartUnpack"

test.ifMac.ifAll("two-package", () =>
Expand Down Expand Up @@ -141,3 +141,29 @@ test.ifMac.ifAll(
)

test.ifWinCi("Build macOS on Windows is not supported", appThrows(platform(Platform.MAC)))

test.ifAll(
"multiple asar resources",
app(
{
targets: Platform.MAC.createTarget("zip", Arch.x64),
config: {
extraResources: [
{ from: "build", to: "./", filter: "*.asar" },
{ from: "build/subdir", to: "./subdir", filter: "*.asar" },
],
electronLanguages: "en",
},
},
{
signed: true,
projectDirCreated: async projectDir => {
await fs.mkdir(path.join(projectDir, "build", "subdir"))
await fs.copyFile(path.join(projectDir, "build", "extraAsar.asar"), path.join(projectDir, "build", "subdir", "extraAsar2.asar"))
},
checkMacApp: async (appDir, info) => {
await checkDirContents(path.join(appDir, "Contents", "Resources"))
},
}
)
)
30 changes: 22 additions & 8 deletions test/src/windows/winPackagerTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ test.ifAll(
{
targets: Platform.WINDOWS.createTarget(["zip"], Arch.x64, Arch.arm64),
config: {
extraResources: [
{ from: "build", to: "./", filter: "*.asar" },
{ from: "build/subdir", to: "./subdir", filter: "*.asar" },
],
electronLanguages: "en",
downloadAlternateFFmpeg: true,
electronFuses: {
Expand All @@ -43,21 +47,31 @@ test.ifAll(
grantFileProtocolExtraPrivileges: undefined, // unsupported on current electron version in our tests
},
},
},
{
signed: false,
projectDirCreated: async projectDir => {
await fs.mkdir(path.join(projectDir, "build", "subdir"))
await fs.copyFile(path.join(projectDir, "build", "extraAsar.asar"), path.join(projectDir, "build", "subdir", "extraAsar2.asar"))
},
}
)
)

test.ifAll(
"zip artifactName",
app({
targets: Platform.WINDOWS.createTarget(["zip"], Arch.x64),
config: {
//tslint:disable-next-line:no-invalid-template-strings
artifactName: "${productName}-${version}-${os}-${arch}.${ext}",
app(
{
targets: Platform.WINDOWS.createTarget(["zip"], Arch.x64),
config: {
//tslint:disable-next-line:no-invalid-template-strings
artifactName: "${productName}-${version}-${os}-${arch}.${ext}",
},
},
}, {
signed: true
})
{
signed: true,
}
)
)

test.ifAll(
Expand Down

0 comments on commit c6d6b6e

Please # to comment.