From 3e954c75fd8428d56812876d6124d17445b5ecb6 Mon Sep 17 00:00:00 2001 From: Cesar Patino Date: Tue, 12 Aug 2025 16:41:17 -0400 Subject: [PATCH 1/3] feat(awm): demo script for dinamo internface --- demo-kms-script/dinamo-interface.md | 266 ++++++++++++++++++++++++++++ 1 file changed, 266 insertions(+) create mode 100644 demo-kms-script/dinamo-interface.md diff --git a/demo-kms-script/dinamo-interface.md b/demo-kms-script/dinamo-interface.md new file mode 100644 index 0000000..b68c0d2 --- /dev/null +++ b/demo-kms-script/dinamo-interface.md @@ -0,0 +1,266 @@ +# Dinamo HSM KMS Implementation Documentation + +This document provides comprehensive documentation for the KMS API's integration with Dinamo HSM, covering the complete request-response flow from API handlers to HSM operations. + +## Demo Scripts + +- **`real-dinamo-flow.ts`** - Complete handler-to-provider-to-HSM-to-database flow demonstration +- **`dinamo-provider-implementation.ts`** - Core provider implementation patterns and methods +- **`DINAMO_HSM_IMPLEMENTATION.md`** - Original deep-dive documentation (legacy) + +## Quick Overview + +The KMS API provides secure key management through four main endpoints that integrate with Dinamo HSM: + +- `POST /key` - Store private keys using envelope encryption +- `GET /key/{pub}` - Retrieve private keys using envelope decryption +- `POST /generateDataKey` - Generate AES keys in HSM for encryption +- `POST /decryptDataKey` - Decrypt data keys using root keys + +## Architecture Flow + +``` +API Request → Handler → KMS Provider → Dinamo HSM → Database → Response +``` + +### Handler-to-Provider Mapping + +| 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 | + +## Envelope Encryption Pattern + +### Layer 1: Root Keys (HSM) +- **Algorithm**: RSA-2048 asymmetric keys +- **Storage**: Dinamo HSM hardware (permanent) +- **Purpose**: Encrypt/decrypt data keys +- **Security**: Never exported from HSM + +### Layer 2: Data Keys (Generated in HSM, Used Locally) +- **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) + +### Layer 3: Private Keys (Application Data) +- **Encryption**: AES-256-CCM using SJCL +- **Key**: Data key plaintext (from Layer 2) +- **Storage**: Database (encrypted only) + +## Implementation Details + +### Connection Management Pattern + +```typescript +private async withClient(fn: (client) => Promise): Promise { + 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); + } + } +} +``` + +**Key Features:** +- Environment-based configuration +- Automatic connection cleanup using try/finally +- Error handling for disconnect failures +- Generic typing for return values +- Centralized connection logic for all HSM operations + +### Root Key Creation + +```typescript +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 + +### Data Key Generation Process + +```typescript +async generateDataKey(rootKey: string, keySpec: DataKeyTypeType): Promise { + return await this.withClient(async (client) => { + // 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'); + + // 3. Encrypt with root key (envelope encryption) + return { + encryptedKey: encrypt(rootKey, plaintextKey), // SJCL encryption + plaintextKey: plaintextKey, // For immediate use + }; + }); +} +``` + +**Process Flow:** +1. **Temporary Key Creation**: AES-256 symmetric key in HSM +2. **Key Export**: Raw key material extracted as Buffer +3. **Format Conversion**: Buffer → base64 string +4. **Envelope Encryption**: Encrypt plaintext with root key +5. **Automatic Cleanup**: HSM deletes temporary key + +### Private Key Storage (POST /key) + +```typescript +async postKey(rootKey: string, prv: string): Promise { + // 1. Generate fresh data key for this private key + const dataKey = await this.generateDataKey(rootKey, 'AES-256'); + + // 2. Encrypt private key with data key + const encryptedPrv = encrypt(dataKey.plaintextKey, prv); + + return { + encryptedPrv, // Encrypted private key + rootKeyId: rootKey, // Root key reference + metadata: { + encryptedDataKey: dataKey.encryptedKey, // Encrypted data key + }, + }; +} +``` + +**Encryption Layers:** +- **Layer 1**: Private Key → AES-256-CCM → Encrypted Private Key + - Uses plaintextKey from HSM-generated data key + - SJCL library for AES-256-CCM encryption +- **Layer 2**: Data Key → Local AES → Encrypted Data Key + - Uses root key as password + - Custom encrypt() function from utils/encrypt.ts + +### Private Key Retrieval (GET /key/{pub}) + +```typescript +async getKey(rootKey: string, keyId: string, options: GetKeyOptions): Promise { + // 1. Decrypt data key using root key + const decryptedKey = await this.decryptDataKey(rootKey, options.encryptedDataKey); + + // 2. Convert data key format + const aesKeyBuffer = Buffer.from(decryptedKey.plaintextKey, 'base64'); + const password = aesKeyBuffer.toString('base64'); + + // 3. Decrypt private key with recovered data key + const decryptedPrv = decrypt(password, keyId); + + return { prv: decryptedPrv }; +} +``` + +**Decryption Process:** +1. **Data Key Recovery**: Decrypt encrypted data key with root key +2. **Format Conversion**: base64 → Buffer → base64 (consistent format) +3. **Private Key Decryption**: Use recovered data key to decrypt private key + +### Data Key Decryption + +```typescript +async decryptDataKey(rootKey: string, encryptedKey: string): Promise { + return { + plaintextKey: decrypt(rootKey, encryptedKey), + }; +} +``` + +**Implementation Notes:** +- **Local Operation**: Uses SJCL decryption, not HSM +- **Root Key as Password**: Simple symmetric decryption +- **Performance**: Fast local operation vs. slower HSM calls + +## Database Schema + +### private_keys Table + +```sql +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 + +## SJCL Encryption Details + +### Configuration +- **Algorithm**: AES-256-CCM +- **Iterations**: 10,000 (PBKDF2) +- **Key Size**: 256 bits +- **Tag Size**: 128 bits +- **Mode**: CCM (Counter with CBC-MAC) + +### Example SJCL Output +```json +{ + "iv": "a1b2c3d4e5f6...", + "v": 1, + "iter": 10000, + "ks": 256, + "ts": 128, + "mode": "ccm", + "adata": "", + "cipher": "aes", + "salt": "f6e5d4c3b2a1...", + "ct": "base64-encrypted-data" +} +``` \ No newline at end of file From 30ea0a0d18a2abb3f339aa45dd625dc3826b293a Mon Sep 17 00:00:00 2001 From: Cesar Patino Date: Wed, 13 Aug 2025 13:45:59 -0400 Subject: [PATCH 2/3] feat(awm): dinamo document update --- demo-kms-script/dinamo-interface.md | 127 +++++++++++++++------------- 1 file changed, 70 insertions(+), 57 deletions(-) diff --git a/demo-kms-script/dinamo-interface.md b/demo-kms-script/dinamo-interface.md index b68c0d2..372deb6 100644 --- a/demo-kms-script/dinamo-interface.md +++ b/demo-kms-script/dinamo-interface.md @@ -1,6 +1,6 @@ # Dinamo HSM KMS Implementation Documentation -This document provides comprehensive documentation for the KMS API's integration with Dinamo HSM, covering the complete request-response flow from API handlers to HSM operations. +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. ## Demo Scripts @@ -32,7 +32,7 @@ API Request → Handler → KMS Provider → Dinamo HSM → Database → Respons | `POST /generateDataKey` | `generateDataKey.ts` | `generateDataKey()` | Create/export AES key | | `POST /decryptDataKey` | `decryptDataKey.ts` | `decryptDataKey()` | Local SJCL decryption | -## Envelope Encryption Pattern +## Envelope Encryption Pattern (Recommended) ### Layer 1: Root Keys (HSM) - **Algorithm**: RSA-2048 asymmetric keys @@ -78,12 +78,11 @@ private async withClient(fn: (client) => Promise): Promise { } ``` -**Key Features:** -- Environment-based configuration -- Automatic connection cleanup using try/finally -- Error handling for disconnect failures -- Generic typing for return values -- Centralized connection logic for all HSM operations +**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 ### Root Key Creation @@ -119,7 +118,7 @@ async createRootKey(): Promise<{ rootKey: string }> { ```typescript async generateDataKey(rootKey: string, keySpec: DataKeyTypeType): Promise { - return await this.withClient(async (client) => { + 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( @@ -132,11 +131,15 @@ async generateDataKey(rootKey: string, keySpec: DataKeyTypeType): Promise { + let plaintextKey: string | null = null; + + try { + const result = await this.generateDataKey(rootKey, 'AES-256'); + plaintextKey = result.plaintextKey; + + // Use the plaintext key immediately + const encryptedData = encrypt(plaintextKey, sensitiveData); + + return { + encryptedKey: result.encryptedKey, + encryptedData: encryptedData + }; + } finally { + // **CRITICAL**: Explicitly wipe plaintext key from memory + if (plaintextKey) { + // Overwrite with random data multiple times + for (let i = 0; i < 3; i++) { + plaintextKey = crypto.randomBytes(plaintextKey.length).toString('base64'); + } + plaintextKey = null; + } + + // Force garbage collection (if available) + if (global.gc) { + global.gc(); + } + } +} +``` + +**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 ### Private Key Storage (POST /key) @@ -156,8 +203,14 @@ async postKey(rootKey: string, prv: string): Promise { // 1. Generate fresh data key for this private key const dataKey = await this.generateDataKey(rootKey, 'AES-256'); - // 2. Encrypt private key with data key - const encryptedPrv = encrypt(dataKey.plaintextKey, prv); + 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 @@ -169,51 +222,11 @@ async postKey(rootKey: string, prv: string): Promise { } ``` -**Encryption Layers:** -- **Layer 1**: Private Key → AES-256-CCM → Encrypted Private Key - - Uses plaintextKey from HSM-generated data key - - SJCL library for AES-256-CCM encryption -- **Layer 2**: Data Key → Local AES → Encrypted Data Key - - Uses root key as password - - Custom encrypt() function from utils/encrypt.ts - -### Private Key Retrieval (GET /key/{pub}) - -```typescript -async getKey(rootKey: string, keyId: string, options: GetKeyOptions): Promise { - // 1. Decrypt data key using root key - const decryptedKey = await this.decryptDataKey(rootKey, options.encryptedDataKey); - - // 2. Convert data key format - const aesKeyBuffer = Buffer.from(decryptedKey.plaintextKey, 'base64'); - const password = aesKeyBuffer.toString('base64'); - - // 3. Decrypt private key with recovered data key - const decryptedPrv = decrypt(password, keyId); - - return { prv: decryptedPrv }; -} -``` - -**Decryption Process:** -1. **Data Key Recovery**: Decrypt encrypted data key with root key -2. **Format Conversion**: base64 → Buffer → base64 (consistent format) -3. **Private Key Decryption**: Use recovered data key to decrypt private key - -### Data Key Decryption - -```typescript -async decryptDataKey(rootKey: string, encryptedKey: string): Promise { - return { - plaintextKey: decrypt(rootKey, encryptedKey), - }; -} -``` - -**Implementation Notes:** -- **Local Operation**: Uses SJCL decryption, not HSM -- **Root Key as Password**: Simple symmetric decryption -- **Performance**: Fast local operation vs. slower HSM calls +**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 ## Database Schema From 03f89c452bb8080403de0053904bfaad12596bfb Mon Sep 17 00:00:00 2001 From: Cesar Patino Date: Thu, 14 Aug 2025 12:07:05 -0400 Subject: [PATCH 3/3] feat(awm): add security recommendation --- demo-kms-script/dinamo-interface.md | 45 +++++++---------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/demo-kms-script/dinamo-interface.md b/demo-kms-script/dinamo-interface.md index 372deb6..5a00a6c 100644 --- a/demo-kms-script/dinamo-interface.md +++ b/demo-kms-script/dinamo-interface.md @@ -1,5 +1,15 @@ # Dinamo HSM KMS Implementation Documentation +## ⚠️ Security Recommendation + +**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 `Uint8Array` for 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. ## Demo Scripts @@ -153,41 +163,6 @@ async generateDataKey(rootKey: string, keySpec: DataKeyTypeType): Promise { - let plaintextKey: string | null = null; - - try { - const result = await this.generateDataKey(rootKey, 'AES-256'); - plaintextKey = result.plaintextKey; - - // Use the plaintext key immediately - const encryptedData = encrypt(plaintextKey, sensitiveData); - - return { - encryptedKey: result.encryptedKey, - encryptedData: encryptedData - }; - } finally { - // **CRITICAL**: Explicitly wipe plaintext key from memory - if (plaintextKey) { - // Overwrite with random data multiple times - for (let i = 0; i < 3; i++) { - plaintextKey = crypto.randomBytes(plaintextKey.length).toString('base64'); - } - plaintextKey = null; - } - - // Force garbage collection (if available) - if (global.gc) { - global.gc(); - } - } -} -``` **Security Considerations:** - **Immediate Use**: Plaintext keys should be used immediately after generation