# SGX enclave attestation (operator-blind verification) For the `high` and `maximum` security tiers, the NOMYO server runs CPU inference inside an Intel SGX enclave. SGX **attestation** lets the client prove — *before sending any plaintext* — that the RSA public key it is about to encrypt to was generated inside a genuine enclave running the expected (measured) code. This upgrades the channel from "encrypted in transit, operator-trusted" to "operator-blind": only the enclave can decrypt, not the host operator. This is **opt-in** and **additive**. With no attestation configured the client behaves exactly as before. ## How it works On each secure request for a tier that requires attestation, the client: 1. Fetches the server public key (`GET /pki/public_key`). 2. Fetches a hardware-signed DCAP quote (`GET /pki/attestation`). 3. **Verifies the quote** (ECDSA signature + Intel PCK chain + TCB) via a `QuoteVerifier` you provide (step 4). 4. Checks policy: `tcb_status`, pinned `MRENCLAVE` (and optionally `MRSIGNER` / `prod_id` / minimum `isv_svn`). 5. Checks the **key binding**: `SHA-512(server_pubkey_der [|| challenge])` must equal the `report_data` inside the verified quote (constant-time compare). 6. Only if all checks pass does it encrypt the payload **to that exact attested key** and send it. In `enforce` mode any failure raises and nothing is sent. ## Quote verification (`QuoteVerifier`) Verifying a DCAP quote must not be hand-rolled. The client delegates step 4 to a `QuoteVerifier`. Three ways to supply one: ### `JwtQuoteVerifier` (recommended) Talks to a remote verification service that returns a signed JWT of appraised claims — Intel Trust Authority, [Veraison](https://github.com/veraison), or a self-hosted wrapper around the Intel DCAP Quote Verification Library (QVL). The native/PCCS dependency lives on that one service, not on every client. Requires the optional dependency: ```bash pip install nomyo[attestation] ``` ```python from nomyo import SecureChatCompletion, AttestationPolicy, JwtQuoteVerifier verifier = JwtQuoteVerifier( verify_url="https://attest.example.com/appraisal/v1/attest", jwks_url="https://attest.example.com/certs", issuer="https://attest.example.com", # optional audience="nomyo-client", # optional # claim_map=... # override if your service uses different claim names ) policy = AttestationPolicy( pinned_mrenclave={""}, allowed_tcb_statuses={"UpToDate"}, enforce=False, # rollout: warn and proceed. Set True to hard-fail. ) client = SecureChatCompletion( base_url="https://api.nomyo.ai", api_key="...", attestation_policy=policy, quote_verifier=verifier, ) resp = await client.create( model="...", messages=[...], security_tier="high", ) ``` The default JWT claim names are in `DEFAULT_CLAIM_MAP`; override `claim_map` if your service differs (e.g. Veraison vs ITA). ### `CallableQuoteVerifier` — bring your own ```python from nomyo import CallableQuoteVerifier, VerifiedQuote async def my_verify(quote: bytes) -> VerifiedQuote: # call your verifier (local QVL FFI, another service, ...) return VerifiedQuote(mrenclave=..., mrsigner=..., isv_prod_id=..., isv_svn=..., tcb_status="UpToDate", report_data=...) client = SecureChatCompletion(..., attestation_policy=policy, quote_verifier=CallableQuoteVerifier(my_verify)) ``` `my_verify` may be sync or async and must raise on any verification failure. ## Policy (`AttestationPolicy`) | Field | Meaning | Default | |---|---|---| | `pinned_mrenclave` | Allowed enclave measurements (a **set** so you can roll builds) | `frozenset()` | | `pinned_mrsigner` | Allowed signer (optional) | `None` | | `pinned_prod_id` / `min_isv_svn` | Product id / minimum security version (optional) | `None` | | `allowed_tcb_statuses` | Accepted platform TCB levels | `{"UpToDate"}` | | `enforce` | Hard-fail vs warn-and-proceed | `False` | | `require_attestation_for_tier` | Tiers that must be operator-blind | `{"high","maximum"}` | | `liveness` | Use a fresh per-request challenge (see below) | `False` | Pin MRENCLAVE values as **configuration**, not constants — they change whenever the enclave build or its file paths change. Printed by `gramine/build-enclave.sh` at sign time. Do **not** silently allow `OutOfDate` or `Revoked` TCB statuses. ## Rollout vs enforce (spec §6) - **`standard` tier:** attestation not expected — proceeds normally. - **`high`/`maximum`, `enforce=False` (rollout, default):** if attestation is unavailable (server not yet under SGX) or fails, a loud warning is logged and the request proceeds. Lets you deploy clients before SGX is live everywhere. - **`high`/`maximum`, `enforce=True` (target):** unavailable or failed attestation ⇒ the request is refused with `AttestationError`, **no plaintext fallback**. ## Liveness (spec §7, optional) The default quote is bound to the key only and is cached server-side (proves binding, not recency). Set `liveness=True` to send a random 32-byte challenge per handshake; the server returns a fresh, uncached quote bound to `SHA-512(pubkey_der || challenge)`, proving the enclave is alive *now*. Liveness bypasses the client-side verification cache. ## Self-hosting the verifier A self-hosted equivalent of Intel Trust Authority is a small service that runs the Intel DCAP QVL (`libsgx_dcap_quoteverify`) with a reachable PCCS, and exposes `POST /verify {quote} -> {token: }` plus a JWKS endpoint. Veraison is an open-source option. Run that one service and point `JwtQuoteVerifier` at it.