62 Commits

Author SHA1 Message Date
Developer 02
988d1e2b16 feat(RSADecryptor): FileNotFoundEvent-Methode aktualisiert, um Datei zu erstellen, wenn nicht gefunden 2024-12-07 03:26:00 +01:00
Developer 02
4e0e907313 feat(RSAEncryptor): FileNotFoundEvent-Methode aktualisiert, um Datei zu erstellen, wenn nicht gefunden 2024-12-07 03:24:29 +01:00
Developer 02
0bfec426d4 refactor: Mergen von Encryptors und Decryptors in eine einzelne Sammlung
- Kombiniert `Encryptors` und `Decryptors` in `cryptographers` für eine vereinfachte Initialisierung in `OnDeserialized`.
2024-12-07 03:10:29 +01:00
Developer 02
08ffe821ff fix: Nullprüfungen und Initialisierung nach der Deserialisierung hinzufügen
- Nullprüfungen in `OnDeserialized` implementiert, um `Directory` und `FileName` für Decryptoren festzulegen.
- `FileName` mit `FileNameFormat` dynamisch erstellt.
- `TypeTagOf` verfeinert, um den richtigen Tag zu bestimmen, und Fehlerbehandlung für nicht unterstützte Kryptografietypen hinzugefügt.
2024-12-07 03:06:57 +01:00
Developer 02
fa5d0f1b26 refactor(IRSACryptographer): Init-Methode, Verzeichnis- und Dateinamen-Getter-Setter hinzugefügt 2024-12-07 02:09:32 +01:00
Developer 02
38bd23d012 refactor(RSAFactory): Entfernen der Methode ReadRSADecryptorAsync. 2024-12-07 02:01:06 +01:00
Developer 02
50e2581727 feat(RSACryptographer): Virtuelle UnableToInitPemEvent-Methode für den Fall hinzugefügt, dass sowohl pem als auch pem-Pfad null sein können 2024-12-07 01:33:56 +01:00
Developer 02
5c09d7775b feat(RSACryptographer): Virtuelle FileNotFoundEvent-Methode für nicht gefundene Pem-Datei hinzugefügt 2024-12-07 01:26:00 +01:00
Developer 02
dbfee49dee refactor(RSADecryptor): RSADecryptor, Version und Passwort entfernen und hinzufügen 2024-12-07 01:14:13 +01:00
Developer 02
0c6c84852d refactor: Validierung für Pem-Eigenschaft hinzugefügt, um Ausnahme bei Nicht-Initialisierung auszulösen
- Die Pem-Eigenschaft wurde aktualisiert, um eine Validierung hinzuzufügen, die eine InvalidOperationException auslöst, falls sie vor der Initialisierung aufgerufen wird.
 - Nicht verwendeten Import System.Text.Json.Serialization entfernt.
 - Fehlermeldungen wurden erweitert, um Issuer und Audience für eine bessere Debugging-Kontextbereitschaft einzuschließen.
2024-12-07 00:57:10 +01:00
Developer 02
3f61b5064c refactor(RSACryptographer): Verzeichnis- und Dateinamen-Intter in Setter umwandeln 2024-12-06 17:27:03 +01:00
Developer 02
f79d2e2352 refactor(IRSACryptographer): IJsonOnDeserialized-Implementierung entfernt 2024-12-06 17:22:42 +01:00
Developer 02
201da81aa5 refactor(RSACryptographer): anstatt PemPath.init zu verwenden, wurden getrennte Verzeichnis- und Dateinameneigenschaften hinzugefügt 2024-12-06 17:17:53 +01:00
Developer 02
bea57a25e8 feat(RSACryptographer) Init-Methode zur Verwaltung des pem-Importprozesses hinzugefügt 2024-12-06 15:12:21 +01:00
Developer 02
0ff89b4906 Reapply "refactor(RSACryptographer): Entfernte nullbare Eigenschaft von Issuer und Audience."
This reverts commit 600d17ef40.
2024-12-05 23:18:19 +01:00
Developer 02
600d17ef40 Revert "refactor(RSACryptographer): Entfernte nullbare Eigenschaft von Issuer und Audience."
This reverts commit 16565eca4d.
2024-12-05 23:08:13 +01:00
Developer 02
16565eca4d refactor(RSACryptographer): Entfernte nullbare Eigenschaft von Issuer und Audience.
- Schnittstelle aktualisiert
 - standardmäßig als leerer String zugewiesen.
