"""Offline Docjig PDF integrity verifier. Requires Python and pyHanko 0.37.0.

Install while online: python -m pip install pyhanko==0.37.0
Then: python verify-docjig.py completed.pdf --certificate trusted-docjig.pem

Keep this script and the certificate obtained from a trusted Docjig source.
No network requests are made. This verifies the exact certificate, CMS signature,
SHA-256 digest and full-file coverage. It does not attest to human identity, signing
time, certificate validity dates, revocation status or long-term archival validity.
"""

import argparse
import io
import logging
from pathlib import Path

from asn1crypto import pem, x509
from pyhanko.pdf_utils.reader import PdfFileReader
from pyhanko.sign.validation import validate_pdf_signature
from pyhanko.sign.validation.status import SignatureCoverageLevel
from pyhanko_certvalidator import ValidationContext


def verify(pdf: bytes, trusted_certificate: bytes) -> bool:
    """The caller must trust the supplied certificate independently of this PDF."""
    if pem.detect(trusted_certificate):
        _, _, trusted_certificate = pem.unarmor(trusted_certificate)
    cert = x509.Certificate.load(trusted_certificate)
    reader = PdfFileReader(io.BytesIO(pdf), strict=True)
    signatures = reader.embedded_signatures
    if len(signatures) != 1 or signatures[0].field_name != "DocjigCompletionSeal":
        return False
    signature = signatures[0]
    if signature.signer_cert.dump() != cert.dump() or signature.md_algorithm != "sha256":
        return False
    # Trust here is explicit certificate pinning. The integrity verdict intentionally
    # does not depend on today's certificate expiry or an unavailable revocation server.
    status = validate_pdf_signature(
        signature,
        signer_validation_context=ValidationContext(trust_roots=[cert], allow_fetching=False),
        skip_diff=True,
    )
    return bool(
        status.intact and status.valid and status.coverage == SignatureCoverageLevel.ENTIRE_FILE
    )


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("pdf", type=Path)
    parser.add_argument("--certificate", type=Path, required=True)
    args = parser.parse_args()
    # Certificate lifecycle warnings are outside this narrowly labelled integrity check.
    logging.getLogger("pyhanko").setLevel(logging.CRITICAL)
    try:
        valid = verify(args.pdf.read_bytes(), args.certificate.read_bytes())
    except Exception:
        valid = False
    if valid:
        print("PASS: Entire PDF is intact and sealed by the supplied trusted certificate.")
        print(
            "Integrity only: no independent identity, signing-time or certificate-lifecycle claim."
        )
    else:
        print("FAIL: PDF is altered, unsigned, malformed, or uses a different certificate.")
    raise SystemExit(0 if valid else 1)


if __name__ == "__main__":
    main()
