Skip to main content

Getting Started

This guide will walk you through setting up the Key Management Service and making your first API calls. You’ll learn how to generate encryption keys, encrypt data, and verify encrypted credentials.
1
Prerequisites
2
Before you begin, ensure you have:
3
  • Node.js 20.x or higher
  • PostgreSQL database
  • yarn or npm package manager
  • 4
    Clone and Install
    5
    Clone the repository and install dependencies:
    6
    npm
    npm install
    
    yarn
    yarn install
    
    7
    The project includes the following key dependencies:
    8
  • @nestjs/graphql - GraphQL integration
  • @nestjs/apollo - Apollo Server driver
  • @prisma/client - Database ORM
  • @nestjs/config - Configuration management
  • @nestjs/schedule - Cron job scheduling
  • 9
    Configure Database
    10
    Create a .env file in the project root with your database connection:
    11
    DATABASE_URL="postgresql://user:password@localhost:5432/kms_db"
    
    # Optional: Customize key rotation and retention policies
    ENCRYPTION_KEY_ROTATION_DAYS=30
    ENCRYPTION_KEY_RETENTION_DAYS=90
    MAX_ENCRYPTED_DATA_SIZE=2048
    
    12
    Key Configuration Options:
    • ENCRYPTION_KEY_ROTATION_DAYS - How long before keys expire (default: 30 days)
    • ENCRYPTION_KEY_RETENTION_DAYS - How long to keep deprecated keys (default: 90 days)
    • MAX_ENCRYPTED_DATA_SIZE - Maximum size of encrypted data in bytes (default: 2048)
    13
    Run Prisma migrations to set up the database schema:
    14
    npx prisma migrate dev
    
    15
    This creates three tables:
    16
  • User - User accounts with encrypted credentials
  • ClientEncryptionKey - RSA key pairs with rotation metadata
  • EncryptionMetric - Operation metrics for monitoring
  • 17
    Start the Service
    18
    Run the development server:
    19
    Development
    npm run start:dev
    
    Production
    npm run build
    npm run start:prod
    
    20
    The GraphQL API will be available at http://localhost:3000/graphql
    21
    The GraphQL Playground is automatically enabled in development mode, allowing you to explore the API and test queries interactively.
    22
    Generate Your First Encryption Key
    23
    Now let’s generate an encryption key for a device. Open the GraphQL Playground at http://localhost:3000/graphql and run:
    24
    mutation {
      generateClientEncryptionKey(
        input: {
          deviceId: "device_123"
          appVersion: "1.0.0"
        }
      ) {
        keyId
        publicKey
      }
    }
    
    25
    Response:
    26
    {
      "data": {
        "generateClientEncryptionKey": {
          "keyId": "key_1709514240000_a1b2c3d4e5f6g7h8i9j0",
          "publicKey": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...\n-----END PUBLIC KEY-----\n"
        }
      }
    }
    
    27
    The keyId is unique and includes a timestamp plus random bytes. Store this keyId and publicKey in your client application - you’ll need them for encryption operations.
    28
    Encrypt Data on the Client
    29
    With the public key from step 5, you can now encrypt sensitive data on your client. Here’s how the encryption works:
    30
    import * as crypto from 'crypto';
    
    function encryptData(data: string, publicKey: string): string {
      const buffer = Buffer.from(data, 'utf-8');
      const encrypted = crypto.publicEncrypt(
        {
          key: publicKey,
          padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        },
        buffer,
      );
      return encrypted.toString('base64');
    }
    
    // Encrypt a passcode
    const publicKey = "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----\n";
    const encryptedPasscode = encryptData("123456", publicKey);
    
    31
    Security Best Practice: Always encrypt sensitive data (passcodes, PINs, tokens) on the client side before sending to any server. The private key never leaves the KMS server.
    32
    Set an Encrypted Passcode
    33
    Once you have encrypted data, you can store it securely. Here’s how to set a user passcode:
    34
    mutation {
      setUserPasscode(
        input: {
          encryptedPasscode: "base64_encrypted_data_here"
          keyId: "key_1709514240000_a1b2c3d4e5f6g7h8i9j0"
          passwordAuthType: PASSCODE
        }
      )
    }
    
    35
    Response:
    36
    {
      "data": {
        "setUserPasscode": true
      }
    }
    
    37
    The service will:
    38
  • Retrieve the private key using the keyId
  • Decrypt the encrypted passcode
  • Hash it with bcrypt
  • Store the hash in the database
  • 39
    Source: This operation is handled by AuthService.setUserPasscode() in src/auth/auth.service.ts
    40
    Verify an Encrypted Passcode
    41
    To verify a passcode later, encrypt it with the same public key and verify:
    42
    mutation {
      verifyUserPasscode(
        input: {
          encryptedPasscode: "base64_encrypted_data_here"
          keyId: "key_1709514240000_a1b2c3d4e5f6g7h8i9j0"
          passwordAuthType: PASSCODE
        }
      )
    }
    
    43
    Response:
    44
    {
      "data": {
        "verifyUserPasscode": true
      }
    }
    
    45
    Rotate Keys (Optional)
    46
    Keys automatically expire after 30 days (configurable). To manually rotate a device’s key:
    47
    mutation {
      rotateClientEncryptionKey(
        input: {
          deviceId: "device_123"
        }
      ) {
        keyId
        publicKey
      }
    }
    
    48
    This will:
    49
  • Mark all existing keys for the device as deprecated
  • Generate a new key pair
  • Return the new public key and keyId
  • 50
    Old keys are retained for 90 days (configurable) so data encrypted with them can still be decrypted during the transition period.
    51
    Monitor Encryption Metrics
    52
    Check operation metrics to monitor system health:
    53
    query {
      getEncryptionMetricsSummary(timeframeHours: 24)
    }
    
    54
    Response:
    55
    {
      "data": {
        "getEncryptionMetricsSummary": "{\n  \"generate\": {\n    \"success\": 145,\n    \"failure\": 2\n  },\n  \"decrypt\": {\n    \"success\": 1203,\n    \"failure\": 15\n  }\n}"
      }
    }
    

    Complete Example Flow

    Here’s a complete TypeScript example showing the entire flow:
    Complete client example
    import { ApolloClient, InMemoryCache, gql } from '@apollo/client';
    import * as crypto from 'crypto';
    
    const client = new ApolloClient({
      uri: 'http://localhost:3000/graphql',
      cache: new InMemoryCache(),
    });
    
    // Step 1: Generate encryption key
    const GENERATE_KEY = gql`
      mutation GenerateKey($deviceId: String!, $appVersion: String) {
        generateClientEncryptionKey(
          input: { deviceId: $deviceId, appVersion: $appVersion }
        ) {
          keyId
          publicKey
        }
      }
    `;
    
    const { data } = await client.mutate({
      mutation: GENERATE_KEY,
      variables: {
        deviceId: 'device_123',
        appVersion: '1.0.0',
      },
    });
    
    const { keyId, publicKey } = data.generateClientEncryptionKey;
    
    // Step 2: Encrypt sensitive data on client
    function encryptData(data: string, publicKey: string): string {
      const buffer = Buffer.from(data, 'utf-8');
      const encrypted = crypto.publicEncrypt(
        {
          key: publicKey,
          padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        },
        buffer,
      );
      return encrypted.toString('base64');
    }
    
    const userPasscode = '123456';
    const encryptedPasscode = encryptData(userPasscode, publicKey);
    
    // Step 3: Set encrypted passcode
    const SET_PASSCODE = gql`
      mutation SetPasscode(
        $encryptedPasscode: String!
        $keyId: String!
      ) {
        setUserPasscode(
          input: {
            encryptedPasscode: $encryptedPasscode
            keyId: $keyId
            passwordAuthType: PASSCODE
          }
        )
      }
    `;
    
    await client.mutate({
      mutation: SET_PASSCODE,
      variables: {
        encryptedPasscode,
        keyId,
      },
    });
    
    console.log('Passcode set successfully!');
    
    // Step 4: Verify passcode later
    const VERIFY_PASSCODE = gql`
      mutation VerifyPasscode(
        $encryptedPasscode: String!
        $keyId: String!
      ) {
        verifyUserPasscode(
          input: {
            encryptedPasscode: $encryptedPasscode
            keyId: $keyId
            passwordAuthType: PASSCODE
          }
        )
      }
    `;
    
    const verifyResult = await client.mutate({
      mutation: VERIFY_PASSCODE,
      variables: {
        encryptedPasscode: encryptData('123456', publicKey),
        keyId,
      },
    });
    
    console.log('Verification:', verifyResult.data.verifyUserPasscode);
    

    Available Operations

    The service provides these GraphQL operations:

    Mutations

    OperationDescription
    generateClientEncryptionKeyGenerate a new RSA key pair for a device
    rotateClientEncryptionKeyRotate keys for a device (deprecates old keys)
    encryptDataLikeClientTest encryption with a public key (utility)
    setUserPasscodeSet a user’s encrypted passcode
    verifyUserPasscodeVerify a user’s encrypted passcode
    setTransactionPinSet a user’s encrypted transaction PIN
    verifyTransactionPinVerify a user’s encrypted transaction PIN

    Queries

    OperationDescription
    getEncryptionMetricsSummaryGet operation metrics for a time window

    Next Steps

    API Reference

    Explore detailed API documentation with all parameters and responses

    Security Guide

    Learn about security best practices and encryption standards

    Key Rotation

    Understand automatic key lifecycle management

    Monitoring

    Set up metrics collection and alerting

    Troubleshooting

    Key Generation Fails

    Rate Limit Exceeded: Each device can generate maximum 5 keys per 24-hour window. Wait or use an existing valid key.

    Decryption Fails

    • Verify the keyId matches the one used for encryption
    • Check if the key has expired (keys expire after 30 days by default)
    • Ensure encrypted data is properly base64 encoded

    Database Connection Issues

    Verify your DATABASE_URL in .env:
    npx prisma db push
    
    This will test the connection and sync the schema.

    Support

    For issues or questions:

    Build docs developers (and LLMs) love