2024-12-05 20:07:17 +01:00
Developer 02
8787c04917 refactor(AsymCryptParams): unnötige Eigenschaften entfernt 2024-12-05 15:50:53 +01:00
Developer 02
b3568216a0 refactor(IAsymCryptService): Indexer entfernt und Decryptors und Encryptors getter Methoden hinzugefügt. 2024-12-05 15:47:46 +01:00
Developer 02
6f520732dd refactor(AsymCryptService): Entschlüsselungswörterbuch entfernt 2024-12-05 15:22:23 +01:00
Developer 02
8003cffb9b refactor(CryptographerExtensions): In die Abstraktionsschicht verschieben 2024-12-05 15:20:56 +01:00
Developer 02
b02f93b38d refactor(RSACryptographerList): entfernt 2024-12-05 15:19:44 +01:00
Developer 02
2f0c6a905a chore: Hinzugefügtes ToDo 2024-12-05 15:03:28 +01:00
Developer 02
baf1f5e045 refactor(CryptographerExtensions): Aktualisiert, um IRSACryptographer anstelle von RSACryptographer zu verwenden, um die Abstraktion zu erhöhen. 2024-12-05 14:58:44 +01:00
Developer 02
b8a4a1f2b5 refactor(IRSACryptographer): Issuer und Audience Identifier String-Eigenschaften hinzugefügt 2024-12-05 14:50:05 +01:00
Developer 02
a69f610ef4 feat(CryptographerExtensions): Abfrage in SingleOrDefault verschieben 2024-12-05 14:38:32 +01:00
Developer 02
016d8bdcf2 feat(RSACryptographerList): Hinzufügen der Methode try get mit dem Wort out-key 2024-12-05 14:36:28 +01:00
Developer 02
738005f5dc feat(RSACryptographerList): Die Ausgabe der Indexer-Methode ist nicht null und wirft eine Ausnahme, wenn sie nicht gefunden wird. 2024-12-05 14:33:24 +01:00
Developer 02
c96af25e23 feat(CryptographerExtensions): Erstellt Erweiterungen zum Suchen und Erstellen von RSACryptographerList. 2024-12-05 14:26:20 +01:00
Developer 02
35e2fef046 feat(RSACryptographerList): Erstellt, um die Cryptographer-Liste sowohl als Wörterbuch als auch als IEnumerable zu verwenden 2024-12-05 13:37:34 +01:00
Developer 02
b8fb45d4a3 feat(AsymCryptService): Decryptors und Encryptors Getter hinzugefügt. 2024-12-05 13:17:23 +01:00
Developer 02
fa60147507 refactor(RSAFactoryParams): Implementierung von IJsonOnDeserialized anstelle von Lazy Initialization. 2024-12-05 12:12:56 +01:00
Developer 02
e9d408a717 feat(AsymCryptParams): EncryptedPrivateKeyFileTag, PrivateKeyFileTag, PublicKeyFileTag und RSAKeyNameSeparator aus RSAFactoryParams verschoben. 2024-12-05 11:34:35 +01:00
Developer 02
5fd3fa2fc6 feat(AsymCryptParams): IRSADecryptor-Liste und IRSAEncryptor-Liste hinzugefügt. 2024-12-05 11:31:00 +01:00
Developer 02
0d5bcedc01 refactor(DIExtensions): Umbenennung von TryAddCryptographerConverter in AddCryptographerConverter 2024-12-05 11:21:34 +01:00
Developer 02
2e68a37944 feat(HashAlgorithmNameConverter): Erstellt für benutzerfreundlichere json de/serilization.
- DI-Erweiterungsmethoden hinzugefügt
2024-12-05 11:06:11 +01:00
Developer 02
8076efb934 refactor(ReadOrCreateDirectory): Entfernt 2024-12-05 10:28:15 +01:00
Developer 02
c38f7dcf72 rektor(RSA): Umbenennung von dir in cryptographer und Verschiebung der zugehörigen Klassen 2024-12-05 10:03:39 +01:00
Developer 02
6e4942c885 feat(Config): Verzeichnis erstellt 2024-12-05 09:58:42 +01:00
Developer 02
d0dfd834b0 feat(Config): Verzeichnis erstellt und Params verschoben 2024-12-05 09:57:12 +01:00
Developer 02
aa9951f242 refactor: KeyType entfernt 2024-12-05 09:30:19 +01:00
Developer 02
506685a0b5 refactor(RSACryptographer): Verfallsdatum und Version entfernt 2024-12-05 09:17:44 +01:00
Developer 02
c9548238bb Revert "feat: CryptographerType-Enum hinzugefügt, um Schlüsseltypen darzustellen"
This reverts commit 3ffdd49a47.
2024-12-05 09:13:54 +01:00
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
24 changed files with 419 additions and 325 deletions

