From 752cc5c2c146aa7e3996c3d763a734d47f0ed891 Mon Sep 17 00:00:00 2001 From: Alpha Nerd Date: Sat, 18 Apr 2026 15:42:22 +0200 Subject: [PATCH] Security Guide aktualisiert --- Security-Guide.md | 79 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/Security-Guide.md b/Security-Guide.md index 08e34cf..3702154 100644 --- a/Security-Guide.md +++ b/Security-Guide.md @@ -162,6 +162,81 @@ Secure memory features: - Guarantees zeroing of sensitive memory - Prevents memory dumps from containing sensitive data +## Hardware Attestation (TPM 2.0) + +### What it is + +When the server has a TPM 2.0 chip, every response includes a `tpm_attestation` block in `_metadata`. This is a cryptographically signed hardware quote proving: + +- Which firmware and Secure Boot state the server is running (PCR 0, 7) +- Which application binary is running, when IMA is active (PCR 10) + +The quote is signed by an ephemeral AIK (Attestation Identity Key) generated fresh for each request and tied to the `payload_id` nonce, so it cannot be replayed for a different request. + +### Reading the attestation + +```python +response = await client.create( + model="Qwen/Qwen3-0.6B", + messages=[{"role": "user", "content": "..."}], + security_tier="maximum" +) + +tpm = response["_metadata"].get("tpm_attestation", {}) + +if tpm.get("is_available"): + print("PCR banks:", tpm["pcr_banks"]) # e.g. "sha256:0,7,10" + print("PCR values:", tpm["pcr_values"]) # {bank: {index: hex}} + print("AIK key:", tpm["aik_pubkey_b64"][:32], "...") +else: + print("TPM not available on this server") +``` + +### Verifying the quote + +The response is self-contained: `aik_pubkey_b64` is the full public key of the AIK that signed the quote, so no separate key-fetch round-trip is needed. + +Verification steps using `tpm2-pytss`: + +```python +import base64 +from tpm2_pytss.types import TPM2B_PUBLIC, TPMT_SIGNATURE, TPM2B_ATTEST + +# 1. Decode the quote components +aik_pub = TPM2B_PUBLIC.unmarshal(base64.b64decode(tpm["aik_pubkey_b64"]))[0] +quote = TPM2B_ATTEST.unmarshal(base64.b64decode(tpm["quote_b64"]))[0] +sig = TPMT_SIGNATURE.unmarshal(base64.b64decode(tpm["signature_b64"]))[0] + +# 2. Verify the signature over the quote using the AIK public key +# (use a TPM ESAPI verify_signature call or an offline RSA verify) + +# 3. Inspect the qualifying_data inside the quote — it must match +# SHA-256(payload_id.encode())[:16] to confirm this quote is for this request + +# 4. Check pcr_values against your known-good baseline +``` + +> Full verification requires `tpm2-pytss` on the client side (`pip install tpm2-pytss` + `sudo apt install libtss2-dev`). It is optional — the attestation is informational unless your deployment policy requires verification. + +### Behaviour per security tier + +| Tier | TPM unavailable | +|------|----------------| +| `standard` | `tpm_attestation: {"is_available": false}` — request proceeds | +| `high` | same as standard | +| `maximum` | `ServiceUnavailableError` (HTTP 503) — request rejected | + +For `maximum` tier, the server enforces TPM availability as a hard requirement. If your server has no TPM and you request `maximum`, catch the error explicitly: + +```python +from nomyo import ServiceUnavailableError + +try: + response = await client.create(..., security_tier="maximum") +except ServiceUnavailableError as e: + print("Server does not meet TPM requirements for maximum tier:", e) +``` + ## Compliance Considerations ### HIPAA Compliance @@ -207,9 +282,11 @@ response = await client.create( messages=[{"role": "user", "content": "Hello"}] ) -print(response["_metadata"]) # Contains security-related information +print(response["_metadata"]) # Contains security_tier, memory_protection, tpm_attestation, etc. ``` +See [Hardware Attestation](#hardware-attestation-tpm-20) for details on the `tpm_attestation` field. + ### Logging Enable logging to see security operations: