#!/usr/bin/env python3
"""Verify a ZIP without extracting it. Requires cryptography for Ed25519.
Usage: python3 verify-dossier.py dossier.zip trusted-public-key.pem
Obtain the key independently from ScaleQuality for the attestation keyId;
a public key supplied inside the ZIP is not a trust anchor.
"""
import base64
import hashlib
import json
import sys
import zipfile
from pathlib import Path, PurePosixPath


def verify(zip_path, trusted_key_path):
    from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
    from cryptography.hazmat.primitives.serialization import load_pem_public_key

    with zipfile.ZipFile(zip_path) as bundle:
        names = [item.filename for item in bundle.infolist() if not item.is_dir()]
        if len(names) != len(set(names)):
            raise ValueError("Duplicate ZIP entry")
        manifest = json.loads(bundle.read("manifest.json"))
        attestation = json.loads(bundle.read("attestation.json"))["attestation"]
        expected = {"manifest.json", "attestation.json"}
        for entry in manifest["files"]:
            name = entry["path"]
            if name in expected or PurePosixPath(name).is_absolute() or "\\" in name or any(p in ("", ".", "..") for p in name.split("/")):
                raise ValueError("Invalid or duplicate manifest path")
            expected.add(name)
            content = bundle.read(name)
            if len(content) != entry["bytes"] or hashlib.sha256(content).hexdigest() != entry["sha256"]:
                raise ValueError("File integrity failed: " + name)
        if expected != set(names):
            raise ValueError("ZIP contains unlisted or missing files")
        # JS compares UTF-16 code units. Match that ordering for Unicode paths.
        files = sorted(({k: f[k] for k in ("path", "sha256", "bytes")} for f in manifest["files"]), key=lambda f: f["path"].encode("utf-16-be"))
        subset = {k: manifest[k] for k in ("organizationId", "generatedAt", "generatedBy")}
        subset["period"] = {k: manifest["period"][k] for k in ("days", "from", "to")}
        subset["files"] = files
        if manifest.get("schemaVersion") == 2:
            subset.update(schemaVersion=2, requestedByUserId=manifest["requestedByUserId"])
            if not any(f["path"].startswith("platform/server-evidence/") for f in files):
                raise ValueError("Missing server evidence")
        canonical = json.dumps(subset, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
        digest = hashlib.sha256(canonical.encode("utf-8")).digest()
        if digest.hex() != attestation["manifestHash"]:
            raise ValueError("Manifest digest mismatch")
        if attestation["algorithm"] != "Ed25519":
            raise ValueError("HMAC requires the authenticated platform verification API; offline verification is unavailable")
        public = load_pem_public_key(Path(trusted_key_path).read_bytes())
        if not isinstance(public, Ed25519PublicKey):
            raise ValueError("Expected a trusted Ed25519 key")
        public.verify(base64.b64decode(attestation["signature"], validate=True), digest)
        return {"integrityValid": True, "signatureValid": True, "filesVerified": len(files),
                "originScope": "SERVER_EVIDENCE_FILES_ONLY" if manifest.get("schemaVersion") == 2 else "LEGACY_CLIENT_MANIFEST"}


if __name__ == "__main__":
    try:
        if len(sys.argv) != 3:
            raise ValueError("Usage: verify-dossier.py dossier.zip trusted-public-key.pem")
        print(json.dumps(verify(sys.argv[1], sys.argv[2])))
    except Exception as error:
        print("Verification FAILED: " + str(error), file=sys.stderr)
        sys.exit(1)
