Skip to content

Commit 228be9f

Browse files
committed
feat(ebe): KMS client and post independent key API
added a KMS client for EBE to connect to added API for EBE to post independent keys TICKET: WP_4374
1 parent 0d97d24 commit 228be9f

File tree

8 files changed

+9458
-111
lines changed

8 files changed

+9458
-111
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"generate-test-ssl": "openssl req -x509 -newkey rsa:2048 -keyout test-ssl-key.pem -out test-ssl-cert.pem -days 365 -nodes -subj '/CN=localhost'"
1818
},
1919
"dependencies": {
20+
"bitgo": "^46.0.1",
2021
"body-parser": "^1.20.3",
2122
"connect-timeout": "^1.9.0",
2223
"debug": "^3.1.0",
@@ -54,6 +55,6 @@
5455
"typescript": "^4.2.4"
5556
},
5657
"engines": {
57-
"node": ">=14"
58+
"node": ">=14 <21"
5859
}
5960
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { BitGo, KeyPair } from 'bitgo'
2+
import * as express from 'express'
3+
import { KmsClient } from '../../kms/kmsClient';
4+
5+
export async function postIndependentKey(req: express.Request, res: express.Response, next: express.NextFunction): Promise<any> {
6+
7+
const { source, seed }: { source: string, seed?: string } = req.body;
8+
if (!source) {
9+
throw new Error('Source is required for key generation');
10+
}
11+
12+
// setup clients
13+
const bitgo: BitGo = req.body.bitgo;
14+
const kms = new KmsClient();
15+
16+
// create public and private key pairs on BitGo SDK
17+
const coin = bitgo.coin(req.params.coin);
18+
const { pub, prv } = coin.keychains().create();
19+
20+
if (!pub) {
21+
throw new Error('BitGo SDK failed to create public key');
22+
}
23+
24+
// post key to KMS for encryption and storage
25+
try { await kms.postKey({
26+
pub,
27+
prv,
28+
coin: req.params.coin,
29+
source,
30+
type: 'independent',
31+
seed
32+
})} catch (error: any) {
33+
res.status(error.status || 500).json({
34+
message: error.message || 'Failed to post key to KMS',
35+
})
36+
return;
37+
}
38+
39+
next();
40+
}

