"""Python 3.11+; standard library only. Run: python3 verify.py document.pdf. Requires CHECKTHISFILE_BASE_URL, CHECKTHISFILE_API_KEY, CHECKTHISFILE_CERTIFICATE_ID. Legacy CONSTA_* names remain supported. Exit 0 verified, 2 negative result, 1 operational/configuration error. """ import hashlib import json import os import re import sys import urllib.error import urllib.parse import urllib.request class NoRedirect(urllib.request.HTTPRedirectHandler): def redirect_request(self, req, fp, code, msg, headers, newurl): raise RuntimeError("Redirect refused") def verify(file_path): def configured(name): value = os.environ.get("CHECKTHISFILE_" + name) or os.environ.get("CONSTA_" + name) if not value: raise KeyError(name) return value base = configured("BASE_URL").rstrip("/") url = urllib.parse.urlsplit(base) local = url.hostname in ("localhost", "127.0.0.1", "::1") if (url.scheme != "https" and not (url.scheme == "http" and local)) or url.username or url.password or url.path or url.query or url.fragment or not url.hostname: raise ValueError("Expected HTTPS origin; loopback HTTP allowed locally") key = configured("API_KEY") public_id = configured("CERTIFICATE_ID") if not re.fullmatch(r"(?:ctf_live_|consta_live_)[A-Za-z0-9_-]{43}", key) or not re.fullmatch(r"ew_[a-z0-9_-]{14}", public_id): raise ValueError("Invalid key or certificate ID") with open(file_path, "rb") as source: digest = hashlib.file_digest(source, "sha256").hexdigest() request = urllib.request.Request(base + "/api/v1/verify", data=json.dumps({"publicId": public_id, "sha256": digest}).encode(), headers={"Authorization": "Bearer " + key, "Content-Type": "application/json", "Accept": "application/json"}) opener = urllib.request.build_opener(NoRedirect()) try: response = opener.open(request, timeout=15) except urllib.error.HTTPError as error: response = error with response: raw = response.read(65537) if len(raw) > 65536: raise RuntimeError("Response too large") payload = json.loads(raw) if not isinstance(payload, dict): raise RuntimeError("Invalid response envelope") data = payload.get("data") missing = response.status == 404 and isinstance(data, dict) and data.get("reason") == "CERTIFICATE_NOT_FOUND" and payload.get("error") is None if response.status != 200 and not missing: raise RuntimeError("CheckThisFile HTTP " + str(response.status)) if payload.get("error") is not None or not isinstance(data, dict) or data.get("publicId") != public_id or type(data.get("verified")) is not bool: raise RuntimeError("Invalid verification response") expected = data.get("hashMatches") is True and data.get("signatureValid") is True and data.get("certificateCurrent") is True and data.get("certificateStatus") == "valid" and data.get("reason") == "MATCH" if data["verified"] != expected or data.get("documentUploaded") is not False or data.get("comparison") != "sha256_exact_bytes": raise RuntimeError("Inconsistent verification response") return data if __name__ == "__main__": try: if len(sys.argv) != 2: raise ValueError("Expected one file path") result = verify(sys.argv[1]) print(json.dumps({"verified": result["verified"], "reason": result["reason"]})) sys.exit(0 if result["verified"] else 2) except (KeyError, ValueError, RuntimeError, OSError, urllib.error.URLError): print("CheckThisFile verification failed: check configuration, access and network", file=sys.stderr) sys.exit(1)