Skip to main content

Key Management Policy

Key Management Policy

Project / Organization: ALAI Holding AS — Drop Payment App Policy Number: POL-SEC-KM-001 Version: 1.0 Date: 2026-02-23 Author: Security Architect Status: Draft Reviewers: CISO, CTO, DPO Next Review: 2027-02-23 Classification: Confidential — Restricted Distribution

Document History

Version Date Author Changes
0.1 2026-02-23 Security Architect Initial draft — Drop key management for AWS KMS + Secrets Manager

1. Purpose & Scope

Purpose: This policy defines the lifecycle management requirements for all cryptographic keys and secrets used by ALAI Holding AS for the Drop payment app, including generation, distribution, storage, usage, rotation, revocation, and destruction.

Regulatory basis:

  • GDPR Art. 32 — appropriate technical measures for personal data
  • Personopplysningsloven (LOV-2018-06-15-38) § 28
  • IKT-forskriften (FOR-2003-05-21-630) §§ 5-6
  • DORA (EU) 2022/2554 Art. 9(4)(d) — encryption key management
  • Hvitvaskingsloven (LOV-2018-06-01-23) § 30 — KYC data protection at rest

Scope: All cryptographic keys and secrets in use across:

  • Production, staging, and development environments for Drop
  • AWS infrastructure (KMS, Secrets Manager, S3, App Runner)
  • All employee and contractor workstations handling Confidential or Restricted data
  • All third-party integrations where ALAI Holding AS holds keys

Policy Owner: CISO ([email protected]) Operational Owner: Security team


2. Key Inventory

2.1 Complete Key Taxonomy

Key ID Type Algorithm Purpose Data Classification Owner Storage
drop-national-id-key KEK AES-256-GCM Foedselsnummer field encryption Restricted (L4) Security team AWS KMS (eu-north-1)
drop-db-master-key KEK (Database) AES-256 XTS PostgreSQL TDE — Phase 2 Restricted (L4) Security team AWS KMS (eu-north-1)
drop-kyc-key KEK (Object) AES-256-GCM KYC document encryption (S3 SSE-KMS) Restricted (L4) Security team AWS KMS (eu-north-1)
drop-backup-key KEK (Backup) AES-256 Database backup encryption Restricted (L4) Security team AWS KMS (eu-west-1) — separate region
JWT_SECRET Signing secret HMAC-SHA-256 JWT session token signing (HS256) Confidential (L3) Security team AWS Secrets Manager
TLS-EXT TLS Certificate ECDSA P-256 External HTTPS (getdrop.no) Cloudflare / Let's Encrypt Cloudflare managed
BankID-cert TLS Certificate RSA-2048 BankID Norway JWT signature verification BankID Norge AS CA BankID managed
SUMSUB-API-KEY API Key HMAC-SHA-256 Sumsub KYC API authentication Confidential (L3) Security team AWS Secrets Manager
DEK-* Data Encryption Keys AES-256-GCM Per-record envelope DEKs Restricted (L4) Application Generated by AWS KMS GenerateDataKey

2.2 Secrets Inventory (AWS Secrets Manager)

Secret Name Purpose Rotation
JWT_SECRET JWT token signing Quarterly
SUMSUB_SECRET_KEY Sumsub KYC integration Annual or on compromise
BANKID_CLIENT_SECRET BankID OIDC client secret Per BankID schedule
DATABASE_URL PostgreSQL connection string On DB credential rotation
SENTRY_DSN Sentry error monitoring Annual

3. Key Hierarchy

AWS KMS Root of Trust (eu-north-1 primary — Stockholm)
    |
    +-- drop-national-id-key (AES-256-GCM — annual rotation)
    |       +-- DEK-{user_id}-{timestamp}: Per-record envelope DEKs
    |               +-- Encrypts: foedselsnummer field in users table
    |               +-- Stored: base64(encryptedDEK || iv || tag || ciphertext)
    |
    +-- drop-db-master-key (AES-256 XTS — annual rotation)
    |       +-- Encrypts: PostgreSQL database at rest (Phase 2 — AWS RDS)
    |
    +-- drop-kyc-key (AES-256-GCM — annual rotation)
    |       +-- Encrypts: KYC document objects in AWS S3 (SSE-KMS)
    |
    +-- AWS KMS Root of Trust (eu-west-1 — SEPARATE REGION)
            +-- drop-backup-key (AES-256 — annual rotation)
                    +-- Encrypts: All database backup files

AWS Secrets Manager (eu-north-1)
    +-- JWT_SECRET (HMAC-SHA-256 — quarterly rotation)
    |       +-- Signs: Drop JWT session tokens (HS256 via jose ^6.1.3)
    +-- SUMSUB_SECRET_KEY (API key — annual)
    +-- BANKID_CLIENT_SECRET (OIDC client secret)
    +-- DATABASE_URL (connection string)

