Files
admin/_docs/02_document/modules/services_security.md
T
Oleksandr Bezdieniezhnykh a77b3f8a59 [AZ-529] [AZ-530] Cycle-2 documentation refresh
Refreshes _docs/02_document/ to reflect the cycle-2 auth-modernization
+ CMMC hardening landings (AZ-531..AZ-538). Authoritative source for
the ripple set is ripple_log_cycle2.md.

Covered:
- architecture.md (section 1 rewritten, ADRs 6-9 added)
- data_model.md (sessions, audit_events, user columns, migrations)
- system-flows.md (F1 rewritten; F11-F17 added; F2/F7/F9 minor)
- module-layout.md (cycle-2 sub-component table)
- diagrams/flows/flow_login.md (dual-token + MFA)
- components/{01_data_layer,03_auth_and_security,05_admin_api}
- modules/ (12 new, 8 modified — full Argon2id/ES256/MFA/refresh
  /mission/session/audit/jwks rollup)
- tests/{blackbox,security,traceability-matrix}

Step 13 (Update Docs) output for cycle 2.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 09:22:53 +03:00

80 lines
4.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Module: Azaion.Services.Security
## Purpose
Static utility class providing password hashing and verification. As of cycle 2, hashes new passwords with **Argon2id (RFC 9106)** and transparently re-hashes legacy SHA-384 entries on the next successful login.
> **Cycle 1 (2026-05-13) note** — `GetHWHash` deleted; `GetApiEncryptionKey` simplified by AZ-197.
>
> **Cycle 2 (2026-05-14) note A** — `GetApiEncryptionKey` / `EncryptTo` / `DecryptTo` removed with the encrypted-download endpoint. The `Azaion.Test` project went with them.
>
> **Cycle 2 (2026-05-14) note B (AZ-536)** — `ToHash` was removed and replaced with `HashPassword` + `VerifyPassword`. Hash format is now PHC: `$argon2id$v=19$m=65536,t=3,p=1$<salt-b64>$<hash-b64>`. Legacy SHA-384 hashes (64-char Base64, no `$` prefix) are still accepted for verification and the verify path returns `NeedsRehash=true` so `UserService.ValidateUser` can rewrite them on the success path. Epic AZ-530, CMMC IA.L2-3.5.10.
## Public Interface
| Method | Signature | Description |
|--------|-----------|-------------|
| `HashPassword` | `static string HashPassword(string plaintext)` | Generates a 16-byte salt, computes Argon2id with the conservative defaults below, returns a PHC string. |
| `VerifyPassword` | `static VerifyResult VerifyPassword(string plaintext, string stored)` | Detects format by prefix. Argon2id PHC → re-derives + constant-time compare; legacy SHA-384 → re-hashes + constant-time compare. Returns `Valid`, plus `NeedsRehash=true` when (a) the stored hash is legacy SHA-384, or (b) the stored Argon2 parameters are weaker than current defaults. |
### `record VerifyResult(bool Valid, bool NeedsRehash)`
Carries the verification outcome. `NeedsRehash` is the trigger for `UserService.RegisterSuccessfulLogin` to write a fresh Argon2id hash back to the row.
## Internal Logic
**Defaults (RFC 9106 §4 conservative profile)**:
- Memory: 65536 KiB (64 MiB)
- Iterations: 3
- Parallelism: 1
- Salt: 16 bytes (128 bits) per RFC §3.1 minimum
- Hash output: 32 bytes (256 bits)
**Format detection**:
- Argon2id PHC string starts with `$argon2id$`.
- Legacy SHA-384: exactly 64 base64 characters and does NOT start with `$`.
- Anything else fails verify with `Valid=false, NeedsRehash=false`.
**PHC encoding** uses base64 *without* padding (PHC convention):
```
$argon2id$v=19$m=<KiB>,t=<iters>,p=<lanes>$<salt-b64-nopad>$<hash-b64-nopad>
```
**Constant-time comparison** uses `CryptographicOperations.FixedTimeEquals` for both formats — addresses AZ-536 AC-5 (no remotely-observable timing leak).
## Dependencies
- `Konscious.Security.Cryptography.Argon2` (Argon2id implementation, pure C#)
- `System.Security.Cryptography.SHA384` (legacy verify path)
- `System.Security.Cryptography.RandomNumberGenerator` (salt entropy)
- `System.Security.Cryptography.CryptographicOperations` (constant-time compare)
## Consumers
- `Azaion.Services/UserService.cs`
- `RegisterUser` — calls `HashPassword(request.Password)`
- `ValidateUser``RegisterSuccessfulLogin` — calls `VerifyPassword`; on `NeedsRehash` writes a fresh Argon2id hash back transactionally (conditional on the original hash to avoid clobbering a parallel rehash)
- `Azaion.Services/MfaService.cs`
- `Enroll` and `Disable` — re-auth via `VerifyPassword(password, user.PasswordHash)`
## Data Models
None.
## Configuration
None directly. The defaults are class-level constants. Bumping them later automatically surfaces `NeedsRehash=true` for any older stored hash, so the upgrade is lazy and transparent.
## External Integrations
None.
## Security
- Argon2id memory cost (64 MiB) makes GPU bruteforce attacks orders of magnitude slower than the previous SHA-384 path. Each verify costs ~50200 ms on commodity hardware (intentional latency floor).
- Legacy SHA-384 hashes are migrated on next successful login (lazy migration). Service accounts that never log in interactively (CompanionPC devices) need an admin-side bulk-reset rotation cycle to upgrade.
- The verify path is constant-time end-to-end via `FixedTimeEquals` — defends AZ-536 AC-5.
- The "needs rehash" flag also covers future parameter bumps: raising `Argon2MemoryKib`/`Argon2Iterations` here will make all weaker stored hashes upgrade themselves on the next login.
## Tests
- `e2e/Azaion.E2E/Tests/PasswordHashingTests.cs` — AC-1 (PHC format), AC-2 (legacy SHA-384 still validates), AC-3 (transparent re-hash), AC-4 (wrong password fails for both formats), AC-5 (constant-time verify).
- **Known follow-up** (carried from cycle 2 batch 4 review) — `PasswordHashingTests.AC5_Verify_uses_constant_time_comparator_no_obvious_timing_leak` is intermittently flaky under suite-level concurrency; widen the assertion bound or warm Argon2 with a non-test login first.
- `Azaion.Services` is exercised end-to-end through every login / register / MFA flow in `e2e/Azaion.E2E/Tests/`.