Add support for ГОСТ Р 34.10 digital signatures
- Implemented the GostKeyValue class for handling public key parameters in ГОСТ Р 34.10 digital signatures. - Created the GostSignedXml class to manage XML signatures using ГОСТ 34.10, including methods for computing and checking signatures. - Developed the GostSignedXmlImpl class to encapsulate the signature computation logic and public key retrieval. - Added specific key value classes for ГОСТ Р 34.10-2001, ГОСТ Р 34.10-2012/256, and ГОСТ Р 34.10-2012/512 to support different signature algorithms. - Ensured compatibility with existing XML signature standards while integrating ГОСТ cryptography.
This commit is contained in:
@@ -1,22 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Excititor.Connectors.Abstractions;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Authentication;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Configuration;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Events;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Metadata;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.State;
|
||||
using StellaOps.Excititor.Core;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Excititor.Connectors.Abstractions;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Authentication;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Configuration;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Events;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.Metadata;
|
||||
using StellaOps.Excititor.Connectors.SUSE.RancherVEXHub.State;
|
||||
using StellaOps.Excititor.Core;
|
||||
using StellaOps.Excititor.Storage.Mongo;
|
||||
|
||||
namespace StellaOps.Excititor.Connectors.SUSE.RancherVEXHub;
|
||||
|
||||
@@ -88,12 +91,14 @@ public sealed class RancherHubConnector : VexConnectorBase
|
||||
throw new InvalidOperationException("Connector must be validated before fetch operations.");
|
||||
}
|
||||
|
||||
if (_metadata is null)
|
||||
{
|
||||
_metadata = await _metadataLoader.LoadAsync(_options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
var checkpoint = await _checkpointManager.LoadAsync(Descriptor.Id, context, cancellationToken).ConfigureAwait(false);
|
||||
if (_metadata is null)
|
||||
{
|
||||
_metadata = await _metadataLoader.LoadAsync(_options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await UpsertProviderAsync(context.Services, _metadata.Metadata.Provider, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var checkpoint = await _checkpointManager.LoadAsync(Descriptor.Id, context, cancellationToken).ConfigureAwait(false);
|
||||
var digestHistory = checkpoint.Digests.ToList();
|
||||
var dedupeSet = new HashSet<string>(checkpoint.Digests, StringComparer.OrdinalIgnoreCase);
|
||||
var latestCursor = checkpoint.Cursor;
|
||||
@@ -210,14 +215,19 @@ public sealed class RancherHubConnector : VexConnectorBase
|
||||
|
||||
var contentBytes = await response.Content.ReadAsByteArrayAsync(cancellationToken).ConfigureAwait(false);
|
||||
var publishedAt = record.PublishedAt ?? UtcNow();
|
||||
var metadata = BuildMetadata(builder => builder
|
||||
.Add("rancher.event.id", record.Id)
|
||||
.Add("rancher.event.type", record.Type)
|
||||
.Add("rancher.event.channel", record.Channel)
|
||||
.Add("rancher.event.published", publishedAt)
|
||||
.Add("rancher.event.cursor", batch.NextCursor ?? batch.Cursor)
|
||||
.Add("rancher.event.offline", batch.FromOfflineSnapshot ? "true" : "false")
|
||||
.Add("rancher.event.declaredDigest", record.DocumentDigest));
|
||||
var metadata = BuildMetadata(builder =>
|
||||
{
|
||||
builder
|
||||
.Add("rancher.event.id", record.Id)
|
||||
.Add("rancher.event.type", record.Type)
|
||||
.Add("rancher.event.channel", record.Channel)
|
||||
.Add("rancher.event.published", publishedAt)
|
||||
.Add("rancher.event.cursor", batch.NextCursor ?? batch.Cursor)
|
||||
.Add("rancher.event.offline", batch.FromOfflineSnapshot ? "true" : "false")
|
||||
.Add("rancher.event.declaredDigest", record.DocumentDigest);
|
||||
|
||||
AddProvenanceMetadata(builder);
|
||||
});
|
||||
|
||||
var format = ResolveFormat(record.DocumentFormat);
|
||||
var document = CreateRawDocument(format, record.DocumentUri, contentBytes, metadata);
|
||||
@@ -240,14 +250,48 @@ public sealed class RancherHubConnector : VexConnectorBase
|
||||
}
|
||||
|
||||
digestHistory.Add(document.Digest);
|
||||
await context.RawSink.StoreAsync(document, cancellationToken).ConfigureAwait(false);
|
||||
return new EventProcessingResult(document, false, publishedAt);
|
||||
}
|
||||
|
||||
private static bool TrimHistory(List<string> digestHistory)
|
||||
{
|
||||
if (digestHistory.Count <= MaxDigestHistory)
|
||||
{
|
||||
await context.RawSink.StoreAsync(document, cancellationToken).ConfigureAwait(false);
|
||||
return new EventProcessingResult(document, false, publishedAt);
|
||||
}
|
||||
|
||||
private void AddProvenanceMetadata(VexConnectorMetadataBuilder builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
var provider = _metadata?.Metadata.Provider;
|
||||
if (provider is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builder
|
||||
.Add("vex.provenance.provider", provider.Id)
|
||||
.Add("vex.provenance.providerName", provider.DisplayName)
|
||||
.Add("vex.provenance.providerKind", provider.Kind.ToString().ToLowerInvariant(CultureInfo.InvariantCulture))
|
||||
.Add("vex.provenance.trust.weight", provider.Trust.Weight.ToString("0.###", CultureInfo.InvariantCulture));
|
||||
|
||||
if (provider.Trust.Cosign is { } cosign)
|
||||
{
|
||||
builder
|
||||
.Add("vex.provenance.cosign.issuer", cosign.Issuer)
|
||||
.Add("vex.provenance.cosign.identityPattern", cosign.IdentityPattern);
|
||||
}
|
||||
|
||||
if (!provider.Trust.PgpFingerprints.IsDefaultOrEmpty && provider.Trust.PgpFingerprints.Length > 0)
|
||||
{
|
||||
builder.Add("vex.provenance.pgp.fingerprints", string.Join(',', provider.Trust.PgpFingerprints));
|
||||
}
|
||||
|
||||
var tier = provider.Kind.ToString().ToLowerInvariant(CultureInfo.InvariantCulture);
|
||||
builder
|
||||
.Add("vex.provenance.trust.tier", tier)
|
||||
.Add("vex.provenance.trust.note", $"tier={tier};weight={provider.Trust.Weight.ToString("0.###", CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
private static bool TrimHistory(List<string> digestHistory)
|
||||
{
|
||||
if (digestHistory.Count <= MaxDigestHistory)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -259,34 +303,55 @@ public sealed class RancherHubConnector : VexConnectorBase
|
||||
private async Task<HttpRequestMessage> CreateDocumentRequestAsync(Uri documentUri, CancellationToken cancellationToken)
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, documentUri);
|
||||
if (_metadata?.Metadata.Subscription.RequiresAuthentication ?? false)
|
||||
{
|
||||
var token = await _tokenProvider.GetAccessTokenAsync(_options!, cancellationToken).ConfigureAwait(false);
|
||||
if (token is not null)
|
||||
{
|
||||
var scheme = string.IsNullOrWhiteSpace(token.TokenType) ? "Bearer" : token.TokenType;
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(scheme, token.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private async Task QuarantineAsync(
|
||||
RancherHubEventRecord record,
|
||||
RancherHubEventBatch batch,
|
||||
string reason,
|
||||
if (_metadata?.Metadata.Subscription.RequiresAuthentication ?? false)
|
||||
{
|
||||
var token = await _tokenProvider.GetAccessTokenAsync(_options!, cancellationToken).ConfigureAwait(false);
|
||||
if (token is not null)
|
||||
{
|
||||
var scheme = string.IsNullOrWhiteSpace(token.TokenType) ? "Bearer" : token.TokenType;
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue(scheme, token.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static async ValueTask UpsertProviderAsync(IServiceProvider services, VexProvider provider, CancellationToken cancellationToken)
|
||||
{
|
||||
if (services is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var store = services.GetService<IVexProviderStore>();
|
||||
if (store is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await store.SaveAsync(provider, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task QuarantineAsync(
|
||||
RancherHubEventRecord record,
|
||||
RancherHubEventBatch batch,
|
||||
string reason,
|
||||
VexConnectorContext context,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var metadata = BuildMetadata(builder => builder
|
||||
.Add("rancher.event.id", record.Id)
|
||||
.Add("rancher.event.type", record.Type)
|
||||
.Add("rancher.event.channel", record.Channel)
|
||||
.Add("rancher.event.quarantine", "true")
|
||||
.Add("rancher.event.error", reason)
|
||||
.Add("rancher.event.cursor", batch.NextCursor ?? batch.Cursor)
|
||||
.Add("rancher.event.offline", batch.FromOfflineSnapshot ? "true" : "false"));
|
||||
var metadata = BuildMetadata(builder =>
|
||||
{
|
||||
builder
|
||||
.Add("rancher.event.id", record.Id)
|
||||
.Add("rancher.event.type", record.Type)
|
||||
.Add("rancher.event.channel", record.Channel)
|
||||
.Add("rancher.event.quarantine", "true")
|
||||
.Add("rancher.event.error", reason)
|
||||
.Add("rancher.event.cursor", batch.NextCursor ?? batch.Cursor)
|
||||
.Add("rancher.event.offline", batch.FromOfflineSnapshot ? "true" : "false");
|
||||
|
||||
AddProvenanceMetadata(builder);
|
||||
});
|
||||
|
||||
var sourceUri = record.DocumentUri ?? _metadata?.Metadata.Subscription.EventsUri ?? _options!.DiscoveryUri;
|
||||
var payload = Encoding.UTF8.GetBytes(record.RawJson);
|
||||
|
||||
@@ -32,10 +32,35 @@ public sealed class UbuntuConnectorOptions
|
||||
/// Optional file path for offline index snapshot.
|
||||
/// </summary>
|
||||
public string? OfflineSnapshotPath { get; set; }
|
||||
/// <summary>
|
||||
/// Controls persistence of network responses to <see cref="OfflineSnapshotPath"/>.
|
||||
/// </summary>
|
||||
public bool PersistOfflineSnapshot { get; set; } = true;
|
||||
/// <summary>
|
||||
/// Controls persistence of network responses to <see cref="OfflineSnapshotPath"/>.
|
||||
/// </summary>
|
||||
public bool PersistOfflineSnapshot { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Weight applied to Ubuntu-sourced statements during trust evaluation.
|
||||
/// </summary>
|
||||
public double TrustWeight { get; set; } = 0.75;
|
||||
|
||||
/// <summary>
|
||||
/// Optional cosign issuer enforcing Ubuntu CSAF signatures.
|
||||
/// </summary>
|
||||
public string? CosignIssuer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Cosign identity pattern matching Ubuntu CSAF log entries.
|
||||
/// </summary>
|
||||
public string? CosignIdentityPattern { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Trusted Ubuntu CSAF GPG fingerprints.
|
||||
/// </summary>
|
||||
public IList<string> PgpFingerprints { get; } = new List<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Friendly trust tier label surfaced in provenance metadata.
|
||||
/// </summary>
|
||||
public string TrustTier { get; set; } = "distro";
|
||||
|
||||
public void Validate(IFileSystem? fileSystem = null)
|
||||
{
|
||||
@@ -77,14 +102,45 @@ public sealed class UbuntuConnectorOptions
|
||||
throw new InvalidOperationException("OfflineSnapshotPath must be provided when PreferOfflineSnapshot is enabled.");
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(OfflineSnapshotPath))
|
||||
{
|
||||
var fs = fileSystem ?? new FileSystem();
|
||||
var directory = Path.GetDirectoryName(OfflineSnapshotPath);
|
||||
if (!string.IsNullOrWhiteSpace(directory) && !fs.Directory.Exists(directory))
|
||||
{
|
||||
fs.Directory.CreateDirectory(directory);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(OfflineSnapshotPath))
|
||||
{
|
||||
var fs = fileSystem ?? new FileSystem();
|
||||
var directory = Path.GetDirectoryName(OfflineSnapshotPath);
|
||||
if (!string.IsNullOrWhiteSpace(directory) && !fs.Directory.Exists(directory))
|
||||
{
|
||||
fs.Directory.CreateDirectory(directory);
|
||||
}
|
||||
}
|
||||
|
||||
if (double.IsNaN(TrustWeight) || double.IsInfinity(TrustWeight))
|
||||
{
|
||||
TrustWeight = 0.75;
|
||||
}
|
||||
else if (TrustWeight <= 0)
|
||||
{
|
||||
TrustWeight = 0.1;
|
||||
}
|
||||
else if (TrustWeight > 1.0)
|
||||
{
|
||||
TrustWeight = 1.0;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(CosignIssuer) && string.IsNullOrWhiteSpace(CosignIdentityPattern))
|
||||
{
|
||||
throw new InvalidOperationException("CosignIdentityPattern must be provided when CosignIssuer is specified.");
|
||||
}
|
||||
|
||||
for (var i = PgpFingerprints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(PgpFingerprints[i]))
|
||||
{
|
||||
PgpFingerprints.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(TrustTier))
|
||||
{
|
||||
TrustTier = "distro";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using StellaOps.Excititor.Connectors.Abstractions;
|
||||
using StellaOps.Excititor.Connectors.Ubuntu.CSAF.Configuration;
|
||||
@@ -34,6 +36,7 @@ public sealed class UbuntuCsafConnector : VexConnectorBase
|
||||
|
||||
private UbuntuConnectorOptions? _options;
|
||||
private UbuntuCatalogResult? _catalog;
|
||||
private VexProvider? _provider;
|
||||
|
||||
public UbuntuCsafConnector(
|
||||
UbuntuCatalogLoader catalogLoader,
|
||||
@@ -58,6 +61,8 @@ public sealed class UbuntuCsafConnector : VexConnectorBase
|
||||
validators: _validators);
|
||||
|
||||
_catalog = await _catalogLoader.LoadAsync(_options, cancellationToken).ConfigureAwait(false);
|
||||
_provider = BuildProvider(_options, _catalog);
|
||||
|
||||
LogConnectorEvent(LogLevel.Information, "validate", "Ubuntu CSAF index loaded.", new Dictionary<string, object?>
|
||||
{
|
||||
["channelCount"] = _catalog.Metadata.Channels.Length,
|
||||
@@ -79,6 +84,13 @@ public sealed class UbuntuCsafConnector : VexConnectorBase
|
||||
_catalog = await _catalogLoader.LoadAsync(_options, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (_provider is null)
|
||||
{
|
||||
_provider = BuildProvider(_options!, _catalog);
|
||||
}
|
||||
|
||||
await UpsertProviderAsync(context.Services, _provider, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var state = await _stateRepository.GetAsync(Descriptor.Id, cancellationToken).ConfigureAwait(false);
|
||||
var knownTokens = state?.DocumentDigests ?? ImmutableArray<string>.Empty;
|
||||
var digestSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
@@ -334,42 +346,44 @@ public sealed class UbuntuCsafConnector : VexConnectorBase
|
||||
? Unquote(etagHeader!)
|
||||
: entry.ETag is null ? null : Unquote(entry.ETag);
|
||||
|
||||
var metadata = BuildMetadata(builder =>
|
||||
{
|
||||
builder.Add("ubuntu.channel", entry.Channel);
|
||||
builder.Add("ubuntu.uri", entry.DocumentUri.ToString());
|
||||
if (!string.IsNullOrWhiteSpace(entry.AdvisoryId))
|
||||
var metadata = BuildMetadata(builder =>
|
||||
{
|
||||
builder.Add("ubuntu.advisoryId", entry.AdvisoryId);
|
||||
}
|
||||
builder.Add("ubuntu.channel", entry.Channel);
|
||||
builder.Add("ubuntu.uri", entry.DocumentUri.ToString());
|
||||
if (!string.IsNullOrWhiteSpace(entry.AdvisoryId))
|
||||
{
|
||||
builder.Add("ubuntu.advisoryId", entry.AdvisoryId);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entry.Title))
|
||||
{
|
||||
builder.Add("ubuntu.title", entry.Title!);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(entry.Title))
|
||||
{
|
||||
builder.Add("ubuntu.title", entry.Title!);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(entry.Version))
|
||||
{
|
||||
builder.Add("ubuntu.version", entry.Version!);
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(entry.Version))
|
||||
{
|
||||
builder.Add("ubuntu.version", entry.Version!);
|
||||
}
|
||||
|
||||
if (entry.LastModified is { } modified)
|
||||
{
|
||||
builder.Add("ubuntu.lastModified", modified.ToString("O"));
|
||||
}
|
||||
if (entry.LastModified is { } modified)
|
||||
{
|
||||
builder.Add("ubuntu.lastModified", modified.ToString("O"));
|
||||
}
|
||||
|
||||
if (entry.Sha256 is not null)
|
||||
{
|
||||
builder.Add("ubuntu.sha256", NormalizeDigest(entry.Sha256));
|
||||
}
|
||||
if (entry.Sha256 is not null)
|
||||
{
|
||||
builder.Add("ubuntu.sha256", NormalizeDigest(entry.Sha256));
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(etagValue))
|
||||
{
|
||||
builder.Add("ubuntu.etag", etagValue!);
|
||||
}
|
||||
});
|
||||
if (!string.IsNullOrWhiteSpace(etagValue))
|
||||
{
|
||||
builder.Add("ubuntu.etag", etagValue!);
|
||||
}
|
||||
|
||||
var document = CreateRawDocument(VexDocumentFormat.Csaf, entry.DocumentUri, payload, metadata);
|
||||
AddProvenanceMetadata(builder);
|
||||
});
|
||||
|
||||
var document = CreateRawDocument(VexDocumentFormat.Csaf, entry.DocumentUri, payload, metadata);
|
||||
return new DownloadResult(document, etagValue);
|
||||
}
|
||||
catch (Exception ex) when (ex is not OperationCanceledException)
|
||||
@@ -386,6 +400,83 @@ public sealed class UbuntuCsafConnector : VexConnectorBase
|
||||
}
|
||||
}
|
||||
|
||||
private VexProvider BuildProvider(UbuntuConnectorOptions options, UbuntuCatalogResult? catalog)
|
||||
{
|
||||
var baseUris = new List<Uri> { options.IndexUri };
|
||||
if (catalog?.Metadata.Channels is { Length: > 0 })
|
||||
{
|
||||
baseUris.AddRange(catalog.Metadata.Channels.Select(channel => channel.CatalogUri));
|
||||
}
|
||||
|
||||
VexCosignTrust? cosign = null;
|
||||
if (!string.IsNullOrWhiteSpace(options.CosignIssuer) && !string.IsNullOrWhiteSpace(options.CosignIdentityPattern))
|
||||
{
|
||||
cosign = new VexCosignTrust(options.CosignIssuer!, options.CosignIdentityPattern!);
|
||||
}
|
||||
|
||||
var trust = new VexProviderTrust(options.TrustWeight, cosign, options.PgpFingerprints);
|
||||
return new VexProvider(
|
||||
Descriptor.Id,
|
||||
Descriptor.DisplayName,
|
||||
Descriptor.Kind,
|
||||
baseUris,
|
||||
new VexProviderDiscovery(options.IndexUri, null),
|
||||
trust);
|
||||
}
|
||||
|
||||
private void AddProvenanceMetadata(VexConnectorMetadataBuilder builder)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(builder);
|
||||
|
||||
var provider = _provider;
|
||||
if (provider is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
builder
|
||||
.Add("vex.provenance.provider", provider.Id)
|
||||
.Add("vex.provenance.providerName", provider.DisplayName)
|
||||
.Add("vex.provenance.providerKind", provider.Kind.ToString().ToLowerInvariant(CultureInfo.InvariantCulture))
|
||||
.Add("vex.provenance.trust.weight", provider.Trust.Weight.ToString("0.###", CultureInfo.InvariantCulture));
|
||||
|
||||
if (provider.Trust.Cosign is { } cosign)
|
||||
{
|
||||
builder
|
||||
.Add("vex.provenance.cosign.issuer", cosign.Issuer)
|
||||
.Add("vex.provenance.cosign.identityPattern", cosign.IdentityPattern);
|
||||
}
|
||||
|
||||
if (!provider.Trust.PgpFingerprints.IsDefaultOrEmpty && provider.Trust.PgpFingerprints.Length > 0)
|
||||
{
|
||||
builder.Add("vex.provenance.pgp.fingerprints", string.Join(',', provider.Trust.PgpFingerprints));
|
||||
}
|
||||
|
||||
var tier = !string.IsNullOrWhiteSpace(_options?.TrustTier)
|
||||
? _options!.TrustTier!
|
||||
: provider.Kind.ToString().ToLowerInvariant(CultureInfo.InvariantCulture);
|
||||
|
||||
builder
|
||||
.Add("vex.provenance.trust.tier", tier)
|
||||
.Add("vex.provenance.trust.note", $"tier={tier};weight={provider.Trust.Weight.ToString("0.###", CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
private static async ValueTask UpsertProviderAsync(IServiceProvider services, VexProvider provider, CancellationToken cancellationToken)
|
||||
{
|
||||
if (services is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var store = services.GetService<IVexProviderStore>();
|
||||
if (store is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await store.SaveAsync(provider, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static string NormalizeDigest(string value)
|
||||
{
|
||||
var trimmed = value.Trim();
|
||||
|
||||
Reference in New Issue
Block a user