diff --git a/README.md b/README.md index 13205350b..a7ab583fe 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Original Contributors: Hang Yin, Kevin Wang, Andrew Miller -[Documentation](https://docs.phala.com/dstack) · [Examples](https://github.com/Dstack-TEE/dstack-examples) · [Community](https://t.me/+UO4bS4jflr45YmUx) +[Documentation](https://docs.phala.com/dstack) · [Security](./SECURITY.md) · [Examples](https://github.com/Dstack-TEE/dstack-examples) · [Community](https://t.me/+UO4bS4jflr45YmUx) @@ -89,6 +89,19 @@ Your container runs inside a Confidential VM, such as Intel TDX or AMD SEV-SNP, [Full security model →](./docs/security/security-model.md) +## Security and Trust + +Security docs are linked here so deployers and reviewers can quickly find the trust model, production guidance, audit, and the status of already-answered public findings. + +- [Security Overview](./docs/security/) - entry point for users, operators, researchers, and AI agents +- [Security Model](./docs/security/security-model.md) - threat model, trust boundaries, and verification checklist +- [Security Issue Triage](./docs/security/security-issue-triage.md) - public status for answered, fixed, accepted, and roadmap security reports +- [Security Best Practices](./docs/security/security-best-practices.md) - production settings and hardening guidance +- [Security Audit](./docs/security/dstack-audit.pdf) - third-party audit by zkSecurity +- [Report a Vulnerability](./SECURITY.md) - use GitHub's private security reporting path + +Please do not disclose exploitable vulnerabilities in public GitHub issues. Use the private reporting path in [SECURITY.md](./SECURITY.md). + ## SDKs Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use the [HTTP API](./sdk/curl/api.md) directly with curl, or use a language SDK: @@ -121,14 +134,6 @@ Apps communicate with the guest agent via HTTP over `/var/run/dstack.sock`. Use - [Design Decisions](./docs/design-and-hardening-decisions.md) - Architecture rationale - [FAQ](./docs/faq.md) - Frequently asked questions -## Security - -- [Security Overview](./docs/security/) - Security documentation and responsible disclosure -- [Security Model](./docs/security/security-model.md) - Threat model and trust boundaries -- [Security Best Practices](./docs/security/security-best-practices.md) - Production hardening -- [Security Audit](./docs/security/dstack-audit.pdf) - Third-party audit by zkSecurity -- [CVM Boundaries](./docs/security/cvm-boundaries.md) - Information exchange and isolation - ## FAQ
@@ -180,7 +185,7 @@ Yes. dstack runs on supported TEE-capable servers, including Intel TDX-capable h - **GCP**: Intel TDX (Confidential VMs) - **AWS**: Nitro Enclaves (NSM attestation) -- **Bare metal**: Intel TDX (4th/5th Gen Xeon) and AMD SEV-SNP on supported dstack OS images +- **Bare metal**: Intel TDX (4th/5th Gen Xeon) and AMD SEV-SNP on supported dstack OS images. Intel TDX is the production path; AMD SEV-SNP is new and experimental. - **GPUs**: NVIDIA Confidential Computing (H100, Blackwell)
@@ -227,5 +232,3 @@ Logo and branding assets: [dstack-logo-kit](./docs/assets/dstack-logo-kit/) ## License Apache 2.0 - - diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000..3342bd792 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,21 @@ +# Security + +Use this file for vulnerability reports. For the security model, production guidance, audit, and already-answered public findings, start with [Security Documentation](./docs/security/). + +## Report a vulnerability + +If you believe you found a vulnerability, please use GitHub's private security reporting features for this repository. If GitHub private reporting is unavailable, contact security@phala.network. + +Do not open public GitHub issues for exploitable vulnerabilities or details that could help exploit production deployments. + +Use private reporting for issues that could expose secrets, bypass attestation or authorization, compromise KMS keys, weaken workload isolation, or enable unauthorized code or configuration changes in production deployments. + +## Public security questions + +Use public issues only for questions about documented behavior, documentation gaps, already-public findings, or hardening ideas that do not include an exploit path. + +Before opening a public security question, check [Security Issue Triage](./docs/security/security-issue-triage.md). It records public findings that were fixed, accepted by design, documented, or moved to roadmap work. + +## Production trust boundary + +Development settings are not production-safe merely because they are present in the codebase. Production deployments must rely on measured configuration, expected TEE measurements, authorization policy, and attestation verification. The documented security model is the source of truth for what dstack treats as a production guarantee. diff --git a/docs/security/README.md b/docs/security/README.md index bc82eded6..978118fb7 100644 --- a/docs/security/README.md +++ b/docs/security/README.md @@ -2,6 +2,13 @@ dstack security resources for auditors, researchers, and operators. +## Start Here + +- **Users and verifiers:** read the [Security Model](./security-model.md) to understand what dstack guarantees and what you must verify. +- **Operators:** read [Security Best Practices](./security-best-practices.md) before deploying production KMS, gateway, or VMM services. +- **Security researchers and AI agents:** report exploitable vulnerabilities through the private path in [SECURITY.md](../../SECURITY.md). For already-public findings or docs questions, check [Security Issue Triage](./security-issue-triage.md) before opening a public issue. +- **Maintainers:** use [Security Issue Triage](./security-issue-triage.md) to classify public reports and close issues once the maintainer position is clear. + ## Audit dstack has been audited by zkSecurity. See the [full audit report](./dstack-audit.pdf). @@ -10,8 +17,15 @@ dstack has been audited by zkSecurity. See the [full audit report](./dstack-audi - [Security Model](./security-model.md) - Threat model, trust boundaries, and verification checklist - [Security Best Practices](./security-best-practices.md) - Production hardening guide +- [Security Issue Triage](./security-issue-triage.md) - Public status for answered, fixed, accepted, and roadmap reports - [CVM Boundaries](./cvm-boundaries.md) - Information exchange and isolation details -## Responsible Disclosure +## Already Answered Reports + +Some public security reports describe real hardening work. Some describe behavior that is intentional for development or compatibility, and some are false positives under production configuration. The canonical list is [Security Issue Triage](./security-issue-triage.md). Search that page by issue number, component, or exact setting name before treating an old report as unresolved. + +## Report Vulnerabilities + +If you believe you found an exploitable vulnerability, use GitHub's private security reporting features as described in [SECURITY.md](../../SECURITY.md). If GitHub private reporting is unavailable, contact security@phala.network. -To report a security vulnerability, email security@phala.network. We will respond within 48 hours. +Do not open GitHub issues for exploitable vulnerabilities. diff --git a/docs/security/security-best-practices.md b/docs/security/security-best-practices.md index e93ffb683..865c7f411 100644 --- a/docs/security/security-best-practices.md +++ b/docs/security/security-best-practices.md @@ -68,6 +68,21 @@ Example app-compose.json: **But keep in mind, even if you disable exposing app-compose.json, it is just hidden from the public API, the physical machine controller can still access it on the file system.** +## Do not use development trust settings in production + +Development settings are intentionally easy to audit, but they are not production-safe. A production deployment should satisfy all of the following: + +- KMS quote verification remains enabled. Do not deploy production KMS with `quote_enabled = false`. +- KMS authorization uses webhook/on-chain policy. Do not use `auth_api.type = "dev"` with real key material. +- The KMS contract pins a concrete gateway app id. Do not use `gateway_app_id = "any"` for production traffic. +- TEE quotes are evaluated by deployment policy, including TCB status and expected OS/application measurements. + +The KMS TLS listener may keep `rpc.tls.mutual.mandatory = false` because bootstrap endpoints need to be reachable before a client has an RA-TLS certificate. Sensitive KMS routes still require the client certificate and attestation evidence in application code before releasing keys or signing certificates. + +## Keep private material owner-only + +Secret-bearing files should be owner-only (`0600`) wherever possible, including app keys, decrypted env files, KMS root keys, gateway WireGuard/TLS keys, and ACME credentials. Preserve restrictive permissions when copying volumes, backing up `/etc/kms/certs`, or moving gateway and certbot state between hosts. Public issue [#606](https://github.com/Dstack-TEE/dstack/issues/606) tracks the remaining low-cost hardening work in dstack-managed file writes. + ## docker logs is public available by default Similarly, to facilitate App observability, docker logs are public by default. You can disable exposing docker logs by setting public_logs=false. diff --git a/docs/security/security-issue-triage.md b/docs/security/security-issue-triage.md new file mode 100644 index 000000000..6db5391e6 --- /dev/null +++ b/docs/security/security-issue-triage.md @@ -0,0 +1,53 @@ +# Security Issue Triage + +Security issues should not remain open after the maintainer position is clear. An open issue means one of two things: a fix is still required, or a concrete design/roadmap item is intentionally being tracked. Everything else should be closed with a final maintainer comment and a link to the code or documentation that records the decision. + +This page is not a vulnerability reporting channel. Report exploitable vulnerabilities privately through [SECURITY.md](../../SECURITY.md). Use public issues only for questions, documentation gaps, duplicate-prone prior findings, or hardening ideas that do not disclose an exploit path. + +## Triage labels + +Use these categories when evaluating public security questions and already-public reports: + +| Category | Meaning | Expected issue state | +| --- | --- | --- | +| Real blocker | Confirmed vulnerability that can compromise production security under supported configuration | Keep open until fixed; close as completed when the fix lands | +| Needs hardening | Not a broken trust boundary, but a defense-in-depth improvement with no compatibility cost | Keep open only while the patch is pending; close as completed when merged | +| Fixed | The reported behavior has already been fixed or is fixed by the linked change | Close as completed | +| Docs-only | The behavior is intentional or lower severity, but the repo must say so clearly | Close after documentation is merged | +| Accepted by design | The report conflicts with the documented threat model or with an intentional compatibility constraint | Close as not planned, with the design rationale linked | + +When a report mixes several claims, split the actionable work into separate issues before closing the original. Do not leave a broad "security" issue open just to remember future work. + +## March 2026 security cluster + +The March cluster contained a mix of real hardening, compatibility decisions, and false positives. The current repo position is: + +| Issue | Classification | Maintainer action | +| --- | --- | --- | +| [#606](https://github.com/Dstack-TEE/dstack/issues/606) App keys and decrypted env files world-readable | Needs hardening | Tightening secret-bearing file writes to owner-only permissions (`0600`) is a valid defense-in-depth improvement with no expected compatibility cost | +| [#605](https://github.com/Dstack-TEE/dstack/issues/605) Identical raw key material across `ed25519` and `secp256k1` for the same path | Accepted compatibility decision, docs-only | Existing derived key bytes are preserved; docs now state that `path` is the domain separator and callers must use algorithm-specific paths when they require independent keys | +| [#607](https://github.com/Dstack-TEE/dstack/issues/607) `gateway_app_id = "any"` disables gateway identity pinning | Accepted by design for dev/test deployments | `gateway_app_id` is KMS contract configuration and is publicly auditable; production deployments must not use `"any"` | +| [#608](https://github.com/Dstack-TEE/dstack/issues/608) `auth_api.type = "dev"` allows all authorization | Accepted by design for local/integration testing | Dev auth is measured runtime configuration, not a production mode; production must use webhook/on-chain authorization | +| [#609](https://github.com/Dstack-TEE/dstack/issues/609) `quote_enabled = false` bypasses attestation | Accepted by design for local development | The flag is measured in runtime configuration and should fail production attestation policy | +| [#561](https://github.com/Dstack-TEE/dstack/issues/561) KMS TLS client certificates are non-mandatory in Rocket config | Docs-only for current architecture | The TLS listener allows unauthenticated bootstrap endpoints, while sensitive KMS handlers enforce client certificate and attestation checks in application code | +| [#552](https://github.com/Dstack-TEE/dstack/issues/552) Static HKDF salt and no key versioning | Design roadmap, not a near-term vulnerability | Static salt is acceptable with high-entropy KMS root material and explicit context; key versioning/rotation requires a broader compatibility design | + +Recommended GitHub cleanup for this cluster: + +- Keep #606 open until the `0600` hardening change lands, then close it as completed. +- Close #605, #561, #607, #608, and #609 with links to the relevant security docs and maintainer rationale. +- Keep a separate roadmap issue for KMS key versioning/rotation if it has an owner and migration plan; otherwise close #552 as not planned for the current KDF version. + +## Search terms for duplicate-prone findings + +Researchers and AI agents should search this page and linked issues before treating these as new vulnerabilities: + +- `quote_enabled = false` +- `auth_api.type = "dev"` +- `gateway_app_id = "any"` +- `rpc.tls.mutual.mandatory = false` +- `get_temp_ca_cert` +- `ed25519` and `secp256k1` with the same derivation path +- `RATLS` HKDF salt +- KMS key versioning and rotation +- app keys and decrypted env file permissions diff --git a/docs/security/security-model.md b/docs/security/security-model.md index fc5ad8475..d819e9050 100644 --- a/docs/security/security-model.md +++ b/docs/security/security-model.md @@ -8,7 +8,7 @@ This document helps you evaluate whether dstack's security model fits your needs dstack removes the need to trust infrastructure operators. The cloud provider cannot read your memory, modify your code, or access your secrets. Network attackers cannot intercept your traffic because TLS terminates inside the TEE with keys fully controlled by the TEE (Zero Trust HTTPS). Docker registries cannot serve malicious images because the TEE verifies SHA256 digests before pulling. -The only thing you must trust is **TEE hardware** (currently Intel TDX, with AMD SEV support planned). You trust that the TEE provides genuine memory encryption and that quotes are signed by real hardware. For GPU workloads, you also trust **NVIDIA GPU hardware** and NVIDIA's Remote Attestation Service (NRAS). These are hardware-level trust assumptions. +The only thing you must trust is **TEE hardware**. Intel TDX is the production path. AMD SEV-SNP is available where the selected dstack OS image and host support it, but it is new and experimental. You trust that the TEE provides genuine memory encryption and that quotes are signed by real hardware. For GPU workloads, you also trust **NVIDIA GPU hardware** and NVIDIA's Remote Attestation Service (NRAS). These are hardware-level trust assumptions. Everything else is verifiable. @@ -134,6 +134,20 @@ The one case dstack does not leave to downstream is a genuinely invalid TCB: `dc > **Future work:** this will be refactored toward a grace-period model, where an out-of-date TCB is accepted for a bounded window after a new TCB level is published rather than being a binary downstream decision. +### Development modes are auditable, not production-safe + +dstack keeps several development switches as runtime or on-chain configuration rather than Cargo feature flags. Examples include KMS `quote_enabled = false`, `auth_api.type = "dev"`, and KMS contract `gateway_app_id = "any"`. These settings exist for local development and integration tests, not for production deployments. + +This is intentional. Runtime configuration that affects the trust boundary is visible in attestation measurements or public contract state. Cargo feature gates are not automatically more auditable because feature unification can enable a feature through a dependency graph, and the resulting runtime behavior is not represented as a measured deployment setting. + +Production verifiers should reject deployments that use these development settings. Operators should treat them the same way they treat debug-mode TEE quotes: useful for testing, invalid for production trust. + +### KMS mTLS is route-enforced for sensitive operations + +The KMS Rocket TLS listener permits connections without a client certificate because some bootstrap and public metadata endpoints must be reachable before a client has an RA-TLS certificate. That listener setting is not the authorization boundary for key material. + +Sensitive KMS handlers enforce their own boundary: callers must present the expected client certificate and attestation evidence before key derivation, KMS key replication, or certificate signing succeeds. Public endpoints are limited to bootstrap, metadata, health, and metrics behavior documented for operators. + ## Limitations ### Attestation proves identity, not correctness diff --git a/docs/tutorials/kms-build-configuration.md b/docs/tutorials/kms-build-configuration.md index 502346879..9d27d3a84 100644 --- a/docs/tutorials/kms-build-configuration.md +++ b/docs/tutorials/kms-build-configuration.md @@ -186,6 +186,9 @@ certs = "/etc/kms/certs/rpc.crt" # Mutual TLS (mTLS) Configuration [rpc.tls.mutual] ca_certs = "/etc/kms/certs/tmp-ca.crt" +# Keep the TLS listener optional because bootstrap/public endpoints must be +# reachable before a client has an RA-TLS certificate. Sensitive KMS RPCs still +# enforce client certificate and attestation checks in their handlers. mandatory = false # Core KMS Configuration @@ -221,7 +224,7 @@ EOF | `[rpc]` | `address` | RPC server bind address | | `[rpc]` | `port` | RPC server port (9100) | | `[core]` | `cert_dir` | Directory for certificates | -| `[core]` | `pccs_url` | Local PCCS via host bridge (`10.0.2.2`) for quote verification | +| `[core]` | `pccs_url` | PCCS endpoint for quote verification | | `[core.auth_api]` | `url` | Auth-eth webhook service URL | | `[core.onboard]` | `enabled` | Enable bootstrap/onboard mode | @@ -321,7 +324,8 @@ chmod 600 /etc/kms/auth-eth.env ### Verify configuration ```bash -cat /etc/kms/auth-eth.env +grep -E '^(HOST|PORT|KMS_CONTRACT_ADDR)=' /etc/kms/auth-eth.env +grep -q '^ETH_RPC_URL=.' /etc/kms/auth-eth.env && echo "ETH_RPC_URL is set" ``` ## Step 7: Create Docker Image for CVM Deployment @@ -465,6 +469,9 @@ certs = "/etc/kms/certs/rpc.crt" # Mutual TLS (mTLS) Configuration [rpc.tls.mutual] ca_certs = "/etc/kms/certs/tmp-ca.crt" +# Keep the TLS listener optional because bootstrap/public endpoints must be +# reachable before a client has an RA-TLS certificate. Sensitive KMS RPCs still +# enforce client certificate and attestation checks in their handlers. mandatory = false # Core KMS Configuration @@ -621,7 +628,7 @@ cat /etc/kms/kms.toml | python3 -c "import sys, tomllib; tomllib.load(sys.stdin. ```bash # Source and verify environment source /etc/kms/auth-eth.env -echo "ETH_RPC_URL: ${ETH_RPC_URL:0:30}..." +test -n "$ETH_RPC_URL" && echo "ETH_RPC_URL is set" echo "KMS_CONTRACT_ADDR: $KMS_CONTRACT_ADDR" ``` diff --git a/docs/tutorials/kms-cvm-deployment.md b/docs/tutorials/kms-cvm-deployment.md index a133b3683..b3827f3aa 100644 --- a/docs/tutorials/kms-cvm-deployment.md +++ b/docs/tutorials/kms-cvm-deployment.md @@ -49,7 +49,7 @@ Before starting, ensure you have: > **Why SGX is required:** The KMS uses Intel SGX to generate TDX attestation quotes via the `local_key_provider`. SGX Auto MP Registration must be enabled in BIOS so your platform is registered with Intel's Provisioning Certification Service (PCS). Without this registration, KMS cannot generate valid attestation quotes, and bootstrap will fail. -> **Why local registry?** The KMS Docker image is cached in your [Local Docker Registry](/tutorial/local-docker-registry) for reliable, fast access from CVMs. The auth-eth service inside the container requires `ETH_RPC_URL` and `KMS_CONTRACT_ADDR` environment variables — these are passed via docker-compose, not baked into the image. +> **Why local registry?** The KMS Docker image is cached in your [Local Docker Registry](/tutorial/local-docker-registry) for reliable, fast access from CVMs. This deployment passes `ETH_RPC_URL` and `KMS_CONTRACT_ADDR` through docker-compose so you can change the RPC endpoint or contract address without rebuilding the image. ## What Gets Deployed @@ -167,6 +167,9 @@ configs: [rpc.tls.mutual] ca_certs = "/etc/kms/certs/tmp-ca.crt" + # Keep the TLS listener optional because bootstrap/public endpoints must be + # reachable before a client has an RA-TLS certificate. Sensitive KMS RPCs + # still enforce client certificate and attestation checks in their handlers. mandatory = false [core] @@ -277,7 +280,7 @@ export DSTACK_VMM_AUTH_PASSWORD=$(cat ~/.dstack/secrets/vmm-auth-token) - `--image dstack-0.5.7`: Guest image from VMM images directory - `--port tcp:0.0.0.0:9100:9100`: Maps host port 9100 to CVM port 9100 on all interfaces -> **Why `0.0.0.0` and not `127.0.0.1`?** Gateway CVMs use QEMU user-mode networking and reach the host via its public IP. If KMS is bound to localhost only, gateway CVMs cannot connect. KMS authentication uses TDX attestation, not network isolation, so public accessibility is safe. +> **Why `0.0.0.0` and not `127.0.0.1`?** Gateway CVMs use QEMU user-mode networking and reach the host via its public IP. If KMS is bound to localhost only, gateway CVMs cannot connect. KMS authorization uses TDX attestation and auth policy, not loopback binding. Expose only the required KMS port and monitor it as a public service. > **Note:** Do NOT use `--secure-time` flag - it causes CVM to hang during boot waiting for time sync. diff --git a/sdk/curl/api.md b/sdk/curl/api.md index b05a6717b..8b864f75d 100644 --- a/sdk/curl/api.md +++ b/sdk/curl/api.md @@ -64,7 +64,7 @@ curl --unix-socket /var/run/dstack.sock -X POST \ ### 2. Get Key -Generates an ECDSA key using the k256 elliptic curve, derived from the application key, and returns both the key and its signature chain. Sutable for ETH key generation. +Generates a deterministic private key from the application key and returns both the key and its signature chain. Suitable for ETH key generation when using the default `secp256k1` algorithm. **Endpoint:** `/GetKey` @@ -72,9 +72,11 @@ Generates an ECDSA key using the k256 elliptic curve, derived from the applicati | Field | Type | Description | Example | |-------|------|-------------|----------| -| `path` | string | Path for the key | `"my/key/path"` | -| `purpose` | string | Purpose for the key. Can be any string. This is used in the signature chain. | `"signing"` | `"encryption"` | -| `algorithm` | string | Either `secp256k1` or `ed25519`. Defaults to `secp256k1` | `ed25519` | +| `path` | string | Path for the key. This is the domain separator for deterministic key material. | `"my/key/path"` | +| `purpose` | string | Purpose for the key. Can be any string. This is used in the signature chain and does not affect the private key bytes. | `"signing"` | +| `algorithm` | string | Either `secp256k1` or `ed25519`. Defaults to `secp256k1`. For compatibility, this selects how the same derived 32-byte material is interpreted; it does not domain-separate the derivation. | `ed25519` | + +Use algorithm-specific paths, such as `wallet/ethereum` and `wallet/solana`, when independent keys are required across algorithms. **Example:** ```bash diff --git a/sdk/go/README.md b/sdk/go/README.md index 43815a563..227ddb400 100644 --- a/sdk/go/README.md +++ b/sdk/go/README.md @@ -21,7 +21,7 @@ dstack applications consist of: ### SDK Capabilities -- **Key Derivation**: Deterministic secp256k1 key generation for blockchain and Web3 applications +- **Key Derivation**: Deterministic key derivation for wallets, signing, encryption, and other application-specific secrets - **Remote Attestation**: TDX quote generation providing cryptographic proof of execution environment - **TLS Certificate Management**: Fresh certificate generation with optional RA-TLS support for secure connections - **Deployment Security**: Client-side encryption of sensitive environment variables ensuring secrets are only accessible to target TEE applications @@ -98,12 +98,12 @@ func main() { fmt.Println("App Name:", info.AppName) fmt.Println("TCB Info:", info.TcbInfo) - // Derive deterministic keys for blockchain applications + // Derive deterministic keys for application-specific secrets walletKey, err := client.GetKey(ctx, "wallet/ethereum", "mainnet", "secp256k1") if err != nil { log.Fatal(err) } - + keyBytes, _ := walletKey.DecodeKey() fmt.Println("Derived key (32 bytes):", hex.EncodeToString(keyBytes)) // secp256k1 private key fmt.Println("Signature chain:", walletKey.SignatureChain) // Authenticity proof @@ -114,13 +114,13 @@ func main() { "timestamp": time.Now().Unix(), "user_id": "alice", } - + jsonData, _ := json.Marshal(applicationData) quote, err := client.GetQuote(ctx, jsonData) if err != nil { log.Fatal(err) } - + fmt.Println("TDX Quote:", quote.Quote) fmt.Println("Event Log:", quote.EventLog) @@ -318,24 +318,16 @@ if err != nil { ```go import ( - "crypto/ed25519" "encoding/hex" - + "github.com/Dstack-TEE/dstack/sdk/go/dstack" ) -keyResult, err := client.GetKey(ctx, "solana/main", "wallet", "secp256k1") +keyResult, err := client.GetKey(ctx, "solana/main", "wallet", "ed25519") if err != nil { log.Fatal(err) } -// Standard keypair creation -keypair, err := dstack.ToSolanaKeypair(keyResult) -if err != nil { - log.Fatal(err) -} - -// Enhanced security with SHA256 hashing (recommended) secureKeypair, err := dstack.ToSolanaKeypairSecure(keyResult) if err != nil { log.Fatal(err) @@ -365,8 +357,8 @@ The SDK provides end-to-end encryption capabilities for securely transmitting se import ( "encoding/hex" "fmt" - "time" - + "log" + "github.com/Dstack-TEE/dstack/sdk/go/dstack" ) @@ -388,21 +380,28 @@ publicKeyBytes, _ := hex.DecodeString(publicKey) signatureBytes, _ := hex.DecodeString(signature) // Prefer timestamped verification to prevent replay attacks. -timestamp := uint64(time.Now().Unix()) // should come from KMS API response -trustedPubkey, err := dstack.VerifyEnvEncryptPublicKeyWithTimestamp( +timestamp := uint64(1710000000) // From the KMS API response, not local time +kmsIdentity, err := dstack.VerifyEnvEncryptPublicKeyWithTimestamp( publicKeyBytes, signatureBytes, "your-app-id-hex", timestamp, nil, // use default freshness policy (max age 300s) ) -if err != nil || trustedPubkey == nil { +if err != nil || kmsIdentity == nil { log.Fatal("KMS API provided untrusted encryption key") } -fmt.Println("Verified KMS public key:", hex.EncodeToString(trustedPubkey)) +expectedKMSIdentity := "03..." // From the DstackKms contract or deployment config +actualKMSIdentity := hex.EncodeToString(kmsIdentity) +if actualKMSIdentity != expectedKMSIdentity { + log.Fatalf("unexpected KMS identity: got %s", actualKMSIdentity) +} + +fmt.Println("Verified KMS public key:", actualKMSIdentity) -// Note: VerifyEnvEncryptPublicKey() is kept for legacy compatibility (without timestamp check). +// VerifyEnvEncryptPublicKey() is available only for explicit compatibility with +// older KMS builds. It does not provide timestamp replay protection. // 4. Encrypt environment variables for secure deployment encryptedData, err := dstack.EncryptEnvVars(envVars, publicKey) @@ -422,7 +421,7 @@ fmt.Println("Encrypted payload:", encryptedData) The SDK implements secure key derivation using: - **Deterministic Generation**: Keys are derived using HMAC-based Key Derivation Function (HKDF) -- **Application Isolation**: Each path produces unique keys, preventing cross-application access +- **Application Isolation**: Different `app_id` values derive different keys even with the same path - **Signature Verification**: All derived keys include cryptographic proof of origin - **TEE Protection**: Master keys never leave the secure enclave @@ -547,36 +546,39 @@ Retrieves comprehensive information about the TEE instance. - `EventLog`: Boot and runtime events - `AppCert`: Application certificate in PEM format -##### `GetKey(ctx context.Context, path string, purpose string) (*GetKeyResponse, error)` +##### `GetKey(ctx context.Context, path string, purpose string, algorithm string) (*GetKeyResponse, error)` -Derives a deterministic secp256k1/K256 private key for blockchain and Web3 applications. This is the primary method for obtaining cryptographic keys for wallets, signing, and other deterministic key scenarios. +Derives deterministic private key material for wallets, signing, encryption, stable service identities, and other application-specific secrets. **Parameters:** - `path`: Unique identifier for key derivation (e.g., `"wallet/ethereum"`, `"signing/solana"`) -- `purpose`: Additional context for key usage (default: `""`) +- `purpose`: Included in the signature-chain message; does not affect the private key bytes +- `algorithm`: `"secp256k1"` (default behavior), `"k256"` (alias), or `"ed25519"` **Returns:** `GetKeyResponse` -- `Key`: 32-byte secp256k1 private key as hex string (suitable for Ethereum, Bitcoin, Solana, etc.) +- `Key`: 32-byte private key material as a hex string - `SignatureChain`: Array of cryptographic signatures proving key authenticity **Key Characteristics:** -- **Deterministic**: Same path + purpose always generates identical key +- **Deterministic**: Same path always generates identical raw key material for the same app - **Isolated**: Different paths produce cryptographically independent keys -- **Blockchain-Ready**: Compatible with secp256k1 curve (Ethereum, Bitcoin, Solana) +- **Blockchain-Ready**: Use `secp256k1` for Ethereum and Bitcoin-style signing; use `ed25519` with a Solana-specific path for independent Solana keys - **Verifiable**: Signature chain proves key was derived inside genuine TEE +For compatibility, `algorithm` selects how the same derived 32-byte material is interpreted; it does not domain-separate the derivation. Use algorithm-specific paths when independent keys are required. + **Use Cases:** -- Cryptocurrency wallets -- Transaction signing -- DeFi protocol interactions -- NFT operations +- Stable service identity keys +- Application signing keys +- Encryption key seeds +- Cryptocurrency wallets and transaction signing - Any scenario requiring consistent, reproducible keys ```go // Examples of deterministic key derivation ethWallet, _ := client.GetKey(ctx, "wallet/ethereum", "mainnet", "secp256k1") -btcWallet, _ := client.GetKey(ctx, "wallet/bitcoin", "mainnet") -solWallet, _ := client.GetKey(ctx, "wallet/solana", "mainnet") +btcWallet, _ := client.GetKey(ctx, "wallet/bitcoin", "mainnet", "secp256k1") +solWallet, _ := client.GetKey(ctx, "wallet/solana", "mainnet", "ed25519") // Same path always returns same key key1, _ := client.GetKey(ctx, "my-app/signing", "", "secp256k1") @@ -695,7 +697,9 @@ Verify the authenticity of encryption public keys provided by KMS APIs: ```go import ( "encoding/hex" - "time" + "fmt" + "log" + "github.com/Dstack-TEE/dstack/sdk/go/dstack" ) @@ -704,16 +708,20 @@ publicKey, _ := hex.DecodeString("e33a1832c6562067ff8f844a61e51ad051f1180b66ec25 signature, _ := hex.DecodeString("8542c49081fbf4e03f62034f13fbf70630bdf256a53032e38465a27c36fd6bed7a5e7111652004aef37f7fd92fbfc1285212c4ae6a6154203a48f5e16cad2cef00") appID := "0000000000000000000000000000000000000000" -timestamp := uint64(time.Now().Unix()) // should come from KMS API response +timestamp := uint64(1710000000) // From the KMS API response kmsIdentity, err := dstack.VerifyEnvEncryptPublicKeyWithTimestamp(publicKey, signature, appID, timestamp, nil) -if err == nil && kmsIdentity != nil { - fmt.Println("Trusted KMS identity:", hex.EncodeToString(kmsIdentity)) - // Safe to use the public key for encryption -} else { - fmt.Println("KMS signature verification failed") - // Potential man-in-the-middle attack +if err != nil || kmsIdentity == nil { + log.Fatal("KMS signature verification failed") } + +expectedKMSIdentity := "03..." // From the DstackKms contract or deployment config +actualKMSIdentity := hex.EncodeToString(kmsIdentity) +if actualKMSIdentity != expectedKMSIdentity { + log.Fatalf("unexpected KMS identity: got %s", actualKMSIdentity) +} + +fmt.Println("Trusted KMS identity:", actualKMSIdentity) ``` ## Security Best Practices @@ -734,9 +742,9 @@ if err == nil && kmsIdentity != nil { - Implement proper certificate validation 4. **Error Handling** - - Handle cryptographic operation failures gracefully + - Fail closed on security-critical cryptographic errors - Log security events for monitoring - - Implement fallback mechanisms where appropriate + - Avoid fallback behavior that weakens verification or key isolation ## Migration Guide @@ -744,7 +752,7 @@ if err == nil && kmsIdentity != nil { The legacy client mixed two different use cases that have now been properly separated: -1. **`GetKey()`**: Deterministic key derivation for Web3/blockchain (secp256k1) +1. **`GetKey()`**: Deterministic key derivation for application-specific secrets 2. **`GetTlsKey()`**: Random TLS certificate generation for HTTPS/SSL ### From TappdClient to DstackClient @@ -778,12 +786,12 @@ client := dstack.NewDstackClient() **Step 2: Update Method Calls** ```go -// For deterministic keys (most common) +// For deterministic application keys (most common) // Before: TappdClient methods keyResult, _ := client.DeriveKey(ctx, "wallet") // After: DstackClient methods -keyResult, _ := client.GetKey(ctx, "wallet", "ethereum") +keyResult, _ := client.GetKey(ctx, "wallet/ethereum", "ethereum", "secp256k1") // For TLS certificates // Before: DeriveKey with TLS options @@ -805,7 +813,7 @@ tlsCert, _ := client.GetTlsKey(ctx, dstack.TlsKeyOptions{ - [ ] **Client Code Updates:** - [ ] Replace `tappd.NewTappdClient()` with `dstack.NewDstackClient()` - [ ] Replace `DeriveKey()` calls with appropriate method: - - [ ] `GetKey()` for Web3/blockchain keys (deterministic) + - [ ] `GetKey()` for deterministic application keys - [ ] `GetTlsKey()` for TLS certificates (random) - [ ] Replace `TdxQuote()` calls with `GetQuote()` - [ ] **SECURITY CRITICAL**: Update blockchain integration functions: diff --git a/sdk/js/README.md b/sdk/js/README.md index ebd13de68..156d706d0 100644 --- a/sdk/js/README.md +++ b/sdk/js/README.md @@ -49,7 +49,7 @@ const client = new DstackClient('/run/dstack/dstack.sock') // custom path ### `getKey(path?, purpose?, algorithm?)` -Derive a deterministic key. Same `(app_id, path, purpose, algorithm)` always returns the same key; different apps deriving on the same path get different keys. +Derive a deterministic key. The same `(app_id, path)` returns the same raw key material; different apps deriving on the same path get different keys. ```typescript const eth = await client.getKey('wallet/ethereum') // secp256k1 (default) @@ -58,7 +58,7 @@ const sol = await client.getKey('wallet/solana', 'mainnet', 'ed25519') // ed25 Returns `{ key: Uint8Array, signature_chain: Uint8Array[] }`. The signature chain proves the key was derived inside a genuine TEE. -`algorithm`: `'secp256k1'` (default), `'k256'` (alias), or `'ed25519'`. ed25519 requires guest agent ≥ 0.5.7. +`purpose` is included in the signature-chain message and does not affect the private key bytes. `algorithm` selects how the derived 32-byte material is interpreted: `'secp256k1'` (default), `'k256'` (alias), or `'ed25519'`. It does not domain-separate the derivation, so use algorithm-specific paths such as `wallet/ethereum` and `wallet/solana` when those keys must be independent. ed25519 requires guest agent ≥ 0.5.7. ### `getTlsKey(options?)` @@ -208,7 +208,6 @@ The full deployment flow mirrors `vmm-cli.py`: fetch the env-encrypt public key ```typescript import { verifyEnvEncryptPublicKey, - verifyEnvEncryptPublicKeyLegacy, } from '@phala/dstack-sdk' import { encryptEnvVars, type EnvVar } from '@phala/dstack-sdk/encrypt-env-vars' @@ -220,27 +219,22 @@ const response = await fetch(`${kmsUrl}/prpc/GetAppEnvEncryptPubKey?json`, { const publicKey = Buffer.from(response.public_key, 'hex') -// Prefer v1 (timestamp-protected against replay) -let signer = response.signature_v1 - ? verifyEnvEncryptPublicKey( - publicKey, - Buffer.from(response.signature_v1, 'hex'), - appId, - BigInt(response.timestamp), - ) - : null - -// Fall back to legacy signature on older KMS -if (!signer && response.signature) { - signer = verifyEnvEncryptPublicKeyLegacy( - publicKey, - Buffer.from(response.signature, 'hex'), - appId, - ) +if (!response.signature_v1 || response.timestamp === undefined) { + throw new Error('KMS response missing timestamped signature') } +const signer = verifyEnvEncryptPublicKey( + publicKey, + Buffer.from(response.signature_v1, 'hex'), + appId, + BigInt(response.timestamp), +) + if (!signer) throw new Error('KMS signature did not verify') +const trustedSigners = new Set(['0x...']) // From the DstackKms contract or deployment config +if (!trustedSigners.has(signer)) throw new Error(`unexpected KMS signer: ${signer}`) + const envs: EnvVar[] = [ { key: 'DATABASE_URL', value: 'postgresql://…' }, { key: 'API_KEY', value: 'sk-test-1234' }, @@ -248,7 +242,7 @@ const envs: EnvVar[] = [ const encrypted = await encryptEnvVars(envs, response.public_key) ``` -Verify functions return the signer's compressed public key (hex) on success, or `null` on failure. Check the signer against your trusted-signer whitelist before encrypting. +Verify functions return the signer's compressed public key (hex) on success, or `null` on failure. `verifyEnvEncryptPublicKeyLegacy` is available only for deployments that explicitly support older KMS builds without `signature_v1`; it does not provide timestamp replay protection and should not be used for new deployments. ## Compatibility diff --git a/sdk/python/README.md b/sdk/python/README.md index 01ec56442..4093521d9 100644 --- a/sdk/python/README.md +++ b/sdk/python/README.md @@ -63,7 +63,7 @@ ed_key = client.get_key('signing/key', algorithm='ed25519') **Parameters:** - `path` (optional): Key derivation path. Defaults to `""` (root). - `purpose` (optional): Included in the signature chain message; does not affect the derived key. -- `algorithm` (optional): `'secp256k1'` (default) or `'ed25519'`. +- `algorithm` (optional): `'secp256k1'` (default) or `'ed25519'`. For compatibility, this selects how the same derived 32-byte material is interpreted; it does not domain-separate the derivation. Use algorithm-specific paths when independent keys are required. **Returns:** `GetKeyResponse` - `key`: Hex-encoded private key @@ -260,7 +260,6 @@ The KMS returns a fresh X25519 public key (with a secp256k1 signature) that you from dstack_sdk import ( encrypt_env_vars, verify_env_encrypt_public_key, - verify_env_encrypt_public_key_legacy, EnvVar, ) @@ -272,15 +271,7 @@ signer = verify_env_encrypt_public_key( timestamp=timestamp, ) if signer is None: - # Fallback for older KMS builds that only emit the unprotected legacy - # signature. Vulnerable to replay; warn loudly if you must use it. - signer = verify_env_encrypt_public_key_legacy( - public_key=public_key_bytes, - signature=legacy_signature_bytes, - app_id=app_id_hex, - ) - if signer is None: - raise RuntimeError('invalid KMS env-encrypt public key') + raise RuntimeError('invalid KMS env-encrypt public key') # Always compare the recovered signer against a known-good KMS signer # address, obtained out-of-band from the DstackKms contract or your @@ -303,6 +294,8 @@ encrypted = await encrypt_env_vars(env_vars, public_key_hex) `verify_env_encrypt_public_key` returns the recovered compressed secp256k1 signer (`0x`-prefixed hex) on success, or `None` for any failure (bad length, expired/future timestamp, malformed `app_id`, invalid signature). The default `max_age_seconds` is 300; pass a larger value if your deployment workflow legitimately holds the response longer. +`verify_env_encrypt_public_key_legacy` remains available only for deployments that explicitly support older KMS builds without `signature_v1`. It does not provide timestamp replay protection and should not be used for new deployments. + ### Calculate Compose Hash ```python diff --git a/sdk/rust/README.md b/sdk/rust/README.md index cc5761b38..4fe1d282f 100644 --- a/sdk/rust/README.md +++ b/sdk/rust/README.md @@ -56,6 +56,8 @@ let testnet_key = client.get_key(Some("wallet/eth/testnet".to_string()), None).a - `path`: Key derivation path (determines the key) - `purpose` (optional): Included in signature chain message, does not affect the derived key +For compatibility, the key algorithm selects how the same derived 32-byte material is interpreted; it does not domain-separate the derivation. Use algorithm-specific paths when independent keys are required. + **Returns:** `GetKeyResponse` - `key`: Hex-encoded private key - `signature_chain`: Signatures proving the key was derived in a genuine TEE