summaryrefslogtreecommitdiffstats
path: root/src/main.lib/Plugins/StorePlugins/CertificateStore
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.lib/Plugins/StorePlugins/CertificateStore')
-rw-r--r--src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs294
-rw-r--r--src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArguments.cs8
-rw-r--r--src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArgumentsProvider.cs28
-rw-r--r--src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptions.cs28
-rw-r--r--src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptionsFactory.cs30
5 files changed, 388 insertions, 0 deletions
diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs
new file mode 100644
index 0000000..3cf9503
--- /dev/null
+++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStore.cs
@@ -0,0 +1,294 @@
+using PKISharp.WACS.Clients.IIS;
+using PKISharp.WACS.DomainObjects;
+using PKISharp.WACS.Plugins.Interfaces;
+using PKISharp.WACS.Services;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Cryptography.X509Certificates;
+
+namespace PKISharp.WACS.Plugins.StorePlugins
+{
+ internal class CertificateStore : IStorePlugin, IDisposable
+ {
+ private ILogService _log;
+ private ISettingsService _settings;
+ private const string _defaultStoreName = nameof(StoreName.My);
+ private string _storeName;
+ private X509Store _store;
+ private IIISClient _iisClient;
+ private CertificateStoreOptions _options;
+
+ public CertificateStore(
+ ILogService log, IIISClient iisClient,
+ ISettingsService settings,
+ CertificateStoreOptions options)
+ {
+ _log = log;
+ _iisClient = iisClient;
+ _options = options;
+ _settings = settings;
+ ParseCertificateStore();
+ _store = new X509Store(_storeName, StoreLocation.LocalMachine);
+ }
+
+ private void ParseCertificateStore()
+ {
+ try
+ {
+ // First priority: specified in the parameters
+ _storeName = _options.StoreName;
+
+ // Second priority: specified in the .config
+ if (string.IsNullOrEmpty(_storeName))
+ {
+ _storeName = _settings.DefaultCertificateStore;
+ }
+
+ // Third priority: defaults
+ if (string.IsNullOrEmpty(_storeName))
+ {
+ // Default store should be WebHosting on IIS8+, and My (Personal) for IIS7.x
+ _storeName = _iisClient.Version.Major < 8 ? nameof(StoreName.My) : "WebHosting";
+ }
+
+ // Rewrite
+ if (string.Equals(_storeName, "Personal", StringComparison.InvariantCultureIgnoreCase))
+ {
+ // Users trying to use the "My" store might have set "Personal" in their
+ // config files, because that's what the store is called in mmc
+ _storeName = nameof(StoreName.My);
+ }
+
+ _log.Debug("Certificate store: {_certificateStore}", _storeName);
+ }
+ catch (Exception ex)
+ {
+ _log.Warning("Error reading CertificateStore from config, defaulting to {_certificateStore} Error: {@ex}", _defaultStoreName, ex);
+ }
+ }
+
+ public void Save(CertificateInfo input)
+ {
+ var existing = FindByThumbprint(input.Certificate.Thumbprint);
+ if (existing != null)
+ {
+ _log.Warning("Certificate with thumbprint {thumbprint} is already in the store", input.Certificate.Thumbprint);
+ }
+ else
+ {
+ var certificate = input.Certificate;
+ if (!_settings.PrivateKeyExportable)
+ {
+ certificate = new X509Certificate2(
+ input.CacheFile.FullName,
+ input.CacheFilePassword,
+ X509KeyStorageFlags.MachineKeySet |
+ X509KeyStorageFlags.PersistKeySet);
+ }
+ _log.Information("Installing certificate in the certificate store");
+ InstallCertificate(certificate);
+ }
+ input.StoreInfo.Add(
+ GetType(),
+ new StoreInfo()
+ {
+ Name = CertificateStoreOptions.PluginName,
+ Path = _store.Name
+ });
+ }
+
+ public void Delete(CertificateInfo input)
+ {
+ _log.Information("Uninstalling certificate from the certificate store");
+ UninstallCertificate(input.Certificate.Thumbprint);
+ }
+
+ public CertificateInfo FindByThumbprint(string thumbprint)
+ {
+ return ToInfo(GetCertificate(CertificateService.ThumbprintFilter(thumbprint)));
+ }
+
+ private CertificateInfo ToInfo(X509Certificate2 cert)
+ {
+ if (cert != null)
+ {
+ var ret = new CertificateInfo()
+ {
+ Certificate = cert
+ };
+ ret.StoreInfo.Add(
+ GetType(),
+ new StoreInfo() {
+ Path = _store.Name
+ });
+ return ret;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ private void InstallCertificate(X509Certificate2 certificate)
+ {
+ X509Store rootStore = null;
+ try
+ {
+ rootStore = new X509Store(StoreName.AuthRoot, StoreLocation.LocalMachine);
+ rootStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
+ }
+ catch
+ {
+ _log.Warning("Error encountered while opening root store");
+ rootStore = null;
+ }
+
+ X509Store imStore = null;
+ try
+ {
+ imStore = new X509Store(StoreName.CertificateAuthority, StoreLocation.LocalMachine);
+ imStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
+ }
+ catch
+ {
+ _log.Warning("Error encountered while opening intermediate certificate store");
+ imStore = null;
+ }
+
+ try
+ {
+ _store.Open(OpenFlags.ReadWrite);
+ _log.Debug("Opened certificate store {Name}", _store.Name);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Error encountered while opening certificate store {name}", _store.Name);
+ throw;
+ }
+
+ try
+ {
+ _log.Information(LogType.All, "Adding certificate {FriendlyName} to store {name}", certificate.FriendlyName, _store.Name);
+ var chain = new X509Chain();
+ chain.Build(certificate);
+ foreach (var chainElement in chain.ChainElements)
+ {
+ var cert = chainElement.Certificate;
+ if (cert.Subject == certificate.Subject)
+ {
+ _log.Verbose("{sub} - {iss} ({thumb})", cert.Subject, cert.Issuer, cert.Thumbprint);
+ _store.Add(cert);
+ }
+ else if (cert.Subject != cert.Issuer && imStore != null)
+ {
+ _log.Verbose("{sub} - {iss} ({thumb}) to CA store", cert.Subject, cert.Issuer, cert.Thumbprint);
+ imStore.Add(cert);
+ }
+ else if (cert.Subject == cert.Issuer && rootStore != null)
+ {
+ _log.Verbose("{sub} - {iss} ({thumb}) to AuthRoot store", cert.Subject, cert.Issuer, cert.Thumbprint);
+ rootStore.Add(cert);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Error saving certificate");
+ }
+ _log.Debug("Closing certificate stores");
+ _store.Close();
+ imStore.Close();
+ rootStore.Close();
+ }
+
+ private void UninstallCertificate(string thumbprint)
+ {
+ try
+ {
+ _store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadWrite);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Error encountered while opening certificate store");
+ throw;
+ }
+
+ _log.Debug("Opened certificate store {Name}", _store.Name);
+ try
+ {
+ var col = _store.Certificates;
+ foreach (var cert in col)
+ {
+ if (string.Equals(cert.Thumbprint, thumbprint, StringComparison.InvariantCultureIgnoreCase))
+ {
+ _log.Information(LogType.All, "Removing certificate {cert} from store {name}", cert.FriendlyName, _store.Name);
+ _store.Remove(cert);
+ }
+ }
+ _log.Debug("Closing certificate store");
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Error removing certificate");
+ throw;
+ }
+ _store.Close();
+ }
+
+ private X509Certificate2 GetCertificate(Func<X509Certificate2, bool> filter)
+ {
+ var possibles = new List<X509Certificate2>();
+ try
+ {
+ _store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Error encountered while opening certificate store");
+ return null;
+ }
+ try
+ {
+ var col = _store.Certificates;
+ foreach (var cert in col)
+ {
+ if (filter(cert))
+ {
+ possibles.Add(cert);
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ _log.Error(ex, "Error finding certificate in certificate store");
+ return null;
+ }
+ _store.Close();
+ return possibles.OrderByDescending(x => x.NotBefore).FirstOrDefault();
+ }
+
+ #region IDisposable
+
+ private bool disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ _store.Dispose();
+ }
+ disposedValue = true;
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ #endregion
+ }
+} \ No newline at end of file
diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArguments.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArguments.cs
new file mode 100644
index 0000000..2cbaae7
--- /dev/null
+++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArguments.cs
@@ -0,0 +1,8 @@
+namespace PKISharp.WACS.Plugins.StorePlugins
+{
+ class CertificateStoreArguments
+ {
+ public bool KeepExisting { get; set; }
+ public string CertificateStore { get; set; }
+ }
+}
diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArgumentsProvider.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArgumentsProvider.cs
new file mode 100644
index 0000000..2834b47
--- /dev/null
+++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreArgumentsProvider.cs
@@ -0,0 +1,28 @@
+using Fclp;
+using PKISharp.WACS.Configuration;
+
+namespace PKISharp.WACS.Plugins.StorePlugins
+{
+ class CertificateStoreArgumentsProvider : BaseArgumentsProvider<CertificateStoreArguments>
+ {
+ public override string Name => "Certificate Store plugin";
+ public override string Group => "Store";
+ public override string Condition => "--store certificatestore";
+ public override bool Default => true;
+
+ public override void Configure(FluentCommandLineParser<CertificateStoreArguments> parser)
+ {
+ parser.Setup(o => o.CertificateStore)
+ .As("certificatestore")
+ .WithDescription("This setting can be used to save the certificate in a specific store. By default it will go to 'WebHosting' store on modern versions of Windows.");
+ parser.Setup(o => o.KeepExisting)
+ .As("keepexisting")
+ .WithDescription("While renewing, do not remove the previous certificate.");
+ }
+
+ public override bool Active(CertificateStoreArguments current)
+ {
+ return !string.IsNullOrEmpty(current.CertificateStore) || current.KeepExisting;
+ }
+ }
+}
diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptions.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptions.cs
new file mode 100644
index 0000000..52ff113
--- /dev/null
+++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptions.cs
@@ -0,0 +1,28 @@
+using PKISharp.WACS.Plugins.Base;
+using PKISharp.WACS.Plugins.Base.Options;
+using PKISharp.WACS.Services;
+
+namespace PKISharp.WACS.Plugins.StorePlugins
+{
+ [Plugin("e30adc8e-d756-4e16-a6f2-450f784b1a97")]
+ internal class CertificateStoreOptions : StorePluginOptions<CertificateStore>
+ {
+ internal const string PluginName = "CertificateStore";
+ public override string Name { get => PluginName; }
+ public override string Description { get => "Windows Certificate Store"; }
+
+ /// <summary>
+ /// Name of the certificate store to use
+ /// </summary>
+ public string StoreName { get; set; }
+
+ public override void Show(IInputService input)
+ {
+ base.Show(input);
+ if (!string.IsNullOrEmpty(StoreName))
+ {
+ input.Show("Store", StoreName, level: 2);
+ }
+ }
+ }
+}
diff --git a/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptionsFactory.cs b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptionsFactory.cs
new file mode 100644
index 0000000..d2bfed9
--- /dev/null
+++ b/src/main.lib/Plugins/StorePlugins/CertificateStore/CertificateStoreOptionsFactory.cs
@@ -0,0 +1,30 @@
+using PKISharp.WACS.Plugins.Base.Factories;
+using PKISharp.WACS.Services;
+
+namespace PKISharp.WACS.Plugins.StorePlugins
+{
+ internal class CertificateStoreOptionsFactory : StorePluginOptionsFactory<CertificateStore, CertificateStoreOptions>
+ {
+ private IArgumentsService _arguments;
+
+ public CertificateStoreOptionsFactory(IArgumentsService arguments)
+ {
+ _arguments = arguments;
+ }
+
+ public override CertificateStoreOptions Aquire(IInputService inputService, RunLevel runLevel)
+ {
+ return Default();
+ }
+
+ public override CertificateStoreOptions Default()
+ {
+ var args = _arguments.GetArguments<CertificateStoreArguments>();
+ return new CertificateStoreOptions
+ {
+ StoreName = args.CertificateStore,
+ KeepExisting = args.KeepExisting
+ };
+ }
+ }
+}