85 Commits

Author SHA1 Message Date
Developer 02
6a92466490 feat: hinzugefügte Index-Eigenschaft zur IAsymCryptService-Schnittstelle
- Neue Index-Eigenschaft `this[string key]` zur `IAsymCryptService`-Schnittstelle hinzugefügt.
- Ermöglicht das Abrufen spezifischer `IRSADecryptor`-Instanzen anhand eines Schlüsselstrings.
- Schnittstellendefinition aktualisiert, um die Funktionalität für implementierende Klassen zu erweitern.
2024-12-13 16:59:02 +01:00
Developer 02
5d9d756b91 feat: hinzugefügte Index-Eigenschaft zur Abfrage eines spezifischen IRSADecryptor anhand eines Schlüssels
- Neue Index-Eigenschaft `this[string key]` in `AsymCryptService` eingeführt, um spezifische `IRSADecryptor`-Instanzen basierend auf Issuer- und Audience-Schlüsseln abzurufen.
- Validierung des Schlüsselformats und Fehlerbehandlung für Fälle hinzugefügt, in denen kein passender Decryptor gefunden wird.
- Implementierung aktualisiert, um die Kompatibilität mit der bestehenden Decryptor-Enumerationslogik sicherzustellen.
2024-12-13 16:57:30 +01:00
Developer 02
f14aaa75e1 refactor(AsymCryptParams): Umbenennung von Separator in FileNameSeparator.
- KeyNameSeparator hinzugefügt.
2024-12-13 16:40:00 +01:00
Developer 02
249f5a0ae5 feat(AsymCryptService): Encryptors.get hinzugefügt, um die Verschlüsseler der Entschlüsseler zu numerieren. 2024-12-13 16:34:53 +01:00
Developer 02
30177cf0c7 feat(AsymCryptService): Implementiert IEnumerable<IRSADecryptor> 2024-12-13 16:25:21 +01:00
Developer 02
68ef0a7537 refactor(RSACryptographer): Entfernen von _pem, IsPemNull, SetPem, Init und Methoden zur Vereinfachung von RSAEncryptor 2024-12-13 16:17:35 +01:00
Developer 02
fe2ee78d14 refactor(RSADecryptor): Umbenennung der Eigenschaft Encrypt in IsEncrypted 2024-12-13 16:02:25 +01:00
Developer 02
53e6f37a09 refactor(AsymCryptParams): Umbenennung von crypt in der Schleife in decryptor 2024-12-13 15:59:28 +01:00
Developer 02
7ec85b4e30 refactor(AsymCryptParams): Unnötige Methoden entfernt 2024-12-13 15:57:17 +01:00
Developer 02
a9ebc406f3 refactor(RSAFactory): Methode CreateEncryptedPrivateKeyPem hinzugefügt, um mit direkt benutzerdefinierten pbeParametern zu erstellen.
- Umbenennung der Methode CreateRSAPrivateKeyPem in CreatePrivateKeyPem
2024-12-13 15:45:09 +01:00
Developer 02
d013d3edfa refactor(AsymCryptService): Verschlüsselungen entfernen, da sie von Entschlüsselungen erzeugt werden müssen. 2024-12-13 15:38:50 +01:00
Developer 02
f267fe955b feat(AsymCryptParams): Funktionalität erstellt, um pem aus der Datei zu setzen, wenn diese null ist.
- Wenn die pem Datei nicht existiert, erstellt
2024-12-13 15:29:42 +01:00
Developer 02
644283cf8f refactor(AsymCryptService): Nicht-generische Schnittstelle erstellt.
- Geordnete DI-Registrierungsmethoden.
2024-12-13 14:44:09 +01:00
Developer 02
82aa8d1143 refactor(DIExtensions): Alle TryAddSingelton wurden in AddSingelton umgewandelt, um im Falle einer falschen Ausnahme den Fehler zu protokollieren.
- SetAsDefault-Parameter hinzugefügt, um nicht-generische IRSAFactory im Falle einer Konfiguration über appsettings registrieren zu können.
2024-12-13 14:00:43 +01:00
Developer 02
7459f05748 refactor(RSAFactory): Schaffung einer nicht-generischen, getrennten Schnittstelle, um eine statische Standardinstanz erstellen zu können.
- Statische Instanzklasse erstellt.
 - Geordnete DI-Registrierungsmethoden.
