import argparse
import hashlib
import json
from pathlib import Path
import re
import struct
import subprocess
import tarfile
import tempfile
ROOT = Path(__file__).resolve().parent.parent
TARGETS = {
"darwin-arm64": "aarch64-apple-darwin",
"darwin-x64": "x86_64-apple-darwin",
"linux-arm64": "aarch64-unknown-linux-gnu",
"linux-x64": "x86_64-unknown-linux-gnu",
"win32-x64": "x86_64-pc-windows-msvc",
}
def require(condition, message):
if not condition:
raise ValueError(message)
def files_in(path):
with tarfile.open(path, "r:gz") as archive:
files = {}
for member in archive.getmembers():
require(member.name not in files, f"duplicate archive member: {member.name}")
require(member.isfile(), f"non-regular archive member: {member.name}")
require(member.name.startswith("package/") and
all(part not in ("", ".", "..") for part in member.name.split("/")),
f"unsafe archive member: {member.name}")
files[member.name] = (archive.extractfile(member).read(), member.mode)
return files
def verify_signature(binary, signature, keys):
for key in keys:
result = subprocess.run(
["minisign", "-V", "-P", key, "-m", str(binary), "-x", str(signature)],
capture_output=True, text=True, check=False,
)
if result.returncode == 0:
return
raise ValueError(f"signature not trusted: {binary.name}")
def verify_format(data, platform):
os_name, arch = platform.split("-")
if os_name == "linux":
require(len(data) >= 20 and data[:6] == b"\x7fELF\x02\x01" and
struct.unpack_from("<H", data, 18)[0] == (183 if arch == "arm64" else 62),
f"wrong ELF target: {platform}")
elif os_name == "darwin":
require(len(data) >= 8 and data[:4] == b"\xcf\xfa\xed\xfe" and
struct.unpack_from("<I", data, 4)[0] == (0x100000C if arch == "arm64" else 0x1000007),
f"wrong Mach-O target: {platform}")
else:
require(len(data) >= 64 and data[:2] == b"MZ", "missing PE header")
offset = struct.unpack_from("<I", data, 60)[0]
require(len(data) >= offset + 6 and data[offset:offset + 6] == b"PE\x00\x00\x64\x86",
"wrong PE target: win32-x64")
def verify(artifacts, packages, version, binary_sha256, trust_root):
require(re.fullmatch(r"[0-9a-f]{64}", binary_sha256), "missing readiness binary SHA-256")
keys = [line.strip() for line in trust_root.read_text().splitlines()
if line.strip() and not line.lstrip().startswith("#")]
require(keys, "no committed signing trust roots")
expected_tarballs = {f"openlatch-client-{platform}-{version}.tgz" for platform in TARGETS}
expected_tarballs.add(f"openlatch-client-{version}.tgz")
require({p.name for p in packages.iterdir()} == expected_tarballs, "unexpected or missing tarballs")
with tempfile.TemporaryDirectory(prefix="native-package-check-") as scratch:
for platform, target in TARGETS.items():
files = files_in(packages / f"openlatch-client-{platform}-{version}.tgz")
manifest = json.loads(files["package/package.json"][0])
os_name, arch = platform.split("-")
require(manifest["name"] == f"@openlatch/client-{platform}" and
manifest["version"] == version and manifest["os"] == [os_name] and
manifest["cpu"] == [arch], f"wrong package identity: {platform}")
extension = ".exe" if os_name == "win32" else ""
names = [f"{binary}{extension}" for binary in ("openlatch", "openlatch-hook")]
expected = {"package/package.json", "package/LICENSE"}
expected.update(f"package/{name}{suffix}" for name in names for suffix in ("", ".minisig"))
require(set(files) == expected, f"missing or unexpected native files: {platform}")
require(files["package/LICENSE"][0], f"empty license: {platform}")
for name in names:
data, mode = files[f"package/{name}"]
require(mode & 0o111, f"binary is not executable: {platform}/{name}")
verify_format(data, platform)
original_name = name.removesuffix(extension) if extension else name
original = artifacts / f"binaries-{target}" / f"{original_name}-{target}{extension}"
require(data == original.read_bytes(), f"artifact mismatch: {platform}/{name}")
signature = files[f"package/{name}.minisig"][0]
require(signature == Path(f"{original}.minisig").read_bytes(),
f"signature artifact mismatch: {platform}/{name}")
binary = Path(scratch) / name
binary.write_bytes(data)
sig_path = Path(f"{binary}.minisig")
sig_path.write_bytes(signature)
verify_signature(binary, sig_path, keys)
if platform == "linux-x64" and name == "openlatch":
require(hashlib.sha256(data).hexdigest() == binary_sha256,
"Linux package differs from readiness-tested binary")
wrapper = files_in(packages / f"openlatch-client-{version}.tgz")
manifest = json.loads(wrapper["package/package.json"][0])
require(manifest["name"] == "@openlatch/client" and manifest["version"] == version,
"wrong wrapper identity")
require(manifest["optionalDependencies"] == {
f"@openlatch/client-{platform}": version for platform in TARGETS
}, "wrapper must resolve exactly the verified native package versions")
require(manifest["bin"]["openlatch"] == "lib/index.js" and
manifest["bin"]["openlatch-hook"] == "lib/openlatch-hook.js", "wrong wrapper entrypoints")
for name in ["LICENSE", "README.md", "lib/index.js", "lib/openlatch-hook.js",
"lib/resolve-binary.js", "lib/postinstall.js"]:
require(wrapper[f"package/{name}"][0], f"empty wrapper file: {name}")
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--artifacts", type=Path, required=True)
parser.add_argument("--packages", type=Path, required=True)
parser.add_argument("--version", required=True)
parser.add_argument("--binary-sha256", required=True)
args = parser.parse_args()
try:
verify(args.artifacts, args.packages, args.version, args.binary_sha256,
ROOT / "signing/openlatch.pub")
except (OSError, ValueError, KeyError, TypeError, tarfile.TarError) as exc:
parser.exit(1, f"Native package verification failed: {exc}\n")
print("Verified five signed native packages and their exact-version wrapper.")
if __name__ == "__main__":
main()