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"; } }