19 Commits

Author SHA1 Message Date
Developer 02
3ffdd49a47 feat: CryptographerType-Enum hinzugefügt, um Schlüsseltypen darzustellen
Schlüssel zu kategorisieren.
- Werte hinzugefügt:
  - `Private` für private Schlüssel.
  - `EncPrivate` für verschlüsselte private Schlüssel.
  - `Public` für öffentliche Schlüssel.
2024-12-05 01:28:22 +01:00
Developer 02
609cd29dc5 feat(RSACryptographer): Issuer und Audience hinzugefügt 2024-12-05 01:24:03 +01:00
Developer 02
cc3d1f58d3 feat(RSACryptographer): Version hinzugefügt 2024-12-05 01:21:49 +01:00
Developer 02
c03f39c1a9 feat(RSACryptographer): Verfall hinzugefügt 2024-12-05 01:15:59 +01:00
Developer 02
750f7bc20c refactor(AsymCryptService): Entschlüsselungsinjektion entfernt 2024-12-05 00:53:27 +01:00
Developer 02
65989b23b3 refactor(RSAFactoryParams): Eigenschaft PbeParameters hinzugefügt 2024-12-05 00:43:42 +01:00
Developer 02
c895d2df0e feat(RSAFactory): Formatierer für Schlüsselnamen entfernen 2024-12-05 00:23:28 +01:00
Developer 02
0c451cb834 feat(Core.Security.DIExtensions): Injektion von Parametern hinzugefügt 2024-12-05 00:19:02 +01:00
Developer 02
9396f48f46 feat(Core.Security.DIExtensions): Arrangierte DI-Erweiterung 2024-12-05 00:02:03 +01:00
Developer 02
1a941b4728 feat(ReadOrCreateDirectory): Erstellt, um alle pem-Dateien aus dem Ordner zu lesen und neu zu erstellen, wenn die angegebenen Dateien nicht vorhanden sind. 2024-12-03 10:45:04 +01:00
Developer 02
c6942164e2 refactor(RSAFactory): _params wurde geschützt und generisch für die Verwendung in geerbten Klassen. 2024-12-03 10:29:40 +01:00
Developer 02
343560ed62 feat(AsymCryptParams): ReadOrCreateDirs-Eigenschaft zu params hinzugefügt, um die Aktualisierung von Entschlüsselungsprogrammen zu automatisieren 2024-12-03 10:24:49 +01:00
Developer 02
6873bac8a1 feat(AsymCryptParams): Erstellt als spezifizierte Optionen für AsymCryptService 2024-12-03 10:12:51 +01:00
Developer 02
09406ca505 feat(IAsymCryptService): Generischer Typ TParams hinzugefügt. 2024-12-03 10:07:58 +01:00
Developer 02
3aa5ad782f refactor: Aktualisierung der DefaultRSAKeyNameFormatter Signatur und Logik in RSAFactory
- Die Methode `DefaultRSAKeyNameFormatter` wurde geändert, um einen `visibilityTag`- und `expiration`-Parameter aufzunehmen.
- Redundante bedingte Logik für das Anhängen von Tags wurde entfernt und der Formatter für bessere Lesbarkeit und Skalierbarkeit umstrukturiert.
- Gewährleistung der Abwärtskompatibilität mit der Versionierung durch bedingte Behandlung von `passwordVersion`.
2024-12-03 09:54:42 +01:00
Developer 02
5991444efd feat(RSAFactoryParams): Erstellt, um die Konfigurationen der RSA-Fabrik zu trennen 2024-12-02 18:08:13 +01:00
Developer 02
f720ea9cd6 refactor(IRSAFactory): Erstellt, um die Funktionalität von RSAFactory zu trennen 2024-12-02 15:10:51 +01:00
Developer 02
a4b96c2f3e feat(Sicherheit): Umbenennung von CryptFactory und seiner Schnittstelle in (I)AsymCryptService 2024-12-02 13:46:15 +01:00
Developer 02
816d5835f1 fix(BaseHttpClientService): Die Kodierung der Abfrageparameter wurde entfernt, da UriBuilder dies bereits tut.
- Hochgestuft auf 2.0.3
2024-11-26 23:45:06 +01:00
15 changed files with 184 additions and 158 deletions

