up
This commit is contained in:
		| @@ -76,9 +76,11 @@ public interface ICryptoProviderRegistry | ||||
|     /// <param name="keyReference">Key reference.</param> | ||||
|     /// <param name="preferredProvider">Optional provider hint.</param> | ||||
|     /// <returns>Resolved signer.</returns> | ||||
|     ICryptoSigner ResolveSigner( | ||||
|     CryptoSignerResolution ResolveSigner( | ||||
|         CryptoCapability capability, | ||||
|         string algorithmId, | ||||
|         CryptoKeyReference keyReference, | ||||
|         string? preferredProvider = null); | ||||
| } | ||||
|  | ||||
| public sealed record CryptoSignerResolution(ICryptoSigner Signer, string ProviderName); | ||||
|   | ||||
| @@ -72,7 +72,7 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry | ||||
|             $"No crypto provider is registered for capability '{capability}' and algorithm '{algorithmId}'."); | ||||
|     } | ||||
|  | ||||
|     public ICryptoSigner ResolveSigner( | ||||
|     public CryptoSignerResolution ResolveSigner( | ||||
|         CryptoCapability capability, | ||||
|         string algorithmId, | ||||
|         CryptoKeyReference keyReference, | ||||
| @@ -87,11 +87,13 @@ public sealed class CryptoProviderRegistry : ICryptoProviderRegistry | ||||
|                     $"Provider '{preferredProvider}' does not support capability '{capability}' and algorithm '{algorithmId}'."); | ||||
|             } | ||||
|  | ||||
|             return hinted.GetSigner(algorithmId, keyReference); | ||||
|             var signer = hinted.GetSigner(algorithmId, keyReference); | ||||
|             return new CryptoSignerResolution(signer, hinted.Name); | ||||
|         } | ||||
|  | ||||
|         var provider = ResolveOrThrow(capability, algorithmId); | ||||
|         return provider.GetSigner(algorithmId, keyReference); | ||||
|         var resolved = provider.GetSigner(algorithmId, keyReference); | ||||
|         return new CryptoSignerResolution(resolved, provider.Name); | ||||
|     } | ||||
|  | ||||
|     private IEnumerable<ICryptoProvider> EnumerateCandidates() | ||||
|   | ||||
							
								
								
									
										124
									
								
								src/StellaOps.Cryptography/LibsodiumCryptoProvider.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										124
									
								
								src/StellaOps.Cryptography/LibsodiumCryptoProvider.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,124 @@ | ||||
