From 6e973a494ed0c85e46b396b3b7b7516e4e3bfc23 Mon Sep 17 00:00:00 2001 From: Developer 02 Date: Thu, 29 Aug 2024 11:35:47 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Implementieren=20der=20Verschl=C3=BCsse?= =?UTF-8?q?lungs-=20und=20Entschl=C3=BCsselungsdienste=20mit=20AES=20und?= =?UTF-8?q?=20Integration=20in=20die=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hinzufügen der `Encryptor`-Klasse für AES-Verschlüsselung und -Entschlüsselung. - Implementierung des `EncryptionController` zur Bereitstellung von Endpunkten für Verschlüsselung, Entschlüsselung und Generierung von Verschlüsselungsparametern. - Erweiterung der DI-Konfiguration mit `AddEncryptor`-Erweiterungsmethode und Integration in `Program.cs`. - Bedingte Registrierung des `EncryptionController` basierend auf der Konfiguration `UseEncryptor`, um sicherzustellen, dass der Controller nur bei Bedarf verfügbar ist. - Implementierung von Lazy Loading für die Verbindungszeichenfolge in `UserManagerDbContext` zur sicheren Handhabung von verschlüsselten Verbindungszeichenfolgen. --- .../Controllers/EncryptionController.cs | 46 +++++++++++ DigitalData.UserManager.API/Program.cs | 31 ++++++-- .../appsettings.Development.json | 3 - DigitalData.UserManager.API/appsettings.json | 14 +++- .../DIExtensions.cs | 9 +++ ...DigitalData.UserManager.Application.csproj | 1 + .../Services/Encryptor.cs | 78 +++++++++++++++++++ .../Services/Options/EncryptionParameters.cs | 9 +++ 8 files changed, 179 insertions(+), 12 deletions(-) create mode 100644 DigitalData.UserManager.API/Controllers/EncryptionController.cs create mode 100644 DigitalData.UserManager.Application/Services/Encryptor.cs create mode 100644 DigitalData.UserManager.Application/Services/Options/EncryptionParameters.cs diff --git a/DigitalData.UserManager.API/Controllers/EncryptionController.cs b/DigitalData.UserManager.API/Controllers/EncryptionController.cs new file mode 100644 index 0000000..5853269 --- /dev/null +++ b/DigitalData.UserManager.API/Controllers/EncryptionController.cs @@ -0,0 +1,46 @@ +using DigitalData.UserManager.Application.Services; +using DigitalData.UserManager.Application.Services.Options; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; + +namespace DigitalData.UserManager.API.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class EncryptionController : ControllerBase + { + private readonly Encryptor _encryptor; + + public EncryptionController(Encryptor encryptor) + { + _encryptor = encryptor; + } + + [HttpPost("encrypt")] + public IActionResult Encrypt([FromQuery] string plainText, [FromBody] EncryptionParameters? options = null) + { + string cipherText = options is null + ? _encryptor.Encrypt(plainText) + : Encryptor.Encrypt(plainText, options.Key, options.IV); + + return Ok(cipherText); + } + + [HttpPost("decrypt")] + public IActionResult Decrypt([FromQuery] string cipherText, [FromBody] EncryptionParameters? options = null) + { + var plainText = options is null + ? _encryptor.Decrypt(cipherText) + : Encryptor.Decrypt(cipherText, options.Key, options.IV); + + return Ok(plainText); + } + + [HttpGet] + public IActionResult Generate() + { + var param = Encryptor.GenerateParameters(); + return Ok(param); + } + } +} \ No newline at end of file diff --git a/DigitalData.UserManager.API/Program.cs b/DigitalData.UserManager.API/Program.cs index c7a8965..415cd1a 100644 --- a/DigitalData.UserManager.API/Program.cs +++ b/DigitalData.UserManager.API/Program.cs @@ -6,12 +6,19 @@ using Microsoft.AspNetCore.Authentication.Cookies; using NLog.Web; using NLog; using DigitalData.Core.API; +using DigitalData.UserManager.API; +using DigitalData.UserManager.API.Controllers; +using DigitalData.UserManager.Application.Services; var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); logger.Debug("init main"); try { var builder = WebApplication.CreateBuilder(args); + + var config = builder.Configuration; + + builder.Services.AddEncryptor(builder.Configuration.GetSection("EncryptionParameters")); if (builder.Configuration.GetValue("RunAsWindowsService")) builder.Host.UseWindowsService(); @@ -27,7 +34,12 @@ try { builder.Services.AddSwaggerGen(); } - builder.Services.AddControllers(); + builder.Services.AddControllers(opt => + { + opt.Conventions.Add(new RemoveIfControllerConvention() + .AndIf(c => c.ControllerName == nameof(EncryptionController).Replace("Controller", "")) + .AndIf(c => !config.GetValue("UseEncryptor"))); + }); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => @@ -39,9 +51,10 @@ try { options.LogoutPath = "/api/auth/logout"; }); - builder.Services.AddDbContext(options => - options.UseSqlServer(builder.Configuration.GetConnectionString("DD_ECM_Connection")) - .EnableDetailedErrors()); + // Once the app is built, the password will be decrypted with Encryptor. lazy loading also acts as a call back method. + Lazy? cnn_str = null; + + builder.Services.AddDbContext(options => options.UseSqlServer(cnn_str!.Value).EnableDetailedErrors()); var allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get() ?? throw new InvalidOperationException("In appsettings there is no allowed origin."); @@ -64,9 +77,17 @@ try { builder.Services.AddDirectorySearchService(); builder.Services.AddCookieBasedLocalizer(); - + var app = builder.Build(); + cnn_str = new(() => + { + var encryptor = app.Services.GetRequiredService(); + var eCnnStr = config.GetConnectionString("DD_ECM_Connection") ?? throw new InvalidOperationException("Connection string 'DD_ECM_Connection' is missing from the configuration."); + var cnnStr = encryptor.Decrypt(eCnnStr); + return cnnStr; + }); + app.UseCors("DefaultCorsPolicy"); if (builder.Configuration.GetValue("UseSwagger")) diff --git a/DigitalData.UserManager.API/appsettings.Development.json b/DigitalData.UserManager.API/appsettings.Development.json index 550c8b9..21e62f6 100644 --- a/DigitalData.UserManager.API/appsettings.Development.json +++ b/DigitalData.UserManager.API/appsettings.Development.json @@ -5,9 +5,6 @@ "Microsoft.AspNetCore": "Warning" } }, - "ConnectionStrings": { - "DD_ECM_Connection": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;" - }, "AllowedOrigins": [ "http://localhost:4200" ], "Jwt": { "Key": "pJBcBWZSjsWlhi1OlCcw6ERTMRNb7qsdvsfvdfbagdfbdfsSDGSDMhsjkfdhsdfbgkHKSDF", diff --git a/DigitalData.UserManager.API/appsettings.json b/DigitalData.UserManager.API/appsettings.json index d106fdc..e00ec43 100644 --- a/DigitalData.UserManager.API/appsettings.json +++ b/DigitalData.UserManager.API/appsettings.json @@ -6,10 +6,9 @@ } }, "ConnectionStrings": { - "DD_ECM_Connection": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;" + "DD_ECM_Connection": "cIFSoeMqHel7SDkAj4MWjy1UHrNJgoHrLkBJ/I/1Y95MsV9vFQjJLn6Shm9qtAyymwSNrX9s+78mW2PX4KulSA/KAaRwNQteP6SHrX0nNOJptot8TcohuiT0m9K2M/GsJEnLyJ+3yb0nJHR5yzRaVvjl8ERhgntW47dFMni98YA=" }, - "AllowedOrigins": [ "http://172.24.12.39:85", "http://localhost:85", "http://localhost:4200", "http://localhost:5500", "https://localhost:7202" ], - "UseSwagger": true, + "AllowedOrigins": [ "https://localhost:7103", "http://172.24.12.39:85", "http://localhost:85", "http://localhost:4200", "http://localhost:5500", "https://localhost:7202" ], "RunAsWindowsService": false, "DirectorySearchOptions": { "ServerName": "DD-VMP01-DC01", @@ -67,5 +66,12 @@ "writeTo": "criticalLogs" } ] - } + }, + "EncryptionParameters": { + "Key": "JGPwHVD0BQmC7upi5OV11PzzIk47ugTJoqBV/et5w40=", + "IV": "gMuetIjlPvJnSzu+i7I3xg==" + }, + // Delete below in production + "UseEncryptor": true, + "UseSwagger": true } \ No newline at end of file diff --git a/DigitalData.UserManager.Application/DIExtensions.cs b/DigitalData.UserManager.Application/DIExtensions.cs index 06e51ce..9117124 100644 --- a/DigitalData.UserManager.Application/DIExtensions.cs +++ b/DigitalData.UserManager.Application/DIExtensions.cs @@ -1,9 +1,11 @@ using DigitalData.UserManager.Application.Contracts; using DigitalData.UserManager.Application.MappingProfiles; using DigitalData.UserManager.Application.Services; +using DigitalData.UserManager.Application.Services.Options; using DigitalData.UserManager.Infrastructure.Contracts; using DigitalData.UserManager.Infrastructure.Repositories; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace DigitalData.UserManager.Application @@ -40,5 +42,12 @@ namespace DigitalData.UserManager.Application .AddScoped() .AddScoped() .AddScoped(); + + public static IServiceCollection AddEncryptor(this IServiceCollection services, IConfiguration configuration) + { + services.AddSingleton(); + services.Configure(configuration); + return services; + } } } diff --git a/DigitalData.UserManager.Application/DigitalData.UserManager.Application.csproj b/DigitalData.UserManager.Application/DigitalData.UserManager.Application.csproj index 52c3242..9e49a15 100644 --- a/DigitalData.UserManager.Application/DigitalData.UserManager.Application.csproj +++ b/DigitalData.UserManager.Application/DigitalData.UserManager.Application.csproj @@ -28,6 +28,7 @@ + diff --git a/DigitalData.UserManager.Application/Services/Encryptor.cs b/DigitalData.UserManager.Application/Services/Encryptor.cs new file mode 100644 index 0000000..ea6c402 --- /dev/null +++ b/DigitalData.UserManager.Application/Services/Encryptor.cs @@ -0,0 +1,78 @@ +using DigitalData.UserManager.Application.Services.Options; +using Microsoft.Extensions.Options; +using System.Security.Cryptography; +using System.Text; + +namespace DigitalData.UserManager.Application.Services +{ + public class Encryptor + { + public const int KeyByteLength = 32; + + private readonly EncryptionParameters _params; + + public Encryptor(IOptions options) + { + _params = options.Value; + } + + public string Encrypt(string plainText) => Encrypt(plainText, _params.Key, _params.IV); + + public string Decrypt(string cipherText) => Decrypt(cipherText, _params.Key, _params.IV); + + public static string Encrypt(string plainText, string key, string iv) + { + using Aes aes = Aes.Create(); + aes.KeySize = KeyByteLength * 8; + aes.Key = AdjustKeySize(Encoding.UTF8.GetBytes(key), aes.KeySize / 8); + aes.IV = AdjustKeySize(Encoding.UTF8.GetBytes(iv), aes.BlockSize / 8); + + ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV); + + using MemoryStream ms = new(); + using (CryptoStream cs = new(ms, encryptor, CryptoStreamMode.Write)) + { + using StreamWriter sw = new(cs); + sw.Write(plainText); + } + return Convert.ToBase64String(ms.ToArray()); + } + + public static string Decrypt(string cipherText, string key, string iv) + { + using Aes aes = Aes.Create(); + aes.KeySize = KeyByteLength * 8; + aes.Key = AdjustKeySize(Encoding.UTF8.GetBytes(key), aes.KeySize / 8); + aes.IV = AdjustKeySize(Encoding.UTF8.GetBytes(iv), aes.BlockSize / 8); + + ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV); + + using MemoryStream ms = new(Convert.FromBase64String(cipherText)); + using CryptoStream cs = new(ms, decryptor, CryptoStreamMode.Read); + using StreamReader sr = new(cs); + return sr.ReadToEnd(); + } + + public static EncryptionParameters GenerateParameters() + { + using Aes aes = Aes.Create(); + aes.KeySize = KeyByteLength * 8; + aes.GenerateKey(); + aes.GenerateIV(); + return new EncryptionParameters + { + Key = Convert.ToBase64String(aes.Key), + IV = Convert.ToBase64String(aes.IV) + }; + } + + private static byte[] AdjustKeySize(byte[] key, int size) + { + if (key.Length < size) + Array.Resize(ref key, size); + else if (key.Length > size) + Array.Resize(ref key, size); + return key; + } + } +} \ No newline at end of file diff --git a/DigitalData.UserManager.Application/Services/Options/EncryptionParameters.cs b/DigitalData.UserManager.Application/Services/Options/EncryptionParameters.cs new file mode 100644 index 0000000..c4ee42c --- /dev/null +++ b/DigitalData.UserManager.Application/Services/Options/EncryptionParameters.cs @@ -0,0 +1,9 @@ +namespace DigitalData.UserManager.Application.Services.Options +{ + public class EncryptionParameters + { + public required string Key { get; init; } + + public required string IV { get; init; } + } +} \ No newline at end of file