View File

@@ -0,0 +1,9 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymCryptService<TParams> : IRSAFactory<TParams>
{
IRSADecryptor this[string key] { get; }
bool TryGetRSADecryptor(string key, out IRSADecryptor? decryptor);
}
}

View File

@@ -1,48 +0,0 @@
using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security
{
public interface ICryptFactory
{
int KeySizeInBits { get; init; }
string PbePassword { init; }
PbeEncryptionAlgorithm PbeEncryptionAlgorithm { get; init; }
HashAlgorithmName PbeHashAlgorithmName { get; init; }
int PbeIterationCount { get; init; }
PbeParameters PbeParameters { get; }
string EncryptedPrivateKeyPemLabel { get; init; }
/// <summary>
/// Gets the formatter function for generating RSA key names.
/// This formatter takes an issuer, audience, isPrivate, and optional version and separator
/// to produce a formatted string used for the key naming convention.
/// </summary>
/// <param name="issuer">A string representing the issuer of the key. It should not contain invalid file name characters or the separator.</param>
/// <param name="audience">A string representing the audience for which the key is intended. It should not contain invalid file name characters or the separator.</param>
/// <param name="isPrivate">An bool to check if the key is private.</param>
/// <param name="version">An instance of the <see cref="Version?"/> interface, which is used to keep the version of Pbe password.</param>
/// <param name="separator">An optional string separator used to separate the issuer and audience. The default is "-_-". It should not be included in the issuer or audience strings.</param>
/// <returns>A formatted string combining the issuer, audience, and separator, which adheres to valid file naming rules.</returns>
/// <exception cref="ArgumentException">Thrown when the issuer, audience, or separator contains invalid characters or when the separator is present within the issuer or audience.</exception>
Func<string, string, bool, Version?, string?, string> RSAKeyNameFormatter { get; }
string CreateRSAPrivateKeyPem(int? keySizeInBits = null);
string CreateEncryptedPrivateKeyPem(
int? keySizeInBits = null,
string? password = null,
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null);
IRSADecryptor this[string key] { get; }
bool TryGetRSADecryptor(string key, out IRSADecryptor? decryptor);
}
}

View File

@@ -0,0 +1,18 @@
using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSAFactory<TParams>
{
string CreateRSAPrivateKeyPem(int? keySizeInBits = null);
string CreateEncryptedPrivateKeyPem(
int? keySizeInBits = null,
string? password = null,
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null);
Task<IRSADecryptor> ReadRSADecryptorAsync(string path, Version? version = null, CancellationToken cancellationToken = default);
}
}

View File

@@ -86,7 +86,7 @@ namespace DigitalData.Core.Client
{
var query = HttpUtility.ParseQueryString(uriBuilder.Query);
var flagParams = queryParams.Where(param => param.Value is null).Select(param => HttpUtility.UrlEncode(param.Key));
var flagParams = queryParams.Where(param => param.Value is null).Select(param => param.Key);
var valueParams = queryParams.Where(param => param.Value is not null);
@@ -94,12 +94,12 @@ namespace DigitalData.Core.Client
query[param.Key] = param.Value switch
{
bool b => b.ToString().ToLower(),
_ => HttpUtility.UrlEncode(param.Value.ToString())
_ => param.Value.ToString()
};
var flagQuery = string.Join(QUERY_SEPARATOR, flagParams);
uriBuilder.Query = string.Join(QUERY_SEPARATOR, query.ToString(), flagQuery);
if (flagParams.Any())
uriBuilder.Query = string.Join(QUERY_SEPARATOR, query.ToString(), string.Join(QUERY_SEPARATOR, flagParams));
else uriBuilder.Query = query.ToString();
}
var requestUri = uriBuilder.Uri;

View File

