PhyOS provides hardware-backed secret storage using TPM 2.0, enabling you to store sensitive data like API keys, certificates, and passwords directly in hardware-protected memory. This guide shows you how to use TPM secrets in your edge applications.
💡 Before You Start: TPM 2.0 support must be available on your device. See TPM 2.0 Support in Advanced PhyOS Features for platform requirements and hardware details.
Using TPM Secrets in Your Edge Application
Your applications interact with the TPM daemon through a Unix domain socket located at /run/tpmd.sock. The API is deliberately minimal—it exposes only three operations to reduce security risks:
| Operation | Purpose | When to Use |
|---|---|---|
set | Store or update a secret | Initial setup, credential rotation |
get | Retrieve a secret value | Application startup, API calls |
del | Delete a secret | Cleanup, credential revocation |
Configuring Your Edge Application
To enable TPM secrets in your application, you need to mount the TPM daemon socket into your container.
💡 Note: When you use
phy app createto create an edge application, you can add the TPM socket configuration to the generatedsettings.jsonfile.
Add the following to your application's settings.json:
{
"Image": "your-app:latest",
"Binds": [
"/run/tpmd.sock:/run/tpmd.sock"
]
}This socket bind mount gives your container direct access to the TPM daemon running on the host, no network configuration, ports, or additional services required.
API Protocol
The TPM daemon uses a simple JSON-based protocol over Unix sockets. Each request is a single JSON object, and the daemon responds with a single JSON object.
Request Format:
{
"action": "set|get|del",
"name": "secret-identifier",
"value": "secret-value" // Only required for 'set' action
}Response Formats:
Success response:
{
"ok": true,
"value": "..." // Present for 'get' operation
"index": "0x15200XX" // Present for 'set' operation (TPM address)
}Error response:
{
"error": "Description of what went wrong",
"rc": "0xXX" // TPM return code for debugging
}Common Workflows
Here are the typical operations you'll perform with TPM secrets:
Workflow 1: Initial Secret Setup
Store an API key during application deployment:
{"action":"set","name":"third-party-api-key","value":"sk_live_abc123xyz"}Response:
{"ok":true,"index":"0x01520042"}The secret is now stored in TPM hardware. The index value shows where in TPM memory it's stored (useful for debugging but not needed for retrieval).
Workflow 2: Retrieving Secrets at Runtime
Your application starts and needs to fetch the API key:
{"action":"get","name":"third-party-api-key"}Response:
{"ok":true,"value":"sk_live_abc123xyz"}Workflow 3: Credential Rotation
Update an existing secret with a new value:
{"action":"set","name":"third-party-api-key","value":"sk_live_new_key_456"}Response:
{"ok":true,"index":"0x01520042"}Note that the index stays the same—the daemon overwrites the existing secret in place.
Workflow 4: Removing Secrets
Delete a secret when it's no longer needed:
{"action":"del","name":"third-party-api-key"}Response:
{"ok":true}The TPM NV memory slots are freed and can be reused for other secrets.
💡 Important: Secret names act as identifiers. Choose unique names, but avoid names that reveal sensitive business logic.
Implementation Example
The TPM daemon API works with any programming language that supports Unix sockets. Here are code snippets showing how to perform each operation:
Store a Secret (Set):
const net = require('net');
function setSecret(name, value) {
return new Promise((resolve, reject) => {
const client = net.createConnection('/run/tpmd.sock', () => {
const request = JSON.stringify({ action: 'set', name, value });
client.write(request);
});
let response = '';
client.on('data', (data) => { response += data.toString(); });
client.on('end', () => resolve(JSON.parse(response)));
client.on('error', reject);
});
}
// Usage
await setSecret('abcd123', 'sk_live_...');
// Response: {"ok":true,"index":"0x01520042"}Retrieve a Secret (Get):
function getSecret(name) {
return new Promise((resolve, reject) => {
const client = net.createConnection('/run/tpmd.sock', () => {
const request = JSON.stringify({ action: 'get', name });
client.write(request);
});
let response = '';
client.on('data', (data) => { response += data.toString(); });
client.on('end', () => {
const result = JSON.parse(response);
resolve(result.value);
});
client.on('error', reject);
});
}
// Usage
const apiKey = await getSecret('abcd123');
// Returns: "sk_live_..."Delete a Secret (Del):
function deleteSecret(name) {
return new Promise((resolve, reject) => {
const client = net.createConnection('/run/tpmd.sock', () => {
const request = JSON.stringify({ action: 'del', name });
client.write(request);
});
let response = '';
client.on('data', (data) => { response += data.toString(); });
client.on('end', () => resolve(JSON.parse(response)));
client.on('error', reject);
});
}
// Usage
await deleteSecret('abcd123');
// Response: {"ok":true}💡 Implementation Notes:
- These examples use Node.js, but the same pattern works in any language with Unix socket support
- Each operation opens a new connection, sends a JSON request, reads the response, and closes
- Always parse the JSON response and check for
errorfields- In production, add proper error handling and retry logic
Real-World Use Cases
TPM 2.0 support enables practical security improvements for edge applications:
Use Case 1: Third-Party API Integration
Problem: Your application needs to call external APIs (payment processors, cloud services) that require API keys. Storing these in environment variables or config files creates exposure risk.
Solution: Store API keys in TPM during deployment, retrieve them when needed:
// One-time setup
await setSecret('stripe-api-key', process.env.STRIPE_KEY);
// Runtime usage
const apiKey = await getSecret('stripe-api-key');
const stripe = require('stripe')(apiKey);Benefit: API keys never appear in logs, backups, or container inspections.
Use Case 2: Database Credentials
Problem: Applications often connect to local or remote databases. Database passwords in config files are a common security vulnerability.
Solution: Store database credentials in TPM:
const dbPassword = await getSecret('postgres-password');
const db = new PostgresClient({
user: 'app_user',
password: dbPassword,
database: 'application_db'
});Benefit: Operations staff can manage the system without ever seeing database passwords.
Use Case 3: Certificate Private Keys
Problem: SSL/TLS certificates require private keys. Storing these in the filesystem creates risk if the device is compromised.
Solution: Store certificate private keys in TPM:
const privateKey = await getSecret('ssl-private-key');
const server = https.createServer({
key: privateKey,
cert: fs.readFileSync('certificate.pem')
}, app);Benefit: Private keys are protected by hardware, not just filesystem permissions.
Use Case 4: Credential Rotation
Problem: Security best practices require periodic credential rotation, but this is complex with filesystem-based storage.
Solution: Update secrets in TPM without redeploying:
// Rotate API key
await setSecret('api-key', newApiKeyFromProvider);Benefit: Rotate credentials without container rebuilds or file edits.
Security Model and Considerations
What TPM Protects Against:
- Filesystem Exposure: Secrets never appear in files, so they can't be accidentally exposed through backups, logs, or file system access
- Log Leakage: Secrets aren't loaded from environment variables or config files that might be logged during startup
- Backup Exposure: Device backups won't contain secret values since they're not in the filesystem
- Operations Access: Operations staff can manage devices without seeing application credentials
What to Understand:
The TPM socket (/run/tpmd.sock) is accessible to processes running on the device. This is by design, it allows your edge applications to use TPM secrets without complex permission management. However, this means:
- Any local process can attempt to retrieve secrets if it knows the secret name
- The API doesn't list existing secrets (no enumeration capability)
- Secret names act as access credentials to retrieve the actual values
Think of it like this: Secret names are like safe deposit box numbers. If you know the number, you can access that box. But you can't browse which boxes exist, and you have to know the exact number.
Security Best Practices:
-
Choose Non-Obvious Secret Names
Don't use:
api-key,password,secretDo use:
stripe-api-key-prod,postgres-main-db-password,oauth-client-secret-v2 -
Never Log Secret Values
// Bad const secret = await getSecret('api-key'); console.log(`Using API key: ${secret}`); // Good const secret = await getSecret('api-key'); console.log('API key retrieved from TPM'); -
Clear Secrets from Memory
let apiKey = await getSecret('api-key'); // Use the key... apiKey = null; // Clear the reference when done -
Document Secret Names Securely
Keep a separate, secure list of what secret names your application uses. Don't hardcode a directory of all secret names in your application code.
-
Use Different Secrets per Environment
Production devices should use different secret names than development devices (e.g.,
api-key-prodvsapi-key-dev).