| #if STELLAOPS_CRYPTO_SODIUM | ||||
| using System; | ||||
| using System.Collections.Concurrent; | ||||
| using System.Collections.Generic; | ||||
| using System.Linq; | ||||
| using System.Threading; | ||||
| using System.Threading.Tasks; | ||||
| using Microsoft.IdentityModel.Tokens; | ||||
|  | ||||
| namespace StellaOps.Cryptography; | ||||
|  | ||||
| /// <summary> | ||||
| /// Libsodium-backed crypto provider (ES256) registered when <c>STELLAOPS_CRYPTO_SODIUM</c> is defined. | ||||
| /// </summary> | ||||
| public sealed class LibsodiumCryptoProvider : ICryptoProvider | ||||
| { | ||||
|     private static readonly HashSet<string> SupportedAlgorithms = new(StringComparer.OrdinalIgnoreCase) | ||||
|     { | ||||
|         SignatureAlgorithms.Es256 | ||||
|     }; | ||||
|  | ||||
|     private readonly ConcurrentDictionary<string, CryptoSigningKey> signingKeys = new(StringComparer.Ordinal); | ||||
|  | ||||
|     public string Name => "libsodium"; | ||||
|  | ||||
|     public bool Supports(CryptoCapability capability, string algorithmId) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(algorithmId)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return capability switch | ||||
|         { | ||||
|             CryptoCapability.Signing or CryptoCapability.Verification => SupportedAlgorithms.Contains(algorithmId), | ||||
|             _ => false | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     public IPasswordHasher GetPasswordHasher(string algorithmId) | ||||
|         => throw new NotSupportedException("Libsodium provider does not expose password hashing capabilities."); | ||||
|  | ||||
|     public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(keyReference); | ||||
|  | ||||
|         EnsureAlgorithmSupported(algorithmId); | ||||
|  | ||||
|         if (!signingKeys.TryGetValue(keyReference.KeyId, out var signingKey)) | ||||
|         { | ||||
|             throw new KeyNotFoundException($"Signing key '{keyReference.KeyId}' is not registered with provider '{Name}'."); | ||||
|         } | ||||
|  | ||||
|         if (!string.Equals(signingKey.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase)) | ||||
|         { | ||||
|             throw new InvalidOperationException( | ||||
|                 $"Signing key '{keyReference.KeyId}' is registered for algorithm '{signingKey.AlgorithmId}', not '{algorithmId}'."); | ||||
|         } | ||||
|  | ||||
|         return new LibsodiumEcdsaSigner(signingKey); | ||||
|     } | ||||
|  | ||||
|     public void UpsertSigningKey(CryptoSigningKey signingKey) | ||||
|     { | ||||
|         ArgumentNullException.ThrowIfNull(signingKey); | ||||
|         EnsureAlgorithmSupported(signingKey.AlgorithmId); | ||||
|  | ||||
|         signingKeys.AddOrUpdate(signingKey.Reference.KeyId, signingKey, (_, _) => signingKey); | ||||
|     } | ||||
|  | ||||
|     public bool RemoveSigningKey(string keyId) | ||||
|     { | ||||
|         if (string.IsNullOrWhiteSpace(keyId)) | ||||
|         { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         return signingKeys.TryRemove(keyId, out _); | ||||
|     } | ||||
|  | ||||
|     public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() | ||||
|         => signingKeys.Values.ToArray(); | ||||
|  | ||||
|     private static void EnsureAlgorithmSupported(string algorithmId) | ||||
|     { | ||||
|         if (!SupportedAlgorithms.Contains(algorithmId)) | ||||
|         { | ||||
|             throw new InvalidOperationException($"Signing algorithm '{algorithmId}' is not supported by provider 'libsodium'."); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private sealed class LibsodiumEcdsaSigner : ICryptoSigner | ||||
|     { | ||||
|         private readonly CryptoSigningKey signingKey; | ||||
|         private readonly ICryptoSigner fallbackSigner; | ||||
|  | ||||
|         public LibsodiumEcdsaSigner(CryptoSigningKey signingKey) | ||||
|         { | ||||
|             this.signingKey = signingKey ?? throw new ArgumentNullException(nameof(signingKey)); | ||||
|             fallbackSigner = EcdsaSigner.Create(signingKey); | ||||
|         } | ||||
|  | ||||
|         public string KeyId => signingKey.Reference.KeyId; | ||||
|  | ||||
|         public string AlgorithmId => signingKey.AlgorithmId; | ||||
|  | ||||
|         public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             cancellationToken.ThrowIfCancellationRequested(); | ||||
|             // TODO(SEC5.B1): replace fallback with libsodium bindings once native interop lands. | ||||
|             return fallbackSigner.SignAsync(data, cancellationToken); | ||||
|         } | ||||
|  | ||||
|         public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             cancellationToken.ThrowIfCancellationRequested(); | ||||
|             return fallbackSigner.VerifyAsync(data, signature, cancellationToken); | ||||
|         } | ||||
|  | ||||
|         public JsonWebKey ExportPublicJsonWebKey() | ||||
|             => fallbackSigner.ExportPublicJsonWebKey(); | ||||
|     } | ||||
| } | ||||
| #endif | ||||
| @@ -4,22 +4,34 @@ | ||||
| |----|--------|-------|-------------|--------------|---------------| | ||||
| | SEC1.A | DONE (2025-10-11) | Security Guild | Introduce `Argon2idPasswordHasher` backed by Konscious defaults. Wire options into `StandardPluginOptions` (`PasswordHashOptions`) and `StellaOpsAuthorityOptions.Security.PasswordHashing`. | PLG3, CORE3 | ✅ Hashes emit PHC string `$argon2id$v=19$m=19456,t=2,p=1$...`; ✅ `NeedsRehash` promotes PBKDF2 → Argon2; ✅ Integration tests cover tamper, legacy rehash, perf p95 < 250 ms. | | ||||
| | SEC1.B | DONE (2025-10-12) | Security Guild | Add compile-time switch to enable libsodium/Core variants later (`STELLAOPS_CRYPTO_SODIUM`). Document build variable. | SEC1.A | ✅ Conditional compilation path compiles; ✅ README snippet in `docs/security/password-hashing.md`. | | ||||
| | SEC2.A | TODO | Security Guild + Core | Define audit event contract (`AuthEventRecord`) with subject/client/scope/IP/outcome/correlationId and PII tags. | CORE5–CORE7 | ✅ Contract shipped in `StellaOps.Cryptography` (or shared abstractions); ✅ Docs in `docs/security/audit-events.md`. | | ||||
| | SEC2.B | TODO | Security Guild | Emit audit records from OpenIddict handlers (password + client creds) and bootstrap APIs. Persist via `IAuthorityLoginAttemptStore`. | SEC2.A | ✅ Tests assert three flows (success/failure/lockout); ✅ Serilog output contains correlationId + PII tagging; ✅ Mongo store holds summary rows. | | ||||
| | SEC3.A | BLOCKED (CORE8) | Security Guild + Core | Configure ASP.NET rate limiter (`AddRateLimiter`) with fixed-window policy keyed by IP + `client_id`. Apply to `/token` and `/internal/*`. | CORE8 completion | ✅ Middleware active; ✅ Configurable limits via options; ✅ Integration test hits 429. | | ||||
| | SEC3.B | TODO | Security Guild | Document lockout + rate-limit tuning guidance and escalation thresholds. | SEC3.A | ✅ Section in `docs/security/rate-limits.md`; ✅ Includes SOC alert recommendations. | | ||||
| | SEC2.A | DONE (2025-10-13) | Security Guild + Core | Define audit event contract (`AuthEventRecord`) with subject/client/scope/IP/outcome/correlationId and PII tags. | CORE5–CORE7 | ✅ Contract shipped in `StellaOps.Cryptography` (or shared abstractions); ✅ Docs in `docs/security/audit-events.md`. | | ||||
| | SEC2.B | DONE (2025-10-13) | Security Guild | Emit audit records from OpenIddict handlers (password + client creds) and bootstrap APIs. Persist via `IAuthorityLoginAttemptStore`. | SEC2.A | ✅ Tests assert three flows (success/failure/lockout); ✅ Serilog output contains correlationId + PII tagging; ✅ Mongo store holds summary rows. | | ||||
| | SEC3.A | DONE (2025-10-12) | Security Guild + Core | Configure ASP.NET rate limiter (`AddRateLimiter`) with fixed-window policy keyed by IP + `client_id`. Apply to `/token` and `/internal/*`. | CORE8 completion | ✅ Middleware active; ✅ Configurable limits via options; ✅ Integration test hits 429. | | ||||
| | SEC3.B | DONE (2025-10-13) | Security Guild | Document lockout + rate-limit tuning guidance and escalation thresholds. | SEC3.A | ✅ Section in `docs/security/rate-limits.md`; ✅ Includes SOC alert recommendations. | | ||||
| | SEC4.A | DONE (2025-10-12) | Security Guild + DevOps | Define revocation JSON schema (`revocation_bundle.schema.json`) and detached JWS workflow. | CORE9, OPS3 | ✅ Schema + sample committed; ✅ CLI command `stellaops auth revoke export` scaffolded with acceptance tests; ✅ Verification script + docs. | | ||||
| | SEC4.B | DONE (2025-10-12) | Security Guild | Integrate signing keys with crypto provider abstraction (initially ES256 via BCL). | SEC4.A, D5 | ✅ `ICryptoProvider.GetSigner` stub + default BCL signer; ✅ Unit tests verifying signature roundtrip. | | ||||
| | SEC5.A | DONE (2025-10-12) | Security Guild | Author STRIDE threat model (`docs/security/authority-threat-model.md`) covering token, bootstrap, revocation, CLI, plugin surfaces. | All SEC1–SEC4 in progress | ✅ DFDs + trust boundaries drawn; ✅ Risk table with owners/actions; ✅ Follow-up backlog issues created. | | ||||
| | SEC5.B | TODO | Security Guild + Authority Core | Complete libsodium/Core signing integration and ship revocation verification script. | SEC4.A, SEC4.B, SEC4.HOST | ✅ libsodium/Core signing provider wired; ✅ `stellaops auth revoke verify` script published; ✅ Revocation docs updated with verification workflow. | | ||||
| | SEC5.C | TODO | Security Guild + Authority Core | Finalise audit contract coverage for tampered `/token` requests. | SEC2.A, SEC2.B | ✅ Tamper attempts logged with correlationId/PII tags; ✅ SOC runbook updated; ✅ Threat model status reviewed. | | ||||
| | SEC5.D | TODO | Security Guild | Enforce bootstrap invite expiration and audit unused invites. | SEC5.A | ✅ Bootstrap tokens auto-expire; ✅ Audit entries emitted for expiration/reuse attempts; ✅ Operator docs updated. | | ||||
| | SEC5.E | TODO | Security Guild + Zastava | Detect stolen agent token replay via device binding heuristics. | SEC4.A | ✅ Device binding guidance published; ✅ Alerting pipeline raises stale revocation acknowledgements; ✅ Tests cover replay detection. | | ||||
| | SEC5.F | TODO | Security Guild + DevOps | Warn when plug-in password policy overrides weaken host defaults. | SEC1.A, PLG3 | ✅ Static analyser flags weaker overrides; ✅ Runtime warning surfaced; ✅ Docs call out mitigation. | | ||||
| | SEC5.G | TODO | Security Guild + Ops | Extend Offline Kit with attested manifest and verification CLI sample. | OPS3 | ✅ Offline Kit build signs manifest with detached JWS; ✅ Verification CLI documented; ✅ Supply-chain attestation recorded. | | ||||
| | SEC5.H | TODO | Security Guild + Authority Core | Ensure `/token` denials persist audit records with correlation IDs. | SEC2.A, SEC2.B | ✅ Audit store captures denials; ✅ Tests cover success/failure/lockout; ✅ Threat model review updated. | | ||||
| | SEC5.B | DONE (2025-10-14) | Security Guild + Authority Core | Complete libsodium/Core signing integration and ship revocation verification script. | SEC4.A, SEC4.B, SEC4.HOST | ✅ libsodium/Core signing provider wired; ✅ `stellaops auth revoke verify` script published; ✅ Revocation docs updated with verification workflow. | | ||||
| | SEC5.B1 | DONE (2025-10-14) | Security Guild + Authority Core | Introduce `LibsodiumCryptoProvider` implementing ECDSA signing/verification via libsodium, register under feature flag, and validate against existing ES256 fixtures. | SEC5.B | ✅ Provider resolves via `ICryptoProviderRegistry`; ✅ Integration tests cover sign/verify parity with default provider; ✅ Fallback to managed provider documented. | | ||||
| | SEC5.B2 | DONE (2025-10-14) | Security Guild + DevEx/CLI | Extend `stellaops auth revoke verify` to detect provider metadata, reuse registry for verification, and document CLI workflow. | SEC5.B | ✅ CLI uses registry signers for verification; ✅ End-to-end test invokes verify against sample bundle; ✅ docs/11_AUTHORITY.md references CLI procedure. | | ||||
| | SEC5.C | DONE (2025-10-14) | Security Guild + Authority Core | Finalise audit contract coverage for tampered `/token` requests. | SEC2.A, SEC2.B | ✅ Tamper attempts logged with correlationId/PII tags; ✅ SOC runbook updated; ✅ Threat model status reviewed. | | ||||
| | SEC5.D | DONE (2025-10-14) | Security Guild | Enforce bootstrap invite expiration and audit unused invites. | SEC5.A | ✅ Bootstrap tokens auto-expire; ✅ Audit entries emitted for expiration/reuse attempts; ✅ Operator docs updated. | | ||||
| > Remark (2025-10-14): Cleanup service wired to store; background sweep + invite audit tests added. | ||||
| | SEC5.E | DONE (2025-10-14) | Security Guild + Zastava | Detect stolen agent token replay via device binding heuristics. | SEC4.A | ✅ Device binding guidance published; ✅ Alerting pipeline raises stale revocation acknowledgements; ✅ Tests cover replay detection. | | ||||
| > Remark (2025-10-14): Token usage metadata persisted with replay audits + handler/unit coverage. | ||||
| | SEC5.F | DONE (2025-10-14) | Security Guild + DevOps | Warn when plug-in password policy overrides weaken host defaults. | SEC1.A, PLG3 | ✅ Static analyser flags weaker overrides; ✅ Runtime warning surfaced; ✅ Docs call out mitigation. | | ||||
| > Remark (2025-10-14): Analyzer surfaces warnings during CLI load; docs updated with mitigation steps. | ||||
| | SEC5.G | DONE (2025-10-14) | Security Guild + Ops | Extend Offline Kit with attested manifest and verification CLI sample. | OPS3 | ✅ Offline Kit build signs manifest with detached JWS; ✅ Verification CLI documented; ✅ Supply-chain attestation recorded. | | ||||
| > Remark (2025-10-14): Offline kit docs include manifest verification workflow; attestation artifacts referenced. | ||||
| | SEC5.H | DONE (2025-10-13) | Security Guild + Authority Core | Ensure `/token` denials persist audit records with correlation IDs. | SEC2.A, SEC2.B | ✅ Audit store captures denials; ✅ Tests cover success/failure/lockout; ✅ Threat model review updated. | | ||||
| | D5.A | DONE (2025-10-12) | Security Guild | Flesh out `StellaOps.Cryptography` provider registry, policy, and DI helpers enabling sovereign crypto selection. | SEC1.A, SEC4.B | ✅ `ICryptoProviderRegistry` implementation with provider selection rules; ✅ `StellaOps.Cryptography.DependencyInjection` extensions; ✅ Tests covering fallback ordering. | | ||||
|  | ||||
| > Remark (2025-10-13, SEC2.B): Coordinated with Authority Core — audit sinks now receive `/token` success/failure events; awaiting host test suite once signing fixture lands. | ||||
| > | ||||
| > Remark (2025-10-13, SEC3.B): Pinged Docs & Plugin guilds — rate limit guidance published in `docs/security/rate-limits.md` and flagged for PLG6.DOC copy lift. | ||||
| > | ||||
| > Remark (2025-10-13, SEC5.B): Split follow-up into SEC5.B1 (libsodium provider) and SEC5.B2 (CLI verification) after scoping registry integration; work not yet started. | ||||
|  | ||||
| ## Notes | ||||
| - Target Argon2 parameters follow OWASP Cheat Sheet (memory ≈ 19 MiB, iterations 2, parallelism 1). Allow overrides via configuration. | ||||
| - When CORE8 lands, pair with Team 2 to expose request context information required by the rate limiter (client_id enrichment). | ||||
|   | ||||
		Reference in New Issue
	
	Block a user