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:
- Fetches the server public key (
GET /pki/public_key). - Fetches a hardware-signed DCAP quote (
GET /pki/attestation). - Verifies the quote (ECDSA signature + Intel PCK chain + TCB) via a
QuoteVerifieryou provide (step 4). - Checks policy:
tcb_status, pinnedMRENCLAVE(and optionallyMRSIGNER/prod_id/ minimumisv_svn). - Checks the key binding:
SHA-512(server_pubkey_der [|| challenge])must equal thereport_datainside the verified quote (constant-time compare). - Only if all checks pass does it encrypt the payload to that exact attested
key and send it. In
enforcemode 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, 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)
standardtier: 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 withAttestationError, 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.