critical priority high complexity infrastructure pending backend specialist Tier 4

Acceptance Criteria

AES-GCM decryption is implemented in field-encryption-utils using the org key retrieved from the key store in task-007
Decryption accepts a ciphertext blob containing IV, ciphertext bytes, and HMAC tag as separate fields
HMAC-SHA256 integrity check is performed on the decrypted plaintext before it is returned to any caller
On successful decryption with valid HMAC, the function returns a typed DecryptionSuccess result containing the plaintext string
On HMAC mismatch (tampered content), the function returns a typed DecryptionError with error code INTEGRITY_VIOLATION and no plaintext exposed
On wrong key or corrupt ciphertext, the function returns a typed DecryptionError with error code DECRYPTION_FAILED
No plaintext value is ever transmitted over the network — all crypto operations occur in isolate or main thread entirely client-side
The decryption function is pure (no side effects) and returns a sealed result union type (DecryptionSuccess | DecryptionError)
IV is never reused: each ciphertext blob includes a unique 12-byte random IV
Function handles null or empty ciphertext input gracefully, returning DecryptionError with code INVALID_INPUT
Decryption completes within 200ms on mid-range Android device for a 4KB payload
No cryptographic keys or intermediate key material appear in logs, error messages, or crash reports

Technical Requirements

frameworks
Flutter
Dart pointycastle or flutter_sodium for AES-GCM
crypto package for HMAC-SHA256
apis
Dart crypto isolate API for offloading CPU-intensive work
Org key store interface established in task-007
data models
EncryptedFieldBlob (iv, ciphertext, hmacTag)
DecryptionSuccess (plaintext: String)
DecryptionError (code: DecryptionErrorCode, message: String)
DecryptionErrorCode enum (INTEGRITY_VIOLATION, DECRYPTION_FAILED, INVALID_INPUT, KEY_UNAVAILABLE)
performance requirements
Decryption of a single field completes in under 200ms on mid-range hardware
CPU-bound crypto work runs in a Dart isolate to avoid jank on the UI thread
Key material is not retained in memory beyond the scope of a single decryption call
security requirements
AES-256-GCM with 12-byte random IV per ciphertext
HMAC-SHA256 over decrypted plaintext for integrity verification
No plaintext or key material written to disk, logs, or network
Sealed result type prevents accidental plaintext access on error path
Dart Isolate used to isolate key material from main UI memory

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Use Dart's pointycastle library for AES-GCM (GCMBlockCipher) and the crypto package for HMAC-SHA256. Store IV (12 bytes), ciphertext, and HMAC tag as separate base64 fields in the EncryptedFieldBlob model — do not concatenate them into a single opaque blob as this makes debugging impossible. Run decryption in a Dart compute() isolate to prevent UI jank on older devices; pass only serialisable data (base64 strings) across the isolate boundary — never pass key objects. Model the result as a sealed class hierarchy (DecryptionSuccess / DecryptionError) using Dart 3 sealed classes; this forces callers to handle the error case at compile time.

The HMAC check must run after decryption, not before — AES-GCM provides authenticated encryption natively, but the additional HMAC provides a secondary, independently auditable integrity layer as required for sensitive personal data (GDPR article 32). Never log the org key or plaintext — use structured logging with a sanitise() helper that redacts known secret field names.

Testing Requirements

Write unit tests using flutter_test covering: (1) successful AES-GCM decryption returns correct plaintext, (2) HMAC mismatch returns DecryptionError with INTEGRITY_VIOLATION, (3) corrupt ciphertext returns DecryptionError with DECRYPTION_FAILED, (4) null/empty input returns INVALID_INPUT without throwing, (5) unavailable org key returns KEY_UNAVAILABLE. Use known test vectors for AES-GCM to validate correctness. Mock the key store to return controlled keys. Verify no plaintext leaks into the DecryptionError fields.

Achieve 100% branch coverage on the result type handling. Integration test: encrypt a real contact field, persist to Supabase, fetch back, decrypt — assert round-trip fidelity.

Component
Field Encryption Utilities
infrastructure high
Epic Risks (3)
high impact medium prob security

Blindeforbundet's encryption key retrieval mechanism may not be finalised at implementation time, or session key availability via Supabase RLS may be inconsistent, causing decryption failures that expose masked placeholders to users and degrade the experience.

Mitigation & Contingency

Mitigation: Agree with Blindeforbundet on key storage and retrieval contract before implementation starts. Prototype key retrieval in a spike against the staging Supabase instance and validate the full decrypt/verify cycle with real test data before committing to the implementation.

Contingency: Implement a fallback that shows a 'field temporarily unavailable' state with a retry affordance. Log decryption failures server-side for audit. Escalate to Blindeforbundet stakeholders to unblock key management before the service tier epic begins.

medium impact medium prob technical

NHF contacts may belong to up to 5 chapters, each governed by separate RLS policies. A coordinator's chapter scope may not cover all affiliations, causing partial profile reads or silent data omissions that are difficult to detect in tests.

Mitigation & Contingency

Mitigation: Map all RLS policy combinations for multi-chapter contacts early. Write integration tests that create contacts with 5 affiliations and query them from coordinators with varying chapter scopes. Use Supabase's RLS test utilities to verify row visibility per role.

Contingency: Add an explicit 'affiliation partially visible' state in the repository response model so the UI can communicate scope limitations to the coordinator rather than silently showing incomplete data.

low impact medium prob scope

Organisation-specific validation rules (e.g., NHF chapter limit, Blindeforbundet encrypted field edit flow) may expand in scope during implementation as edge cases are discovered, causing the validator to grow beyond the planned complexity.

Mitigation & Contingency

Mitigation: Define the complete validation rule set with product and org stakeholders before coding begins. Document each rule with its source organisation and acceptance test. Use a rule registry pattern so new rules can be added without modifying core validator logic.

Contingency: Timebox validator enhancements to 2 hours per additional rule. Defer non-blocking rules to a follow-on maintenance task rather than blocking the epic delivery.