feat: Implementieren der Verschlüsselungs- und Entschlüsselungsdienste mit AES und Integration in die API

- 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.
This commit is contained in:
Developer 02 2024-08-29 11:35:47 +02:00
parent c8bcb5a6ac
commit 6e973a494e
8 changed files with 179 additions and 12 deletions

View File

@ -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);
}
}
}

View File

@ -6,6 +6,9 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using NLog.Web; using NLog.Web;
using NLog; using NLog;
using DigitalData.Core.API; using DigitalData.Core.API;
using DigitalData.UserManager.API;
using DigitalData.UserManager.API.Controllers;
using DigitalData.UserManager.Application.Services;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger(); var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Debug("init main"); logger.Debug("init main");
@ -13,6 +16,10 @@ logger.Debug("init main");
try { try {
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
builder.Services.AddEncryptor(builder.Configuration.GetSection("EncryptionParameters"));
if (builder.Configuration.GetValue<bool>("RunAsWindowsService")) if (builder.Configuration.GetValue<bool>("RunAsWindowsService"))
builder.Host.UseWindowsService(); builder.Host.UseWindowsService();
@ -27,7 +34,12 @@ try {
builder.Services.AddSwaggerGen(); 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<bool>("UseEncryptor")));
});
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => .AddCookie(options =>
@ -39,9 +51,10 @@ try {
options.LogoutPath = "/api/auth/logout"; options.LogoutPath = "/api/auth/logout";
}); });
builder.Services.AddDbContext<UserManagerDbContext>(options => // Once the app is built, the password will be decrypted with Encryptor. lazy loading also acts as a call back method.
options.UseSqlServer(builder.Configuration.GetConnectionString("DD_ECM_Connection")) Lazy<string>? cnn_str = null;
.EnableDetailedErrors());
builder.Services.AddDbContext<UserManagerDbContext>(options => options.UseSqlServer(cnn_str!.Value).EnableDetailedErrors());
var allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get<string[]>() ?? throw new InvalidOperationException("In appsettings there is no allowed origin."); var allowedOrigins = builder.Configuration.GetSection("AllowedOrigins").Get<string[]>() ?? throw new InvalidOperationException("In appsettings there is no allowed origin.");
@ -67,6 +80,14 @@ try {
var app = builder.Build(); var app = builder.Build();
cnn_str = new(() =>
{
var encryptor = app.Services.GetRequiredService<Encryptor>();
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"); app.UseCors("DefaultCorsPolicy");
if (builder.Configuration.GetValue<bool>("UseSwagger")) if (builder.Configuration.GetValue<bool>("UseSwagger"))

View File

@ -5,9 +5,6 @@
"Microsoft.AspNetCore": "Warning" "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" ], "AllowedOrigins": [ "http://localhost:4200" ],
"Jwt": { "Jwt": {
"Key": "pJBcBWZSjsWlhi1OlCcw6ERTMRNb7qsdvsfvdfbagdfbdfsSDGSDMhsjkfdhsdfbgkHKSDF", "Key": "pJBcBWZSjsWlhi1OlCcw6ERTMRNb7qsdvsfvdfbagdfbdfsSDGSDMhsjkfdhsdfbgkHKSDF",

View File

@ -6,10 +6,9 @@
} }
}, },
"ConnectionStrings": { "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" ], "AllowedOrigins": [ "https://localhost:7103", "http://172.24.12.39:85", "http://localhost:85", "http://localhost:4200", "http://localhost:5500", "https://localhost:7202" ],
"UseSwagger": true,
"RunAsWindowsService": false, "RunAsWindowsService": false,
"DirectorySearchOptions": { "DirectorySearchOptions": {
"ServerName": "DD-VMP01-DC01", "ServerName": "DD-VMP01-DC01",
@ -67,5 +66,12 @@
"writeTo": "criticalLogs" "writeTo": "criticalLogs"
} }
] ]
} },
"EncryptionParameters": {
"Key": "JGPwHVD0BQmC7upi5OV11PzzIk47ugTJoqBV/et5w40=",
"IV": "gMuetIjlPvJnSzu+i7I3xg=="
},
// Delete below in production
"UseEncryptor": true,
"UseSwagger": true
} }

View File

@ -1,9 +1,11 @@
using DigitalData.UserManager.Application.Contracts; using DigitalData.UserManager.Application.Contracts;
using DigitalData.UserManager.Application.MappingProfiles; using DigitalData.UserManager.Application.MappingProfiles;
using DigitalData.UserManager.Application.Services; using DigitalData.UserManager.Application.Services;
using DigitalData.UserManager.Application.Services.Options;
using DigitalData.UserManager.Infrastructure.Contracts; using DigitalData.UserManager.Infrastructure.Contracts;
using DigitalData.UserManager.Infrastructure.Repositories; using DigitalData.UserManager.Infrastructure.Repositories;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace DigitalData.UserManager.Application namespace DigitalData.UserManager.Application
@ -40,5 +42,12 @@ namespace DigitalData.UserManager.Application
.AddScoped<IModuleService, ModuleService>() .AddScoped<IModuleService, ModuleService>()
.AddScoped<IModuleOfUserService, ModuleOfUserService>() .AddScoped<IModuleOfUserService, ModuleOfUserService>()
.AddScoped<IUserRepService, UserRepService>(); .AddScoped<IUserRepService, UserRepService>();
public static IServiceCollection AddEncryptor(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<Encryptor>();
services.Configure<EncryptionParameters>(configuration);
return services;
}
} }
} }

View File

@ -28,6 +28,7 @@
<PackageReference Include="DigitalData.Core.Application" Version="1.0.0" /> <PackageReference Include="DigitalData.Core.Application" Version="1.0.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher.Abstraction" Version="1.0.0" /> <PackageReference Include="DigitalData.EmailProfilerDispatcher.Abstraction" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" /> <PackageReference Include="Microsoft.Extensions.Localization.Abstractions" Version="7.0.16" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices" Version="7.0.1" /> <PackageReference Include="System.DirectoryServices" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" /> <PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" /> <PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />

View File

@ -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<EncryptionParameters> 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;
}
}
}

View File

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