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 (Data Encryption) AES-256-GCM Fødselsnummer (national ID)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 integrationJWT JWTsignature 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 data encryption (envelope pattern)DEKs Restricted (L4) Application Generated by AWS KMS GenerateDataKey

2.2 Secrets Inventory (Non-KMS)AWS Secrets Manager)

Secret Name PurposeStorage Rotation
JWT_SECRET JWT token signing AWS Secrets ManagerQuarterly
SUMSUB_SECRET_KEY Sumsub KYC integrationAWS Secrets Manager Annual or on compromise
BANKID_CLIENT_SECRET BankID OIDC client secret AWS Secrets ManagerPer BankID schedule
DATABASE_URL PostgreSQL connection stringAWS Secrets Manager On DB credential rotation
SENTRY_DSN Sentry error monitoringAWS Secrets Manager Annual

3. Key Hierarchy

AWS KMS Root of Trust (eu-north-1 primary)primary  ├──Stockholm)
    |
    +-- drop-national-id-key (AES-256-GCM — annual rotation)
    |       └──+-- DEK-{user_id}-{timestamp}: Per-record envelope DEKs
    |               └──+-- Encrypts: fødselsnummerfoedselsnummer 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 REGIONREGION)
            for backups)
            └──+-- 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 — annualannual)
    rotation)
    ├──+-- BANKID_CLIENT_SECRET (OIDC client secretsecret)
    — BankID schedule)
    └──+-- DATABASE_URL (connection string — on rotation)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 cryptographically secure random number generator (CSPRNG)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:

  • NeverNEVER transmit keys in plaintext over any channel
  • NeverNEVER include keys in source code, .env files committed to Git, configuration files in VCS, or log output
  • NeverNEVER 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 to non-production secrets AWS IAM role + MFA Least privilege
CI/CD pipeline secrets GitHub Actions encrypted secrets (scoped to repo)scoped) Rotated per project
BankID client secretAWS Secrets ManagerFetched at runtime

4.4 Storage

Storage hierarchy for Drop:

Level 1 — AWS KMS HSMs (FIPS 140-2 Level 3)
  ├──+-- drop-national-id-key (fødselsnummerfoedselsnummer 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
  operation
  └──+-- JWT_SECRET during token signing/verification
  NOTE: NEVER persist Level 3 to disk

Prohibited storage locations:

  • Source code or config files in Git (.env, config.ts, secrets.json)
  • Application logs or error messages (Sentry, BetterStack)
  • Unencrypted database columns
  • Email attachments or Slack messages
  • Browser localStorage or sessionStorage
  • Container image layers (Dockerfile, .dockerignore exclusions not sufficient)

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 — not for other purposes
Audit all access Every KMS operation logged in AWS CloudTrail — actor, key ID, operation, timestamp
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, VPC constraint)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)authenticate
  6. Delete old secret version from Secrets Manager

AWS KMS automatic rotation: When enabled, AWS KMS generates new key material annually while retaining all prior versions for decryption of existing ciphertext. Re-encryption of existing data is required when performing cryptographic erasure (GDPR Art. 17).


4.7 Revocation Procedures

Trigger conditions for immediate revocation:

  • Known or suspected key compromise (e.g., key material logged, unauthorized access)
  • Employee departure with access to key management IAM rolesrole access
  • System compromise where key was in use
  • Legal hold or regulatory requirement
  • Detection of unauthorized decryption activity in CloudTrail logs

Emergency revocation procedure (target: < 1 hour):

Step 1: Alert Security Lead immediately 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 (priority: Restricted first)
Step 6: Update KMS key references in application configuration
Step 7: Deploy updated application configuration
Step 8: Verify all systems using new key
Step 9: Audit CloudTrail: What decrypt operations used the compromised key in last 30 days?
Step 10: Assess breach notification requirement (see 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
(acceptable security impact)
Step 5: Document in incident log

4.8 Destruction

When destruction is required:

  • Key has been rotated out and overlap period expired (KMS: schedule deletion with 30-day waiting period)
  • Crypto-shredding: deleting user datafoedselsnummer by destroying the envelope DEK encrypted under drop-national-id-key (GDPR Art. 17 erasure mechanism)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
Application memory (DEK)Garbage collection (JavaScript — no explicit zeroing in V8 engine)Process termination
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 fødselsnummerfoedselsnummer is deleted from the database
  2. The ciphertext (fødselsnummer)foedselsnummer) becomes permanently unreadable
  3. AML retention: transaction records retained 5 years butper user'sHvitvaskingsloven fødselsnummer§ is unrecoverable30

5. Key Management System

Primary KMS: AWS KMS (eu-north-1 — Stockholm region) Backup KMS for backups: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)

Multi-region note: KMS keys are single-region by default. drop-backup-key is intentionally in a separate region (eu-west-1) so that a regional outage does not prevent backup restoration.


6. Access Controls for Key Operations

Separation of duties requirements:

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 via IAM policy
Emergency key disable Security Lead (any one) Single — immediate incident ticket required
Schedule key deletion drop-key-admin + CISO Dual approval — minimum 7-day waiting period
Grant new service access to key Security Lead + system owner IAM PR review + approval
Rotate JWT_SECRET Security Lead Single — with deployment coordination

7. 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)
  • Access grant/revoke (actor, grantee, key ID, permissions, 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 minimum — 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)

8. FødselsnummerFoedselsnummer Field Encryption — Implementation Pattern

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

// Envelope encryption for fødselsnummer — src/drop-app/src/lib/encrypt.ts (Phase 2)foedselsnummer
// Key: drop-national-id-key (AWS KMS — separate from db master)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);  // Securely zeroZero DEK plaintext from memory after use
    dek.Plaintext.fill(0);
    // Return: base64(encryptedDEK || iv || tag || ciphertext)
    return Buffer.concat([dek.CiphertextBlob, iv, tag, ciphertext]).toString('base64');
}
async function decryptNationalId(encrypted: string): Promise<string> {
    const buf = Buffer.from(encrypted, 'base64');
    // AWS KMS CiphertextBlob is always 184 bytes for AES_256 DEK
    const encryptedDek = buf.subarray(0, 184);
    const iv = buf.subarray(184, 196);          // 12 bytes
    const tag = buf.subarray(196, 212);          // 16 bytes
    const ciphertext = buf.subarray(212);
    const dek = await kmsClient.decrypt({ CiphertextBlob: encryptedDek });
    const decipher = crypto.createDecipheriv('aes-256-gcm', dek.Plaintext, iv);
    decipher.setAuthTag(tag);
    const plaintext = decipher.update(ciphertext) + decipher.final('utf8');
    dek.Plaintext.fill(0);
    return plaintext;
}

Access restriction: Only the Compliance function (KYC verification, AML reporting) has IAM access to drop-compliance-decrypt role. Application code cannot decrypt fødselsnummerfoedselsnummer 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 are not permitted for: Restricted (L4) data (fødselsnummer,foedselsnummer, KYC documents) — no exceptions to field-level encryption requirement.encryption.

Exception request process:

  1. Submit request to: [email protected]
  2. Required: system, data classification, key being excepted, business justification, risk assessment, compensating controls, proposed exception duration (max 12 months)
  3. Approval required from:Approval: CISO
  4. Exceptions loggedLogged in: compliance register
  5. Review: Quarterly — exceptions exceeding> 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, no PostgreSQL TDEonly 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 the compromised key in last 30 days?
□ 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
□ Document everything in incident log
□ Post-mortem within 48 hours

Re-encryption priority:

  1. FødselsnummerFoedselsnummer (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