nomyo/doc/attestation.md

5.6 KiB

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:

Talks to a remote verification service that returns a signed JWT of appraised claims — Intel Trust Authority, 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:

pip install nomyo[attestation]
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={"<hex from gramine/build-enclave.sh>"},
    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

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: <JWT>} plus a JWKS endpoint. Veraison is an open-source option. Run that one service and point JwtQuoteVerifier at it.