Gaps fill up, fixes, ui restructuring
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
|
||||
Reference in New Issue
Block a user