135 lines
5.6 KiB
Markdown
135 lines
5.6 KiB
Markdown
|
|
# 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={"<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
|
||
|
|
|
||
|
|
```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: <JWT>}` plus a JWKS endpoint. Veraison is an
|
||
|
|
open-source option. Run that one service and point `JwtQuoteVerifier` at it.
|