2024-12-13 13:47:44 +01:00
Developer 02
36f75d003a refactor(AsymCryptParams): Umbenennung von Directory in PemDirectory, um Namenskonflikte zu vermeiden. 2024-12-13 11:11:12 +01:00
Developer 02
76ce64691a refactor(RSACryptographer): Verzeichnis und Dateiname wurden entfernt.
- Datei-Leseprozess in init-Methode entfernt.
2024-12-13 10:29:49 +01:00
Developer 02
7c03282066 refactor(RSACryptographer): Interne Methoden zur Konfiguration von RSACryptographen hinzugefügt.
- IsPemNull.get-Methode hinzugefügt, um zu prüfen, ob _pem null ist.
 - SetPem-Methode hinzugefügt, um pem im Projekt aktualisieren zu können.
2024-12-13 10:15:22 +01:00
Developer 02
7ae95b729f Fix: Upgrade aufgrund einer Nuget-Paket-Abhängigkeit der abstrakten Schicht 2024-12-10 23:32:49 +01:00
Developer 02
9ee8a51664 chore(Abstraktionen): Aktualisiert von 2.2.1 auf 3.0.0 2024-12-10 23:28:34 +01:00
Developer 02
b1d1a898b8 chore(API): Aktualisiert von 2.0.1 auf 2.1 2024-12-10 23:20:40 +01:00
Developer 02
4ed3e79565 chore(Anwendung): Hochgestuft von 2 auf 3 2024-12-10 23:19:27 +01:00
Developer 02
8d9de4502e refactor(Application): Verbesserung der CRUDService zur Steigerung der Typflexibilität und Wartbarkeit
- Entfernen redundanter generischer Einschränkungen für `TUpdateDto` in `CRUDService`.
- Aktualisierung der Methode `UpdateAsync`, um einen generischen Parameter für eine bessere Typflexibilität von `TUpdateDto` einzuschließen.
- Verbesserung der Typ-Einschränkungen durch Durchsetzung von `IUnique<TId>` direkt im Methodenumfang, wo zutreffend.
- Vereinfachung der Klasse zur Einhaltung bewährter Praktiken im Design generischer Dienste.
2024-12-10 23:17:41 +01:00
Developer 02
7dd91c73c4 Merge branch 'feat/client' 2024-12-10 23:03:01 +01:00
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
33 changed files with 489 additions and 324 deletions

View File

