up
Some checks failed
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Build Test Deploy / authority-container (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Build Test Deploy / docs (push) Has been cancelled
Build Test Deploy / deploy (push) Has been cancelled
Build Test Deploy / build-test (push) Has been cancelled
Build Test Deploy / authority-container (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
This commit is contained in:
154
src/StellaOps.Cryptography.Tests/CryptoProviderRegistryTests.cs
Normal file
154
src/StellaOps.Cryptography.Tests/CryptoProviderRegistryTests.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using StellaOps.Cryptography;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Cryptography.Tests;
|
||||
|
||||
public class CryptoProviderRegistryTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResolveOrThrow_RespectsPreferredProviderOrder()
|
||||
{
|
||||
var providerA = new FakeCryptoProvider("providerA")
|
||||
.WithSupport(CryptoCapability.Signing, SignatureAlgorithms.Es256)
|
||||
.WithSigner(SignatureAlgorithms.Es256, "key-a");
|
||||
|
||||
var providerB = new FakeCryptoProvider("providerB")
|
||||
.WithSupport(CryptoCapability.Signing, SignatureAlgorithms.Es256)
|
||||
.WithSigner(SignatureAlgorithms.Es256, "key-b");
|
||||
|
||||
var registry = new CryptoProviderRegistry(new[] { providerA, providerB }, new[] { "providerB" });
|
||||
|
||||
var resolved = registry.ResolveOrThrow(CryptoCapability.Signing, SignatureAlgorithms.Es256);
|
||||
|
||||
Assert.Same(providerB, resolved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResolveSigner_UsesPreferredProviderHint()
|
||||
{
|
||||
var providerA = new FakeCryptoProvider("providerA")
|
||||
.WithSupport(CryptoCapability.Signing, SignatureAlgorithms.Es256)
|
||||
.WithSigner(SignatureAlgorithms.Es256, "key-a");
|
||||
|
||||
var providerB = new FakeCryptoProvider("providerB")
|
||||
.WithSupport(CryptoCapability.Signing, SignatureAlgorithms.Es256)
|
||||
.WithSigner(SignatureAlgorithms.Es256, "key-b");
|
||||
|
||||
var registry = new CryptoProviderRegistry(new[] { providerA, providerB }, Array.Empty<string>());
|
||||
|
||||
var hintSigner = registry.ResolveSigner(
|
||||
CryptoCapability.Signing,
|
||||
SignatureAlgorithms.Es256,
|
||||
new CryptoKeyReference("key-b"),
|
||||
preferredProvider: "providerB");
|
||||
|
||||
Assert.Equal("key-b", hintSigner.KeyId);
|
||||
|
||||
var fallbackSigner = registry.ResolveSigner(
|
||||
CryptoCapability.Signing,
|
||||
SignatureAlgorithms.Es256,
|
||||
new CryptoKeyReference("key-a"));
|
||||
|
||||
Assert.Equal("key-a", fallbackSigner.KeyId);
|
||||
}
|
||||
|
||||
private sealed class FakeCryptoProvider : ICryptoProvider
|
||||
{
|
||||
private readonly Dictionary<string, FakeSigner> signers = new(StringComparer.Ordinal);
|
||||
private readonly HashSet<(CryptoCapability Capability, string Algorithm)> supported;
|
||||
|
||||
public FakeCryptoProvider(string name)
|
||||
{
|
||||
Name = name;
|
||||
supported = new HashSet<(CryptoCapability, string)>(new CapabilityAlgorithmComparer());
|
||||
}
|
||||
|
||||
public string Name { get; }
|
||||
|
||||
public FakeCryptoProvider WithSupport(CryptoCapability capability, string algorithm)
|
||||
{
|
||||
supported.Add((capability, algorithm));
|
||||
return this;
|
||||
}
|
||||
|
||||
public FakeCryptoProvider WithSigner(string algorithm, string keyId)
|
||||
{
|
||||
WithSupport(CryptoCapability.Signing, algorithm);
|
||||
var signer = new FakeSigner(Name, keyId, algorithm);
|
||||
signers[keyId] = signer;
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
=> supported.Contains((capability, algorithmId));
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId)
|
||||
=> throw new NotSupportedException();
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
if (!signers.TryGetValue(keyReference.KeyId, out var signer))
|
||||
{
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
|
||||
if (!string.Equals(signer.AlgorithmId, algorithmId, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Signer algorithm mismatch.");
|
||||
}
|
||||
|
||||
return signer;
|
||||
}
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
||||
=> signers[signingKey.Reference.KeyId] = new FakeSigner(Name, signingKey.Reference.KeyId, signingKey.AlgorithmId);
|
||||
|
||||
public bool RemoveSigningKey(string keyId) => signers.Remove(keyId);
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() => Array.Empty<CryptoSigningKey>();
|
||||
|
||||
private sealed class CapabilityAlgorithmComparer : IEqualityComparer<(CryptoCapability Capability, string Algorithm)>
|
||||
{
|
||||
public bool Equals((CryptoCapability Capability, string Algorithm) x, (CryptoCapability Capability, string Algorithm) y)
|
||||
=> x.Capability == y.Capability && string.Equals(x.Algorithm, y.Algorithm, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public int GetHashCode((CryptoCapability Capability, string Algorithm) obj)
|
||||
=> HashCode.Combine(obj.Capability, obj.Algorithm.ToUpperInvariant());
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeSigner : ICryptoSigner
|
||||
{
|
||||
public FakeSigner(string provider, string keyId, string algorithmId)
|
||||
{
|
||||
Provider = provider;
|
||||
KeyId = keyId;
|
||||
AlgorithmId = algorithmId;
|
||||
}
|
||||
|
||||
public string Provider { get; }
|
||||
|
||||
public string KeyId { get; }
|
||||
|
||||
public string AlgorithmId { get; }
|
||||
|
||||
public ValueTask<byte[]> SignAsync(ReadOnlyMemory<byte> data, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult(Array.Empty<byte>());
|
||||
|
||||
public ValueTask<bool> VerifyAsync(ReadOnlyMemory<byte> data, ReadOnlyMemory<byte> signature, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult(true);
|
||||
|
||||
public JsonWebKey ExportPublicJsonWebKey() => new()
|
||||
{
|
||||
Kid = KeyId,
|
||||
Alg = AlgorithmId,
|
||||
Kty = JsonWebAlgorithmsKeyTypes.Octet,
|
||||
Use = JsonWebKeyUseNames.Sig
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user