#!/usr/bin/env python3
"""Independent check of a Clairwave cloud workspace (Intel TDX confidential VM) — for the customer's own IT (2026-10-23).

    pip install cryptography
    python verify_workspace.py <id>.ws.clairwave.com --manifest <version>.json

Needs: this file + tdx_quote.py next to it (both short, read them), the release manifest of the image version the
workspace shows (published by Clairwave per release), and a computer whose public IP is the one that launched the
workspace (the VM accepts no other). It does not need a Clairwave account or our servers.

What is checked, in order:
  1. TLS: connect to the workspace with normal certificate validation (public CA) and keep the server's public key.
  2. Fresh quote: ask THAT connection for /attestation?nonce=<random>; the VM asks the CPU for a TDX quote.
  3. Hardware signature: the quote chains to the Intel SGX Root CA (pinned in tdx_quote.py) — made by a genuine
     Intel TDX CPU — and the VM is not a debug VM (whose memory the host could read).
  4. Binding: the quote's report data = SHA-512(prefix + your nonce + SHA-256(the TLS key you are connected to) +
     SHA-256(controller key)) — so the quote is fresh, and the TLS key that encrypts your session lives inside that VM.
  5. Image: RTMR1/RTMR2 (boot chain: the unified kernel image, whose command line carries the dm-verity root hash of
     the whole read-only disk) equal the manifest — i.e. the VM runs exactly that Clairwave image.
  6. Firmware: Google publishes a signed endorsement for its TDX firmware measurement (MRTD); we look it up.
Not checked (prototype): the platform's TCB level against Intel's current collateral (out-of-date microcode would
still pass) and the signature on Google's firmware endorsement — the values are printed for a manual check.
"""
import argparse
import base64
import hashlib
import http.client
import json
import os
import secrets
import socket
import ssl
import sys
import urllib.request

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import tdx_quote  # noqa: E402

from cryptography import x509  # noqa: E402
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat  # noqa: E402


def main():
    ap = argparse.ArgumentParser(description=__doc__.split("\n")[0])
    ap.add_argument("host", help="<id>.ws.clairwave.com")
    ap.add_argument("--manifest", required=True, help="release manifest JSON (file path or https URL)")
    a = ap.parse_args()
    src = a.manifest
    # a named user agent: Cloudflare in front of www rejects Python's default one (403)
    req = urllib.request.Request(src, headers={"User-Agent": "clairwave-verify/1.0 (+https://www.clairwave.com/workspace-verify/)"})
    manifest = json.load(urllib.request.urlopen(req, timeout=20) if src.startswith("https://") else open(src))
    ok_all = True

    def res(ok, what, detail=""):
        nonlocal ok_all
        ok_all &= bool(ok)
        print(f"  [{'PASS' if ok else 'FAIL'}] {what}" + (f" — {detail}" if detail else ""))

    print(f"Clairwave workspace verification: {a.host}  (image manifest {manifest.get('version')}, {manifest.get('image')})")
    ctx = ssl.create_default_context()
    sock = ctx.wrap_socket(socket.create_connection((a.host, 443), timeout=20), server_hostname=a.host)
    der = sock.getpeercert(binary_form=True)
    spki = x509.load_der_x509_certificate(der).public_key().public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
    res(True, "TLS certificate valid for the name", f"server key SHA-256 {hashlib.sha256(spki).hexdigest()[:16]}…")
    nonce = secrets.token_hex(16)
    conn = http.client.HTTPSConnection(a.host, 443, context=ctx)
    conn.sock = sock      # the quote must come over the very connection whose key we just recorded
    conn.request("GET", f"/attestation?nonce={nonce}")
    r = conn.getresponse()
    if r.status != 200:
        res(False, "attestation endpoint", f"HTTP {r.status}")
        return 1
    j = json.loads(r.read())
    q = base64.b64decode(j["quote"])
    try:
        f = tdx_quote.verify(q)
        res(True, "quote signed by a genuine Intel TDX CPU", f["pck_subject"])
        res(True, "not a debug TD", f"td_attributes {f['td_attributes'].hex()}")
    except tdx_quote.QuoteError as e:
        res(False, "quote signature chain", str(e))
        return 1
    want = tdx_quote.report_data(nonce, spki, ctl_pub_sha256=bytes.fromhex(j["ctl_pub_sha256"]))
    res(f["report_data"] == want, "quote is fresh and bound to THIS TLS connection's key",
        "report data matches nonce + TLS key" if f["report_data"] == want else "mismatch: the TLS key is not the VM's")
    for i in (1, 2):
        got, exp = f["rtmr"][i].hex(), (manifest.get(f"rtmr{i}") or "").lower()
        res(got == exp, f"RTMR{i} = image {manifest.get('version')}", got[:24] + "…")
    res(j.get("image", {}).get("roothash") == manifest.get("roothash"), "dm-verity root hash reported by the VM",
        str(j.get("image", {}).get("roothash", ""))[:24] + "…")
    mrtd = f["mrtd"].hex()
    try:
        code = urllib.request.urlopen(f"https://storage.googleapis.com/gce_tcb_integrity/ovmf_x64_csm/tdx/{mrtd}.binarypb",
                                      timeout=20).status
    except Exception as e:
        code = getattr(e, "code", str(e))
    res(code == 200, "firmware (MRTD) has a Google endorsement", f"MRTD {mrtd[:24]}… ({code})")
    print(f"  [info] TEE TCB SVN {f['tee_tcb_svn'].hex()}  MRSEAM {f['mrseam'].hex()[:24]}…  quote v{f['version']}")
    print(f"  [info] controller key SHA-256 {j['ctl_pub_sha256'][:16]}…  workspace {j.get('workspace')}  VM time {j.get('time')}")
    print("RESULT:", "VERIFIED" if ok_all else "NOT VERIFIED")
    return 0 if ok_all else 1


if __name__ == "__main__":
    sys.exit(main())
