Skip to content

Commit 3a7b2d9

Browse files
committed
v2.7
1 parent ee54a24 commit 3a7b2d9

13 files changed

Lines changed: 1025 additions & 6 deletions

File tree

SentryCore/Engines/DriverAuditor.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ private List<DriverEntry> EnumerateDrivers()
150150
// DriverDate is in WMI CIM_DATETIME format: "20230615000000.000000+000"
151151
DateTime? driverDate = null;
152152
var dateStr = obj["DriverDate"]?.ToString();
153-
if (!string.IsNullOrEmpty(dateStr) && dateStr.Length >= 8)
153+
if (dateStr != null && dateStr.Length >= 8)
154154
{
155155
if (DateTime.TryParseExact(dateStr!.Substring(0, Math.Min(8, dateStr.Length)), "yyyyMMdd",
156156
null, System.Globalization.DateTimeStyles.None, out var parsedDate))

SentryCore/Engines/SupplierFileValidator.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ private async Task<ValidationResult> CheckSBOMAsync(string sbomPath)
247247
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(version))
248248
continue;
249249

250-
var vulnsTask = Task.Run(() => InvokeVulnerabilityPluginAsync(name, version));
250+
var vulnsTask = Task.Run(() => InvokeVulnerabilityPluginAsync(name!, version!));
251251
var vulns = vulnsTask.GetAwaiter().GetResult();
252252

253253
if (vulns.Count > 0)
@@ -283,7 +283,7 @@ private async Task<ValidationResult> CheckSBOMAsync(string sbomPath)
283283
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(version))
284284
continue;
285285

286-
var vulnsTask = Task.Run(() => InvokeVulnerabilityPluginAsync(name, version));
286+
var vulnsTask = Task.Run(() => InvokeVulnerabilityPluginAsync(name!, version!));
287287
var vulns = vulnsTask.GetAwaiter().GetResult();
288288

289289
if (vulns.Count > 0)

SentryDatabase/Schema/init.sql

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,17 @@ CREATE TABLE IF NOT EXISTS trusted_suppliers (
109109
is_active INTEGER DEFAULT 1 -- 0=false, 1=true
110110
);
111111

