94 lines
2.6 KiB
JavaScript
94 lines
2.6 KiB
JavaScript
import { spawnSync } from "node:child_process";
|
|
import { existsSync, readdirSync } from "node:fs";
|
|
import { join, resolve } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
export const EDGE_EVER_ANDROID_SIGNER_SHA256 =
|
|
"22bf52a9501c89020f5acc966960152c826bfa64f31e578e858d088f8cd75d87";
|
|
|
|
const normalizeFingerprint = (value) =>
|
|
value.toLowerCase().replaceAll(":", "").trim();
|
|
|
|
export const verifyAndroidSignerOutput = (output) => {
|
|
const signerDigests = [
|
|
...output.matchAll(
|
|
/Signer #\d+ certificate SHA-256 digest:\s*([0-9a-f:]+)/gi,
|
|
),
|
|
].map((match) => normalizeFingerprint(match[1]));
|
|
|
|
if (signerDigests.length !== 1) {
|
|
throw new Error(
|
|
`Expected exactly one Android signer, found ${signerDigests.length}.`,
|
|
);
|
|
}
|
|
|
|
if (signerDigests[0] !== EDGE_EVER_ANDROID_SIGNER_SHA256) {
|
|
throw new Error(
|
|
`Android signer mismatch: expected ${EDGE_EVER_ANDROID_SIGNER_SHA256}, received ${signerDigests[0]}.`,
|
|
);
|
|
}
|
|
|
|
return signerDigests[0];
|
|
};
|
|
|
|
const findApkSigner = (explicitPath) => {
|
|
if (explicitPath) return explicitPath;
|
|
|
|
const sdkRoot = process.env.ANDROID_SDK_ROOT || process.env.ANDROID_HOME;
|
|
if (!sdkRoot) {
|
|
throw new Error(
|
|
"ANDROID_SDK_ROOT or ANDROID_HOME is required to locate apksigner.",
|
|
);
|
|
}
|
|
|
|
const buildToolsRoot = join(sdkRoot, "build-tools");
|
|
const versions = readdirSync(buildToolsRoot, { withFileTypes: true })
|
|
.filter((entry) => entry.isDirectory())
|
|
.map((entry) => entry.name)
|
|
.sort((left, right) =>
|
|
right.localeCompare(left, undefined, { numeric: true }),
|
|
);
|
|
|
|
for (const version of versions) {
|
|
const candidate = join(buildToolsRoot, version, "apksigner");
|
|
if (existsSync(candidate)) return candidate;
|
|
}
|
|
|
|
throw new Error(`Could not find apksigner under ${buildToolsRoot}.`);
|
|
};
|
|
|
|
const run = () => {
|
|
const [apkPath, explicitApkSigner] = process.argv.slice(2);
|
|
if (!apkPath) {
|
|
console.error(
|
|
"Usage: node scripts/verify-android-apk-signature.mjs <apk-path> [apksigner-path]",
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
const apkSigner = findApkSigner(explicitApkSigner);
|
|
const result = spawnSync(
|
|
apkSigner,
|
|
["verify", "--verbose", "--print-certs", apkPath],
|
|
{
|
|
encoding: "utf8",
|
|
},
|
|
);
|
|
const output = `${result.stdout || ""}\n${result.stderr || ""}`;
|
|
|
|
if (result.status !== 0) {
|
|
process.stderr.write(output);
|
|
throw new Error(`apksigner rejected ${apkPath}.`);
|
|
}
|
|
|
|
const signerSha256 = verifyAndroidSignerOutput(output);
|
|
process.stdout.write(`Android signer SHA-256: ${signerSha256}\n`);
|
|
};
|
|
|
|
if (
|
|
process.argv[1] &&
|
|
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
) {
|
|
run();
|
|
}
|