diff --git a/DigitalData.Core.Security/Config/CryptoFactoryParams.cs b/DigitalData.Core.Security/Config/CryptoFactoryParams.cs index 6498c2d..5bd60a7 100644 --- a/DigitalData.Core.Security/Config/CryptoFactoryParams.cs +++ b/DigitalData.Core.Security/Config/CryptoFactoryParams.cs @@ -44,61 +44,5 @@ namespace DigitalData.Core.Security.Config public IEnumerable TokenDescriptors { get; init; } = new List(); public RSADecryptor? VaultDecryptor { get; init; } - - public CryptoFactoryParams() - { - // init decryptors - AfterCreate += () => - { - // Create root folder if it does not exist - if (!Directory.Exists(PemDirectory)) - Directory.CreateDirectory(PemDirectory); - - var privateKeys = new List(); - privateKeys.AddRange(Decryptors); - privateKeys.AddRange(TokenDescriptors); - if (VaultDecryptor is not null) - privateKeys.Add(VaultDecryptor); - - foreach (var privateKey in privateKeys) - { - // set default path - if (privateKey.IsPemNull) - { - // file name - var file_name_params = new List(); - - if (privateKey.Id is not null) - file_name_params.Add(privateKey.Id); - else if (privateKey is RSATokenDescriptor descriptor) - file_name_params.Add(descriptor.Issuer); - - file_name_params.Add(KeySizeInBits); - file_name_params.Add(DateTime.Now.ToTag(DateTagFormat)); - - if (privateKey.IsEncrypted) - file_name_params.Add(Secrets.Version); - - var file_name = $"{string.Join(FileNameSeparator, file_name_params)}.{FileExtension}"; - - var path = Path.Combine(PemDirectory, file_name); - - if (File.Exists(path)) - privateKey.SetPem(File.ReadAllText(path)); - else - { - var pem = privateKey.IsEncrypted - ? Instance.RSAFactory.CreateEncryptedPrivateKeyPem(pbeParameters: PbeParameters, keySizeInBits: KeySizeInBits, password: Secrets.PBE_PASSWORD) - : Instance.RSAFactory.CreatePrivateKeyPem(keySizeInBits: KeySizeInBits); - - privateKey.SetPem(pem); - - // Save file in background - Task.Run(async () => await File.WriteAllTextAsync(path: path, pem)); - } - } - } - }; - } } } \ No newline at end of file diff --git a/DigitalData.Core.Security/Config/ParamsConfigureOptions.cs b/DigitalData.Core.Security/Config/ParamsConfigureOptions.cs deleted file mode 100644 index 90be984..0000000 --- a/DigitalData.Core.Security/Config/ParamsConfigureOptions.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Microsoft.Extensions.Options; - -namespace DigitalData.Core.Security.Config -{ - public class ParamsConfigureOptions : IConfigureOptions where TParams : RSAFactoryParams - { - public void Configure(TParams options) => options.Init(); - } -} \ No newline at end of file diff --git a/DigitalData.Core.Security/Config/RSAFactoryParams.cs b/DigitalData.Core.Security/Config/RSAFactoryParams.cs index 6a67223..2351b3e 100644 --- a/DigitalData.Core.Security/Config/RSAFactoryParams.cs +++ b/DigitalData.Core.Security/Config/RSAFactoryParams.cs @@ -4,7 +4,7 @@ using System.Text.Json.Serialization; namespace DigitalData.Core.Security.Config { - public class RSAFactoryParams : IJsonOnDeserialized + public class RSAFactoryParams { public int KeySizeInBits { get; init; } = 2048; @@ -27,33 +27,14 @@ namespace DigitalData.Core.Security.Config public string EncryptedPrivateKeyPemLabel { get; init; } = "ENCRYPTED PRIVATE KEY"; - private PbeParameters? _pbeParameters; + private readonly Lazy _lazyPbeParameters; [JsonIgnore] - public PbeParameters PbeParameters => _pbeParameters!; - - /// - /// Provides a thread-safe initialization mechanism using Lazy initialization. - /// - private readonly Lazy _lazyInitializer; - - public bool IsInitialized => _lazyInitializer.IsValueCreated; + public PbeParameters PbeParameters => _lazyPbeParameters.Value; public RSAFactoryParams() { - _lazyInitializer = new(() => - { - AfterCreate?.Invoke(); - return true; - }); - - AfterCreate += () => _pbeParameters = new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithm, PbeIterationCount); + _lazyPbeParameters = new(() => new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithm, PbeIterationCount)); } - - protected event Action AfterCreate; - - public void Init() => _ = _lazyInitializer.Value; - - public void OnDeserialized() => Init(); } } \ No newline at end of file diff --git a/DigitalData.Core.Security/DIExtensions.cs b/DigitalData.Core.Security/DIExtensions.cs index 883f957..7e05dd3 100644 --- a/DigitalData.Core.Security/DIExtensions.cs +++ b/DigitalData.Core.Security/DIExtensions.cs @@ -1,6 +1,7 @@ using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Security.Config; using DigitalData.Core.Security.RSAKey; +using DigitalData.Core.Security.Services; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; @@ -10,62 +11,27 @@ namespace DigitalData.Core.Security { public static class DIExtensions { - private static IServiceCollection AddParamsConfigureOptions(this IServiceCollection services) where TParams : RSAFactoryParams - => services.AddSingleton, ParamsConfigureOptions>(); - - private static IServiceCollection AddCryptoFactory(this IServiceCollection services) => services - .AddParamsConfigureOptions() - .AddAutoMapper(typeof(MappingProfile).Assembly) - .AddSingleton(); - /// /// Registers a custom asym crypt service with specified parameters from the given configuration section. /// /// /// /// The updated with the RSA Factory registered. - public static IServiceCollection AddCryptoFactory(this IServiceCollection services, IConfigurationSection section) => services - .Configure(section) - .AddCryptoFactory(); - - /// - /// Registers an asym crypt service with the specified parameters from the given instance. - /// - /// - /// The updated with the RSA Factory registered. - public static IServiceCollection AddCryptoFactory(this IServiceCollection services, CryptoFactoryParams? factoryParams = null) => services - .AddSingleton(Options.Create(factoryParams ?? new())) - .AddCryptoFactory(); - - /// - /// Registers a custom RSA Factory with specified parameters from the given configuration section. - /// - /// - /// - /// The updated with the RSA Factory registered. - public static IServiceCollection AddRSAFactory(this IServiceCollection services, IConfigurationSection section) => services - .AddParamsConfigureOptions() - .Configure(section) - .AddSingleton>(); - - private static IServiceCollection AddClaimDescriptor(this IServiceCollection services, - Func>? claimsMapper = null, - Func? subjectMapper = null) - { - var descriptor = new ClaimDescriptor - { - CreateClaims = claimsMapper, - CreateSubject = subjectMapper - }; - - return services.AddSingleton(sp => Options.Create(descriptor)); - } + public static IServiceCollection AddCryptoFactory(this IServiceCollection services, IConfiguration configuration) => services + .Configure(configuration) + .AddAutoMapper(typeof(MappingProfile).Assembly) + .AddSingleton() + .AddHostedService(); public static IServiceCollection AddJwtSignatureHandler(this IServiceCollection services, Func>? claimsMapper = null, Func? subjectMapper = null) => services - .AddClaimDescriptor(claimsMapper: claimsMapper, subjectMapper: subjectMapper) - .AddSingleton, JwtSignatureHandler>(); + .AddSingleton, JwtSignatureHandler>() + .AddSingleton(sp => Options.Create(new ClaimDescriptor + { + CreateClaims = claimsMapper, + CreateSubject = subjectMapper + })); } } \ No newline at end of file diff --git a/DigitalData.Core.Security/DigitalData.Core.Security.csproj b/DigitalData.Core.Security/DigitalData.Core.Security.csproj index e766397..09cba31 100644 --- a/DigitalData.Core.Security/DigitalData.Core.Security.csproj +++ b/DigitalData.Core.Security/DigitalData.Core.Security.csproj @@ -28,6 +28,7 @@ + diff --git a/DigitalData.Core.Security/RSAKey/RSAFactory.cs b/DigitalData.Core.Security/RSAKey/RSAFactory.cs index 5b80075..82ce426 100644 --- a/DigitalData.Core.Security/RSAKey/RSAFactory.cs +++ b/DigitalData.Core.Security/RSAKey/RSAFactory.cs @@ -11,7 +11,6 @@ namespace DigitalData.Core.Security.RSAKey public RSAFactory(IOptions options) { - options.Value.Init(); _params = options.Value; } diff --git a/DigitalData.Core.Security/Services/PemFileInitalizer.cs b/DigitalData.Core.Security/Services/PemFileInitalizer.cs new file mode 100644 index 0000000..d22f76e --- /dev/null +++ b/DigitalData.Core.Security/Services/PemFileInitalizer.cs @@ -0,0 +1,86 @@ +using DigitalData.Core.Security.Config; +using DigitalData.Core.Security.RSAKey; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace DigitalData.Core.Security.Services; + +public class PemFileInitalizer : BackgroundService +{ + private readonly CryptoFactoryParams _factoryParams; + + private readonly ILogger? _logger; + + public PemFileInitalizer(IOptions factoryParamsOptions, ILogger? logger = null) + { + _factoryParams = factoryParamsOptions.Value; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (_logger is null) + await InitPemFiles(stoppingToken); + else try + { + await InitPemFiles(stoppingToken); + } + catch(Exception ex) + { + _logger.LogError(ex, "Pem files cannot be initialized."); + } + } + + private async Task InitPemFiles(CancellationToken stoppingToken = default) + { + // Create root folder if it does not exist + if (!Directory.Exists(_factoryParams.PemDirectory)) + Directory.CreateDirectory(_factoryParams.PemDirectory); + + var privateKeys = new List(); + privateKeys.AddRange(_factoryParams.Decryptors); + privateKeys.AddRange(_factoryParams.TokenDescriptors); + if (_factoryParams.VaultDecryptor is not null) + privateKeys.Add(_factoryParams.VaultDecryptor); + + foreach (var privateKey in privateKeys) + { + // set default path + if (privateKey.IsPemNull) + { + // file name + var file_name_params = new List(); + + if (privateKey.Id is not null) + file_name_params.Add(privateKey.Id); + else if (privateKey is RSATokenDescriptor descriptor) + file_name_params.Add(descriptor.Issuer); + + file_name_params.Add(_factoryParams.KeySizeInBits); + file_name_params.Add(DateTime.Now.ToTag(_factoryParams.DateTagFormat)); + + if (privateKey.IsEncrypted) + file_name_params.Add(Secrets.Version); + + var file_name = $"{string.Join(_factoryParams.FileNameSeparator, file_name_params)}.{_factoryParams.FileExtension}"; + + var path = Path.Combine(_factoryParams.PemDirectory, file_name); + + if (File.Exists(path)) + privateKey.SetPem(File.ReadAllText(path)); + else + { + var pem = privateKey.IsEncrypted + ? Instance.RSAFactory.CreateEncryptedPrivateKeyPem(pbeParameters: _factoryParams.PbeParameters, keySizeInBits: _factoryParams.KeySizeInBits, password: Secrets.PBE_PASSWORD) + : Instance.RSAFactory.CreatePrivateKeyPem(keySizeInBits: _factoryParams.KeySizeInBits); + + privateKey.SetPem(pem); + + // Save file in background + await File.WriteAllTextAsync(path: path, pem, stoppingToken); + } + } + } + } +}