From ecdf11a42145dc1085cb67065c9f9313544a4b16 Mon Sep 17 00:00:00 2001 From: Pranav Jain Date: Mon, 24 Nov 2025 17:26:14 -0500 Subject: [PATCH] feat: add OpenAPI spec for KMS API integration Ticket: WP-6992 This specification defines the required API interface that clients must implement to integrate their KMS/HSM provider with advanced wallets. Includes four core endpoints: store key, retrieve key, generate data key, and decrypt data key. --- README.md | 10 +- kms-api-spec.yaml | 438 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 443 insertions(+), 5 deletions(-) create mode 100644 kms-api-spec.yaml diff --git a/README.md b/README.md index fecd9f2..edb5dc4 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Advanced wallets operate in two modes: Key features include: - **Complete Infrastructure Control** - Host and manage all components in your own secure environment. -- **KMS/HSM Integration** - Bring your own KMS or HSM by implementing the provided KMS API interface. Reference implementations available for [AWS HSM](./demo-kms-script/aws-interface.md) and [Dinamo HSM](./demo-kms-script/dinamo-interface.md). +- **KMS/HSM Integration** - Bring your own KMS or HSM by implementing the provided [KMS API interface specification](./kms-api-spec.yaml). Reference implementations available for [AWS HSM](./demo-kms-script/aws-interface.md) and [Dinamo HSM](./demo-kms-script/dinamo-interface.md). - **Network Isolation** - Advanced Wallet Manager operates in a completely isolated network segment with no external internet access. - **mTLS Security** - Optional mutual TLS with client certificate validation for secure inter-service communications. - **Flexible Configuration** - Environment-based setup with file or variable-based certificates. @@ -38,7 +38,7 @@ Key features include: ## Architecture -- **Advanced Wallet Manager** (Port 3080) - An isolated signing server with no internet access that only connects to the key management service (KMS) API for key operations. +- **Advanced Wallet Manager** (Port 3080) - An isolated signing server with no internet access that only connects to your KMS API implementation for key operations. - **Master Express** (Port 3081) - An API gateway providing end-to-end wallet creation and transaction support, integrating [BitGo APIs](https://developers.bitgo.com/reference/overview#/) with secure communication to Advanced Wallet Manager. ## Installation @@ -49,7 +49,7 @@ Key features include: - **npm** or **yarn** package manager. - **OpenSSL** for certificate generation. - **Docker** and **Docker Compose** for containerized deployment (or you can use **Podman** as alternative to Docker). -- **KMS API Implementation** - You must implement the KMS API interface to connect your KMS/HSM to the Advanced Wallet Manager. BitGo provides a specification for the interface and the following example implementations: +- **KMS API Implementation** - You must implement the [KMS API interface specification](./kms-api-spec.yaml) to connect your KMS/HSM to the Advanced Wallet Manager. Reference implementations available: - [AWS HSM Implementation Example](./demo-kms-script/aws-interface.md) - [Dinamo HSM Implementation Example](./demo-kms-script/dinamo-interface.md) @@ -170,7 +170,7 @@ curl -X POST http://localhost:3081/ping/advancedWalletManager | `ADVANCED_WALLET_MANAGER_PORT` | Port to listen on | `3080` | ❌ | | `KMS_URL` | URL to your KMS API implementation | - | ✅ | -> **Note:** The `KMS_URL` points to your implementation of the KMS API interface. You must implement this interface to connect your KMS/HSM. For implementation details and examples, see [Prerequisites](#prerequisites). +> **Note:** The `KMS_URL` points to your implementation of the KMS API interface. You must implement this interface to connect your KMS/HSM. See [Prerequisites](#prerequisites) for the specification and examples. ### Master Express Settings @@ -351,7 +351,7 @@ The setup creates two distinct networks: ### Prerequisites 1. **Install Docker and Docker Compose** -2. **Ensure KMS service is running** on your host machine (typically on port 3000) +2. **Ensure your KMS API implementation is running** on your host machine (typically on port 3000) ### Quick Start diff --git a/kms-api-spec.yaml b/kms-api-spec.yaml new file mode 100644 index 0000000..078e412 --- /dev/null +++ b/kms-api-spec.yaml @@ -0,0 +1,438 @@ +openapi: 3.0.0 + +info: + title: KMS API Interface Specification + version: 1.0.0 + description: | + # API Interface for Advanced Wallet Integration + + This specification defines the required API interface that must be implemented by clients to integrate + their KMS (Key Management Service) or HSM (Hardware Security Module) provider with advanced wallets. + + ## Purpose + + Clients must implement this API specification to enable secure cryptographic key storage and management + for advanced wallets. The implementation should connect to your organization's KMS/HSM provider. + + ## Implementation Requirements + + Your implementation must: + - Expose all four endpoints defined in this specification + - Support the request/response schemas defined in this specification + - Integrate with your chosen KMS/HSM provider backend + - Handle encryption/decryption operations securely + - Support envelope encryption for large payloads + +tags: + - name: Key Management + description: Operations for storing and retrieving private keys + - name: Data Keys + description: Operations for generating and decrypting data encryption keys + +paths: + /key: + post: + tags: + - Key Management + summary: Store a private key + description: | + Store a new private key encrypted using the configured KMS provider. + + The private key is encrypted using the KMS before storage. The implementation uses envelope + encryption for large payloads and direct encryption for smaller payloads. + + **Important**: Keys are identified by the combination of `pub` (public key) and `source` + (user/backup). Attempting to store a duplicate will result in a 409 Conflict error. + operationId: storePrivateKey + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/StoreKeyRequest' + examples: + userKey: + summary: Store a user key + value: + prv: 'MIICXAIBAAKBgH3D4WKfdvhhj9TSGrI0FxAmdfiyfOphuM/kmLMIMKdahZLE5b8Y...' + pub: 'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgH3D4WKfdvhhj9TSGrI0FxAmdfiyfO...' + coin: 'sol' + source: 'user' + type: 'tss' + backupKey: + summary: Store a backup key + value: + prv: 'MIICWwIBAAKBgGxj7AvFxTQ2jOHh8K9ZS...' + pub: 'MIGdMA0GCSqGSIb3DQEBAQUAA4GLADCBhwKBgGxj7...' + coin: 'btc' + source: 'backup' + type: 'independent' + responses: + '200': + description: Private key stored successfully + content: + application/json: + schema: + $ref: '#/components/schemas/StoreKeyResponse' + example: + pub: 'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgH3D4WKfdvhhj9TSGrI0FxAmdfiyfO...' + coin: 'sol' + source: 'user' + type: 'tss' + '400': + $ref: '#/components/responses/BadRequest' + '409': + description: Duplicate key - entry with same pub and source already exists + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Duplicated Key for source: user and pub: MIGeMA0GCSqGSIb3DQEBAQUAA4...' + '500': + $ref: '#/components/responses/InternalServerError' + + /key/{pub}: + get: + tags: + - Key Management + summary: Retrieve a private key + description: | + Retrieve and decrypt a previously stored private key. + + The key is identified by the public key (`pub`) path parameter and the `source` query parameter. + The KMS provider decrypts the key before returning it. + operationId: getPrivateKey + parameters: + - name: pub + in: path + required: true + description: The public key associated with the private key to retrieve + schema: + type: string + example: 'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgH3D4WKfdvhhj9TSGrI0FxAmdfiyfO...' + - name: source + in: query + required: true + description: The key source type + schema: + $ref: '#/components/schemas/KeySource' + example: 'user' + responses: + '200': + description: Private key retrieved and decrypted successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GetKeyResponse' + example: + prv: 'MIICXAIBAAKBgH3D4WKfdvhhj9TSGrI0FxAmdfiyfOphuM/kmLMIMKdahZLE5b8Y...' + pub: 'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgH3D4WKfdvhhj9TSGrI0FxAmdfiyfO...' + source: 'user' + type: 'independent' + '404': + description: Private key not found for the given pub and source + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Entry with pub MIGeMA0GCSqGSIb3DQEBAQUAA4... and source user not found in database' + '500': + $ref: '#/components/responses/InternalServerError' + + /generateDataKey: + post: + tags: + - Data Keys + summary: Generate a data encryption key + description: | + Generate a new data encryption key for envelope encryption operations. + + The KMS provider generates a symmetric key and returns both: + - **plaintextKey**: Use immediately for encryption, then discard + - **encryptedKey**: Store this to decrypt data later using `/decryptDataKey` + + This follows the envelope encryption pattern where data is encrypted with the data key, + and the data key itself is encrypted with the KMS root key. + operationId: generateDataKey + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateDataKeyRequest' + examples: + aes256: + summary: Generate AES-256 key + value: + keyType: 'AES-256' + rsa2048: + summary: Generate RSA-2048 key + value: + keyType: 'RSA-2048' + responses: + '200': + description: Data key generated successfully + content: + application/json: + schema: + $ref: '#/components/schemas/GenerateDataKeyResponse' + example: + plaintextKey: '1,2,3,0,120,222,140,157,217,111,195,208,47,200,213,217,82,189,16,171,207,16,138,46,228,224,190,138,63,132,239,80,164,8,124,105,140,1,174,211,14,152,144,66,115,54,226,169,178,37,100,105,154,15,0,0,0,126,48,124,6,9,42,134,72,134,247,13,1,7,6,160,111,48,109,2,1,0,48,104,6,9,42,134,72,134,247,13,1,7,1,48,30,6,9,96,134,72,1,101,3,4,1,46,48,17,4,12,247,0,189,155,147,80,121,250,71,64,30,121,2,1,16,128,59,175,44,60,80,240,109,12,47,202,7,20,250,186,219,247,41,129,85,0,16,202,62,33,42,240,91,175,106,165,120,107,65,28,21,122,211,235,23,79,65,25,56,107,106,95,112,39,148,183,6,160,119,205,12,116,187,127,63,83' + encryptedKey: '62,137,108,179,93,237,135,139,245,68,225,226,124,238,181,134,151,103,246,23,4,9,186,197,223,50,53,196,52,94,22,16' + '409': + description: Database conflict - generated another root key for the same KMS provider + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + $ref: '#/components/responses/InternalServerError' + + /decryptDataKey: + post: + tags: + - Data Keys + summary: Decrypt a data encryption key + description: | + Decrypt a previously generated data encryption key. + + Provide the `encryptedKey` value that was returned from `/generateDataKey`. + The KMS provider decrypts it and returns the plaintext key. + + Use the plaintext key for decryption operations, then discard it immediately. + operationId: decryptDataKey + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DecryptDataKeyRequest' + example: + encryptedKey: '62,137,108,179,93,237,135,139,245,68,225,226,124,238,181,134,151,103,246,23,4,9,186,197,223,50,53,196,52,94,22,16' + responses: + '200': + description: Data key decrypted successfully + content: + application/json: + schema: + $ref: '#/components/schemas/DecryptDataKeyResponse' + example: + plaintextKey: '1,2,3,0,120,222,140,157,217,111,195,208,47,200,213,217,82,189,16,171,207,16,138,46,228,224,190,138,63,132,239,80,164,8,124,105,140,1,174,211,14,152,144,66,115,54,226,169,178,37,100,105,154,15,0,0,0,126,48,124,6,9,42,134,72,134,247,13,1,7,6,160,111,48,109,2,1,0,48,104,6,9,42,134,72,134,247,13,1,7,1,48,30,6,9,96,134,72,1,101,3,4,1,46,48,17,4,12,247,0,189,155,147,80,121,250,71,64,30,121,2,1,16,128,59,175,44,60,80,240,109,12,47,202,7,20,250,186,219,247,41,129,85,0,16,202,62,33,42,240,91,175,106,165,120,107,65,28,21,122,211,235,23,79,65,25,56,107,106,95,112,39,148,183,6,160,119,205,12,116,187,127,63,83' + '404': + description: Encrypted key not found or root key not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Internal server error. Failed to fetch root key from database' + '500': + $ref: '#/components/responses/InternalServerError' + + /openapi.json: + get: + tags: + - Documentation + summary: Get OpenAPI specification + description: | + Returns the OpenAPI specification in JSON format. + + This endpoint is optional but recommended for clients to provide access to the API specification. + operationId: getOpenApiSpec + responses: + '200': + description: OpenAPI specification + content: + application/json: + schema: + type: object + +components: + schemas: + KeySource: + type: string + enum: + - user + - backup + description: | + The key source type: + - `user`: Keys used for transaction signing + - `backup`: Keys used for wallet recovery + + KeyType: + type: string + enum: + - independent + - tss + description: | + The key type: + - `independent`: Standard single-signature keys + - `tss`: Threshold Signature Scheme (multi-party computation) keys + + DataKeyType: + type: string + enum: + - AES-256 + - RSA-2048 + - ECDSA-P256 + description: | + The encryption algorithm for data key generation: + - `AES-256`: 256-bit Advanced Encryption Standard + - `RSA-2048`: 2048-bit RSA + - `ECDSA-P256`: Elliptic Curve Digital Signature Algorithm with P-256 curve + + StoreKeyRequest: + type: object + required: + - prv + - pub + - coin + - source + - type + properties: + prv: + type: string + description: The private key to be encrypted and stored + minLength: 1 + pub: + type: string + description: The corresponding public key (used as identifier) + minLength: 1 + coin: + type: string + description: The coin/blockchain type (e.g., "btc", "eth", "sol") + minLength: 1 + example: 'sol' + source: + $ref: '#/components/schemas/KeySource' + type: + $ref: '#/components/schemas/KeyType' + + StoreKeyResponse: + type: object + required: + - pub + - coin + - source + - type + properties: + pub: + type: string + description: The public key + coin: + type: string + description: The coin type + source: + $ref: '#/components/schemas/KeySource' + type: + $ref: '#/components/schemas/KeyType' + + GetKeyResponse: + type: object + required: + - prv + - pub + - source + - type + properties: + prv: + type: string + description: The decrypted private key + pub: + type: string + description: The public key + source: + $ref: '#/components/schemas/KeySource' + type: + $ref: '#/components/schemas/KeyType' + + GenerateDataKeyRequest: + type: object + required: + - keyType + properties: + keyType: + $ref: '#/components/schemas/DataKeyType' + + GenerateDataKeyResponse: + type: object + required: + - plaintextKey + - encryptedKey + properties: + plaintextKey: + type: string + description: | + The data key in plaintext (comma-separated byte array as string). + Use immediately for encryption operations, then discard. + encryptedKey: + type: string + description: | + Encrypted data key or KMS identifier (comma-separated byte array as string). + Store this value to decrypt data later using /decryptDataKey. + + DecryptDataKeyRequest: + type: object + required: + - encryptedKey + properties: + encryptedKey: + type: string + description: The encrypted key or identifier from /generateDataKey + + DecryptDataKeyResponse: + type: object + required: + - plaintextKey + properties: + plaintextKey: + type: string + description: | + The decrypted data key in plaintext (comma-separated byte array as string). + Use for decryption operations, then discard immediately. + + ErrorResponse: + type: object + required: + - message + properties: + message: + type: string + description: Human-readable error message describing what went wrong + + responses: + BadRequest: + description: Invalid request data or schema validation failure + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Invalid data provided from client: keyType must be one of [AES-256, RSA-2048, ECDSA-P256]' + + NotFound: + description: Resource not found + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + example: + message: 'Entry with pub MIGeMA0GCSqGSIb3DQEBAQUAA4... and source user not found in database' + + InternalServerError: + description: Internal server error or KMS operation failure + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + examples: + kmsFailure: + summary: KMS operation failed + value: + message: 'Failed to encrypt private key in KMS. AWS KMS returned InvalidKeyId' + databaseFailure: + summary: Database operation failed + value: + message: 'Internal server error. Failed to fetch or create root key from database'