Documentation Banner

Documentation

TEE Plugins

Advanced Topic

This guide is for developers who need to extend the TEE signer with custom cryptographic operations or specialized functionality. Plugins have direct access to the signing key.

Overview

TEE plugins allow you to extend the trusted execution environment with custom operations while maintaining the security guarantees of hardware-protected keys. Plugins have full access to the signing key and can perform any cryptographic operation within the secure enclave.

Security Benefits

  • • Operations execute inside TEE
  • • Direct access to sealed keys
  • • No key material exposed
  • • Hardware attestation preserved

Use Cases

  • • Custom signature schemes
  • • Specialized encryption
  • • Protocol-specific operations
  • • Extended attestation formats

Plugin Structure

Plugins are Rust modules that implement the Plugin trait. They are automatically discovered at compile time by the build system and compiled into the TEE signer WASM binary.

Important: Plugins must be placed in the tee/src/plugins/directory. Each plugin must export a PLUGIN constant. The build system automatically discovers and registers all plugins.

Basic Plugin Template

1// tee/src/plugins/my_plugin/mod.rs
2use crate::plugin::Plugin;
3use serde_json::{json, Value};
4use k256::ecdsa::SigningKey;
5
6pub struct MyPlugin;
7
8impl Plugin for MyPlugin {
9 fn name(&self) -> &str {
10 "my_plugin"
11 }
12
13 fn methods(&self) -> Vec<&str> {
14 vec!["custom_sign", "get_info", "derive_key"]
15 }
16
17 fn handle(
18 &self,
19 method: &str,
20 params: Option<&Value>,
21 signing_key: Option<&SigningKey>
22 ) -> Result<Value, String> {
23 match method {
24 "custom_sign" => self.custom_sign(params, signing_key),
25 "get_info" => self.get_info(),
26 "derive_key" => self.derive_key(params, signing_key),
27 _ => Err(format!("Unknown method: {}", method))
28 }
29 }
30}
31
32impl MyPlugin {
33 fn custom_sign(
34 &self,
35 params: Option<&Value>,
36 signing_key: Option<&SigningKey>
37 ) -> Result<Value, String> {
38 // Get the signing key (required for this operation)
39 let key = signing_key.ok_or("No signing key available")?;
40
41 // Parse message from params
42 let message = params
43 .and_then(|p| p.get("message"))
44 .and_then(|m| m.as_str())
45 .ok_or("Missing message parameter")?;
46
47 // Perform custom signing with domain separation
48 use k256::ecdsa::signature::Signer;
49 use sha2::{Sha256, Digest};
50
51 // Add custom prefix for domain separation
52 let mut hasher = Sha256::new();
53 hasher.update(b"MyPlugin:");
54 hasher.update(message.as_bytes());
55 let digest = hasher.finalize();
56
57 // Sign the digest
58 let signature = key.sign(&digest[..]);
59
60 Ok(json!({
61 "signature": format!("0x{}", hex::encode(signature.to_bytes())),
62 "message": message,
63 "prefix": "MyPlugin"
64 }))
65 }
66
67 fn get_info(&self) -> Result<Value, String> {
68 Ok(json!({
69 "plugin": self.name(),
70 "version": "1.0.0",
71 "methods": self.methods(),
72 "description": "Custom plugin for specialized operations"
73 }))
74 }
75
76 fn derive_key(
77 &self,
78 params: Option<&Value>,
79 signing_key: Option<&SigningKey>
80 ) -> Result<Value, String> {
81 let key = signing_key.ok_or("No signing key available")?;
82
83 let path = params
84 .and_then(|p| p.get("path"))
85 .and_then(|p| p.as_str())
86 .ok_or("Missing path parameter")?;
87
88 // Derive a deterministic key using HKDF-like construction
89 use sha2::{Sha256, Digest};
90
91 let mut hasher = Sha256::new();
92 hasher.update(key.to_bytes());
93 hasher.update(b"derive:");
94 hasher.update(path.as_bytes());
95 let derived = hasher.finalize();
96
97 Ok(json!({
98 "derived_key": format!("0x{}", hex::encode(derived)),
99 "path": path
100 }))
101 }
102}
103
104// CRITICAL: Must export as PLUGIN constant for auto-discovery
105pub const PLUGIN: MyPlugin = MyPlugin;

Building and Installing Plugins

Step 1: Create Plugin Directory

Create your plugin in the tee/src/plugins/ directory:

# Create your plugin directory structure
mkdir -p tee/src/plugins/my_plugin
# Create the plugin module
touch tee/src/plugins/my_plugin/mod.rs

Step 2: How the Build System Works

The existing build.rs automatically discovers all plugins in the src/plugins/ directory:

