feat: prep SGX attestation
This commit is contained in:
parent
0547a6ab3e
commit
7c06b960b1
7 changed files with 680 additions and 12 deletions
134
doc/attestation.md
Normal file
134
doc/attestation.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue