CH-J Server Managerserver management over SSH
Menu
Published source

CH-J Server Manager

Browse directories and files for a specific application release.

Download source ZIP
CH-J / Server Manager 196 files
core-20261013T133631Z-faa4ab45100e Added: Date not recorded · Built:
Files
macNetworkIdentity.js Added: Date not recorded 6.3 KB · 133 lines
Download file
1"use strict";
3const crypto = require("node:crypto");
4const fs = require("node:fs");
5const path = require("node:path");
6const { execFileSync } = require("node:child_process");
8// TN3179 / TN3178: Local Network privacy uses the executable's LC_UUID.
9// Electron's prebuilt launchers otherwise share it with unrelated applications.
10// Only change launcher UUIDs before signing; leave Electron Framework (and its
11// upstream debugging symbols), native runtimes and installed apps untouched.
12function uuidCommands(bytes) {
13 const entries = [];
14 const range = (offset, size, end = bytes.length) => {
15 if (!Number.isSafeInteger(offset) || !Number.isSafeInteger(size) || offset < 0 || size < 0 || offset + size > end) {
16 throw new Error("Invalid Mach-O bounds.");
17 }
18 };
19 const thin = (start, size) => {
20 range(start, size);
21 range(start, 28, start + size);
22 const magic = bytes.readUInt32BE(start);
23 const little = magic === 0xcefaedfe || magic === 0xcffaedfe;
24 if (![0xcefaedfe, 0xcffaedfe, 0xfeedface, 0xfeedfacf].includes(magic)) throw new Error("Unsupported Mach-O executable.");
25 const read = (offset) => little ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
26 const headerSize = [0xcffaedfe, 0xfeedfacf].includes(magic) ? 32 : 28;
27 const count = read(start + 16);
28 const commandsSize = read(start + 20);
29 range(start, headerSize + commandsSize, start + size);
30 const end = start + headerSize + commandsSize;
31 let cursor = start + headerSize;
32 let found = false;
33 for (let i = 0; i < count; i++) {
34 range(cursor, 8, end);
35 const command = read(cursor);
36 const commandSize = read(cursor + 4);
37 if (commandSize < 8 || commandSize % 4) throw new Error("Invalid Mach-O load command.");
38 range(cursor, commandSize, end);
39 if (command === 0x1b) {
40 if (found || commandSize !== 24) throw new Error("Invalid or duplicate LC_UUID.");
41 entries.push({ offset: cursor + 8, cpuType: read(start + 4), cpuSubtype: read(start + 8) });
42 found = true;
43 }
44 cursor += commandSize;
45 }
46 if (cursor !== end || !found) throw new Error("Mach-O executable must have exactly one LC_UUID per architecture.");
47 };
48 range(0, 4);
49 const magic = bytes.readUInt32BE(0);
50 if ([0xcafebabe, 0xbebafeca, 0xcafebabf, 0xbfbafeca].includes(magic)) {
51 range(0, 8);
52 const little = [0xbebafeca, 0xbfbafeca].includes(magic);
53 const fat64 = [0xcafebabf, 0xbfbafeca].includes(magic);
54 const read = (offset) => little ? bytes.readUInt32LE(offset) : bytes.readUInt32BE(offset);
55 const read64 = (offset) => Number(little ? bytes.readBigUInt64LE(offset) : bytes.readBigUInt64BE(offset));
56 const count = read(4);
57 const stride = fat64 ? 32 : 20;
58 range(8, count * stride);
59 if (!count) throw new Error("Empty universal Mach-O executable.");
60 let previousEnd = 8 + count * stride;
61 for (let i = 0; i < count; i++) {
62 const cursor = 8 + i * stride;
63 const offset = fat64 ? read64(cursor + 8) : read(cursor + 8);
64 const size = fat64 ? read64(cursor + 16) : read(cursor + 12);
65 if (offset < previousEnd) throw new Error("Overlapping Mach-O architectures.");
66 thin(offset, size);
67 previousEnd = offset + size;
68 }
69 } else thin(0, bytes.length);
70 return entries;
73function launcherUuid(identity, entry) {
74 const uuid = crypto.createHash("sha256")
75 .update(JSON.stringify(["chj-local-network-v1", identity, entry.cpuType, entry.cpuSubtype]))
76 .digest().subarray(0, 16);
77 uuid[6] = (uuid[6] & 0x0f) | 0x80; // RFC 9562 custom UUID (version 8).
78 uuid[8] = (uuid[8] & 0x3f) | 0x80;
79 return uuid;
82function readPlist(bundlePath) {
83 return JSON.parse(execFileSync("/usr/bin/plutil", ["-convert", "json", "-o", "-", path.join(bundlePath, "Contents", "Info.plist")], { encoding: "utf8" }));
86function processNetworkIdentity(appPath, expected, patch) {
87 const info = readPlist(appPath);
88 if (info.CFBundleIdentifier !== expected.appId) throw new Error("macOS bundle ID does not match the application identity.");
89 if (info.CFBundleShortVersionString !== expected.version) throw new Error("macOS application version differs from package.json.");
90 if (!String(info.NSLocalNetworkUsageDescription || "").trim()) throw new Error("macOS app lacks NSLocalNetworkUsageDescription.");
91 const payload = crypto.createHash("sha256").update(fs.readFileSync(path.join(appPath, "Contents", "Resources", "app.asar"))).digest("hex");
92 const frameworks = path.join(appPath, "Contents", "Frameworks");
93 const bundles = [appPath, ...fs.readdirSync(frameworks, { withFileTypes: true })
94 .filter((entry) => entry.isDirectory() && entry.name.endsWith(".app"))
95 .map((entry) => path.join(frameworks, entry.name))];
96 const uuids = new Set();
97 const changes = [];
98 const result = [];
99 for (const bundle of bundles) {
100 const plist = bundle === appPath ? info : readPlist(bundle);
101 const name = plist.CFBundleExecutable;
102 if (!name || path.basename(name) !== name) throw new Error("Invalid CFBundleExecutable.");
103 const executable = path.join(bundle, "Contents", "MacOS", name);
104 const relativePath = path.relative(appPath, executable).split(path.sep).join("/");
105 const bytes = fs.readFileSync(executable);
106 const identity = [info.CFBundleIdentifier, plist.CFBundleIdentifier, relativePath, payload];
107 let changed = false;
108 for (const entry of uuidCommands(bytes)) {
109 const uuid = launcherUuid(identity, entry);
110 const actual = bytes.subarray(entry.offset, entry.offset + 16);
111 if (!actual.equals(uuid)) {
112 if (!patch) throw new Error(`macOS launcher has a shared or unexpected LC_UUID: ${relativePath}`);
113 uuid.copy(bytes, entry.offset);
114 changed = true;
115 }
116 const hex = uuid.toString("hex");
117 if (uuids.has(hex)) throw new Error("Duplicate macOS launcher UUID.");
118 uuids.add(hex);
119 result.push({ executable: relativePath, uuid: hex });
120 }
121 if (changed) changes.push({ executable, bytes });
122 }
123 // Validate every architecture and bundle before changing any output file.
124 for (const { executable, bytes } of changes) fs.writeFileSync(executable, bytes);
125 return result;
128module.exports = {
129 uuidCommands,
130 launcherUuid,
131 prepareMacNetworkIdentity: (appPath, expected) => processNetworkIdentity(appPath, expected, true),
132 verifyMacNetworkIdentity: (appPath, expected) => processNetworkIdentity(appPath, expected, false)
133};
SHA-256da3c63c71d78c38e8fdcdbcec7dd018acbccdd5c81dc032b031ce24178ce2f9f

CH-J Proprietary Software License 1.14 · Source is provided under the CH-J Proprietary Software License 1.14. Its availability does not change the license terms or grant additional rights.

Archive SHA-256de757268dbc4f0226549f8b4f41f9b7e585c856ee97fa0d43b17a0e95c281449