diff --git a/Toolkit.Foundation/AesDecryptor.cs b/Toolkit.Foundation/AesDecryptor.cs new file mode 100644 index 0000000..5ef8653 --- /dev/null +++ b/Toolkit.Foundation/AesDecryptor.cs @@ -0,0 +1,28 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class AesDecryptor : + IDecryptor +{ + private const int IvSize = 16; + + public byte[] Decrypt(byte[] cipher, byte[] key) + { + Span iv = cipher.AsSpan(0, IvSize); + ReadOnlySpan encryptedContent = cipher.AsSpan(IvSize); + + using Aes aes = Aes.Create(); + aes.Key = key; + aes.IV = iv.ToArray(); + + using MemoryStream memoryStream = new(encryptedContent.ToArray()); + using ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + using CryptoStream cryptoStream = new(memoryStream, decryptor, CryptoStreamMode.Read); + + using MemoryStream resultStream = new(); + cryptoStream.CopyTo(resultStream); + + return resultStream.ToArray(); + } +} diff --git a/Toolkit.Foundation/AesEncryptor.cs b/Toolkit.Foundation/AesEncryptor.cs new file mode 100644 index 0000000..9e844d3 --- /dev/null +++ b/Toolkit.Foundation/AesEncryptor.cs @@ -0,0 +1,34 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class AesEncryptor : + IEncryptor +{ + private const int IvSize = 16; + + public byte[] Encrypt(byte[] data, + byte[] key) + { + if (key.Length != 32) + { + throw new ArgumentException("Key must be 256 bits (32 bytes)."); + } + + using Aes aes = Aes.Create(); + aes.Key = key; + aes.GenerateIV(); + + using MemoryStream memoryStream = new(); + memoryStream.Write(aes.IV.AsSpan(0, IvSize)); + + using (ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV)) + using (CryptoStream cryptoStream = new(memoryStream, encryptor, CryptoStreamMode.Write)) + { + cryptoStream.Write(data, 0, data.Length); + cryptoStream.FlushFinalBlock(); + } + + return memoryStream.ToArray(); + } +} diff --git a/Toolkit.Foundation/ConfigurationSource.cs b/Toolkit.Foundation/ConfigurationSource.cs index 58155ee..1538b76 100644 --- a/Toolkit.Foundation/ConfigurationSource.cs +++ b/Toolkit.Foundation/ConfigurationSource.cs @@ -120,7 +120,6 @@ public class ConfigurationSource(IConfigurationFile(IConfigurationFile(currentNode[segments[lastIndex]], - serializerOptions ?? defaultSerializerOptions()); - return true; + if (currentNode[segments[lastIndex]] is JsonNode sectionNode) + { + value = JsonSerializer.Deserialize(sectionNode, + serializerOptions ?? defaultSerializerOptions()); + return true; + } } } diff --git a/Toolkit.Foundation/ConfigurationWriter.cs b/Toolkit.Foundation/ConfigurationWriter.cs index 41544be..3274b14 100644 --- a/Toolkit.Foundation/ConfigurationWriter.cs +++ b/Toolkit.Foundation/ConfigurationWriter.cs @@ -1,19 +1,22 @@ namespace Toolkit.Foundation; -public class ConfigurationWriter(IConfigurationSource source) : +public class ConfigurationWriter(IConfigurationSource source, + IConfigurationFactory factory) : IConfigurationWriter where TConfiguration : class { public void Write(Action updateDelegate) { - if (source.TryGet(out TConfiguration? value)) + if (!source.TryGet(out TConfiguration? value)) { - if (value is not null) - { - updateDelegate?.Invoke(value); - Write(value); - } + value = (TConfiguration)factory.Create(); + } + + if (value is not null) + { + updateDelegate?.Invoke(value); + Write(value); } } diff --git a/Toolkit.Foundation/Container.cs b/Toolkit.Foundation/Container.cs new file mode 100644 index 0000000..30516e0 --- /dev/null +++ b/Toolkit.Foundation/Container.cs @@ -0,0 +1,9 @@ +namespace Toolkit.Foundation; + +public class Container : + IContainer +{ + public T? Value { get; private set; } + + public void Set(T value) => Value = value; +} diff --git a/Toolkit.Foundation/IContainer.cs b/Toolkit.Foundation/IContainer.cs new file mode 100644 index 0000000..7c7dd2d --- /dev/null +++ b/Toolkit.Foundation/IContainer.cs @@ -0,0 +1,8 @@ +namespace Toolkit.Foundation; + +public interface IContainer +{ + T? Value { get; } + + void Set(T value); +} diff --git a/Toolkit.Foundation/IDecryptor.cs b/Toolkit.Foundation/IDecryptor.cs new file mode 100644 index 0000000..0a618f7 --- /dev/null +++ b/Toolkit.Foundation/IDecryptor.cs @@ -0,0 +1,6 @@ +namespace Toolkit.Foundation; +public interface IDecryptor +{ + byte[] Decrypt(byte[] cipher, + byte[] key); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IEncryptor.cs b/Toolkit.Foundation/IEncryptor.cs new file mode 100644 index 0000000..fc37690 --- /dev/null +++ b/Toolkit.Foundation/IEncryptor.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation; +public interface IEncryptor +{ + byte[] Encrypt(byte[] data, byte[] key); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IHostBuilderExtension.cs b/Toolkit.Foundation/IHostBuilderExtension.cs index de95c71..dd9723e 100644 --- a/Toolkit.Foundation/IHostBuilderExtension.cs +++ b/Toolkit.Foundation/IHostBuilderExtension.cs @@ -100,7 +100,7 @@ public static class IHostBuilderExtension return new ConfigurationFile(fileInfo); }); - services.TryAddKeyedSingleton>(section, (provider, KeyAccelerator) => + services.TryAddKeyedTransient>(section, (provider, key) => { JsonSerializerOptions? defaultSerializer = null; if (serializerDelegate is not null) @@ -119,10 +119,11 @@ public static class IHostBuilderExtension provider.GetRequiredKeyedService>(key))); services.TryAddKeyedTransient>(section, (provider, key) => - new ConfigurationWriter(provider.GetRequiredKeyedService>(key))); + new ConfigurationWriter(provider.GetRequiredKeyedService>(key), + provider.GetRequiredKeyedService>(key))); services.TryAddKeyedTransient>(section, (provider, key) => - new ConfigurationFactory(() => defaultConfiguration ?? new TConfiguration())); + new ConfigurationFactory(() => defaultConfiguration ?? provider.GetRequiredKeyedService(key))); services.AddTransient>(provider => new ConfigurationInitializer(provider.GetRequiredKeyedService>(section), @@ -136,6 +137,9 @@ public static class IHostBuilderExtension services.TryAddKeyedTransient>(section, (provider, key) => new WritableConfiguration(provider.GetRequiredKeyedService>(key))); + services.TryAddTransient>(provider => + new WritableConfiguration(provider.GetRequiredKeyedService>(section))); + services.TryAddKeyedTransient>(section, (provider, key) => new ConfigurationDescriptor(section, provider.GetRequiredKeyedService>(key))); diff --git a/Toolkit.Foundation/IInitializer.cs b/Toolkit.Foundation/IInitializer.cs index 00a126a..1f803f6 100644 --- a/Toolkit.Foundation/IInitializer.cs +++ b/Toolkit.Foundation/IInitializer.cs @@ -3,4 +3,9 @@ public interface IInitializer { Task Initialize(); +} + +public interface IInitializer +{ + Task Initialize(); } \ No newline at end of file diff --git a/Toolkit.Foundation/IKeyDeriver.cs b/Toolkit.Foundation/IKeyDeriver.cs new file mode 100644 index 0000000..4a5a1b6 --- /dev/null +++ b/Toolkit.Foundation/IKeyDeriver.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation; +public interface IKeyDeriver +{ + byte[] DeriveKey(string password, byte[] salt, int keySize = 32, int iterations = 10000); +} diff --git a/Toolkit.Foundation/IKeyGenerator.cs b/Toolkit.Foundation/IKeyGenerator.cs new file mode 100644 index 0000000..aeaaa74 --- /dev/null +++ b/Toolkit.Foundation/IKeyGenerator.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation; +public interface IKeyGenerator +{ + byte[] Generate(int size); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IPasswordHasher.cs b/Toolkit.Foundation/IPasswordHasher.cs new file mode 100644 index 0000000..d31d66e --- /dev/null +++ b/Toolkit.Foundation/IPasswordHasher.cs @@ -0,0 +1,5 @@ +namespace Toolkit.Foundation; +public interface IPasswordHasher +{ + string HashPassword(string password, int iterations = 10000); +} \ No newline at end of file diff --git a/Toolkit.Foundation/IServiceCollectionExtensions.cs b/Toolkit.Foundation/IServiceCollectionExtensions.cs index bee5f85..e53b5c1 100644 --- a/Toolkit.Foundation/IServiceCollectionExtensions.cs +++ b/Toolkit.Foundation/IServiceCollectionExtensions.cs @@ -35,126 +35,6 @@ public static class IServiceCollectionExtensions return services; } - public static IServiceCollection AddConfiguration(this IServiceCollection services, - Func> changed) - where TConfiguration : class - where TValue : class, new() - { - services.AddSingleton(new ConfigurationValue(changed)); - services.AddHandler>(); - - return services; - } - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - string section) - where TConfiguration : class, new() => - services.AddConfiguration(section, "Settings.json", null); - - public static IServiceCollection AddConfiguration(this IServiceCollection services) - where TConfiguration : class, new() => - services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", null); - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - Action configurationDelegate) - where TConfiguration : class, new() - { - TConfiguration configuration = new(); - configurationDelegate.Invoke(configuration); - - return services.AddConfiguration(typeof(TConfiguration).Name, "Settings.json", configuration); - } - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - Action configurationDelegate, - string section) - where TConfiguration : class, new() - { - TConfiguration configuration = new(); - configurationDelegate.Invoke(configuration); - - return services.AddConfiguration(section, "Settings.json", configuration); - } - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - TConfiguration configuration) - where TConfiguration : class, new() => - services.AddConfiguration(configuration.GetType().Name, "Settings.json", configuration); - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - object configuration) - where TConfiguration : class, new() => - services.AddConfiguration(configuration.GetType().Name, - "Settings.json", (TConfiguration?)configuration); - - public static IServiceCollection AddConfiguration(this IServiceCollection services, - string section, - string path = "Settings.json", - TConfiguration? defaultConfiguration = null, - Action? serializerDelegate = null) - where TConfiguration : class, new() - { - services.TryAddSingleton>(provider => - { - IFileInfo? fileInfo = null; - if (provider.GetService() is IHostEnvironment hostEnvironment) - { - IFileProvider fileProvider = hostEnvironment.ContentRootFileProvider; - fileInfo = fileProvider.GetFileInfo(path); - } - - fileInfo ??= new PhysicalFileInfo(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, path))); - return new ConfigurationFile(fileInfo); - }); - - services.TryAddKeyedSingleton>(section, (provider, KeyAccelerator) => - { - JsonSerializerOptions? defaultSerializer = null; - if (serializerDelegate is not null) - { - defaultSerializer = new JsonSerializerOptions(); - serializerDelegate.Invoke(defaultSerializer); - } - - return new ConfigurationSource(provider.GetRequiredService>(), - section, defaultSerializer); - }); - - //services.AddHostedService>(); - services.TryAddKeyedTransient>(section, (provider, key) => - new ConfigurationReader(provider.GetRequiredKeyedService>(key), - provider.GetRequiredKeyedService>(key))); - - services.TryAddKeyedTransient>(section, (provider, key) => - new ConfigurationWriter(provider.GetRequiredKeyedService>(key))); - - services.TryAddKeyedTransient>(section, (provider, key) => - new ConfigurationFactory(() => defaultConfiguration ?? new TConfiguration())); - - services.AddTransient>(provider => - new ConfigurationInitializer(provider.GetRequiredKeyedService>(section), - provider.GetRequiredKeyedService>(section), - provider.GetRequiredKeyedService>(section), - provider.GetRequiredService())); - - services.AddTransient, ConfigurationInitializer>(provider => - provider.GetRequiredService().Create>(section)); - - services.AddTransient, WritableConfiguration>(); - - services.TryAddKeyedTransient>(section, (provider, key) => - new ConfigurationDescriptor(section, provider.GetRequiredKeyedService>(key))); - - services.AddTransient(provider => - provider.GetRequiredKeyedService>(section)); - - services.AddTransient(provider => - provider.GetRequiredKeyedService>(section).Value); - - return services; - } - public static IServiceCollection AddHandler(this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient) where THandler : IHandler diff --git a/Toolkit.Foundation/KeyDeriver.cs b/Toolkit.Foundation/KeyDeriver.cs new file mode 100644 index 0000000..af3ea68 --- /dev/null +++ b/Toolkit.Foundation/KeyDeriver.cs @@ -0,0 +1,13 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class KeyDeriver : + IKeyDeriver +{ + public byte[] DeriveKey(string password, byte[] salt, int keySize = 32, int iterations = 100000) + { + using Rfc2898DeriveBytes pbkdf2 = new(password, salt, iterations, HashAlgorithmName.SHA256); + return pbkdf2.GetBytes(keySize); + } +} diff --git a/Toolkit.Foundation/KeyGenerator.cs b/Toolkit.Foundation/KeyGenerator.cs new file mode 100644 index 0000000..362e201 --- /dev/null +++ b/Toolkit.Foundation/KeyGenerator.cs @@ -0,0 +1,15 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class KeyGenerator : + IKeyGenerator +{ + public byte[] Generate(int size) + { + byte[] key = new byte[size]; + RandomNumberGenerator.Fill(key); + + return key; + } +} diff --git a/Toolkit.Foundation/PasswordHasher.cs b/Toolkit.Foundation/PasswordHasher.cs new file mode 100644 index 0000000..4ad4d48 --- /dev/null +++ b/Toolkit.Foundation/PasswordHasher.cs @@ -0,0 +1,19 @@ +using System.Security.Cryptography; + +namespace Toolkit.Foundation; + +public class PasswordHasher : + IPasswordHasher +{ + private const int SaltSize = 16; + + public string HashPassword(string password, int iterations = 10000) + { + using Rfc2898DeriveBytes pbkdf2 = new(password, SaltSize, iterations, HashAlgorithmName.SHA256); + + byte[] salt = pbkdf2.Salt; + byte[] hash = pbkdf2.GetBytes(32); + + return $"{Convert.ToBase64String(salt)}:{Convert.ToBase64String(hash)}"; + } +}