@@ -7,7 +7,7 @@ namespace DigitalData.Core.API
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class BasicCRUDControllerBase<TCRUDService, TDto, TEntity, TId> : CRUDControllerBase<TCRUDService, TDto, TDto, TDto, TEntity, TId> public class BasicCRUDControllerBase<TCRUDService, TDto, TEntity, TId> : CRUDControllerBase<TCRUDService, TDto, TDto, TDto, TEntity, TId>
where TCRUDService : ICRUDService<TDto, TDto, TDto, TEntity, TId> where TCRUDService : ICRUDService<TDto, TDto, TEntity, TId>
where TDto : class, IUnique<TId> where TDto : class, IUnique<TId>
where TEntity : class, IUnique<TId> where TEntity : class, IUnique<TId>
{ {

View File

@@ -11,13 +11,12 @@ namespace DigitalData.Core.API
/// <typeparam name="TCRUDService">The derived CRUD service type implementing ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>.</typeparam> /// <typeparam name="TCRUDService">The derived CRUD service type implementing ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>.</typeparam>
/// <typeparam name="TCreateDto">The Data Transfer Object type for create operations.</typeparam> /// <typeparam name="TCreateDto">The Data Transfer Object type for create operations.</typeparam>
/// <typeparam name="TReadDto">The Data Transfer Object type for read operations.</typeparam> /// <typeparam name="TReadDto">The Data Transfer Object type for read operations.</typeparam>
/// <typeparam name="TUpdateDto">The Data Transfer Object type for update operations.</typeparam>
/// <typeparam name="TEntity">The entity type CRUD operations will be performed on.</typeparam> /// <typeparam name="TEntity">The entity type CRUD operations will be performed on.</typeparam>
/// <typeparam name="TId">The type of the entity's identifier.</typeparam> /// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class CRUDControllerBase<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase public class CRUDControllerBase<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId> where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCreateDto : class where TCreateDto : class
where TReadDto : class where TReadDto : class
where TUpdateDto : class, IUnique<TId> where TUpdateDto : class, IUnique<TId>

View File

@@ -12,13 +12,12 @@ namespace DigitalData.Core.API
/// <typeparam name="TCRUDService">The derived CRUD service type implementing ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>.</typeparam> /// <typeparam name="TCRUDService">The derived CRUD service type implementing ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>.</typeparam>
/// <typeparam name="TCreateDto">The Data Transfer Object type for create operations.</typeparam> /// <typeparam name="TCreateDto">The Data Transfer Object type for create operations.</typeparam>
/// <typeparam name="TReadDto">The Data Transfer Object type for read operations.</typeparam> /// <typeparam name="TReadDto">The Data Transfer Object type for read operations.</typeparam>
/// <typeparam name="TUpdateDto">The Data Transfer Object type for update operations.</typeparam>
/// <typeparam name="TEntity">The entity type CRUD operations will be performed on.</typeparam> /// <typeparam name="TEntity">The entity type CRUD operations will be performed on.</typeparam>
/// <typeparam name="TId">The type of the entity's identifier.</typeparam> /// <typeparam name="TId">The type of the entity's identifier.</typeparam>
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class CRUDControllerBaseWithErrorHandling<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase public class CRUDControllerBaseWithErrorHandling<TCRUDService, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ControllerBase
where TCRUDService : ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId> where TCRUDService : ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCreateDto : class where TCreateDto : class
where TReadDto : class where TReadDto : class
where TUpdateDto : class, IUnique<TId> where TUpdateDto : class, IUnique<TId>

View File

@@ -8,7 +8,7 @@
<OutputType>Library</OutputType> <OutputType>Library</OutputType>
<Description>This package provides a comprehensive set of API controllers and related utilities for the DigitalData.Core library. It includes generic CRUD controllers, localization extensions, middleware for security policies, and application model conventions.</Description> <Description>This package provides a comprehensive set of API controllers and related utilities for the DigitalData.Core library. It includes generic CRUD controllers, localization extensions, middleware for security policies, and application model conventions.</Description>
<PackageId>DigitalData.Core.API</PackageId> <PackageId>DigitalData.Core.API</PackageId>
<Version>2.0.1</Version> <Version>2.1.1</Version>
<Authors>Digital Data GmbH</Authors> <Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company> <Company>Digital Data GmbH</Company>
<Product>DigitalData.Core.API</Product> <Product>DigitalData.Core.API</Product>
@@ -16,8 +16,8 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core api</PackageTags> <PackageTags>digital data core api</PackageTags>
<PackageIcon>core_icon.png</PackageIcon> <PackageIcon>core_icon.png</PackageIcon>
<AssemblyVersion>2.0.1</AssemblyVersion> <AssemblyVersion>2.1.1</AssemblyVersion>
<FileVersion>2.0.1</FileVersion> <FileVersion>2.1.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -15,7 +15,7 @@ namespace DigitalData.Core.Abstractions.Application
/// This interface is useful for entities that do not require different DTOs for different operations, /// This interface is useful for entities that do not require different DTOs for different operations,
/// allowing for a more concise and maintainable codebase when implementing services for such entities. /// allowing for a more concise and maintainable codebase when implementing services for such entities.
/// </remarks> /// </remarks>
public interface IBasicCRUDService<TDto, TEntity, TId> : ICRUDService<TDto, TDto, TDto, TEntity, TId> public interface IBasicCRUDService<TDto, TEntity, TId> : ICRUDService<TDto, TDto, TEntity, TId>
where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId> where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
{ {
} }

View File

@@ -2,8 +2,8 @@
namespace DigitalData.Core.Abstractions.Application namespace DigitalData.Core.Abstractions.Application
{ {
public interface ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId> public interface ICRUDService<TCreateDto, TReadDto, TEntity, TId> : IReadService<TReadDto, TEntity, TId>
where TCreateDto : class where TReadDto : class where TUpdateDto : IUnique<TId> where TEntity : class, IUnique<TId> where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
{ {
/// <summary> /// <summary>
/// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>. /// Asynchronously creates a new entity based on the provided <paramref name="createDto"/> and returns the identifier of the created entity wrapped in a <see cref="DataResult{TId}"/>.
@@ -20,6 +20,6 @@ namespace DigitalData.Core.Abstractions.Application
/// </summary> /// </summary>
/// <param name="updateDto">The updateDTO with updated values for the entity.</param> /// <param name="updateDto">The updateDTO with updated values for the entity.</param>
/// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns> /// <returns>An Result indicating the outcome of the update operation, with an appropriate message.</returns>
Task<Result> UpdateAsync(TUpdateDto updateDto); Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>;
} }
} }

View File

@@ -17,9 +17,9 @@
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackAsTool>False</PackAsTool> <PackAsTool>False</PackAsTool>
<PackageIcon>core_icon.png</PackageIcon> <PackageIcon>core_icon.png</PackageIcon>
<Version>2.2.1</Version> <Version>3.0.0</Version>
<AssemblyVersion>2.2.1</AssemblyVersion> <AssemblyVersion>3.0.0</AssemblyVersion>
<FileVersion>2.2.1</FileVersion> <FileVersion>3.0.0</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

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,11 @@
namespace DigitalData.Core.Abstractions.Security
{
public interface IAsymCryptService : IRSAFactory
{
public IEnumerable<IRSADecryptor> Decryptors { get; }
public IRSADecryptor this[string key] { get; }
}
public interface IAsymCryptService<TParams> : IAsymCryptService, IRSAFactory<TParams> { }
}

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,9 @@ namespace DigitalData.Core.Abstractions.Security
public string Pem { get; init; } public string Pem { get; init; }
public RSAEncryptionPadding Padding { get; init; } public RSAEncryptionPadding Padding { get; init; }
public string Issuer { get; init; }
public string Audience { get; init; }
} }
} }

