< Summary

Information
Class: AsiBackbone.Core.Signing.SignedGovernanceArtifacts
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Signing/SignedGovernanceArtifact.cs
Line coverage
97%
Covered lines: 36
Uncovered lines: 1
Coverable lines: 37
Total lines: 190
Line coverage: 97.2%
Branch coverage
75%
Covered branches: 12
Total branches: 16
Branch coverage: 75%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
WithoutSignature(...)100%11100%
SigningReady(...)100%11100%
FromSigningMetadata(...)100%11100%
MergeCanonicalHashMetadata(...)75%161695.23%

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Signing/SignedGovernanceArtifact.cs

#LineLine coverage
 1namespace AsiBackbone.Core.Signing;
 2
 3/// <summary>
 4/// Represents an AsiBackbone governance artifact together with its canonical payload, canonical hash,
 5/// and optional provider-neutral signing metadata.
 6/// </summary>
 7/// <typeparam name="TArtifact">The governance artifact type.</typeparam>
 8/// <remarks>
 9/// This type preserves the boundary between unsigned, signing-ready, and signed artifacts. A signed artifact
 10/// has provider metadata attached, but verification, immutable storage, hash chaining, and external anchoring
 11/// remain separate host or provider responsibilities.
 12/// </remarks>
 13public sealed class SignedGovernanceArtifact<TArtifact>
 14{
 15    internal SignedGovernanceArtifact(
 16        TArtifact artifact,
 17        CanonicalPayload canonicalPayload,
 18        CanonicalPayloadHash canonicalHash,
 19        SigningMetadata signingMetadata)
 20    {
 21        ArgumentNullException.ThrowIfNull(artifact);
 22        ArgumentNullException.ThrowIfNull(canonicalPayload);
 23        ArgumentNullException.ThrowIfNull(canonicalHash);
 24        ArgumentNullException.ThrowIfNull(signingMetadata);
 25
 26        if (!string.Equals(canonicalPayload.ArtifactType, canonicalHash.ArtifactType, StringComparison.Ordinal)
 27            || !string.Equals(canonicalPayload.ArtifactId, canonicalHash.ArtifactId, StringComparison.Ordinal)
 28            || !string.Equals(canonicalPayload.PayloadSchemaVersion, canonicalHash.PayloadSchemaVersion, StringCompariso
 29            || !string.Equals(canonicalPayload.CanonicalizationVersion, canonicalHash.CanonicalizationVersion, StringCom
 30        {
 31            throw new ArgumentException("The canonical payload and canonical hash must describe the same governance arti
 32        }
 33
 34        Artifact = artifact;
 35        CanonicalPayload = canonicalPayload;
 36        CanonicalHash = canonicalHash;
 37        SigningMetadata = signingMetadata;
 38    }
 39
 40    /// <summary>
 41    /// Gets the original governance artifact.
 42    /// </summary>
 43    public TArtifact Artifact { get; }
 44
 45    /// <summary>
 46    /// Gets the deterministic canonical payload used for hashing and signing.
 47    /// </summary>
 48    public CanonicalPayload CanonicalPayload { get; }
 49
 50    /// <summary>
 51    /// Gets the canonical payload hash metadata.
 52    /// </summary>
 53    public CanonicalPayloadHash CanonicalHash { get; }
 54
 55    /// <summary>
 56    /// Gets provider-neutral signing metadata. This may be empty, signing-ready, failed, or signed metadata.
 57    /// </summary>
 58    public SigningMetadata SigningMetadata { get; }
 59
 60    /// <summary>
 61    /// Gets the stable artifact type bound into the canonical payload and signing metadata.
 62    /// </summary>
 63    public string ArtifactType => CanonicalHash.ArtifactType;
 64
 65    /// <summary>
 66    /// Gets the stable artifact identifier bound into the canonical payload and signing metadata.
 67    /// </summary>
 68    public string ArtifactId => CanonicalHash.ArtifactId;
 69
 70    /// <summary>
 71    /// Gets the hash algorithm used to compute <see cref="SigningHash" />.
 72    /// </summary>
 73    public string HashAlgorithm => CanonicalHash.HashAlgorithm;
 74
 75    /// <summary>
 76    /// Gets the canonical payload hash value that is signed or made signing-ready.
 77    /// </summary>
 78    public string SigningHash => CanonicalHash.HashValue;
 79
 80    /// <summary>
 81    /// Gets a value indicating whether provider signing metadata includes a signature.
 82    /// </summary>
 83    public bool IsSigned => SigningMetadata.IsSigned;
 84
 85    /// <summary>
 86    /// Gets a value indicating whether hash metadata is present but no signature has been attached.
 87    /// </summary>
 88    public bool IsSigningReady => !SigningMetadata.HasSignature && SigningMetadata.SigningHash is not null;
 89
 90    /// <summary>
 91    /// Gets a value indicating whether the artifact carries no signing metadata.
 92    /// </summary>
 93    public bool HasNoSignature => !SigningMetadata.HasSignature && SigningMetadata.SigningHash is null;
 94}
 95
 96/// <summary>
 97/// Provides non-generic factories for creating signed governance artifact wrappers.
 98/// </summary>
 99public static class SignedGovernanceArtifacts
 100{
 101    /// <summary>
 102    /// Creates an artifact wrapper with no signing metadata attached.
 103    /// </summary>
 104    public static SignedGovernanceArtifact<TArtifact> WithoutSignature<TArtifact>(
 105        TArtifact artifact,
 106        CanonicalPayload canonicalPayload,
 107        CanonicalPayloadHash canonicalHash)
 108    {
 5109        return new SignedGovernanceArtifact<TArtifact>(
 5110            artifact,
 5111            canonicalPayload,
 5112            canonicalHash,
 5113            SigningMetadata.NoSignature);
 114    }
 115
 116    /// <summary>
 117    /// Creates a signing-ready artifact wrapper. Hash metadata is attached without implying that a signature exists.
 118    /// </summary>
 119    public static SignedGovernanceArtifact<TArtifact> SigningReady<TArtifact>(
 120        TArtifact artifact,
 121        CanonicalPayload canonicalPayload,
 122        CanonicalPayloadHash canonicalHash,
 123        IReadOnlyDictionary<string, string>? metadata = null)
 124    {
 2125        return new SignedGovernanceArtifact<TArtifact>(
 2126            artifact,
 2127            canonicalPayload,
 2128            canonicalHash,
 2129            canonicalHash.ToSigningMetadata(metadata));
 130    }
 131
 132    /// <summary>
 133    /// Creates an artifact wrapper from signing metadata returned by a host or provider package.
 134    /// </summary>
 135    public static SignedGovernanceArtifact<TArtifact> FromSigningMetadata<TArtifact>(
 136        TArtifact artifact,
 137        CanonicalPayload canonicalPayload,
 138        CanonicalPayloadHash canonicalHash,
 139        SigningMetadata signingMetadata)
 140    {
 55141        ArgumentNullException.ThrowIfNull(signingMetadata);
 142
 55143        return new SignedGovernanceArtifact<TArtifact>(
 55144            artifact,
 55145            canonicalPayload,
 55146            canonicalHash,
 55147            MergeCanonicalHashMetadata(canonicalHash, signingMetadata));
 148    }
 149
 150    private static SigningMetadata MergeCanonicalHashMetadata(
 151        CanonicalPayloadHash canonicalHash,
 152        SigningMetadata signingMetadata)
 153    {
 55154        if (signingMetadata.SigningHash is not null
 55155            && !string.Equals(signingMetadata.SigningHash, canonicalHash.HashValue, StringComparison.Ordinal))
 156        {
 0157            throw new ArgumentException("Signing metadata hash must match the canonical payload hash.", nameof(signingMe
 158        }
 159
 55160        Dictionary<string, string> metadata = new(StringComparer.Ordinal);
 161
 550162        foreach (KeyValuePair<string, string> item in canonicalHash.ToSigningMetadata().Metadata)
 163        {
 220164            metadata[item.Key] = item.Value;
 165        }
 166
 362167        foreach (KeyValuePair<string, string> item in signingMetadata.Metadata)
 168        {
 126169            if (string.IsNullOrWhiteSpace(item.Key))
 170            {
 171                continue;
 172            }
 173
 126174            metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 175        }
 176
 55177        return SigningMetadata.Create(
 55178            signingHash: canonicalHash.HashValue,
 55179            hashAlgorithm: string.IsNullOrWhiteSpace(signingMetadata.HashAlgorithm)
 55180                ? canonicalHash.HashAlgorithm
 55181                : signingMetadata.HashAlgorithm,
 55182            signature: signingMetadata.Signature,
 55183            signatureAlgorithm: signingMetadata.SignatureAlgorithm,
 55184            keyId: signingMetadata.KeyId,
 55185            keyVersion: signingMetadata.KeyVersion,
 55186            provider: signingMetadata.Provider,
 55187            signedUtc: signingMetadata.SignedUtc,
 55188            metadata: metadata);
 189    }
 190}