Files
123123/ZA.CoreService.ESBCertificateManager/Services/LocalCertificateDeployer.cs
T

186 lines
5.3 KiB
C#

namespace ZA.CoreService.ESBCertificateManager.Services;
public sealed class LocalCertificateDeployer
{
private static readonly HashSet<string> CertificateExtensions =
new(StringComparer.OrdinalIgnoreCase)
{
".cer",
".crt",
".pem",
".pfx",
".p12"
};
public async Task<string> DeployAsync(
string sourceCertificatePath,
string configuredTargetDirectory,
string targetFileName,
bool backupEnabled,
string backupDirectoryName,
CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(sourceCertificatePath))
{
throw new ArgumentException(
"Es wurde keine Zertifikatsquelle angegeben.",
nameof(sourceCertificatePath));
}
if (!File.Exists(sourceCertificatePath))
{
throw new FileNotFoundException(
"Die ausgewählte Zertifikatsdatei wurde nicht gefunden.",
sourceCertificatePath);
}
if (string.IsNullOrWhiteSpace(configuredTargetDirectory))
{
throw new ArgumentException(
"Der Zertifikats-Zielordner ist nicht konfiguriert.",
nameof(configuredTargetDirectory));
}
if (string.IsNullOrWhiteSpace(targetFileName))
{
throw new ArgumentException(
"Der Zertifikats-Zieldateiname ist nicht konfiguriert.",
nameof(targetFileName));
}
if (Path.GetFileName(targetFileName) != targetFileName)
{
throw new InvalidOperationException(
"TargetFileName darf keinen Verzeichnispfad enthalten.");
}
string targetDirectory = Path.GetFullPath(
Path.IsPathRooted(configuredTargetDirectory)
? configuredTargetDirectory
: Path.Combine(
AppContext.BaseDirectory,
configuredTargetDirectory));
cancellationToken.ThrowIfCancellationRequested();
Directory.CreateDirectory(targetDirectory);
string destinationPath = Path.Combine(
targetDirectory,
targetFileName);
string temporaryPath = destinationPath + ".tmp";
try
{
if (backupEnabled)
{
BackupExistingCertificates(
targetDirectory,
backupDirectoryName,
cancellationToken);
}
await using (FileStream source = new(
sourceCertificatePath,
FileMode.Open,
FileAccess.Read,
FileShare.Read))
await using (FileStream temporaryTarget = new(
temporaryPath,
FileMode.Create,
FileAccess.Write,
FileShare.None))
{
await source.CopyToAsync(
temporaryTarget,
cancellationToken);
}
cancellationToken.ThrowIfCancellationRequested();
File.Move(
temporaryPath,
destinationPath,
overwrite: true);
return destinationPath;
}
catch
{
TryDeleteTemporaryFile(temporaryPath);
throw;
}
}
private static void BackupExistingCertificates(
string targetDirectory,
string configuredBackupDirectoryName,
CancellationToken cancellationToken)
{
List<string> filesToBackup = Directory
.EnumerateFiles(
targetDirectory,
"*",
SearchOption.TopDirectoryOnly)
.Where(path =>
CertificateExtensions.Contains(
Path.GetExtension(path)))
.ToList();
if (filesToBackup.Count == 0)
{
return;
}
string backupDirectoryName =
string.IsNullOrWhiteSpace(configuredBackupDirectoryName)
? "Backup"
: configuredBackupDirectoryName.Trim();
if (Path.GetFileName(backupDirectoryName)
!= backupDirectoryName)
{
throw new InvalidOperationException(
"BackupDirectoryName darf keinen Verzeichnispfad enthalten.");
}
string backupDirectory = Path.Combine(
targetDirectory,
backupDirectoryName,
DateTime.Now.ToString("yyyyMMdd_HHmmss_fff"));
Directory.CreateDirectory(backupDirectory);
foreach (string certificateFile in filesToBackup)
{
cancellationToken.ThrowIfCancellationRequested();
string backupPath = Path.Combine(
backupDirectory,
Path.GetFileName(certificateFile));
File.Copy(
certificateFile,
backupPath,
overwrite: false);
}
}
private static void TryDeleteTemporaryFile(
string temporaryPath)
{
try
{
if (File.Exists(temporaryPath))
{
File.Delete(temporaryPath);
}
}
catch
{
// Der ursprüngliche Fehler darf nicht verdeckt werden.
// TODO: Aufräumfehler später separat protokollieren.
}
}
}