View File

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

View File

@@ -0,0 +1,23 @@
using System.Security.Cryptography;
namespace DigitalData.Core.Abstractions.Security
{
public interface IRSAFactory
{
string CreatePrivateKeyPem(int? keySizeInBits = null);
public string CreateEncryptedPrivateKeyPem(
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null,
int? keySizeInBits = null,
string? password = null);
public string CreateEncryptedPrivateKeyPem(
PbeParameters pbeParameters,
int? keySizeInBits = null,
string? password = null);
}
public interface IRSAFactory<TParams> : IRSAFactory { }
}

View File

@@ -19,7 +19,7 @@ namespace DigitalData.Core.Application
/// and a culture-specific translation service for any necessary text translations, ensuring a versatile and internationalized approach to CRUD operations. /// and a culture-specific translation service for any necessary text translations, ensuring a versatile and internationalized approach to CRUD operations.
/// </remarks> /// </remarks>
public class BasicCRUDService<TCRUDRepository, TDto, TEntity, TId> : public class BasicCRUDService<TCRUDRepository, TDto, TEntity, TId> :
CRUDService<TCRUDRepository, TDto, TDto, TDto, TEntity, TId>, IBasicCRUDService<TDto, TEntity, TId> CRUDService<TCRUDRepository, TDto, TDto, TEntity, TId>, IBasicCRUDService<TDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId> where TCRUDRepository : ICRUDRepository<TEntity, TId> where TDto : class, IUnique<TId> where TEntity : class, IUnique<TId>
{ {
/// <summary> /// <summary>

View File

@@ -12,11 +12,10 @@ namespace DigitalData.Core.Application
/// </summary> /// </summary>
/// <typeparam name="TCreateDto">The DTO type for create operations.</typeparam> /// <typeparam name="TCreateDto">The DTO type for create operations.</typeparam>
/// <typeparam name="TReadDto">The DTO type for read operations.</typeparam> /// <typeparam name="TReadDto">The DTO type for read operations.</typeparam>
/// <typeparam name="TUpdateDto">The DTO type for update operations.</typeparam>
/// <typeparam name="TEntity">The entity type.</typeparam> /// <typeparam name="TEntity">The entity type.</typeparam>
/// <typeparam name="TId">The type of the identifier for the entity.</typeparam> /// <typeparam name="TId">The type of the identifier for the entity.</typeparam>
public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId> : ReadService<TCRUDRepository, TReadDto, TEntity, TId>, ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId> public class CRUDService<TCRUDRepository, TCreateDto, TReadDto, TEntity, TId> : ReadService<TCRUDRepository, TReadDto, TEntity, TId>, ICRUDService<TCreateDto, TReadDto, TEntity, TId>
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : IUnique<TId> where TEntity : class, IUnique<TId> where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TEntity : class, IUnique<TId>
{ {
/// <summary> /// <summary>
@@ -45,7 +44,7 @@ namespace DigitalData.Core.Application
/// </summary> /// </summary>
/// <param name="updateDto">The DTO to update an entity from.</param> /// <param name="updateDto">The DTO to update an entity from.</param>
/// <returns>A service message indicating success or failure.</returns> /// <returns>A service message indicating success or failure.</returns>
public virtual async Task<Result> UpdateAsync(TUpdateDto updateDto) public virtual async Task<Result> UpdateAsync<TUpdateDto>(TUpdateDto updateDto) where TUpdateDto : IUnique<TId>
{ {
var currentEntitiy = await _repository.ReadByIdAsync(updateDto.Id); var currentEntitiy = await _repository.ReadByIdAsync(updateDto.Id);

View File

@@ -40,7 +40,6 @@ namespace DigitalData.Core.Application
/// <typeparam name="TCRUDRepository">The repository type that provides CRUD operations for entities of type TEntity.</typeparam> /// <typeparam name="TCRUDRepository">The repository type that provides CRUD operations for entities of type TEntity.</typeparam>
/// <typeparam name="TCreateDto">The DTO type used for create operations.</typeparam> /// <typeparam name="TCreateDto">The DTO type used for create operations.</typeparam>
/// <typeparam name="TReadDto">The DTO type used for read operations.</typeparam> /// <typeparam name="TReadDto">The DTO type used for read operations.</typeparam>
/// <typeparam name="TUpdateDto">The DTO type used for update operations.</typeparam>
/// <typeparam name="TEntity">The entity type corresponding to the DTOs.</typeparam> /// <typeparam name="TEntity">The entity type corresponding to the DTOs.</typeparam>
/// <typeparam name="TId">The type of the entity's identifier.</typeparam> /// <typeparam name="TId">The type of the entity's identifier.</typeparam>
/// <typeparam name="TProfile">The AutoMapper profile type for configuring mappings between the DTOs and the entity.</typeparam> /// <typeparam name="TProfile">The AutoMapper profile type for configuring mappings between the DTOs and the entity.</typeparam>
@@ -48,9 +47,9 @@ namespace DigitalData.Core.Application
/// <param name="configureService">An optional action to configure additional services for the CRUD service.</param> /// <param name="configureService">An optional action to configure additional services for the CRUD service.</param>
/// <returns>The original <see cref="IServiceCollection"/> instance, allowing further configuration.</returns> /// <returns>The original <see cref="IServiceCollection"/> instance, allowing further configuration.</returns>
public static IServiceCollection AddCleanCRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId, TProfile>(this IServiceCollection services, Action<IServiceCollection>? configureService = null) public static IServiceCollection AddCleanCRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId, TProfile>(this IServiceCollection services, Action<IServiceCollection>? configureService = null)
where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class where TUpdateDto : class, IUnique<TId> where TEntity : class, IUnique<TId> where TProfile : Profile where TCRUDRepository : ICRUDRepository<TEntity, TId> where TCreateDto : class where TReadDto : class, IUnique<TId> where TEntity : class, IUnique<TId> where TProfile : Profile
{ {
services.AddScoped<ICRUDService<TCreateDto, TReadDto, TUpdateDto, TEntity, TId>, CRUDService<TCRUDRepository, TCreateDto, TReadDto, TUpdateDto, TEntity, TId>>(); services.AddScoped<ICRUDService<TCreateDto, TReadDto, TEntity, TId>, CRUDService<TCRUDRepository, TCreateDto, TReadDto, TEntity, TId>>();
configureService?.Invoke(services); configureService?.Invoke(services);
services.AddAutoMapper(typeof(TProfile).Assembly); services.AddAutoMapper(typeof(TProfile).Assembly);

View File

@@ -14,7 +14,9 @@
<PackageIcon>core_icon.png</PackageIcon> <PackageIcon>core_icon.png</PackageIcon>
<RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl> <RepositoryUrl>http://git.dd:3000/AppStd/WebCoreModules.git</RepositoryUrl>
<PackageTags>digital data core application clean architecture</PackageTags> <PackageTags>digital data core application clean architecture</PackageTags>
<Version>2.0.0.0</Version> <Version>3.0.1</Version>
<AssemblyVersion>3.0.1</AssemblyVersion>
<FileVersion>3.0.1</FileVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -12,13 +12,7 @@ namespace DigitalData.Core.Security.Extensions
rsa.ImportFromPem(pem); rsa.ImportFromPem(pem);
return rsa; 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) private static string CreatePath(string filename, string? directory = null)
{ {
directory ??= Environment.CurrentDirectory; directory ??= Environment.CurrentDirectory;

View File

@@ -0,0 +1,49 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System.Collections;
namespace DigitalData.Core.Security
{
public class AsymCryptService<TAsymCryptParams> : RSAFactory<TAsymCryptParams>, IAsymCryptService<TAsymCryptParams>, IRSAFactory<TAsymCryptParams>, IEnumerable<IRSADecryptor>
where TAsymCryptParams : AsymCryptParams
{
public IEnumerable<IRSADecryptor> Decryptors => _params.Decryptors;
public IRSADecryptor this[string key]
{
get
{
var key_params = key.Split(_params.KeyNameSeparator);
if (key_params.Length != 2)
throw new ArgumentException($"Invalid key format. Expected two segments separated by '{_params.KeyNameSeparator}', but received: '{key}'.", nameof(key));
return _params.Decryptors.FirstOrDefault(d => d.Issuer == key_params[0] && d.Audience == key_params[1])
?? throw new KeyNotFoundException($"No decryptor found matching the issuer '{key_params[0]}' and audience '{key_params[1]}'.");
}
}
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"));
}
public IEnumerator<IRSADecryptor> GetEnumerator() => Decryptors.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => Decryptors.GetEnumerator();
public IEnumerable<IRSAEncryptor> Encryptors
{
get
{
foreach (var decryptor in Decryptors)
{
yield return decryptor.Encryptor;
}
}
}
}
}

View File

@@ -0,0 +1,77 @@
using DigitalData.Core.Security.Cryptographer;
namespace DigitalData.Core.Security.Config
{
public class AsymCryptParams : RSAFactoryParams
{
public string PemDirectory { get; init; } = string.Empty;
/// <summary>
/// Represents the separator used to concatenate the components of a file-related token string.
/// </summary>
/// <remarks>
/// The resulting file-related token string is constructed as follows:
/// <c>string.Join(FileNameSeparator, Issuer, Audience, Secret_version)</c>.
/// If <c>Secret_version</c> is not null, it will be included in the concatenation.
/// </remarks>
/// <example>
/// For example, if <c>FileNameSeparator = "_-_"</c>, the output might look like:
/// <c>"Issuer_-_Audience_-_Secret_version"</c>.
/// </example>
public string FileNameSeparator { get; init; } = "_-_";
/// <summary>
/// Represents the separator used to concatenate the components of a key-related token string.
/// </summary>
/// <remarks>
/// The resulting key-related token string is constructed as follows:
/// <c>string.Join(KeyNameSeparator, Issuer, Audience, Secret_version)</c>.
/// If <c>Secret_version</c> is not null, it will be included in the concatenation.
/// </remarks>
/// <example>
/// For example, if <c>KeyNameSeparator = ":"</c>, the output might look like:
/// <c>"Issuer:Audience:Secret_version"</c>.
/// </example>
public string KeyNameSeparator { get; init; } = ":";
public IEnumerable<RSADecryptor> Decryptors { get; init; } = new List<RSADecryptor>();
public override void OnDeserialized()
{
base.OnDeserialized();
// Create root folder if it does not exist
if (!Directory.Exists(PemDirectory))
Directory.CreateDirectory(PemDirectory);
foreach (var decryptor in Decryptors)
{
// set default path
if (decryptor.IsPemNull)
{
var file_name_params = new List<object> { decryptor.Issuer, decryptor.Audience };
if (decryptor.IsEncrypted)
file_name_params.Add(Secrets.Version);
var path = Path.Combine(PemDirectory, string.Join(FileNameSeparator, file_name_params));
if (File.Exists(path))
decryptor.SetPem(File.ReadAllText(path));
else
{
var pem = decryptor.IsEncrypted
? Instance.RSAFactory.CreateEncryptedPrivateKeyPem(pbeParameters: PbeParameters, keySizeInBits: KeySizeInBits, password: Secrets.PBE_PASSWORD)
: Instance.RSAFactory.CreatePrivateKeyPem(keySizeInBits: KeySizeInBits);
decryptor.SetPem(File.ReadAllText(pem));
// Save file in background
Task.Run(async () => await File.WriteAllTextAsync(path: path, pem));
}
}
decryptor.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,22 @@
using DigitalData.Core.Abstractions.Security;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSACryptographer : IRSACryptographer
{
public virtual string Pem { get; init; }
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;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
internal RSACryptographer() { }
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
}
}

View File

@@ -0,0 +1,49 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Extensions;
using System.Security.Cryptography;
namespace DigitalData.Core.Security.Cryptographer
{
public class RSADecryptor : RSACryptographer, IRSADecryptor, IRSACryptographer
{
private string? _pem;
public override string Pem { get => _pem ?? throw PemIsNullException; init => _pem = value; }
public bool IsPemNull => _pem is null;
public bool IsEncrypted { 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();
internal void SetPem(string pem) => _pem = pem;
public void Init()
{
if (_pem is null)
throw PemIsNullException;
if (IsEncrypted)
RSA.ImportFromEncryptedPem(Pem, Secrets.PBE_PASSWORD.AsSpan());
else
RSA.ImportFromPem(Pem);
}
private InvalidOperationException PemIsNullException => new($"Pem is not initialized. Please ensure that the PEM is set or properly loaded from the file. Issuer: {Issuer}, Audience: {Audience}.");
}
}

View File

@@ -1,17 +1,17 @@
using DigitalData.Core.Abstractions.Security; using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Extensions; using DigitalData.Core.Security.Extensions;
namespace DigitalData.Core.Security namespace DigitalData.Core.Security.Cryptographer
{ {
public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer public class RSAEncryptor : RSACryptographer, IRSAEncryptor, IRSACryptographer
{ {
public override required string Pem public override string Pem
{ {
get => base.Pem; get => base.Pem;
init init
{ {
RSA.ImportFromPem(base.Pem);
base.Pem = value; base.Pem = value;
RSA.ImportFromPem(value);
} }
} }

View File

@@ -0,0 +1,54 @@
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
{
protected readonly TRSAFactoryParams _params;
public RSAFactory(IOptions<TRSAFactoryParams> options) => _params = options.Value;
public string CreatePrivateKeyPem(int? keySizeInBits = null)
=> RSA.Create(keySizeInBits ?? _params.KeySizeInBits).ExportRSAPrivateKeyPem();
public string CreateEncryptedPrivateKeyPem(
PbeEncryptionAlgorithm? pbeEncryptionAlgorithm = null,
HashAlgorithmName? hashAlgorithmName = null,
int? iterationCount = null,
int? keySizeInBits = null,
string? password = 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);
}
public string CreateEncryptedPrivateKeyPem(
PbeParameters pbeParameters,
int? keySizeInBits = null,
string? password = null)
{
password ??= _params.PbePassword;
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,104 @@
using DigitalData.Core.Abstractions.Security; 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;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options;
using System.Text.Json;
using System.Text.Json.Serialization;
namespace DigitalData.Core.Security namespace DigitalData.Core.Security
{ {
public static class DIExtensions 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());
return services; if (!options.Converters.OfType<JsonStringEnumConverter>().Any())
options.Converters.Add(new JsonStringEnumConverter());
return options;
}
private static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, bool setAsDefault = false) where TAsymCryptParams : AsymCryptParams
=> setAsDefault
? services.AddScoped<IAsymCryptService, AsymCryptService<TAsymCryptParams>>()
: services.AddScoped<IAsymCryptService<TAsymCryptParams>, AsymCryptService<TAsymCryptParams>>();
/// <summary>
/// Registers a custom asym crypt service with specified parameters from the given configuration section.
/// </summary>
/// <typeparam name="TAsymCryptParams"></typeparam>
/// <param name="services"></param>
/// <param name="section"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, IConfigurationSection section, bool setAsDefault = false)
where TAsymCryptParams : AsymCryptParams
=> services.Configure<TAsymCryptParams>(section).AddAsymCryptService<TAsymCryptParams>(setAsDefault: setAsDefault);
/// <summary>
/// Registers an asym crypt service with the specified parameters from the given instance. Optionally, sets it as the default factory.
/// </summary>
/// <typeparam name="TAsymCryptParams"></typeparam>
/// <param name="services"></param>
/// <param name="param"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService<TAsymCryptParams>(this IServiceCollection services, TAsymCryptParams param, bool setAsDefault = false)
where TAsymCryptParams : AsymCryptParams
=> services.AddSingleton(Options.Create(param)).AddAsymCryptService<TAsymCryptParams>(setAsDefault: setAsDefault);
/// <summary>
/// Registers default asym crypt service with the specified parameters from the given instance.
/// </summary>
/// <param name="services"></param>
/// <param name="param"></param>
/// <returns></returns>
public static IServiceCollection AddAsymCryptService(this IServiceCollection services, AsymCryptParams param) => services.AddAsymCryptService(param: param, setAsDefault: true);
/// <summary>
/// Registers default RSA Factory instance with default params
/// </summary>
/// <param name="services"></param>
/// <param name="factoryParams"></param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddRSAFactory(this IServiceCollection services, RSAFactoryParams? factoryParams = null)
=> services.AddScoped<IRSAFactory>(_ => new RSAFactory<RSAFactoryParams>(Options.Create(factoryParams ?? new())));
/// <summary>
/// Registers a custom RSA Factory with specified parameters from the given configuration section.
/// </summary>
/// <typeparam name="TRSAFactoryParams"></typeparam>
/// <param name="services"></param>
/// <param name="section"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, IConfigurationSection section, bool setAsDefault = false)
where TRSAFactoryParams : RSAFactoryParams
{
services.Configure<TRSAFactoryParams>(section);
return setAsDefault
? services.AddSingleton<IRSAFactory, RSAFactory<TRSAFactoryParams>>()
: services.AddSingleton<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
}
/// <summary>
/// Registers an RSA Factory with the specified parameters from the given instance. Optionally, sets it as the default factory.
/// </summary>
/// <typeparam name="TRSAFactoryParams">The type of the RSA factory parameters.</typeparam>
/// <param name="services"></param>
/// <param name="rsaParams"></param>
/// <param name="setAsDefault">If true, the factory is registered as the default <see cref="IRSAFactory"/>. Otherwise, it is registered as <see cref="IRSAFactory{TRSAFactoryParams}"/>.</param>
/// <returns>The updated <see cref="IServiceCollection"/> with the RSA Factory registered.</returns>
public static IServiceCollection AddRSAFactory<TRSAFactoryParams>(this IServiceCollection services, TRSAFactoryParams rsaParams, bool setAsDefault = false)
where TRSAFactoryParams : RSAFactoryParams
{
services.AddSingleton(Options.Create(rsaParams));
return setAsDefault
? services.AddSingleton<IRSAFactory, RSAFactory<TRSAFactoryParams>>()
: services.AddSingleton<IRSAFactory<TRSAFactoryParams>, RSAFactory<TRSAFactoryParams>>();
} }
} }
} }

View File

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

@@ -0,0 +1,14 @@
using DigitalData.Core.Abstractions.Security;
using DigitalData.Core.Security.Config;
using DigitalData.Core.Security.Cryptographer;
using Microsoft.Extensions.Options;
namespace DigitalData.Core.Security
{
public static class Instance
{
private static readonly Lazy<RSAFactory<RSAFactoryParams>> LazyInstance = new(() => new(Options.Create<RSAFactoryParams>(new())));
public static IRSAFactory RSAFactory => LazyInstance.Value;
}
}

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,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
};
}
}
}