Cloudflare / Let's Encrypt
    +-- TLS Certificate (ECDSA P-256 — 90-day automated rotation)
            +-- External HTTPS: getdrop.no

BankID Norge AS CA
    +-- BankID Certificate (RSA-2048 — per BankID renewal schedule)
            +-- Used for: BankID JWT signature verification (Phase 2)

4. Key Lifecycle

4.1 Lifecycle Overview

flowchart LR
    GEN[Generation] -->|"AWS KMS CSPRNG"| DIST[Distribution]
    DIST -->|"Runtime API call\nnever in env files"| STORE[Storage]
    STORE -->|"KMS / Secrets Manager\naccess controlled"| USE[Usage]
    USE -->|"Scheduled"| ROT[Rotation]
    ROT -->|"New key active\noverlap period"| USE
    ROT -->|"Old key retired"| ARCH[Archive / Revoke]
    ARCH -->|"End of life"| DEST[Destruction]

    REVOKE[Emergency Revocation] -->|"Compromise detected"| DEST
    USE --> REVOKE

4.2 Generation

Entropy requirements:

  • All keys MUST be generated using AWS KMS or a CSPRNG
  • NEVER use user-supplied passphrases, timestamps, UUIDs, or predictable values as keys
  • NEVER generate keys in application code for Restricted data — use AWS KMS GenerateKey or GenerateDataKey

Approved key generation methods:

Key Type Generation Method Tool
Symmetric (AES-256) KMS keys KMS CreateKey API AWS KMS (FIPS 140-2 Level 3 HSMs)
Envelope DEKs KMS GenerateDataKey API AWS KMS
JWT signing secret crypto.randomBytes(32).toString('hex') Node.js CSPRNG
TLS certificates ACME protocol Cloudflare / Let's Encrypt
BankID certificates BankID Norge AS CA External CA

Generation environment:

  • All KMS-managed keys for Restricted data: generated within AWS KMS HSMs — key material never leaves AWS
  • JWT_SECRET: generated using crypto.randomBytes(32) then stored in AWS Secrets Manager
  • All key generation events logged in AWS CloudTrail

4.3 Distribution

Principles:

  • NEVER transmit keys in plaintext over any channel
  • NEVER include keys in source code, .env files committed to Git, or log output
  • NEVER send keys via email, Slack, or any messaging platform
  • Always fetch secrets at application runtime from AWS Secrets Manager or KMS API

Distribution methods:

Scenario Method Notes
Application runtime secrets (JWT_SECRET) AWS Secrets Manager API at startup Fatal error if JWT_SECRET missing
Envelope DEK generation AWS KMS GenerateDataKey per encryption operation Key material decrypted in memory only
Developer access AWS IAM role + MFA Least privilege
CI/CD pipeline secrets GitHub Actions encrypted secrets (scoped) Rotated per project

4.4 Storage

Storage hierarchy for Drop:

Level 1 — AWS KMS HSMs (FIPS 140-2 Level 3)
  +-- drop-national-id-key (foedselsnummer encryption)
  +-- drop-db-master-key (database TDE — Phase 2)
  +-- drop-kyc-key (KYC document S3 encryption)
  +-- drop-backup-key (backup encryption — eu-west-1)

Level 2 — AWS Secrets Manager
  +-- JWT_SECRET (HMAC signing key)
  +-- SUMSUB_SECRET_KEY (API key)
  +-- BANKID_CLIENT_SECRET (OIDC secret)
  +-- DATABASE_URL (connection string)

Level 3 — Application memory (ephemeral only)
  +-- Decrypted DEK during active encryption/decryption
  +-- JWT_SECRET during token signing/verification
  NOTE: NEVER persist Level 3 to disk

Prohibited storage locations:

  • Source code or config files in Git
  • Application logs or error messages (Sentry, BetterStack)
  • Unencrypted database columns
  • Email attachments or Slack messages
  • Browser localStorage or sessionStorage
  • Container image layers

4.5 Usage

Access control principles:

Principle Implementation
Least privilege KMS key policies: encrypt-only for application, decrypt-only for compliance function
Separation of duties key-admin cannot perform decryption; application service cannot create keys
Key purpose binding drop-national-id-key used only for national ID encryption
Audit all access Every KMS operation logged in AWS CloudTrail
Time-bound access IAM role assumption with time-bound session tokens

KMS key policy roles:

