using Microsoft.Extensions.Configuration; using StellaOps.Doctor.Models; using StellaOps.Doctor.Plugins; using StellaOps.Doctor.Plugins.Builders; using StellaOps.Doctor.Plugins.Verification.Configuration; namespace StellaOps.Doctor.Plugins.Verification.Checks; /// /// Verifies signature and attestations for test artifact. /// public sealed class SignatureVerificationCheck : VerificationCheckBase { private const string RunbookUrlValue = "docs/doctor/articles/verification/verification-signature.md"; /// public override string CheckId => "check.verification.signature"; /// public override string Name => "Signature Verification"; /// public override string Description => "Verifies signature and attestations for test artifact (DSSE in Rekor or offline bundle)"; /// public override IReadOnlyList Tags => ["verification", "signature", "dsse", "attestation", "security"]; /// public override TimeSpan EstimatedDuration => TimeSpan.FromSeconds(10); /// protected override string RunbookUrl => RunbookUrlValue; /// public override bool CanRun(DoctorPluginContext context) { if (!base.CanRun(context)) return false; var options = VerificationPlugin.GetOptions(context); return HasTestArtifactConfigured(options); } /// protected override async Task ExecuteCheckAsync( DoctorPluginContext context, VerificationPluginOptions options, CheckResultBuilder result, CancellationToken ct) { if (!HasTestArtifactConfigured(options)) { return GetNoTestArtifactConfiguredResult(result, CheckId); } // Check for offline bundle if (!string.IsNullOrEmpty(options.TestArtifact.OfflineBundlePath)) { return await VerifyFromOfflineBundle(options, result, ct); } // Online verification return await VerifyFromOnline(context, options, result, ct); } private static Task VerifyFromOfflineBundle( VerificationPluginOptions options, CheckResultBuilder result, CancellationToken ct) { var bundlePath = options.TestArtifact.OfflineBundlePath!; if (!File.Exists(bundlePath)) { return Task.FromResult(result .Fail($"Offline bundle not found: {bundlePath}") .WithEvidence("Verification", e => e .Add("Mode", "Offline") .Add("BundlePath", bundlePath) .Add("FileExists", "false")) .WithRemediation(r => r .AddShellStep(1, "Export bundle", "stella verification bundle export --output " + bundlePath) .WithRunbookUrl(RunbookUrlValue)) .WithVerification($"stella doctor --check check.verification.signature") .Build()); } // In a real implementation, we would parse the bundle and verify signatures // For doctor check, we verify the bundle structure contains signature data try { var content = File.ReadAllText(bundlePath); // Check for signature indicators in the bundle var hasSignatures = content.Contains("\"signatures\"", StringComparison.OrdinalIgnoreCase) || content.Contains("\"payloadType\"", StringComparison.OrdinalIgnoreCase) || content.Contains("\"dsse\"", StringComparison.OrdinalIgnoreCase); if (!hasSignatures) { return Task.FromResult(result .Warn("Offline bundle may not contain signature data") .WithEvidence("Verification", e => e .Add("Mode", "Offline") .Add("BundlePath", bundlePath) .Add("SignatureDataFound", "false") .Add("Note", "Bundle should contain DSSE signatures for verification")) .WithRemediation(r => r .AddShellStep(1, "Re-export with signatures", "stella verification bundle export --include-signatures --output " + bundlePath) .WithRunbookUrl(RunbookUrlValue)) .WithVerification($"stella doctor --check check.verification.signature") .Build()); } return Task.FromResult(result .Pass("Offline bundle contains signature data") .WithEvidence("Verification", e => e .Add("Mode", "Offline") .Add("BundlePath", bundlePath) .Add("SignatureDataFound", "true") .Add("Note", "Full signature verification requires runtime attestor service")) .Build()); } catch (Exception ex) { return Task.FromResult(result .Fail($"Cannot read offline bundle: {ex.Message}") .WithEvidence("Verification", e => e .Add("Mode", "Offline") .Add("BundlePath", bundlePath) .Add("Error", ex.Message)) .Build()); } } private static async Task VerifyFromOnline( DoctorPluginContext context, VerificationPluginOptions options, CheckResultBuilder result, CancellationToken ct) { var reference = options.TestArtifact.Reference!; var rekorUrl = context.Configuration["Sigstore:RekorUrl"] ?? "https://rekor.sigstore.dev"; // Note: Full signature verification requires the Attestor service // For doctor check, we verify that the infrastructure is in place // Check if Sigstore is enabled var sigstoreEnabled = context.Configuration.GetValue("Sigstore:Enabled"); if (!sigstoreEnabled) { return result .Info("Signature verification skipped - Sigstore not enabled") .WithEvidence("Verification", e => e .Add("Mode", "Online") .Add("SigstoreEnabled", "false") .Add("Reference", reference) .Add("Note", "Enable Sigstore to verify artifact signatures")) .WithRemediation(r => r .AddManualStep(1, "Enable Sigstore", "Set Sigstore:Enabled to true") .AddManualStep(2, "Configure signing", "Set up signing keys or keyless mode") .WithRunbookUrl(RunbookUrlValue)) .Build(); } // Check if Rekor is reachable (signature verification requires Rekor) using var httpClient = CreateHttpClient(options); try { var rekorHealthUrl = $"{rekorUrl.TrimEnd('/')}/api/v1/log"; var response = await httpClient.GetAsync(rekorHealthUrl, ct); if (!response.IsSuccessStatusCode) { return result .Fail($"Rekor transparency log unavailable ({(int)response.StatusCode})") .WithEvidence("Verification", e => e .Add("Mode", "Online") .Add("RekorUrl", rekorUrl) .Add("RekorStatus", ((int)response.StatusCode).ToString()) .Add("Reference", reference)) .WithCauses( "Rekor service is down", "Network connectivity issue") .WithRemediation(r => r .AddShellStep(1, "Test Rekor", $"curl -I {rekorHealthUrl}") .AddManualStep(2, "Or use offline mode", "Configure offline verification bundle") .WithRunbookUrl(RunbookUrlValue)) .WithVerification($"stella doctor --check check.verification.signature") .Build(); } return result .Pass("Signature verification infrastructure available") .WithEvidence("Verification", e => e .Add("Mode", "Online") .Add("SigstoreEnabled", "true") .Add("RekorUrl", rekorUrl) .Add("RekorReachable", "true") .Add("Reference", reference) .Add("Note", "Full signature verification requires runtime attestor service")) .Build(); } catch (HttpRequestException ex) { return result .Fail($"Cannot reach Rekor: {ex.Message}") .WithEvidence("Verification", e => e .Add("Mode", "Online") .Add("RekorUrl", rekorUrl) .Add("Error", ex.Message) .Add("Reference", reference)) .WithCauses("Network connectivity issue") .WithRemediation(r => r .AddManualStep(1, "Check network", "Verify connectivity to Rekor") .AddManualStep(2, "Use offline mode", "Configure offline verification bundle") .WithRunbookUrl(RunbookUrlValue)) .WithVerification($"stella doctor --check check.verification.signature") .Build(); } } }