@@ -6,7 +6,7 @@
<Nullable>enable</Nullable>
<Description>This package provides HTTP client extension methods for the DigitalData.Core library, offering simplified and asynchronous methods for fetching and handling HTTP responses. It includes utility methods for sending GET requests, reading response content as text or JSON, and deserializing JSON into dynamic or strongly-typed objects using Newtonsoft.Json. These extensions facilitate efficient and easy-to-read HTTP interactions in client applications.</Description>
<PackageId>DigitalData.Core.Client</PackageId>
<Version>2.0.1</Version>
<Version>2.0.3</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>Digital Data GmbH</Product>
@@ -15,8 +15,8 @@
<PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core http client json serilization</PackageTags>
<AssemblyVersion>2.0.1</AssemblyVersion>
<FileVersion>2.0.1</FileVersion>
<AssemblyVersion>2.0.3</AssemblyVersion>
<FileVersion>2.0.3</FileVersion>
</PropertyGroup>
<ItemGroup>

View File

@@ -12,13 +12,7 @@ namespace DigitalData.Core.Security.Extensions
rsa.ImportFromPem(pem);
return rsa;
}
public static IRSADecryptor GetRSADecryptor(this ICryptFactory factory, string issuer, string audience, Version? version = null, string? seperator = null)
=> factory[factory.RSAKeyNameFormatter(issuer, audience, true, version, seperator)];
public static bool TryGetRSADecryptor(this ICryptFactory factory, string issuer, string audience, out IRSADecryptor? decryptor, Version? version = null, string? seperator = null)
=> factory.TryGetRSADecryptor(factory.RSAKeyNameFormatter(issuer, audience, true, version, seperator), out decryptor);
private static string CreatePath(string filename, string? directory = null)
{
directory ??= Environment.CurrentDirectory;

View File

@@ -0,0 +1,7 @@
namespace DigitalData.Core.Security
{
public class AsymCryptParams : RSAFactoryParams
{
public IEnumerable<ReadOrCreateDirectory> ReadOrCreateDirs { get; init; } = new List<ReadOrCreateDirectory>();
}
}

View File

@@ -1,22 +1,18 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security
{
public class CryptFactory : RSAFactory, ICryptFactory
public class AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams> where TAsymCryptParams : AsymCryptParams
{
private readonly IDictionary<string, IRSADecryptor> _decryptors;
private readonly Dictionary<string, IRSADecryptor> _decryptors;
public IRSADecryptor this[string key] { get => _decryptors[key]; set => _decryptors[key] = value; }
public Func<string, string, bool, Version?, string?, string> RSAKeyNameFormatter { get; }
public CryptFactory(ILogger<CryptFactory> logger, IDictionary<string, IRSADecryptor> decryptors, Func<string, string, bool, Version?, string?, string> rsaKeyNameFormatter) : base()
public AsymCryptService(IOptions<TAsymCryptParams> options, ILogger<AsymCryptService<TAsymCryptParams>>? logger = null) : base(options)
{
_decryptors = decryptors ?? new Dictionary<string, IRSADecryptor>();
RSAKeyNameFormatter = rsaKeyNameFormatter;
_decryptors = new();
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
}

View File

@@ -0,0 +1,12 @@
namespace DigitalData.Core.Security
{
namespace DigitalData.Core.Security
{
public enum CryptographicKeyType
{
PrivateKey,
EncryptedPrivateKey,
PublicKey
}
}
}

View File

@@ -1,16 +1,41 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security
{
public static class DIExtensions
{
public static IServiceCollection AddSecurity(this IServiceCollection services)
private static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services)
where TAsymCryptParams : AsymCryptParams
{
services.TryAddScoped<ICryptFactory, CryptFactory>();
services.TryAddScoped<IAsymCryptService<TAsymCryptParams>, AsymCryptService<TAsymCryptParams>>();
return services;
}
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, IConfigurationSection section)
where TAsymCryptParams : AsymCryptParams
=> services.Configure<TAsymCryptParams>(section).AddAsymCryptService<TAsymCryptParams>();
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, TAsymCryptParams param)
where TAsymCryptParams : AsymCryptParams
=> services.AddSingleton(Options.Create(param)).AddAsymCryptService<TAsymCryptParams>();
private static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services)
where TRSAFactoryParams : RSAFactoryParams
{
services.TryAddScoped<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
return services;
}
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, IConfigurationSection section)
where TRSAFactoryParams : RSAFactoryParams
=> services.Configure<TRSAFactoryParams>(section).AddRSAFactory<TRSAFactoryParams>();
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, TRSAFactoryParams param)
where TRSAFactoryParams : RSAFactoryParams
=> services.AddSingleton(Options.Create(param)).AddRSAFactory<TRSAFactoryParams>();
}
}

