notify doctors work, audit work, new product advisory sprints
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@@ -414,23 +415,23 @@ public sealed class PrReachabilityGate : IPrReachabilityGate
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.AppendLine(passed
|
||||
? "## ✅ Reachability Gate Passed"
|
||||
: "## ❌ Reachability Gate Blocked");
|
||||
? "## [OK] Reachability Gate Passed"
|
||||
: "## [BLOCKED] Reachability Gate Blocked");
|
||||
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("| Metric | Value |");
|
||||
sb.AppendLine("|--------|-------|");
|
||||
sb.AppendLine($"| New reachable paths | {decision.NewReachableCount} |");
|
||||
sb.AppendLine($"| New reachable paths | {decision.NewReachableCount.ToString(CultureInfo.InvariantCulture)} |");
|
||||
|
||||
if (options.IncludeMitigatedInSummary)
|
||||
{
|
||||
sb.AppendLine($"| Mitigated paths | {decision.MitigatedCount} |");
|
||||
sb.AppendLine($"| Net change | {decision.NetChange:+#;-#;0} |");
|
||||
sb.AppendLine($"| Mitigated paths | {decision.MitigatedCount.ToString(CultureInfo.InvariantCulture)} |");
|
||||
sb.AppendLine($"| Net change | {decision.NetChange.ToString("+#;-#;0", CultureInfo.InvariantCulture)} |");
|
||||
}
|
||||
|
||||
sb.AppendLine($"| Analysis type | {(decision.WasIncremental ? "Incremental" : "Full")} |");
|
||||
sb.AppendLine($"| Cache savings | {decision.SavingsRatio:P0} |");
|
||||
sb.AppendLine($"| Duration | {decision.Duration.TotalMilliseconds:F0}ms |");
|
||||
sb.AppendLine($"| Cache savings | {decision.SavingsRatio.ToString("P0", CultureInfo.InvariantCulture)} |");
|
||||
sb.AppendLine($"| Duration | {decision.Duration.TotalMilliseconds.ToString("F0", CultureInfo.InvariantCulture)}ms |");
|
||||
|
||||
if (!passed && decision.BlockingFlips.Count > 0)
|
||||
{
|
||||
@@ -440,12 +441,13 @@ public sealed class PrReachabilityGate : IPrReachabilityGate
|
||||
|
||||
foreach (var flip in decision.BlockingFlips.Take(10))
|
||||
{
|
||||
sb.AppendLine($"- `{flip.EntryMethodKey}` -> `{flip.SinkMethodKey}` (confidence: {flip.Confidence:P0})");
|
||||
sb.AppendLine($"- `{flip.EntryMethodKey}` -> `{flip.SinkMethodKey}` (confidence: {flip.Confidence.ToString("P0", CultureInfo.InvariantCulture)})");
|
||||
}
|
||||
|
||||
if (decision.BlockingFlips.Count > 10)
|
||||
{
|
||||
sb.AppendLine($"- ... and {decision.BlockingFlips.Count - 10} more");
|
||||
var remaining = decision.BlockingFlips.Count - 10;
|
||||
sb.AppendLine($"- ... and {remaining.ToString(CultureInfo.InvariantCulture)} more");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -124,7 +125,7 @@ public sealed class PathExplanationService : IPathExplanationService
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<ExplainedPath?> ExplainPathAsync(
|
||||
public async Task<ExplainedPath?> ExplainPathAsync(
|
||||
RichGraph graph,
|
||||
string pathId,
|
||||
CancellationToken cancellationToken = default)
|
||||
@@ -145,20 +146,22 @@ public sealed class PathExplanationService : IPathExplanationService
|
||||
MaxPaths = 100
|
||||
};
|
||||
|
||||
var resultTask = ExplainAsync(graph, query, cancellationToken);
|
||||
return resultTask.ContinueWith(t =>
|
||||
var result = await ExplainAsync(graph, query, cancellationToken).ConfigureAwait(false);
|
||||
if (result.Paths.Count == 0)
|
||||
{
|
||||
if (t.Result.Paths.Count == 0)
|
||||
return null;
|
||||
return null;
|
||||
}
|
||||
|
||||
// If path index specified, return that specific one
|
||||
if (parts.Length >= 3 && int.TryParse(parts[2], out var idx) && idx < t.Result.Paths.Count)
|
||||
{
|
||||
return t.Result.Paths[idx];
|
||||
}
|
||||
// If path index specified, return that specific one
|
||||
if (parts.Length >= 3 &&
|
||||
int.TryParse(parts[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out var idx) &&
|
||||
idx >= 0 &&
|
||||
idx < result.Paths.Count)
|
||||
{
|
||||
return result.Paths[idx];
|
||||
}
|
||||
|
||||
return t.Result.Paths[0];
|
||||
}, cancellationToken);
|
||||
return result.Paths[0];
|
||||
}
|
||||
|
||||
private static Dictionary<string, List<RichGraphEdge>> BuildEdgeLookup(RichGraph graph)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Scanner.CallGraph;
|
||||
using StellaOps.Scanner.Explainability.Assumptions;
|
||||
@@ -11,6 +12,7 @@ using StellaOps.Scanner.Reachability.Binary;
|
||||
using StellaOps.Scanner.Reachability.Runtime;
|
||||
using StellaOps.Scanner.Reachability.Services;
|
||||
using StellaOps.Scanner.Reachability.Stack;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
// Aliases to disambiguate types with same name in different namespaces
|
||||
using StackEntrypointType = StellaOps.Scanner.Reachability.Stack.EntrypointType;
|
||||
@@ -33,6 +35,7 @@ public sealed class ReachabilityEvidenceJobExecutor : IReachabilityEvidenceJobEx
|
||||
private readonly IRuntimeReachabilityCollector? _runtimeCollector;
|
||||
private readonly ILogger<ReachabilityEvidenceJobExecutor> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public ReachabilityEvidenceJobExecutor(
|
||||
ICveSymbolMappingService cveSymbolService,
|
||||
@@ -42,6 +45,7 @@ public sealed class ReachabilityEvidenceJobExecutor : IReachabilityEvidenceJobEx
|
||||
ILogger<ReachabilityEvidenceJobExecutor> logger,
|
||||
IBinaryPatchVerifier? binaryPatchVerifier = null,
|
||||
IRuntimeReachabilityCollector? runtimeCollector = null,
|
||||
IGuidProvider? guidProvider = null,
|
||||
TimeProvider? timeProvider = null)
|
||||
{
|
||||
_cveSymbolService = cveSymbolService ?? throw new ArgumentNullException(nameof(cveSymbolService));
|
||||
@@ -51,6 +55,7 @@ public sealed class ReachabilityEvidenceJobExecutor : IReachabilityEvidenceJobEx
|
||||
_binaryPatchVerifier = binaryPatchVerifier;
|
||||
_runtimeCollector = runtimeCollector;
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_guidProvider = guidProvider ?? SystemGuidProvider.Instance;
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
}
|
||||
|
||||
@@ -409,7 +414,7 @@ public sealed class ReachabilityEvidenceJobExecutor : IReachabilityEvidenceJobEx
|
||||
// Create a minimal stack with Unknown verdict
|
||||
var unknownStack = new ReachabilityStack
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Id = _guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||
FindingId = $"{job.CveId}:{job.Purl}",
|
||||
Symbol = new StackVulnerableSymbol(
|
||||
Name: "unknown",
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -22,7 +23,7 @@ public sealed class ReachabilityUnionWriter
|
||||
|
||||
private static readonly JsonWriterOptions JsonOptions = new()
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Encoder = JavaScriptEncoder.Default,
|
||||
Indented = false,
|
||||
SkipValidation = false
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
@@ -94,7 +95,7 @@ public static class RichGraphSemanticExtensions
|
||||
return null;
|
||||
}
|
||||
|
||||
return double.TryParse(value, out var score) ? score : null;
|
||||
return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var score) ? score : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the confidence score.</summary>
|
||||
@@ -106,7 +107,7 @@ public static class RichGraphSemanticExtensions
|
||||
return null;
|
||||
}
|
||||
|
||||
return double.TryParse(value, out var score) ? score : null;
|
||||
return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var score) ? score : null;
|
||||
}
|
||||
|
||||
/// <summary>Checks if this node is an entrypoint.</summary>
|
||||
@@ -190,13 +191,13 @@ public sealed class RichGraphNodeSemanticBuilder
|
||||
|
||||
public RichGraphNodeSemanticBuilder WithRiskScore(double score)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.RiskScore] = score.ToString("F3");
|
||||
_attributes[RichGraphSemanticAttributes.RiskScore] = score.ToString("F3", CultureInfo.InvariantCulture);
|
||||
return this;
|
||||
}
|
||||
|
||||
public RichGraphNodeSemanticBuilder WithConfidence(double score, string tier)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.Confidence] = score.ToString("F3");
|
||||
_attributes[RichGraphSemanticAttributes.Confidence] = score.ToString("F3", CultureInfo.InvariantCulture);
|
||||
_attributes[RichGraphSemanticAttributes.ConfidenceTier] = tier;
|
||||
return this;
|
||||
}
|
||||
@@ -225,7 +226,7 @@ public sealed class RichGraphNodeSemanticBuilder
|
||||
|
||||
public RichGraphNodeSemanticBuilder WithCweId(int cweId)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.CweId] = cweId.ToString();
|
||||
_attributes[RichGraphSemanticAttributes.CweId] = cweId.ToString(CultureInfo.InvariantCulture);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -22,7 +23,7 @@ public sealed class RichGraphWriter
|
||||
|
||||
private static readonly JsonWriterOptions JsonOptions = new()
|
||||
{
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
|
||||
Encoder = JavaScriptEncoder.Default,
|
||||
Indented = false,
|
||||
SkipValidation = false
|
||||
};
|
||||
|
||||
@@ -11,6 +11,7 @@ public sealed class InMemorySliceCache : ISliceCache, IDisposable
|
||||
private readonly ConcurrentDictionary<string, CacheEntry> _cache = new();
|
||||
private readonly ILogger<InMemorySliceCache> _logger;
|
||||
private readonly TimeProvider _timeProvider;
|
||||
private readonly CancellationTokenSource _evictionCts = new();
|
||||
private readonly Timer _evictionTimer;
|
||||
private readonly SemaphoreSlim _evictionLock = new(1, 1);
|
||||
|
||||
@@ -26,7 +27,7 @@ public sealed class InMemorySliceCache : ISliceCache, IDisposable
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_timeProvider = timeProvider ?? TimeProvider.System;
|
||||
_evictionTimer = new Timer(
|
||||
_ => _ = EvictExpiredEntriesAsync(CancellationToken.None),
|
||||
_ => _ = EvictExpiredEntriesAsync(_evictionCts.Token),
|
||||
null,
|
||||
TimeSpan.FromSeconds(EvictionIntervalSeconds),
|
||||
TimeSpan.FromSeconds(EvictionIntervalSeconds));
|
||||
@@ -116,7 +117,19 @@ public sealed class InMemorySliceCache : ISliceCache, IDisposable
|
||||
|
||||
private async Task EvictExpiredEntriesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await _evictionLock.WaitAsync(0, cancellationToken).ConfigureAwait(false))
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!await _evictionLock.WaitAsync(0, cancellationToken).ConfigureAwait(false))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -199,8 +212,14 @@ public sealed class InMemorySliceCache : ISliceCache, IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_evictionCts.IsCancellationRequested)
|
||||
{
|
||||
_evictionCts.Cancel();
|
||||
}
|
||||
|
||||
_evictionTimer?.Dispose();
|
||||
_evictionLock?.Dispose();
|
||||
_evictionCts.Dispose();
|
||||
}
|
||||
|
||||
private sealed record CacheEntry(
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using StellaOps.Determinism;
|
||||
using StellaOps.Scanner.Explainability.Assumptions;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Stack;
|
||||
@@ -48,6 +50,18 @@ public interface IReachabilityStackEvaluator
|
||||
/// </remarks>
|
||||
public sealed class ReachabilityStackEvaluator : IReachabilityStackEvaluator
|
||||
{
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public ReachabilityStackEvaluator()
|
||||
: this(SystemGuidProvider.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public ReachabilityStackEvaluator(IGuidProvider guidProvider)
|
||||
{
|
||||
_guidProvider = guidProvider ?? throw new ArgumentNullException(nameof(guidProvider));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReachabilityStack Evaluate(
|
||||
string findingId,
|
||||
@@ -63,7 +77,7 @@ public sealed class ReachabilityStackEvaluator : IReachabilityStackEvaluator
|
||||
|
||||
return new ReachabilityStack
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
Id = _guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||
FindingId = findingId,
|
||||
Symbol = symbol,
|
||||
StaticCallGraph = layer1,
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.GraphRoot\StellaOps.Attestor.GraphRoot.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Replay.Core\StellaOps.Replay.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Canonical.Json\StellaOps.Canonical.Json.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Determinism.Abstractions\StellaOps.Determinism.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\..\..\Signals\__Libraries\StellaOps.Signals.Ebpf\StellaOps.Signals.Ebpf.csproj" />
|
||||
<ProjectReference Include="..\..\..\BinaryIndex\__Libraries\StellaOps.BinaryIndex.Ghidra\StellaOps.BinaryIndex.Ghidra.csproj" />
|
||||
<ProjectReference Include="..\..\..\BinaryIndex\__Libraries\StellaOps.BinaryIndex.Decompiler\StellaOps.BinaryIndex.Decompiler.csproj" />
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
|
||||
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Attestor;
|
||||
using StellaOps.Determinism;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability;
|
||||
|
||||
@@ -16,17 +18,29 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
private readonly IEntryPointResolver _entryPointResolver;
|
||||
private readonly IVulnSurfaceService _vulnSurfaceService;
|
||||
private readonly ILogger<SubgraphExtractor> _logger;
|
||||
private readonly IGuidProvider _guidProvider;
|
||||
|
||||
public SubgraphExtractor(
|
||||
IRichGraphStore graphStore,
|
||||
IEntryPointResolver entryPointResolver,
|
||||
IVulnSurfaceService vulnSurfaceService,
|
||||
ILogger<SubgraphExtractor> logger)
|
||||
: this(graphStore, entryPointResolver, vulnSurfaceService, logger, SystemGuidProvider.Instance)
|
||||
{
|
||||
}
|
||||
|
||||
public SubgraphExtractor(
|
||||
IRichGraphStore graphStore,
|
||||
IEntryPointResolver entryPointResolver,
|
||||
IVulnSurfaceService vulnSurfaceService,
|
||||
ILogger<SubgraphExtractor> logger,
|
||||
IGuidProvider guidProvider)
|
||||
{
|
||||
_graphStore = graphStore ?? throw new ArgumentNullException(nameof(graphStore));
|
||||
_entryPointResolver = entryPointResolver ?? throw new ArgumentNullException(nameof(entryPointResolver));
|
||||
_vulnSurfaceService = vulnSurfaceService ?? throw new ArgumentNullException(nameof(vulnSurfaceService));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_guidProvider = guidProvider ?? throw new ArgumentNullException(nameof(guidProvider));
|
||||
}
|
||||
|
||||
public async Task<PoESubgraph?> ResolveAsync(
|
||||
@@ -206,7 +220,7 @@ public class SubgraphExtractor : IReachabilityResolver
|
||||
if (sinkSet.Contains(current))
|
||||
{
|
||||
paths.Add(new CallPath(
|
||||
PathId: Guid.NewGuid().ToString(),
|
||||
PathId: _guidProvider.NewGuid().ToString("N", CultureInfo.InvariantCulture),
|
||||
Nodes: path.ToList(),
|
||||
Edges: ExtractEdgesFromPath(path, graph),
|
||||
Length: path.Count - 1,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Attestor.Envelope;
|
||||
using StellaOps.Canonical.Json;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Witnesses;
|
||||
|
||||
@@ -11,11 +13,12 @@ namespace StellaOps.Scanner.Reachability.Witnesses;
|
||||
public sealed class SuppressionDsseSigner : ISuppressionDsseSigner
|
||||
{
|
||||
private readonly EnvelopeSignatureService _signatureService;
|
||||
private static readonly JsonSerializerOptions CanonicalJsonOptions = new(JsonSerializerDefaults.Web)
|
||||
private static readonly JsonSerializerOptions CanonicalJsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.Default
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -44,13 +47,10 @@ public sealed class SuppressionDsseSigner : ISuppressionDsseSigner
|
||||
try
|
||||
{
|
||||
// Serialize witness to canonical JSON bytes
|
||||
var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(witness, CanonicalJsonOptions);
|
||||
var payloadBytes = CanonJson.Canonicalize(witness, CanonicalJsonOptions);
|
||||
|
||||
// Build the PAE (Pre-Authentication Encoding) for DSSE
|
||||
var pae = BuildPae(SuppressionWitnessSchema.DssePayloadType, payloadBytes);
|
||||
|
||||
// Sign the PAE
|
||||
var signResult = _signatureService.Sign(pae, signingKey, cancellationToken);
|
||||
// Sign DSSE payload using the shared PAE helper
|
||||
var signResult = _signatureService.SignDsse(SuppressionWitnessSchema.DssePayloadType, payloadBytes, signingKey, cancellationToken);
|
||||
if (!signResult.IsSuccess)
|
||||
{
|
||||
return SuppressionDsseResult.Failure($"Signing failed: {signResult.Error?.Message}");
|
||||
@@ -114,12 +114,10 @@ public sealed class SuppressionDsseSigner : ISuppressionDsseSigner
|
||||
return SuppressionVerifyResult.Failure($"No signature found for key ID: {publicKey.KeyId}");
|
||||
}
|
||||
|
||||
// Build PAE and verify signature
|
||||
var pae = BuildPae(envelope.PayloadType, envelope.Payload.ToArray());
|
||||
var signatureBytes = Convert.FromBase64String(matchingSignature.Signature);
|
||||
var envelopeSignature = new EnvelopeSignature(publicKey.KeyId, publicKey.AlgorithmId, signatureBytes);
|
||||
|
||||
var verifyResult = _signatureService.Verify(pae, envelopeSignature, publicKey, cancellationToken);
|
||||
var verifyResult = _signatureService.VerifyDsse(envelope.PayloadType, envelope.Payload.Span, envelopeSignature, publicKey, cancellationToken);
|
||||
if (!verifyResult.IsSuccess)
|
||||
{
|
||||
return SuppressionVerifyResult.Failure($"Signature verification failed: {verifyResult.Error?.Message}");
|
||||
@@ -133,43 +131,6 @@ public sealed class SuppressionDsseSigner : ISuppressionDsseSigner
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the DSSE Pre-Authentication Encoding (PAE) for a payload.
|
||||
/// PAE = "DSSEv1" SP len(type) SP type SP len(payload) SP payload
|
||||
/// </summary>
|
||||
private static byte[] BuildPae(string payloadType, byte[] payload)
|
||||
{
|
||||
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
|
||||
// Write "DSSEv1 "
|
||||
writer.Write(Encoding.UTF8.GetBytes("DSSEv1 "));
|
||||
|
||||
// Write len(type) as ASCII decimal string followed by space
|
||||
WriteLengthAndSpace(writer, typeBytes.Length);
|
||||
|
||||
// Write type followed by space
|
||||
writer.Write(typeBytes);
|
||||
writer.Write((byte)' ');
|
||||
|
||||
// Write len(payload) as ASCII decimal string followed by space
|
||||
WriteLengthAndSpace(writer, payload.Length);
|
||||
|
||||
// Write payload
|
||||
writer.Write(payload);
|
||||
|
||||
writer.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteLengthAndSpace(BinaryWriter writer, int length)
|
||||
{
|
||||
// Write length as ASCII decimal string
|
||||
writer.Write(Encoding.UTF8.GetBytes(length.ToString()));
|
||||
writer.Write((byte)' ');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using StellaOps.Attestor.Envelope;
|
||||
using StellaOps.Canonical.Json;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Witnesses;
|
||||
|
||||
@@ -11,11 +13,12 @@ namespace StellaOps.Scanner.Reachability.Witnesses;
|
||||
public sealed class WitnessDsseSigner : IWitnessDsseSigner
|
||||
{
|
||||
private readonly EnvelopeSignatureService _signatureService;
|
||||
private static readonly JsonSerializerOptions CanonicalJsonOptions = new(JsonSerializerDefaults.Web)
|
||||
private static readonly JsonSerializerOptions CanonicalJsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
|
||||
WriteIndented = false,
|
||||
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
Encoder = JavaScriptEncoder.Default
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -44,13 +47,10 @@ public sealed class WitnessDsseSigner : IWitnessDsseSigner
|
||||
try
|
||||
{
|
||||
// Serialize witness to canonical JSON bytes
|
||||
var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(witness, CanonicalJsonOptions);
|
||||
var payloadBytes = CanonJson.Canonicalize(witness, CanonicalJsonOptions);
|
||||
|
||||
// Build the PAE (Pre-Authentication Encoding) for DSSE
|
||||
var pae = BuildPae(WitnessSchema.DssePayloadType, payloadBytes);
|
||||
|
||||
// Sign the PAE
|
||||
var signResult = _signatureService.Sign(pae, signingKey, cancellationToken);
|
||||
// Sign DSSE payload using the shared PAE helper
|
||||
var signResult = _signatureService.SignDsse(WitnessSchema.DssePayloadType, payloadBytes, signingKey, cancellationToken);
|
||||
if (!signResult.IsSuccess)
|
||||
{
|
||||
return WitnessDsseResult.Failure($"Signing failed: {signResult.Error?.Message}");
|
||||
@@ -114,12 +114,10 @@ public sealed class WitnessDsseSigner : IWitnessDsseSigner
|
||||
return WitnessVerifyResult.Failure($"No signature found for key ID: {publicKey.KeyId}");
|
||||
}
|
||||
|
||||
// Build PAE and verify signature
|
||||
var pae = BuildPae(envelope.PayloadType, envelope.Payload.ToArray());
|
||||
var signatureBytes = Convert.FromBase64String(matchingSignature.Signature);
|
||||
var envelopeSignature = new EnvelopeSignature(publicKey.KeyId, publicKey.AlgorithmId, signatureBytes);
|
||||
|
||||
var verifyResult = _signatureService.Verify(pae, envelopeSignature, publicKey, cancellationToken);
|
||||
var verifyResult = _signatureService.VerifyDsse(envelope.PayloadType, envelope.Payload.Span, envelopeSignature, publicKey, cancellationToken);
|
||||
if (!verifyResult.IsSuccess)
|
||||
{
|
||||
return WitnessVerifyResult.Failure($"Signature verification failed: {verifyResult.Error?.Message}");
|
||||
@@ -133,43 +131,6 @@ public sealed class WitnessDsseSigner : IWitnessDsseSigner
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds the DSSE Pre-Authentication Encoding (PAE) for a payload.
|
||||
/// PAE = "DSSEv1" SP len(type) SP type SP len(payload) SP payload
|
||||
/// </summary>
|
||||
private static byte[] BuildPae(string payloadType, byte[] payload)
|
||||
{
|
||||
var typeBytes = Encoding.UTF8.GetBytes(payloadType);
|
||||
|
||||
using var stream = new MemoryStream();
|
||||
using var writer = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
|
||||
|
||||
// Write "DSSEv1 "
|
||||
writer.Write(Encoding.UTF8.GetBytes("DSSEv1 "));
|
||||
|
||||
// Write len(type) as little-endian 8-byte integer followed by space
|
||||
WriteLengthAndSpace(writer, typeBytes.Length);
|
||||
|
||||
// Write type followed by space
|
||||
writer.Write(typeBytes);
|
||||
writer.Write((byte)' ');
|
||||
|
||||
// Write len(payload) as little-endian 8-byte integer followed by space
|
||||
WriteLengthAndSpace(writer, payload.Length);
|
||||
|
||||
// Write payload
|
||||
writer.Write(payload);
|
||||
|
||||
writer.Flush();
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
private static void WriteLengthAndSpace(BinaryWriter writer, int length)
|
||||
{
|
||||
// Write length as ASCII decimal string
|
||||
writer.Write(Encoding.UTF8.GetBytes(length.ToString()));
|
||||
writer.Write((byte)' ');
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user