// ============================================================================= // StellaOps.Integration.AirGap - Air-Gap Test Fixture // Sprint 3500.0004.0003 - T8: Air-Gap Integration Tests // ============================================================================= using System.Security.Cryptography; using System.Text.Json; namespace StellaOps.Integration.AirGap; /// /// Test fixture for air-gap integration tests. /// Manages offline kit, network simulation, and test artifacts. /// public sealed class AirGapTestFixture : IDisposable { private readonly string _offlineKitPath; private readonly string _tempDir; private bool _offlineMode; private Action? _connectionMonitor; private Action? _dnsMonitor; public AirGapTestFixture() { _offlineKitPath = Path.Combine(AppContext.BaseDirectory, "offline-kit"); _tempDir = Path.Combine(Path.GetTempPath(), $"stellaops-airgap-test-{Guid.NewGuid():N}"); Directory.CreateDirectory(_tempDir); } #region Offline Kit public OfflineKitManifest GetOfflineKitManifest() { var manifestPath = Path.Combine(_offlineKitPath, "manifest.json"); if (File.Exists(manifestPath)) { var json = File.ReadAllText(manifestPath); return JsonSerializer.Deserialize(json) ?? GetDefaultManifest(); } return GetDefaultManifest(); } public async Task ComputeComponentHashAsync(string componentName) { var componentPath = Path.Combine(_offlineKitPath, componentName); if (!Directory.Exists(componentPath) && !File.Exists(componentPath)) { return "MISSING"; } using var sha256 = SHA256.Create(); if (File.Exists(componentPath)) { await using var stream = File.OpenRead(componentPath); var hash = await sha256.ComputeHashAsync(stream); return Convert.ToHexString(hash).ToLowerInvariant(); } // Directory - hash all files var files = Directory.GetFiles(componentPath, "*", SearchOption.AllDirectories) .OrderBy(f => f) .ToList(); using var combinedStream = new MemoryStream(); foreach (var file in files) { await using var fileStream = File.OpenRead(file); await fileStream.CopyToAsync(combinedStream); } combinedStream.Position = 0; var dirHash = await sha256.ComputeHashAsync(combinedStream); return Convert.ToHexString(dirHash).ToLowerInvariant(); } public async Task InstallOfflineKitAsync(string targetPath) { await Task.Delay(10); // Simulate installation var manifest = GetOfflineKitManifest(); var installed = new List(); foreach (var (name, _) in manifest.Components) { var sourcePath = Path.Combine(_offlineKitPath, name); var destPath = Path.Combine(targetPath, name); if (Directory.Exists(sourcePath)) { Directory.CreateDirectory(destPath); // Simulate copy } else if (File.Exists(sourcePath)) { Directory.CreateDirectory(Path.GetDirectoryName(destPath)!); // Simulate copy } installed.Add(name); } return new InstallationResult { Success = true, InstalledComponents = installed }; } #endregion #region Test Images public string GetLocalTestImage() { return "localhost/test-image:v1.0.0"; } #endregion #region Scanning public async Task RunOfflineScanAsync(string targetImage) { await Task.Delay(50); // Simulate scan if (!_offlineMode) { _connectionMonitor?.Invoke("nvd.nist.gov:443"); } return new ScanResult { Success = true, Findings = GenerateSampleFindings(), ManifestHash = "sha256:abc123def456", DataSource = _offlineMode ? "offline-kit" : "online", DataSourcePath = _offlineMode ? _offlineKitPath : "https://feeds.stellaops.io", TelemetrySent = !_offlineMode, Configuration = new ScanConfiguration { TelemetryEnabled = !_offlineMode } }; } #endregion #region Score Replay public ProofBundle GetSampleProofBundle() { return new ProofBundle { Id = Guid.NewGuid().ToString(), CreatedAt = DateTime.UtcNow.AddDays(-1), OriginalScore = 7.5, OriginalScoreHash = "sha256:score123", Signature = Convert.ToBase64String(new byte[64]), CertificateChain = new[] { "cert1", "cert2", "root" } }; } public async Task ReplayScoreOfflineAsync(ProofBundle bundle) { await Task.Delay(20); // Simulate replay return new ReplayResult { Success = true, Score = bundle.OriginalScore, ScoreHash = bundle.OriginalScoreHash, ReplayedAt = DateTime.UtcNow, AuditTrail = new[] { new AuditEntry { Type = "replay_started", Timestamp = DateTime.UtcNow.AddMilliseconds(-20) }, new AuditEntry { Type = "data_loaded", Timestamp = DateTime.UtcNow.AddMilliseconds(-15) }, new AuditEntry { Type = "score_computed", Timestamp = DateTime.UtcNow.AddMilliseconds(-5) }, new AuditEntry { Type = "replay_completed", Timestamp = DateTime.UtcNow } } }; } #endregion #region Proof Verification public async Task VerifyProofOfflineAsync(ProofBundle bundle) { await Task.Delay(10); // Simulate verification var isTampered = bundle.Signature.Contains("TAMPERED"); var isExpired = bundle.CertificateChain.Any(c => c.Contains("EXPIRED")); return new VerificationResult { Valid = !isTampered && !isExpired, VerifiedAt = DateTime.UtcNow, TrustSource = "offline-trust-store", CertificateChain = bundle.CertificateChain, FailureReason = isTampered ? "Invalid signature" : (isExpired ? "Certificate expired" : null), Warnings = isExpired ? new[] { "certificate chain contains expired certificate" } : Array.Empty() }; } public ProofBundle TamperWithProof(ProofBundle original) { return original with { Signature = "TAMPERED_" + original.Signature }; } public ProofBundle GetProofBundleWithExpiredCert() { return new ProofBundle { Id = Guid.NewGuid().ToString(), CreatedAt = DateTime.UtcNow.AddYears(-2), OriginalScore = 5.0, OriginalScoreHash = "sha256:expired123", Signature = Convert.ToBase64String(new byte[64]), CertificateChain = new[] { "cert1", "EXPIRED_cert2", "root" } }; } #endregion #region Network Control public void SetOfflineMode(bool offline) { _offlineMode = offline; } public async Task DisableNetworkAsync() { _offlineMode = true; await Task.CompletedTask; } public async Task EnableNetworkAsync() { _offlineMode = false; await Task.CompletedTask; } public void SetConnectionMonitor(Action monitor) { _connectionMonitor = monitor; } public void SetDnsMonitor(Action monitor) { _dnsMonitor = monitor; } public async Task AttemptOnlineUpdateAsync() { if (_offlineMode) { return new OnlineUpdateResult { Success = false, FailureReason = "System is in offline mode", SuggestedAction = "Use offline-kit update mechanism" }; } await Task.Delay(100); return new OnlineUpdateResult { Success = true }; } #endregion #region Helpers public string GetTempDirectory() { var path = Path.Combine(_tempDir, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(path); return path; } private static List GenerateSampleFindings() { return new List { new() { CveId = "CVE-2024-00001", Severity = "HIGH", Score = 8.0 }, new() { CveId = "CVE-2024-00002", Severity = "MEDIUM", Score = 5.5 }, new() { CveId = "CVE-2024-00003", Severity = "LOW", Score = 3.2 } }; } private static OfflineKitManifest GetDefaultManifest() { return new OfflineKitManifest { Version = "1.0.0", CreatedAt = DateTime.UtcNow.AddDays(-7), Components = new Dictionary { ["vulnerability-database"] = new() { Hash = "sha256:vulndb123", Size = 1024 * 1024 }, ["advisory-feeds"] = new() { Hash = "sha256:feeds456", Size = 512 * 1024 }, ["trust-bundles"] = new() { Hash = "sha256:trust789", Size = 64 * 1024 }, ["signing-keys"] = new() { Hash = "sha256:keys012", Size = 16 * 1024 } } }; } #endregion public void Dispose() { if (Directory.Exists(_tempDir)) { try { Directory.Delete(_tempDir, true); } catch { // Best effort cleanup } } } } #region Record Types public record OfflineKitManifest { public string Version { get; init; } = ""; public DateTime CreatedAt { get; init; } public Dictionary Components { get; init; } = new(); } public record OfflineComponent { public string Hash { get; init; } = ""; public long Size { get; init; } } public record InstallationResult { public bool Success { get; init; } public List InstalledComponents { get; init; } = new(); } public record ScanResult { public bool Success { get; init; } public List Findings { get; init; } = new(); public string ManifestHash { get; init; } = ""; public string DataSource { get; init; } = ""; public string DataSourcePath { get; init; } = ""; public bool TelemetrySent { get; init; } public ScanConfiguration Configuration { get; init; } = new(); } public record ScanConfiguration { public bool TelemetryEnabled { get; init; } } public record Finding { public string CveId { get; init; } = ""; public string Severity { get; init; } = ""; public double Score { get; init; } } public record ProofBundle { public string Id { get; init; } = ""; public DateTime CreatedAt { get; init; } public double OriginalScore { get; init; } public string OriginalScoreHash { get; init; } = ""; public string Signature { get; init; } = ""; public string[] CertificateChain { get; init; } = Array.Empty(); } public record ReplayResult { public bool Success { get; init; } public double Score { get; init; } public string ScoreHash { get; init; } = ""; public DateTime ReplayedAt { get; init; } public AuditEntry[] AuditTrail { get; init; } = Array.Empty(); } public record AuditEntry { public string Type { get; init; } = ""; public DateTime Timestamp { get; init; } } public record VerificationResult { public bool Valid { get; init; } public DateTime VerifiedAt { get; init; } public string TrustSource { get; init; } = ""; public string[] CertificateChain { get; init; } = Array.Empty(); public string? FailureReason { get; init; } public string[] Warnings { get; init; } = Array.Empty(); } public record OnlineUpdateResult { public bool Success { get; init; } public string? FailureReason { get; init; } public string? SuggestedAction { get; init; } } #endregion