View File

@@ -6,6 +6,10 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\DigitalData.Core.Abstractions\DigitalData.Core.Abstractions.csproj" />
<ProjectReference Include="..\DigitalData.Core.Security.Extensions\DigitalData.Core.Security.Extensions.csproj" />

View File

@@ -1,5 +1,6 @@
using DigitalData.Core.Abstractions.Security;
using System.Security.Cryptography;
using System.Text.Json;
namespace DigitalData.Core.Security
{
@@ -11,6 +12,39 @@ namespace DigitalData.Core.Security
protected virtual RSA RSA { get; } = RSA.Create();
public string? Issuer { get; init; }
public string? Audience { get; init; }
private DateOnly? _expiration;
public DateOnly? Expiration
{
get => _expiration;
init
{
if (value <= DateOnly.FromDateTime(DateTime.Now))
throw new InvalidOperationException($"Cryptographer expiration date has already passed. Cryptographer: {JsonSerializer.Serialize(this)}");
_expiration = value;
}
}
private Version? _version;
public Version? Version
{
get => _version;
init
{
if (value != Secrets.Version)
throw new InvalidOperationException($"Cryptographer version ({value}) does not match the expected version ({Secrets.Version}). Cryptographer: {JsonSerializer.Serialize(this)}");
_version = value;
}
}
internal RSACryptographer() { }
}
}

View File

