feat(eidas): Implement eIDAS Crypto Plugin with dependency injection and signing capabilities

- Added ServiceCollectionExtensions for eIDAS crypto providers.
- Implemented EidasCryptoProvider for handling eIDAS-compliant signatures.
- Created LocalEidasProvider for local signing using PKCS#12 keystores.
- Defined SignatureLevel and SignatureFormat enums for eIDAS compliance.
- Developed TrustServiceProviderClient for remote signing via TSP.
- Added configuration support for eIDAS options in the project file.
- Implemented unit tests for SM2 compliance and crypto operations.
- Introduced dependency injection extensions for SM software and remote plugins.
This commit is contained in:
master
2025-12-23 14:06:48 +02:00
parent ef933db0d8
commit 84d97fd22c
51 changed files with 4353 additions and 747 deletions

View File

@@ -0,0 +1,133 @@
namespace StellaOps.Feedser.BinaryAnalysis;
using StellaOps.Feedser.BinaryAnalysis.Fingerprinters;
using StellaOps.Feedser.BinaryAnalysis.Models;
/// <summary>
/// Factory for creating and managing binary fingerprinters.
/// Provides access to all available fingerprinting methods (Tier 4).
/// </summary>
public sealed class BinaryFingerprintFactory
{
private readonly Dictionary<FingerprintMethod, IBinaryFingerprinter> _fingerprinters;
public BinaryFingerprintFactory()
{
_fingerprinters = new Dictionary<FingerprintMethod, IBinaryFingerprinter>
{
[FingerprintMethod.TLSH] = new SimplifiedTlshFingerprinter(),
[FingerprintMethod.InstructionHash] = new InstructionHashFingerprinter()
};
}
/// <summary>
/// Get fingerprinter for specified method.
/// </summary>
public IBinaryFingerprinter GetFingerprinter(FingerprintMethod method)
{
if (!_fingerprinters.TryGetValue(method, out var fingerprinter))
{
throw new NotSupportedException($"Fingerprint method {method} is not supported");
}
return fingerprinter;
}
/// <summary>
/// Extract fingerprints using all available methods.
/// </summary>
public async Task<IReadOnlyList<BinaryFingerprint>> ExtractAllAsync(
string binaryPath,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default)
{
var tasks = _fingerprinters.Values.Select(fp =>
fp.ExtractAsync(binaryPath, cveId, targetFunction, cancellationToken));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
/// <summary>
/// Extract fingerprints using all available methods from binary data.
/// </summary>
public async Task<IReadOnlyList<BinaryFingerprint>> ExtractAllAsync(
ReadOnlyMemory<byte> binaryData,
string binaryName,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default)
{
var tasks = _fingerprinters.Values.Select(fp =>
fp.ExtractAsync(binaryData, binaryName, cveId, targetFunction, cancellationToken));
var results = await Task.WhenAll(tasks);
return results.ToList();
}
/// <summary>
/// Match candidate binary against known fingerprints using all methods.
/// Returns best match result.
/// </summary>
public async Task<FingerprintMatchResult?> MatchBestAsync(
string candidatePath,
IEnumerable<BinaryFingerprint> knownFingerprints,
CancellationToken cancellationToken = default)
{
var matchTasks = new List<Task<FingerprintMatchResult>>();
foreach (var known in knownFingerprints)
{
if (_fingerprinters.TryGetValue(known.Method, out var fingerprinter))
{
matchTasks.Add(fingerprinter.MatchAsync(candidatePath, known, cancellationToken));
}
}
var results = await Task.WhenAll(matchTasks);
// Return best match (highest confidence)
return results
.Where(r => r.IsMatch)
.OrderByDescending(r => r.Confidence)
.ThenByDescending(r => r.Similarity)
.FirstOrDefault();
}
/// <summary>
/// Match candidate binary data against known fingerprints using all methods.
/// Returns best match result.
/// </summary>
public async Task<FingerprintMatchResult?> MatchBestAsync(
ReadOnlyMemory<byte> candidateData,
IEnumerable<BinaryFingerprint> knownFingerprints,
CancellationToken cancellationToken = default)
{
var matchTasks = new List<Task<FingerprintMatchResult>>();
foreach (var known in knownFingerprints)
{
if (_fingerprinters.TryGetValue(known.Method, out var fingerprinter))
{
matchTasks.Add(fingerprinter.MatchAsync(candidateData, known, cancellationToken));
}
}
var results = await Task.WhenAll(matchTasks);
return results
.Where(r => r.IsMatch)
.OrderByDescending(r => r.Confidence)
.ThenByDescending(r => r.Similarity)
.FirstOrDefault();
}
/// <summary>
/// Get all available fingerprinting methods.
/// </summary>
public IReadOnlyList<FingerprintMethod> GetAvailableMethods()
{
return _fingerprinters.Keys.ToList();
}
}

View File

@@ -0,0 +1,249 @@
namespace StellaOps.Feedser.BinaryAnalysis.Fingerprinters;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Feedser.BinaryAnalysis.Models;
/// <summary>
/// Fingerprinter based on normalized instruction sequences.
/// Extracts and hashes instruction opcodes while normalizing out operands.
///
/// This approach is resistant to:
/// - Address randomization (ASLR)
/// - Register allocation differences
/// - Minor compiler optimizations
///
/// NOTE: This is a simplified implementation. Production use should integrate
/// with disassemblers like Capstone for proper instruction decoding.
/// </summary>
public sealed class InstructionHashFingerprinter : IBinaryFingerprinter
{
private const string Version = "1.0.0";
private const int MinInstructionSequence = 16; // Minimum instructions to fingerprint
public FingerprintMethod Method => FingerprintMethod.InstructionHash;
public async Task<BinaryFingerprint> ExtractAsync(
string binaryPath,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default)
{
var binaryData = await File.ReadAllBytesAsync(binaryPath, cancellationToken);
var binaryName = Path.GetFileName(binaryPath);
return await ExtractAsync(binaryData, binaryName, cveId, targetFunction, cancellationToken);
}
public Task<BinaryFingerprint> ExtractAsync(
ReadOnlyMemory<byte> binaryData,
string binaryName,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default)
{
var metadata = ExtractMetadata(binaryData.Span, binaryName);
var instructionHash = ComputeInstructionHash(binaryData.Span, metadata.Architecture);
var fingerprint = new BinaryFingerprint
{
FingerprintId = $"fingerprint:instruction:{instructionHash}",
CveId = cveId,
Method = FingerprintMethod.InstructionHash,
FingerprintValue = instructionHash,
TargetBinary = binaryName,
TargetFunction = targetFunction,
Metadata = metadata,
ExtractedAt = DateTimeOffset.UtcNow,
ExtractorVersion = Version
};
return Task.FromResult(fingerprint);
}
public async Task<FingerprintMatchResult> MatchAsync(
string candidatePath,
BinaryFingerprint knownFingerprint,
CancellationToken cancellationToken = default)
{
var candidateData = await File.ReadAllBytesAsync(candidatePath, cancellationToken);
return await MatchAsync(candidateData, knownFingerprint, cancellationToken);
}
public Task<FingerprintMatchResult> MatchAsync(
ReadOnlyMemory<byte> candidateData,
BinaryFingerprint knownFingerprint,
CancellationToken cancellationToken = default)
{
var metadata = ExtractMetadata(candidateData.Span, "candidate");
var candidateHash = ComputeInstructionHash(candidateData.Span, metadata.Architecture);
// Exact match only (instruction sequences must be identical after normalization)
var isMatch = candidateHash.Equals(knownFingerprint.FingerprintValue, StringComparison.Ordinal);
var similarity = isMatch ? 1.0 : 0.0;
var confidence = isMatch ? 0.80 : 0.0; // High confidence for exact matches
var result = new FingerprintMatchResult
{
IsMatch = isMatch,
Similarity = similarity,
Confidence = confidence,
MatchedFingerprintId = isMatch ? knownFingerprint.FingerprintId : null,
Method = FingerprintMethod.InstructionHash,
MatchDetails = new Dictionary<string, object>
{
["candidate_hash"] = candidateHash,
["known_hash"] = knownFingerprint.FingerprintValue,
["match_type"] = isMatch ? "exact" : "none"
}
};
return Task.FromResult(result);
}
private static string ComputeInstructionHash(ReadOnlySpan<byte> data, string architecture)
{
// Extract opcode patterns (simplified - production would use proper disassembly)
var opcodes = ExtractOpcodePatterns(data, architecture);
// Normalize by removing operand-specific bytes
var normalized = NormalizeOpcodes(opcodes);
// Hash the normalized sequence
var hash = SHA256.HashData(Encoding.UTF8.GetBytes(normalized));
return Convert.ToHexString(hash).ToLowerInvariant();
}
private static string ExtractOpcodePatterns(ReadOnlySpan<byte> data, string architecture)
{
// Simplified opcode extraction
// Production implementation would use Capstone or similar for proper disassembly
var sb = new StringBuilder();
var step = architecture switch
{
"x86_64" or "x86" => 1, // Variable length instructions
"aarch64" => 4, // Fixed 4-byte instructions
"armv7" => 2, // Thumb: 2-byte, ARM: 4-byte (simplified to 2)
_ => 1
};
// Sample instructions at regular intervals
for (int i = 0; i < data.Length && i < 1024; i += step)
{
if (i + step <= data.Length)
{
// Extract opcode prefix (first byte for x86, full instruction for RISC)
var opcode = data[i];
// Filter out likely data sections (high entropy, unusual patterns)
if (IsLikelyInstruction(opcode))
{
sb.Append(opcode.ToString("x2"));
sb.Append('-');
}
}
}
return sb.ToString();
}
private static bool IsLikelyInstruction(byte opcode)
{
// Simple heuristic: filter out common data patterns
// Real implementation would use proper code/data discrimination
return opcode != 0x00 && opcode != 0xFF && opcode != 0xCC; // Not null, not padding, not int3
}
private static string NormalizeOpcodes(string opcodes)
{
// Remove position-dependent patterns
// This is a simplified normalization
var sb = new StringBuilder();
var parts = opcodes.Split('-', StringSplitOptions.RemoveEmptyEntries);
// Group similar opcodes to reduce position sensitivity
var groups = parts.GroupBy(p => p).OrderBy(g => g.Key);
foreach (var group in groups)
{
sb.Append(group.Key);
sb.Append(':');
sb.Append(group.Count());
sb.Append(';');
}
return sb.ToString();
}
private static FingerprintMetadata ExtractMetadata(ReadOnlySpan<byte> data, string binaryName)
{
var format = DetectFormat(data);
var architecture = DetectArchitecture(data, format);
return new FingerprintMetadata
{
Architecture = architecture,
Format = format,
Compiler = null,
OptimizationLevel = null,
HasDebugSymbols = false,
FileOffset = null,
RegionSize = data.Length
};
}
private static string DetectFormat(ReadOnlySpan<byte> data)
{
if (data.Length < 4) return "unknown";
if (data[0] == 0x7F && data[1] == 'E' && data[2] == 'L' && data[3] == 'F')
return "ELF";
if (data[0] == 'M' && data[1] == 'Z')
return "PE";
if (data.Length >= 4)
{
var magic = BitConverter.ToUInt32(data[..4]);
if (magic == 0xFEEDFACE || magic == 0xFEEDFACF ||
magic == 0xCEFAEDFE || magic == 0xCFFAEDFE)
return "Mach-O";
}
return "unknown";
}
private static string DetectArchitecture(ReadOnlySpan<byte> data, string format)
{
if (format == "ELF" && data.Length >= 18)
{
var machine = BitConverter.ToUInt16(data.Slice(18, 2));
return machine switch
{
0x3E => "x86_64",
0x03 => "x86",
0xB7 => "aarch64",
0x28 => "armv7",
_ => "unknown"
};
}
if (format == "PE" && data.Length >= 0x3C + 4)
{
var peOffset = BitConverter.ToInt32(data.Slice(0x3C, 4));
if (peOffset > 0 && peOffset + 6 < data.Length)
{
var machine = BitConverter.ToUInt16(data.Slice(peOffset + 4, 2));
return machine switch
{
0x8664 => "x86_64",
0x014C => "x86",
0xAA64 => "aarch64",
_ => "unknown"
};
}
}
return "unknown";
}
}

View File

@@ -0,0 +1,315 @@
namespace StellaOps.Feedser.BinaryAnalysis.Fingerprinters;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Feedser.BinaryAnalysis.Models;
/// <summary>
/// Simplified locality-sensitive hash fingerprinter.
///
/// NOTE: This is a simplified implementation for proof-of-concept.
/// Production use should integrate with a full TLSH library (e.g., via P/Invoke to libtlsh).
///
/// This implementation captures key TLSH principles:
/// - Sliding window analysis
/// - Byte distribution histograms
/// - Quartile-based digest
/// - Fuzzy matching with Hamming distance
/// </summary>
public sealed class SimplifiedTlshFingerprinter : IBinaryFingerprinter
{
private const string Version = "1.0.0-simplified";
private const int WindowSize = 5;
private const int BucketCount = 256;
private const int DigestSize = 32; // 32 bytes = 256 bits
public FingerprintMethod Method => FingerprintMethod.TLSH;
public async Task<BinaryFingerprint> ExtractAsync(
string binaryPath,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default)
{
var binaryData = await File.ReadAllBytesAsync(binaryPath, cancellationToken);
var binaryName = Path.GetFileName(binaryPath);
return await ExtractAsync(binaryData, binaryName, cveId, targetFunction, cancellationToken);
}
public Task<BinaryFingerprint> ExtractAsync(
ReadOnlyMemory<byte> binaryData,
string binaryName,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default)
{
var hash = ComputeLocalitySensitiveHash(binaryData.Span);
var metadata = ExtractMetadata(binaryData.Span, binaryName);
var fingerprint = new BinaryFingerprint
{
FingerprintId = $"fingerprint:tlsh:{hash}",
CveId = cveId,
Method = FingerprintMethod.TLSH,
FingerprintValue = hash,
TargetBinary = binaryName,
TargetFunction = targetFunction,
Metadata = metadata,
ExtractedAt = DateTimeOffset.UtcNow,
ExtractorVersion = Version
};
return Task.FromResult(fingerprint);
}
public async Task<FingerprintMatchResult> MatchAsync(
string candidatePath,
BinaryFingerprint knownFingerprint,
CancellationToken cancellationToken = default)
{
var candidateData = await File.ReadAllBytesAsync(candidatePath, cancellationToken);
return await MatchAsync(candidateData, knownFingerprint, cancellationToken);
}
public Task<FingerprintMatchResult> MatchAsync(
ReadOnlyMemory<byte> candidateData,
BinaryFingerprint knownFingerprint,
CancellationToken cancellationToken = default)
{
var candidateHash = ComputeLocalitySensitiveHash(candidateData.Span);
var similarity = ComputeSimilarity(candidateHash, knownFingerprint.FingerprintValue);
// TLSH matching thresholds:
// similarity > 0.90: High confidence match
// similarity > 0.75: Medium confidence match
// similarity > 0.60: Low confidence match
var isMatch = similarity >= 0.60;
var confidence = similarity switch
{
>= 0.90 => 0.85, // Tier 4 max confidence
>= 0.75 => 0.70,
>= 0.60 => 0.55,
_ => 0.0
};
var result = new FingerprintMatchResult
{
IsMatch = isMatch,
Similarity = similarity,
Confidence = confidence,
MatchedFingerprintId = isMatch ? knownFingerprint.FingerprintId : null,
Method = FingerprintMethod.TLSH,
MatchDetails = new Dictionary<string, object>
{
["candidate_hash"] = candidateHash,
["known_hash"] = knownFingerprint.FingerprintValue,
["hamming_distance"] = ComputeHammingDistance(candidateHash, knownFingerprint.FingerprintValue)
}
};
return Task.FromResult(result);
}
private static string ComputeLocalitySensitiveHash(ReadOnlySpan<byte> data)
{
if (data.Length < WindowSize)
{
// For very small data, fall back to regular hash
return Convert.ToHexString(SHA256.HashData(data)).ToLowerInvariant()[..DigestSize];
}
// Step 1: Compute sliding window triplets (pearson hashing)
var buckets = new int[BucketCount];
for (int i = 0; i < data.Length - WindowSize + 1; i++)
{
var triplet = ComputeTripletHash(data.Slice(i, WindowSize));
buckets[triplet % BucketCount]++;
}
// Step 2: Compute quartiles (Q1, Q2, Q3)
var sorted = buckets.OrderBy(b => b).ToArray();
var q1 = sorted[BucketCount / 4];
var q2 = sorted[BucketCount / 2];
var q3 = sorted[3 * BucketCount / 4];
// Step 3: Generate digest based on quartile comparisons
var digest = new byte[DigestSize];
for (int i = 0; i < BucketCount && i / 8 < DigestSize; i++)
{
var byteIdx = i / 8;
var bitIdx = i % 8;
// Set bit based on quartile position
if (buckets[i] >= q3)
{
digest[byteIdx] |= (byte)(1 << bitIdx);
}
else if (buckets[i] >= q2)
{
digest[byteIdx] |= (byte)(1 << (bitIdx + 1) % 8);
}
}
// Step 4: Add length and checksum metadata
var length = Math.Min(data.Length, 0xFFFF);
var lengthBytes = BitConverter.GetBytes((ushort)length);
digest[0] ^= lengthBytes[0];
digest[1] ^= lengthBytes[1];
return Convert.ToHexString(digest).ToLowerInvariant();
}
private static byte ComputeTripletHash(ReadOnlySpan<byte> window)
{
// Pearson hashing for the window
byte hash = 0;
foreach (var b in window)
{
hash = PearsonTable[(hash ^ b) % 256];
}
return hash;
}
private static double ComputeSimilarity(string hash1, string hash2)
{
if (hash1.Length != hash2.Length)
{
return 0.0;
}
var distance = ComputeHammingDistance(hash1, hash2);
var maxDistance = hash1.Length * 4; // Each hex char = 4 bits
return 1.0 - ((double)distance / maxDistance);
}
private static int ComputeHammingDistance(string hash1, string hash2)
{
var bytes1 = Convert.FromHexString(hash1);
var bytes2 = Convert.FromHexString(hash2);
var distance = 0;
for (int i = 0; i < Math.Min(bytes1.Length, bytes2.Length); i++)
{
var xor = (byte)(bytes1[i] ^ bytes2[i]);
distance += CountBits(xor);
}
return distance;
}
private static int CountBits(byte b)
{
var count = 0;
while (b != 0)
{
count += b & 1;
b >>= 1;
}
return count;
}
private static FingerprintMetadata ExtractMetadata(ReadOnlySpan<byte> data, string binaryName)
{
// Detect binary format from magic bytes
var format = DetectFormat(data);
var architecture = DetectArchitecture(data, format);
return new FingerprintMetadata
{
Architecture = architecture,
Format = format,
Compiler = null, // Would require deeper analysis
OptimizationLevel = null,
HasDebugSymbols = false, // Simplified
FileOffset = null,
RegionSize = data.Length
};
}
private static string DetectFormat(ReadOnlySpan<byte> data)
{
if (data.Length < 4) return "unknown";
// ELF: 0x7F 'E' 'L' 'F'
if (data[0] == 0x7F && data[1] == 'E' && data[2] == 'L' && data[3] == 'F')
{
return "ELF";
}
// PE: 'M' 'Z'
if (data[0] == 'M' && data[1] == 'Z')
{
return "PE";
}
// Mach-O: 0xFEEDFACE or 0xFEEDFACF (32/64-bit)
if (data.Length >= 4)
{
var magic = BitConverter.ToUInt32(data[..4]);
if (magic == 0xFEEDFACE || magic == 0xFEEDFACF ||
magic == 0xCEFAEDFE || magic == 0xCFFAEDFE)
{
return "Mach-O";
}
}
return "unknown";
}
private static string DetectArchitecture(ReadOnlySpan<byte> data, string format)
{
if (format == "ELF" && data.Length >= 18)
{
var machine = BitConverter.ToUInt16(data.Slice(18, 2));
return machine switch
{
0x3E => "x86_64",
0x03 => "x86",
0xB7 => "aarch64",
0x28 => "armv7",
_ => "unknown"
};
}
if (format == "PE" && data.Length >= 0x3C + 4)
{
// PE offset is at 0x3C
var peOffset = BitConverter.ToInt32(data.Slice(0x3C, 4));
if (peOffset > 0 && peOffset + 6 < data.Length)
{
var machine = BitConverter.ToUInt16(data.Slice(peOffset + 4, 2));
return machine switch
{
0x8664 => "x86_64",
0x014C => "x86",
0xAA64 => "aarch64",
_ => "unknown"
};
}
}
return "unknown";
}
// Pearson hash lookup table
private static readonly byte[] PearsonTable = new byte[256]
{
// Standard Pearson hash permutation table
98, 6, 85, 150, 36, 23, 112, 164, 135, 207, 169, 5, 26, 64, 165, 219,
61, 20, 68, 89, 130, 63, 52, 102, 24, 229, 132, 245, 80, 216, 195, 115,
90, 168, 156, 203, 177, 120, 2, 190, 188, 7, 100, 185, 174, 243, 162, 10,
237, 18, 253, 225, 8, 208, 172, 244, 255, 126, 101, 79, 145, 235, 228, 121,
123, 251, 67, 250, 161, 0, 107, 97, 241, 111, 181, 82, 249, 33, 69, 55,
59, 153, 29, 9, 213, 167, 84, 93, 30, 46, 94, 75, 151, 114, 73, 222,
197, 96, 210, 45, 16, 227, 248, 202, 51, 152, 252, 125, 81, 206, 215, 186,
39, 158, 178, 187, 131, 136, 1, 49, 50, 17, 141, 91, 47, 129, 60, 99,
154, 35, 86, 171, 105, 34, 38, 200, 147, 58, 77, 118, 173, 246, 76, 254,
133, 232, 196, 144, 198, 124, 53, 4, 108, 74, 223, 234, 134, 230, 157, 139,
189, 205, 199, 128, 176, 19, 211, 236, 127, 192, 231, 70, 233, 88, 146, 44,
183, 201, 22, 83, 13, 214, 116, 109, 159, 32, 95, 226, 140, 220, 57, 12,
221, 31, 209, 182, 143, 92, 149, 184, 148, 62, 113, 65, 37, 27, 106, 166,
3, 14, 204, 72, 21, 41, 56, 66, 28, 193, 40, 217, 25, 54, 179, 117,
238, 87, 240, 155, 180, 170, 242, 212, 191, 163, 78, 218, 137, 194, 175, 110,
43, 119, 224, 71, 122, 142, 42, 160, 104, 48, 247, 103, 15, 11, 138, 239
};
}

View File

@@ -0,0 +1,68 @@
namespace StellaOps.Feedser.BinaryAnalysis;
using StellaOps.Feedser.BinaryAnalysis.Models;
/// <summary>
/// Interface for extracting binary fingerprints from compiled artifacts.
/// </summary>
public interface IBinaryFingerprinter
{
/// <summary>
/// Fingerprinting method this implementation provides.
/// </summary>
FingerprintMethod Method { get; }
/// <summary>
/// Extract fingerprint from binary file.
/// </summary>
/// <param name="binaryPath">Path to binary file.</param>
/// <param name="cveId">Associated CVE ID.</param>
/// <param name="targetFunction">Optional function name to fingerprint.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Binary fingerprint.</returns>
Task<BinaryFingerprint> ExtractAsync(
string binaryPath,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Extract fingerprint from binary bytes.
/// </summary>
/// <param name="binaryData">Binary data.</param>
/// <param name="binaryName">Binary name for identification.</param>
/// <param name="cveId">Associated CVE ID.</param>
/// <param name="targetFunction">Optional function name to fingerprint.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Binary fingerprint.</returns>
Task<BinaryFingerprint> ExtractAsync(
ReadOnlyMemory<byte> binaryData,
string binaryName,
string? cveId,
string? targetFunction = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Match candidate binary against known fingerprint.
/// </summary>
/// <param name="candidatePath">Path to candidate binary.</param>
/// <param name="knownFingerprint">Known fingerprint to match against.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Match result.</returns>
Task<FingerprintMatchResult> MatchAsync(
string candidatePath,
BinaryFingerprint knownFingerprint,
CancellationToken cancellationToken = default);
/// <summary>
/// Match candidate binary bytes against known fingerprint.
/// </summary>
/// <param name="candidateData">Candidate binary data.</param>
/// <param name="knownFingerprint">Known fingerprint to match against.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Match result.</returns>
Task<FingerprintMatchResult> MatchAsync(
ReadOnlyMemory<byte> candidateData,
BinaryFingerprint knownFingerprint,
CancellationToken cancellationToken = default);
}

View File

@@ -0,0 +1,161 @@
namespace StellaOps.Feedser.BinaryAnalysis.Models;
/// <summary>
/// Binary fingerprint for matching patched code in compiled artifacts (Tier 4).
/// </summary>
public sealed record BinaryFingerprint
{
/// <summary>
/// Unique fingerprint identifier.
/// Format: "fingerprint:{method}:{hash}"
/// </summary>
public required string FingerprintId { get; init; }
/// <summary>
/// CVE ID this fingerprint is associated with.
/// </summary>
public required string? CveId { get; init; }
/// <summary>
/// Fingerprinting method used.
/// </summary>
public required FingerprintMethod Method { get; init; }
/// <summary>
/// Binary hash or signature value.
/// </summary>
public required string FingerprintValue { get; init; }
/// <summary>
/// Binary file or symbol this fingerprint applies to.
/// </summary>
public required string TargetBinary { get; init; }
/// <summary>
/// Optional function or symbol name.
/// </summary>
public string? TargetFunction { get; init; }
/// <summary>
/// Metadata about the fingerprint.
/// </summary>
public required FingerprintMetadata Metadata { get; init; }
/// <summary>
/// When this fingerprint was extracted.
/// </summary>
public required DateTimeOffset ExtractedAt { get; init; }
/// <summary>
/// Version of the extraction tool.
/// </summary>
public required string ExtractorVersion { get; init; }
}
/// <summary>
/// Fingerprinting method.
/// </summary>
public enum FingerprintMethod
{
/// <summary>
/// Trend Micro Locality Sensitive Hash (fuzzy hashing).
/// </summary>
TLSH,
/// <summary>
/// Function-level control flow graph hash.
/// </summary>
CFGHash,
/// <summary>
/// Normalized instruction sequence hash.
/// </summary>
InstructionHash,
/// <summary>
/// Symbol table hash.
/// </summary>
SymbolHash,
/// <summary>
/// Section hash (e.g., .text section).
/// </summary>
SectionHash
}
/// <summary>
/// Metadata for a binary fingerprint.
/// </summary>
public sealed record FingerprintMetadata
{
/// <summary>
/// Architecture (e.g., x86_64, aarch64, armv7).
/// </summary>
public required string Architecture { get; init; }
/// <summary>
/// Binary format (ELF, PE, Mach-O).
/// </summary>
public required string Format { get; init; }
/// <summary>
/// Compiler and version if detected.
/// </summary>
public string? Compiler { get; init; }
/// <summary>
/// Optimization level if detected.
/// </summary>
public string? OptimizationLevel { get; init; }
/// <summary>
/// Debug symbols present.
/// </summary>
public required bool HasDebugSymbols { get; init; }
/// <summary>
/// File offset of the fingerprinted region.
/// </summary>
public long? FileOffset { get; init; }
/// <summary>
/// Size of the fingerprinted region in bytes.
/// </summary>
public long? RegionSize { get; init; }
}
/// <summary>
/// Result of fingerprint matching.
/// </summary>
public sealed record FingerprintMatchResult
{
/// <summary>
/// Whether a match was found.
/// </summary>
public required bool IsMatch { get; init; }
/// <summary>
/// Similarity score (0.0-1.0).
/// </summary>
public required double Similarity { get; init; }
/// <summary>
/// Confidence in the match (0.0-1.0).
/// </summary>
public required double Confidence { get; init; }
/// <summary>
/// Matching fingerprint ID.
/// </summary>
public string? MatchedFingerprintId { get; init; }
/// <summary>
/// Method used for matching.
/// </summary>
public required FingerprintMethod Method { get; init; }
/// <summary>
/// Additional matching details.
/// </summary>
public Dictionary<string, object>? MatchDetails { get; init; }
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>