For production KMS implementations, consider implementing the KMS-API in a C++ like language, or use typed arrays like Uint8Array for all sensitive data because JavaScript does not support secure memory management.
Recommended Alternatives:
- C++/Rust: Languages with explicit memory management and secure allocation
- Node.js Typed Arrays: Use
Uint8Arrayfor sensitive data with explicit zeroing - Native Addons: Implement cryptographic operations in native C++ modules
- Hardware Security: Use HSM-backed secure memory when available
This document provides a reference implementation for integrating the 4 KMS API's with Dinamo HSM, covering the complete request-response flow from API handlers to HSM operations.
real-dinamo-flow.ts- Complete handler-to-provider-to-HSM-to-database flow demonstrationdinamo-provider-implementation.ts- Core provider implementation patterns and methodsDINAMO_HSM_IMPLEMENTATION.md- Original deep-dive documentation (legacy)
The KMS API provides secure key management through four main endpoints that integrate with Dinamo HSM:
POST /key- Store private keys using envelope encryptionGET /key/{pub}- Retrieve private keys using envelope decryptionPOST /generateDataKey- Generate AES keys in HSM for encryptionPOST /decryptDataKey- Decrypt data keys using root keys
API Request → Handler → KMS Provider → Dinamo HSM → Database → Response
| API Endpoint | Handler File | Provider Method | HSM Operations |
|---|---|---|---|
POST /key |
storePrivateKey.ts |
postKey() |
Create AES key, export, encrypt |
GET /key/{pub} |
getPrivateKey.ts |
getKey() |
Decrypt data key locally |
POST /generateDataKey |
generateDataKey.ts |
generateDataKey() |
Create/export AES key |
POST /decryptDataKey |
decryptDataKey.ts |
decryptDataKey() |
Local SJCL decryption |
- Algorithm: RSA-2048 asymmetric keys
- Storage: Dinamo HSM hardware (permanent)
- Purpose: Encrypt/decrypt data keys
- Security: Never exported from HSM
- Algorithm: AES-256 symmetric keys
- Generation: Dinamo HSM (temporary keys)
- Export: Raw key material exported as Buffer
- Encryption: Encrypted with root key using SJCL
- Storage: Database (encrypted), Memory (plaintext, temporary)
- Encryption: AES-256-CCM using SJCL
- Key: Data key plaintext (from Layer 2)
- Storage: Database (encrypted only)
private async withClient<T>(fn: (client) => Promise<T>): Promise<T> {
const conn = await hsm.connect({
host: process.env.DINAMO_HOST || "",
authUsernamePassword: {
username: process.env.DINAMO_USERNAME || "",
password: process.env.DINAMO_PASSWORD || "",
},
});
try {
return await fn(conn);
} finally {
try {
await conn.disconnect();
} catch (e) {
logger.warn("Failed to disconnect from Dinamo HSM", e);
}
}
}Why Connection Management is Critical:
- Prevents Resource Leaks: Ensures HSM connections are properly closed to avoid dangling connections
- HSM Connection Limits: Hardware security modules have limited concurrent connection pools
- Network Stability: Prevents socket exhaustion and connection timeouts
- Security Best Practice: Minimizes attack surface by closing connections immediately after use
async createRootKey(): Promise<{ rootKey: string }> {
const keyName = getRandomHash(32);
return await this.withClient(async (client) => {
const created = await client.key.create(
keyName, // Unique key identifier
hsm.enums.RSA_ASYMMETRIC_KEYS.ALG_RSA_2048, // 2048-bit RSA
true, // Exportable for public key ops
false // Permanent storage
);
if (!created) {
throw { message: 'Failed to create symmetric key in HSM', code: 500 };
}
return { rootKey: keyName };
});
}HSM Operations:
- Key Naming: 32-character random hash for uniqueness
- Algorithm: RSA-2048 for asymmetric operations
- Exportable: Set to true to allow public key export
- Permanent: Root keys stored permanently in HSM
- Error Handling: Structured errors with HTTP codes
async generateDataKey(rootKey: string, keySpec: DataKeyTypeType): Promise<GenerateDataKeyKmsRes> {
return await this.withClient(async (client) => { // Connection auto-managed to prevent dangling connections
// 1. Create temporary AES key in HSM
const dataKeyName = getRandomHash(32);
const created = await client.key.create(
dataKeyName,
hsm.enums.SYMMETRICAL_KEYS.ALG_AES_256, // 256-bit AES
true, // Exportable
true // Temporary (auto-deleted)
);
// 2. Export plaintext key material
const exportedKey = await client.key.exportSymmetric(dataKeyName);
const plaintextKey = exportedKey.toString('base64');
// **CRITICAL SECURITY NOTE**: The plaintextKey contains raw cryptographic material
// and MUST be wiped from memory immediately after encryption operations.
// In production, use secure memory allocation and explicit zeroing.
// 3. Encrypt with root key (envelope encryption)
return {
encryptedKey: encrypt(rootKey, plaintextKey), // SJCL encryption
plaintextKey: plaintextKey, // For immediate use - WIPE AFTER USE
};
});
}Process Flow:
- Temporary Key Creation: AES-256 symmetric key in HSM
- Key Export: Raw key material extracted as Buffer
- Format Conversion: Buffer → base64 string
- Envelope Encryption: Encrypt plaintext with root key
- Automatic Cleanup: HSM deletes temporary key
⚠️ MEMORY SECURITY: Plaintext key must be wiped from memory after use
Security Considerations:
- Immediate Use: Plaintext keys should be used immediately after generation
- Memory Overwriting: Overwrite memory locations with random data before deallocation
- Garbage Collection: Force GC to clear memory pages containing sensitive data
- Process Isolation: Consider using separate processes for key operations
- Hardware Security: Use HSM-backed secure memory when available
async postKey(rootKey: string, prv: string): Promise<PostKeyKmsRes> {
// 1. Generate fresh data key for this private key
const dataKey = await this.generateDataKey(rootKey, 'AES-256');
let encryptedPrv: string;
try {
// 2. Encrypt private key with data key (use immediately)
encryptedPrv = encrypt(dataKey.plaintextKey, prv);
} finally {
// **CRITICAL**: Wipe plaintext data key from memory immediately after use
// Production code should implement secure memory wiping here
}
return {
encryptedPrv, // Encrypted private key
rootKeyId: rootKey, // Root key reference
metadata: {
encryptedDataKey: dataKey.encryptedKey, // Encrypted data key
},
};
}Memory Security Notes:
- Immediate Encryption: Use plaintext data key immediately for encryption
- Secure Disposal: Wipe plaintext key from memory after single use
- No Persistence: Never store plaintext data keys in variables or logs
- Error Handling: Ensure memory wiping occurs even if encryption fails
CREATE TABLE private_keys (
id INTEGER PRIMARY KEY AUTOINCREMENT,
pub TEXT NOT NULL, -- Public key (identifier)
source TEXT NOT NULL, -- 'user' or 'backup'
encryptedPrv TEXT NOT NULL, -- Private key encrypted with data key
encryptedDataKey TEXT NOT NULL, -- Data key encrypted with root key
provider TEXT NOT NULL, -- 'dinamo'
rootKey TEXT NOT NULL, -- Root key name in HSM
coin TEXT NOT NULL, -- Cryptocurrency type
type TEXT NOT NULL, -- Key type (e.g., 'tss')
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);Storage Pattern:
- encryptedPrv: SJCL AES-256-CCM encrypted private key
- encryptedDataKey: Root-key-encrypted data key
- rootKey: Reference to HSM root key name
- No plaintext: All sensitive data encrypted
- Algorithm: AES-256-CCM
- Iterations: 10,000 (PBKDF2)
- Key Size: 256 bits
- Tag Size: 128 bits
- Mode: CCM (Counter with CBC-MAC)
{
"iv": "a1b2c3d4e5f6...",
"v": 1,
"iter": 10000,
"ks": 256,
"ts": 128,
"mode": "ccm",
"adata": "",
"cipher": "aes",
"salt": "f6e5d4c3b2a1...",
"ct": "base64-encrypted-data"
}