154 lines
4.5 KiB
C#
154 lines
4.5 KiB
C#
using System.Security.Cryptography.X509Certificates;
|
|
using ZA.CoreService.ESBCertificateManager.Models;
|
|
using static ZA.CoreService.ESBCertificateManager.Models.RuntimeSettings;
|
|
|
|
namespace ZA.CoreService.ESBCertificateManager.Services;
|
|
|
|
public sealed class AlwaysEncryptedCertificateService
|
|
{
|
|
private readonly string _expectedThumbprint;
|
|
private readonly StoreName _storeName;
|
|
private readonly StoreLocation _storeLocation;
|
|
|
|
public AlwaysEncryptedCertificateService(
|
|
AlwaysEncryptedSettings settings)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(
|
|
settings.CertificateThumbprint))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"AlwaysEncrypted:CertificateThumbprint fehlt.");
|
|
}
|
|
|
|
_expectedThumbprint = NormalizeThumbprint(
|
|
settings.CertificateThumbprint);
|
|
|
|
if (!Enum.TryParse(
|
|
settings.StoreName,
|
|
ignoreCase: true,
|
|
out _storeName))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Ungültiger Zertifikatsspeicher: '{settings.StoreName}'.");
|
|
}
|
|
|
|
if (!Enum.TryParse(
|
|
settings.StoreLocation,
|
|
ignoreCase: true,
|
|
out _storeLocation))
|
|
{
|
|
throw new InvalidOperationException(
|
|
$"Ungültiger Zertifikatsspeicherort: " +
|
|
$"'{settings.StoreLocation}'.");
|
|
}
|
|
}
|
|
|
|
public CertificateCheckResult CheckCertificate()
|
|
{
|
|
using X509Store store = new(
|
|
_storeName,
|
|
_storeLocation);
|
|
|
|
store.Open(OpenFlags.ReadOnly);
|
|
|
|
X509Certificate2? certificate = store.Certificates
|
|
.Find(
|
|
X509FindType.FindByThumbprint,
|
|
_expectedThumbprint,
|
|
validOnly: false)
|
|
.OfType<X509Certificate2>()
|
|
.FirstOrDefault();
|
|
|
|
if (certificate is null)
|
|
{
|
|
return new CertificateCheckResult(
|
|
IsValid: false,
|
|
Message:
|
|
$"Das Always-Encrypted-Zertifikat wurde nicht gefunden. " +
|
|
$"Speicher: {_storeLocation}\\{_storeName}, " +
|
|
$"Thumbprint: {_expectedThumbprint}");
|
|
}
|
|
|
|
if (!certificate.HasPrivateKey)
|
|
{
|
|
return new CertificateCheckResult(
|
|
IsValid: false,
|
|
Message:
|
|
"Das Always-Encrypted-Zertifikat wurde gefunden, " +
|
|
"besitzt aber keinen verfügbaren privaten Schlüssel.");
|
|
}
|
|
|
|
return new CertificateCheckResult(
|
|
IsValid: true,
|
|
Message:
|
|
"Always-Encrypted-Zertifikat und privater " +
|
|
"Schlüssel sind verfügbar.");
|
|
}
|
|
|
|
public void ImportPfx(
|
|
string pfxFilePath,
|
|
string pfxPassword)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(pfxFilePath))
|
|
{
|
|
throw new ArgumentException(
|
|
"Es wurde keine PFX-Datei ausgewählt.",
|
|
nameof(pfxFilePath));
|
|
}
|
|
|
|
if (!File.Exists(pfxFilePath))
|
|
{
|
|
throw new FileNotFoundException(
|
|
"Die ausgewählte PFX-Datei wurde nicht gefunden.",
|
|
pfxFilePath);
|
|
}
|
|
|
|
X509KeyStorageFlags flags =
|
|
X509KeyStorageFlags.UserKeySet
|
|
| X509KeyStorageFlags.PersistKeySet;
|
|
|
|
using X509Certificate2 certificate = new(
|
|
pfxFilePath,
|
|
pfxPassword,
|
|
flags);
|
|
|
|
string importedThumbprint =
|
|
NormalizeThumbprint(certificate.Thumbprint);
|
|
|
|
if (!string.Equals(
|
|
importedThumbprint,
|
|
_expectedThumbprint,
|
|
StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Die ausgewählte PFX-Datei gehört nicht zum " +
|
|
"erwarteten Always-Encrypted-Zertifikat.");
|
|
}
|
|
|
|
if (!certificate.HasPrivateKey)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Die ausgewählte PFX-Datei enthält keinen privaten Schlüssel.");
|
|
}
|
|
|
|
using X509Store store = new(
|
|
_storeName,
|
|
_storeLocation);
|
|
|
|
store.Open(OpenFlags.ReadWrite);
|
|
store.Add(certificate);
|
|
}
|
|
|
|
private static string NormalizeThumbprint(
|
|
string thumbprint)
|
|
{
|
|
return thumbprint
|
|
.Replace(" ", string.Empty)
|
|
.Trim()
|
|
.ToUpperInvariant();
|
|
}
|
|
}
|
|
|
|
public sealed record CertificateCheckResult(
|
|
bool IsValid,
|
|
string Message); |