< Summary

Information
Class: AsiBackbone.Core.Signing.GovernanceArtifactSigner
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Signing/GovernanceArtifactSigner.cs
Line coverage
99%
Covered lines: 113
Uncovered lines: 1
Coverable lines: 114
Total lines: 386
Line coverage: 99.1%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

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

#LineLine coverage
 1using AsiBackbone.Core.Audit;
 2using AsiBackbone.Core.Emissions;
 3using AsiBackbone.Core.Outbox;
 4
 5namespace AsiBackbone.Core.Signing;
 6
 7/// <summary>
 8/// Provides provider-neutral helper methods for preparing and signing AsiBackbone governance artifacts.
 9/// </summary>
 10/// <remarks>
 11/// The helpers canonicalize and hash artifacts before optionally invoking <see cref="IGovernanceSigningService" />.
 12/// They do not verify signatures, persist records, provide immutable storage, or make tamper-evidence claims.
 13/// </remarks>
 14public static class GovernanceArtifactSigner
 15{
 16    /// <summary>
 17    /// Creates an unsigned wrapper for decision receipt.
 18    /// </summary>
 19    public static SignedGovernanceArtifact<IDecisionReceipt> CreateUnsignedDecisionReceipt(
 20        IDecisionReceipt receipt,
 21        CanonicalPayloadOptions? options = null,
 22        string? hashAlgorithm = null)
 23    {
 124        return CreateUnsigned(receipt, CanonicalPayloadBuilder.ForDecisionReceipt(receipt, options), hashAlgorithm);
 25    }
 26
 27    /// <summary>
 28    /// Creates signing-ready metadata for decision receipt without invoking a signing provider.
 29    /// </summary>
 30    public static SignedGovernanceArtifact<IDecisionReceipt> CreateSigningReadyDecisionReceipt(
 31        IDecisionReceipt receipt,
 32        CanonicalPayloadOptions? options = null,
 33        string? hashAlgorithm = null,
 34        IReadOnlyDictionary<string, string>? metadata = null)
 35    {
 136        return CreateSigningReady(receipt, CanonicalPayloadBuilder.ForDecisionReceipt(receipt, options), hashAlgorithm, 
 37    }
 38
 39    /// <summary>
 40    /// Signs decision receipt after canonical payload hashing.
 41    /// </summary>
 42    public static ValueTask<SignedGovernanceArtifact<IDecisionReceipt>> SignDecisionReceiptAsync(
 43        IDecisionReceipt receipt,
 44        IGovernanceSigningService signingService,
 45        CanonicalPayloadOptions? options = null,
 46        string? hashAlgorithm = null,
 47        string? keyId = null,
 48        string? keyVersion = null,
 49        IReadOnlyDictionary<string, string>? metadata = null,
 50        bool requireSignature = true,
 51        CancellationToken cancellationToken = default)
 52    {
 153        return SignAsync(
 154            receipt,
 155            CanonicalPayloadBuilder.ForDecisionReceipt(receipt, options),
 156            signingService,
 157            hashAlgorithm,
 158            keyId,
 159            keyVersion,
 160            metadata,
 161            requireSignature,
 162            cancellationToken);
 63    }
 64
 65    /// <summary>
 66    /// Creates an unsigned wrapper for a persistence-ready audit ledger record.
 67    /// </summary>
 68    public static SignedGovernanceArtifact<AuditLedgerRecord> CreateUnsignedAuditLedgerRecord(
 69        AuditLedgerRecord record,
 70        CanonicalPayloadOptions? options = null,
 71        string? hashAlgorithm = null)
 72    {
 073        return CreateUnsigned(record, CanonicalPayloadBuilder.ForAuditLedgerRecord(record, options), hashAlgorithm);
 74    }
 75
 76    /// <summary>
 77    /// Creates signing-ready metadata for a persistence-ready audit ledger record without invoking a signing provider.
 78    /// </summary>
 79    public static SignedGovernanceArtifact<AuditLedgerRecord> CreateSigningReadyAuditLedgerRecord(
 80        AuditLedgerRecord record,
 81        CanonicalPayloadOptions? options = null,
 82        string? hashAlgorithm = null,
 83        IReadOnlyDictionary<string, string>? metadata = null)
 84    {
 185        return CreateSigningReady(record, CanonicalPayloadBuilder.ForAuditLedgerRecord(record, options), hashAlgorithm, 
 86    }
 87
 88    /// <summary>
 89    /// Signs a persistence-ready audit ledger record after canonical payload hashing.
 90    /// </summary>
 91    public static ValueTask<SignedGovernanceArtifact<AuditLedgerRecord>> SignAuditLedgerRecordAsync(
 92        AuditLedgerRecord record,
 93        IGovernanceSigningService signingService,
 94        CanonicalPayloadOptions? options = null,
 95        string? hashAlgorithm = null,
 96        string? keyId = null,
 97        string? keyVersion = null,
 98        IReadOnlyDictionary<string, string>? metadata = null,
 99        bool requireSignature = true,
 100        CancellationToken cancellationToken = default)
 101    {
 11102        return SignAsync(
 11103            record,
 11104            CanonicalPayloadBuilder.ForAuditLedgerRecord(record, options),
 11105            signingService,
 11106            hashAlgorithm,
 11107            keyId,
 11108            keyVersion,
 11109            metadata,
 11110            requireSignature,
 11111            cancellationToken);
 112    }
 113
 114    /// <summary>
 115    /// Creates an unsigned wrapper for an decision receipt lifecycle event.
 116    /// </summary>
 117    public static SignedGovernanceArtifact<DecisionReceiptLifecycleEvent> CreateUnsignedDecisionReceiptLifecycleEvent(
 118        DecisionReceiptLifecycleEvent lifecycleEvent,
 119        CanonicalPayloadOptions? options = null,
 120        string? hashAlgorithm = null)
 121    {
 1122        return CreateUnsigned(lifecycleEvent, CanonicalPayloadBuilder.ForDecisionReceiptLifecycleEvent(lifecycleEvent, o
 123    }
 124
 125    /// <summary>
 126    /// Creates signing-ready metadata for an decision receipt lifecycle event without invoking a signing provider.
 127    /// </summary>
 128    public static SignedGovernanceArtifact<DecisionReceiptLifecycleEvent> CreateSigningReadyDecisionReceiptLifecycleEven
 129        DecisionReceiptLifecycleEvent lifecycleEvent,
 130        CanonicalPayloadOptions? options = null,
 131        string? hashAlgorithm = null,
 132        IReadOnlyDictionary<string, string>? metadata = null)
 133    {
 1134        return CreateSigningReady(lifecycleEvent, CanonicalPayloadBuilder.ForDecisionReceiptLifecycleEvent(lifecycleEven
 135    }
 136
 137    /// <summary>
 138    /// Signs an decision receipt lifecycle event after canonical payload hashing.
 139    /// </summary>
 140    public static ValueTask<SignedGovernanceArtifact<DecisionReceiptLifecycleEvent>> SignDecisionReceiptLifecycleEventAs
 141        DecisionReceiptLifecycleEvent lifecycleEvent,
 142        IGovernanceSigningService signingService,
 143        CanonicalPayloadOptions? options = null,
 144        string? hashAlgorithm = null,
 145        string? keyId = null,
 146        string? keyVersion = null,
 147        IReadOnlyDictionary<string, string>? metadata = null,
 148        bool requireSignature = true,
 149        CancellationToken cancellationToken = default)
 150    {
 1151        return SignAsync(
 1152            lifecycleEvent,
 1153            CanonicalPayloadBuilder.ForDecisionReceiptLifecycleEvent(lifecycleEvent, options),
 1154            signingService,
 1155            hashAlgorithm,
 1156            keyId,
 1157            keyVersion,
 1158            metadata,
 1159            requireSignature,
 1160            cancellationToken);
 161    }
 162
 163    /// <summary>
 164    /// Creates an unsigned wrapper for a governance emission envelope.
 165    /// </summary>
 166    public static SignedGovernanceArtifact<GovernanceEmissionEnvelope> CreateUnsignedGovernanceEmissionEnvelope(
 167        GovernanceEmissionEnvelope envelope,
 168        CanonicalPayloadOptions? options = null,
 169        string? hashAlgorithm = null)
 170    {
 1171        return CreateUnsigned(envelope, CanonicalPayloadBuilder.ForGovernanceEmissionEnvelope(envelope, options), hashAl
 172    }
 173
 174    /// <summary>
 175    /// Creates signing-ready metadata for a governance emission envelope without invoking a signing provider.
 176    /// </summary>
 177    public static SignedGovernanceArtifact<GovernanceEmissionEnvelope> CreateSigningReadyGovernanceEmissionEnvelope(
 178        GovernanceEmissionEnvelope envelope,
 179        CanonicalPayloadOptions? options = null,
 180        string? hashAlgorithm = null,
 181        IReadOnlyDictionary<string, string>? metadata = null)
 182    {
 1183        return CreateSigningReady(envelope, CanonicalPayloadBuilder.ForGovernanceEmissionEnvelope(envelope, options), ha
 184    }
 185
 186    /// <summary>
 187    /// Signs a governance emission envelope after canonical payload hashing.
 188    /// </summary>
 189    public static ValueTask<SignedGovernanceArtifact<GovernanceEmissionEnvelope>> SignGovernanceEmissionEnvelopeAsync(
 190        GovernanceEmissionEnvelope envelope,
 191        IGovernanceSigningService signingService,
 192        CanonicalPayloadOptions? options = null,
 193        string? hashAlgorithm = null,
 194        string? keyId = null,
 195        string? keyVersion = null,
 196        IReadOnlyDictionary<string, string>? metadata = null,
 197        bool requireSignature = true,
 198        CancellationToken cancellationToken = default)
 199    {
 1200        return SignAsync(
 1201            envelope,
 1202            CanonicalPayloadBuilder.ForGovernanceEmissionEnvelope(envelope, options),
 1203            signingService,
 1204            hashAlgorithm,
 1205            keyId,
 1206            keyVersion,
 1207            metadata,
 1208            requireSignature,
 1209            cancellationToken);
 210    }
 211
 212    /// <summary>
 213    /// Creates an unsigned wrapper for an outbox entry.
 214    /// </summary>
 215    public static SignedGovernanceArtifact<GovernanceOutboxEntry> CreateUnsignedGovernanceOutboxEntry(
 216        GovernanceOutboxEntry entry,
 217        CanonicalPayloadOptions? options = null,
 218        string? hashAlgorithm = null)
 219    {
 1220        return CreateUnsigned(entry, CanonicalPayloadBuilder.ForGovernanceOutboxEntry(entry, options), hashAlgorithm);
 221    }
 222
 223    /// <summary>
 224    /// Creates signing-ready metadata for an outbox entry without invoking a signing provider.
 225    /// </summary>
 226    public static SignedGovernanceArtifact<GovernanceOutboxEntry> CreateSigningReadyGovernanceOutboxEntry(
 227        GovernanceOutboxEntry entry,
 228        CanonicalPayloadOptions? options = null,
 229        string? hashAlgorithm = null,
 230        IReadOnlyDictionary<string, string>? metadata = null)
 231    {
 1232        return CreateSigningReady(entry, CanonicalPayloadBuilder.ForGovernanceOutboxEntry(entry, options), hashAlgorithm
 233    }
 234
 235    /// <summary>
 236    /// Signs an outbox entry after canonical payload hashing.
 237    /// </summary>
 238    public static ValueTask<SignedGovernanceArtifact<GovernanceOutboxEntry>> SignGovernanceOutboxEntryAsync(
 239        GovernanceOutboxEntry entry,
 240        IGovernanceSigningService signingService,
 241        CanonicalPayloadOptions? options = null,
 242        string? hashAlgorithm = null,
 243        string? keyId = null,
 244        string? keyVersion = null,
 245        IReadOnlyDictionary<string, string>? metadata = null,
 246        bool requireSignature = true,
 247        CancellationToken cancellationToken = default)
 248    {
 1249        return SignAsync(
 1250            entry,
 1251            CanonicalPayloadBuilder.ForGovernanceOutboxEntry(entry, options),
 1252            signingService,
 1253            hashAlgorithm,
 1254            keyId,
 1255            keyVersion,
 1256            metadata,
 1257            requireSignature,
 1258            cancellationToken);
 259    }
 260
 261    /// <summary>
 262    /// Creates a signing request from canonical payload hash metadata.
 263    /// </summary>
 264    public static SigningRequest CreateSigningRequest(
 265        CanonicalPayloadHash canonicalHash,
 266        string? keyId = null,
 267        string? keyVersion = null,
 268        IReadOnlyDictionary<string, string>? metadata = null)
 269    {
 19270        ArgumentNullException.ThrowIfNull(canonicalHash);
 271
 19272        var signingReadyMetadata = canonicalHash.ToSigningMetadata(metadata);
 273
 19274        return new SigningRequest(
 19275            canonicalHash.HashValue,
 19276            canonicalHash.HashAlgorithm,
 19277            purpose: canonicalHash.ArtifactType,
 19278            keyId: keyId,
 19279            keyVersion: keyVersion,
 19280            metadata: signingReadyMetadata.Metadata)
 19281        {
 19282            SignatureInput = GovernanceSignatureInput.CreateV1(canonicalHash, signingReadyMetadata.Metadata)
 19283        };
 284    }
 285
 286    private static SignedGovernanceArtifact<TArtifact> CreateUnsigned<TArtifact>(
 287        TArtifact artifact,
 288        CanonicalPayload payload,
 289        string? hashAlgorithm)
 290    {
 4291        return SignedGovernanceArtifacts.WithoutSignature(
 4292            artifact,
 4293            payload,
 4294            CanonicalPayloadHasher.ComputeHash(payload, hashAlgorithm));
 295    }
 296
 297    private static SignedGovernanceArtifact<TArtifact> CreateSigningReady<TArtifact>(
 298        TArtifact artifact,
 299        CanonicalPayload payload,
 300        string? hashAlgorithm,
 301        IReadOnlyDictionary<string, string>? metadata)
 302    {
 5303        return SignedGovernanceArtifacts.SigningReady(
 5304            artifact,
 5305            payload,
 5306            CanonicalPayloadHasher.ComputeHash(payload, hashAlgorithm),
 5307            metadata);
 308    }
 309
 310    private static async ValueTask<SignedGovernanceArtifact<TArtifact>> SignAsync<TArtifact>(
 311        TArtifact artifact,
 312        CanonicalPayload payload,
 313        IGovernanceSigningService signingService,
 314        string? hashAlgorithm,
 315        string? keyId,
 316        string? keyVersion,
 317        IReadOnlyDictionary<string, string>? metadata,
 318        bool requireSignature,
 319        CancellationToken cancellationToken)
 320    {
 15321        ArgumentNullException.ThrowIfNull(signingService);
 15322        cancellationToken.ThrowIfCancellationRequested();
 323
 15324        CanonicalPayloadHash hash = CanonicalPayloadHasher.ComputeHash(payload, hashAlgorithm);
 15325        SigningRequest signingRequest = CreateSigningRequest(hash, keyId, keyVersion, metadata);
 15326        SigningResult signingResult = await signingService
 15327            .SignAsync(signingRequest, cancellationToken)
 15328            .ConfigureAwait(false);
 329
 330        // A provider returning a failure or no-signature result previously produced an artifact with IsSigned false and
 331        // no exception, so a caller that asked to sign could carry on holding an unsigned artifact. Callers that want t
 332        // unsigned result back pass requireSignature: false and inspect IsSigned themselves.
 15333        return requireSignature && !signingResult.Metadata.IsSigned
 15334            ? throw new InvalidOperationException(
 15335                $"The signing provider returned no signature for artifact type '{payload.ArtifactType}'. Pass requireSig
 15336            : SignedGovernanceArtifacts.FromSigningMetadata(
 15337                artifact,
 15338                payload,
 15339                hash,
 15340                BindSignedPolicyContext(signingResult.Metadata, signingRequest.Metadata));
 14341    }
 342
 343    /// <summary>
 344    /// Restores the signing policy context that was bound into the signature input.
 345    /// </summary>
 346    /// <remarks>
 347    /// The version 1 signature input binds the policy version and policy hash the signer was asked to sign. A provider 
 348    /// dropped, altered, or added either key in its returned metadata would otherwise produce an artifact whose recorde
 349    /// policy context no longer rebuilds the signed input: it would fail verification, or carry a label that differs fr
 350    /// what was signed.
 351    /// </remarks>
 352    private static SigningMetadata BindSignedPolicyContext(
 353        SigningMetadata providerMetadata,
 354        IReadOnlyDictionary<string, string> requestMetadata)
 355    {
 14356        Dictionary<string, string> metadata = new(providerMetadata.Metadata, StringComparer.Ordinal);
 14357        CopyBoundValue(requestMetadata, metadata, GovernanceSignatureInput.PolicyVersionMetadataKey);
 14358        CopyBoundValue(requestMetadata, metadata, GovernanceSignatureInput.PolicyHashMetadataKey);
 359
 14360        return SigningMetadata.Create(
 14361            signingHash: providerMetadata.SigningHash,
 14362            hashAlgorithm: providerMetadata.HashAlgorithm,
 14363            signature: providerMetadata.Signature,
 14364            signatureAlgorithm: providerMetadata.SignatureAlgorithm,
 14365            keyId: providerMetadata.KeyId,
 14366            keyVersion: providerMetadata.KeyVersion,
 14367            provider: providerMetadata.Provider,
 14368            signedUtc: providerMetadata.SignedUtc,
 14369            metadata: metadata);
 370    }
 371
 372    private static void CopyBoundValue(
 373        IReadOnlyDictionary<string, string> source,
 374        Dictionary<string, string> target,
 375        string key)
 376    {
 28377        if (source.TryGetValue(key, out string? value))
 378        {
 9379            target[key] = value;
 380        }
 381        else
 382        {
 19383            _ = target.Remove(key);
 384        }
 19385    }
 386}

Methods/Properties

CreateUnsignedDecisionReceipt(AsiBackbone.Core.Audit.IDecisionReceipt,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String)
CreateSigningReadyDecisionReceipt(AsiBackbone.Core.Audit.IDecisionReceipt,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
SignDecisionReceiptAsync(AsiBackbone.Core.Audit.IDecisionReceipt,AsiBackbone.Core.Signing.IGovernanceSigningService,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Boolean,System.Threading.CancellationToken)
CreateUnsignedAuditLedgerRecord(AsiBackbone.Core.Audit.AuditLedgerRecord,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String)
CreateSigningReadyAuditLedgerRecord(AsiBackbone.Core.Audit.AuditLedgerRecord,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
SignAuditLedgerRecordAsync(AsiBackbone.Core.Audit.AuditLedgerRecord,AsiBackbone.Core.Signing.IGovernanceSigningService,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Boolean,System.Threading.CancellationToken)
CreateUnsignedDecisionReceiptLifecycleEvent(AsiBackbone.Core.Audit.DecisionReceiptLifecycleEvent,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String)
CreateSigningReadyDecisionReceiptLifecycleEvent(AsiBackbone.Core.Audit.DecisionReceiptLifecycleEvent,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
SignDecisionReceiptLifecycleEventAsync(AsiBackbone.Core.Audit.DecisionReceiptLifecycleEvent,AsiBackbone.Core.Signing.IGovernanceSigningService,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Boolean,System.Threading.CancellationToken)
CreateUnsignedGovernanceEmissionEnvelope(AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String)
CreateSigningReadyGovernanceEmissionEnvelope(AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
SignGovernanceEmissionEnvelopeAsync(AsiBackbone.Core.Emissions.GovernanceEmissionEnvelope,AsiBackbone.Core.Signing.IGovernanceSigningService,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Boolean,System.Threading.CancellationToken)
CreateUnsignedGovernanceOutboxEntry(AsiBackbone.Core.Outbox.GovernanceOutboxEntry,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String)
CreateSigningReadyGovernanceOutboxEntry(AsiBackbone.Core.Outbox.GovernanceOutboxEntry,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
SignGovernanceOutboxEntryAsync(AsiBackbone.Core.Outbox.GovernanceOutboxEntry,AsiBackbone.Core.Signing.IGovernanceSigningService,AsiBackbone.Core.Signing.CanonicalPayloadOptions,System.String,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Boolean,System.Threading.CancellationToken)
CreateSigningRequest(AsiBackbone.Core.Signing.CanonicalPayloadHash,System.String,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
CreateUnsigned(TArtifact,AsiBackbone.Core.Signing.CanonicalPayload,System.String)
CreateSigningReady(TArtifact,AsiBackbone.Core.Signing.CanonicalPayload,System.String,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
SignAsync()
BindSignedPolicyContext(AsiBackbone.Core.Signing.SigningMetadata,System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>)
CopyBoundValue(System.Collections.Generic.IReadOnlyDictionary`2<System.String,System.String>,System.Collections.Generic.Dictionary`2<System.String,System.String>,System.String)