IAM Role Permissions MFA Required
drop-key-admin CreateKey, ScheduleKeyDeletion, RotateKey — NO Decrypt YES
drop-app-encrypt kms:Encrypt, kms:GenerateDataKey No (service account)
drop-compliance-decrypt kms:Decrypt for drop-national-id-key only YES
drop-key-auditor kms:ListKeys, kms:DescribeKey, CloudTrail read YES
drop-app-runner kms:GenerateDataKey, kms:Decrypt (scoped per key) No (IAM role)

4.6 Rotation Schedule

Key Rotation Period Method Owner Alert if Overdue
drop-national-id-key Annual AWS KMS automatic key rotation Security team Yes — 7 days overdue
drop-db-master-key Annual AWS KMS automatic key rotation Security team Yes — 7 days overdue
drop-kyc-key Annual AWS KMS automatic key rotation Security team Yes — 7 days overdue
drop-backup-key Annual Manual + AWS KMS rotation Security team Yes — 7 days overdue
JWT_SECRET Quarterly Manual — generate new, deploy, rotate Security team Yes — 3 days overdue
TLS certificate (getdrop.no) 90 days Automated (Cloudflare / Let's Encrypt) Platform Yes — 14 days before expiry
BankID certificate Per BankID schedule Manual — BankID renewal process Security team Calendar reminder
SUMSUB_SECRET_KEY Annual or on compromise Manual via Sumsub dashboard Security team Yes — 7 days overdue

JWT_SECRET rotation overlap procedure:

  1. Generate new JWT_SECRET value
  2. Deploy new secret to AWS Secrets Manager
  3. Rolling deploy of Drop app (picks up new secret)
  4. Wait for all existing sessions to expire (max 24h — JWT expiry)
  5. All sessions issued with old secret are now invalid — users re-authenticate
  6. Delete old secret version from Secrets Manager

AWS KMS automatic rotation: AWS KMS generates new key material annually while retaining all prior versions for decryption of existing ciphertext.


4.7 Revocation Procedures

Trigger conditions for immediate revocation:

  • Known or suspected key compromise (key material logged, unauthorized access)
  • Employee departure with key management IAM role access
  • System compromise where key was in use
  • Detection of unauthorized decryption in CloudTrail logs

Emergency revocation procedure (target: < 1 hour):

Step 1: Alert Security Lead via #security-incident Slack channel
Step 2: Identify scope — which data was accessible with compromised key?
Step 3: AWS KMS: Disable compromised key (prevents new encrypt/decrypt)
Step 4: Generate replacement key via KMS CreateKey
Step 5: Re-encrypt all data protected by compromised key (Restricted first)
Step 6: Update KMS key references in application configuration
Step 7: Deploy updated configuration
Step 8: Verify all systems using new key
Step 9: Audit CloudTrail: What decrypt operations used compromised key in last 30 days?
Step 10: Assess breach notification requirement (data-breach-response-plan.md §5)
         -> GDPR Art. 33 / Personopplysningsloven § 32: 72h to Datatilsynet if personal data affected
         -> Finanstilsynet notification if payment data affected
Step 11: Document in incident log
Step 12: Post-mortem within 48 hours

JWT_SECRET emergency rotation (session compromise):

Step 1: Generate new JWT_SECRET immediately
Step 2: Deploy to AWS Secrets Manager
Step 3: Rolling deploy Drop application — all in-flight sessions invalidated
Step 4: All users must re-authenticate
Step 5: Document in incident log

4.8 Destruction

When destruction is required:

  • Key rotated out and overlap period expired
  • Crypto-shredding: deleting user foedselsnummer by destroying envelope DEK (GDPR Art. 17)
  • System decommissioned

Destruction methods:

Key Location Destruction Method Verification
AWS KMS key ScheduleKeyDeletion (7-30 day waiting period) KMS + CloudTrail audit log
AWS Secrets Manager DeleteSecret (7-30 day waiting period) Secrets Manager audit log
Envelope DEK in database Delete encrypted DEK field Verify decryption fails

Crypto-shredding for GDPR erasure (Art. 17): When a user requests account deletion:

  1. The envelope DEK for that user's foedselsnummer is deleted from the database
  2. The ciphertext (foedselsnummer) becomes permanently unreadable
  3. AML retention: transaction records retained 5 years per Hvitvaskingsloven § 30

5. Key Management System

Primary KMS: AWS KMS (eu-north-1 — Stockholm region) Backup KMS: AWS KMS (eu-west-1 — Ireland) for drop-backup-key only — separate region for disaster recovery Secrets management: AWS Secrets Manager (eu-north-1)


6. Access Controls for Key Operations

Operation Required Roles Approval Process
Create new KMS key drop-key-admin + CISO approval AWS IAM + ticket
Rotate KMS key (manual) drop-key-admin (proposer) + CISO (approver) Dual approval
Emergency key disable Security Lead (any one) Single — immediate incident ticket
Schedule key deletion drop-key-admin + CISO Dual approval — minimum 7-day waiting period
Grant new service access Security Lead + system owner IAM PR review + approval
Rotate JWT_SECRET Security Lead Single — with deployment coordination

7. Foedselsnummer Field Encryption — Implementation Pattern

Key: drop-national-id-key (AWS KMS — separate from database master key) Stored: Only AES-256-GCM ciphertext — never plaintext foedselsnummer in database Source: src/drop-app/src/lib/encrypt.ts (Phase 2 implementation)

// Envelope encryption for foedselsnummer
// Key: drop-national-id-key (AWS KMS)
// Stored: base64(encryptedDEK || iv || tag || ciphertext)

async function encryptNationalId(fodselsnummer: string): Promise<string> {
    const iv = crypto.randomBytes(12);  // 96-bit random IV — never reused
    const dek = await kmsClient.generateDataKey({
        KeyId: process.env.NATIONAL_ID_KEY_ARN,
        KeySpec: 'AES_256'
    });
    const cipher = crypto.createCipheriv('aes-256-gcm', dek.Plaintext, iv);
    const ciphertext = Buffer.concat([cipher.update(fodselsnummer, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    dek.Plaintext.fill(0);  // Zero DEK plaintext from memory after use
    // Return: base64(encryptedDEK || iv || tag || ciphertext)
    return Buffer.concat([dek.CiphertextBlob, iv, tag, ciphertext]).toString('base64');
}

Access restriction: Only the Compliance function has IAM access to drop-compliance-decrypt role. Application code cannot decrypt foedselsnummer except during explicit compliance workflows.


8. Audit Logging for Key Operations

All key operations logged in AWS CloudTrail, including:

  • Key creation (actor, key ID, key type, timestamp)
  • Encryption/decryption (actor, key ID, context, timestamp)
  • Key rotation (actor, old key version, new key version, timestamp)
  • Key disabling / deletion scheduling (actor, key ID, reason, timestamp)
  • Failed access attempts (actor, key ID, reason, timestamp)

Log destination: AWS CloudTrail → S3 (immutable, append-only) + BetterStack aggregation Log retention: 5 years (AML compliance — Hvitvaskingsloven § 30) Alert on:

  • Decrypt operations by unexpected IAM role
  • Failed KMS access attempts > 3 in 5 minutes
  • Off-hours access to drop-national-id-key or drop-kyc-key
  • Any ScheduleKeyDeletion event (immediate alert)

9. Exception Process

Exceptions not permitted for: Restricted (L4) data (foedselsnummer, KYC documents) — no exceptions to field-level encryption.

Exception request process:

  1. Submit request to: [email protected]
  2. Required: system, data classification, key being excepted, business justification, risk assessment, compensating controls, duration (max 12 months)
  3. Approval: CISO
  4. Logged in: compliance register
  5. Review: Quarterly — exceptions > 12 months require board-level approval

Active exceptions:

System Exception Expiry Compensating Controls
JWT signing (HS256 shared secret) Shared-secret JWT instead of asymmetric RS256/EdDSA Phase 2 migration JWT_SECRET in AWS Secrets Manager; 24h session expiry; server-side revocation table
SQLite MVP (no column-level TDE) Filesystem encryption only Phase 2 migration Full disk encryption; private network; no public DB access

10. Emergency Procedures — Key Compromise

Immediate response checklist (execute within 1 hour of suspected compromise):

□ Activate incident response — notify: Security Lead + CISO + CTO
□ Identify scope: Which data was accessible with compromised key?
□ AWS KMS: Disable compromised key immediately
□ For JWT_SECRET compromise: rotate immediately (all sessions invalidated)
□ Generate replacement key
□ Re-encrypt all data protected by compromised key (Restricted data first)
□ Audit CloudTrail: What was decrypted using compromised key in last 30 days?
□ Assess breach notification requirement (data-breach-response-plan.md §5)
   -> GDPR Art. 33 / Personopplysningsloven § 32: 72h to Datatilsynet
   -> Finanstilsynet notification if payment data affected
□ Document everything in incident log
□ Post-mortem within 48 hours

Re-encryption priority:

  1. Foedselsnummer (drop-national-id-key) — Restricted (L4), highest priority
  2. KYC documents (drop-kyc-key) — Restricted (L4)
  3. Database backup files (drop-backup-key) — Restricted (L4)
  4. Database master (drop-db-master-key) — Phase 2

Approval

Role Name Date Signature
Author Security Architect 2026-02-23
CISO
CTO
DPO (GDPR relevance)
Management