Skip to content
Last updated

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

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.

GET /v1/credentials/{clientId}/keys
Authorization: Bearer {access_token}

2. Upload Secondary Key

Uploads a PEM-formatted RSA public key.

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}.

POST /v1/credentials/{clientId}/keys/secondary/challenge
Authorization: Bearer {access_token}

Response:

{
  "challenge": "YWJjLTEyMy1kZWYtNDU2LjE3MDI5ODc2NTQuYTNmMmIxYzQ...",
  "expiresUtc": "2024-01-15T10:35:00Z"
}

4. Verify Proof-of-Possession

Submit the signature for the challenge.

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.

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.

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:
    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.