Gaps fill up, fixes, ui restructuring

This commit is contained in:
master
2026-02-19 22:10:54 +02:00
parent b5829dce5c
commit 04cacdca8a
331 changed files with 42859 additions and 2174 deletions

View File

@@ -113,6 +113,12 @@ public sealed class CycloneDxComposer
var jsonHash = ComputeSha256(jsonBytes);
var protobufHash = ComputeSha256(protobufBytes);
// Compute canonical_id: SHA-256 of RFC 8785 (JCS) canonicalized JSON.
// Stable across serializers and machines. See docs/contracts/canonical-sbom-id-v1.md.
// Sprint: SPRINT_20260219_009 (CID-02)
var canonicalBytes = CanonicalizJson(jsonBytes);
var canonicalId = ComputeSha256(canonicalBytes);
var merkleRoot = request.AdditionalProperties is not null
&& request.AdditionalProperties.TryGetValue("stellaops:merkle.root", out var root)
? root
@@ -132,6 +138,7 @@ public sealed class CycloneDxComposer
JsonBytes = jsonBytes,
JsonSha256 = jsonHash,
ContentHash = jsonHash,
CanonicalId = canonicalId,
MerkleRoot = merkleRoot,
CompositionUri = compositionUri,
CompositionRecipeUri = compositionRecipeUri,
@@ -246,6 +253,10 @@ public sealed class CycloneDxComposer
Value = view.ToString().ToLowerInvariant(),
});
// canonical_id is emitted post-composition (added to the artifact after BuildMetadata returns).
// The property is injected via the composition pipeline that has access to the final canonical hash.
// See CycloneDxComposer.Compose() → inventoryArtifact/usageArtifact post-processing.
return metadata;
}
@@ -680,4 +691,50 @@ public sealed class CycloneDxComposer
var hash = sha256.ComputeHash(bytes);
return Convert.ToHexString(hash).ToLowerInvariant();
}
/// <summary>
/// Canonicalizes JSON per RFC 8785 (JSON Canonicalization Scheme):
/// sorted object keys (lexicographic/ordinal), no whitespace, no BOM.
/// Sprint: SPRINT_20260219_009 (CID-02)
/// </summary>
private static byte[] CanonicalizJson(byte[] jsonBytes)
{
using var doc = JsonDocument.Parse(jsonBytes);
using var stream = new MemoryStream();
using var writer = new Utf8JsonWriter(stream, new JsonWriterOptions
{
Indented = false,
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
});
WriteElementSorted(doc.RootElement, writer);
writer.Flush();
return stream.ToArray();
}
private static void WriteElementSorted(JsonElement element, Utf8JsonWriter writer)
{
switch (element.ValueKind)
{
case JsonValueKind.Object:
writer.WriteStartObject();
foreach (var property in element.EnumerateObject().OrderBy(p => p.Name, StringComparer.Ordinal))
{
writer.WritePropertyName(property.Name);
WriteElementSorted(property.Value, writer);
}
writer.WriteEndObject();
break;
case JsonValueKind.Array:
writer.WriteStartArray();
foreach (var item in element.EnumerateArray())
{
WriteElementSorted(item, writer);
}
writer.WriteEndArray();
break;
default:
element.WriteTo(writer);
break;
}
}
}

View File

@@ -20,10 +20,19 @@ public sealed record CycloneDxArtifact
public required string JsonSha256 { get; init; }
/// <summary>
/// Canonical content hash (sha256, hex) of the CycloneDX JSON payload.
/// Content hash (sha256, hex) of the serialized CycloneDX JSON payload.
/// Depends on serializer key ordering and whitespace; use for integrity checks of a specific serialized form.
/// </summary>
public required string ContentHash { get; init; }
/// <summary>
/// Canonical content identifier: sha256 of RFC 8785 (JCS) canonicalized CycloneDX JSON.
/// Stable across serializers, machines, and .NET versions. Use for cross-module evidence threading.
/// Format: lowercase hex (no "sha256:" prefix). See docs/contracts/canonical-sbom-id-v1.md.
/// Sprint: SPRINT_20260219_009 (CID-02)
/// </summary>
public required string CanonicalId { get; init; }
/// <summary>
/// Merkle root over fragments (hex). Present when composition metadata is provided.
/// </summary>
@@ -59,10 +68,16 @@ public sealed record SpdxArtifact
public required string JsonSha256 { get; init; }
/// <summary>
/// Canonical content hash (sha256, hex) of the SPDX JSON-LD payload.
/// Content hash (sha256, hex) of the serialized SPDX JSON-LD payload.
/// </summary>
public required string ContentHash { get; init; }
/// <summary>
/// Canonical content identifier: sha256 of RFC 8785 (JCS) canonicalized SPDX JSON.
/// Sprint: SPRINT_20260219_009 (CID-02)
/// </summary>
public string? CanonicalId { get; init; }
public required string JsonMediaType { get; init; }
public byte[]? TagValueBytes { get; init; }