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.rs2use crate::plugin::Plugin;3use serde_json::{json, Value};4use k256::ecdsa::SigningKey;56pub struct MyPlugin;78impl Plugin for MyPlugin {9 fn name(&self) -> &str {10 "my_plugin"11 }1213 fn methods(&self) -> Vec<&str> {14 vec!["custom_sign", "get_info", "derive_key"]15 }1617 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}3132impl 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")?;4041 // Parse message from params42 let message = params43 .and_then(|p| p.get("message"))44 .and_then(|m| m.as_str())45 .ok_or("Missing message parameter")?;4647 // Perform custom signing with domain separation48 use k256::ecdsa::signature::Signer;49 use sha2::{Sha256, Digest};5051 // Add custom prefix for domain separation52 let mut hasher = Sha256::new();53 hasher.update(b"MyPlugin:");54 hasher.update(message.as_bytes());55 let digest = hasher.finalize();5657 // Sign the digest58 let signature = key.sign(&digest[..]);5960 Ok(json!({61 "signature": format!("0x{}", hex::encode(signature.to_bytes())),62 "message": message,63 "prefix": "MyPlugin"64 }))65 }6667 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 }7576 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")?;8283 let path = params84 .and_then(|p| p.get("path"))85 .and_then(|p| p.as_str())86 .ok_or("Missing path parameter")?;8788 // Derive a deterministic key using HKDF-like construction89 use sha2::{Sha256, Digest};9091 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();9697 Ok(json!({98 "derived_key": format!("0x{}", hex::encode(derived)),99 "path": path100 }))101 }102}103104// CRITICAL: Must export as PLUGIN constant for auto-discovery105pub 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 structuremkdir -p tee/src/plugins/my_plugin# Create the plugin moduletouch 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 changesprintln!("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/ directorylet 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 pluginwriteln!(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 includedcd teecargo 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 bridgews.send(JSON.stringify({type: 'tee_call',id: 'plugin-1',method: 'my_plugin_custom_sign', // Format: pluginName_methodNameparams: {message: 'Hello World'}}));// Response{"type": "tee_response","id": "plugin-1","result": {"signature": "0x3045022100...","message": "Hello World","prefix": "MyPlugin"}}
Direct TEE Client
// Using TEESigner directlyconst signer = new TEESigner({ mode: 'development' });await signer.connect();// Call plugin methodconst 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 cleanand 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 implementpub trait Plugin: Send + Sync {/// Plugin identifier used in method routingfn name(&self) -> &str;/// List of methods this plugin providesfn 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 failurefn handle(&self,method: &str,params: Option<&Value>,signing_key: Option<&SigningKey>) -> Result<Value, String>;}
