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() .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);