Architecture
The modular architecture, host/enclave split, module system, and data flow of Enclave OS (Mini).
Enclave OS (Mini) is a lightweight SGX enclave runtime that exposes an RA-TLS HTTPS server from inside an Intel SGX enclave. It is written in Rust, built with a fork of the Teaclave SGX SDK, and provides a minimal, auditable Trusted Computing Base.
Design Principles
| Principle | How it's applied |
|---|---|
| Minimal TCB | Only the enclave binary runs inside SGX. The host is untrusted. |
| Standard protocols | Uses TLS 1.3, X.509, HTTPS with no proprietary attestation channels. |
| Defence in depth | MRENCLAVE sealing, AES-256-GCM KV encryption, per-app namespace isolation, config Merkle tree for integrity. |
| Modular by design | All business logic lives in pluggable module crates. The core OS provides only RA-TLS ingress, HTTPS egress, and sealed storage. |
| No vendor lock-in | Open source (AGPL-3.0), self-hostable on any SGX-capable hardware. |
What the Enclave Provides
- RA-TLS Ingress. Accept incoming TCP connections authenticated via RA-TLS (TLS 1.3 with SGX quotes embedded in X.509 certificates).
- HTTPS Egress. Make outbound HTTPS requests from inside the enclave (TLS termination inside the enclave, network I/O via host RPC).
- Sealed Key-Value Store. Encrypted KV database stored on the host, with both keys and values encrypted using a master key sealed to MRENCLAVE.
- WASM Runtime. Execute WebAssembly bytecode inside SGX with WASI support (random, sockets, KV).
- JWT-Authenticated Vault. Store and retrieve secrets gated by ES256 JWT verification.
- Sealed Config. All persistent state (CA material, egress CA bundle, KV master key) stored as a single MRENCLAVE-bound blob.
- Config Attestation. A Merkle root and per-module OIDs over all config inputs are embedded as custom X.509 extensions in every RA-TLS certificate.
High-Level Topology
┌─────────────────────────────────────────────────────────────────┐
│ Host (Untrusted) │
│ │
│ ┌──────────────┐ ┌─────────────┐ ┌────────────────┐ │
│ │ TCP Proxy │ │ KV Store │ │ RPC Dispatcher │ │
│ │ (tcp_proxy/) │ │ Backend │ │ (spin-polls │ │
│ │ :443 │ │ (kvstore/) │ │ enc_to_host) │ │
│ └──────┬───────┘ └──────┬──────┘ └────────┬───────┘ │
│ │ │ │ │
│ │ └───────┬───────────┘ │
│ │ │ │
│ ┌──────┴───────────┐ ┌──────────┴─────────────────────────┐ │
│ │ Data Channel │ │ RPC Channel │ │
│ │ (TCP bytes) │ │ (KV ops, egress, module dispatch) │ │
│ │ host↔enc [SPSC] │ │ enc↔host [SPSC] │ │
│ └──────┬───────────┘ └──────────┬─────────────────────────┘ │
│ │ │ │
├─────────┼──────────────────────────┼────────────────────────────┤
│ │ │ ← SGX boundary │
│ │ │ (4 ECALLs + 1 OCALL) │
│ ┌──────┴──────────┐ ┌───────────┴─────────────────────┐ │
│ │ TLS Engine │ │ RPC Client │ │
│ │ (rustls) │ │ (encodes req → enc_to_host, │ │
│ │ TCP bytes in, │ │ reads responses) │ │
│ │ HTTP out │ └───┬──────────────┬──────────────┘ │
│ └──────┬──────────┘ │ │ │
│ │ ┌────┴──────┐ ┌────┴───────┐ │
│ │ │ HTTPS │ │ Sealed KV │ │
│ │ │ Egress │ │ Store │ │
│ │ │ (rustls) │ │ (AES-GCM) │ │
│ │ └───────────┘ └────────────┘ │
│ ┌──────┴───────────────────────────────────────────────┐ │
│ │ Pluggable Modules (EnclaveModule) │ │
│ │ ┌────────┐ ┌────────┐ ┌──────┐ ┌──────────────┐ │ │
│ │ │ Egress │ │ WASM │ │Vault │ │ HelloWorld │ │ │
│ │ └────────┘ └────────┘ └──────┘ └──────────────┘ │ │
│ └──────────────────────────────────────────────────────┘ │
│ Enclave (Trusted) │
└─────────────────────────────────────────────────────────────────┘The host runs two dedicated threads alongside the enclave:
- TCP Proxy thread: accepts inbound TCP connections and shuttles raw bytes to/from the enclave over a dedicated Data Channel (pair of SPSC queues). The enclave performs TLS termination internally, so the host never sees plaintext.
- RPC Dispatcher thread: processes enclave requests (KV operations, egress HTTP, etc.) arriving over the RPC Channel (a second pair of SPSC queues).
The two channel pairs are fully independent: the data-plane (network I/O) never contends with the control-plane (KV and egress RPC).
Modular Architecture
All business logic is implemented as pluggable modules that conform to the EnclaveModule trait and are registered at enclave startup. The core OS provides only the RA-TLS ingress server, HTTPS egress client, and sealed key-value store. Each module lives in its own crate under crates/enclave-os-*, keeping the core enclave small and each concern independently testable.
Module Interface
Each module implements the following trait (defined in enclave/src/modules/mod.rs):
pub trait EnclaveModule: Send + Sync {
/// Human-readable module name.
fn name(&self) -> &str;
/// Handle a client request.
fn handle(&self, req: &Request) -> Option<Response>;
/// Config leaves for the Merkle tree (default: none).
fn config_leaves(&self) -> Vec<ConfigLeaf> { Vec::new() }
/// Custom X.509 OIDs for RA-TLS certificates (default: none).
fn custom_oids(&self) -> Vec<ModuleOid> { Vec::new() }
}config_leaves(): named inputs hashed into the config Merkle root OID.custom_oids(): individual X.509 OID extensions embedded in every RA-TLS certificate, allowing clients to verify specific module properties without a full Merkle audit.
Module Crates
| Module | Crate | Description |
|---|---|---|
| egress | crates/enclave-os-egress | HTTPS egress client; owns egress root CA store; registers egress.ca_bundle config leaf + OID 1.3.6.1.4.1.65230.2.1 |
| kvstore | crates/enclave-os-kvstore | Sealed (AES-256-GCM) KV store; owns master key from sealed config |
| wasm | crates/enclave-os-wasm | WASM runtime (wasmtime); registers wasm.code_hash config leaf + OID 1.3.6.1.4.1.65230.2.3 |
| vault | crates/enclave-os-vault | JWT-authenticated (ES256) secret store and retrieval |
| helloworld | enclave/src/modules/helloworld.rs | Smoke-test module: responds "world" to "hello" (inline, used by default-ecall feature) |
Modules are registered at startup in ecall_run():
crate::modules::register_module(Box::new(MyModule));The enclave dispatches all incoming requests to registered modules. If a module returns Some(response), that response is sent to the client.
Adding Your Own Module
- Create a new crate:
crates/enclave-os-mymodule/depending onenclave-os-common. - Implement the
EnclaveModuletrait (includingname(),handle(), and optionallyconfig_leaves()+custom_oids()). - Add your crate as a dependency in
enclave/Cargo.toml. - Register your module in
ecall_run()inecall.rs.
The Two Processes
Host (untrusted)
The host process is a standard Linux binary that:
- Creates the enclave. Loads the signed enclave binary via the SGX SDK's
SgxEnclave::createAPI. - Allocates two SPSC channel pairs: one for RPC (KV, egress, etc.) and one for the data plane (TCP proxy ↔ enclave TLS).
- Runs the TCP Proxy thread. A non-blocking TCP listener on the configured port (default
0.0.0.0:443) accepts connections, assigns aconn_id, and shuttles raw TCP bytes through the Data Channel. All sockets are non-blocking; the proxy polls both the listener and the enclave output queue in a single loop. - Runs the RPC Dispatcher thread. Spin-polls the RPC Channel for enclave requests (KV reads/writes, egress HTTP, etc.) and dispatches them to the appropriate host-side handler.
- Persists encrypted KV data to a RocksDB store on behalf of the enclave. The host never sees plaintext, only AES-256-GCM ciphertexts.
The host has no access to TLS keys, plaintext application data, or the master sealing key.
Enclave (trusted)
The enclave runs inside SGX-protected memory. It:
- Generates or unseals its master key. On first boot, generates a 256-bit master key via SGX's hardware random number generator and seals it to MRENCLAVE. On subsequent boots, unseals the existing key.
- Generates the RA-TLS certificate. An ECDSA P-256 key pair is generated inside the enclave, bound to an SGX quote via
ReportData, and wrapped in an X.509 certificate signed by the intermediary CA. - Runs the TLS 1.3 server. Raw TCP bytes arrive from the host; the enclave performs the TLS handshake, decrypts HTTPS requests, and dispatches them to registered modules.
- Manages the WASM runtime. Loads wasmtime Component Model modules, enforcing per-app namespace isolation for KV storage and filesystem access.
- Makes HTTPS egress requests. Modules can call out to external services; TLS termination happens inside the enclave with network I/O proxied through the host RPC layer.
- Maintains the Merkle tree. All configuration (CA cert, egress CA bundle, WASM app hashes, module config) is captured in a Merkle tree whose root is embedded in the RA-TLS certificate.
Certificate Trust Chain
Root CA (privasys.root-ca.crt)
└── Intermediary CA (sealed inside enclave)
└── Leaf RA-TLS certificate (generated per-connection)
├── Extension: SGX Quote (OID 1.2.840.113741.1.13.1.0)
├── Extension: Config Merkle Root (OID 1.3.6.1.4.1.65230.1.1)
├── Extension: Egress CA Hash (OID 1.3.6.1.4.1.65230.2.1) ← egress module
└── Extension: WASM Code Hash (OID 1.3.6.1.4.1.65230.2.3) ← wasm moduleThe intermediary CA cert + key are passed to the enclave at init time, sealed to disk as part of the unified SealedConfig (MRENCLAVE policy), and kept only in encrypted enclave memory. The leaf certificate is generated per-connection with an embedded SGX quote and signed by the intermediary CA. Module OIDs are registered by each module via custom_oids() and embedded as non-critical X.509 extensions.
ECALL / OCALL Interface
Enclave OS uses a deliberately narrow EDL (Enclave Definition Language) interface with only 4 ECALLs and 1 OCALL:
ECALLs (Host → Enclave)
| ECALL | Purpose |
|---|---|
ecall_init_channel | Sets up the RPC SPSC queue pair between host and enclave. |
ecall_init_data_channel | Sets up the Data SPSC queue pair used by the TCP proxy for raw network I/O. |
ecall_run | Starts the enclave's main event loop. The enclave reads from both channels, processes HTTPS requests, and writes responses. |
ecall_shutdown | Gracefully shuts down the enclave, flushing sealed state. |
OCALLs (Enclave → Host)
| OCALL | Purpose |
|---|---|
ocall_notify | A single notification OCALL that the enclave uses to signal the host. All rich communication happens through the shared SPSC queues and RPC protocol, not through OCALL parameters. |
This minimal interface is a key security property: the attack surface between trusted and untrusted code is just five functions.
Data Flow: An HTTPS Request
Here's how a client HTTPS request flows through the system:
- Client connects to port 443. The host TCP Proxy thread accepts the connection, assigns a
conn_id, and sends aTcpNewmessage on the Data Channel. - TCP Proxy reads raw TCP bytes from the socket and writes
TcpDataframes to the Data Channel. - Enclave reads the data frames, performs the TLS 1.3 handshake (sending the RA-TLS certificate), and decrypts the HTTP request.
- Enclave dispatches the request to registered modules (e.g., WASM, vault, egress).
- Module processes the request. If it needs KV or egress, it sends requests over the RPC Channel, independently of the data plane.
- Enclave encrypts the HTTP response under TLS and writes the ciphertext back to the Data Channel.
- TCP Proxy reads the enclave's TLS output from the Data Channel and writes it to the client socket.
At no point does the host see plaintext HTTP payloads or TLS session keys.
Production Deployment
Because TLS terminates inside the enclave, any front-end load balancer must operate at Layer 4 (TCP passthrough) and must not terminate TLS itself. The load balancer forwards opaque TCP to the host's TCP Proxy port, which relays into the enclave.
See the Layer 4 Proxy guide for Caddy (caddy-l4) and HAProxy configurations, including SNI-based routing for multiple enclave instances.