72 lines
2.6 KiB
C#
72 lines
2.6 KiB
C#
using StellaOps.Cryptography;
|
|
|
|
namespace StellaOps.Audit.ReplayToken;
|
|
|
|
/// <summary>
|
|
/// Generates replay tokens using SHA-256 hashing with deterministic canonicalization.
|
|
/// </summary>
|
|
public sealed partial class Sha256ReplayTokenGenerator : IReplayTokenGenerator
|
|
{
|
|
private readonly ICryptoHash _cryptoHash;
|
|
private readonly TimeProvider _timeProvider;
|
|
|
|
public Sha256ReplayTokenGenerator(ICryptoHash cryptoHash, TimeProvider timeProvider)
|
|
{
|
|
_cryptoHash = cryptoHash ?? throw new ArgumentNullException(nameof(cryptoHash));
|
|
_timeProvider = timeProvider ?? throw new ArgumentNullException(nameof(timeProvider));
|
|
}
|
|
|
|
public ReplayToken Generate(ReplayTokenRequest request)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var hashHex = ComputeTokenValue(request, ReplayToken.DefaultVersion);
|
|
return new ReplayToken(hashHex, _timeProvider.GetUtcNow());
|
|
}
|
|
|
|
public ReplayToken GenerateWithExpiration(ReplayTokenRequest request, TimeSpan? expiration = null)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var effectiveExpiration = expiration ?? ReplayToken.DefaultExpiration;
|
|
if (effectiveExpiration <= TimeSpan.Zero)
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(expiration), "Expiration must be positive.");
|
|
}
|
|
|
|
var hashHex = ComputeTokenValue(request, ReplayToken.VersionWithExpiration);
|
|
var now = _timeProvider.GetUtcNow();
|
|
var expiresAt = now + effectiveExpiration;
|
|
|
|
return new ReplayToken(hashHex, now, expiresAt, ReplayToken.DefaultAlgorithm, ReplayToken.VersionWithExpiration);
|
|
}
|
|
|
|
public bool Verify(ReplayToken token, ReplayTokenRequest request)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(token);
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var computed = ComputeTokenValue(request, token.Version);
|
|
return string.Equals(token.Value, computed, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
public ReplayTokenVerificationResult VerifyWithExpiration(ReplayToken token, ReplayTokenRequest request)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(token);
|
|
ArgumentNullException.ThrowIfNull(request);
|
|
|
|
var computed = ComputeTokenValue(request, token.Version);
|
|
if (!string.Equals(token.Value, computed, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return ReplayTokenVerificationResult.Invalid;
|
|
}
|
|
|
|
if (token.IsExpired(_timeProvider.GetUtcNow()))
|
|
{
|
|
return ReplayTokenVerificationResult.Expired;
|
|
}
|
|
|
|
return ReplayTokenVerificationResult.Valid;
|
|
}
|
|
}
|