# Self-Service Key Management API This guide covers how to manage your RSA public keys for asymmetric message signing using the BCB Self-Service API. ## Overview BCB allows you to manage your own RSA public keys to ensure zero-downtime rotation. You can store up to two public keys per `ApiClient`: - **Primary key**: The active key currently used to verify your inbound requests. - **Secondary key**: A staging slot used for testing and safe rotation. All endpoints require authentication with strong client credentials and the `manage-credentials` scope. ## Rotation Paths You can choose between two paths for rotating your keys, depending on your safety requirements: ### Path A: Simple Rotation (2 Steps) Best for rapid development or non-critical environments. 1. **Upload**: `PUT /v1/credentials/{clientId}/keys/secondary` 2. **Promote**: `POST /v1/credentials/{clientId}/keys/promote` ### Path B: Safe Rotation (4 Steps - Recommended) Industry standard for production environments. Prevents accidental lockout by verifying the key works before making it primary. 1. **Upload**: `PUT /v1/credentials/{clientId}/keys/secondary` 2. **Challenge**: `POST /v1/credentials/{clientId}/keys/secondary/challenge` 3. **Verify**: `POST /v1/credentials/{clientId}/keys/secondary/verify` (Proof-of-Possession) 4. **Promote**: `POST /v1/credentials/{clientId}/keys/promote` ## API Endpoints ### 1. View Key Metadata Returns fingerprints, algorithms, and verification status. ```http GET /v1/credentials/{clientId}/keys Authorization: Bearer {access_token} ``` ### 2. Upload Secondary Key Uploads a PEM-formatted RSA public key. ```http PUT /v1/credentials/{clientId}/keys/secondary Content-Type: application/json Authorization: Bearer {access_token} { "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkq...\n-----END PUBLIC KEY-----" } ``` ### 3. Request Proof-of-Possession Challenge Returns a Base64 string that you must sign to prove ownership. **Format**: The decoded challenge is a dot-separated string: `{clientId}.{nonce}.{expiry}.{keyFingerprint}`. ```http POST /v1/credentials/{clientId}/keys/secondary/challenge Authorization: Bearer {access_token} ``` **Response:** ```json { "challenge": "YWJjLTEyMy1kZWYtNDU2LjE3MDI5ODc2NTQuYTNmMmIxYzQ...", "expiresUtc": "2024-01-15T10:35:00Z" } ``` ### 4. Verify Proof-of-Possession Submit the signature for the challenge. ```http POST /v1/credentials/{clientId}/keys/secondary/verify Content-Type: application/json Authorization: Bearer {access_token} { "challenge": "YWJjLTEyMy1kZWYtNDU2LjE3MDI5ODc2NTQuYTNmMmIxYzQ...", "signature": "dGhpcyBpcyBhIHNpZ25hdHVyZQ..." } ``` ### 5. Promote Secondary to Primary Makes the secondary key the new active primary key. ```http POST /v1/credentials/{clientId}/keys/promote Authorization: Bearer {access_token} ``` **Response:** Key metadata showing the new primary key fingerprint. **Important Logic:** - **Copy vs Swap**: This operation copies the secondary key into the primary slot and **clears** the secondary slot. - **Verification Requirement**: While the API allows promoting an unverified key (Simple Path), it is **strongly recommended** to complete the PoP flow first. The server check for "verified" is reflected in the `secondaryKeyVerified` field of the metadata. - **Immediate Effect**: Once promoted, the old primary key is discarded and cannot be used for verification anymore. ### 6. Delete Secondary Key Removes the staged secondary key. Useful for aborting a rotation or correcting a mistake. ```http DELETE /v1/credentials/{clientId}/keys/secondary Authorization: Bearer {access_token} ``` **Response:** Key metadata showing the secondary slot is now empty. ## Proof-of-Possession (PoP) Clarity **What exactly do I sign?** The challenge received from the `/challenge` endpoint is a **Base64-encoded string**. You must sign the **decoded raw bytes** of that challenge. - **Decoding**: Decode the challenge string from Base64 to get the raw challenge bytes. - **Signing**: Use RSA-PSS + SHA-256 to sign those raw bytes with your private key. - **Verification**: Submit the resulting signature as a standard **Base64** encoded string. **Do NOT** sign the Base64 string itself. The server decodes your submitted challenge before verifying the signature against the original raw bytes. ## Complete Key Rotation Tutorial (Safe Path) 1. **Generate Keys**: Create a new RSA-3072 or RSA-4096 pair. 2. **Upload**: Use `PUT /keys/secondary` to upload the new public key. 3. **Challenge**: Use `POST /keys/secondary/challenge` to get the challenge string. 4. **Sign Locally**: ```javascript const crypto = require('crypto'); // challenge received from API (Base64 string) const signature = crypto.createSign('RSA-SHA256') .update(Buffer.from(challenge, 'base64')) // Decode and sign raw bytes .sign({ key: privateKeyPem, padding: crypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: crypto.constants.RSA_PSS_SALTLEN_DIGEST }, 'base64'); ``` 5. **Verify**: Use `POST /keys/secondary/verify` with the signature. 6. **Promote**: Use `POST /keys/promote` to swap the keys. ## Troubleshooting - **"Challenge has expired"**: Challenges are valid for 5 minutes. Request a new one. - **"Signature verification failed"**: Ensure you are using **RSA-PSS** padding and signing the **decoded raw bytes** of the Base64 challenge string. Do NOT sign the Base64 string literal. - **Base64 vs Base64url**: - Signatures submitted to the API must be standard **Base64**. - Public keys in `JWKS` use **Base64url**. Be careful not to swap them when manually testing.