Skip to content

Commit 033811a

Browse files
authored
Merge pull request #117 from BitGo/WP5535/demo-script-dinamo
feat(kms): demo script for dinamo interface
2 parents 8b09f59 + 03f89c4 commit 033811a

File tree

1 file changed

+254
-0
lines changed

1 file changed

+254
-0
lines changed
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Dinamo HSM KMS Implementation Documentation
2+
3+
## ⚠️ Security Recommendation
4+
5+
**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.**
6+
7+
**Recommended Alternatives:**
8+
- **C++/Rust**: Languages with explicit memory management and secure allocation
9+
- **Node.js Typed Arrays**: Use `Uint8Array` for sensitive data with explicit zeroing
10+
- **Native Addons**: Implement cryptographic operations in native C++ modules
11+
- **Hardware Security**: Use HSM-backed secure memory when available
12+
13+
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.
14+
15+
## Demo Scripts
16+
17+
- **`real-dinamo-flow.ts`** - Complete handler-to-provider-to-HSM-to-database flow demonstration
18+
- **`dinamo-provider-implementation.ts`** - Core provider implementation patterns and methods
19+
- **`DINAMO_HSM_IMPLEMENTATION.md`** - Original deep-dive documentation (legacy)
20+
21+
## Quick Overview
22+
23+
The KMS API provides secure key management through four main endpoints that integrate with Dinamo HSM:
24+
25+
- `POST /key` - Store private keys using envelope encryption
26+
- `GET /key/{pub}` - Retrieve private keys using envelope decryption
27+
- `POST /generateDataKey` - Generate AES keys in HSM for encryption
28+
- `POST /decryptDataKey` - Decrypt data keys using root keys
29+
30+
## Architecture Flow
31+
32+
```
33+
API Request → Handler → KMS Provider → Dinamo HSM → Database → Response
34+
```
35+
36+
### Handler-to-Provider Mapping
37+
38+
| API Endpoint | Handler File | Provider Method | HSM Operations |
39+
|--------------|--------------|-----------------|----------------|
40+
| `POST /key` | `storePrivateKey.ts` | `postKey()` | Create AES key, export, encrypt |
41+
| `GET /key/{pub}` | `getPrivateKey.ts` | `getKey()` | Decrypt data key locally |
42+
| `POST /generateDataKey` | `generateDataKey.ts` | `generateDataKey()` | Create/export AES key |
43+
| `POST /decryptDataKey` | `decryptDataKey.ts` | `decryptDataKey()` | Local SJCL decryption |
44+
45+
## Envelope Encryption Pattern (Recommended)
46+
47+
### Layer 1: Root Keys (HSM)
48+
- **Algorithm**: RSA-2048 asymmetric keys
49+
- **Storage**: Dinamo HSM hardware (permanent)
50+
- **Purpose**: Encrypt/decrypt data keys
51+
- **Security**: Never exported from HSM
52+
53+
### Layer 2: Data Keys (Generated in HSM, Used Locally)
54+
- **Algorithm**: AES-256 symmetric keys
55+
- **Generation**: Dinamo HSM (temporary keys)
56+
- **Export**: Raw key material exported as Buffer
57+
- **Encryption**: Encrypted with root key using SJCL
58+
- **Storage**: Database (encrypted), Memory (plaintext, temporary)
59+
60+
### Layer 3: Private Keys (Application Data)
61+
- **Encryption**: AES-256-CCM using SJCL
62+
- **Key**: Data key plaintext (from Layer 2)
63+
- **Storage**: Database (encrypted only)
64+
65+
## Implementation Details
66+
67+
### Connection Management Pattern
68+
69+
```typescript
70+
private async withClient<T>(fn: (client) => Promise<T>): Promise<T> {
71+
const conn = await hsm.connect({
72+
host: process.env.DINAMO_HOST || "",
73+
authUsernamePassword: {
74+
username: process.env.DINAMO_USERNAME || "",
75+
password: process.env.DINAMO_PASSWORD || "",
76+
},
77+
});
78+
79+
try {
80+
return await fn(conn);
81+
} finally {
82+
try {
83+
await conn.disconnect();
84+
} catch (e) {
85+
logger.warn("Failed to disconnect from Dinamo HSM", e);
86+
}
87+
}
88+
}
89+
```
90+
91+
**Why Connection Management is Critical:**
92+
- **Prevents Resource Leaks**: Ensures HSM connections are properly closed to avoid dangling connections
93+
- **HSM Connection Limits**: Hardware security modules have limited concurrent connection pools
94+
- **Network Stability**: Prevents socket exhaustion and connection timeouts
95+
- **Security Best Practice**: Minimizes attack surface by closing connections immediately after use
96+
97+
### Root Key Creation
98+
99+
```typescript
100+
async createRootKey(): Promise<{ rootKey: string }> {
101+
const keyName = getRandomHash(32);
102+
103+
return await this.withClient(async (client) => {
104+
const created = await client.key.create(
105+
keyName, // Unique key identifier
106+
hsm.enums.RSA_ASYMMETRIC_KEYS.ALG_RSA_2048, // 2048-bit RSA
107+
true, // Exportable for public key ops
108+
false // Permanent storage
109+
);
110+
111+
if (!created) {
112+
throw { message: 'Failed to create symmetric key in HSM', code: 500 };
113+
}
114+
115+
return { rootKey: keyName };
116+
});
117+
}
118+
```
119+
120+
**HSM Operations:**
121+
- **Key Naming**: 32-character random hash for uniqueness
122+
- **Algorithm**: RSA-2048 for asymmetric operations
123+
- **Exportable**: Set to true to allow public key export
124+
- **Permanent**: Root keys stored permanently in HSM
125+
- **Error Handling**: Structured errors with HTTP codes
126+
127+
### Data Key Generation Process
128+
129+
```typescript
130+
async generateDataKey(rootKey: string, keySpec: DataKeyTypeType): Promise<GenerateDataKeyKmsRes> {
131+
return await this.withClient(async (client) => { // Connection auto-managed to prevent dangling connections
132+
// 1. Create temporary AES key in HSM
133+
const dataKeyName = getRandomHash(32);
134+
const created = await client.key.create(
135+
dataKeyName,
136+
hsm.enums.SYMMETRICAL_KEYS.ALG_AES_256, // 256-bit AES
137+
true, // Exportable
138+
true // Temporary (auto-deleted)
139+
);
140+
141+
// 2. Export plaintext key material
142+
const exportedKey = await client.key.exportSymmetric(dataKeyName);
143+
const plaintextKey = exportedKey.toString('base64');
144+
145+
// **CRITICAL SECURITY NOTE**: The plaintextKey contains raw cryptographic material
146+
// and MUST be wiped from memory immediately after encryption operations.
147+
// In production, use secure memory allocation and explicit zeroing.
148+
149+
// 3. Encrypt with root key (envelope encryption)
150+
return {
151+
encryptedKey: encrypt(rootKey, plaintextKey), // SJCL encryption
152+
plaintextKey: plaintextKey, // For immediate use - WIPE AFTER USE
153+
};
154+
});
155+
}
156+
```
157+
158+
**Process Flow:**
159+
1. **Temporary Key Creation**: AES-256 symmetric key in HSM
160+
2. **Key Export**: Raw key material extracted as Buffer
161+
3. **Format Conversion**: Buffer → base64 string
162+
4. **Envelope Encryption**: Encrypt plaintext with root key
163+
5. **Automatic Cleanup**: HSM deletes temporary key
164+
6. **⚠️ MEMORY SECURITY**: Plaintext key must be wiped from memory after use
165+
166+
167+
**Security Considerations:**
168+
- **Immediate Use**: Plaintext keys should be used immediately after generation
169+
- **Memory Overwriting**: Overwrite memory locations with random data before deallocation
170+
- **Garbage Collection**: Force GC to clear memory pages containing sensitive data
171+
- **Process Isolation**: Consider using separate processes for key operations
172+
- **Hardware Security**: Use HSM-backed secure memory when available
173+
174+
### Private Key Storage (POST /key)
175+
176+
```typescript
177+
async postKey(rootKey: string, prv: string): Promise<PostKeyKmsRes> {
178+
// 1. Generate fresh data key for this private key
179+
const dataKey = await this.generateDataKey(rootKey, 'AES-256');
180+
181+
let encryptedPrv: string;
182+
try {
183+
// 2. Encrypt private key with data key (use immediately)
184+
encryptedPrv = encrypt(dataKey.plaintextKey, prv);
185+
} finally {
186+
// **CRITICAL**: Wipe plaintext data key from memory immediately after use
187+
// Production code should implement secure memory wiping here
188+
}
189+
190+
return {
191+
encryptedPrv, // Encrypted private key
192+
rootKeyId: rootKey, // Root key reference
193+
metadata: {
194+
encryptedDataKey: dataKey.encryptedKey, // Encrypted data key
195+
},
196+
};
197+
}
198+
```
199+
200+
**Memory Security Notes:**
201+
- **Immediate Encryption**: Use plaintext data key immediately for encryption
202+
- **Secure Disposal**: Wipe plaintext key from memory after single use
203+
- **No Persistence**: Never store plaintext data keys in variables or logs
204+
- **Error Handling**: Ensure memory wiping occurs even if encryption fails
205+
206+
## Database Schema
207+
208+
### private_keys Table
209+
210+
```sql
211+
CREATE TABLE private_keys (
212+
id INTEGER PRIMARY KEY AUTOINCREMENT,
213+
pub TEXT NOT NULL, -- Public key (identifier)
214+
source TEXT NOT NULL, -- 'user' or 'backup'
215+
encryptedPrv TEXT NOT NULL, -- Private key encrypted with data key
216+
encryptedDataKey TEXT NOT NULL, -- Data key encrypted with root key
217+
provider TEXT NOT NULL, -- 'dinamo'
218+
rootKey TEXT NOT NULL, -- Root key name in HSM
219+
coin TEXT NOT NULL, -- Cryptocurrency type
220+
type TEXT NOT NULL, -- Key type (e.g., 'tss')
221+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
222+
);
223+
```
224+
225+
**Storage Pattern:**
226+
- **encryptedPrv**: SJCL AES-256-CCM encrypted private key
227+
- **encryptedDataKey**: Root-key-encrypted data key
228+
- **rootKey**: Reference to HSM root key name
229+
- **No plaintext**: All sensitive data encrypted
230+
231+
## SJCL Encryption Details
232+
233+
### Configuration
234+
- **Algorithm**: AES-256-CCM
235+
- **Iterations**: 10,000 (PBKDF2)
236+
- **Key Size**: 256 bits
237+
- **Tag Size**: 128 bits
238+
- **Mode**: CCM (Counter with CBC-MAC)
239+
240+
### Example SJCL Output
241+
```json
242+
{
243+
"iv": "a1b2c3d4e5f6...",
244+
"v": 1,
245+
"iter": 10000,
246+
"ks": 256,
247+
"ts": 128,
248+
"mode": "ccm",
249+
"adata": "",
250+
"cipher": "aes",
251+
"salt": "f6e5d4c3b2a1...",
252+
"ct": "base64-encrypted-data"
253+
}
254+
```

0 commit comments

Comments
 (0)