up
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.Plugin.SmRemote;
|
||||
|
||||
/// <summary>
|
||||
/// SM2 provider delegating to a remote SM microservice (HTTP).
|
||||
/// Designed to be swapped with hardware-backed service when available.
|
||||
/// </summary>
|
||||
public sealed class SmRemoteHttpProvider : ICryptoProvider, ICryptoProviderDiagnostics
|
||||
{
|
||||
private const string ProviderNameConst = "cn.sm.remote.http";
|
||||
private const string GateEnv = "SM_REMOTE_ALLOWED";
|
||||
|
||||
private readonly SmRemoteHttpClient client;
|
||||
private readonly ILogger<SmRemoteHttpProvider>? logger;
|
||||
private readonly ConcurrentDictionary<string, SmKeyEntry> entries = new(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly SmRemoteStatus status;
|
||||
|
||||
public SmRemoteHttpProvider(
|
||||
SmRemoteHttpClient client,
|
||||
IOptions<SmRemoteProviderOptions>? optionsAccessor = null,
|
||||
ILogger<SmRemoteHttpProvider>? logger = null)
|
||||
{
|
||||
this.client = client ?? throw new ArgumentNullException(nameof(client));
|
||||
this.logger = logger;
|
||||
|
||||
var options = optionsAccessor?.Value ?? new SmRemoteProviderOptions();
|
||||
status = options.SkipProbe
|
||||
? new SmRemoteStatus { IsAvailable = true, ProviderName = ProviderNameConst, SupportedAlgorithms = new[] { SignatureAlgorithms.Sm2 } }
|
||||
: ProbeStatus();
|
||||
|
||||
foreach (var key in options.Keys)
|
||||
{
|
||||
entries[key.KeyId] = new SmKeyEntry(key.KeyId, key.RemoteKeyId ?? key.KeyId);
|
||||
}
|
||||
}
|
||||
|
||||
public string Name => ProviderNameConst;
|
||||
|
||||
public bool Supports(CryptoCapability capability, string algorithmId)
|
||||
{
|
||||
if (!GateEnabled() || !status.IsAvailable)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return capability is CryptoCapability.Signing or CryptoCapability.Verification &&
|
||||
string.Equals(algorithmId, SignatureAlgorithms.Sm2, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public IPasswordHasher GetPasswordHasher(string algorithmId) =>
|
||||
throw new NotSupportedException("SM remote provider does not expose password hashing.");
|
||||
|
||||
public ICryptoHasher GetHasher(string algorithmId) =>
|
||||
throw new NotSupportedException("SM remote provider does not expose hashing.");
|
||||
|
||||
public ICryptoSigner GetSigner(string algorithmId, CryptoKeyReference keyReference)
|
||||
{
|
||||
if (!Supports(CryptoCapability.Signing, algorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Algorithm '{algorithmId}' not supported by '{Name}'.");
|
||||
}
|
||||
|
||||
var entry = entries.GetOrAdd(keyReference.KeyId, id => new SmKeyEntry(id, id));
|
||||
return new SmRemoteSigner(client, entry.RemoteKeyId, algorithmId);
|
||||
}
|
||||
|
||||
public void UpsertSigningKey(CryptoSigningKey signingKey)
|
||||
{
|
||||
if (!Supports(CryptoCapability.Signing, signingKey.AlgorithmId))
|
||||
{
|
||||
throw new InvalidOperationException($"Algorithm '{signingKey.AlgorithmId}' not supported by '{Name}'.");
|
||||
}
|
||||
|
||||
entries[signingKey.Reference.KeyId] = new SmKeyEntry(signingKey.Reference.KeyId, signingKey.Reference.KeyId);
|
||||
}
|
||||
|
||||
public bool RemoveSigningKey(string keyId) => entries.TryRemove(keyId, out _);
|
||||
|
||||
public IReadOnlyCollection<CryptoSigningKey> GetSigningKeys() => Array.Empty<CryptoSigningKey>();
|
||||
|
||||
public IEnumerable<CryptoProviderKeyDescriptor> DescribeKeys() =>
|
||||
entries.Values.Select(e => new CryptoProviderKeyDescriptor(Name, e.KeyId, SignatureAlgorithms.Sm2,
|
||||
new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["provider"] = Name,
|
||||
["remoteKeyId"] = e.RemoteKeyId,
|
||||
["simulation"] = "remote-soft"
|
||||
}));
|
||||
|
||||
private SmRemoteStatus ProbeStatus()
|
||||
{
|
||||
try
|
||||
{
|
||||
var probe = client.GetStatusAsync().GetAwaiter().GetResult();
|
||||
return probe;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger?.LogWarning(ex, "SM remote service probe failed");
|
||||
return new SmRemoteStatus { IsAvailable = false, Error = ex.Message };
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GateEnabled()
|
||||
{
|
||||
var value = Environment.GetEnvironmentVariable(GateEnv);
|
||||
return string.IsNullOrEmpty(value) ||
|
||||
string.Equals(value, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(value, "true", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private sealed record SmKeyEntry(string KeyId, string RemoteKeyId);
|
||||
}
|
||||
Reference in New Issue
Block a user