< Summary

Information
Class: AsiBackbone.Signing.ManagedKey.ManagedKeySigningService
Assembly: AsiBackbone.Signing.ManagedKey
File(s): /home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Signing.ManagedKey/ManagedKeySigningService.cs
Line coverage
100%
Covered lines: 202
Uncovered lines: 0
Coverable lines: 202
Total lines: 434
Line coverage: 100%
Branch coverage
84%
Covered branches: 79
Total branches: 94
Branch coverage: 84%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/AsiBackbone/AsiBackbone/src/AsiBackbone.Signing.ManagedKey/ManagedKeySigningService.cs

#LineLine coverage
 1using System.Globalization;
 2using System.Runtime.ExceptionServices;
 3using AsiBackbone.Core.Signing;
 4
 5namespace AsiBackbone.Signing.ManagedKey;
 6
 7/// <summary>
 8/// Provides managed-key signing for AsiBackbone signing abstractions through a host-owned managed-key client.
 9/// </summary>
 10/// <remarks>
 11/// This service signs precomputed hashes only. It never requests or handles raw private key material.
 12/// </remarks>
 13public sealed class ManagedKeySigningService : IAsiBackboneSigningService
 14{
 15    private const string ProviderKind = "managed-key";
 16
 17    private readonly ManagedKeySigningOptions options;
 18    private readonly IManagedKeySigningClient client;
 19    private readonly IManagedKeyRetryJitterSource jitterSource;
 20    private readonly IManagedKeyRetryDelay retryDelay;
 21
 22    /// <summary>
 23    /// Initializes a new instance of the <see cref="ManagedKeySigningService" /> class.
 24    /// </summary>
 25    public ManagedKeySigningService(ManagedKeySigningOptions options, IManagedKeySigningClient client)
 6626        : this(
 6627            options,
 6628            client,
 6629            SharedRandomManagedKeyRetryJitterSource.Instance,
 6630            SystemManagedKeyRetryDelay.Instance)
 31    {
 6632    }
 33
 7034    internal ManagedKeySigningService(
 7035        ManagedKeySigningOptions options,
 7036        IManagedKeySigningClient client,
 7037        IManagedKeyRetryJitterSource jitterSource,
 7038        IManagedKeyRetryDelay retryDelay)
 39    {
 7040        ArgumentNullException.ThrowIfNull(options);
 7041        ArgumentNullException.ThrowIfNull(client);
 7042        ArgumentNullException.ThrowIfNull(jitterSource);
 7043        ArgumentNullException.ThrowIfNull(retryDelay);
 44
 7045        options.Validate();
 7046        this.options = options;
 7047        this.client = client;
 7048        this.jitterSource = jitterSource;
 7049        this.retryDelay = retryDelay;
 7050    }
 51
 52    /// <inheritdoc />
 53    public async ValueTask<SigningResult> SignAsync(
 54        SigningRequest request,
 55        CancellationToken cancellationToken = default)
 56    {
 5457        ArgumentNullException.ThrowIfNull(request);
 5458        cancellationToken.ThrowIfCancellationRequested();
 59
 5460        ManagedKeySigningAttemptDiagnostics diagnostics = CreateAttemptDiagnostics();
 5461        string? validationFailure = ValidateSigningRequest(request);
 5462        if (validationFailure is not null)
 63        {
 1264            return HandleValidationFailure(request, validationFailure, diagnostics);
 65        }
 66
 4267        int attempt = 0;
 68
 69        while (true)
 70        {
 71            try
 72            {
 5873                ManagedKeySignResult managedResult = await client
 5874                    .SignAsync(CreateManagedKeyRequest(request), cancellationToken)
 5875                    .ConfigureAwait(false);
 76
 2477                return CreateSignedResult(request, managedResult, attempt, diagnostics);
 78            }
 2679            catch (ManagedKeySigningException exception) when (exception.IsRetryable && attempt < options.MaxRetryAttemp
 80            {
 1681                diagnostics.RecordRetry(exception);
 1682                attempt++;
 1683                await DelayForRetryAsync(attempt, diagnostics, cancellationToken).ConfigureAwait(false);
 1684            }
 1085            catch (ManagedKeySigningException exception)
 86            {
 1087                return HandleFailure(request, exception.FailureCode, exception.Message, attempt, diagnostics, exception)
 88            }
 289            catch (TimeoutException exception)
 90            {
 291                return HandleFailure(request, "managedkey.signing.provider-unavailable", exception.GetType().Name, attem
 92            }
 493            catch (InvalidOperationException exception)
 94            {
 495                return HandleFailure(request, "managedkey.signing.failed", exception.GetType().Name, attempt, diagnostic
 96            }
 297            catch (NotSupportedException exception)
 98            {
 299                return HandleFailure(request, "managedkey.signing.unsupported", exception.GetType().Name, attempt, diagn
 100            }
 101        }
 48102    }
 103
 104    private SigningResult HandleValidationFailure(
 105        SigningRequest request,
 106        string failureCode,
 107        ManagedKeySigningAttemptDiagnostics diagnostics)
 108    {
 12109        diagnostics.RecordValidationFailure();
 110
 12111        return !options.ReturnUnsignedOnFailure
 12112            ? throw new ManagedKeySigningException(failureCode, failureCode)
 12113            : CreateUnsignedFailureResult(request, failureCode, failureCode, retryAttempts: 0, diagnostics);
 114    }
 115
 116    private SigningResult HandleFailure(
 117        SigningRequest request,
 118        string failureCode,
 119        string failureMessage,
 120        int retryAttempts,
 121        ManagedKeySigningAttemptDiagnostics diagnostics,
 122        Exception exception)
 123    {
 18124        diagnostics.RecordFailure(exception);
 125
 18126        if (!options.ReturnUnsignedOnFailure)
 127        {
 4128            ExceptionDispatchInfo.Capture(exception).Throw();
 129        }
 130
 14131        return CreateUnsignedFailureResult(request, failureCode, failureMessage, retryAttempts, diagnostics);
 132    }
 133
 134    private ManagedKeySignRequest CreateManagedKeyRequest(SigningRequest request)
 135    {
 58136        return new ManagedKeySignRequest(
 58137            request.SigningHash,
 58138            NormalizeHashAlgorithm(request.HashAlgorithm),
 58139            NormalizeRequired(options.SignatureAlgorithm, ManagedKeySigningOptions.DefaultSignatureAlgorithm),
 58140            ResolveKeyId(request),
 58141            ResolveKeyVersion(request),
 58142            request.Purpose,
 58143            request.Metadata);
 144    }
 145
 146    private SigningResult CreateSignedResult(
 147        SigningRequest request,
 148        ManagedKeySignResult managedResult,
 149        int retryAttempts,
 150        ManagedKeySigningAttemptDiagnostics diagnostics)
 151    {
 24152        string resolvedKeyVersion = managedResult.KeyVersion ?? ResolveKeyVersion(request) ?? string.Empty;
 24153        Dictionary<string, string> metadata = CreateBaseMetadata(request, retryAttempts, providerAttempts: retryAttempts
 24154        metadata["signing_status"] = "signed";
 155
 24156        if (managedResult.ProviderOperationId is not null)
 157        {
 12158            metadata["provider_operation_id"] = managedResult.ProviderOperationId;
 159        }
 160
 72161        foreach (KeyValuePair<string, string> item in managedResult.Metadata)
 162        {
 12163            if (!IsSafeProviderMetadataKey(item.Key) || IsReservedDiagnosticMetadataKey(item.Key))
 164            {
 165                continue;
 166            }
 167
 12168            metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 169        }
 170
 24171        var signingMetadata = SigningMetadata.Create(
 24172            signingHash: request.SigningHash,
 24173            hashAlgorithm: NormalizeHashAlgorithm(request.HashAlgorithm),
 24174            signature: managedResult.Signature,
 24175            signatureAlgorithm: managedResult.SignatureAlgorithm,
 24176            keyId: managedResult.KeyId,
 24177            keyVersion: string.IsNullOrWhiteSpace(resolvedKeyVersion) ? null : resolvedKeyVersion,
 24178            provider: NormalizeRequired(options.ProviderName, ManagedKeySigningOptions.DefaultProviderName),
 24179            signedUtc: managedResult.SignedUtc,
 24180            metadata: metadata);
 181
 24182        return SigningResult.FromMetadata(signingMetadata);
 183    }
 184
 185    private SigningResult CreateUnsignedFailureResult(
 186        SigningRequest request,
 187        string failureCode,
 188        string failureMessage,
 189        int retryAttempts,
 190        ManagedKeySigningAttemptDiagnostics diagnostics)
 191    {
 24192        int providerAttempts = diagnostics.HasValidationFailure ? 0 : retryAttempts + 1;
 24193        Dictionary<string, string> metadata = CreateBaseMetadata(request, retryAttempts, providerAttempts, diagnostics);
 24194        metadata["signing_status"] = "failed";
 24195        metadata["failure_code"] = failureCode;
 24196        metadata["failure_message"] = failureMessage;
 197
 24198        var signingMetadata = SigningMetadata.Create(
 24199            signingHash: request.SigningHash,
 24200            hashAlgorithm: NormalizeHashAlgorithm(request.HashAlgorithm),
 24201            keyId: ResolveKeyId(request),
 24202            keyVersion: ResolveKeyVersion(request),
 24203            provider: NormalizeRequired(options.ProviderName, ManagedKeySigningOptions.DefaultProviderName),
 24204            metadata: metadata);
 205
 24206        return SigningResult.FromMetadata(signingMetadata);
 207    }
 208
 209    private Dictionary<string, string> CreateBaseMetadata(
 210        SigningRequest request,
 211        int retryAttempts,
 212        int providerAttempts,
 213        ManagedKeySigningAttemptDiagnostics diagnostics)
 214    {
 48215        Dictionary<string, string> metadata = new(StringComparer.Ordinal);
 216
 216217        foreach (KeyValuePair<string, string> item in request.Metadata)
 218        {
 60219            if (string.IsNullOrWhiteSpace(item.Key) || IsReservedDiagnosticMetadataKey(item.Key))
 220            {
 221                continue;
 222            }
 223
 10224            metadata[item.Key.Trim()] = item.Value?.Trim() ?? string.Empty;
 225        }
 226
 48227        metadata["provider_kind"] = ProviderKind;
 48228        metadata["remote_key_material"] = "true";
 48229        metadata["raw_private_key_loaded"] = "false";
 48230        metadata["retry_attempts"] = retryAttempts.ToString(CultureInfo.InvariantCulture);
 48231        metadata["provider_attempts"] = providerAttempts.ToString(CultureInfo.InvariantCulture);
 48232        metadata["max_retry_attempts"] = diagnostics.MaxRetryAttempts.ToString(CultureInfo.InvariantCulture);
 48233        metadata["retry_delay_milliseconds"] = diagnostics.BaseRetryDelay.TotalMilliseconds.ToString("0.###", CultureInf
 48234        metadata["max_retry_delay_milliseconds"] = diagnostics.MaxRetryDelay.TotalMilliseconds.ToString("0.###", Culture
 48235        metadata["last_retry_delay_milliseconds"] = diagnostics.LastRetryDelay.TotalMilliseconds.ToString("0.###", Cultu
 48236        metadata["total_retry_delay_milliseconds"] = diagnostics.TotalRetryDelay.TotalMilliseconds.ToString("0.###", Cul
 48237        metadata["retry_delay_count"] = diagnostics.RetryDelayCount.ToString(CultureInfo.InvariantCulture);
 48238        metadata["retry_backoff_strategy"] = ManagedKeyRetryBackoff.StrategyName;
 48239        metadata["retry_delay_configured"] = ToMetadataBoolean(diagnostics.BaseRetryDelay > TimeSpan.Zero);
 48240        metadata["retry_delay_applied"] = ToMetadataBoolean(diagnostics.RetryDelayCount > 0);
 48241        metadata["signature_algorithm"] = NormalizeRequired(options.SignatureAlgorithm, ManagedKeySigningOptions.Default
 242
 48243        if (diagnostics.LastRetryFailureCode is not null)
 244        {
 12245            metadata["last_retry_failure_code"] = diagnostics.LastRetryFailureCode;
 246        }
 247
 48248        if (diagnostics.LastRetryFailureExceptionType is not null)
 249        {
 12250            metadata["last_retry_failure_exception_type"] = diagnostics.LastRetryFailureExceptionType;
 251        }
 252
 48253        if (diagnostics.FailureExceptionType is not null)
 254        {
 14255            metadata["failure_exception_type"] = diagnostics.FailureExceptionType;
 256        }
 257
 48258        if (diagnostics.FailureRetryable is not null)
 259        {
 14260            metadata["failure_retryable"] = ToMetadataBoolean(diagnostics.FailureRetryable.Value);
 261        }
 262
 48263        if (request.Purpose is not null)
 264        {
 18265            metadata["purpose"] = request.Purpose;
 266        }
 267
 48268        return metadata;
 269    }
 270
 271    private string? ValidateSigningRequest(SigningRequest request)
 272    {
 54273        if (!IsSupportedHashAlgorithm(request.HashAlgorithm))
 274        {
 4275            return "managedkey.signing.hash-algorithm-unsupported";
 276        }
 277
 50278        string configuredKeyId = NormalizeRequired(options.KeyId, string.Empty);
 50279        if (request.KeyId is not null && !string.Equals(request.KeyId, configuredKeyId, StringComparison.Ordinal))
 280        {
 2281            return "managedkey.signing.key-mismatch";
 282        }
 283
 48284        string? configuredKeyVersion = NormalizeOptional(options.KeyVersion);
 48285        return options.RequireKeyVersion && request.KeyVersion is null && configuredKeyVersion is null
 48286            ? "managedkey.signing.key-version-missing"
 48287            : request.KeyVersion is not null
 48288            && configuredKeyVersion is not null
 48289            && !string.Equals(request.KeyVersion, configuredKeyVersion, StringComparison.Ordinal)
 48290            ? "managedkey.signing.key-version-mismatch"
 48291            : null;
 292    }
 293
 294    private string ResolveKeyId(SigningRequest request)
 295    {
 82296        return request.KeyId ?? NormalizeRequired(options.KeyId, string.Empty);
 297    }
 298
 299    private string? ResolveKeyVersion(SigningRequest request)
 300    {
 84301        return request.KeyVersion ?? NormalizeOptional(options.KeyVersion);
 302    }
 303
 304    private string NormalizeHashAlgorithm(string? hashAlgorithm)
 305    {
 214306        string normalized = string.IsNullOrWhiteSpace(hashAlgorithm)
 214307            ? options.HashAlgorithm
 214308            : hashAlgorithm.Trim();
 309
 214310        return normalized.Equals("SHA256", StringComparison.OrdinalIgnoreCase)
 214311            ? ManagedKeySigningOptions.DefaultHashAlgorithm
 214312            : normalized.Equals(ManagedKeySigningOptions.DefaultHashAlgorithm, StringComparison.OrdinalIgnoreCase)
 214313                ? ManagedKeySigningOptions.DefaultHashAlgorithm
 214314                : normalized;
 315    }
 316
 317    private bool IsSupportedHashAlgorithm(string? hashAlgorithm)
 318    {
 54319        return NormalizeHashAlgorithm(hashAlgorithm).Equals(
 54320            NormalizeHashAlgorithm(options.HashAlgorithm),
 54321            StringComparison.OrdinalIgnoreCase);
 322    }
 323
 324    private async ValueTask DelayForRetryAsync(
 325        int retryAttempt,
 326        ManagedKeySigningAttemptDiagnostics diagnostics,
 327        CancellationToken cancellationToken)
 328    {
 16329        TimeSpan delay = ManagedKeyRetryBackoff.CalculateDelay(
 16330            options.RetryDelay,
 16331            options.MaxRetryDelay,
 16332            retryAttempt,
 16333            jitterSource.NextDouble(),
 16334            diagnostics.LastRetryDelay);
 335
 16336        if (delay <= TimeSpan.Zero)
 337        {
 6338            return;
 339        }
 340
 10341        diagnostics.RecordRetryDelay(delay);
 10342        await retryDelay.DelayAsync(delay, cancellationToken).ConfigureAwait(false);
 16343    }
 344
 345    private ManagedKeySigningAttemptDiagnostics CreateAttemptDiagnostics()
 346    {
 54347        return new ManagedKeySigningAttemptDiagnostics(
 54348            options.MaxRetryAttempts,
 54349            options.RetryDelay,
 54350            options.MaxRetryDelay);
 351    }
 352
 353    private static bool IsSafeProviderMetadataKey(string key)
 354    {
 12355        return !string.IsNullOrWhiteSpace(key)
 12356            && !key.Contains("secret", StringComparison.OrdinalIgnoreCase)
 12357            && !key.Contains("token", StringComparison.OrdinalIgnoreCase)
 12358            && !key.Contains("credential", StringComparison.OrdinalIgnoreCase)
 12359            && !key.Contains("private", StringComparison.OrdinalIgnoreCase)
 12360            && !key.Contains("connection", StringComparison.OrdinalIgnoreCase);
 361    }
 362
 363    private static bool IsReservedDiagnosticMetadataKey(string key)
 364    {
 72365        return ManagedKeyDiagnosticMetadataKeyClassifier.IsReserved(key);
 366    }
 367
 368    private static string NormalizeRequired(string? value, string fallback)
 369    {
 204370        return string.IsNullOrWhiteSpace(value) ? fallback : value.Trim();
 371    }
 372
 373    private static string? NormalizeOptional(string? value)
 374    {
 56375        return string.IsNullOrWhiteSpace(value) ? null : value.Trim();
 376    }
 377
 378    private static string ToMetadataBoolean(bool value)
 379    {
 110380        return value ? "true" : "false";
 381    }
 382
 54383    private sealed class ManagedKeySigningAttemptDiagnostics(
 54384        int maxRetryAttempts,
 54385        TimeSpan baseRetryDelay,
 54386        TimeSpan maxRetryDelay)
 387    {
 102388        public int MaxRetryAttempts { get; } = maxRetryAttempts;
 389
 150390        public TimeSpan BaseRetryDelay { get; } = baseRetryDelay;
 391
 102392        public TimeSpan MaxRetryDelay { get; } = maxRetryDelay;
 393
 74394        public TimeSpan LastRetryDelay { get; private set; }
 395
 68396        public TimeSpan TotalRetryDelay { get; private set; }
 397
 116398        public int RetryDelayCount { get; private set; }
 399
 36400        public bool HasValidationFailure { get; private set; }
 401
 76402        public string? LastRetryFailureCode { get; private set; }
 403
 76404        public string? LastRetryFailureExceptionType { get; private set; }
 405
 80406        public string? FailureExceptionType { get; private set; }
 407
 80408        public bool? FailureRetryable { get; private set; }
 409
 410        public void RecordValidationFailure()
 411        {
 12412            HasValidationFailure = true;
 12413        }
 414
 415        public void RecordRetry(ManagedKeySigningException exception)
 416        {
 16417            LastRetryFailureCode = exception.FailureCode;
 16418            LastRetryFailureExceptionType = exception.GetType().Name;
 16419        }
 420
 421        public void RecordRetryDelay(TimeSpan delay)
 422        {
 10423            LastRetryDelay = delay;
 10424            TotalRetryDelay += delay;
 10425            RetryDelayCount++;
 10426        }
 427
 428        public void RecordFailure(Exception exception)
 429        {
 18430            FailureExceptionType = exception.GetType().Name;
 18431            FailureRetryable = exception is ManagedKeySigningException managedKeyException && managedKeyException.IsRetr
 18432        }
 433    }
 434}

Methods/Properties

.ctor(AsiBackbone.Signing.ManagedKey.ManagedKeySigningOptions,AsiBackbone.Signing.ManagedKey.IManagedKeySigningClient)
.ctor(AsiBackbone.Signing.ManagedKey.ManagedKeySigningOptions,AsiBackbone.Signing.ManagedKey.IManagedKeySigningClient,AsiBackbone.Signing.ManagedKey.IManagedKeyRetryJitterSource,AsiBackbone.Signing.ManagedKey.IManagedKeyRetryDelay)
SignAsync()
HandleValidationFailure(AsiBackbone.Core.Signing.SigningRequest,System.String,AsiBackbone.Signing.ManagedKey.ManagedKeySigningService/ManagedKeySigningAttemptDiagnostics)
HandleFailure(AsiBackbone.Core.Signing.SigningRequest,System.String,System.String,System.Int32,AsiBackbone.Signing.ManagedKey.ManagedKeySigningService/ManagedKeySigningAttemptDiagnostics,System.Exception)
CreateManagedKeyRequest(AsiBackbone.Core.Signing.SigningRequest)
CreateSignedResult(AsiBackbone.Core.Signing.SigningRequest,AsiBackbone.Signing.ManagedKey.ManagedKeySignResult,System.Int32,AsiBackbone.Signing.ManagedKey.ManagedKeySigningService/ManagedKeySigningAttemptDiagnostics)
CreateUnsignedFailureResult(AsiBackbone.Core.Signing.SigningRequest,System.String,System.String,System.Int32,AsiBackbone.Signing.ManagedKey.ManagedKeySigningService/ManagedKeySigningAttemptDiagnostics)
CreateBaseMetadata(AsiBackbone.Core.Signing.SigningRequest,System.Int32,System.Int32,AsiBackbone.Signing.ManagedKey.ManagedKeySigningService/ManagedKeySigningAttemptDiagnostics)
ValidateSigningRequest(AsiBackbone.Core.Signing.SigningRequest)
ResolveKeyId(AsiBackbone.Core.Signing.SigningRequest)
ResolveKeyVersion(AsiBackbone.Core.Signing.SigningRequest)
NormalizeHashAlgorithm(System.String)
IsSupportedHashAlgorithm(System.String)
DelayForRetryAsync()
CreateAttemptDiagnostics()
IsSafeProviderMetadataKey(System.String)
IsReservedDiagnosticMetadataKey(System.String)
NormalizeRequired(System.String,System.String)
NormalizeOptional(System.String)
ToMetadataBoolean(System.Boolean)
.ctor(System.Int32,System.TimeSpan,System.TimeSpan)
get_MaxRetryAttempts()
get_BaseRetryDelay()
get_MaxRetryDelay()
get_LastRetryDelay()
get_TotalRetryDelay()
get_RetryDelayCount()
get_HasValidationFailure()
get_LastRetryFailureCode()
get_LastRetryFailureExceptionType()
get_FailureExceptionType()
get_FailureRetryable()
RecordValidationFailure()
RecordRetry(AsiBackbone.Signing.ManagedKey.ManagedKeySigningException)
RecordRetryDelay(System.TimeSpan)
RecordFailure(System.Exception)