View File

@@ -0,0 +1,21 @@
namespace DigitalData.Core.Abstractions.Security
{
public static class CryptographerExtensions
{
public static IEnumerable<TRSACryptographer> GetByIssuer<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string issuer) where TRSACryptographer: IRSACryptographer
=> cryptographers.Where(c => c.Issuer == issuer);
public static IEnumerable<TRSACryptographer> GetByAudience<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string audience) where TRSACryptographer : IRSACryptographer
=> cryptographers.Where(c => c.Audience == audience);
public static TRSACryptographer Get<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string issuer, string audience) where TRSACryptographer : IRSACryptographer
=> cryptographers.Where(c => c.Issuer == issuer && c.Audience == audience).SingleOrDefault()
?? throw new InvalidOperationException($"No {typeof(TRSACryptographer).GetType().Name.TrimStart('I')} found with Issuer: {issuer} and Audience: {audience}.");
public static bool TryGet<TRSACryptographer>(this IEnumerable<TRSACryptographer> cryptographers, string issuer, string audience, out TRSACryptographer? cryptographer) where TRSACryptographer : IRSACryptographer
{
cryptographer = cryptographers.SingleOrDefault(c => c.Issuer == issuer && c.Audience == audience);
return cryptographer is not null;
}
}
}

View File

@@ -0,0 +1,9 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymCryptService<TParams> : IRSAFactory<TParams>
{
public IEnumerable<IRSADecryptor> Decryptors { get; }
public IEnumerable<IRSAEncryptor> Encryptors { get; }
}
}

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

@@ -7,5 +7,15 @@ namespace DigitalData.Core.Abstractions.Security
public string Pem { get; init; }
public RSAEncryptionPadding Padding { get; init; }
public string? Directory { get; set; }
public string? FileName { get; set; }
public string Issuer { get; init; }
public string Audience { get; init; }
public void Init();
}
}

View File

@@ -2,11 +2,7 @@
{
public interface IRSADecryptor : IRSACryptographer
{
(string Value, Version Version)? VersionedPassword { init; }
Version? PasswordVersion { get; }
bool HasEncryptedPem { get; }
public bool Encrypt { get; init; }
IRSAEncryptor Encryptor { get; }

View File

@@ -0,0 +1,16 @@
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);
}
}

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,20 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security
{
public class AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams> where TAsymCryptParams : AsymCryptParams
{
public IEnumerable<IRSADecryptor> Decryptors => _params.Decryptors;
public IEnumerable<IRSAEncryptor> Encryptors => _params.Encryptors;
public AsymCryptService(IOptions<TAsymCryptParams> options, ILogger<AsymCryptService<TAsymCryptParams>>? logger = null) : base(options)
{
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
}
}
}

View File

