< Summary

Information
Class: AsiBackbone.Core.Signing.VerificationPolicyOutcome
Assembly: AsiBackbone.Core
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Core/Signing/VerificationPolicyOutcome.cs
Line coverage
95%
Covered lines: 114
Uncovered lines: 5
Coverable lines: 119
Total lines: 276
Line coverage: 95.7%
Branch coverage
84%
Covered branches: 27
Total branches: 32
Branch coverage: 84.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%210%
.ctor(...)50%4495.12%
get_ShouldAllow()100%11100%
Create(...)100%11100%
CreateCore(...)100%22100%
BuildSafeMetadata(...)75%1212100%
AddIfPresent(...)100%22100%
IsSafeSigningMetadataKey(...)100%1010100%
NormalizeOptional(...)100%22100%

File(s)

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

#LineLine coverage
 1using System.Collections.ObjectModel;
 2
 3namespace AsiBackbone.Core.Signing;
 4
 5/// <summary>
 6/// Represents the host-facing outcome of signature verification policy evaluation.
 7/// </summary>
 8public sealed class VerificationPolicyOutcome
 9{
 010    private static readonly IReadOnlyDictionary<string, string> EmptyMetadata =
 011        new ReadOnlyDictionary<string, string>(
 012            new Dictionary<string, string>(StringComparer.Ordinal));
 13
 6414    private VerificationPolicyOutcome(
 6415        bool isVerified,
 6416        SignatureVerificationCategory category,
 6417        VerificationPolicyAction action,
 6418        string status,
 6419        string? failureCode,
 6420        string? failureMessage,
 6421        string artifactType,
 6422        string artifactId,
 6423        string signingHash,
 6424        string hashAlgorithm,
 6425        string? keyId,
 6426        string? keyVersion,
 6427        string? signatureAlgorithm,
 6428        string? provider,
 6429        IReadOnlyDictionary<string, string> safeMetadata)
 30    {
 6431        ArgumentException.ThrowIfNullOrWhiteSpace(status);
 6432        ArgumentException.ThrowIfNullOrWhiteSpace(artifactType);
 6433        ArgumentException.ThrowIfNullOrWhiteSpace(artifactId);
 6434        ArgumentException.ThrowIfNullOrWhiteSpace(signingHash);
 6435        ArgumentException.ThrowIfNullOrWhiteSpace(hashAlgorithm);
 36
 6437        if (!Enum.IsDefined(category))
 38        {
 039            throw new ArgumentOutOfRangeException(nameof(category), category, "Verification category must be defined.");
 40        }
 41
 6442        if (!Enum.IsDefined(action))
 43        {
 044            throw new ArgumentOutOfRangeException(nameof(action), action, "Verification policy action must be defined.")
 45        }
 46
 6447        IsVerified = isVerified;
 6448        Category = category;
 6449        Action = action;
 6450        Status = status.Trim();
 6451        FailureCode = NormalizeOptional(failureCode);
 6452        FailureMessage = NormalizeOptional(failureMessage);
 6453        ArtifactType = artifactType.Trim();
 6454        ArtifactId = artifactId.Trim();
 6455        SigningHash = signingHash.Trim();
 6456        HashAlgorithm = hashAlgorithm.Trim();
 6457        KeyId = NormalizeOptional(keyId);
 6458        KeyVersion = NormalizeOptional(keyVersion);
 6459        SignatureAlgorithm = NormalizeOptional(signatureAlgorithm);
 6460        Provider = NormalizeOptional(provider);
 6461        SafeMetadata = safeMetadata;
 6462    }
 63
 64    /// <summary>
 65    /// Gets a value indicating whether verification succeeded.
 66    /// </summary>
 67    public bool IsVerified { get; }
 68
 69    /// <summary>
 70    /// Gets the provider-neutral verification category.
 71    /// </summary>
 72    public SignatureVerificationCategory Category { get; }
 73
 74    /// <summary>
 75    /// Gets the host-facing action selected by verification policy.
 76    /// </summary>
 77    public VerificationPolicyAction Action { get; }
 78
 79    /// <summary>
 80    /// Gets a value indicating whether the policy selected the allow action.
 81    /// </summary>
 4582    public bool ShouldAllow => Action is VerificationPolicyAction.Allow;
 83
 84    /// <summary>
 85    /// Gets the provider-neutral verification status.
 86    /// </summary>
 87    public string Status { get; }
 88
 89    /// <summary>
 90    /// Gets the provider-neutral failure code, when verification did not succeed.
 91    /// </summary>
 92    public string? FailureCode { get; }
 93
 94    /// <summary>
 95    /// Gets the provider-neutral failure message, when verification did not succeed.
 96    /// </summary>
 97    public string? FailureMessage { get; }
 98
 99    /// <summary>
 100    /// Gets the artifact type that was verified or evaluated.
 101    /// </summary>
 102    public string ArtifactType { get; }
 103
 104    /// <summary>
 105    /// Gets the artifact identifier that was verified or evaluated.
 106    /// </summary>
 107    public string ArtifactId { get; }
 108
 109    /// <summary>
 110    /// Gets the signing hash expected for verification.
 111    /// </summary>
 112    public string SigningHash { get; }
 113
 114    /// <summary>
 115    /// Gets the hash algorithm expected for verification.
 116    /// </summary>
 117    public string HashAlgorithm { get; }
 118
 119    /// <summary>
 120    /// Gets the signing key identifier, when supplied.
 121    /// </summary>
 122    public string? KeyId { get; }
 123
 124    /// <summary>
 125    /// Gets the signing key version, when supplied.
 126    /// </summary>
 127    public string? KeyVersion { get; }
 128
 129    /// <summary>
 130    /// Gets the signature algorithm descriptor, when supplied.
 131    /// </summary>
 132    public string? SignatureAlgorithm { get; }
 133
 134    /// <summary>
 135    /// Gets the signing provider descriptor, when supplied.
 136    /// </summary>
 137    public string? Provider { get; }
 138
 139    /// <summary>
 140    /// Gets safe-to-log verification metadata. Signature values and secrets are never included.
 141    /// </summary>
 142    public IReadOnlyDictionary<string, string> SafeMetadata { get; }
 143
 144    /// <summary>
 145    /// Creates a verification policy outcome.
 146    /// </summary>
 147    public static VerificationPolicyOutcome Create<TArtifact>(
 148        SignedGovernanceArtifact<TArtifact> artifact,
 149        SignatureVerificationResult verificationResult,
 150        VerificationPolicyOptions? options = null)
 151    {
 2152        ArgumentNullException.ThrowIfNull(artifact);
 153
 2154        return CreateCore(
 2155            artifact.ArtifactType,
 2156            artifact.ArtifactId,
 2157            artifact.SigningHash,
 2158            artifact.HashAlgorithm,
 2159            artifact.SigningMetadata,
 2160            verificationResult,
 2161            options);
 162    }
 163
 164    internal static VerificationPolicyOutcome CreateCore(
 165        string artifactType,
 166        string artifactId,
 167        string signingHash,
 168        string hashAlgorithm,
 169        SigningMetadata signingMetadata,
 170        SignatureVerificationResult verificationResult,
 171        VerificationPolicyOptions? options = null)
 172    {
 64173        ArgumentNullException.ThrowIfNull(signingMetadata);
 64174        ArgumentNullException.ThrowIfNull(verificationResult);
 175
 64176        VerificationPolicyOptions effectiveOptions = options ?? VerificationPolicyOptions.Default;
 64177        SignatureVerificationCategory category = VerificationPolicyEvaluator.Categorize(verificationResult);
 64178        VerificationPolicyAction action = effectiveOptions.GetAction(category);
 64179        IReadOnlyDictionary<string, string> safeMetadata = BuildSafeMetadata(
 64180            artifactType,
 64181            artifactId,
 64182            signingHash,
 64183            hashAlgorithm,
 64184            signingMetadata,
 64185            verificationResult,
 64186            category,
 64187            action);
 188
 64189        return new VerificationPolicyOutcome(
 64190            verificationResult.IsValid,
 64191            category,
 64192            action,
 64193            verificationResult.Status,
 64194            verificationResult.FailureCode,
 64195            verificationResult.FailureMessage,
 64196            artifactType,
 64197            artifactId,
 64198            signingHash,
 64199            hashAlgorithm,
 64200            signingMetadata.KeyId,
 64201            signingMetadata.KeyVersion,
 64202            signingMetadata.SignatureAlgorithm,
 64203            signingMetadata.Provider,
 64204            safeMetadata);
 205    }
 206
 207    private static IReadOnlyDictionary<string, string> BuildSafeMetadata(
 208        string artifactType,
 209        string artifactId,
 210        string signingHash,
 211        string hashAlgorithm,
 212        SigningMetadata signingMetadata,
 213        SignatureVerificationResult verificationResult,
 214        SignatureVerificationCategory category,
 215        VerificationPolicyAction action)
 216    {
 64217        Dictionary<string, string> metadata = new(StringComparer.Ordinal)
 64218        {
 64219            ["artifact_id"] = artifactId,
 64220            ["artifact_type"] = artifactType,
 64221            ["category"] = category.ToString(),
 64222            ["hash_algorithm"] = hashAlgorithm,
 64223            ["policy_action"] = action.ToString(),
 64224            ["signing_hash"] = signingHash,
 64225            ["status"] = verificationResult.Status
 64226        };
 227
 64228        AddIfPresent(metadata, "failure_code", verificationResult.FailureCode);
 64229        AddIfPresent(metadata, "key_id", signingMetadata.KeyId);
 64230        AddIfPresent(metadata, "key_version", signingMetadata.KeyVersion);
 64231        AddIfPresent(metadata, "provider", signingMetadata.Provider);
 64232        AddIfPresent(metadata, "signature_algorithm", signingMetadata.SignatureAlgorithm);
 233
 64234        if (signingMetadata.SignedUtc.HasValue)
 235        {
 60236            metadata["signed_utc"] = signingMetadata.SignedUtc.Value.ToString("O", System.Globalization.CultureInfo.Inva
 237        }
 238
 796239        foreach (KeyValuePair<string, string> item in signingMetadata.Metadata)
 240        {
 334241            if (IsSafeSigningMetadataKey(item.Key))
 242            {
 329243                metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 244            }
 245        }
 246
 64247        return metadata.Count == 0
 64248            ? EmptyMetadata
 64249            : new ReadOnlyDictionary<string, string>(metadata);
 250    }
 251
 252    private static void AddIfPresent(Dictionary<string, string> metadata, string key, string? value)
 253    {
 320254        if (!string.IsNullOrWhiteSpace(value))
 255        {
 282256            metadata[key] = value.Trim();
 257        }
 320258    }
 259
 260    private static bool IsSafeSigningMetadataKey(string key)
 261    {
 334262        return !string.IsNullOrWhiteSpace(key)
 334263            && !key.Contains("signature", StringComparison.OrdinalIgnoreCase)
 334264            && !key.Contains("secret", StringComparison.OrdinalIgnoreCase)
 334265            && !key.Contains("token", StringComparison.OrdinalIgnoreCase)
 334266            && !key.Contains("credential", StringComparison.OrdinalIgnoreCase)
 334267            && !key.Contains("private", StringComparison.OrdinalIgnoreCase);
 268    }
 269
 270    private static string? NormalizeOptional(string? value)
 271    {
 384272        return string.IsNullOrWhiteSpace(value)
 384273            ? null
 384274            : value.Trim();
 275    }
 276}