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
1 change: 1 addition & 0 deletions src/asar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ export function extractAll(archivePath: string, dest: string) {
}
}

export { getArchiveIntegrity, getAsarIntegrityInfo } from './integrity.js';
export { uncacheAll, uncacheFilesystem as uncache, FileRecord, DirectoryRecord } from './disk.js';
export {
calculateIntegrityDigestForApp,
Expand Down
4 changes: 2 additions & 2 deletions src/integrity-digest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import crypto from 'node:crypto';
import plist from 'plist';

import { wrappedFs as fs } from './wrapped-fs.js';
import { FileRecord } from './disk.js';
import { ArchiveIntegrity } from './integrity.js';

// Integrity digest type definitions

Expand Down Expand Up @@ -34,7 +34,7 @@ type AnyIntegrityDigest = DigestByVersion[keyof DigestByVersion];

// Integrity digest calculation functions

type AsarIntegrity = Record<string, Pick<FileRecord['integrity'], 'algorithm' | 'hash'>>;
type AsarIntegrity = Record<string, ArchiveIntegrity>;

function isValidAsarIntegrity(asarIntegrity: any): asarIntegrity is AsarIntegrity {
if (typeof asarIntegrity !== 'object' || asarIntegrity === null) return false;
Expand Down
57 changes: 57 additions & 0 deletions src/integrity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import crypto from 'node:crypto';
import stream from 'node:stream';
import streamPromises from 'node:stream/promises';
import { FileRecord, getRawHeader } from './asar.js';

const ALGORITHM = 'SHA256';
// 4MB default block size
Expand Down Expand Up @@ -64,3 +65,59 @@ export async function getFileIntegrity(
blocks: blockHashes,
};
}

export type ArchiveIntegrity = Pick<FileRecord['integrity'], 'algorithm' | 'hash'>;
Copy link
Contributor Author

@nmggithub nmggithub Mar 18, 2026

Choose a reason for hiding this comment

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

I stole this from @electron/packager's implementation. I wonder if its the best way to type this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems, even with Windows' differing implementation, we still use this as the canonical shape and simply convert it at build time in @electron/packager. Not sure if we want to expose the Windows shape as well or just have consumers do this conversion themselves for Windows, when needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ended up exposing the Windows resource records and macOS Info.plist dictionaries seperately.


export function getArchiveIntegrity(archivePath: string): ArchiveIntegrity {
const { headerString } = getRawHeader(archivePath);
return {
algorithm: 'SHA256',
hash: crypto.createHash('SHA256').update(headerString).digest('hex'),
};
}

// To be inserted into Info.plist of the app.
export type AsarIntegrityInfoMacOS = Record<string, ArchiveIntegrity>;

// To be added as a resource to the app.
export type AsarIntegrityInfoWindows = { resourceType: 'Integrity', resourceName: 'ElectronAsar', resourceData: Buffer };
export type AsarIntegrityInfoWindowsFiles = {file: string, alg: string, value: string}[];

type ASARIntegrityPlatformInfoMap = {
macos: AsarIntegrityInfoMacOS;
windows: AsarIntegrityInfoWindows;
};

export function getAsarIntegrityInfo(
files: { relativePath: string; fullPath: string }[],
platform: 'macos',
): AsarIntegrityInfoMacOS;
export function getAsarIntegrityInfo(
files: { relativePath: string; fullPath: string }[],
platform: 'windows',
): AsarIntegrityInfoWindows;
export function getAsarIntegrityInfo(
files: { relativePath: string; fullPath: string }[],
platform: keyof ASARIntegrityPlatformInfoMap,
) {
switch (platform) {
case 'macos':
return Object.fromEntries(
files.map((file) => [file.relativePath, getArchiveIntegrity(file.fullPath)]),
);
case 'windows': {
const filesJson: AsarIntegrityInfoWindowsFiles = files.map((file) => ({
file: file.relativePath,
alg: 'SHA256',
value: getArchiveIntegrity(file.fullPath).hash,
}));
return {
resourceType: 'Integrity',
resourceName: 'ElectronAsar',
resourceData: Buffer.from(JSON.stringify(filesJson), 'utf-8'),
} as AsarIntegrityInfoWindows;
}
default:
throw new Error(`Invalid platform: ${platform}`);
}
}