Files
edgeever/scripts/verify-android-apk-signature.mjs
T

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();
}