part #2
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoComplianceOptionsConfiguration : IConfigureOptions<CryptoComplianceOptions>
|
||||
{
|
||||
private readonly IConfiguration? _configuration;
|
||||
|
||||
public CryptoComplianceOptionsConfiguration(IConfiguration? configuration = null)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public void Configure(CryptoComplianceOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (_configuration is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoComplianceOptionsPostConfigure : IPostConfigureOptions<CryptoComplianceOptions>
|
||||
{
|
||||
public void PostConfigure(string? name, CryptoComplianceOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
options.ApplyEnvironmentOverrides();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal static class CryptoComplianceOptionsRegistration
|
||||
{
|
||||
internal static void Register(IServiceCollection services, bool bindFromConfiguration)
|
||||
{
|
||||
services.AddOptions<CryptoComplianceOptions>();
|
||||
|
||||
if (bindFromConfiguration)
|
||||
{
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<CryptoComplianceOptions>, CryptoComplianceOptionsConfiguration>());
|
||||
}
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CryptoComplianceOptions>, CryptoComplianceOptionsPostConfigure>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using StellaOps.Cryptography.Plugin.SimRemote;
|
||||
using StellaOps.Cryptography.Plugin.SmRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal static class CryptoHttpClientNames
|
||||
{
|
||||
internal const string SimRemote = nameof(SimRemoteHttpClient);
|
||||
internal const string SmRemote = nameof(SmRemoteHttpClient);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoPluginConfigurationOptions : IOptions<CryptoPluginConfiguration>
|
||||
{
|
||||
public CryptoPluginConfigurationOptions(CryptoPluginConfiguration configuration)
|
||||
{
|
||||
Value = configuration ?? throw new ArgumentNullException(nameof(configuration));
|
||||
}
|
||||
|
||||
public CryptoPluginConfiguration Value { get; }
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoPluginConfigurationRegistry : ICryptoProviderRegistry
|
||||
{
|
||||
private readonly CryptoProviderRegistry _registry;
|
||||
|
||||
public CryptoPluginConfigurationRegistry(
|
||||
IReadOnlyList<ICryptoProvider> providers,
|
||||
IOptions<CryptoPluginConfiguration> configuration,
|
||||
ILogger<CryptoPluginConfigurationRegistry>? logger = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(providers);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var config = configuration.Value;
|
||||
var preferredProviderNames = providers
|
||||
.OrderByDescending(provider => GetProviderPriority(provider, config))
|
||||
.Select(provider => provider.Name)
|
||||
.ToList();
|
||||
|
||||
logger?.LogInformation(
|
||||
"Loaded {Count} crypto provider(s) with preferred order: {Providers}",
|
||||
providers.Count,
|
||||
string.Join(", ", preferredProviderNames));
|
||||
|
||||
_registry = new CryptoProviderRegistry(providers, preferredProviderNames);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ICryptoProvider> Providers => _registry.Providers;
|
||||
public bool TryResolve(string preferredProvider, out ICryptoProvider provider)
|
||||
=> _registry.TryResolve(preferredProvider, out provider);
|
||||
public ICryptoProvider ResolveOrThrow(CryptoCapability capability, string algorithmId)
|
||||
=> _registry.ResolveOrThrow(capability, algorithmId);
|
||||
public CryptoSignerResolution ResolveSigner(
|
||||
CryptoCapability capability,
|
||||
string algorithmId,
|
||||
CryptoKeyReference keyReference,
|
||||
string? preferredProvider = null)
|
||||
=> _registry.ResolveSigner(capability, algorithmId, keyReference, preferredProvider);
|
||||
public CryptoHasherResolution ResolveHasher(string algorithmId, string? preferredProvider = null)
|
||||
=> _registry.ResolveHasher(algorithmId, preferredProvider);
|
||||
|
||||
private static int GetProviderPriority(ICryptoProvider provider, CryptoPluginConfiguration config)
|
||||
{
|
||||
var enabledEntry = config.Enabled.FirstOrDefault(entry =>
|
||||
entry.Id.Equals(provider.Name, StringComparison.OrdinalIgnoreCase));
|
||||
return enabledEntry?.Priority ?? 50;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoPluginDirectoryOptions
|
||||
{
|
||||
public CryptoPluginDirectoryOptions(string? directory)
|
||||
{
|
||||
Directory = directory;
|
||||
}
|
||||
|
||||
public string? Directory { get; }
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoPluginProviderEnumerable : IEnumerable<ICryptoProvider>
|
||||
{
|
||||
private readonly IReadOnlyList<ICryptoProvider> _providers;
|
||||
|
||||
public CryptoPluginProviderEnumerable(IReadOnlyList<ICryptoProvider> providers)
|
||||
{
|
||||
_providers = providers ?? throw new ArgumentNullException(nameof(providers));
|
||||
}
|
||||
|
||||
public IEnumerator<ICryptoProvider> GetEnumerator() => _providers.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoPluginProviderList : IReadOnlyList<ICryptoProvider>
|
||||
{
|
||||
private readonly IReadOnlyList<ICryptoProvider> _providers;
|
||||
|
||||
public CryptoPluginProviderList(
|
||||
IOptions<CryptoPluginConfiguration> configuration,
|
||||
ILogger<CryptoPluginLoader>? logger = null,
|
||||
CryptoPluginDirectoryOptions? directory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var loader = new CryptoPluginLoader(configuration.Value, logger, directory?.Directory);
|
||||
try
|
||||
{
|
||||
_providers = loader.LoadProviders();
|
||||
}
|
||||
catch (CryptoPluginLoadException ex)
|
||||
{
|
||||
logger?.LogCritical(ex, "Failed to load crypto plugins: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (_providers.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"No crypto providers were loaded. Check plugin configuration and manifest.");
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => _providers.Count;
|
||||
public ICryptoProvider this[int index] => _providers[index];
|
||||
public IEnumerator<ICryptoProvider> GetEnumerator() => _providers.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class CryptoPluginProviderRegistry : ICryptoProviderRegistry
|
||||
{
|
||||
private readonly CryptoProviderRegistry _registry;
|
||||
|
||||
public CryptoPluginProviderRegistry(
|
||||
IReadOnlyList<ICryptoProvider> providers,
|
||||
IOptionsMonitor<CryptoProviderRegistryOptions>? optionsMonitor = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(providers);
|
||||
var preferred = optionsMonitor?.CurrentValue?.ResolvePreferredProviders();
|
||||
_registry = new CryptoProviderRegistry(providers, preferred);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ICryptoProvider> Providers => _registry.Providers;
|
||||
public bool TryResolve(string preferredProvider, out ICryptoProvider provider)
|
||||
=> _registry.TryResolve(preferredProvider, out provider);
|
||||
public ICryptoProvider ResolveOrThrow(CryptoCapability capability, string algorithmId)
|
||||
=> _registry.ResolveOrThrow(capability, algorithmId);
|
||||
public CryptoSignerResolution ResolveSigner(
|
||||
CryptoCapability capability,
|
||||
string algorithmId,
|
||||
CryptoKeyReference keyReference,
|
||||
string? preferredProvider = null)
|
||||
=> _registry.ResolveSigner(capability, algorithmId, keyReference, preferredProvider);
|
||||
public CryptoHasherResolution ResolveHasher(string algorithmId, string? preferredProvider = null)
|
||||
=> _registry.ResolveHasher(algorithmId, preferredProvider);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoPluginServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers crypto providers with plugin loading and compliance profile configuration.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Application configuration.</param>
|
||||
/// <param name="configurePlugins">Optional plugin configuration.</param>
|
||||
/// <param name="configureCompliance">Optional compliance configuration.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoWithPluginsAndCompliance(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<CryptoPluginConfiguration>? configurePlugins = null,
|
||||
Action<CryptoComplianceOptions>? configureCompliance = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
services.AddStellaOpsCryptoWithPlugins(configuration, configurePlugins);
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
configureCompliance?.Invoke(options);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace StellaOps.Cryptography.DependencyInjection;
|
||||
/// <summary>
|
||||
/// DI extension methods for configuration-driven crypto plugin loading.
|
||||
/// </summary>
|
||||
public static class CryptoPluginServiceCollectionExtensions
|
||||
public static partial class CryptoPluginServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers crypto providers using configuration-driven plugin loading.
|
||||
@@ -29,112 +29,25 @@ public static class CryptoPluginServiceCollectionExtensions
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
// Bind plugin configuration from appsettings
|
||||
CryptoComplianceOptionsRegistration.Register(services, bindFromConfiguration: false);
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
});
|
||||
|
||||
services.Configure<CryptoPluginConfiguration>(options =>
|
||||
{
|
||||
configuration.GetSection("StellaOps:Crypto:Plugins").Bind(options);
|
||||
configurePlugins?.Invoke(options);
|
||||
});
|
||||
|
||||
// Register compliance options (reuse existing code)
|
||||
services.TryAddSingleton<IOptionsMonitor<CryptoComplianceOptions>>(sp =>
|
||||
{
|
||||
var config = sp.GetService<IConfiguration>();
|
||||
var options = new CryptoComplianceOptions();
|
||||
config?.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
options.ApplyEnvironmentOverrides();
|
||||
return new StaticComplianceOptionsMonitor(options);
|
||||
});
|
||||
|
||||
services.TryAddSingleton<ICryptoComplianceService, CryptoComplianceService>();
|
||||
services.TryAddSingleton<ICryptoHash, DefaultCryptoHash>();
|
||||
services.TryAddSingleton<ICryptoHmac, DefaultCryptoHmac>();
|
||||
|
||||
// Register plugin loader and load providers dynamically
|
||||
services.TryAddSingleton<CryptoPluginLoader>(sp =>
|
||||
{
|
||||
var pluginConfig = sp.GetRequiredService<IOptions<CryptoPluginConfiguration>>().Value;
|
||||
var logger = sp.GetService<ILogger<CryptoPluginLoader>>();
|
||||
return new CryptoPluginLoader(pluginConfig, logger);
|
||||
});
|
||||
|
||||
// Load all configured crypto providers
|
||||
services.TryAddSingleton(sp =>
|
||||
{
|
||||
var loader = sp.GetRequiredService<CryptoPluginLoader>();
|
||||
return loader.LoadProviders();
|
||||
});
|
||||
|
||||
// Register each loaded provider as ICryptoProvider
|
||||
services.TryAddSingleton<IEnumerable<ICryptoProvider>>(sp =>
|
||||
{
|
||||
return sp.GetRequiredService<IReadOnlyList<ICryptoProvider>>();
|
||||
});
|
||||
|
||||
// Register crypto provider registry with loaded providers
|
||||
services.TryAddSingleton<ICryptoProviderRegistry>(sp =>
|
||||
{
|
||||
var providers = sp.GetRequiredService<IReadOnlyList<ICryptoProvider>>();
|
||||
var options = sp.GetService<IOptionsMonitor<CryptoProviderRegistryOptions>>();
|
||||
IEnumerable<string>? preferred = options?.CurrentValue?.ResolvePreferredProviders();
|
||||
return new CryptoProviderRegistry(providers, preferred);
|
||||
});
|
||||
services.TryAddSingleton<IReadOnlyList<ICryptoProvider>, CryptoPluginProviderList>();
|
||||
services.TryAddSingleton<IEnumerable<ICryptoProvider>, CryptoPluginProviderEnumerable>();
|
||||
services.TryAddSingleton<ICryptoProviderRegistry, CryptoPluginProviderRegistry>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers crypto providers with plugin loading and compliance profile configuration.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Application configuration.</param>
|
||||
/// <param name="configurePlugins">Optional plugin configuration.</param>
|
||||
/// <param name="configureCompliance">Optional compliance configuration.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoWithPluginsAndCompliance(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<CryptoPluginConfiguration>? configurePlugins = null,
|
||||
Action<CryptoComplianceOptions>? configureCompliance = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
// Bind compliance options from configuration
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
configureCompliance?.Invoke(options);
|
||||
options.ApplyEnvironmentOverrides();
|
||||
});
|
||||
|
||||
// Register base crypto services with plugin loading
|
||||
services.AddStellaOpsCryptoWithPlugins(configuration, configurePlugins);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for static options monitoring.
|
||||
/// </summary>
|
||||
private sealed class StaticComplianceOptionsMonitor : IOptionsMonitor<CryptoComplianceOptions>
|
||||
{
|
||||
private readonly CryptoComplianceOptions _options;
|
||||
|
||||
public StaticComplianceOptionsMonitor(CryptoComplianceOptions options)
|
||||
=> _options = options;
|
||||
|
||||
public CryptoComplianceOptions CurrentValue => _options;
|
||||
|
||||
public CryptoComplianceOptions Get(string? name) => _options;
|
||||
|
||||
public IDisposable OnChange(Action<CryptoComplianceOptions, string> listener)
|
||||
=> NullDisposable.Instance;
|
||||
|
||||
private sealed class NullDisposable : IDisposable
|
||||
{
|
||||
public static readonly NullDisposable Instance = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public sealed class CryptoProviderProfileOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ordered list of preferred provider names for the profile.
|
||||
/// </summary>
|
||||
public IList<string> PreferredProviders { get; } = new List<string>();
|
||||
}
|
||||
@@ -74,11 +74,3 @@ public sealed class CryptoProviderRegistryOptions
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class CryptoProviderProfileOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Ordered list of preferred provider names for the profile.
|
||||
/// </summary>
|
||||
public IList<string> PreferredProviders { get; } = new List<string>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoProviderRegistryValidator
|
||||
{
|
||||
private static bool GetEnvFlag(string name, bool defaultValue)
|
||||
{
|
||||
var raw = Environment.GetEnvironmentVariable(name);
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return raw.Equals("1", StringComparison.OrdinalIgnoreCase) ||
|
||||
raw.Equals("true", StringComparison.OrdinalIgnoreCase) ||
|
||||
raw.Equals("yes", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void EnsureBaselineProfiles(CryptoProviderRegistryOptions options)
|
||||
{
|
||||
if (!options.PreferredProviders.Any())
|
||||
{
|
||||
options.PreferredProviders.Add("default");
|
||||
}
|
||||
|
||||
if (!options.Profiles.TryGetValue("ru-offline", out var ruOffline))
|
||||
{
|
||||
ruOffline = new CryptoProviderProfileOptions();
|
||||
options.Profiles["ru-offline"] = ruOffline;
|
||||
}
|
||||
|
||||
if (!options.Profiles.ContainsKey("ru-linux-soft"))
|
||||
{
|
||||
options.Profiles["ru-linux-soft"] = new CryptoProviderProfileOptions();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureDefaultPreferred(
|
||||
IList<string> providers,
|
||||
bool enableOpenSsl,
|
||||
bool enablePkcs11,
|
||||
bool enableWineCsp
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
, bool enableCryptoPro
|
||||
#endif
|
||||
)
|
||||
{
|
||||
InsertIfMissing(providers, "default");
|
||||
|
||||
if (enableOpenSsl)
|
||||
{
|
||||
InsertIfMissing(providers, "ru.openssl.gost");
|
||||
}
|
||||
|
||||
if (enablePkcs11)
|
||||
{
|
||||
InsertIfMissing(providers, "ru.pkcs11");
|
||||
}
|
||||
|
||||
if (enableWineCsp)
|
||||
{
|
||||
InsertIfMissing(providers, "ru.winecsp.http");
|
||||
}
|
||||
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
if (enableCryptoPro && OperatingSystem.IsWindows())
|
||||
{
|
||||
InsertIfMissing(providers, "ru.cryptopro.csp");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void InsertIfMissing(IList<string> providers, string name)
|
||||
{
|
||||
for (var i = 0; i < providers.Count; i++)
|
||||
{
|
||||
if (string.Equals(providers[i], name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
providers.Insert(0, name);
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,9 @@ namespace StellaOps.Cryptography.DependencyInjection;
|
||||
/// <summary>
|
||||
/// Validates and normalises crypto provider registry options for RU/GOST baselines.
|
||||
/// </summary>
|
||||
public static class CryptoProviderRegistryValidator
|
||||
public static partial class CryptoProviderRegistryValidator
|
||||
{
|
||||
private static readonly StringComparer OrdinalIgnoreCase = StringComparer.OrdinalIgnoreCase;
|
||||
private static readonly StringComparer _ordinalIgnoreCase = StringComparer.OrdinalIgnoreCase;
|
||||
|
||||
public static void EnforceRuLinuxDefaults(CryptoProviderRegistryOptions options)
|
||||
{
|
||||
@@ -50,7 +50,7 @@ public static class CryptoProviderRegistryValidator
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux() && enableOpenSsl &&
|
||||
!resolved.Contains("ru.openssl.gost", OrdinalIgnoreCase))
|
||||
!resolved.Contains("ru.openssl.gost", _ordinalIgnoreCase))
|
||||
{
|
||||
throw new InvalidOperationException("Linux RU baseline requires provider 'ru.openssl.gost' (set STELLAOPS_CRYPTO_ENABLE_RU_OPENSSL=0 to override explicitly).");
|
||||
}
|
||||
@@ -60,84 +60,4 @@ public static class CryptoProviderRegistryValidator
|
||||
throw new InvalidOperationException("RU Linux baseline is misconfigured: both ru.openssl.gost and ru.pkcs11 are disabled via environment. Enable at least one provider.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetEnvFlag(string name, bool defaultValue)
|
||||
{
|
||||
var raw = Environment.GetEnvironmentVariable(name);
|
||||
if (string.IsNullOrWhiteSpace(raw))
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return raw.Equals("1", StringComparison.OrdinalIgnoreCase) ||
|
||||
raw.Equals("true", StringComparison.OrdinalIgnoreCase) ||
|
||||
raw.Equals("yes", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
private static void EnsureBaselineProfiles(CryptoProviderRegistryOptions options)
|
||||
{
|
||||
if (!options.PreferredProviders.Any())
|
||||
{
|
||||
options.PreferredProviders.Add("default");
|
||||
}
|
||||
|
||||
if (!options.Profiles.TryGetValue("ru-offline", out var ruOffline))
|
||||
{
|
||||
ruOffline = new CryptoProviderProfileOptions();
|
||||
options.Profiles["ru-offline"] = ruOffline;
|
||||
}
|
||||
|
||||
if (!options.Profiles.ContainsKey("ru-linux-soft"))
|
||||
{
|
||||
options.Profiles["ru-linux-soft"] = new CryptoProviderProfileOptions();
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnsureDefaultPreferred(
|
||||
IList<string> providers,
|
||||
bool enableOpenSsl,
|
||||
bool enablePkcs11,
|
||||
bool enableWineCsp
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
, bool enableCryptoPro
|
||||
#endif
|
||||
)
|
||||
{
|
||||
InsertIfMissing(providers, "default");
|
||||
|
||||
if (enableOpenSsl)
|
||||
{
|
||||
InsertIfMissing(providers, "ru.openssl.gost");
|
||||
}
|
||||
|
||||
if (enablePkcs11)
|
||||
{
|
||||
InsertIfMissing(providers, "ru.pkcs11");
|
||||
}
|
||||
|
||||
if (enableWineCsp)
|
||||
{
|
||||
InsertIfMissing(providers, "ru.winecsp.http");
|
||||
}
|
||||
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
if (enableCryptoPro && OperatingSystem.IsWindows())
|
||||
{
|
||||
InsertIfMissing(providers, "ru.cryptopro.csp");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private static void InsertIfMissing(IList<string> providers, string name)
|
||||
{
|
||||
for (var i = 0; i < providers.Count; i++)
|
||||
{
|
||||
if (string.Equals(providers[i], name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
providers.Insert(0, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers crypto services with compliance profile configuration.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="configureCompliance">Optional compliance configuration.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoWithCompliance(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<CryptoComplianceOptions>? configureCompliance = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
services.AddStellaOpsCrypto();
|
||||
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
configureCompliance?.Invoke(options);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.PqSoft;
|
||||
using StellaOps.Cryptography.Plugin.SmRemote;
|
||||
using StellaOps.Cryptography.Plugin.SmSoft;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
private static void RegisterDefaultProviders(
|
||||
IServiceCollection services,
|
||||
Action<DefaultCryptoProvider>? configureProvider)
|
||||
{
|
||||
var defaultProvider = new DefaultCryptoProvider();
|
||||
configureProvider?.Invoke(defaultProvider);
|
||||
|
||||
services.TryAddSingleton(defaultProvider);
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider>(defaultProvider));
|
||||
|
||||
#if STELLAOPS_CRYPTO_SODIUM
|
||||
services.TryAddSingleton<LibsodiumCryptoProvider>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, LibsodiumCryptoProvider>());
|
||||
#endif
|
||||
|
||||
services.TryAddSingleton<ICryptoHash, DefaultCryptoHash>();
|
||||
services.TryAddSingleton<ICryptoHmac, DefaultCryptoHmac>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SmSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SmRemoteHttpProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, PqSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, FipsSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, EidasSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, KcmvpHashOnlyProvider>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers crypto services using configuration-driven plugin loading.
|
||||
/// This is the recommended method for production deployments with regional compliance requirements.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="pluginDirectory">Optional custom plugin directory path. Defaults to application base directory.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoFromConfiguration(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string? pluginDirectory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var pluginConfig = new CryptoPluginConfiguration();
|
||||
configuration.GetSection("StellaOps:Crypto:Plugins").Bind(pluginConfig);
|
||||
|
||||
var complianceConfig = new CryptoComplianceConfiguration();
|
||||
configuration.GetSection("StellaOps:Crypto:Compliance").Bind(complianceConfig);
|
||||
pluginConfig.Compliance = complianceConfig;
|
||||
|
||||
services.AddSingleton(pluginConfig);
|
||||
services.TryAddSingleton<IOptions<CryptoPluginConfiguration>, CryptoPluginConfigurationOptions>();
|
||||
services.TryAddSingleton(new CryptoPluginDirectoryOptions(pluginDirectory));
|
||||
|
||||
CryptoComplianceOptionsRegistration.Register(services, bindFromConfiguration: false);
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
});
|
||||
|
||||
services.TryAddSingleton<ICryptoComplianceService, CryptoComplianceService>();
|
||||
services.TryAddSingleton<IReadOnlyList<ICryptoProvider>, CryptoPluginProviderList>();
|
||||
services.TryAddSingleton<IEnumerable<ICryptoProvider>, CryptoPluginProviderEnumerable>();
|
||||
services.TryAddSingleton<ICryptoProviderRegistry, CryptoPluginConfigurationRegistry>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers crypto services using configuration-driven plugin loading with explicit compliance profile.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="complianceProfileId">Compliance profile identifier (e.g., "gost", "fips", "eidas", "sm").</param>
|
||||
/// <param name="strictValidation">Enable strict compliance validation.</param>
|
||||
/// <param name="pluginDirectory">Optional custom plugin directory path.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoFromConfiguration(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string complianceProfileId,
|
||||
bool strictValidation = true,
|
||||
string? pluginDirectory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
ArgumentNullException.ThrowIfNull(complianceProfileId);
|
||||
|
||||
services.AddStellaOpsCryptoFromConfiguration(configuration, pluginDirectory);
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
options.ProfileId = complianceProfileId;
|
||||
options.StrictValidation = strictValidation;
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
private static void RegisterRegistry(IServiceCollection services)
|
||||
{
|
||||
services.TryAddSingleton<ICryptoProviderRegistry, ServiceRegisteredCryptoProviderRegistry>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using StellaOps.Cryptography.Plugin.OpenSslGost;
|
||||
using StellaOps.Cryptography.Plugin.Pkcs11Gost;
|
||||
using StellaOps.Cryptography.Plugin.WineCsp;
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
using StellaOps.Cryptography.Plugin.CryptoPro;
|
||||
#endif
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
public static IServiceCollection AddStellaOpsCryptoRu(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<CryptoProviderRegistryOptions>? configureRegistry = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var baseSection = configuration.GetSection("StellaOps:Crypto");
|
||||
services.Configure<StellaOpsCryptoOptions>(baseSection);
|
||||
services.Configure<CryptoProviderRegistryOptions>(baseSection.GetSection("Registry"));
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
services.Configure<CryptoProGostProviderOptions>(baseSection.GetSection("CryptoPro"));
|
||||
#endif
|
||||
services.Configure<Pkcs11GostProviderOptions>(baseSection.GetSection("Pkcs11"));
|
||||
services.Configure<OpenSslGostProviderOptions>(baseSection.GetSection("OpenSsl"));
|
||||
services.Configure<WineCspProviderOptions>(baseSection.GetSection("WineCsp"));
|
||||
|
||||
services.AddStellaOpsCrypto(configureRegistry);
|
||||
services.AddOpenSslGostProvider();
|
||||
services.AddPkcs11GostProvider();
|
||||
services.AddWineCspProvider();
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
services.AddCryptoProGostProvider();
|
||||
}
|
||||
#endif
|
||||
|
||||
services.PostConfigure<CryptoProviderRegistryOptions>(CryptoProviderRegistryValidator.EnforceRuLinuxDefaults);
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.Plugin.SimRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
private static void RegisterSimRemote(IServiceCollection services)
|
||||
{
|
||||
services.AddOptions<SimRemoteProviderOptions>();
|
||||
services.AddHttpClient<SimRemoteHttpClient>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<SimRemoteProviderOptions>, SimRemoteProviderOptionsConfiguration>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<SimRemoteProviderOptions>, SimRemoteProviderOptionsPostConfigure>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, SimRemoteHttpClientOptionsConfiguration>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SimRemoteProvider>());
|
||||
}
|
||||
|
||||
private static void RegisterSimRemoteRegistryProfile(IServiceCollection services)
|
||||
{
|
||||
services.PostConfigure<CryptoProviderRegistryOptions>(options =>
|
||||
{
|
||||
var enableSimEnv = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_ENABLE_SIM");
|
||||
var enableSim = string.Equals(enableSimEnv, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(enableSimEnv, "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!enableSim)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AddIfMissing(IList<string> list, string provider)
|
||||
{
|
||||
if (!list.Contains(provider, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
list.Add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(options.ActiveProfile) &&
|
||||
options.Profiles.TryGetValue(options.ActiveProfile, out var profile))
|
||||
{
|
||||
AddIfMissing(profile.PreferredProviders, "sim.crypto.remote");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(options.PreferredProviders, "sim.crypto.remote");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography.Plugin.SmRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
private static void RegisterSmRemote(IServiceCollection services)
|
||||
{
|
||||
services.AddOptions<SmRemoteProviderOptions>();
|
||||
services.AddHttpClient<SmRemoteHttpClient>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<HttpClientFactoryOptions>, SmRemoteHttpClientOptionsConfiguration>());
|
||||
}
|
||||
}
|
||||
@@ -1,29 +1,13 @@
|
||||
using StellaOps.Cryptography.Plugin.SimRemote;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
using StellaOps.Cryptography.PluginLoader;
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
using StellaOps.Cryptography.Plugin.CryptoPro;
|
||||
#endif
|
||||
using StellaOps.Cryptography.Plugin.Pkcs11Gost;
|
||||
using StellaOps.Cryptography.Plugin.OpenSslGost;
|
||||
using StellaOps.Cryptography.Plugin.SmRemote;
|
||||
using StellaOps.Cryptography.Plugin.SmSoft;
|
||||
using StellaOps.Cryptography.Plugin.PqSoft;
|
||||
using StellaOps.Cryptography.Plugin.WineCsp;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Dependency injection helpers for registering StellaOps cryptography services.
|
||||
/// </summary>
|
||||
public static class CryptoServiceCollectionExtensions
|
||||
public static partial class CryptoServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the default crypto provider and registry.
|
||||
@@ -39,328 +23,19 @@ public static class CryptoServiceCollectionExtensions
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
|
||||
CryptoComplianceOptionsRegistration.Register(services, bindFromConfiguration: true);
|
||||
|
||||
if (configureRegistry is not null)
|
||||
{
|
||||
services.Configure(configureRegistry);
|
||||
}
|
||||
|
||||
// Register compliance options with default profile
|
||||
services.TryAddSingleton<IOptionsMonitor<CryptoComplianceOptions>>(sp =>
|
||||
{
|
||||
var configuration = sp.GetService<IConfiguration>();
|
||||
var options = new CryptoComplianceOptions();
|
||||
configuration?.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
options.ApplyEnvironmentOverrides();
|
||||
return new StaticComplianceOptionsMonitor(options);
|
||||
});
|
||||
|
||||
// Register compliance service
|
||||
services.TryAddSingleton<ICryptoComplianceService, CryptoComplianceService>();
|
||||
|
||||
services.TryAddSingleton<DefaultCryptoProvider>(sp =>
|
||||
{
|
||||
var provider = new DefaultCryptoProvider();
|
||||
configureProvider?.Invoke(provider);
|
||||
return provider;
|
||||
});
|
||||
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, DefaultCryptoProvider>());
|
||||
|
||||
#if STELLAOPS_CRYPTO_SODIUM
|
||||
services.TryAddSingleton<LibsodiumCryptoProvider>();
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, LibsodiumCryptoProvider>());
|
||||
#endif
|
||||
|
||||
services.TryAddSingleton<ICryptoHash, DefaultCryptoHash>();
|
||||
services.TryAddSingleton<ICryptoHmac, DefaultCryptoHmac>();
|
||||
services.AddOptions<SmRemoteProviderOptions>();
|
||||
services.AddHttpClient<SmRemoteHttpClient>((sp, httpClient) =>
|
||||
{
|
||||
var opts = sp.GetService<IOptions<SmRemoteProviderOptions>>()?.Value;
|
||||
if (opts is not null && !string.IsNullOrWhiteSpace(opts.BaseAddress))
|
||||
{
|
||||
httpClient.BaseAddress = new Uri(opts.BaseAddress);
|
||||
}
|
||||
});
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SmSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, StellaOps.Cryptography.Plugin.SmRemote.SmRemoteHttpProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, PqSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, FipsSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, EidasSoftCryptoProvider>());
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, KcmvpHashOnlyProvider>());
|
||||
|
||||
// Unified simulation provider (sim-crypto-service)
|
||||
services.AddOptions<SimRemoteProviderOptions>()
|
||||
.Configure<IConfiguration>((opts, config) =>
|
||||
{
|
||||
config?.GetSection("StellaOps:Crypto:Sim").Bind(opts);
|
||||
})
|
||||
.PostConfigure(opts =>
|
||||
{
|
||||
var simUrl = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_SIM_URL");
|
||||
if (!string.IsNullOrWhiteSpace(simUrl))
|
||||
{
|
||||
opts.BaseAddress = simUrl;
|
||||
}
|
||||
});
|
||||
|
||||
services.AddHttpClient<SimRemoteHttpClient>((sp, httpClient) =>
|
||||
{
|
||||
var opts = sp.GetService<IOptions<SimRemoteProviderOptions>>()?.Value;
|
||||
if (opts is not null && !string.IsNullOrWhiteSpace(opts.BaseAddress))
|
||||
{
|
||||
httpClient.BaseAddress = new Uri(opts.BaseAddress);
|
||||
}
|
||||
});
|
||||
services.TryAddEnumerable(ServiceDescriptor.Singleton<ICryptoProvider, SimRemoteProvider>());
|
||||
|
||||
services.PostConfigure<CryptoProviderRegistryOptions>(opts =>
|
||||
{
|
||||
var enableSimEnv = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_ENABLE_SIM");
|
||||
var enableSim = string.Equals(enableSimEnv, "1", StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(enableSimEnv, "true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
if (!enableSim)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
void AddIfMissing(IList<string> list, string provider)
|
||||
{
|
||||
if (!list.Contains(provider, StringComparer.OrdinalIgnoreCase))
|
||||
{
|
||||
list.Add(provider);
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(opts.ActiveProfile) &&
|
||||
opts.Profiles.TryGetValue(opts.ActiveProfile, out var profile))
|
||||
{
|
||||
AddIfMissing(profile.PreferredProviders, "sim.crypto.remote");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddIfMissing(opts.PreferredProviders, "sim.crypto.remote");
|
||||
}
|
||||
});
|
||||
|
||||
services.TryAddSingleton<ICryptoProviderRegistry>(sp =>
|
||||
{
|
||||
var providers = sp.GetServices<ICryptoProvider>();
|
||||
var options = sp.GetService<IOptionsMonitor<CryptoProviderRegistryOptions>>();
|
||||
IEnumerable<string>? preferred = options?.CurrentValue?.ResolvePreferredProviders();
|
||||
return new CryptoProviderRegistry(providers, preferred);
|
||||
});
|
||||
RegisterDefaultProviders(services, configureProvider);
|
||||
RegisterSmRemote(services);
|
||||
RegisterSimRemote(services);
|
||||
RegisterSimRemoteRegistryProfile(services);
|
||||
RegisterRegistry(services);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers crypto services with compliance profile configuration.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="configureCompliance">Optional compliance configuration.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoWithCompliance(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<CryptoComplianceOptions>? configureCompliance = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
// Bind compliance options from configuration
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
configureCompliance?.Invoke(options);
|
||||
options.ApplyEnvironmentOverrides();
|
||||
});
|
||||
|
||||
// Register compliance service with options monitor
|
||||
services.TryAddSingleton<ICryptoComplianceService, CryptoComplianceService>();
|
||||
|
||||
// Register base crypto services
|
||||
services.AddStellaOpsCrypto();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for static options monitoring.
|
||||
/// </summary>
|
||||
private sealed class StaticComplianceOptionsMonitor : IOptionsMonitor<CryptoComplianceOptions>
|
||||
{
|
||||
private readonly CryptoComplianceOptions _options;
|
||||
|
||||
public StaticComplianceOptionsMonitor(CryptoComplianceOptions options)
|
||||
=> _options = options;
|
||||
|
||||
public CryptoComplianceOptions CurrentValue => _options;
|
||||
|
||||
public CryptoComplianceOptions Get(string? name) => _options;
|
||||
|
||||
public IDisposable OnChange(Action<CryptoComplianceOptions, string> listener)
|
||||
=> NullDisposable.Instance;
|
||||
|
||||
private sealed class NullDisposable : IDisposable
|
||||
{
|
||||
public static readonly NullDisposable Instance = new();
|
||||
public void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
public static IServiceCollection AddStellaOpsCryptoRu(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<CryptoProviderRegistryOptions>? configureRegistry = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
var baseSection = configuration.GetSection("StellaOps:Crypto");
|
||||
services.Configure<StellaOpsCryptoOptions>(baseSection);
|
||||
services.Configure<CryptoProviderRegistryOptions>(baseSection.GetSection("Registry"));
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
services.Configure<CryptoProGostProviderOptions>(baseSection.GetSection("CryptoPro"));
|
||||
#endif
|
||||
services.Configure<Pkcs11GostProviderOptions>(baseSection.GetSection("Pkcs11"));
|
||||
services.Configure<OpenSslGostProviderOptions>(baseSection.GetSection("OpenSsl"));
|
||||
services.Configure<WineCspProviderOptions>(baseSection.GetSection("WineCsp"));
|
||||
|
||||
services.AddStellaOpsCrypto(configureRegistry);
|
||||
services.AddOpenSslGostProvider();
|
||||
services.AddPkcs11GostProvider();
|
||||
services.AddWineCspProvider();
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
if (OperatingSystem.IsWindows())
|
||||
{
|
||||
services.AddCryptoProGostProvider();
|
||||
}
|
||||
#endif
|
||||
|
||||
services.PostConfigure<CryptoProviderRegistryOptions>(CryptoProviderRegistryValidator.EnforceRuLinuxDefaults);
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers crypto services using configuration-driven plugin loading.
|
||||
/// This is the recommended method for production deployments with regional compliance requirements.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="pluginDirectory">Optional custom plugin directory path. Defaults to application base directory.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoFromConfiguration(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string? pluginDirectory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
|
||||
// Bind plugin configuration from appsettings
|
||||
var pluginConfig = new CryptoPluginConfiguration();
|
||||
configuration.GetSection("StellaOps:Crypto:Plugins").Bind(pluginConfig);
|
||||
|
||||
// Bind compliance configuration
|
||||
var complianceConfig = new CryptoComplianceConfiguration();
|
||||
configuration.GetSection("StellaOps:Crypto:Compliance").Bind(complianceConfig);
|
||||
pluginConfig.Compliance = complianceConfig;
|
||||
|
||||
// Register plugin configuration as singleton
|
||||
services.AddSingleton(pluginConfig);
|
||||
|
||||
// Register compliance options with configuration binding
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
options.ApplyEnvironmentOverrides();
|
||||
});
|
||||
|
||||
// Register compliance service
|
||||
services.TryAddSingleton<ICryptoComplianceService, CryptoComplianceService>();
|
||||
|
||||
// Load crypto providers using plugin loader
|
||||
services.TryAddSingleton<ICryptoProviderRegistry>(sp =>
|
||||
{
|
||||
var logger = sp.GetService<ILoggerFactory>()?.CreateLogger<CryptoPluginLoader>();
|
||||
var loader = new CryptoPluginLoader(pluginConfig, logger, pluginDirectory);
|
||||
|
||||
IReadOnlyList<ICryptoProvider> providers;
|
||||
try
|
||||
{
|
||||
providers = loader.LoadProviders();
|
||||
}
|
||||
catch (CryptoPluginLoadException ex)
|
||||
{
|
||||
logger?.LogCritical(ex, "Failed to load crypto plugins: {Message}", ex.Message);
|
||||
throw;
|
||||
}
|
||||
|
||||
if (providers.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"No crypto providers were loaded. Check plugin configuration and manifest.");
|
||||
}
|
||||
|
||||
// Extract provider names for preferred ordering (uses priority from manifest/config)
|
||||
var preferredProviderNames = providers
|
||||
.OrderByDescending(p => GetProviderPriority(p, pluginConfig))
|
||||
.Select(p => p.Name)
|
||||
.ToList();
|
||||
|
||||
logger?.LogInformation(
|
||||
"Loaded {Count} crypto provider(s) with preferred order: {Providers}",
|
||||
providers.Count,
|
||||
string.Join(", ", preferredProviderNames));
|
||||
|
||||
return new CryptoProviderRegistry(providers, preferredProviderNames);
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers crypto services using configuration-driven plugin loading with explicit compliance profile.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection.</param>
|
||||
/// <param name="configuration">Configuration root.</param>
|
||||
/// <param name="complianceProfileId">Compliance profile identifier (e.g., "gost", "fips", "eidas", "sm").</param>
|
||||
/// <param name="strictValidation">Enable strict compliance validation.</param>
|
||||
/// <param name="pluginDirectory">Optional custom plugin directory path.</param>
|
||||
/// <returns>The service collection.</returns>
|
||||
public static IServiceCollection AddStellaOpsCryptoFromConfiguration(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
string complianceProfileId,
|
||||
bool strictValidation = true,
|
||||
string? pluginDirectory = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(services);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
ArgumentNullException.ThrowIfNull(complianceProfileId);
|
||||
|
||||
// Override compliance configuration with explicit profile
|
||||
services.Configure<CryptoComplianceOptions>(options =>
|
||||
{
|
||||
configuration.GetSection(CryptoComplianceOptions.SectionKey).Bind(options);
|
||||
options.ProfileId = complianceProfileId;
|
||||
options.StrictValidation = strictValidation;
|
||||
options.ApplyEnvironmentOverrides();
|
||||
});
|
||||
|
||||
return services.AddStellaOpsCryptoFromConfiguration(configuration, pluginDirectory);
|
||||
}
|
||||
|
||||
private static int GetProviderPriority(ICryptoProvider provider, CryptoPluginConfiguration config)
|
||||
{
|
||||
// Check if priority was overridden in configuration
|
||||
var enabledEntry = config.Enabled.FirstOrDefault(e =>
|
||||
e.Id.Equals(provider.Name, StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
return enabledEntry?.Priority ?? 50; // Default priority
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class ServiceRegisteredCryptoProviderRegistry : ICryptoProviderRegistry
|
||||
{
|
||||
private readonly CryptoProviderRegistry _registry;
|
||||
|
||||
public ServiceRegisteredCryptoProviderRegistry(
|
||||
IEnumerable<ICryptoProvider> providers,
|
||||
IOptionsMonitor<CryptoProviderRegistryOptions>? optionsMonitor = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(providers);
|
||||
var preferred = optionsMonitor?.CurrentValue?.ResolvePreferredProviders();
|
||||
_registry = new CryptoProviderRegistry(providers, preferred);
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ICryptoProvider> Providers => _registry.Providers;
|
||||
public bool TryResolve(string preferredProvider, out ICryptoProvider provider)
|
||||
=> _registry.TryResolve(preferredProvider, out provider);
|
||||
public ICryptoProvider ResolveOrThrow(CryptoCapability capability, string algorithmId)
|
||||
=> _registry.ResolveOrThrow(capability, algorithmId);
|
||||
public CryptoSignerResolution ResolveSigner(
|
||||
CryptoCapability capability,
|
||||
string algorithmId,
|
||||
CryptoKeyReference keyReference,
|
||||
string? preferredProvider = null)
|
||||
=> _registry.ResolveSigner(capability, algorithmId, keyReference, preferredProvider);
|
||||
public CryptoHasherResolution ResolveHasher(string algorithmId, string? preferredProvider = null)
|
||||
=> _registry.ResolveHasher(algorithmId, preferredProvider);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography.Plugin.SimRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class SimRemoteHttpClientOptionsConfiguration : IConfigureNamedOptions<HttpClientFactoryOptions>
|
||||
{
|
||||
private readonly IOptionsMonitor<SimRemoteProviderOptions> _options;
|
||||
|
||||
public SimRemoteHttpClientOptionsConfiguration(IOptionsMonitor<SimRemoteProviderOptions> options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public void Configure(HttpClientFactoryOptions options)
|
||||
{
|
||||
Configure(Options.DefaultName, options);
|
||||
}
|
||||
|
||||
public void Configure(string? name, HttpClientFactoryOptions options)
|
||||
{
|
||||
if (!string.Equals(name, CryptoHttpClientNames.SimRemote, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.HttpClientActions.Add(client =>
|
||||
{
|
||||
var current = _options.CurrentValue;
|
||||
if (!string.IsNullOrWhiteSpace(current.BaseAddress))
|
||||
{
|
||||
client.BaseAddress = new Uri(current.BaseAddress);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography.Plugin.SimRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class SimRemoteProviderOptionsConfiguration : IConfigureOptions<SimRemoteProviderOptions>
|
||||
{
|
||||
private readonly IConfiguration? _configuration;
|
||||
|
||||
public SimRemoteProviderOptionsConfiguration(IConfiguration? configuration = null)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public void Configure(SimRemoteProviderOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
if (_configuration is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_configuration.GetSection("StellaOps:Crypto:Sim").Bind(options);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography.Plugin.SimRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class SimRemoteProviderOptionsPostConfigure : IPostConfigureOptions<SimRemoteProviderOptions>
|
||||
{
|
||||
public void PostConfigure(string? name, SimRemoteProviderOptions options)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(options);
|
||||
|
||||
var simUrl = Environment.GetEnvironmentVariable("STELLAOPS_CRYPTO_SIM_URL");
|
||||
if (!string.IsNullOrWhiteSpace(simUrl))
|
||||
{
|
||||
options.BaseAddress = simUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using Microsoft.Extensions.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using StellaOps.Cryptography.Plugin.SmRemote;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
internal sealed class SmRemoteHttpClientOptionsConfiguration : IConfigureNamedOptions<HttpClientFactoryOptions>
|
||||
{
|
||||
private readonly IOptionsMonitor<SmRemoteProviderOptions> _options;
|
||||
|
||||
public SmRemoteHttpClientOptionsConfiguration(IOptionsMonitor<SmRemoteProviderOptions> options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
public void Configure(HttpClientFactoryOptions options)
|
||||
{
|
||||
Configure(Options.DefaultName, options);
|
||||
}
|
||||
|
||||
public void Configure(string? name, HttpClientFactoryOptions options)
|
||||
{
|
||||
if (!string.Equals(name, CryptoHttpClientNames.SmRemote, StringComparison.Ordinal))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
options.HttpClientActions.Add(client =>
|
||||
{
|
||||
var current = _options.CurrentValue;
|
||||
if (!string.IsNullOrWhiteSpace(current.BaseAddress))
|
||||
{
|
||||
client.BaseAddress = new Uri(current.BaseAddress);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using StellaOps.Cryptography.Plugin.OpenSslGost;
|
||||
using StellaOps.Cryptography.Plugin.Pkcs11Gost;
|
||||
#if STELLAOPS_CRYPTO_PRO
|
||||
using StellaOps.Cryptography.Plugin.CryptoPro;
|
||||
#endif
|
||||
using StellaOps.Cryptography.Plugin.OpenSslGost;
|
||||
using StellaOps.Cryptography.Plugin.Pkcs11Gost;
|
||||
|
||||
namespace StellaOps.Cryptography.DependencyInjection;
|
||||
|
||||
|
||||
@@ -9,3 +9,4 @@ Source of truth: `docs-archived/implplan/2025-12-29-csproj-audit/SPRINT_20251229
|
||||
| AUDIT-0050-T | DONE | Revalidated 2026-01-08. |
|
||||
| AUDIT-0050-A | TODO | Revalidated 2026-01-08 (open findings). |
|
||||
| REMED-06 | DONE | SOLID review notes captured for SPRINT_20260130_002. |
|
||||
| REMED-05 | DONE | Removed service locator usage, split DI files/options/validator helpers, added DI ordering/plugin-loading tests; `dotnet test src/__Libraries/__Tests/StellaOps.Cryptography.Tests/StellaOps.Cryptography.Tests.csproj -p:BuildInParallel=false -p:UseSharedCompilation=false` passed (326 tests). |
|
||||
|
||||
Reference in New Issue
Block a user