using Microsoft.Extensions.Configuration;
using StellaOps.Doctor.Models;
using StellaOps.Doctor.Plugins;
using System.Runtime.InteropServices;
namespace StellaOps.Doctor.Plugins.Cryptography.Checks;
///
/// Validates SM2/SM3/SM4 (Chinese) cryptography provider availability.
///
public sealed class SmProviderCheck : IDoctorCheck
{
///
public string CheckId => "check.crypto.sm";
///
public string Name => "SM Cryptography";
///
public string Description => "Validates SM2/SM3/SM4 (GB/T) cryptography provider";
///
public DoctorSeverity DefaultSeverity => DoctorSeverity.Info;
///
public IReadOnlyList Tags => ["cryptography", "sm2", "sm3", "sm4", "regional", "china"];
///
public TimeSpan EstimatedDuration => TimeSpan.FromMilliseconds(100);
///
public bool CanRun(DoctorPluginContext context)
{
// Only run if SM crypto is configured or enabled
var smEnabled = context.Configuration.GetValue("Cryptography:Sm:Enabled")
?? context.Configuration.GetValue("Cryptography:EnableSm");
return smEnabled == true;
}
///
public Task RunAsync(DoctorPluginContext context, CancellationToken ct)
{
var result = context.CreateResult(CheckId, "stellaops.doctor.cryptography", DoctorCategory.Cryptography.ToString());
var smProvider = context.Configuration.GetValue("Cryptography:Sm:Provider")
?? "smsoft";
var smEndpoint = context.Configuration.GetValue("Cryptography:Sm:Endpoint")
?? context.Configuration.GetValue("SmRemote:Endpoint");
var issues = new List();
var providerInfo = new Dictionary();
// Check environment gate for SM providers
var smSoftAllowed = Environment.GetEnvironmentVariable("SM_SOFT_ALLOWED");
providerInfo["SM_SOFT_ALLOWED"] = smSoftAllowed ?? "(not set)";
switch (smProvider.ToLowerInvariant())
{
case "smsoft":
CheckSmSoft(issues, providerInfo);
break;
case "smremote":
case "remote":
CheckSmRemote(issues, providerInfo, smEndpoint);
break;
case "bouncycastle":
CheckBouncyCastleSm(issues, providerInfo);
break;
default:
issues.Add($"Unknown SM provider: {smProvider}");
break;
}
providerInfo["ConfiguredProvider"] = smProvider;
if (issues.Count > 0)
{
return Task.FromResult(result
.Warn($"{issues.Count} SM provider issue(s)")
.WithEvidence("SM cryptography", e =>
{
foreach (var kvp in providerInfo)
{
e.Add(kvp.Key, kvp.Value);
}
})
.WithCauses(issues.ToArray())
.WithRemediation(r => r
.AddManualStep(1, "Set environment gate", "Set SM_SOFT_ALLOWED=1 to enable SM software providers")
.AddManualStep(2, "Configure SmRemote", "Configure SmRemote:Endpoint for remote SM crypto service")
.WithRunbookUrl("docs/doctor/articles/crypto/crypto-sm.md"))
.WithVerification("stella doctor --check check.crypto.sm")
.Build());
}
return Task.FromResult(result
.Pass("SM cryptography provider is configured")
.WithEvidence("SM cryptography", e =>
{
foreach (var kvp in providerInfo)
{
e.Add(kvp.Key, kvp.Value);
}
})
.Build());
}
private static void CheckSmSoft(List issues, Dictionary providerInfo)
{
providerInfo["Provider"] = "SmSoft (Software Implementation)";
var smSoftAllowed = Environment.GetEnvironmentVariable("SM_SOFT_ALLOWED");
if (smSoftAllowed != "1" && smSoftAllowed?.ToLowerInvariant() != "true")
{
issues.Add("SM_SOFT_ALLOWED environment variable not set - SmSoft provider may not be available");
}
// Check if BouncyCastle is available for SM algorithms
providerInfo["Implementation"] = "BouncyCastle (managed)";
}
private static void CheckSmRemote(List issues, Dictionary providerInfo, string? endpoint)
{
providerInfo["Provider"] = "SmRemote (Remote Service)";
if (string.IsNullOrWhiteSpace(endpoint))
{
issues.Add("SmRemote endpoint not configured");
providerInfo["Endpoint"] = "(not set)";
}
else
{
providerInfo["Endpoint"] = endpoint;
// Check if endpoint looks valid
if (!Uri.TryCreate(endpoint, UriKind.Absolute, out var uri))
{
issues.Add($"SmRemote endpoint is not a valid URI: {endpoint}");
}
else
{
providerInfo["Host"] = uri.Host;
providerInfo["Port"] = uri.Port.ToString();
}
}
}
private static void CheckBouncyCastleSm(List issues, Dictionary providerInfo)
{
providerInfo["Provider"] = "BouncyCastle";
providerInfo["Implementation"] = "Managed .NET implementation";
// BouncyCastle should be available if the package is referenced
providerInfo["Algorithms"] = "SM2, SM3, SM4";
}
}