@@ -0,0 +1,59 @@
using DigitalData.Core.Abstractions.Security;
namespace DigitalData.Core.Security.Config
{
public class AsymCryptParams : RSAFactoryParams
{
public string Directory { get; init; } = string.Empty;
/// <summary>
/// 0: Issuer - 1: Audience - 2: Type tag - 3: Version
/// </summary>
public string FileNameFormat { get; init; } = "{0}_-_{1}_-_{2}_-_{3}.pem";
public string EncryptorTag { get; init; } = "public";
public string DecryptorTag { get; init; } = "private";
public string EncryptedDecryptorTag { get; init; } = "enc-private";
public IEnumerable<IRSADecryptor> Decryptors { get; init; } = new List<IRSADecryptor>();
public IEnumerable<IRSAEncryptor> Encryptors { get; init; } = new List<IRSAEncryptor>();
private string TypeTagOf(IRSACryptographer crypt)
{
if (crypt is IRSAEncryptor)
return EncryptorTag;
else if (crypt is IRSADecryptor decryptor)
return decryptor.Encrypt ? EncryptedDecryptorTag : DecryptorTag;
else
throw new InvalidOperationException(
"Unknown cryptographer type. The crypt parameter must be either IRSAEncryptor or IRSADecryptor.");
}
public override void OnDeserialized()
{
base.OnDeserialized();
var cryptographers = Encryptors.Cast<IRSACryptographer>().Concat(Decryptors.Cast<IRSACryptographer>());
foreach (var crypt in cryptographers)
{
// set default path
if (crypt.Pem is null)
{
crypt.Directory ??= Directory;
crypt.FileName ??= string.Format(
FileNameFormat,
crypt.Issuer,
crypt.Audience,
TypeTagOf(crypt),
Secrets.Version);
}
crypt.Init();
}
}
}
}

View File

@@ -0,0 +1,27 @@
using System.Security.Cryptography;
using System.Text.Json.Serialization;
namespace DigitalData.Core.Security.Config
{
public class RSAFactoryParams : IJsonOnDeserialized
{
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 PbeParameters? _pbeParameters;
[JsonIgnore]
public PbeParameters PbeParameters => _pbeParameters!;
public virtual void OnDeserialized() => _pbeParameters = new PbeParameters(PbeEncryptionAlgorithm, PbeHashAlgorithmName, PbeIterationCount);
}
}

View File

@@ -1,25 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using Microsoft.Extensions.Logging;
namespace DigitalData.Core.Security
{
public class CryptFactory : RSAFactory, ICryptFactory
{
private readonly IDictionary<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()
{
_decryptors = decryptors ?? new Dictionary<string, IRSADecryptor>();
RSAKeyNameFormatter = rsaKeyNameFormatter;
logger?.LogInformation("Core.Secrets version: {Version}, Created on: {CreationDate}.", Secrets.Version, Secrets.CreationDate.ToString("dd.MM.yyyy"));
}
public bool TryGetRSADecryptor(string key, out IRSADecryptor? decryptor) => _decryptors.TryGetValue(key, out decryptor);
}
}

View File

@@ -0,0 +1,53 @@
using DigitalData.Core.Abstractions.Security;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSACryptographer : IRSACryptographer
{
protected string? _pem;
public string Pem
{
get => _pem
?? throw new InvalidOperationException($"Pem is not initialized. Please ensure that the PEM is set or properly loaded from the file. Issuer: {Issuer}, Audience: {Audience}.");
init => _pem = value;
}
public string? PemPath => FileName is null ? null : Path.Combine(Directory ?? string.Empty, FileName);
public string? Directory { get; set; }
public string? FileName { get; set; }
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
protected virtual RSA RSA { get; } = RSA.Create();
public string Issuer { get; init; } = string.Empty;
public string Audience { get; init; } = string.Empty;
internal RSACryptographer() { }
public virtual void UnableToInitPemEvent() => throw new InvalidOperationException(
$"Pem is not initialized and pem file is null. Issuer is {Issuer} and audience {Audience}.");
public virtual void FileNotFoundEvent() => throw new FileNotFoundException(
$"Pem is not initialized and pem file is not found in {PemPath}. Issuer is {Issuer} and audience {Audience}.");
// TODO: make file read asynchronous, consider multiple routing
public virtual void Init()
{
if(_pem is null)
{
if(PemPath is null)
UnableToInitPemEvent();
if (File.Exists(PemPath))
_pem = File.ReadAllText(PemPath);
else
FileNotFoundEvent();
}
}
}
}

View File

