Files
duplicati/Duplicati/Library/Certificates/CertificateConfigurationHelper.cs
T

624 lines
30 KiB
C#

// Copyright (C) 2025, The Duplicati Team
// https://duplicati.com, hello@duplicati.com
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Security.Cryptography.X509Certificates;
using Duplicati.Library.Logging;
namespace Duplicati.Library.Certificates;
/// <summary>
/// Result of a CA trust installation operation.
/// </summary>
public enum CATrustInstallationStatus
{
/// <summary>Installation was successful.</summary>
Success,
/// <summary>Certificate was already installed.</summary>
AlreadyInstalled,
/// <summary>Installation requires elevated privileges.</summary>
RequiresElevation,
/// <summary>Installation failed.</summary>
Failed,
/// <summary>No trust installer available for this platform.</summary>
NotSupported
}
/// <summary>
/// Result of a CA trust installation attempt.
/// </summary>
public class CATrustInstallationResult
{
/// <summary>
/// The status of the installation.
/// </summary>
public CATrustInstallationStatus Status { get; set; }
/// <summary>
/// An optional error message if installation failed.
/// </summary>
public string? ErrorMessage { get; set; }
}
/// <summary>
/// Status information about the current certificate configuration.
/// </summary>
public class CertificateStatusInfo
{
/// <summary>
/// The CA certificate, if available.
/// </summary>
public X509Certificate2? CACert { get; set; }
/// <summary>
/// The server certificate (with private key), if available.
/// </summary>
public X509Certificate2? ServerCert { get; set; }
/// <summary>
/// Whether the certificate was autogenerated.
/// </summary>
public bool IsAutogenerated { get; set; }
/// <summary>
/// Whether the CA certificate is installed in the system trust store.
/// </summary>
public bool IsCATrusted { get; set; }
/// <summary>
/// Days until the CA certificate expires, or null if no CA certificate.
/// </summary>
public double? CaDaysUntilExpiry { get; set; }
/// <summary>
/// Days until the server certificate expires, or null if no server certificate.
/// </summary>
public double? ServerDaysUntilExpiry { get; set; }
/// <summary>
/// DNS names from the server certificate's Subject Alternative Names.
/// </summary>
public List<string> DnsNames { get; set; } = new();
/// <summary>
/// IP addresses from the server certificate's Subject Alternative Names.
/// </summary>
public List<string> IpAddresses { get; set; } = new();
/// <summary>
/// Whether database field encryption is enabled.
/// </summary>
public bool IsEncryptingFields { get; set; }
}
/// <summary>
/// Helper class for certificate configuration operations.
/// Provides high-level operations for generating, renewing, and managing HTTPS certificates.
/// </summary>
public static class CertificateConfigurationHelper
{
/// <summary>
/// The log tag for messages from this class.
/// </summary>
private static readonly string LOGTAG = Log.LogTagFromType(typeof(CertificateConfigurationHelper));
/// <summary>
/// Installs a CA certificate in the system trust store.
/// </summary>
/// <param name="caCert">The CA certificate to install.</param>
/// <param name="storeLocation">The store location to use (Windows only). If null, auto-detects based on privileges.</param>
/// <param name="linuxCertDirectory">The certificate directory to use (Linux only). If null, uses the default system location.</param>
/// <param name="macOSKeychainPath">The keychain path to use (macOS only). If null, uses the default login keychain.</param>
/// <returns>The result of the installation attempt.</returns>
public static CATrustInstallationResult InstallCATrust(X509Certificate2 caCert, StoreLocation? storeLocation, string? linuxCertDirectory, string? macOSKeychainPath)
{
var installer = CATrustInstallerFactory.CreateInstaller(storeLocation, linuxCertDirectory, macOSKeychainPath);
if (installer == null)
{
return new CATrustInstallationResult
{
Status = CATrustInstallationStatus.NotSupported,
ErrorMessage = "No trust installer available for this platform."
};
}
var result = installer.Install(caCert);
return new CATrustInstallationResult
{
Status = result switch
{
TrustInstallationResult.Success => CATrustInstallationStatus.Success,
TrustInstallationResult.AlreadyInstalled => CATrustInstallationStatus.AlreadyInstalled,
TrustInstallationResult.RequiresElevation => CATrustInstallationStatus.RequiresElevation,
_ => CATrustInstallationStatus.Failed
}
};
}
/// <summary>
/// Checks if a CA certificate is installed in the system trust store.
/// </summary>
/// <param name="caCert">The CA certificate to check.</param>
/// <param name="storeLocation">The store location to use (Windows only). If null, auto-detects based on privileges.</param>
/// <param name="linuxCertDirectory">The certificate directory to use (Linux only). If null, uses the default system location.</param>
/// <param name="macOSKeychainPath">The keychain path to use (macOS only). If null, uses the default login keychain.</param>
/// <returns>True if the certificate is installed; false otherwise.</returns>
public static bool IsCATrustInstalled(X509Certificate2 caCert, StoreLocation? storeLocation, string? linuxCertDirectory, string? macOSKeychainPath)
{
var installer = CATrustInstallerFactory.CreateInstaller(storeLocation, linuxCertDirectory, macOSKeychainPath);
return installer != null && installer.IsInstalled(caCert);
}
/// <summary>
/// Removes a CA certificate from the system trust store.
/// </summary>
/// <param name="caCert">The CA certificate to remove.</param>
/// <param name="storeLocation">The store location to use (Windows only). If null, auto-detects based on privileges.</param>
/// <param name="linuxCertDirectory">The certificate directory to use (Linux only). If null, uses the default system location.</param>
/// <param name="macOSKeychainPath">The keychain path to use (macOS only). If null, uses the default login keychain.</param>
/// <returns>True if removal was successful or certificate was not installed; false otherwise.</returns>
public static bool RemoveCATrust(X509Certificate2 caCert, StoreLocation? storeLocation, string? linuxCertDirectory, string? macOSKeychainPath)
{
var installer = CATrustInstallerFactory.CreateInstaller(storeLocation, linuxCertDirectory, macOSKeychainPath);
if (installer == null)
return false;
var result = installer.Uninstall(caCert);
return result is TrustUninstallationResult.Success or TrustUninstallationResult.NotInstalled;
}
/// <summary>
/// Ensures HTTPS certificates are available and valid.
/// If valid certificates already exist, returns success without changes.
/// If a CA exists but the server certificate is expiring, renews the server certificate using the existing CA.
/// If no CA exists, generates new CA and server certificates.
/// </summary>
/// <param name="existingIsAutogenerated">Whether existing certificate was autogenerated.</param>
/// <param name="existingServerCert">The existing server certificate collection.</param>
/// <param name="existingCaData">The existing CA certificate data.</param>
/// <param name="hostnames">Optional comma-separated list of hostnames. If null, auto-detected hostnames are used.</param>
/// <param name="skipTrustInstallation">If true, skips installing the CA in the system trust store.</param>
/// <param name="storeLocation">The store location to use for trust installation (Windows only).</param>
/// <param name="linuxCertDirectory">The certificate directory to use for trust installation (Linux only).</param>
/// <param name="macOSKeychainPath">The keychain path to use for trust installation (macOS only).</param>
/// <returns>A result object containing success status, certificate data, and trust installation status.</returns>
public static CertificateGenerationResult EnsureCertificates(
bool existingIsAutogenerated,
X509Certificate2Collection? existingServerCert,
CACertificateData? existingCaData,
string? hostnames = null,
bool skipTrustInstallation = false,
StoreLocation? storeLocation = null,
string? linuxCertDirectory = null,
string? macOSKeychainPath = null)
{
var result = new CertificateGenerationResult();
try
{
// Check if valid certificates already exist
if (CertificateRenewalChecker.HasValidCertificates(existingIsAutogenerated, existingServerCert, existingCaData))
{
var serverCertWithKey = existingServerCert!.Cast<X509Certificate2>().FirstOrDefault(c => c.HasPrivateKey);
var daysUntilExpiry = (serverCertWithKey!.NotAfter - DateTime.UtcNow).TotalDays;
Log.WriteInformationMessage(LOGTAG, "CertificatesExist", $"Valid certificates already exist (expires in {daysUntilExpiry:F0} days).");
result.Success = true;
return result;
}
// If a CA already exists, try to renew the server certificate using the existing CA
if (existingCaData != null)
{
Log.WriteInformationMessage(LOGTAG, "ExistingCAFound", "Existing CA found, attempting to renew server certificate...");
var renewResult = RenewServerCertificate(existingCaData, hostnames);
if (renewResult.Renewed && renewResult.RenewedCertificate != null)
{
Log.WriteInformationMessage(LOGTAG, "ServerCertRenewed", "Server certificate renewed using existing CA.");
result.Success = true;
result.ServerCertificate = renewResult.RenewedCertificate;
return result;
}
// If renewal failed (e.g., CA expired), fall through to generate new CA + server cert
Log.WriteWarningMessage(LOGTAG, "RenewalFailed", null, $"Could not renew with existing CA: {renewResult.RenewalFailedReason}. Generating new CA and server certificate...");
}
// No valid CA exists or renewal failed — generate new CA and server certificates
return GenerateCertificatesCore(hostnames, skipTrustInstallation, storeLocation, linuxCertDirectory, macOSKeychainPath);
}
catch (Exception ex)
{
Log.WriteErrorMessage(LOGTAG, "EnsureCertificatesFailed", ex, $"Failed to ensure HTTPS certificates: {ex.Message}");
result.Success = false;
result.ErrorMessage = ex.Message;
return result;
}
}
/// <summary>
/// Generates new CA and server certificates for HTTPS.
/// If valid certificates already exist, returns success without generating new ones.
/// </summary>
/// <param name="existingIsAutogenerated">Whether existing certificate was autogenerated.</param>
/// <param name="existingServerCert">The existing server certificate collection.</param>
/// <param name="existingCaData">The existing CA certificate data.</param>
/// <param name="hostnames">Optional comma-separated list of hostnames. If null, auto-detected hostnames are used.</param>
/// <param name="skipTrustInstallation">If true, skips installing the CA in the system trust store.</param>
/// <param name="storeLocation">The store location to use for trust installation (Windows only).</param>
/// <param name="linuxCertDirectory">The certificate directory to use for trust installation (Linux only).</param>
/// <param name="macOSKeychainPath">The keychain path to use for trust installation (macOS only).</param>
/// <returns>A result object containing success status, certificate data, and trust installation status.</returns>
public static CertificateGenerationResult GenerateCertificates(
bool existingIsAutogenerated,
X509Certificate2Collection? existingServerCert,
CACertificateData? existingCaData,
string? hostnames = null,
bool skipTrustInstallation = false,
StoreLocation? storeLocation = null,
string? linuxCertDirectory = null,
string? macOSKeychainPath = null)
{
var result = new CertificateGenerationResult();
try
{
// Check if valid certificates already exist
if (CertificateRenewalChecker.HasValidCertificates(existingIsAutogenerated, existingServerCert, existingCaData))
{
var serverCertWithKey = existingServerCert!.Cast<X509Certificate2>().FirstOrDefault(c => c.HasPrivateKey);
var daysUntilExpiry = (serverCertWithKey!.NotAfter - DateTime.UtcNow).TotalDays;
Log.WriteInformationMessage(LOGTAG, "CertificatesExist", $"Valid certificates already exist (expires in {daysUntilExpiry:F0} days).");
result.Success = true;
return result;
}
// Generate new CA and server certificates
return GenerateCertificatesCore(hostnames, skipTrustInstallation, storeLocation, linuxCertDirectory, macOSKeychainPath);
}
catch (Exception ex)
{
Log.WriteErrorMessage(LOGTAG, "CertificateGenerationFailed", ex, $"Failed to generate HTTPS certificates: {ex.Message}");
result.Success = false;
result.ErrorMessage = ex.Message;
return result;
}
}
/// <summary>
/// Regenerates the CA and server certificates, optionally removing the old CA from the trust store first.
/// Unlike <see cref="GenerateCertificates"/>, this always generates new certificates regardless of existing validity.
/// </summary>
/// <param name="existingCaCertBase64">The existing CA certificate in Base64 format, for removal from trust store. Can be null.</param>
/// <param name="hostnames">Optional comma-separated list of hostnames. If null, auto-detected hostnames are used.</param>
/// <param name="skipTrustInstallation">If true, skips installing the CA in the system trust store.</param>
/// <param name="storeLocation">The store location to use for trust installation (Windows only).</param>
/// <param name="linuxCertDirectory">The certificate directory to use for trust installation (Linux only).</param>
/// <param name="macOSKeychainPath">The keychain path to use for trust installation (macOS only).</param>
/// <returns>A result object containing success status, certificate data, trust installation status, and old CA removal status.</returns>
public static CertificateGenerationResult RegenerateCACertificates(
string? existingCaCertBase64,
string? hostnames = null,
bool skipTrustInstallation = false,
StoreLocation? storeLocation = null,
string? linuxCertDirectory = null,
string? macOSKeychainPath = null)
{
var result = new CertificateGenerationResult();
try
{
// Remove old CA from trust store if it exists
if (!string.IsNullOrWhiteSpace(existingCaCertBase64) && !skipTrustInstallation)
{
try
{
var existingCaCert = CertificateStorageHelper.DeserializeCertificate(existingCaCertBase64);
Log.WriteInformationMessage(LOGTAG, "RemovingOldCA", "Removing old CA certificate from trust store...");
if (IsCATrustInstalled(existingCaCert, storeLocation, linuxCertDirectory, macOSKeychainPath))
{
if (RemoveCATrust(existingCaCert, storeLocation, linuxCertDirectory, macOSKeychainPath))
Log.WriteInformationMessage(LOGTAG, "OldCARemoved", "Old CA certificate removed from trust store.");
else
Log.WriteWarningMessage(LOGTAG, "OldCARemovalFailed", null, "Failed to remove old CA certificate from trust store.");
}
else
{
Log.WriteInformationMessage(LOGTAG, "OldCANotInTrustStore", "Old CA certificate was not in trust store.");
}
}
catch (Exception ex)
{
Log.WriteWarningMessage(LOGTAG, "OldCARemovalError", ex, $"Could not remove old CA from trust store: {ex.Message}");
}
}
// Generate new CA and server certificates (always, no validity check)
return GenerateCertificatesCore(hostnames, skipTrustInstallation, storeLocation, linuxCertDirectory, macOSKeychainPath);
}
catch (Exception ex)
{
Log.WriteErrorMessage(LOGTAG, "CertificateRegenerationFailed", ex, $"Failed to regenerate HTTPS certificates: {ex.Message}");
result.Success = false;
result.ErrorMessage = ex.Message;
return result;
}
}
/// <summary>
/// Renews the server certificate using an existing CA certificate.
/// This always generates a new server certificate regardless of the current certificate's validity.
/// </summary>
/// <param name="caData">The CA certificate data to use for signing the new server certificate.</param>
/// <param name="hostnames">Optional comma-separated list of hostnames. If null, auto-detected hostnames are used.</param>
/// <returns>A result object containing renewal status and new certificate data if renewed.</returns>
public static CertificateRenewalResult RenewServerCertificate(CACertificateData caData, string? hostnames = null)
{
var result = new CertificateRenewalResult();
try
{
// Deserialize CA certificate pair
CACertificatePair caPair;
try
{
caPair = CertificateStorageHelper.DeserializeCACertificatePair(
caData.CACertificate,
caData.CAKey,
caData.CAPassword);
}
catch (Exception ex)
{
Log.WriteErrorMessage(LOGTAG, "CADeserializationFailed", ex, "Failed to deserialize CA certificate for renewal.");
result.RenewalFailedReason = $"Failed to deserialize CA certificate: {ex.Message}";
return result;
}
try
{
// 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();
}
}
finally
{
// Dispose CA certificate key material promptly
caPair.PrivateKey.Dispose();
caPair.Certificate.Dispose();
}
}
catch (Exception ex)
{
Log.WriteErrorMessage(LOGTAG, "RenewalFailed", ex, $"Failed to renew SSL certificate: {ex.Message}");
result.RenewalFailedReason = ex.Message;
return result;
}
}
/// <summary>
/// Gets the current certificate status information.
/// </summary>
/// <param name="caCertBase64">The CA certificate in Base64 format.</param>
/// <param name="serverCertCollection">The server certificate collection.</param>
/// <param name="isAutogenerated">Whether the certificate was autogenerated.</param>
/// <param name="storeLocation">The store location to check (Windows only).</param>
/// <param name="linuxCertDirectory">The certificate directory to check (Linux only).</param>
/// <param name="macOSKeychainPath">The keychain path to check (macOS only).</param>
/// <returns>A status info object with certificate details.</returns>
public static CertificateStatusInfo GetCertificateStatus(
string? caCertBase64,
X509Certificate2Collection? serverCertCollection,
bool isAutogenerated,
StoreLocation? storeLocation = null,
string? linuxCertDirectory = null,
string? macOSKeychainPath = null)
{
var status = new CertificateStatusInfo
{
IsAutogenerated = isAutogenerated
};
// Parse CA certificate
if (!string.IsNullOrWhiteSpace(caCertBase64))
{
try
{
var caCert = CertificateStorageHelper.DeserializeCertificate(caCertBase64);
status.CACert = caCert;
status.CaDaysUntilExpiry = (caCert.NotAfter - DateTime.Now).TotalDays;
status.IsCATrusted = IsCATrustInstalled(caCert, storeLocation, linuxCertDirectory, macOSKeychainPath);
}
catch (Exception ex)
{
Log.WriteWarningMessage(LOGTAG, "CAStatusError", ex, $"Error reading CA certificate: {ex.Message}");
}
}
// Parse server certificate
if (serverCertCollection != null)
{
try
{
var serverCert = serverCertCollection.Cast<X509Certificate2>().FirstOrDefault(c => c.HasPrivateKey);
if (serverCert != null)
{
status.ServerCert = serverCert;
status.ServerDaysUntilExpiry = (serverCert.NotAfter - DateTime.Now).TotalDays;
// Extract SANs
var sanExtension = serverCert.Extensions["2.5.29.17"];
if (sanExtension is X509SubjectAlternativeNameExtension sanExt)
{
status.DnsNames = sanExt.EnumerateDnsNames().ToList();
status.IpAddresses = sanExt.EnumerateIPAddresses().Select(ip => ip.ToString()).ToList();
}
}
}
catch (Exception ex)
{
Log.WriteWarningMessage(LOGTAG, "ServerCertStatusError", ex, $"Error reading server certificate: {ex.Message}");
}
}
return status;
}
/// <summary>
/// Core certificate generation logic shared by <see cref="GenerateCertificates"/> and <see cref="RegenerateCACertificates"/>.
/// </summary>
private static CertificateGenerationResult GenerateCertificatesCore(
string? hostnames,
bool skipTrustInstallation,
StoreLocation? storeLocation,
string? linuxCertDirectory,
string? macOSKeychainPath)
{
var result = new CertificateGenerationResult();
// Parse hostnames
var hostnameList = CertificateRenewalChecker.ParseHostnames(hostnames);
Log.WriteInformationMessage(LOGTAG, "GeneratingCertificates", $"Generating HTTPS certificates with hostnames: {string.Join(", ", hostnameList)}");
// Generate CA certificate
Log.WriteInformationMessage(LOGTAG, "GeneratingCA", "Generating CA certificate...");
var caPair = CertificateGenerator.GenerateCACertificate();
try
{
// Generate server certificate
Log.WriteInformationMessage(LOGTAG, "GeneratingServerCert", "Generating server certificate...");
var serverPair = CertificateGenerator.GenerateServerCertificate(caPair.Certificate, caPair.PrivateKey, hostnameList);
try
{
// 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();
}
}
finally
{
// Dispose CA certificate key material promptly
caPair.PrivateKey.Dispose();
caPair.Certificate.Dispose();
}
}
}