Improve error handling and stuck process issues

This commit is contained in:
Kenneth Skovhede
2026-03-02 15:51:26 +01:00
parent 6ec7a24bb8
commit b6ef3d1b2b
3 changed files with 156 additions and 94 deletions
@@ -392,38 +392,56 @@ public static class CertificateConfigurationHelper
return result;
}
// Check if CA itself is expired
if (caPair.Certificate.NotAfter < DateTime.UtcNow)
try
{
Log.WriteErrorMessage(LOGTAG, "CAExpired", null, "Cannot renew certificate: CA certificate has expired.");
result.RenewalFailedReason = "CA certificate has expired.";
return result;
// Check if CA itself is expired
if (caPair.Certificate.NotAfter < DateTime.UtcNow)
{
Log.WriteErrorMessage(LOGTAG, "CAExpired", null, "Cannot renew certificate: CA certificate has expired.");
result.RenewalFailedReason = "CA certificate has expired.";
return result;
}
// Parse hostnames
var hostnameList = CertificateRenewalChecker.ParseHostnames(hostnames);
Log.WriteInformationMessage(LOGTAG, "GeneratingCertificate", $"Generating new server certificate with hostnames: {string.Join(", ", hostnameList)}");
var newServerPair = CertificateGenerator.GenerateServerCertificate(caPair.Certificate, caPair.PrivateKey, hostnameList);
try
{
// Generate new password for PFX
var pfxPassword = CertificateStorageHelper.GeneratePfxPassword();
// Create PFX bundle
var pfxBytes = CertificateGenerator.CreatePfxBundle(newServerPair, caPair, pfxPassword);
var pfxBase64 = Convert.ToBase64String(pfxBytes);
Log.WriteInformationMessage(LOGTAG, "CertificateRenewed", $"Server certificate renewed successfully. New certificate expires: {newServerPair.Certificate.NotAfter:yyyy-MM-dd}");
result.Renewed = true;
result.RenewedCertificate = new ServerCertificateData
{
ServerCertificate = pfxBase64,
Password = pfxPassword,
Autogenerated = true
};
return result;
}
finally
{
// Dispose server certificate key material promptly
newServerPair.PrivateKey.Dispose();
newServerPair.Certificate.Dispose();
}
}
// Parse hostnames
var hostnameList = CertificateRenewalChecker.ParseHostnames(hostnames);
Log.WriteInformationMessage(LOGTAG, "GeneratingCertificate", $"Generating new server certificate with hostnames: {string.Join(", ", hostnameList)}");
var newServerPair = CertificateGenerator.GenerateServerCertificate(caPair.Certificate, caPair.PrivateKey, hostnameList);
// Generate new password for PFX
var pfxPassword = CertificateStorageHelper.GeneratePfxPassword();
// Create PFX bundle
var pfxBytes = CertificateGenerator.CreatePfxBundle(newServerPair, caPair, pfxPassword);
var pfxBase64 = Convert.ToBase64String(pfxBytes);
Log.WriteInformationMessage(LOGTAG, "CertificateRenewed", $"Server certificate renewed successfully. New certificate expires: {newServerPair.Certificate.NotAfter:yyyy-MM-dd}");
result.Renewed = true;
result.RenewedCertificate = new ServerCertificateData
finally
{
ServerCertificate = pfxBase64,
Password = pfxPassword,
Autogenerated = true
};
return result;
// Dispose CA certificate key material promptly
caPair.PrivateKey.Dispose();
caPair.Certificate.Dispose();
}
}
catch (Exception ex)
{
@@ -521,67 +539,85 @@ public static class CertificateConfigurationHelper
Log.WriteInformationMessage(LOGTAG, "GeneratingCA", "Generating CA certificate...");
var caPair = CertificateGenerator.GenerateCACertificate();
// Generate server certificate
Log.WriteInformationMessage(LOGTAG, "GeneratingServerCert", "Generating server certificate...");
var serverPair = CertificateGenerator.GenerateServerCertificate(caPair.Certificate, caPair.PrivateKey, hostnameList);
// Generate separate passwords for CA key encryption and server PFX
var caPassword = CertificateStorageHelper.GeneratePfxPassword();
var pfxPassword = CertificateStorageHelper.GeneratePfxPassword();
// Install CA in trust store if not skipped
if (!skipTrustInstallation)
try
{
Log.WriteInformationMessage(LOGTAG, "InstallingCA", "Installing CA certificate in system trust store...");
var installResult = InstallCATrust(caPair.Certificate, storeLocation, linuxCertDirectory, macOSKeychainPath);
result.TrustInstallationStatus = installResult.Status;
// Generate server certificate
Log.WriteInformationMessage(LOGTAG, "GeneratingServerCert", "Generating server certificate...");
var serverPair = CertificateGenerator.GenerateServerCertificate(caPair.Certificate, caPair.PrivateKey, hostnameList);
switch (installResult.Status)
try
{
case CATrustInstallationStatus.Success:
Log.WriteInformationMessage(LOGTAG, "CAInstalled", "CA certificate installed successfully.");
break;
case CATrustInstallationStatus.AlreadyInstalled:
Log.WriteInformationMessage(LOGTAG, "CAAlreadyInstalled", "CA certificate was already installed.");
break;
case CATrustInstallationStatus.NotSupported:
Log.WriteWarningMessage(LOGTAG, "NoTrustInstaller", null, "No trust installer available for this platform.");
break;
case CATrustInstallationStatus.RequiresElevation:
Log.WriteWarningMessage(LOGTAG, "CARequiresElevation", null, "Administrator/root privileges required to install CA certificate.");
break;
case CATrustInstallationStatus.Failed:
Log.WriteWarningMessage(LOGTAG, "CAInstallFailed", null, "Failed to install CA certificate.");
break;
// Generate separate passwords for CA key encryption and server PFX
var caPassword = CertificateStorageHelper.GeneratePfxPassword();
var pfxPassword = CertificateStorageHelper.GeneratePfxPassword();
// Install CA in trust store if not skipped
if (!skipTrustInstallation)
{
Log.WriteInformationMessage(LOGTAG, "InstallingCA", "Installing CA certificate in system trust store...");
var installResult = InstallCATrust(caPair.Certificate, storeLocation, linuxCertDirectory, macOSKeychainPath);
result.TrustInstallationStatus = installResult.Status;
switch (installResult.Status)
{
case CATrustInstallationStatus.Success:
Log.WriteInformationMessage(LOGTAG, "CAInstalled", "CA certificate installed successfully.");
break;
case CATrustInstallationStatus.AlreadyInstalled:
Log.WriteInformationMessage(LOGTAG, "CAAlreadyInstalled", "CA certificate was already installed.");
break;
case CATrustInstallationStatus.NotSupported:
Log.WriteWarningMessage(LOGTAG, "NoTrustInstaller", null, "No trust installer available for this platform.");
break;
case CATrustInstallationStatus.RequiresElevation:
Log.WriteWarningMessage(LOGTAG, "CARequiresElevation", null, "Administrator/root privileges required to install CA certificate.");
break;
case CATrustInstallationStatus.Failed:
Log.WriteWarningMessage(LOGTAG, "CAInstallFailed", null, "Failed to install CA certificate.");
break;
}
}
else
{
Log.WriteInformationMessage(LOGTAG, "SkippingCATrust", "Skipping CA trust installation.");
}
// Serialize certificates
Log.WriteInformationMessage(LOGTAG, "StoringCertificates", "Serializing certificates...");
var (caCertBase64, caKeyEncrypted) = CertificateStorageHelper.SerializeCACertificatePair(caPair, caPassword);
var pfxBytes = CertificateGenerator.CreatePfxBundle(serverPair, caPair, pfxPassword);
var pfxBase64 = Convert.ToBase64String(pfxBytes);
Log.WriteInformationMessage(LOGTAG, "CertificatesGenerated", "HTTPS certificates generated successfully.");
result.Success = true;
result.CACertificate = new CACertificateData
{
CACertificate = caCertBase64,
CAKey = caKeyEncrypted,
CAPassword = caPassword
};
result.ServerCertificate = new ServerCertificateData
{
ServerCertificate = pfxBase64,
Password = pfxPassword,
Autogenerated = true
};
return result;
}
finally
{
// Dispose server certificate key material promptly
serverPair.PrivateKey.Dispose();
serverPair.Certificate.Dispose();
}
}
else
finally
{
Log.WriteInformationMessage(LOGTAG, "SkippingCATrust", "Skipping CA trust installation.");
// Dispose CA certificate key material promptly
caPair.PrivateKey.Dispose();
caPair.Certificate.Dispose();
}
// Serialize certificates
Log.WriteInformationMessage(LOGTAG, "StoringCertificates", "Serializing certificates...");
var (caCertBase64, caKeyEncrypted) = CertificateStorageHelper.SerializeCACertificatePair(caPair, caPassword);
var pfxBytes = CertificateGenerator.CreatePfxBundle(serverPair, caPair, pfxPassword);
var pfxBase64 = Convert.ToBase64String(pfxBytes);
Log.WriteInformationMessage(LOGTAG, "CertificatesGenerated", "HTTPS certificates generated successfully.");
result.Success = true;
result.CACertificate = new CACertificateData
{
CACertificate = caCertBase64,
CAKey = caKeyEncrypted,
CAPassword = caPassword
};
result.ServerCertificate = new ServerCertificateData
{
ServerCertificate = pfxBase64,
Password = pfxPassword,
Autogenerated = true
};
return result;
}
}
@@ -222,9 +222,24 @@ public class LinuxCATrustInstaller : ICATrustInstaller
if (process == null)
return (-1, string.Empty, $"Failed to start process: {fileName}");
// Read stdout/stderr to completion before waiting for exit to avoid
// deadlocks when the process output fills the OS pipe buffer.
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
if (!Task.WaitAll([outputTask, errorTask], PROCESS_TIMEOUT))
{
try
{
process.Kill();
}
catch
{
// Ignore kill errors
}
return (-1, string.Empty, $"Process timed out after {PROCESS_TIMEOUT}");
}
process.WaitForExit(PROCESS_TIMEOUT);
if (!process.HasExited)
{
@@ -239,9 +254,6 @@ public class LinuxCATrustInstaller : ICATrustInstaller
return (-1, string.Empty, $"Process timed out after {PROCESS_TIMEOUT}");
}
var output = outputTask.Result;
var error = errorTask.Result;
return (process.ExitCode, output, error);
return (process.ExitCode, outputTask.Result, errorTask.Result);
}
}
@@ -142,8 +142,10 @@ public class MacOSCATrustInstaller : ICATrustInstaller
try
{
// Find and delete certificate
var result = RunSecurityCommand("delete-certificate", "-c", caCertificate.Subject, KeychainPath);
// Delete certificate by SHA-1 hash to avoid accidentally removing
// other certificates that share the same subject name.
var hash = caCertificate.GetCertHashString();
var result = RunSecurityCommand("delete-certificate", "-Z", hash, KeychainPath);
if (result.ExitCode == 0)
return TrustUninstallationResult.Success;
@@ -181,9 +183,24 @@ public class MacOSCATrustInstaller : ICATrustInstaller
if (process == null)
return (-1, string.Empty, "Failed to start security process");
// Read stdout/stderr to completion before waiting for exit to avoid
// deadlocks when the process output fills the OS pipe buffer.
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
if (!Task.WaitAll([outputTask, errorTask], PROCESS_TIMEOUT))
{
try
{
process.Kill();
}
catch
{
// Ignore kill errors
}
return (-1, string.Empty, $"Security process timed out after {PROCESS_TIMEOUT}");
}
process.WaitForExit(PROCESS_TIMEOUT);
if (!process.HasExited)
{
@@ -198,9 +215,6 @@ public class MacOSCATrustInstaller : ICATrustInstaller
return (-1, string.Empty, $"Security process timed out after {PROCESS_TIMEOUT}");
}
var output = outputTask.Result;
var error = errorTask.Result;
return (process.ExitCode, output, error);
return (process.ExitCode, outputTask.Result, errorTask.Result);
}
}