@@ -0,0 +1,55 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Extensions;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
{
public bool Encrypt { get; init; }
private readonly Lazy<IRSAEncryptor> _lazyEncryptor;
public IRSAEncryptor Encryptor => _lazyEncryptor.Value;
public RSADecryptor()
{
_lazyEncryptor = new(() => new RSAEncryptor()
{
Pem = RSA.ExportRSAPublicKeyPem(),
Padding = Padding
});
}
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
public override void Init()
{
base.Init();
if (Encrypt)
RSA.ImportFromEncryptedPem(Pem, Secrets.PBE_PASSWORD.AsSpan());
else
RSA.ImportFromPem(Pem);
}
public override void FileNotFoundEvent()
{
var new_decryptor = new RSADecryptor()
{
Pem = RSAFactory<RSAFactoryParams>.Static.CreateRSAPrivateKeyPem(),
Encrypt = Encrypt
};
_pem = new_decryptor.Pem;
if (PemPath is not null)
Task.Run(async () =>
{
await File.WriteAllTextAsync(_pem, PemPath);
});
}
}
}

View File

@@ -0,0 +1,37 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Extensions;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
{
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
public string Encrypt(string data) => RSA.Encrypt(data.Base64ToByte(), Padding).BytesToString();
public bool Verify(string data, string signature) => Encrypt(data) == signature;
public override void Init()
{
base.Init();
RSA.ImportFromPem(base.Pem);
}
public override void FileNotFoundEvent()
{
var new_decryptor = new RSADecryptor()
{
Pem = RSAFactory<RSAFactoryParams>.Static.CreateRSAPrivateKeyPem()
};
_pem = new_decryptor.Encryptor.Pem;
if (PemPath is not null)
Task.Run(async () =>
{
await File.WriteAllTextAsync(_pem, PemPath);
});
}
}
}

View File

@@ -0,0 +1,44 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using Microsoft.Extensions.Options;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSAFactory<TRSAFactoryParams> : IRSAFactory<TRSAFactoryParams> where TRSAFactoryParams : RSAFactoryParams
{
private static readonly Lazy<RSAFactory<RSAFactoryParams>> LazyInstance = new(() => new(Options.Create<RSAFactoryParams>(new())));
public static RSAFactory<RSAFactoryParams> Static => LazyInstance.Value;
protected readonly TRSAFactoryParams _params;
public RSAFactory(IOptions<TRSAFactoryParams> options) => _params = options.Value;
public string CreateRSAPrivateKeyPem(int? keySizeInBits = null)
=> RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem();
public string CreateEncryptedPrivateKeyPem(
int? keySizeInBits = null,
string? password = null,
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null)
{
password ??= _params.PbePassword;
var pbeParameters = pbeEncryptionAlgorithm is null && hashAlgorithmName is null && iterationCount is null
? new PbeParameters(
pbeEncryptionAlgorithm ?? _params.PbeEncryptionAlgorithm,
hashAlgorithmName ?? _params.PbeHashAlgorithmName,
iterationCount ?? _params.PbeIterationCount)
: _params.PbeParameters;
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
var pemChars = PemEncoding.Write(_params.EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
return new string(pemChars);
}
}
}

View File

