Build PhyStack
Managing On-Device Secrets · Build PhyStack

Managing On-Device Secrets

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:

OperationPurposeWhen to Use
setStore or update a secretInitial setup, credential rotation
getRetrieve a secret valueApplication startup, API calls
delDelete a secretCleanup, 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 create to create an edge application, you can add the TPM socket configuration to the generated settings.json file.

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 error fields
  • 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:

  1. Choose Non-Obvious Secret Names

    Don't use: api-key, password, secret

    Do use: stripe-api-key-prod, postgres-main-db-password, oauth-client-secret-v2

  2. 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');
  3. Clear Secrets from Memory

    let apiKey = await getSecret('api-key');
    // Use the key...
    apiKey = null; // Clear the reference when done
  4. 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.

  5. Use Different Secrets per Environment

    Production devices should use different secret names than development devices (e.g., api-key-prod vs api-key-dev).

© 2026 · PhyStack. An Ombori company