using System; using System.Security.Cryptography; using System.Text; using static StellaOps.Localization.T; namespace StellaOps.Auth.Security.Dpop; internal static class DpopNonceUtilities { private static readonly char[] _base64Padding = { '=' }; internal static string GenerateNonce() { Span buffer = stackalloc byte[32]; RandomNumberGenerator.Fill(buffer); return Convert.ToBase64String(buffer) .TrimEnd(_base64Padding) .Replace('+', '-') .Replace('/', '_'); } internal static byte[] ComputeNonceHash(string nonce) { ArgumentException.ThrowIfNullOrWhiteSpace(nonce); var bytes = Encoding.UTF8.GetBytes(nonce); return SHA256.HashData(bytes); } internal static string EncodeHash(ReadOnlySpan hash) => Convert.ToHexString(hash); internal static string ComputeStorageKey(string audience, string clientId, string keyThumbprint) { ArgumentException.ThrowIfNullOrWhiteSpace(audience); 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 + normalizedAudience.Length + normalizedClientId.Length + normalizedThumbprint.Length + 2, (normalizedAudience, normalizedClientId, normalizedThumbprint), static (span, parts) => { var index = 0; const string Prefix = "dpop-nonce:"; Prefix.CopyTo(span); index += Prefix.Length; index = Append(span, index, parts.Item1); span[index++] = ':'; index = Append(span, index, parts.Item2); span[index++] = ':'; _ = Append(span, index, parts.Item3); }); static int Append(Span span, int index, string value) { if (value.Length == 0) { throw new ArgumentException(_t("common.validation.empty_after_trim", "Value")); } value.AsSpan().CopyTo(span[index..]); return index + value.Length; } } }