Overview
Client Setup
import { EnclaveClient } from '@enclave-vm/client';
const client = new EnclaveClient({
serverUrl: 'https://broker.example.com',
// Enable encryption
encryption: {
enabled: true,
curve: 'P-256', // ECDH curve
},
// Optional: Custom key management
keyProvider: {
getPublicKey: async () => myKeyPair.publicKey,
sign: async (data) => sign(myKeyPair.privateKey, data),
},
});
ECDH Key Exchange Implementation
// crypto.ts
export async function generateKeyPair(): Promise<CryptoKeyPair> {
return crypto.subtle.generateKey(
{
name: 'ECDH',
namedCurve: 'P-256',
},
true,
['deriveKey', 'deriveBits']
);
}
export async function deriveSharedSecret(
privateKey: CryptoKey,
publicKey: CryptoKey
): Promise<CryptoKey> {
return crypto.subtle.deriveKey(
{
name: 'ECDH',
public: publicKey,
},
privateKey,
{
name: 'AES-GCM',
length: 256,
},
true,
['encrypt', 'decrypt']
);
}
export async function exportPublicKey(key: CryptoKey): Promise<string> {
const exported = await crypto.subtle.exportKey('spki', key);
return Buffer.from(exported).toString('base64');
}
export async function importPublicKey(base64: string): Promise<CryptoKey> {
const keyData = Buffer.from(base64, 'base64');
return crypto.subtle.importKey(
'spki',
keyData,
{
name: 'ECDH',
namedCurve: 'P-256',
},
true,
[]
);
}
Encryption/Decryption
// encryption.ts
export async function encrypt(
key: CryptoKey,
data: string
): Promise<EncryptedPayload> {
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(data);
const ciphertext = await crypto.subtle.encrypt(
{
name: 'AES-GCM',
iv,
},
key,
encoded
);
return {
iv: Buffer.from(iv).toString('base64'),
ciphertext: Buffer.from(ciphertext).toString('base64'),
};
}
export async function decrypt(
key: CryptoKey,
payload: EncryptedPayload
): Promise<string> {
const iv = Buffer.from(payload.iv, 'base64');
const ciphertext = Buffer.from(payload.ciphertext, 'base64');
const decrypted = await crypto.subtle.decrypt(
{
name: 'AES-GCM',
iv,
},
key,
ciphertext
);
return new TextDecoder().decode(decrypted);
}
interface EncryptedPayload {
iv: string;
ciphertext: string;
}
Encrypted Client
import {
generateKeyPair,
deriveSharedSecret,
exportPublicKey,
importPublicKey,
encrypt,
decrypt,
} from './crypto';
class EncryptedEnclaveClient {
private keyPair: CryptoKeyPair | null = null;
private sharedSecret: CryptoKey | null = null;
private sessionId: string | null = null;
constructor(private serverUrl: string) {}
async connect(): Promise<void> {
// Generate client key pair
this.keyPair = await generateKeyPair();
const clientPublicKey = await exportPublicKey(this.keyPair.publicKey);
// Send ClientHello
const response = await fetch(`${this.serverUrl}/handshake`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
type: 'ClientHello',
publicKey: clientPublicKey,
}),
});
const serverHello = await response.json();
// Import server public key
const serverPublicKey = await importPublicKey(serverHello.publicKey);
// Derive shared secret
this.sharedSecret = await deriveSharedSecret(
this.keyPair.privateKey,
serverPublicKey
);
this.sessionId = serverHello.sessionId;
}
async execute(code: string): Promise<ExecutionResult> {
if (!this.sharedSecret || !this.sessionId) {
throw new Error('Not connected. Call connect() first.');
}
// Encrypt code
const encryptedCode = await encrypt(this.sharedSecret, code);
// Send encrypted request
const response = await fetch(`${this.serverUrl}/execute`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-Id': this.sessionId,
},
body: JSON.stringify({
encrypted: true,
...encryptedCode,
}),
});
// Decrypt response
const encryptedResult = await response.json();
const decryptedResult = await decrypt(this.sharedSecret, encryptedResult);
return JSON.parse(decryptedResult);
}
async *executeStreaming(
code: string
): AsyncGenerator<StreamEvent, void, unknown> {
if (!this.sharedSecret || !this.sessionId) {
throw new Error('Not connected. Call connect() first.');
}
// Encrypt code
const encryptedCode = await encrypt(this.sharedSecret, code);
// Open streaming connection
const response = await fetch(`${this.serverUrl}/execute/stream`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Session-Id': this.sessionId,
},
body: JSON.stringify({
encrypted: true,
...encryptedCode,
}),
});
if (!response.body) {
throw new Error('Response body is null');
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
buffer = lines.pop() || '';
for (const line of lines) {
if (!line.trim()) continue;
const encryptedEvent = JSON.parse(line);
const decryptedEvent = await decrypt(
this.sharedSecret!,
encryptedEvent
);
yield JSON.parse(decryptedEvent);
}
}
}
}
Server-Side Implementation
import {
generateKeyPair,
deriveSharedSecret,
exportPublicKey,
importPublicKey,
encrypt,
decrypt,
} from './crypto';
import { Enclave } from '@enclave-vm/core';
interface Session {
id: string;
sharedSecret: CryptoKey;
createdAt: Date;
}
const sessions = new Map<string, Session>();
// Handshake endpoint
app.post('/handshake', async (req, res) => {
const { publicKey: clientPublicKey } = req.body;
// Generate server key pair
const serverKeyPair = await generateKeyPair();
const serverPublicKey = await exportPublicKey(serverKeyPair.publicKey);
// Import client public key
const clientKey = await importPublicKey(clientPublicKey);
// Derive shared secret
const sharedSecret = await deriveSharedSecret(
serverKeyPair.privateKey,
clientKey
);
// Create session
const sessionId = crypto.randomUUID();
sessions.set(sessionId, {
id: sessionId,
sharedSecret,
createdAt: new Date(),
});
res.json({
type: 'ServerHello',
publicKey: serverPublicKey,
sessionId,
});
});
// Encrypted execute endpoint
app.post('/execute', async (req, res) => {
const sessionId = req.headers['x-session-id'] as string;
const session = sessions.get(sessionId);
if (!session) {
return res.status(401).json({ error: 'Invalid session' });
}
// Decrypt request
const code = await decrypt(session.sharedSecret, req.body);
// Execute
const enclave = new Enclave({
securityLevel: 'STRICT',
toolHandler: async (name, args) => {
return executeTool(name, args);
},
});
try {
const result = await enclave.run(code);
// Encrypt response
const encryptedResult = await encrypt(
session.sharedSecret,
JSON.stringify(result)
);
res.json(encryptedResult);
} finally {
enclave.dispose();
}
});
// Encrypted streaming endpoint
app.post('/execute/stream', async (req, res) => {
const sessionId = req.headers['x-session-id'] as string;
const session = sessions.get(sessionId);
if (!session) {
return res.status(401).json({ error: 'Invalid session' });
}
// Decrypt request
const code = await decrypt(session.sharedSecret, req.body);
// Set streaming headers
res.setHeader('Content-Type', 'application/x-ndjson');
res.setHeader('Transfer-Encoding', 'chunked');
const enclave = new Enclave({
securityLevel: 'STRICT',
toolHandler: async (name, args) => {
// Emit encrypted tool call event
const event = { type: 'tool_call', tool: name, args };
const encrypted = await encrypt(
session.sharedSecret,
JSON.stringify(event)
);
res.write(JSON.stringify(encrypted) + '\n');
const result = await executeTool(name, args);
// Emit encrypted tool result event
const resultEvent = { type: 'tool_result', tool: name };
const encryptedResult = await encrypt(
session.sharedSecret,
JSON.stringify(resultEvent)
);
res.write(JSON.stringify(encryptedResult) + '\n');
return result;
},
onConsole: async (level, ...args) => {
const event = { type: 'stdout', data: args.join(' ') };
const encrypted = await encrypt(
session.sharedSecret,
JSON.stringify(event)
);
res.write(JSON.stringify(encrypted) + '\n');
},
});
try {
const result = await enclave.run(code);
// Final encrypted event
const finalEvent = { type: 'final', result };
const encrypted = await encrypt(
session.sharedSecret,
JSON.stringify(finalEvent)
);
res.write(JSON.stringify(encrypted) + '\n');
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
const errorEvent = { type: 'error', message: errorMessage };
const encrypted = await encrypt(
session.sharedSecret,
JSON.stringify(errorEvent)
);
res.write(JSON.stringify(encrypted) + '\n');
} finally {
res.end();
enclave.dispose();
}
});
Usage Example
async function main() {
// Create encrypted client
const client = new EncryptedEnclaveClient('https://api.example.com');
// Establish encrypted connection
await client.connect();
// Execute code - all data is encrypted end-to-end
const result = await client.execute(`
const secret = await callTool('secrets:get', { key: 'api_key' });
const response = await callTool('http:post', {
url: 'https://api.service.com/data',
headers: { 'Authorization': 'Bearer ' + secret },
body: { sensitive: 'data' },
});
return response;
`);
console.log('Result:', result);
}
React Integration
import React, { useState, useEffect } from 'react';
import { EncryptedEnclaveClient } from './encrypted-client';
export function SecureCodeEditor() {
const [client, setClient] = useState<EncryptedEnclaveClient | null>(null);
const [connected, setConnected] = useState(false);
const [code, setCode] = useState('');
const [result, setResult] = useState<unknown>(null);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const newClient = new EncryptedEnclaveClient('https://api.example.com');
setClient(newClient);
let mounted = true;
newClient.connect()
.then(() => {
if (mounted) setConnected(true);
})
.catch((err) => {
if (mounted) setError(`Connection failed: ${err.message}`);
});
// Cleanup on unmount
return () => {
mounted = false;
// Disconnect if client supports it
// newClient.disconnect?.();
};
}, []);
const execute = async () => {
if (!client || !connected) return;
setError(null);
try {
const result = await client.execute(code);
setResult(result);
} catch (err) {
setError(`Execution failed: ${err instanceof Error ? err.message : String(err)}`);
}
};
return (
<div>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<span
style={{
width: '10px',
height: '10px',
borderRadius: '50%',
backgroundColor: connected ? '#22c55e' : '#ef4444',
}}
/>
<span>{connected ? 'Encrypted Connection' : 'Connecting...'}</span>
</div>
{error && <div style={{ color: '#ef4444' }}>{error}</div>}
<textarea
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="Enter code..."
/>
<button onClick={execute} disabled={!connected}>
Execute Securely
</button>
{result && <pre>{JSON.stringify(result, null, 2)}</pre>}
</div>
);
}
Security Considerations
- Key Rotation - Rotate session keys periodically
- Perfect Forward Secrecy - Use ephemeral keys for each session
- Certificate Pinning - Pin server certificates in production
- Secure Key Storage - Use secure storage for private keys
- Session Expiry - Expire sessions after inactivity
// Session cleanup
setInterval(() => {
const now = Date.now();
for (const [id, session] of sessions) {
// Expire sessions after 1 hour
if (now - session.createdAt.getTime() > 3600000) {
sessions.delete(id);
}
}
}, 60000);
Next Steps
- Security Hardening - More security practices
- Streaming Protocol - Protocol details
- Production Deployment - Deploy securely