-
Notifications
You must be signed in to change notification settings - Fork 1
feat(kms): demo script for dinamo interface #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,254 @@ | ||
| # 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 | ||
|
|
||
| - **`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 (Recommended) | ||
|
|
||
| ### 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<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 | ||
|
|
||
| ### 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<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:** | ||
| 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 | ||
| 6. **⚠️ 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 | ||
|
|
||
| ### Private Key Storage (POST /key) | ||
|
|
||
| ```typescript | ||
| 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 | ||
|
|
||
| ## 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" | ||
| } | ||
| ``` | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to add a comment here in bold saying the plaintext key should be wiped out of memory after use.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated