# 06 — Data Classification, Encryption Plan and Privacy Assessment

## 1. Classification
| Class | Meaning | Examples | Handling |
|---|---|---|---|
| C0 Public | Intended public | Brand name, public class timetable | No restriction |
| C1 Internal | Business operational | Settings, product catalogue, rooms | Authenticated, tenant-scoped |
| C2 Personal | PII | Name, email, phone, address, attendance, invoices, notes | Tenant-scoped, permissioned, audited on sensitive access; encrypted at rest at disk level where possible |
| C3 Sensitive | POPIA special personal information and high-risk identifiers | Health disclosures, assessments with health data, injuries, ID number, date of birth (decision D-09), emergency-contact medical notes, signed waivers/documents, ID/medical uploads | **Application-layer encrypted**, field-level permission, every view audited, excluded from default exports/logs |
| C4 Secrets | Credentials/keys | Password hashes, MFA secrets, recovery codes, SMTP/provider/backup credentials, API tokens, encryption keys | Hash (passwords, tokens) or encrypt; never logged, never in backups unencrypted, never in repo |
| Financial | Cross-cutting tag on C2 | Invoices, payments, bank refs | Append-only, audited; card data **never** stored (PAN/CVV banned) |

Each column in `DB-MAP.md` carries a class. Logs may contain only C0/C1 plus IDs; the redactor strips known C2–C4 keys.

## 2. Algorithms
- Preferred: **XChaCha20-Poly1305 IETF** (libsodium `sodium_crypto_aead_xchacha20poly1305_ietf_*`), 192-bit random nonces (safe to generate randomly).
- Fallback: **AES-256-GCM** via OpenSSL, 96-bit random nonce, 128-bit tag. Because random 96-bit nonces limit usage per key (~2^32 messages), AES-GCM mode uses per-record data keys derived by HKDF, or rotates the key at a documented counter threshold.
- Password hashing: Argon2id (`password_hash`), parameters benchmarked on the production server (target ≈ 250–500 ms), stored in ADR.
- Tokens: 256-bit random, stored as SHA-256 (hash only).
- Blind index: HMAC-SHA-256 with a separate per-tenant index key.

## 3. Key hierarchy
```
Master Key (MK)   — 32 bytes, outside DB and docroot (file mode 0600 owned by the PHP user, or env var), never in repo/backups
  └─ Tenant Key Encryption Key (KEK_t)  = HKDF(MK, "tenant-kek", tenant_id, key_version)
       └─ Tenant Data Key (DEK_t,v)  — random, stored wrapped by KEK_t in `tenant_keys`
       └─ Tenant Index Key (IDX_t,v) — random, stored wrapped, for blind indexes
       └─ File DEKs — random per document, wrapped by DEK_t in file metadata
```
Platform-level secrets (SMTP defaults, backup destination) use a platform data key wrapped by MK.

## 3b. Erasure versus immutable records (crypto-shredding)
Problem found in design review: invoices, signed agreements and audit rows must be immutable, yet a POPIA deletion request must remove personal information that was copied into them.
Design: personal details copied into immutable snapshots (customer name, address, email on an invoice; signatory on an agreement) are stored in a `pii` block encrypted with a **per-client key** (`client_keys`). The rest of the snapshot (amounts, tax, dates, numbers) stays readable, so accounting records survive. On an erasure request that is lawful, the client key is destroyed and the client row is anonymised; the snapshot then renders "Erased client". Records the tenant must keep by law keep their financial content but lose the identity. Audit `change_redacted` never stores personal values, only field names and record IDs. Backups still hold the wrapped key until they expire, so the key is also excluded from backup retention beyond the declared window (documented in KNOWN-LIMITATIONS).

## 4. Envelope format (versioned)
Stored text/binary: `GOS1.<alg>.<key_id>.<base64url(nonce)>.<base64url(ciphertext||tag)>`
- `GOS1` = envelope version; `alg` = `xc20p` | `a256gcm`; `key_id` = tenant key version (e.g. `t42v3`).
- **AAD** (authenticated, not stored): `tenant_id | table | column | row public_id | envelope version`. This binds ciphertext to its location so copying an encrypted value into another row/tenant fails to decrypt.
- Decryption always goes through `Crypto::decrypt(context)`, which checks permission (`Gate`) for C3 fields and writes an audit event for sensitive reads.
- Unknown version/alg or authentication failure → exception, audit/security event, never partial output.

## 5. Rotation and recovery
- **DEK rotation:** new `key_version`; new writes use it; background job re-encrypts old rows lazily/in batches; old DEK kept (wrapped) until no rows reference it.
- **MK rotation:** re-wrap all KEK-derived records; the process is a CLI command with a dry-run and a resumable cursor.
- **Recovery:** MK backup is a sealed copy (printed/offline, split among two custodians recommended) **kept separately from database backups and off-site backup archives**. Losing MK means C3/C4 data is unrecoverable: this is documented to the owner and a recovery drill is part of the backup verification (09 §7).
- **Compromise procedure:** rotate MK and DEKs, invalidate sessions, force secret re-entry (SMTP/provider), review audit.

## 6. Searching encrypted fields
Default: encrypted C3 fields are **not searchable**. Where a business need exists (e.g. look up client by ID number), use an **exact-match blind index** `HMAC-SHA256(IDX_t, normalise(value))`, per-tenant key.
Leakage: equality is revealed (identical values share an index, frequency analysis possible), low-entropy fields (DOB, short numbers) can be brute-forced by an attacker holding both the index and the key. No partial/range/prefix search over C3. Name/email/phone (C2) remain searchable plaintext columns protected by access control and disk encryption, an explicit trade-off.

## 7. Limits of application-layer encryption (honest statement)
Protects: stolen DB dumps, backup/off-site leaks, SQL-injection read access, curious DBAs without file access.
Does **not** protect: an attacker who can execute code as the PHP user or read the key file (they can decrypt anything the app can); a malicious tenant admin with legitimate permission; data in memory; data after export. Mitigations: key file isolation, separate PHP user/pool, audit on decrypts, anomaly alerts, least-privilege DB users, optional future KMS/HSM on VPS.
Disk-level (LUKS/volume) encryption on the server is recommended as an additional, separate layer.

## 8. Privacy assessment — South Africa (POPIA) first
*This is an engineering assessment, not legal advice. Confirm with a South African attorney or the tenant's information officer before go-live.*

**Roles:** Each gym tenant is generally the **responsible party**; the platform operator acts as an **operator** processing on its behalf, requiring a written operator agreement (POPIA s20–21) covering security measures and breach notification. Platform's own data (users, billing of tenants) makes the operator the responsible party for that.

| POPIA area | Design response |
|---|---|
| Lawful processing & purpose specification (conditions 1–3) | Each field collected has a documented purpose; configurable registration fields default to minimal set; purposes recorded with consents |
| Special personal information (health, biometrics, children's data) | Health data = C3, collected only with explicit consent or another lawful ground; field-level permission; trainers see only assigned clients; waiver text versioned. **Children (<18):** guardian consent flow required for minors (planned in M04) |
| Biometrics | No biometric access control in scope; if later added it is special information and needs separate assessment |
| Further processing limits | Marketing uses separate opt-in consent (also ECTA/POPIA s69 direct marketing rules); unsubscribe in every marketing email |
| Information quality / openness | Client correction workflow (`data_requests`), visible privacy notice per tenant |
| Security safeguards (condition 7) | Sections 2–7 above, audit log, access control, backups encrypted |
| Operators | Operator agreement template shipped; sub-processors list (SMTP, payment provider, off-site storage) maintained |
| Breach notification (s22) | Incident runbook: detect via audit/anomaly, assess, notify Information Regulator and affected data subjects "as soon as reasonably possible"; tenant-facing notification tooling in M20 |
| Data subject rights (s23–25) | Access/export (machine-readable + PDF), correction, deletion/de-identification workflow with due dates tracking (`data_requests`) |
| Retention (s14) | Per-class defaults, configurable per tenant, enforced by scheduled job; financial records retained for the legally required period (tax records commonly 5 years under SA tax law — to be confirmed by tenant's accountant); health data kept only as long as purpose needs |
| Cross-border transfer (s72) | Hosting location documented; off-site backup/email/payment providers outside SA require adequate protection or a binding agreement/consent; recommend SA or adequacy-region destinations; recorded in sub-processor list |
| Prior authorisation / Information Officer | Tenant registers Information Officer; field in tenant profile and privacy notice |

**Other jurisdictions** (GDPR, CCPA, HIPAA, etc.) are **additional per-tenant requirements only if applicable**, e.g. a tenant serving EU residents. The architecture does not assume them; the configurable retention, consent, export and deletion tooling is built to accommodate extra rules later.

## 9. Retention defaults (configurable; POPIA-minded)
| Data | Default | Action at end |
|---|---|---|
| Prospect/lead with no consent to keep | 12 months after last contact | Delete/anonymise |
| Inactive client PII (C2) | 24 months after last activity, subject to open balances | Anonymise |
| Health disclosures (C3) | Until membership end + 12 months unless longer purpose | Delete |
| Signed agreements | Contract term + 5 years (limitation/evidence) | Archive then delete |
| Invoices/payments | 5 years minimum (tenant accountant to confirm) | De-identify where permissible |
| Audit log | 24 months default, financial/security events longer | Purge by maintenance user, logged |
| Email outbox payloads | 90 days; metadata longer | Purge bodies |
| Backups | Rolling per 09 §6, shorter than data retention | Expire; note deleted data persists in backups until expiry |

## 10. Payment data
Card entry only via provider-hosted fields/redirect; store provider customer/payment-method tokens, last4, brand, expiry. No PAN, CVV or magnetic data in DB, logs or backups. Aims for PCI DSS SAQ A.