@@ -1,89 +1,21 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;
using System.Text;
namespace DigitalData.Core.Security
{
public class RSAFactory
public class RSAFactory<TRSAFactoryParams> : IRSAFactory<TRSAFactoryParams> where TRSAFactoryParams : RSAFactoryParams
{
private static readonly Lazy<RSAFactory> LazyInstance = new(() => new());
private static readonly Lazy<RSAFactory<RSAFactoryParams>> LazyInstance = new(() => new(Options.Create<RSAFactoryParams>(new())));
public static RSAFactory Static => LazyInstance.Value;
public static RSAFactory<RSAFactoryParams> Static => LazyInstance.Value;
public static readonly string DefaultEncryptedPrivateKeyFileTag = "enc-private";
public static readonly string DefaultPrivateKeyFileTag = "private";
public static readonly string DefaultPublicKeyFileTag = "public";
public static readonly IEnumerable<string> KeyFileTags = new string[] { DefaultEncryptedPrivateKeyFileTag, DefaultPrivateKeyFileTag, DefaultPublicKeyFileTag };
private static readonly Lazy<IEnumerable<string>> LazyLowerFileTags = new(() => KeyFileTags.Select(tag => tag.ToLower()));
public static readonly string DefaultRSAKeyNameSeparator = "-_-";
//TODO: make the validation using regex
public static string DefaultRSAKeyNameFormatter(string issuer, string audience, bool isPrivate = true, Version? passwordVersion = null, string? separator = null)
{
separator ??= DefaultRSAKeyNameSeparator;
void ValidateForbidden(string value, string paramName)
{
if (Path.GetInvalidFileNameChars().Any(value.Contains) || LazyLowerFileTags.Value.Any(tag => value.ToLower().Contains(tag)))
throw new ArgumentException($"RSA decryptor key name creation is forbidden. The {paramName} contains forbidden characters that are not allowed in file naming.", paramName);
}
static void ValidateSeparator(string value, string paramName, string separator)
{
if (value.Contains(separator))
throw new ArgumentException($"RSA decryptor key name creation is forbidden. The {paramName} contains separator characters ({separator}) that are not allowed in file naming.", paramName);
}
ValidateForbidden(issuer, nameof(issuer));
ValidateForbidden(audience, nameof(audience));
ValidateForbidden(separator, nameof(separator));
ValidateSeparator(issuer, nameof(issuer), separator);
ValidateSeparator(audience, nameof(audience), separator);
var sb = new StringBuilder(issuer.Length + audience.Length + separator.Length * 2 + 20);
sb.Append(issuer).Append(separator).Append(audience).Append(separator);
if (passwordVersion is null && isPrivate)
sb.Append(DefaultPrivateKeyFileTag);
else if (isPrivate)
sb.Append(DefaultEncryptedPrivateKeyFileTag).Append(separator).Append(passwordVersion);
else if (passwordVersion is null)
sb.Append(DefaultPublicKeyFileTag);
else
sb.Append(DefaultPublicKeyFileTag).Append(separator).Append(passwordVersion);
return sb.ToString();
}
public int KeySizeInBits { get; init; } = 2048;
public string PbePassword { private get; init; } = Secrets.PBE_PASSWORD;
public PbeEncryptionAlgorithm PbeEncryptionAlgorithm { get; init; } = PbeEncryptionAlgorithm.Aes256Cbc;
public HashAlgorithmName PbeHashAlgorithmName { get; init; } = HashAlgorithmName.SHA256;
public int PbeIterationCount { get; init; } = 100_000;
private readonly Lazy<PbeParameters> _lazyPbeParameters;
public PbeParameters PbeParameters => _lazyPbeParameters.Value;
public string EncryptedPrivateKeyPemLabel { get; init; } = "ENCRYPTED PRIVATE KEY";
internal RSAFactory()
{
_lazyPbeParameters = new(() => new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithmName, PbeIterationCount));
}
protected readonly TRSAFactoryParams _params;
public RSAFactory(IOptions<TRSAFactoryParams> options) => _params = options.Value;
public string CreateRSAPrivateKeyPem(int? keySizeInBits = null)
=> RSA.Create(keySizeInBits ?? KeySizeInBits).ExportRSAPrivateKeyPem();
=> RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem();
public string CreateEncryptedPrivateKeyPem(
int? keySizeInBits = null,
@@ -92,18 +24,18 @@ namespace DigitalData.Core.Security
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null)
{
password ??= PbePassword;
password ??= _params.PbePassword;
var pbeParameters = (pbeEncryptionAlgorithm is null && hashAlgorithmName is null && iterationCount is null)
? new PbeParameters(
pbeEncryptionAlgorithm ?? PbeEncryptionAlgorithm,
hashAlgorithmName ?? PbeHashAlgorithmName,
iterationCount ?? PbeIterationCount)
: PbeParameters;
pbeEncryptionAlgorithm ?? _params.PbeEncryptionAlgorithm,
hashAlgorithmName ?? _params.PbeHashAlgorithmName,
iterationCount ?? _params.PbeIterationCount)
: _params.PbeParameters;
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
var pemChars = PemEncoding.Write(EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
var pemChars = PemEncoding.Write(_params.EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
return new string(pemChars);
}

View File

@@ -0,0 +1,34 @@
using System.Security.Cryptography;
namespace DigitalData.Core.Security
{
public class RSAFactoryParams
{
public string EncryptedPrivateKeyFileTag { get; init; } = "enc-private";
public string PrivateKeyFileTag { get; init; } = "private";
public string PublicKeyFileTag { get; init; } = "public";
public string RSAKeyNameSeparator { get; init; } = "-_-";
public int KeySizeInBits { get; init; } = 2048;
public string PbePassword { internal get; init; } = Secrets.PBE_PASSWORD;
public PbeEncryptionAlgorithm PbeEncryptionAlgorithm { get; init; } = PbeEncryptionAlgorithm.Aes256Cbc;
public HashAlgorithmName PbeHashAlgorithmName { get; init; } = HashAlgorithmName.SHA256;
public int PbeIterationCount { get; init; } = 100_000;
public string EncryptedPrivateKeyPemLabel { get; init; } = "ENCRYPTED PRIVATE KEY";
private readonly Lazy<PbeParameters> _lazyPbeParameters;
public PbeParameters PbeParameters => _lazyPbeParameters.Value;
public RSAFactoryParams()
=> _lazyPbeParameters = new(() => new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithmName, PbeIterationCount));
}
}

View File

@@ -0,0 +1,9 @@
namespace DigitalData.Core.Security
{
public class ReadOrCreateDirectory
{
public required string Dir { get; init; }
public IEnumerable<string> ReadOrCreateFiles { get; init; } = new List<string>();
}
}