186 lines
5.3 KiB
C#
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.
|
|
}
|
|
}
|
|
} |