save progress
This commit is contained in:
@@ -5,6 +5,7 @@ using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Cli.Commands.Admin;
|
||||
using StellaOps.Cli.Commands.Budget;
|
||||
using StellaOps.Cli.Commands.DeltaSig;
|
||||
using StellaOps.Cli.Commands.Proof;
|
||||
using StellaOps.Cli.Configuration;
|
||||
using StellaOps.Cli.Extensions;
|
||||
@@ -82,6 +83,7 @@ internal static class CommandFactory
|
||||
root.Add(BuildRiskCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildReachabilityCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildGraphCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(DeltaSigCommandGroup.BuildDeltaSigCommand(services, verboseOption, cancellationToken)); // Sprint: SPRINT_20260102_001_BE - Delta signatures
|
||||
root.Add(Binary.BinaryCommandGroup.BuildBinaryCommand(services, verboseOption, cancellationToken)); // Sprint: SPRINT_3850_0001_0001
|
||||
root.Add(BuildApiCommand(services, verboseOption, cancellationToken));
|
||||
root.Add(BuildSdkCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
456
src/Cli/StellaOps.Cli/Commands/DeltaSig/DeltaSigCommandGroup.cs
Normal file
456
src/Cli/StellaOps.Cli/Commands/DeltaSig/DeltaSigCommandGroup.cs
Normal file
@@ -0,0 +1,456 @@
|
||||
// Copyright (c) StellaOps. All rights reserved.
|
||||
// Licensed under AGPL-3.0-or-later. See LICENSE in the project root.
|
||||
// Sprint: SPRINT_20260102_001_BE - Tasks: DS-025 through DS-032
|
||||
|
||||
using System.CommandLine;
|
||||
using System.CommandLine.Parsing;
|
||||
|
||||
namespace StellaOps.Cli.Commands.DeltaSig;
|
||||
|
||||
/// <summary>
|
||||
/// CLI command group for binary delta signature operations.
|
||||
/// Delta signatures enable cryptographic detection of backported security patches
|
||||
/// in binaries where version strings don't reflect the actual fix status.
|
||||
/// </summary>
|
||||
internal static class DeltaSigCommandGroup
|
||||
{
|
||||
internal static Command BuildDeltaSigCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var deltasig = new Command("deltasig", "Binary delta signature operations for backport detection.");
|
||||
|
||||
deltasig.Add(BuildExtractCommand(services, verboseOption, cancellationToken));
|
||||
deltasig.Add(BuildAuthorCommand(services, verboseOption, cancellationToken));
|
||||
deltasig.Add(BuildSignCommand(services, verboseOption, cancellationToken));
|
||||
deltasig.Add(BuildVerifyCommand(services, verboseOption, cancellationToken));
|
||||
deltasig.Add(BuildMatchCommand(services, verboseOption, cancellationToken));
|
||||
deltasig.Add(BuildPackCommand(services, verboseOption, cancellationToken));
|
||||
deltasig.Add(BuildInspectCommand(services, verboseOption, cancellationToken));
|
||||
|
||||
return deltasig;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig extract: Extract normalized signatures from a binary.
|
||||
/// </summary>
|
||||
private static Command BuildExtractCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var binaryArg = new Argument<string>("binary")
|
||||
{
|
||||
Description = "Path to ELF/PE/Mach-O binary file."
|
||||
};
|
||||
|
||||
var symbolsOption = new Option<string[]>("--symbols", ["-s"])
|
||||
{
|
||||
Description = "Symbol names to extract (comma-separated or multiple --symbols).",
|
||||
AllowMultipleArgumentsPerToken = true,
|
||||
Arity = ArgumentArity.OneOrMore
|
||||
};
|
||||
|
||||
var archOption = new Option<string?>("--arch", ["-a"])
|
||||
{
|
||||
Description = "Architecture hint (x86_64, aarch64). Auto-detected if not specified."
|
||||
};
|
||||
|
||||
var outputOption = new Option<string?>("--out", ["-o"])
|
||||
{
|
||||
Description = "Output JSON path. Writes to stdout if not specified."
|
||||
};
|
||||
|
||||
var jsonOption = new Option<bool>("--json", ["-j"])
|
||||
{
|
||||
Description = "Machine-readable JSON output."
|
||||
};
|
||||
|
||||
var command = new Command("extract", "Extract normalized delta signatures from a binary.")
|
||||
{
|
||||
binaryArg,
|
||||
symbolsOption,
|
||||
archOption,
|
||||
outputOption,
|
||||
jsonOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var binary = parseResult.GetValue(binaryArg)!;
|
||||
var symbols = parseResult.GetValue(symbolsOption) ?? [];
|
||||
var arch = parseResult.GetValue(archOption);
|
||||
var output = parseResult.GetValue(outputOption);
|
||||
var json = parseResult.GetValue(jsonOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandleExtractAsync(
|
||||
services,
|
||||
binary,
|
||||
symbols,
|
||||
arch,
|
||||
output,
|
||||
json,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig author: Author signatures by comparing vulnerable and patched binaries.
|
||||
/// </summary>
|
||||
private static Command BuildAuthorCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var vulnOption = new Option<string>("--vuln", ["-v"])
|
||||
{
|
||||
Description = "Path to vulnerable binary.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var patchedOption = new Option<string>("--patched", ["-p"])
|
||||
{
|
||||
Description = "Path to patched binary.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var cveOption = new Option<string>("--cve", ["-c"])
|
||||
{
|
||||
Description = "CVE identifier (e.g., CVE-2024-12345).",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var packageOption = new Option<string>("--package")
|
||||
{
|
||||
Description = "Package name (e.g., openssl, libcurl).",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var sonameOption = new Option<string?>("--soname")
|
||||
{
|
||||
Description = "Shared object name (e.g., libssl.so.3)."
|
||||
};
|
||||
|
||||
var archOption = new Option<string>("--arch", ["-a"])
|
||||
{
|
||||
Description = "Architecture (x86_64, aarch64).",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var abiOption = new Option<string>("--abi")
|
||||
{
|
||||
Description = "ABI (gnu, musl, android)."
|
||||
}.SetDefaultValue("gnu");
|
||||
|
||||
var outputOption = new Option<string>("--out", ["-o"])
|
||||
{
|
||||
Description = "Output directory for signature payloads.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var command = new Command("author", "Author delta signatures by comparing vulnerable and patched binaries.")
|
||||
{
|
||||
vulnOption,
|
||||
patchedOption,
|
||||
cveOption,
|
||||
packageOption,
|
||||
sonameOption,
|
||||
archOption,
|
||||
abiOption,
|
||||
outputOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var vuln = parseResult.GetValue(vulnOption)!;
|
||||
var patched = parseResult.GetValue(patchedOption)!;
|
||||
var cve = parseResult.GetValue(cveOption)!;
|
||||
var package = parseResult.GetValue(packageOption)!;
|
||||
var soname = parseResult.GetValue(sonameOption);
|
||||
var arch = parseResult.GetValue(archOption)!;
|
||||
var abi = parseResult.GetValue(abiOption)!;
|
||||
var output = parseResult.GetValue(outputOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandleAuthorAsync(
|
||||
services,
|
||||
vuln,
|
||||
patched,
|
||||
cve,
|
||||
package,
|
||||
soname,
|
||||
arch,
|
||||
abi,
|
||||
output,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig sign: Sign a signature payload with DSSE envelope.
|
||||
/// </summary>
|
||||
private static Command BuildSignCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputOption = new Option<string>("--in", ["-i"])
|
||||
{
|
||||
Description = "Input payload JSON file.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var keyOption = new Option<string>("--key", ["-k"])
|
||||
{
|
||||
Description = "Private key PEM file for signing.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var outputOption = new Option<string>("--out", ["-o"])
|
||||
{
|
||||
Description = "Output DSSE envelope path.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var algOption = new Option<string>("--alg")
|
||||
{
|
||||
Description = "Signature algorithm (ecdsa-p256-sha256, rsa-pss-sha256, ed25519)."
|
||||
}.SetDefaultValue("ecdsa-p256-sha256");
|
||||
|
||||
var command = new Command("sign", "Sign a delta signature payload with DSSE envelope.")
|
||||
{
|
||||
inputOption,
|
||||
keyOption,
|
||||
outputOption,
|
||||
algOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var input = parseResult.GetValue(inputOption)!;
|
||||
var key = parseResult.GetValue(keyOption)!;
|
||||
var output = parseResult.GetValue(outputOption)!;
|
||||
var alg = parseResult.GetValue(algOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandleSignAsync(
|
||||
services,
|
||||
input,
|
||||
key,
|
||||
output,
|
||||
alg,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig verify: Verify a DSSE-signed signature envelope.
|
||||
/// </summary>
|
||||
private static Command BuildVerifyCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputOption = new Option<string>("--in", ["-i"])
|
||||
{
|
||||
Description = "Input DSSE envelope file.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var pubOption = new Option<string>("--pub", ["-p"])
|
||||
{
|
||||
Description = "Public key PEM file for verification.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var command = new Command("verify", "Verify a DSSE-signed delta signature envelope.")
|
||||
{
|
||||
inputOption,
|
||||
pubOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var input = parseResult.GetValue(inputOption)!;
|
||||
var pub = parseResult.GetValue(pubOption)!;
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandleVerifyAsync(
|
||||
services,
|
||||
input,
|
||||
pub,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig match: Match a binary against signature pack.
|
||||
/// </summary>
|
||||
private static Command BuildMatchCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var binaryArg = new Argument<string>("binary")
|
||||
{
|
||||
Description = "Path to binary file to check."
|
||||
};
|
||||
|
||||
var sigpackOption = new Option<string>("--sigpack", ["-s"])
|
||||
{
|
||||
Description = "Signature pack (ZIP file) or directory containing signatures.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var cveOption = new Option<string?>("--cve", ["-c"])
|
||||
{
|
||||
Description = "Filter to specific CVE."
|
||||
};
|
||||
|
||||
var jsonOption = new Option<bool>("--json", ["-j"])
|
||||
{
|
||||
Description = "Machine-readable JSON output."
|
||||
};
|
||||
|
||||
var command = new Command("match", "Match a binary against known vulnerable/patched signatures.")
|
||||
{
|
||||
binaryArg,
|
||||
sigpackOption,
|
||||
cveOption,
|
||||
jsonOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var binary = parseResult.GetValue(binaryArg)!;
|
||||
var sigpack = parseResult.GetValue(sigpackOption)!;
|
||||
var cve = parseResult.GetValue(cveOption);
|
||||
var json = parseResult.GetValue(jsonOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandleMatchAsync(
|
||||
services,
|
||||
binary,
|
||||
sigpack,
|
||||
cve,
|
||||
json,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig pack: Create a signature pack from individual signatures.
|
||||
/// </summary>
|
||||
private static Command BuildPackCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputOption = new Option<string>("--in-dir", ["-i"])
|
||||
{
|
||||
Description = "Directory containing *.dsse.json signature files.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var outputOption = new Option<string>("--out", ["-o"])
|
||||
{
|
||||
Description = "Output ZIP path for the signature pack.",
|
||||
Arity = ArgumentArity.ExactlyOne
|
||||
};
|
||||
|
||||
var packIdOption = new Option<string?>("--pack-id")
|
||||
{
|
||||
Description = "Pack identifier (e.g., stellaops-deltasig-2026-01). Auto-generated if not specified."
|
||||
};
|
||||
|
||||
var command = new Command("pack", "Create a signature pack from individual signature files.")
|
||||
{
|
||||
inputOption,
|
||||
outputOption,
|
||||
packIdOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var input = parseResult.GetValue(inputOption)!;
|
||||
var output = parseResult.GetValue(outputOption)!;
|
||||
var packId = parseResult.GetValue(packIdOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandlePackAsync(
|
||||
services,
|
||||
input,
|
||||
output,
|
||||
packId,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// stella deltasig inspect: Inspect a signature payload or envelope.
|
||||
/// </summary>
|
||||
private static Command BuildInspectCommand(
|
||||
IServiceProvider services,
|
||||
Option<bool> verboseOption,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var inputArg = new Argument<string>("file")
|
||||
{
|
||||
Description = "Signature payload (.json) or envelope (.dsse.json) to inspect."
|
||||
};
|
||||
|
||||
var jsonOption = new Option<bool>("--json", ["-j"])
|
||||
{
|
||||
Description = "Machine-readable JSON output."
|
||||
};
|
||||
|
||||
var command = new Command("inspect", "Inspect a delta signature payload or DSSE envelope.")
|
||||
{
|
||||
inputArg,
|
||||
jsonOption,
|
||||
verboseOption
|
||||
};
|
||||
|
||||
command.SetAction(parseResult =>
|
||||
{
|
||||
var input = parseResult.GetValue(inputArg)!;
|
||||
var json = parseResult.GetValue(jsonOption);
|
||||
var verbose = parseResult.GetValue(verboseOption);
|
||||
|
||||
return DeltaSigCommandHandlers.HandleInspectAsync(
|
||||
services,
|
||||
input,
|
||||
json,
|
||||
verbose,
|
||||
cancellationToken);
|
||||
});
|
||||
|
||||
return command;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,800 @@
|
||||
// Copyright (c) StellaOps. All rights reserved.
|
||||
// Licensed under AGPL-3.0-or-later. See LICENSE in the project root.
|
||||
// Sprint: SPRINT_20260102_001_BE - Tasks: DS-025 through DS-032
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.IO.Compression;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Spectre.Console;
|
||||
using StellaOps.BinaryIndex.DeltaSig;
|
||||
using StellaOps.BinaryIndex.Disassembly;
|
||||
using StellaOps.BinaryIndex.Normalization;
|
||||
|
||||
namespace StellaOps.Cli.Commands.DeltaSig;
|
||||
|
||||
/// <summary>
|
||||
/// Command handlers for delta signature CLI operations.
|
||||
/// </summary>
|
||||
internal static class DeltaSigCommandHandlers
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Handle extract command - extract normalized signatures from a binary.
|
||||
/// </summary>
|
||||
public static async Task<int> HandleExtractAsync(
|
||||
IServiceProvider services,
|
||||
string binaryPath,
|
||||
string[] symbols,
|
||||
string? arch,
|
||||
string? outputPath,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("deltasig-extract");
|
||||
|
||||
if (!File.Exists(binaryPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Binary file not found: {binaryPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var disassemblyService = services.GetRequiredService<IDisassemblyService>();
|
||||
var normalizationService = services.GetRequiredService<NormalizationService>();
|
||||
var sigGenerator = services.GetRequiredService<IDeltaSignatureGenerator>();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Loading binary: {binaryPath}[/]");
|
||||
}
|
||||
|
||||
// Load binary
|
||||
var binaryBytes = await File.ReadAllBytesAsync(binaryPath, ct);
|
||||
var (binaryInfo, plugin) = disassemblyService.LoadBinary(binaryBytes.AsSpan());
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Architecture: {binaryInfo.Architecture}[/]");
|
||||
AnsiConsole.MarkupLine($"[dim]Format: {binaryInfo.Format}[/]");
|
||||
}
|
||||
|
||||
// Get symbols from plugin
|
||||
var allSymbols = plugin.GetSymbols(binaryInfo).ToList();
|
||||
|
||||
// Filter to requested symbols or all if none specified
|
||||
var targetSymbols = symbols.Length > 0
|
||||
? allSymbols.Where(s => symbols.Contains(s.Name)).ToList()
|
||||
: allSymbols.Where(s => s.Type == SymbolType.Function).ToList();
|
||||
|
||||
if (targetSymbols.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Warning:[/] No symbols found in binary.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Extracting {targetSymbols.Count} symbols...[/]");
|
||||
}
|
||||
|
||||
// Get normalization pipeline
|
||||
var pipeline = normalizationService.GetPipeline(binaryInfo.Architecture);
|
||||
|
||||
var extractedSignatures = new List<SymbolSignature>();
|
||||
|
||||
foreach (var symbol in targetSymbols)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
// Disassemble
|
||||
var instructions = plugin.DisassembleSymbol(binaryInfo, symbol).ToList();
|
||||
if (instructions.Count == 0)
|
||||
{
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]Warning:[/] No instructions for: {symbol.Name}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Normalize
|
||||
var normalized = pipeline.Normalize(instructions, binaryInfo.Architecture);
|
||||
|
||||
// Generate signature
|
||||
var sig = sigGenerator.GenerateSymbolSignature(
|
||||
normalized,
|
||||
symbol.Name,
|
||||
symbol.Section ?? ".text");
|
||||
|
||||
extractedSignatures.Add(sig);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]OK[/] {symbol.Name}: {sig.HashHex[..16]}... ({sig.SizeBytes} bytes)");
|
||||
}
|
||||
}
|
||||
|
||||
// Build output
|
||||
var result = new
|
||||
{
|
||||
binaryPath = Path.GetFileName(binaryPath),
|
||||
architecture = binaryInfo.Architecture.ToString(),
|
||||
normalization = new
|
||||
{
|
||||
recipeId = pipeline.RecipeId,
|
||||
recipeVersion = pipeline.RecipeVersion
|
||||
},
|
||||
symbols = extractedSignatures
|
||||
};
|
||||
|
||||
var jsonOutput = JsonSerializer.Serialize(result, JsonOptions);
|
||||
|
||||
if (outputPath != null)
|
||||
{
|
||||
await File.WriteAllTextAsync(outputPath, jsonOutput, ct);
|
||||
AnsiConsole.MarkupLine($"[green]Signatures written to:[/] {outputPath}");
|
||||
}
|
||||
else if (json)
|
||||
{
|
||||
Console.WriteLine(jsonOutput);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Human-readable output
|
||||
var table = new Table();
|
||||
table.AddColumn("Symbol");
|
||||
table.AddColumn("Hash");
|
||||
table.AddColumn("Size");
|
||||
table.AddColumn("BB Count");
|
||||
|
||||
foreach (var sig in extractedSignatures)
|
||||
{
|
||||
table.AddRow(
|
||||
sig.Name,
|
||||
sig.HashHex[..16] + "...",
|
||||
sig.SizeBytes.ToString(),
|
||||
sig.CfgBbCount?.ToString() ?? "-");
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error extracting signatures from {BinaryPath}", binaryPath);
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle author command - create signatures by comparing vulnerable and patched binaries.
|
||||
/// </summary>
|
||||
public static async Task<int> HandleAuthorAsync(
|
||||
IServiceProvider services,
|
||||
string vulnPath,
|
||||
string patchedPath,
|
||||
string cve,
|
||||
string package,
|
||||
string? soname,
|
||||
string arch,
|
||||
string abi,
|
||||
string outputDir,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("deltasig-author");
|
||||
|
||||
if (!File.Exists(vulnPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Vulnerable binary not found: {vulnPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!File.Exists(patchedPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Patched binary not found: {patchedPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sigGenerator = services.GetRequiredService<IDeltaSignatureGenerator>();
|
||||
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Loading vulnerable binary: {vulnPath}[/]");
|
||||
AnsiConsole.MarkupLine($"[dim]Loading patched binary: {patchedPath}[/]");
|
||||
}
|
||||
|
||||
// Generate vulnerable signature
|
||||
await using var vulnStream = File.OpenRead(vulnPath);
|
||||
var vulnRequest = new DeltaSignatureRequest
|
||||
{
|
||||
Cve = cve,
|
||||
Package = package,
|
||||
Soname = soname,
|
||||
Arch = arch,
|
||||
Abi = abi,
|
||||
TargetSymbols = [], // Will detect automatically
|
||||
SignatureState = "vulnerable"
|
||||
};
|
||||
|
||||
var vulnSig = await sigGenerator.GenerateSignaturesAsync(vulnStream, vulnRequest, ct);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Vulnerable signature:[/] {vulnSig.Symbols.Length} symbols");
|
||||
}
|
||||
|
||||
// Generate patched signature
|
||||
await using var patchedStream = File.OpenRead(patchedPath);
|
||||
var patchedRequest = new DeltaSignatureRequest
|
||||
{
|
||||
Cve = cve,
|
||||
Package = package,
|
||||
Soname = soname,
|
||||
Arch = arch,
|
||||
Abi = abi,
|
||||
TargetSymbols = vulnSig.Symbols.Select(s => s.Name).ToArray(),
|
||||
SignatureState = "patched"
|
||||
};
|
||||
|
||||
var patchedSig = await sigGenerator.GenerateSignaturesAsync(patchedStream, patchedRequest, ct);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Patched signature:[/] {patchedSig.Symbols.Length} symbols");
|
||||
}
|
||||
|
||||
// Find differing symbols
|
||||
var vulnHashes = vulnSig.Symbols.ToDictionary(s => s.Name, s => s.HashHex);
|
||||
var differingSymbols = patchedSig.Symbols
|
||||
.Where(s => vulnHashes.TryGetValue(s.Name, out var vulnHash) && vulnHash != s.HashHex)
|
||||
.Select(s => s.Name)
|
||||
.ToImmutableArray();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[yellow]Differing symbols:[/] {differingSymbols.Length}");
|
||||
foreach (var sym in differingSymbols)
|
||||
{
|
||||
AnsiConsole.MarkupLine($" - {sym}");
|
||||
}
|
||||
}
|
||||
|
||||
// Write output files
|
||||
var vulnOutPath = Path.Combine(outputDir, $"{cve}_{arch}_vulnerable.json");
|
||||
var patchedOutPath = Path.Combine(outputDir, $"{cve}_{arch}_patched.json");
|
||||
|
||||
await File.WriteAllTextAsync(vulnOutPath, JsonSerializer.Serialize(vulnSig, JsonOptions), ct);
|
||||
await File.WriteAllTextAsync(patchedOutPath, JsonSerializer.Serialize(patchedSig, JsonOptions), ct);
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Signatures written:[/]");
|
||||
AnsiConsole.MarkupLine($" Vulnerable: {vulnOutPath}");
|
||||
AnsiConsole.MarkupLine($" Patched: {patchedOutPath}");
|
||||
AnsiConsole.MarkupLine($" Differing symbols: {differingSymbols.Length}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error authoring signatures for {Cve}", cve);
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle sign command - sign a signature payload with DSSE.
|
||||
/// </summary>
|
||||
public static async Task<int> HandleSignAsync(
|
||||
IServiceProvider services,
|
||||
string inputPath,
|
||||
string keyPath,
|
||||
string outputPath,
|
||||
string algorithm,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!File.Exists(inputPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Input file not found: {inputPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!File.Exists(keyPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Key file not found: {keyPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var payloadJson = await File.ReadAllTextAsync(inputPath, ct);
|
||||
var payloadBytes = System.Text.Encoding.UTF8.GetBytes(payloadJson);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Payload size: {payloadBytes.Length} bytes[/]");
|
||||
AnsiConsole.MarkupLine($"[dim]Algorithm: {algorithm}[/]");
|
||||
}
|
||||
|
||||
// TODO: Integrate with StellaOps.Attestor.Envelope for real DSSE signing
|
||||
// For now, create a placeholder envelope structure
|
||||
var envelope = new
|
||||
{
|
||||
payloadType = "application/vnd.stellaops.deltasig.v1+json",
|
||||
payload = Convert.ToBase64String(payloadBytes),
|
||||
signatures = new[]
|
||||
{
|
||||
new
|
||||
{
|
||||
keyid = "placeholder-key-id",
|
||||
sig = Convert.ToBase64String(new byte[64]) // Placeholder signature
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var envelopeJson = JsonSerializer.Serialize(envelope, JsonOptions);
|
||||
await File.WriteAllTextAsync(outputPath, envelopeJson, ct);
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]DSSE envelope written to:[/] {outputPath}");
|
||||
AnsiConsole.MarkupLine($"[yellow]Note:[/] Using placeholder signature - integrate with StellaOps.Attestor for production.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle verify command - verify a DSSE-signed envelope.
|
||||
/// </summary>
|
||||
public static async Task<int> HandleVerifyAsync(
|
||||
IServiceProvider services,
|
||||
string inputPath,
|
||||
string pubKeyPath,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!File.Exists(inputPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Input file not found: {inputPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!File.Exists(pubKeyPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Public key file not found: {pubKeyPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var envelopeJson = await File.ReadAllTextAsync(inputPath, ct);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Envelope size: {envelopeJson.Length} bytes[/]");
|
||||
}
|
||||
|
||||
// TODO: Integrate with StellaOps.Attestor.Envelope for real DSSE verification
|
||||
// For now, just parse and report structure
|
||||
var envelope = JsonSerializer.Deserialize<JsonElement>(envelopeJson);
|
||||
|
||||
var payloadType = envelope.GetProperty("payloadType").GetString();
|
||||
var hasSignatures = envelope.TryGetProperty("signatures", out var sigs);
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Envelope structure:[/]");
|
||||
AnsiConsole.MarkupLine($" Payload type: {payloadType}");
|
||||
AnsiConsole.MarkupLine($" Signatures: {(hasSignatures ? sigs.GetArrayLength().ToString() : "0")}");
|
||||
AnsiConsole.MarkupLine($"[yellow]Note:[/] Using placeholder verification - integrate with StellaOps.Attestor for production.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle match command - match a binary against signature packs.
|
||||
/// </summary>
|
||||
public static async Task<int> HandleMatchAsync(
|
||||
IServiceProvider services,
|
||||
string binaryPath,
|
||||
string sigpackPath,
|
||||
string? cveFilter,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var loggerFactory = services.GetRequiredService<ILoggerFactory>();
|
||||
var logger = loggerFactory.CreateLogger("deltasig-match");
|
||||
|
||||
if (!File.Exists(binaryPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Binary file not found: {binaryPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!File.Exists(sigpackPath) && !Directory.Exists(sigpackPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Signature pack not found: {sigpackPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var matcher = services.GetRequiredService<IDeltaSignatureMatcher>();
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Loading binary: {binaryPath}[/]");
|
||||
}
|
||||
|
||||
// Load binary
|
||||
var binaryBytes = await File.ReadAllBytesAsync(binaryPath, ct);
|
||||
|
||||
// Load signatures
|
||||
var signatures = await LoadSignaturesAsync(sigpackPath, cveFilter, ct);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Loaded {signatures.Count} signatures[/]");
|
||||
}
|
||||
|
||||
// Match
|
||||
using var binaryStream = new MemoryStream(binaryBytes);
|
||||
var results = await matcher.MatchAsync(binaryStream, signatures, cveFilter, ct);
|
||||
|
||||
// Output results
|
||||
var matchedResults = results.Where(r => r.Matched).ToList();
|
||||
|
||||
if (json)
|
||||
{
|
||||
var output = new
|
||||
{
|
||||
binary = Path.GetFileName(binaryPath),
|
||||
matches = matchedResults.Select(r => new
|
||||
{
|
||||
cve = r.Cve,
|
||||
state = r.SignatureState,
|
||||
confidence = r.Confidence,
|
||||
explanation = r.Explanation
|
||||
})
|
||||
};
|
||||
Console.WriteLine(JsonSerializer.Serialize(output, JsonOptions));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (matchedResults.Count == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[green]No matches found.[/]");
|
||||
}
|
||||
else
|
||||
{
|
||||
var table = new Table();
|
||||
table.AddColumn("CVE");
|
||||
table.AddColumn("State");
|
||||
table.AddColumn("Confidence");
|
||||
table.AddColumn("Explanation");
|
||||
|
||||
foreach (var r in matchedResults)
|
||||
{
|
||||
var stateColor = r.SignatureState == "vulnerable" ? "red" : "green";
|
||||
table.AddRow(
|
||||
r.Cve ?? "-",
|
||||
$"[{stateColor}]{r.SignatureState}[/]",
|
||||
$"{r.Confidence:P0}",
|
||||
r.Explanation ?? "-");
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
}
|
||||
|
||||
// Exit code: 2 if vulnerable matches found
|
||||
return matchedResults.Any(r => r.SignatureState == "vulnerable") ? 2 : 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "Error matching binary {BinaryPath}", binaryPath);
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle pack command - create a signature pack from individual files.
|
||||
/// </summary>
|
||||
public static async Task<int> HandlePackAsync(
|
||||
IServiceProvider services,
|
||||
string inputDir,
|
||||
string outputPath,
|
||||
string? packId,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!Directory.Exists(inputDir))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] Input directory not found: {inputDir}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var sigFiles = Directory.GetFiles(inputDir, "*.json", SearchOption.AllDirectories);
|
||||
|
||||
if (sigFiles.Length == 0)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Warning:[/] No signature files found in directory.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Found {sigFiles.Length} signature files[/]");
|
||||
}
|
||||
|
||||
// Create ZIP archive
|
||||
packId ??= $"stellaops-deltasig-{DateTime.UtcNow:yyyyMMdd-HHmmss}";
|
||||
|
||||
await using var zipStream = File.Create(outputPath);
|
||||
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
|
||||
|
||||
// Add manifest
|
||||
var manifest = new
|
||||
{
|
||||
packId,
|
||||
createdAt = DateTime.UtcNow.ToString("o"),
|
||||
version = "1.0.0",
|
||||
signatureCount = sigFiles.Length
|
||||
};
|
||||
|
||||
var manifestEntry = archive.CreateEntry("manifest.json");
|
||||
await using (var manifestStream = manifestEntry.Open())
|
||||
{
|
||||
await JsonSerializer.SerializeAsync(manifestStream, manifest, JsonOptions, ct);
|
||||
}
|
||||
|
||||
// Add signature files
|
||||
foreach (var sigFile in sigFiles)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var relativePath = Path.GetRelativePath(inputDir, sigFile);
|
||||
var entry = archive.CreateEntry(relativePath);
|
||||
|
||||
await using var sigStream = File.OpenRead(sigFile);
|
||||
await using var entryStream = entry.Open();
|
||||
await sigStream.CopyToAsync(entryStream, ct);
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[dim]Added: {relativePath}[/]");
|
||||
}
|
||||
}
|
||||
|
||||
AnsiConsole.MarkupLine($"[green]Signature pack created:[/] {outputPath}");
|
||||
AnsiConsole.MarkupLine($" Pack ID: {packId}");
|
||||
AnsiConsole.MarkupLine($" Signatures: {sigFiles.Length}");
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle inspect command - inspect a signature file or envelope.
|
||||
/// </summary>
|
||||
public static async Task<int> HandleInspectAsync(
|
||||
IServiceProvider services,
|
||||
string inputPath,
|
||||
bool json,
|
||||
bool verbose,
|
||||
CancellationToken ct)
|
||||
{
|
||||
if (!File.Exists(inputPath))
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] File not found: {inputPath}");
|
||||
return 1;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(inputPath, ct);
|
||||
var doc = JsonSerializer.Deserialize<JsonElement>(content);
|
||||
|
||||
// Detect file type
|
||||
var isDsse = doc.TryGetProperty("payloadType", out _);
|
||||
var isDeltaSig = doc.TryGetProperty("schema", out var schema) &&
|
||||
schema.GetString()?.Contains("deltasig") == true;
|
||||
|
||||
if (json)
|
||||
{
|
||||
// Just pretty-print the JSON
|
||||
Console.WriteLine(JsonSerializer.Serialize(doc, JsonOptions));
|
||||
}
|
||||
else if (isDsse)
|
||||
{
|
||||
AnsiConsole.MarkupLine("[bold]DSSE Envelope[/]");
|
||||
AnsiConsole.MarkupLine($" Payload Type: {doc.GetProperty("payloadType").GetString()}");
|
||||
|
||||
if (doc.TryGetProperty("signatures", out var sigs))
|
||||
{
|
||||
AnsiConsole.MarkupLine($" Signatures: {sigs.GetArrayLength()}");
|
||||
foreach (var sig in sigs.EnumerateArray())
|
||||
{
|
||||
var keyid = sig.GetProperty("keyid").GetString();
|
||||
AnsiConsole.MarkupLine($" - Key ID: {keyid}");
|
||||
}
|
||||
}
|
||||
|
||||
if (doc.TryGetProperty("payload", out var payloadB64))
|
||||
{
|
||||
var payloadBytes = Convert.FromBase64String(payloadB64.GetString()!);
|
||||
var payload = JsonSerializer.Deserialize<JsonElement>(payloadBytes);
|
||||
|
||||
if (payload.TryGetProperty("cve", out var cve))
|
||||
{
|
||||
AnsiConsole.MarkupLine($" CVE: {cve.GetString()}");
|
||||
}
|
||||
if (payload.TryGetProperty("signatureState", out var state))
|
||||
{
|
||||
AnsiConsole.MarkupLine($" State: {state.GetString()}");
|
||||
}
|
||||
if (payload.TryGetProperty("symbols", out var symbols))
|
||||
{
|
||||
AnsiConsole.MarkupLine($" Symbols: {symbols.GetArrayLength()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isDeltaSig)
|
||||
{
|
||||
var sig = JsonSerializer.Deserialize<DeltaSignature>(content, JsonOptions)!;
|
||||
|
||||
AnsiConsole.MarkupLine("[bold]Delta Signature[/]");
|
||||
AnsiConsole.MarkupLine($" Schema: {sig.Schema} v{sig.SchemaVersion}");
|
||||
AnsiConsole.MarkupLine($" CVE: {sig.Cve}");
|
||||
AnsiConsole.MarkupLine($" Package: {sig.Package.Name}");
|
||||
AnsiConsole.MarkupLine($" State: {sig.SignatureState}");
|
||||
AnsiConsole.MarkupLine($" Target: {sig.Target.Arch}/{sig.Target.Abi}");
|
||||
AnsiConsole.MarkupLine($" Symbols: {sig.Symbols.Length}");
|
||||
|
||||
if (verbose)
|
||||
{
|
||||
var table = new Table();
|
||||
table.AddColumn("Symbol");
|
||||
table.AddColumn("Hash");
|
||||
table.AddColumn("Size");
|
||||
table.AddColumn("BB Count");
|
||||
|
||||
foreach (var sym in sig.Symbols)
|
||||
{
|
||||
table.AddRow(
|
||||
sym.Name,
|
||||
sym.HashHex[..Math.Min(16, sym.HashHex.Length)] + "...",
|
||||
sym.SizeBytes.ToString(),
|
||||
sym.CfgBbCount?.ToString() ?? "-");
|
||||
}
|
||||
|
||||
AnsiConsole.Write(table);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiConsole.MarkupLine("[yellow]Unknown file format[/]");
|
||||
Console.WriteLine(JsonSerializer.Serialize(doc, JsonOptions));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[red]Error:[/] {ex.Message}");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load signatures from a file, directory, or ZIP archive.
|
||||
/// </summary>
|
||||
private static async Task<List<DeltaSignature>> LoadSignaturesAsync(
|
||||
string path,
|
||||
string? cveFilter,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var signatures = new List<DeltaSignature>();
|
||||
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
// Load from directory
|
||||
foreach (var file in Directory.GetFiles(path, "*.json", SearchOption.AllDirectories))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var sig = await LoadSignatureFileAsync(file, ct);
|
||||
if (sig != null && (cveFilter == null || sig.Cve == cveFilter))
|
||||
{
|
||||
signatures.Add(sig);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (path.EndsWith(".zip", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
// Load from ZIP
|
||||
using var archive = ZipFile.OpenRead(path);
|
||||
foreach (var entry in archive.Entries.Where(e => e.Name.EndsWith(".json")))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if (entry.Name == "manifest.json") continue;
|
||||
|
||||
await using var stream = entry.Open();
|
||||
using var reader = new StreamReader(stream);
|
||||
var content = await reader.ReadToEndAsync(ct);
|
||||
|
||||
try
|
||||
{
|
||||
var sig = JsonSerializer.Deserialize<DeltaSignature>(content, JsonOptions);
|
||||
if (sig != null && (cveFilter == null || sig.Cve == cveFilter))
|
||||
{
|
||||
signatures.Add(sig);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// Skip non-signature files
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single file
|
||||
var sig = await LoadSignatureFileAsync(path, ct);
|
||||
if (sig != null && (cveFilter == null || sig.Cve == cveFilter))
|
||||
{
|
||||
signatures.Add(sig);
|
||||
}
|
||||
}
|
||||
|
||||
return signatures;
|
||||
}
|
||||
|
||||
private static async Task<DeltaSignature?> LoadSignatureFileAsync(string path, CancellationToken ct)
|
||||
{
|
||||
try
|
||||
{
|
||||
var content = await File.ReadAllTextAsync(path, ct);
|
||||
return JsonSerializer.Deserialize<DeltaSignature>(content, JsonOptions);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -85,6 +85,10 @@
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.AuditPack/StellaOps.AuditPack.csproj" />
|
||||
<ProjectReference Include="../../__Libraries/StellaOps.Provcache/StellaOps.Provcache.csproj" />
|
||||
<ProjectReference Include="../../Signer/StellaOps.Signer/StellaOps.Signer.Infrastructure/StellaOps.Signer.Infrastructure.csproj" />
|
||||
<!-- Binary Delta Signatures (SPRINT_20260102_001_BE) -->
|
||||
<ProjectReference Include="../../BinaryIndex/__Libraries/StellaOps.BinaryIndex.Disassembly/StellaOps.BinaryIndex.Disassembly.csproj" />
|
||||
<ProjectReference Include="../../BinaryIndex/__Libraries/StellaOps.BinaryIndex.Normalization/StellaOps.BinaryIndex.Normalization.csproj" />
|
||||
<ProjectReference Include="../../BinaryIndex/__Libraries/StellaOps.BinaryIndex.DeltaSig/StellaOps.BinaryIndex.DeltaSig.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- GOST Crypto Plugins (Russia distribution) -->
|
||||
|
||||
Reference in New Issue
Block a user