@@ -1,16 +1,55 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DigitalData.Core.Security
{
public static class DIExtensions
{
public static IServiceCollection AddSecurity(this IServiceCollection services)
public static JsonSerializerOptions AddCryptographerConverter(this JsonSerializerOptions options)
{
services.TryAddScoped<ICryptFactory, CryptFactory>();
if (!options.Converters.OfType<HashAlgorithmNameConverter>().Any())
options.Converters.Add(new HashAlgorithmNameConverter());
if (!options.Converters.OfType<JsonStringEnumConverter>().Any())
options.Converters.Add(new JsonStringEnumConverter());
return options;
}
private static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services)
where TAsymCryptParams : AsymCryptParams
{
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

@@ -0,0 +1,13 @@
using System.Security.Cryptography;
using System.Text.Json.Serialization;
using System.Text.Json;
namespace DigitalData.Core.Security
{
public class HashAlgorithmNameConverter : JsonConverter<HashAlgorithmName>
{
public override HashAlgorithmName Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => new(reader.GetString() ?? string.Empty);
public override void Write(Utf8JsonWriter writer, HashAlgorithmName value, JsonSerializerOptions options) => writer.WriteStringValue(value.Name);
}
}

View File

@@ -1,16 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using System.Security.Cryptography;
namespace DigitalData.Core.Security
{
public class RSACryptographer : IRSACryptographer
{
public required virtual string Pem { get; init; }
public RSAEncryptionPadding Padding { get; init; } = RSAEncryptionPadding.OaepSHA256;
protected virtual RSA RSA { get; } = RSA.Create();
internal RSACryptographer() { }
}
}

View File

@@ -1,58 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Extensions;
using System.Security.Cryptography;
namespace DigitalData.Core.Security
{
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
{
public (string Value, Version Version)? VersionedPassword
{
init
{
_password = value?.Value;
PasswordVersion = value?.Version;
}
}
private string? _password;
public Version? PasswordVersion { get; private init; } = null;
public bool HasEncryptedPem => _password is not null;
public bool IsEncrypted => _password is not null;
private readonly Lazy<IRSAEncryptor> _lazyEncryptor;
public IRSAEncryptor Encryptor => _lazyEncryptor.Value;
private readonly Lazy<RSA> lazyRSA;
protected override RSA RSA => lazyRSA.Value;
public RSADecryptor()
{
_lazyEncryptor = new(() => new RSAEncryptor()
{
Pem = RSA.ExportRSAPublicKeyPem(),
Padding = Padding
});
lazyRSA = new(() =>
{
var rsa = RSA.Create();
if (_password is null)
RSA.ImportFromPem(Pem);
else
RSA.ImportFromEncryptedPem(Pem, _password.AsSpan());
return rsa;
});
}
public byte[] Decrypt(byte[] data) => RSA.Decrypt(data, Padding);
public string Decrypt(string data) => RSA.Decrypt(data.Base64ToByte(), Padding).BytesToString();
}
}

View File

@@ -1,24 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Extensions;
namespace DigitalData.Core.Security
{
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
{
public override required string Pem
{
get => base.Pem;
init
{
RSA.ImportFromPem(base.Pem);
base.Pem = value;
}
}
public byte[] Encrypt(byte[] data) => RSA.Encrypt(data, Padding);
public string Encrypt(string data) => RSA.Encrypt(data.Base64ToByte(), Padding).BytesToString();
public bool Verify(string data, string signature) => Encrypt(data) == signature;
}
}

View File

@@ -1,132 +0,0 @@
using DigitalData.Core.Abstractions.Security;
using System.Security.Cryptography;
using System.Text;
namespace DigitalData.Core.Security
{
public class RSAFactory
{
private static readonly Lazy<RSAFactory> LazyInstance = new(() => new());
public static RSAFactory 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));
}
public string CreateRSAPrivateKeyPem(int? keySizeInBits = null)
=> RSA.Create(keySizeInBits ?? KeySizeInBits).ExportRSAPrivateKeyPem();
public string CreateEncryptedPrivateKeyPem(
int? keySizeInBits = null,
string? password = null,
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null)
{
password ??= PbePassword;
var pbeParameters = (pbeEncryptionAlgorithm is null && hashAlgorithmName is null && iterationCount is null)
? new PbeParameters(
pbeEncryptionAlgorithm ?? PbeEncryptionAlgorithm,
hashAlgorithmName ?? PbeHashAlgorithmName,
iterationCount ?? PbeIterationCount)
: PbeParameters;
var encryptedPrivateKey = RSA.Create(keySizeInBits ?? KeySizeInBits).ExportEncryptedPkcs8PrivateKey(password.AsSpan(), pbeParameters);
var pemChars = PemEncoding.Write(EncryptedPrivateKeyPemLabel, encryptedPrivateKey);
return new string(pemChars);
}
public async Task<IRSADecryptor> ReadRSADecryptorAsync(string path, Version? version = null, CancellationToken cancellationToken = default)
{
var pem = await File.ReadAllTextAsync(path, cancellationToken);
(string Value, Version Version)? versionedPassword = null;
if(version is not null)
{
if (version != Secrets.Version)
throw new InvalidOperationException($"The provided version {version} does not match the expected version {Secrets.Version}.");
versionedPassword = (Secrets.PBE_PASSWORD, Secrets.Version);
}
return new RSADecryptor()
{
Pem = pem,
VersionedPassword = versionedPassword
};
}
}
}