112+
-- ---------------------------------------------------------------------------
113+
-- Audit log (tracks sneakernet threat imports)
114+
-- ---------------------------------------------------------------------------
115+
CREATE TABLE IF NOT EXISTS audit_log (
116+
id INTEGER PRIMARY KEY AUTOINCREMENT,
117+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
118+
source_machine TEXT NOT NULL,
119+
records_imported INTEGER DEFAULT 0,
120+
bundle_hash TEXT NOT NULL
121+
);
122+
112123
-- ---------------------------------------------------------------------------
113124
-- Indexes for query performance
114125
-- ---------------------------------------------------------------------------
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
using System;
2+
using System.IO;
3+
using System.IO.Compression;
4+
using System.Security.Cryptography;
5+
using System.Text;
6+
using Microsoft.Data.Sqlite;
7+
using Microsoft.Extensions.Logging;
8+
9+
namespace SentryShield.Database
10+
{
11+
public class SneakernetExporter
12+
{
13+
private readonly string _dbPath;
14+
private readonly ILogger _logger;
15+
16+
public SneakernetExporter(string dbPath, ILogger logger)
17+
{
18+
_dbPath = dbPath;
19+
_logger = logger;
20+
}
21+
22+
public void ExportThreatBundle(string outputPath)
23+
{
24+
string? syncKey = Environment.GetEnvironmentVariable("SENTRY_SYNC_KEY");
25+
if (string.IsNullOrEmpty(syncKey))
26+
{
27+
throw new InvalidOperationException("SENTRY_SYNC_KEY environment variable is not set. Cannot export threat bundle.");
28+
}
29+
30+
string tmpDir = Path.Combine(Path.GetTempPath(), "SentryExport_" + Guid.NewGuid().ToString("N"));
31+
string tmpZipPath = outputPath + ".tmp";
32+
33+
try
34+
{
35+
Directory.CreateDirectory(tmpDir);
36+
37+
string threatsDbPath = Path.Combine(tmpDir, "threats.db");
38+
long iocCount = 0;
39+
long vulnCount = 0;
40+
long yaraCount = 0;
41+
42+
// 1. Create SQLite snapshot containing only the required tables
43+
using (var conn = new SqliteConnection($"Data Source={_dbPath}"))
44+
{
45+
conn.Open();
46+
using (var cmd = conn.CreateCommand())
47+
{
48+
cmd.CommandText = $"ATTACH DATABASE '{threatsDbPath.Replace("'", "''")}' AS threats;";
49+
cmd.ExecuteNonQuery();
50+
51+
// Export iocs
52+
cmd.CommandText = "CREATE TABLE threats.iocs AS SELECT * FROM iocs;";
53+
cmd.ExecuteNonQuery();
54+
cmd.CommandText = "SELECT COUNT(*) FROM threats.iocs;";
55+
iocCount = (long)(cmd.ExecuteScalar() ?? 0L);
56+
57+
// Export vulnerabilities
58+
cmd.CommandText = "CREATE TABLE threats.vulnerabilities AS SELECT * FROM vulnerabilities;";
59+
cmd.ExecuteNonQuery();
60+
cmd.CommandText = "SELECT COUNT(*) FROM threats.vulnerabilities;";
61+
vulnCount = (long)(cmd.ExecuteScalar() ?? 0L);
62+
63+
// Export yara_rules_metadata if it exists
64+
cmd.CommandText = "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='yara_rules_metadata';";
65+
long yaraExists = (long)(cmd.ExecuteScalar() ?? 0L);
66+
if (yaraExists > 0)
67+
{
68+
cmd.CommandText = "CREATE TABLE threats.yara_rules_metadata AS SELECT * FROM yara_rules_metadata;";
69+
cmd.ExecuteNonQuery();
70+
cmd.CommandText = "SELECT COUNT(*) FROM threats.yara_rules_metadata;";
71+
yaraCount = (long)(cmd.ExecuteScalar() ?? 0L);
72+
}
73+
74+
cmd.CommandText = "DETACH DATABASE threats;";
75+
cmd.ExecuteNonQuery();
76+
}
77+
}
78+
79+
// 2. Compute SHA-256 of the extracted threats.db
80+
string threatsDbHash = ComputeSha256(threatsDbPath);
81+
82+
// 3. Create manifest.json (manually serialized to remain strictly independent of JSON libraries across frameworks)
83+
string manifestPath = Path.Combine(tmpDir, "manifest.json");
84+
string timestamp = DateTime.UtcNow.ToString("o");
85+
string machineName = Environment.MachineName;
86+
87+
string manifestJson = "{\n" +
88+
$" \"ExportTimestamp\": \"{timestamp}\",\n" +
89+
$" \"SourceMachine\": \"{EscapeJson(machineName)}\",\n" +
90+
$" \"IocCount\": {iocCount},\n" +
91+
$" \"VulnerabilityCount\": {vulnCount},\n" +
92+
$" \"YaraCount\": {yaraCount},\n" +
93+
$" \"ThreatsDbHash\": \"{threatsDbHash}\"\n" +
94+
"}";
95+
96+
File.WriteAllText(manifestPath, manifestJson, Encoding.UTF8);
97+
98+
// 4. Create signature.sig (HMAC-SHA256 of manifest.json)
99+
string signaturePath = Path.Combine(tmpDir, "signature.sig");
100+
string signature = ComputeHmacSha256(manifestJson, syncKey);
101+
File.WriteAllText(signaturePath, signature, Encoding.UTF8);
102+
103+
// 5. Compress to ZIP (.sentry format internally)
104+
if (File.Exists(tmpZipPath))
105+
{
106+
File.Delete(tmpZipPath);
107+
}
108+
ZipFile.CreateFromDirectory(tmpDir, tmpZipPath, CompressionLevel.Optimal, false);
109+
110+
// 6. Atomic write via rename
111+
if (File.Exists(outputPath))
112+
{
113+
File.Delete(outputPath);
114+
}
115+
File.Move(tmpZipPath, outputPath);
116+
117+
if (_logger != null)
118+
{
119+
_logger.LogInformation($"Successfully exported threat bundle to {outputPath}");
120+
}
121+
}
122+
catch (Exception ex)
123+
{
124+
if (_logger != null)
125+
{
126+
_logger.LogError(ex, $"Failed to export threat bundle to {outputPath}");
127+
}
128+
129+
try
130+
{
131+
if (File.Exists(tmpZipPath))
132+
{
133+
File.Delete(tmpZipPath);
134+
}
135+
}
136+
catch { }
137+
}
138+
finally
139+
{
140+
try
141+
{
142+
if (Directory.Exists(tmpDir))
143+
{
144+
Directory.Delete(tmpDir, true);
145+
}
146+
}
147+
catch { }
148+
}
149+
}
150+
151+
private string ComputeSha256(string filePath)
152+
{
153+
using (var sha256 = SHA256.Create())
154+
{
155+
using (var stream = File.OpenRead(filePath))
156+
{
157+
byte[] hash = sha256.ComputeHash(stream);
158+
StringBuilder sb = new StringBuilder();
159+
foreach (byte b in hash)
160+
{
161+
sb.Append(b.ToString("x2"));
162+
}
163+
return sb.ToString();
164+
}
165+
}
166+
}
167+
168+
private string ComputeHmacSha256(string data, string key)
169+
{
170+
byte[] keyBytes = Encoding.UTF8.GetBytes(key);
171+
using (var hmac = new HMACSHA256(keyBytes))
172+
{
173+
byte[] hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
174+
StringBuilder sb = new StringBuilder();
175+
foreach (byte b in hash)
176+
{
177+
sb.Append(b.ToString("x2"));
178+
}
179+
return sb.ToString();
180+
}
181+
}
182+
183+
private string EscapeJson(string str)
184+
{
185+
if (string.IsNullOrEmpty(str)) return "";
186+
return str.Replace("\\", "\\\\").Replace("\"", "\\\"");
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)