src/config.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ const defaultEnclavedConfig: EnclavedConfig = {
4949
bind: 'localhost',
5050
timeout: 305 * 1000,
5151
logFile: '',
52+
kmsUrl: '', // Will be overridden by environment variable
5253
tlsMode: TlsMode.ENABLED,
5354
mtlsRequestCert: false,
5455
mtlsRejectUnauthorized: false,
@@ -68,6 +69,12 @@ function determineTlsMode(): TlsMode {
6869
}
6970

7071
function enclavedEnvConfig(): Partial<EnclavedConfig> {
72+
const kmsUrl = readEnvVar('KMS_URL');
73+
74+
if (!kmsUrl) {
75+
throw new Error('KMS_URL environment variable is required and cannot be empty');
76+
}
77+
7178
return {
7279
appMode: AppMode.ENCLAVED,
7380
port: Number(readEnvVar('MASTER_BITGO_EXPRESS_PORT')),
@@ -80,6 +87,8 @@ function enclavedEnvConfig(): Partial<EnclavedConfig> {
8087
timeout: Number(readEnvVar('MASTER_BITGO_EXPRESS_TIMEOUT')),
8188
keepAliveTimeout: Number(readEnvVar('MASTER_BITGO_EXPRESS_KEEP_ALIVE_TIMEOUT')),
8289
headersTimeout: Number(readEnvVar('MASTER_BITGO_EXPRESS_HEADERS_TIMEOUT')),
90+
// KMS settings
91+
kmsUrl,
8392
// TLS settings
8493
keyPath: readEnvVar('MASTER_BITGO_EXPRESS_KEYPATH'),
8594
crtPath: readEnvVar('MASTER_BITGO_EXPRESS_CRTPATH'),
@@ -112,6 +121,7 @@ function mergeEnclavedConfigs(...configs: Partial<EnclavedConfig>[]): EnclavedCo
112121
timeout: get('timeout'),
113122
keepAliveTimeout: get('keepAliveTimeout'),
114123
headersTimeout: get('headersTimeout'),
124+
kmsUrl: get('kmsUrl'),
115125
keyPath: get('keyPath'),
116126
crtPath: get('crtPath'),
117127
tlsKey: get('tlsKey'),

src/kms/kmsClient.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import debug from "debug";
2+
import * as superagent from "superagent";
3+
import { config, isMasterExpressConfig } from "../config";
4+
import { PostKeyParams, PostKeyResponse } from "./types/postKey";
5+
6+
const debugLogger = debug('bitgo:express:kmsClient');
7+
8+
export class KmsClient {
9+
private readonly url: string;
10+
11+
constructor() {
12+
const cfg = config();
13+
if (isMasterExpressConfig(cfg)) {
14+
throw new Error('Configuration is not in enclaved express mode');
15+
}
16+
17+
if (!cfg.kmsUrl) {
18+
throw new Error('KMS URL not configured. Please set KMS_URL in your environment.');
19+
}
20+
21+
this.url = cfg.kmsUrl;
22+
debugLogger('kmsClient initialized with URL: %s', this.url);
23+
}
24+
25+
async postKey(params: PostKeyParams): Promise<PostKeyResponse> {
26+
debugLogger('Posting key to KMS: %O', params);
27+
28+
const kmsResponse = await superagent.post(`${this.url}/key`)
29+
.set("x-api-key", "abc")
30+
.send(params);
31+
32+
const { pub, coin, source, type } = kmsResponse.body;
33+
return { pub, coin, source } as PostKeyResponse;
34+
}
35+
}

src/kms/types/postKey.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export interface PostKeyParams {
2+
prv: string;
3+
pub: string;
4+
coin: string;
5+
source: string;
6+
type: 'independent' | 'enclaved';
7+
seed?: string; // Optional seed for key generation
8+
}
9+
10+
export interface PostKeyResponse {
11+
pub: string;
12+
coin: string;
13+
source: string;
14+
type: 'independent' | 'enclaved';
15+
}

src/routes.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import express from 'express';
55
import debug from 'debug';
66
import pjson from '../package.json';
7+
import { BitGo, BitGoOptions } from 'bitgo';
8+
import { postIndependentKey } from './api/enclaved/postIndependentKey';
79

810
const debugLogger = debug('enclaved:routes');
911

@@ -37,8 +39,16 @@ function setupPingRoutes(app: express.Application) {
3739
app.get('/version', promiseWrapper(handleVersionInfo));
3840
}
3941

40-
function setupKeyGenRoutes() {
42+
function prepBitGo(req: express.Request, res: express.Response, next: express.NextFunction) {
43+
const bitgoConstructorParams: BitGoOptions = {};
44+
req.body.bitgo = new BitGo(bitgoConstructorParams);
45+
46+
next();
47+
};
48+
49+
function setupKeyGenRoutes(app: express.Application) {
4150
// Register additional routes here as needed
51+
app.post('/:coin/independentKey', prepBitGo, promiseWrapper(postIndependentKey));
4252
debugLogger('KeyGen routes configured');
4353
}
4454

@@ -51,7 +61,7 @@ export function setupRoutes(app: express.Application): void {
5161
setupPingRoutes(app);
5262

5363
// Register keygen routes
54-
setupKeyGenRoutes();
64+
setupKeyGenRoutes(app);
5565

5666
// Add a catch-all for unsupported routes
5767
app.use('*', (_req, res) => {

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ interface BaseConfig {
3030
// Enclaved mode specific configuration
3131
export interface EnclavedConfig extends BaseConfig {
3232
appMode: AppMode.ENCLAVED;
33+
// KMS settings
34+
kmsUrl: string;
3335
// TLS settings
3436
keyPath?: string;
3537
crtPath?: string;

0 commit comments

Comments
 (0)