mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 15:31:09 +00:00
[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>
This commit is contained in:
@@ -1,39 +1,79 @@
|
||||
# Module: Azaion.Services.Security
|
||||
|
||||
## Purpose
|
||||
Static utility class providing the SHA-384 password hashing helper used by `UserService`.
|
||||
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` was deleted and `GetApiEncryptionKey` was simplified from `(email, password, hardwareHash)` to `(email, password)` by AZ-197.
|
||||
> **Cycle 1 (2026-05-13) note** — `GetHWHash` deleted; `GetApiEncryptionKey` simplified by AZ-197.
|
||||
>
|
||||
> **Cycle 2 (2026-05-14) note** — `GetApiEncryptionKey`, `EncryptTo`, and `DecryptTo` were all removed along with the encrypted-download endpoint. Only `ToHash` remains; it still backs SHA-384 password hashing in `UserService` (`PasswordHash = request.Password.ToHash()`). The `Azaion.Test/SecurityTest.cs` unit tests went with the removed methods, leaving the `Azaion.Test` project empty (also removed from the solution). See `_docs/06_metrics/retro_2026-05-14.md` once cycle 2's retro lands.
|
||||
> **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 |
|
||||
|--------|-----------|-------------|
|
||||
| `ToHash` | `static string ToHash(this string str)` | Extension: SHA-384 hash of input, returned as Base64 |
|
||||
| `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
|
||||
- `ToHash` uses SHA-384 with UTF-8 encoding, outputting Base64.
|
||||
|
||||
**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
|
||||
- `System.Security.Cryptography` (SHA384)
|
||||
- `System.Text.Encoding`
|
||||
|
||||
- `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` (password storage) and `ValidateUser` (login comparison) both call `request.Password.ToHash()`
|
||||
|
||||
- `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.
|
||||
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
|
||||
- Password hashing uses SHA-384 with no per-user salt and no key stretching. Not resistant to rainbow-table attacks (security audit F-7 — open). Unchanged by cycles 1 and 2.
|
||||
|
||||
- Argon2id memory cost (64 MiB) makes GPU bruteforce attacks orders of magnitude slower than the previous SHA-384 path. Each verify costs ~50–200 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
|
||||
None at the unit-test level after the `Azaion.Test` project was removed in cycle 2. `ToHash` is exercised end-to-end through every login / register e2e test (`e2e/Azaion.E2E/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/`.
|
||||
|
||||
Reference in New Issue
Block a user