// tee/build.rs (already exists - no need to create)
use std::env;
use std::fs;
use std::io::Write;
use std::path::Path;
fn main() {
// Re-run build if plugins directory changes
println!("cargo:rerun-if-changed=src/plugins/");
let out_dir = env::var("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("plugin_loader.rs");
let mut file = fs::File::create(&dest_path).unwrap();
writeln!(file, "fn load_plugins() {{").unwrap();
// Scan the src/plugins/ directory
let plugins_dir = Path::new("src/plugins");
if plugins_dir.exists() {
for entry in fs::read_dir(plugins_dir).unwrap() {
let entry = entry.unwrap();
let path = entry.path();
if path.is_dir() {
let name = path.file_name().unwrap().to_str().unwrap();
// Generate code to load each plugin
writeln!(file, " #[path = \"../../src/plugins/{}/mod.rs\"]", name).unwrap();
writeln!(file, " mod {};", name).unwrap();
writeln!(file, " if let Ok(mut plugins) = PLUGINS.write() {{").unwrap();
writeln!(file, " plugins.insert(\"{}\".to_string(), Box::new({}::PLUGIN));", name, name).unwrap();
writeln!(file, " }}").unwrap();
}
}
}
writeln!(file, "}}").unwrap();
}

Step 3: Build the WASM Binary

# Build with your plugin included
cd tee
cargo build --target wasm32-wasip1 --release
# Your plugin is now part of the TEE signer
# The build system automatically discovered and included it

Directory Structure

tee/
├── build.rs # Auto-discovery system (already exists)
├── src/
│ ├── main.rs # Main TEE signer
│ ├── plugin.rs # Plugin trait definition
│ ├── attestation.rs
│ ├── crypto.rs
│ └── ...
├── src/plugins/ # Your plugins go here
│ ├── example/ # Example plugin (if exists)
│ │ └── mod.rs
│ └── my_plugin/ # Your custom plugin
│ └── mod.rs
└── Cargo.toml

Using Plugins

Once built, plugins are accessible through the TEE signer using the format pluginName_methodName:

Via WebSocket Bridge

// Call plugin method through the bridge
ws.send(JSON.stringify({
type: 'tee_call',
id: 'plugin-1',
method: 'my_plugin_custom_sign', // Format: pluginName_methodName
params: {
message: 'Hello World'
}
}));
// Response
{
"type": "tee_response",
"id": "plugin-1",
"result": {
"signature": "0x3045022100...",
"message": "Hello World",
"prefix": "MyPlugin"
}
}

Direct TEE Client

// Using TEESigner directly
const signer = new TEESigner({ mode: 'development' });
await signer.connect();
// Call plugin method
const result = await signer.callTEE('my_plugin_custom_sign', {
message: 'Hello World'
});
console.log('Custom signature:', result.data.signature);
console.log('Prefix used:', result.data.prefix);

Common Issues

Plugin Not Found

If your plugin isn’t being discovered:

  • • Ensure it’s in tee/src/plugins/
  • • Check that your plugin exports pub const PLUGIN: YourPlugin = YourPlugin;
  • • Verify the directory name matches the module name
  • • Run cargo clean and rebuild

Method Not Found

Remember to use the format pluginName_methodName when calling methods. The plugin name comes from the name() function, not the directory name.

Best Practices

Do’s

  • ✓ Place plugins in tee/src/plugins/
  • ✓ Export the PLUGIN constant
  • ✓ Validate all input parameters
  • ✓ Use constant-time crypto operations
  • ✓ Handle missing signing key gracefully
  • ✓ Clear sensitive data after use
  • ✓ Test with dev and production modes

Don’ts

  • ✗ Don’t modify plugin trait implementations without understanding security implications
  • ✗ Never log private keys
  • ✗ Don’t expose key material in errors
  • ✗ Avoid time-based side channels
  • ✗ Don’t trust external input
  • ✗ Never store keys outside TEE
  • ✗ Don’t forget the PLUGIN export

Plugin Trait Reference

// The Plugin trait that all plugins must implement
pub trait Plugin: Send + Sync {
/// Plugin identifier used in method routing
fn name(&self) -> &str;
/// List of methods this plugin provides
fn methods(&self) -> Vec<&str>;
/// Handle method calls with optional access to signing key
///
/// # Parameters
/// - method: The method name (without plugin prefix)
/// - params: Optional JSON parameters
/// - signing_key: Optional access to the TEE signing key
///
/// # Returns
/// - Ok(Value): JSON response on success
/// - Err(String): Error message on failure
fn handle(
&self,
method: &str,
params: Option<&Value>,
signing_key: Option<&SigningKey>
) -> Result<Value, String>;
}

Next Steps