save progress
This commit is contained in:
@@ -35,9 +35,13 @@ internal static class DpopNonceUtilities
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(clientId);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(keyThumbprint);
|
||||
|
||||
var normalizedAudience = audience.Trim().ToLowerInvariant();
|
||||
var normalizedClientId = clientId.Trim().ToLowerInvariant();
|
||||
var normalizedThumbprint = keyThumbprint.Trim().ToLowerInvariant();
|
||||
|
||||
return string.Create(
|
||||
"dpop-nonce:".Length + audience.Length + clientId.Length + keyThumbprint.Length + 2,
|
||||
(audience.Trim(), clientId.Trim(), keyThumbprint.Trim()),
|
||||
"dpop-nonce:".Length + normalizedAudience.Length + normalizedClientId.Length + normalizedThumbprint.Length + 2,
|
||||
(normalizedAudience, normalizedClientId, normalizedThumbprint),
|
||||
static (span, parts) =>
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
@@ -27,10 +27,8 @@ public sealed class DpopProofValidator : IDpopProofValidator
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var cloned = options.Value ?? throw new InvalidOperationException("DPoP options must be provided.");
|
||||
cloned.Validate();
|
||||
|
||||
this.options = cloned;
|
||||
var snapshot = options.Value ?? throw new InvalidOperationException("DPoP options must be provided.");
|
||||
this.options = snapshot.Snapshot();
|
||||
this.replayCache = replayCache ?? NullReplayCache.Instance;
|
||||
this.timeProvider = timeProvider ?? TimeProvider.System;
|
||||
this.logger = logger;
|
||||
@@ -50,12 +48,14 @@ public sealed class DpopProofValidator : IDpopProofValidator
|
||||
return DpopValidationResult.Failure("invalid_header", headerError ?? "Unable to decode header.");
|
||||
}
|
||||
|
||||
if (!headerElement.TryGetProperty("typ", out var typElement) || !string.Equals(typElement.GetString(), ProofType, StringComparison.OrdinalIgnoreCase))
|
||||
if (!headerElement.TryGetProperty("typ", out var typElement) ||
|
||||
typElement.ValueKind != JsonValueKind.String ||
|
||||
!string.Equals(typElement.GetString(), ProofType, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DpopValidationResult.Failure("invalid_header", "DPoP proof missing typ=dpop+jwt header.");
|
||||
}
|
||||
|
||||
if (!headerElement.TryGetProperty("alg", out var algElement))
|
||||
if (!headerElement.TryGetProperty("alg", out var algElement) || algElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
return DpopValidationResult.Failure("invalid_header", "DPoP proof missing alg header.");
|
||||
}
|
||||
@@ -88,7 +88,7 @@ public sealed class DpopProofValidator : IDpopProofValidator
|
||||
return DpopValidationResult.Failure("invalid_payload", payloadError ?? "Unable to decode payload.");
|
||||
}
|
||||
|
||||
if (!payloadElement.TryGetProperty("htm", out var htmElement))
|
||||
if (!payloadElement.TryGetProperty("htm", out var htmElement) || htmElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
return DpopValidationResult.Failure("invalid_payload", "DPoP proof missing htm claim.");
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public sealed class DpopProofValidator : IDpopProofValidator
|
||||
return DpopValidationResult.Failure("invalid_payload", "DPoP htm does not match request method.");
|
||||
}
|
||||
|
||||
if (!payloadElement.TryGetProperty("htu", out var htuElement))
|
||||
if (!payloadElement.TryGetProperty("htu", out var htuElement) || htuElement.ValueKind != JsonValueKind.String)
|
||||
{
|
||||
return DpopValidationResult.Failure("invalid_payload", "DPoP proof missing htu claim.");
|
||||
}
|
||||
|
||||
@@ -42,6 +42,25 @@ public sealed class DpopValidationOptions
|
||||
/// </summary>
|
||||
public IReadOnlySet<string> NormalizedAlgorithms { get; private set; } = ImmutableHashSet<string>.Empty;
|
||||
|
||||
internal DpopValidationOptions Snapshot()
|
||||
{
|
||||
var clone = new DpopValidationOptions
|
||||
{
|
||||
ProofLifetime = ProofLifetime,
|
||||
AllowedClockSkew = AllowedClockSkew,
|
||||
ReplayWindow = ReplayWindow
|
||||
};
|
||||
|
||||
clone.allowedAlgorithms.Clear();
|
||||
foreach (var algorithm in allowedAlgorithms)
|
||||
{
|
||||
clone.allowedAlgorithms.Add(algorithm);
|
||||
}
|
||||
|
||||
clone.Validate();
|
||||
return clone;
|
||||
}
|
||||
|
||||
public void Validate()
|
||||
{
|
||||
if (ProofLifetime <= TimeSpan.Zero)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<LangVersion>preview</LangVersion>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Description>Sender-constrained authentication primitives (DPoP, mTLS) shared across StellaOps services.</Description>
|
||||
@@ -35,6 +35,11 @@
|
||||
<ItemGroup>
|
||||
<None Include="README.md" Pack="true" PackagePath="" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
|
||||
<_Parameter1>StellaOps.Auth.Security.Tests</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Router\__Libraries\StellaOps.Messaging\StellaOps.Messaging.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,4 +7,4 @@ Source of truth: `docs/implplan/SPRINT_20251229_049_BE_csproj_audit_maint_tests.
|
||||
| --- | --- | --- |
|
||||
| AUDIT-0082-M | DONE | Maintainability audit for StellaOps.Auth.Security. |
|
||||
| AUDIT-0082-T | DONE | Test coverage audit for StellaOps.Auth.Security. |
|
||||
| AUDIT-0082-A | TODO | Pending approval for changes. |
|
||||
| AUDIT-0082-A | DONE | DPoP validation hardening, nonce normalization, and tests added. |
|
||||
|
||||
Reference in New Issue
Block a user