From dc85151bbea1a8dca51b35f6003c25c0475bce50 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 4 Sep 2024 21:56:44 -0500 Subject: [PATCH 01/21] rebase 48867-Add_SSH onto current main changes --- IISU/ClientPSCertStoreInventory.cs | 49 ++++ IISU/ClientPSCertStoreReEnrollment.cs | 2 +- IISU/ClientPSIIManager.cs | 4 +- IISU/ClientPsSqlManager.cs | 4 +- IISU/ImplementedStoreTypes/Win/Inventory.cs | 28 +- IISU/ImplementedStoreTypes/Win/Management.cs | 4 +- .../ImplementedStoreTypes/Win/WinInventory.cs | 33 +++ .../ImplementedStoreTypes/WinIIS/Inventory.cs | 26 +- .../WinIIS/Management.cs | 4 +- .../WinIIS/WinIISInventory.cs | 91 ++++++ .../ImplementedStoreTypes/WinSQL/Inventory.cs | 17 +- .../WinSQL/Management.cs | 4 +- .../WinSQL/SQLServerInventory.cs | 75 +++++ IISU/Models/CertificateInfo.cs | 21 ++ IISU/PAMUtilities.cs | 11 +- IISU/PSHelper.cs | 260 +++++++++++++++++- IISU/PowerShellScripts/GetSQLInstances.ps1 | 14 + .../NonSignedWinCertAddCert.ps1 | 72 +++++ .../NonSignedWinCertInventory.ps1 | 39 +++ .../ReadBoundCertificates.ps1 | 49 ++++ IISU/PowerShellScripts/ReadCertificates.ps1 | 44 +++ .../PowerShellScripts/ReadSQLCertificates.ps1 | 18 ++ IISU/PowerShellScripts/ReadSQLInstances.ps1 | 14 + IISU/PowerShellScripts/WinCertAddCert.ps1 | 223 +++++++++++++++ IISU/PowerShellScripts/WinCertInventory.ps1 | 190 +++++++++++++ IISU/RemoteSettings.cs | 22 ++ IISU/WindowsCertStore.csproj | 21 ++ WinCertTestConsole/Program.cs | 86 ++++-- .../Properties/launchSettings.json | 3 + WinCertTestConsole/WinCertInventory.json | 29 ++ WinCertTestConsole/WinCertTestConsole.csproj | 5 +- .../{Inventory.json => WinIISInventory.json} | 4 +- 32 files changed, 1398 insertions(+), 68 deletions(-) create mode 100644 IISU/Models/CertificateInfo.cs create mode 100644 IISU/PowerShellScripts/GetSQLInstances.ps1 create mode 100644 IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 create mode 100644 IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 create mode 100644 IISU/PowerShellScripts/ReadBoundCertificates.ps1 create mode 100644 IISU/PowerShellScripts/ReadCertificates.ps1 create mode 100644 IISU/PowerShellScripts/ReadSQLCertificates.ps1 create mode 100644 IISU/PowerShellScripts/ReadSQLInstances.ps1 create mode 100644 IISU/PowerShellScripts/WinCertAddCert.ps1 create mode 100644 IISU/PowerShellScripts/WinCertInventory.ps1 create mode 100644 IISU/RemoteSettings.cs create mode 100644 WinCertTestConsole/WinCertInventory.json rename WinCertTestConsole/{Inventory.json => WinIISInventory.json} (80%) diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs index b3b85e1..c0c0b11 100644 --- a/IISU/ClientPSCertStoreInventory.cs +++ b/IISU/ClientPSCertStoreInventory.cs @@ -16,8 +16,10 @@ using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Runtime.ConstrainedExecution; using System.Text; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore @@ -36,8 +38,55 @@ public ClientPSCertStoreInventory(ILogger logger) _logger = logger; } + public List GetCertificatesFromStore(RemoteSettings settings, string storePath) + { + try + { + ILogger _logger = LogHandler.GetClassLogger(this.GetType()); + + List myCertificates = new(); + + _logger.LogTrace("Attempting to establish PowerShell connection."); + using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + { + _logger.LogTrace("Initializing connection"); + ps.Initialize(); + + var scriptParameters = new Dictionary + { + { "StoreName", storePath } + }; + + var results = ps.ExecuteCommand(PSHelper.LoadScript("WinCertInventory.ps1"), scriptParameters); + + foreach (var c in results) + { + myCertificates.Add(new Certificate + { + Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", + HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), + RawData = (byte[])c.Properties["RawData"]?.Value, + CryptoServiceProvider = $"{c.Properties["CSP"]?.Value}", + SAN = Certificate.Utilities.FormatSAN($"{c.Properties["san"]?.Value}") + }); + } + } + + _logger.LogTrace($"found: {myCertificates.Count} certificate(s), exiting GetCertificatesFromStore()"); + return myCertificates; + + } + catch (Exception ex) + { + throw new Exception ("An error occurred while attempting to read the certificates from the store.\n" + ex.Message.ToString()); + } + } + + // ORIG public List GetCertificatesFromStore(Runspace runSpace, string storePath) { + ILogger _logger = LogHandler.GetClassLogger(this.GetType()); + List myCertificates = new List(); try { diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index 85ce1ad..ea87440 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -65,7 +65,7 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit string storePath = config.CertificateStoreDetails.StorePath; _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var runSpace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + using var runSpace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); _logger.LogTrace("Runspace created"); runSpace.Open(); diff --git a/IISU/ClientPSIIManager.cs b/IISU/ClientPSIIManager.cs index 996b46d..255fedf 100644 --- a/IISU/ClientPSIIManager.cs +++ b/IISU/ClientPSIIManager.cs @@ -107,7 +107,7 @@ public ClientPSIIManager(ReenrollmentJobConfiguration config, string serverUsern bool includePortInSPN = jobProperties.SpnPortFlag; _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PsHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); } catch (Exception e) { @@ -158,7 +158,7 @@ public ClientPSIIManager(ManagementJobConfiguration config, string serverUsernam bool includePortInSPN = jobProperties.SpnPortFlag; _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PsHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); } catch (Exception e) { diff --git a/IISU/ClientPsSqlManager.cs b/IISU/ClientPsSqlManager.cs index de3ab68..454cb12 100644 --- a/IISU/ClientPsSqlManager.cs +++ b/IISU/ClientPsSqlManager.cs @@ -66,7 +66,7 @@ public ClientPsSqlManager(ManagementJobConfiguration config, string serverUserna RestartService = jobProperties.RestartService; _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PsHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); } catch (Exception e) { @@ -121,7 +121,7 @@ public ClientPsSqlManager(ReenrollmentJobConfiguration config, string serverUser RestartService = jobProperties.RestartService; _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PsHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); + _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); } catch (Exception e) { diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index c04f322..54fe6e9 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -14,6 +14,8 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Net; @@ -49,6 +51,7 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven { try { + var inventoryItems = new List(); _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(config)); @@ -66,20 +69,17 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven if (storePath != null) { - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var myRunspace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - myRunspace.Open(); - - _logger.LogTrace("Runspace is now open"); - _logger.LogTrace($"Attempting to read certificates from cert store: {storePath}"); - - //foreach (Certificate cert in PowerShellUtilities.CertificateStore.GetCertificatesFromStore(myRunspace, storePath)) - WinInventory winInv = new WinInventory(_logger); - inventoryItems = winInv.GetInventoryItems(myRunspace, storePath); - - _logger.LogTrace($"A total of {inventoryItems.Count} were found"); - _logger.LogTrace("Closing runspace"); - myRunspace.Close(); + // Create the remote connection class to pass to Inventory Class + RemoteSettings settings = new(); + settings.ClientMachineName = config.CertificateStoreDetails.ClientMachine; + settings.Protocol = jobProperties.WinRmProtocol; + settings.Port = jobProperties.WinRmPort; + settings.IncludePortInSPN = jobProperties.SpnPortFlag; + settings.ServerUserName = serverUserName; + settings.ServerPassword = serverPassword; + + WinInventory winInventory = new(_logger); + inventoryItems = winInventory.GetInventoryItems(settings, storePath); _logger.LogTrace("Invoking Inventory..."); submitInventory.Invoke(inventoryItems); diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index b897987..cc2e64f 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -72,7 +72,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) long JobHistoryID = config.JobHistoryId; _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - myRunspace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); var complete = new JobResult { @@ -136,7 +136,7 @@ private JobResult performAddition(ManagementJobConfiguration config) if (cryptoProvider != null) { _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); - if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) + if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) { throw new Exception($"The Crypto Provider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } } diff --git a/IISU/ImplementedStoreTypes/Win/WinInventory.cs b/IISU/ImplementedStoreTypes/Win/WinInventory.cs index 99954e6..0cae7b8 100644 --- a/IISU/ImplementedStoreTypes/Win/WinInventory.cs +++ b/IISU/ImplementedStoreTypes/Win/WinInventory.cs @@ -11,11 +11,14 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Text; @@ -29,11 +32,41 @@ public WinInventory(ILogger logger) : base(logger) _logger = logger; } + public List GetInventoryItems(RemoteSettings settings, string storePath) + { + _logger.LogTrace("Entering WinCert GetInventoryItems."); + List inventoryItems = new List(); + + _logger.LogTrace($"Attempting to read certificates from store: {storePath}."); + foreach (Certificate cert in base.GetCertificatesFromStore(settings, storePath)) + { + var entryParms = new Dictionary + { + { "ProviderName", cert.CryptoServiceProvider }, + { "SAN", cert.SAN } + }; + + inventoryItems.Add(new CurrentInventoryItem + { + Certificates = new[] { cert.CertificateData }, + Alias = cert.Thumbprint, + PrivateKeyEntry = cert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = entryParms + }); + } + + _logger.LogTrace($"Found {inventoryItems.Count} certificates. Exiting WinCert GetInventoryItems."); + return inventoryItems; + } + public List GetInventoryItems(Runspace runSpace, string storePath) { _logger.LogTrace("Entering WinCert GetInventoryItems."); List inventoryItems = new List(); + foreach (Certificate cert in base.GetCertificatesFromStore(runSpace, storePath)) { var entryParms = new Dictionary diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 0255948..7fa2d55 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -66,18 +67,25 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven if (storePath != null) { - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var myRunspace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - myRunspace.Open(); + _logger.LogTrace($"Getting settings to connect to: {clientMachineName}"); - _logger.LogTrace("Runspace is now open"); - _logger.LogTrace($"Attempting to read bound IIS certificates from cert store: {storePath}"); - WinIISInventory IISInventory = new WinIISInventory(_logger); - inventoryItems = IISInventory.GetInventoryItems(myRunspace, storePath); + // Create the remote connection class to pass to Inventory Class + RemoteSettings settings = new(); + settings.ClientMachineName = config.CertificateStoreDetails.ClientMachine; + settings.Protocol = jobProperties.WinRmProtocol; + settings.Port = jobProperties.WinRmPort; + settings.IncludePortInSPN = jobProperties.SpnPortFlag; + settings.ServerUserName = serverUserName; + settings.ServerPassword = serverPassword; + _logger.LogTrace($"Attempting to read bound IIS certificates from cert store: {storePath}"); + WinIISInventory winIISInventory = new(_logger); + inventoryItems = winIISInventory.GetInventoryItems(settings, storePath); _logger.LogTrace($"A total of {inventoryItems.Count} bound certificate(s) were found"); - _logger.LogTrace("Closing runspace..."); - myRunspace.Close(); + + _logger.LogTrace("Invoking Inventory..."); + submitInventory.Invoke(inventoryItems); + _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); _logger.LogTrace("Invoking submitInventory.."); submitInventory.Invoke(inventoryItems); diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index f917eb7..9047ea0 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -69,7 +69,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) long JobHistoryID = config.JobHistoryId; _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - myRunspace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); var complete = new JobResult { @@ -136,7 +136,7 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin if (cryptoProvider != null) { _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); - if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) + if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } } diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs index 0ce7320..dc5926d 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs @@ -40,6 +40,97 @@ public WinIISInventory(ILogger logger) : base(logger) _logger = logger; } + public List GetInventoryItems(RemoteSettings settings, string storePath) + { + _logger.LogTrace("Entering IISU GetInventoryItems"); + + // Get the raw certificate inventory from cert store + List certificates = base.GetCertificatesFromStore(settings, storePath); + + // Contains the inventory items to be sent back to KF + List myBoundCerts = new List(); + + _logger.LogTrace("Attempting to establish PowerShell connection."); + using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + { + _logger.LogTrace("Initializing connection"); + ps.Initialize(); + + var scriptParameters = new Dictionary + { + { "isRemote", false} + }; + try + { + var iisBindings = ps.ExecuteCommand(PSHelper.LoadScript("ReadBoundCertificates.ps1"), scriptParameters); + if (iisBindings.Count == 0) + { + _logger.LogTrace("No binding certificates were found. Exiting IISU GetInventoryItems."); + return myBoundCerts; + } + + foreach (var binding in iisBindings) + { + var thumbPrint = $"{(binding.Properties["thumbprint"]?.Value)}"; + if (string.IsNullOrEmpty(thumbPrint)) continue; + + Certificate foundCert = certificates.Find(m => m.Thumbprint.Equals(thumbPrint)); + + if (foundCert == null) continue; + + var sniValue = ""; + switch (Convert.ToInt16(binding.Properties["sniFlg"]?.Value)) + { + case 0: + sniValue = "0 - No SNI"; + break; + case 1: + sniValue = "1 - SNI Enabled"; + break; + case 2: + sniValue = "2 - Non SNI Binding"; + break; + case 3: + sniValue = "3 - SNI Binding"; + break; + } + + var siteSettingsDict = new Dictionary + { + { "SiteName", binding.Properties["Name"]?.Value }, + { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, + { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, + { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, + { "SniFlag", sniValue }, + { "Protocol", binding.Properties["Protocol"]?.Value }, + { "ProviderName", foundCert.CryptoServiceProvider }, + { "SAN", foundCert.SAN } + }; + + myBoundCerts.Add( + new CurrentInventoryItem + { + Certificates = new[] { foundCert.CertificateData }, + Alias = thumbPrint, + PrivateKeyEntry = foundCert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = siteSettingsDict + } + ); + } + + _logger.LogTrace($"Found {myBoundCerts.Count} bound certificates. Exiting IISU GetInventoryItems."); + return myBoundCerts; + } + catch (Exception) + { + _logger.LogError($"An error occurred while attempting to execute script: ReadBoundCertificates.ps1"); + return myBoundCerts; + } + } + } + public List GetInventoryItems(Runspace runSpace, string storePath) { _logger.LogTrace("Entering IISU GetInventoryItems"); diff --git a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs index 31511a0..69174cb 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs @@ -67,8 +67,23 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven if (storePath != null) { + // Create the remote connection class to pass to Inventory Class + RemoteSettings settings = new(); + settings.ClientMachineName = config.CertificateStoreDetails.ClientMachine; + settings.Protocol = jobProperties.WinRmProtocol; + settings.Port = jobProperties.WinRmPort; + settings.IncludePortInSPN = jobProperties.SpnPortFlag; + settings.ServerUserName = serverUserName; + settings.ServerPassword = serverPassword; + + //SQLServerInventory sqlInventory = new SQLServerInventory(_logger); + //inventoryItems = sqlInventory.GetInventoryItems(myRunspace, config); + + + + // _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var myRunspace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + using var myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); myRunspace.Open(); _logger.LogTrace("Runspace is now open"); diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index a4d2ffe..c1d4198 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -68,7 +68,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) long JobHistoryID = config.JobHistoryId; _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - myRunspace = PsHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); var complete = new JobResult { @@ -133,7 +133,7 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin if (cryptoProvider != null) { _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); - if (!PsHelper.IsCSPFound(PsHelper.GetCSPList(myRunspace), cryptoProvider)) + if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } } diff --git a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs index dc290fa..0c54ac5 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs @@ -26,9 +26,84 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql internal class SQLServerInventory : ClientPSCertStoreInventory { private string SqlInstanceName { get; set; } + private ILogger _logger; public SQLServerInventory(ILogger logger) : base(logger) { + _logger = logger; + } + + public List GetInventoryItems(RemoteSettings settings, InventoryJobConfiguration jobConfig) + { + var jobProperties = JsonConvert.DeserializeObject(jobConfig.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + List certificates = base.GetCertificatesFromStore(settings, jobConfig.CertificateStoreDetails.StorePath); + + List myBoundCerts = new List(); + + _logger.LogTrace("Attempting to establish PowerShell connection."); + using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + { + // Get the list of SQL Instances on the machine + var instances = ps.ExecuteCommand(PSHelper.LoadScript("GetSQLInstances.ps1")); + if (instances != null && instances[0] != null) + { + //var psSqlManager = new ClientPsSqlManager(jobConfig, runSpace); + var commonInstances = new Dictionary(); + + foreach (var instance in instances) + { + var regLocation = psSqlManager.GetSqlCertRegistryLocation(instance.ToString(), ps2); + + funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate"); + ps2.AddScript(funcScript); + //_logger.LogTrace("funcScript added..."); + var thumbprint = ps2.Invoke()[0].ToString(); + ps2.Commands.Clear(); + if (string.IsNullOrEmpty(thumbprint)) continue; + thumbprint = thumbprint.ToUpper(); + + if (!commonInstances.ContainsKey(thumbprint)) + { + commonInstances.Add(thumbprint, instance.ToString()); + } + else + { + commonInstances[thumbprint] = commonInstances[thumbprint] + "," + instance.ToString(); + } + } + + foreach (var kp in commonInstances) + { + Certificate foundCert = certificates.Find(m => m.Thumbprint.ToUpper().Equals(kp.Key)); + + if (foundCert == null) continue; + + var sqlSettingsDict = new Dictionary + { + { "InstanceName", kp.Value.ToString() }, + { "ProviderName", foundCert.CryptoServiceProvider } + }; + + myBoundCerts.Add( + new CurrentInventoryItem + { + Certificates = new[] { foundCert.CertificateData }, + Alias = kp.Key, + PrivateKeyEntry = foundCert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = sqlSettingsDict + }); + } + return myBoundCerts; + } + else + { + return null; + } + + } + } public List GetInventoryItems(Runspace runSpace, InventoryJobConfiguration jobConfig) diff --git a/IISU/Models/CertificateInfo.cs b/IISU/Models/CertificateInfo.cs new file mode 100644 index 0000000..a3e2351 --- /dev/null +++ b/IISU/Models/CertificateInfo.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Models +{ + internal class CertificateInfo + { + public string StoreName { get; set; } + public string Certificate { get; set; } + public DateTime ExpiryDate { get; set; } + public string Issuer { get; set; } + public string Thumbprint { get; set; } + public bool HasPrivateKey { get; set; } + public string SAN { get; set; } + public string ProviderName { get; set; } + public string Base64Data { get; set; } + } +} diff --git a/IISU/PAMUtilities.cs b/IISU/PAMUtilities.cs index 38de2f3..ae9c15b 100644 --- a/IISU/PAMUtilities.cs +++ b/IISU/PAMUtilities.cs @@ -14,9 +14,6 @@ using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Text; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -24,12 +21,8 @@ internal class PAMUtilities { internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logger, string name, string key) { - if (resolver == null) return key; - else - { - return resolver.Resolve(key); - } - + logger.LogDebug($"Attempting to resolve PAM eligible field {name}"); + return string.IsNullOrEmpty(key) ? key : resolver.Resolve(key); } } } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index e1016f6..0bc6f62 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -12,24 +12,278 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.Models; using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; using System.Management.Automation; +using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Net; +using System.Runtime.InteropServices.Marshalling; +using System.ServiceModel.Security; +using System.Text.Json; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - public class PsHelper + internal class PSHelper : IDisposable { private static ILogger _logger; + private PowerShell PS; + private Collection _PSSession = new Collection(); + + private string protocol; + private string port; + private bool useSPN; + private string machineName; + private string? argument; + + private string serverUserName; + private string serverPassword; + + private bool isLocalMachine; + public bool IsLocalMachine + { + get { return isLocalMachine; } + private set { isLocalMachine = value; } + } + + private string clientMachineName; + public string ClientMachineName + { + get { return clientMachineName; } + private set + { + clientMachineName = value; + + // Break the clientMachineName into parts + string[] parts = clientMachineName.Split('|'); + + // Extract the client machine name and arguments based upon the number of parts + machineName = parts.Length > 1 ? parts[0] : clientMachineName; + argument = parts.Length > 1 ? parts[1] : null; + + // Determine if this is truly a local connection + isLocalMachine = (machineName.ToLower() == "localhost") || (argument != null && argument.ToLower() == "localmachine"); + if (isLocalMachine) clientMachineName = argument; else clientMachineName = machineName; + } + } + + public PSHelper(string protocol, string port, bool useSPN, string clientMachineName, string serverUserName, string serverPassword) + { + this.protocol = protocol.ToLower(); + this.port = port; + this.useSPN = useSPN; + this.clientMachineName = clientMachineName; + this.serverUserName = serverUserName; + this.serverPassword = serverPassword; + + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + } + + public void Initialize() + { + PS = PowerShell.Create(); + + // Add listeners to raise events + PS.Streams.Debug.DataAdded += PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Error.DataAdded += PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Information.DataAdded += PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Verbose.DataAdded += PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Warning.DataAdded += PSHelper.ProcessPowerShellScriptEvent; + + _logger.LogDebug($"isLocalMachine flag set to: {isLocalMachine}"); + _logger.LogDebug($"Protocol is set to: {protocol}"); + + if (!isLocalMachine) + { + if (protocol == "ssh") + { + // TODO: Need to add logic when using keyfilePath + // TODO: Need to add keyfilePath parameter + PS.AddCommand("New-PSSession") + .AddParameter("HostName", ClientMachineName) + .AddParameter("UserName", serverUserName); + + } + else + { + var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; + PSCredential myCreds = new PSCredential(serverUserName, pw); + + // Create the PSSessionOption object + var sessionOption = new PSSessionOption + { + IncludePortInSPN = useSPN + }; + + PS.AddCommand("New-PSSession") + .AddParameter("ComputerName", ClientMachineName) + .AddParameter("Port", port) + .AddParameter("Credential", myCreds) + .AddParameter("SessionOption", sessionOption); + } + } + + _logger.LogTrace("Attempting to invoke PS-Session command."); + _PSSession = PS.Invoke(); + if (PS.HadErrors) + { + foreach (var error in PS.Streams.Error) + { + _logger.LogError($"Error: {error}"); + } + throw new Exception($"An error occurred while attempting to connect to the client machine: {clientMachineName}"); + } + + PS.Commands.Clear(); + _logger.LogTrace("PS-Session established"); + } + + public Collection? ExecuteCommand(string scriptBlock, Dictionary parameters = null) + { + _logger.LogTrace("Executing PowerShell Script"); + + using (PS) + { + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) // send session only when necessary (remote) + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)); + + // Add parameters to the script block + if (parameters != null) + { + foreach (var parameter in parameters) + { + PS.AddParameter("ArgumentList", parameters.Values.ToArray()); + } + } + + try + { + _logger.LogTrace("Ready to invoke the script"); + var results = PS.Invoke(); + if (PS.HadErrors) + { + _logger.LogTrace("There were errors detected."); + foreach (var error in PS.Streams.Error) + { + _logger.LogError($"Error: {error}"); + } + + throw new Exception("An error occurred when executing a PowerShell script. Please review the logs for more information."); + } + + var jsonResults = results[0].ToString(); + var certInfoList = JsonSerializer.Deserialize>(jsonResults); + + return results; + } + catch (Exception ex) + { + _logger.LogError($"Exception: {ex.Message}"); + return null; + } + + } + } + + public static string LoadScript(string scriptFileName) + { + string scriptFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PowerShellScripts", scriptFileName); + _logger.LogTrace($"Attempting to load script {scriptFilePath}"); + + if (File.Exists(scriptFilePath)) + { + return File.ReadAllText(scriptFilePath); + }else + { throw new Exception($"File: {scriptFilePath} was not found."); } + } + + public static void ProcessPowerShellScriptEvent(object? sender, DataAddedEventArgs e) + { + if (sender != null) + { + { + switch (sender) + { + case PSDataCollection: + var debugMessages = sender as PSDataCollection; + if (debugMessages != null) + { + var debugMessage = debugMessages[e.Index]; + _logger.LogDebug($"Debug: {debugMessage.Message}"); + } + break; + case PSDataCollection: + var verboseMessages = sender as PSDataCollection; + if (verboseMessages != null) + { + var verboseMessage = verboseMessages[e.Index]; + _logger.LogTrace($"Verbose: {verboseMessage.Message}"); + } + break; + case PSDataCollection: + var errorMessages = sender as PSDataCollection; + if (errorMessages != null) + { + var errorMessage = errorMessages[e.Index]; + _logger.LogError($"Error: {errorMessage.Exception.Message}"); + } + break; + case PSDataCollection: + var infoMessages = sender as PSDataCollection; + if (infoMessages != null) + { + var infoMessage = infoMessages[e.Index]; + _logger.LogInformation($"INFO: {infoMessage.MessageData.ToString()}"); + } + break; + + case PSDataCollection: + var warningMessages = sender as PSDataCollection; + if (warningMessages != null) + { + var warningMessage = warningMessages[e.Index]; + _logger.LogWarning($"WARN: {warningMessage.Message}"); + } + break; + default: + break; + + } + } + } + } + + public void Dispose() + { + if (PS != null) + { + // Remove the listeners + PS.Streams.Debug.DataAdded -= PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Error.DataAdded -= PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Information.DataAdded -= PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Verbose.DataAdded -= PSHelper.ProcessPowerShellScriptEvent; + PS.Streams.Warning.DataAdded -= PSHelper.ProcessPowerShellScriptEvent; + + PS.Dispose(); + } + } + + + // Code below is ORIGINAL public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMachineName, string winRmPort, bool includePortInSpn, string serverUserName, string serverPassword) { - _logger = LogHandler.GetClassLogger(); + _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); // 2.4 - Client Machine Name now follows the naming conventions of {clientMachineName}|{localMachine} @@ -130,5 +384,7 @@ public static bool IsCSPFound(IEnumerable cspList, string userCSP) _logger.LogTrace($"CSP: {userCSP} was not found"); return false; } + + } } diff --git a/IISU/PowerShellScripts/GetSQLInstances.ps1 b/IISU/PowerShellScripts/GetSQLInstances.ps1 new file mode 100644 index 0000000..79693d9 --- /dev/null +++ b/IISU/PowerShellScripts/GetSQLInstances.ps1 @@ -0,0 +1,14 @@ +# Summary: +# Gets a list of SQL instances defined in the Windows Registry + +# Setting Preference Variables can have an impact on how the PowerShell Script runs +# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend +# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables + +$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection +$ErrorActionPreference = 'Continue' # Default = 'Continue' +$InformationPreference = 'Continue' # Default = 'SilentlyContinue' +$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection +$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection + +Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server" InstalledInstances \ No newline at end of file diff --git a/IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 b/IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 new file mode 100644 index 0000000..eca30bb --- /dev/null +++ b/IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 @@ -0,0 +1,72 @@ +param ( + [Parameter(Mandatory = $true)] + [string]$Base64Cert, + + [Parameter(Mandatory = $false)] + [string]$PrivateKeyPassword, + + [Parameter(Mandatory = $true)] + [string]$StorePath, + + [Parameter(Mandatory = $false)] + [string]$CryptoServiceProvider +) + +function Add-CertificateToStore { + param ( + [string]$Base64Cert, + [string]$PrivateKeyPassword, + [string]$StorePath, + [string]$CryptoServiceProvider + ) + + try { + # Convert Base64 string to byte array + $certBytes = [Convert]::FromBase64String($Base64Cert) + + # Create a temporary file to store the certificate + $tempCertPath = [System.IO.Path]::GetTempFileName() + [System.IO.File]::WriteAllBytes($tempCertPath, $certBytes) + + if ($CryptoServiceProvider) { + # Create a temporary PFX file + $tempPfxPath = [System.IO.Path]::ChangeExtension($tempCertPath, ".pfx") + $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } + $pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + $pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) | Set-Content -Encoding Byte -Path $tempPfxPath + + # Use certutil to import the PFX with the specified CSP + $importCmd = "certutil -f -importpfx $tempPfxPath -p $pfxPassword -csp `"$CryptoServiceProvider`"" + Invoke-Expression $importCmd + + # Clean up the temporary PFX file + Remove-Item $tempPfxPath + } else { + # Load the certificate from the temporary file + if ($PrivateKeyPassword) { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $PrivateKeyPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + } else { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath) + } + + # Open the certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StorePath, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + + # Add the certificate to the store + $store.Add($cert) + + # Close the store + $store.Close() + } + + # Clean up the temporary file + Remove-Item $tempCertPath + + Write-Host "Certificate added successfully to $StorePath." + } catch { + Write-Error "An error occurred: $_" + } +} + +Add-CertificateToStore -Base64Cert $Base64Cert -PrivateKeyPassword $PrivateKeyPassword -StorePath $StorePath -CryptoServiceProvider $CryptoServiceProvider diff --git a/IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 b/IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 new file mode 100644 index 0000000..2417ed7 --- /dev/null +++ b/IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 @@ -0,0 +1,39 @@ +param ( + [string]$StoreName = "My" # Default store name is "My" (Personal) +) + +# Function to get SAN (Subject Alternative Names) from a certificate +function Get-SAN($cert) { + $san = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } + if ($san) { + return ($san.Format(1) -split ", " -join "; ") + } + return $null +} + +# Get all certificates from the specified store +$certificates = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" + +# Initialize an array to store the results +$certInfoList = @() + +foreach ($cert in $certificates) { + # Create a custom object to store the certificate information + $certInfo = [PSCustomObject]@{ + StoreName = $StoreName + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + SAN = Get-SAN $cert + ProviderName = $cert.ProviderName + Base64Data = [System.Convert]::ToBase64String($cert.RawData) + } + + # Add the certificate information to the array + $certInfoList += $certInfo +} + +# Output the results +$certInfoList | ConvertTo-Json diff --git a/IISU/PowerShellScripts/ReadBoundCertificates.ps1 b/IISU/PowerShellScripts/ReadBoundCertificates.ps1 new file mode 100644 index 0000000..36ee3ea --- /dev/null +++ b/IISU/PowerShellScripts/ReadBoundCertificates.ps1 @@ -0,0 +1,49 @@ +# Summary: +# Args: $isRemote - flag to indicate whether the connection is being executed on a remote machine. +# Sets additional modules based on this flag. + +param( + [bool]$isRemote +) + +# Example of setting $isRemote directly for testing purposes +# In actual use, you would pass this parameter when calling the script +#$isRemote = $true + +# Setting Preference Variables can have an impact on how the PowerShell Script runs +# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend +# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables + +$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection +$ErrorActionPreference = 'Continue' # Default = 'Continue' +$InformationPreference = 'Continue' # Default = 'SilentlyContinue' +$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection +$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection + +# Import modules based on the $isRemote flag +if ($isRemote) +{ + Write-Information "Running in remote mode. Importing remote modules." + Import-Module -Name 'WebAdministration' +} +else +{ + Write-Information "Running in local mode. Setting execution policy and importing local modules." + Set-ExecutionPolicy RemoteSigned -Scope Process -Force + Import-Module WebAdministration +} + +# Iterate through websites and their bindings +Foreach ($Site in Get-Website) +{ + Foreach ($Bind in $Site.bindings.collection) + { + [pscustomobject]@{ + Name = $Site.name + Protocol = $Bind.Protocol + Bindings = $Bind.BindingInformation + Thumbprint = $Bind.certificateHash + SniFlg = $Bind.sslFlags + } + } +} diff --git a/IISU/PowerShellScripts/ReadCertificates.ps1 b/IISU/PowerShellScripts/ReadCertificates.ps1 new file mode 100644 index 0000000..8cc8a69 --- /dev/null +++ b/IISU/PowerShellScripts/ReadCertificates.ps1 @@ -0,0 +1,44 @@ +# Summary: This script gets the certificates from the LocalMachine for the given store path. +# Args: $storePath - Contains the cert path to read the certificates +# +# Return Value: $certs - contains the specific cert details for each certificate in the given cert path + +param( + [string]$storePath +) + +# Setting Preference Variables can have an impact on how the PowerShell Script runs +# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend +# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables +$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection +$ErrorActionPreference = 'Continue' # Default = 'Continue' +$InformationPreference = 'Continue' # Default = 'SilentlyContinue' +$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection +$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection +# + +Write-Information "WARN: Store path passed from extension: $storePath" + +$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store($storePath,'LocalMachine') +$certStore.Open('ReadOnly') + +$certs = $certStore.Certificates + +$certStore.Close() +$certStore.Dispose() + +$certs | ForEach-Object { + $certDetails = @{ + Subject = $_.Subject + Thumbprint = $_.Thumbprint + HasPrivateKey = $_.HasPrivateKey + RawData = $_.RawData + san = $_.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } | ForEach-Object { $_.Format($false) } + } + + if ($_.HasPrivateKey) { + $certDetails.CSP = $_.PrivateKey.CspKeyContainerInfo.ProviderName + } + + New-Object PSObject -Property $certDetails +} \ No newline at end of file diff --git a/IISU/PowerShellScripts/ReadSQLCertificates.ps1 b/IISU/PowerShellScripts/ReadSQLCertificates.ps1 new file mode 100644 index 0000000..dadc004 --- /dev/null +++ b/IISU/PowerShellScripts/ReadSQLCertificates.ps1 @@ -0,0 +1,18 @@ +# Setting Preference Variables can have an impact on how the PowerShell Script runs +# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend +# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables + + +$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection +$ErrorActionPreference = 'Continue' # Default = 'Continue' +$InformationPreference = 'Continue' # Default = 'SilentlyContinue' +$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection +$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection + +# + +param( + [string]$regLocation +) + +Get-ItemPropertyValue $regLocation -Name Certificate \ No newline at end of file diff --git a/IISU/PowerShellScripts/ReadSQLInstances.ps1 b/IISU/PowerShellScripts/ReadSQLInstances.ps1 new file mode 100644 index 0000000..8820dac --- /dev/null +++ b/IISU/PowerShellScripts/ReadSQLInstances.ps1 @@ -0,0 +1,14 @@ +# Setting Preference Variables can have an impact on how the PowerShell Script runs +# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend +# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables + + +$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection +$ErrorActionPreference = 'Continue' # Default = 'Continue' +$InformationPreference = 'Continue' # Default = 'SilentlyContinue' +$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection +$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection + +# + +Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server" .InstalledInstances" \ No newline at end of file diff --git a/IISU/PowerShellScripts/WinCertAddCert.ps1 b/IISU/PowerShellScripts/WinCertAddCert.ps1 new file mode 100644 index 0000000..86657c4 --- /dev/null +++ b/IISU/PowerShellScripts/WinCertAddCert.ps1 @@ -0,0 +1,223 @@ +param ( + [Parameter(Mandatory = $true)] + [string]$Base64Cert, + + [Parameter(Mandatory = $false)] + [string]$PrivateKeyPassword, + + [Parameter(Mandatory = $true)] + [string]$StorePath, + + [Parameter(Mandatory = $false)] + [string]$CryptoServiceProvider +) + +function Add-CertificateToStore { + param ( + [string]$Base64Cert, + [string]$PrivateKeyPassword, + [string]$StorePath, + [string]$CryptoServiceProvider + ) + + try { + # Convert Base64 string to byte array + $certBytes = [Convert]::FromBase64String($Base64Cert) + + # Create a temporary file to store the certificate + $tempCertPath = [System.IO.Path]::GetTempFileName() + [System.IO.File]::WriteAllBytes($tempCertPath, $certBytes) + + if ($CryptoServiceProvider) { + # Create a temporary PFX file + $tempPfxPath = [System.IO.Path]::ChangeExtension($tempCertPath, ".pfx") + $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } + $pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + $pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) | Set-Content -Encoding Byte -Path $tempPfxPath + + # Use certutil to import the PFX with the specified CSP + $importCmd = "certutil -f -importpfx $tempPfxPath -p $pfxPassword -csp `"$CryptoServiceProvider`"" + Invoke-Expression $importCmd + + # Clean up the temporary PFX file + Remove-Item $tempPfxPath + } else { + # Load the certificate from the temporary file + if ($PrivateKeyPassword) { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $PrivateKeyPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + } else { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath) + } + + # Open the certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StorePath, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + + # Add the certificate to the store + $store.Add($cert) + + # Close the store + $store.Close() + } + + # Clean up the temporary file + Remove-Item $tempCertPath + + Write-Host "Certificate added successfully to $StorePath." + } catch { + Write-Error "An error occurred: $_" + } +} + +Add-CertificateToStore -Base64Cert $Base64Cert -PrivateKeyPassword $PrivateKeyPassword -StorePath $StorePath -CryptoServiceProvider $CryptoServiceProvider + +# SIG # Begin signature block +# MIIbnQYJKoZIhvcNAQcCoIIbjjCCG4oCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB +# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR +# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUY71rrjro6KAhSWXsIyR37p9d +# 6tagghYTMIIDBjCCAe6gAwIBAgIQZS3tjlHZZI1KI1qdjoozTzANBgkqhkiG9w0B +# AQsFADAbMRkwFwYDVQQDDBBBVEEgQXV0aGVudGljb2RlMB4XDTI0MDcyOTE1MzQw +# MVoXDTI1MDcyOTE1NTQwMVowGzEZMBcGA1UEAwwQQVRBIEF1dGhlbnRpY29kZTCC +# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1pkoUO+MhwhPi5SQZpXOT9 +# vrKdLo5CofebPe8S0nLi1E5/ZmCOiE3nek2s9gK1fODhXiB4SbeEFaGIJOJJzHtJ +# Pc+6o0iKFpBbsmP+XrzcNHq7+ymtlMmPtfyKWRd7BsNXXfOsT1NnSsf7M3u54l9O +# suJkzq6bEur0iftK6X0T7IneQuuN91r4Nx0UM4AiakwXKsyN7axQzmZZWfSx4M1G +# NvIbUF6qt8cp5KPXJUIDsDhIyE93EZtxJ2A2sXotrl3tcSHis0IVCOSSj+JQtX+J +# 8AugzNGTsWL/V4Cnz5Yw4WzmmtPe0PGzlY7UHv11a9meFwl//ZnOZaqajmpJD90C +# AwEAAaNGMEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0G +# A1UdDgQWBBQh5V3P5qDcFYqqWfrqmABgjBnWiDANBgkqhkiG9w0BAQsFAAOCAQEA +# ibxFp6+qii/Cze5Au+444NZc0Kp5ryuYlMnSS9raRzJOXnl1pv/s71MM8wmaHyAu +# exZeexeK//8ABSn/HXMoR4hqq/WNtjmhgmDkpTjMos3byAdrPphCA7Rx9XZquUjQ +# glvEzaje2M0f6gSz1t3/Op2pU5KuzxB4998acdZuQuezwI9/tLgn8srC0kLQS1CM +# 4gw0uR7JmH6H4yYgEEG9JaRJbPy9pXlQij2DWMZ8cfpXm0ju3vfM0F/QFeAkvAO2 +# ZqBbD+/btr7D1qDrCuNpzzFdOeTVOaA4O1175Dz6VBeT15nqCzWLXKTAs4bP315e +# 0pDus7eJRvKLkAUrhpFnEzCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFow +# DQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 +# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNl +# cnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIz +# NTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG +# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3Rl +# ZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2je +# u+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bG +# l20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBE +# EC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/N +# rDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A +# 2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8 +# IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfB +# aYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaa +# RBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZi +# fvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXe +# eqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g +# /KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB +# /wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQY +# MBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEF +# BQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBD +# BggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 +# QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3Js +# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1Ud +# IAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22 +# Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih +# 9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYD +# E3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c +# 2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88n +# q2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5 +# lDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAw +# YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ +# d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290 +# IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMC +# VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBU +# cnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJ +# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh +# 1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+Feo +# An39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1 +# decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxnd +# X7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6 +# Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPj +# Q2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlREr +# WHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JM +# q++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh +# 3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8j +# u2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnS +# DmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud +# DgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzf +# Lmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgw +# dwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +# dC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E +# aWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6 +# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAG +# A1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOC +# AgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp +# /GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40B +# IiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2d +# fNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibB +# t94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7 +# T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZA +# myEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdB +# eHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnK +# cPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/ +# pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yY +# lvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbCMIIEqqADAgEC +# AhAFRK/zlJ0IOaa/2z9f5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVT +# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 +# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0 +# MDAwMDAwWhcNMzQxMDEzMjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO +# RGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIz +# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DP +# n9fl0uddoQ4J3C9Io5d6OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX +# 65RQjxwg6seaOy+WZuNp52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSk +# ghraarrYO8pd3hkYhftF6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2 +# SC1eRXWWdf7dEKEbg8G45lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG +# 7rSkIWRw69XloNpjsy7pBe6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k +# /XtzPjLuUjT71Lvr1KAsNJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP +# 4FhB+9ixLOFRr7StFQYU6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7 +# Qv1zfe7dCv95NBB+plwKWEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOupr +# AbD3+yqG7HtSOKmYCaFxsmxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBx +# PZySkwS0aXAnDU+3tTbRyV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E4 +# 2FEHypS34lCh8zrTioPLQHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAM +# BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcw +# CAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91 +# jGogj57IbzAdBgNVHQ4EFgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMw +# UTBPoE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3Rl +# ZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEE +# gYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggr +# BgEFBQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 +# c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0B +# AQsFAAOCAgEAgRrW3qCptZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn +# 48XtJoKKcS8Y3U623mzX4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S +# 2sJAOJ9dyKAuJXglnSoFeoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedt +# QVyMadG5K8TGe8+c+njikxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6 +# ubzBaRm6zxbygzc0brBBJt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXh +# RsUo063nQwBw3syYnhmJA+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC +# 1o/jF5HRqsBV44a/rCcsQdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH +# 5U81PAC9vpwqbHkB3NpE5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLK +# O+uagjVXKBbLafIymrLS2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc +# 9bahuEMs305MfR5ocMB3CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdE +# Qcm4RtNsMnxYL2dHZeUbc7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYoxggT0MIIE +# 8AIBATAvMBsxGTAXBgNVBAMMEEFUQSBBdXRoZW50aWNvZGUCEGUt7Y5R2WSNSiNa +# nY6KM08wCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJ +# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB +# gjcCARUwIwYJKoZIhvcNAQkEMRYEFOk4p/p5IbDHLUVRn1QQ09uVofSNMA0GCSqG +# SIb3DQEBAQUABIIBAASDMEd/7ePVCI/F57CXse3NAYCkeNapzhZdVIchuyd+V2H0 +# st7P5ut6KHqTHQGqAHH5Lmm60EAJ8n+SlIUNWvZq8BVh8N79gLoVyy8cGUXX8oZ2 +# 1wTIImdXlHqn8W89xP/5lqx5ZyW3eaKt9ov3TdfSAPj4F0MmOW50fmln1xr6eg/K +# QRmVp3MjH0qn4ssWRnuQubQ+vugxY071NW8qp3fT6rp/r1QWKOo8g7eP3i6I5S+D +# h0mr2c6pQWd5dB+UaESk4kAElKXywRvWJRLjh4Sbp5QQ4Rje6PtOUVMBRNmEk9aP +# QzXwpqDNixw2ns2gMBXzfjtuyd3DOAy8S8eloFehggMgMIIDHAYJKoZIhvcNAQkG +# MYIDDTCCAwkCAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +# IEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEy +# NTYgVGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQC +# AQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcN +# MjQwNzI5MTU1MjIxWjAvBgkqhkiG9w0BCQQxIgQgsbb8WIi6yJQ7OSi7v/5CaS/z +# ByfSsM13cXsO5LMO0q4wDQYJKoZIhvcNAQEBBQAEggIAWo2/oqH+2qOYkFCR2kOj +# RCgQ6NRDPUv+4PP6uJKxwQwp8IxiEEsmTFojrcKntDVxQKPqWfEo3eoJVfeQNLm0 +# VvxLvbj5UXNMNbWFCNtQhBcp4Tgzpb0BXiRwEHtnoskt59YkKA3g5TOO9Wl7ADtf +# fkjoRBzCzdMRRDzT4LByMEybK+JpEOD1RoNAzHp2LY3gnj1d9S530MwXGqawfTKB +# jWkWSPCy+7blCc4B5LEL+lqZStUqzE4C06RguSfEub7oumdgxoPQH8hgofEt5MSm +# xDUHyI0+4wWIroTgU6UCZAFWy5+5C0f0BLOuDXhuu5PLR/eHv/4A89z9ckvDcFuc +# 0NugIn8Q+XVRlQAouKcxZxUj0DUjJUEbe9EavFnxKx7Uu98h8ZX9XJ0BusouHHOJ +# YkUFo9mK16Rn7PATu+V4NyC0fRX9Oc64sAQGlrGIJ0IKdN6hkgYED8WfxxZJCF/w +# /yoBRlSvaQu7KEyMvFwOg20PBST1zOa84B9P52dHXex/HHVRU7+wwcO9dTJGDcCN +# t4FprYI/ahlGEctOj6Sh+bG7RkZNtCB1LJ2y0v96u2fCP7ElBGQYecnPGrUhzwX3 +# OizLTlzKnlI4jkP2vlKKpDLGuJObfUxQ4pVOtzIa+fc4dMETvzcOkWaTLECevR1D +# swmRbNMufhgJaWDKtyvnjBc= +# SIG # End signature block diff --git a/IISU/PowerShellScripts/WinCertInventory.ps1 b/IISU/PowerShellScripts/WinCertInventory.ps1 new file mode 100644 index 0000000..b8d2717 --- /dev/null +++ b/IISU/PowerShellScripts/WinCertInventory.ps1 @@ -0,0 +1,190 @@ +param ( + [string]$StoreName = "My" # Default store name is "My" (Personal) +) + +# Function to get SAN (Subject Alternative Names) from a certificate +function Get-SAN($cert) { + $san = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } + if ($san) { + return ($san.Format(1) -split ", " -join "; ") + } + return $null +} + +# Get all certificates from the specified store +$certificates = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" + +# Initialize an array to store the results +$certInfoList = @() + +foreach ($cert in $certificates) { + # Create a custom object to store the certificate information + $certInfo = [PSCustomObject]@{ + StoreName = $StoreName + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + SAN = Get-SAN $cert + ProviderName = $cert.ProviderName + Base64Data = [System.Convert]::ToBase64String($cert.RawData) + } + + # Add the certificate information to the array + $certInfoList += $certInfo +} + +# Output the results +$certInfoList | ConvertTo-Json + +# SIG # Begin signature block +# MIIbnQYJKoZIhvcNAQcCoIIbjjCCG4oCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB +# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR +# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUFe5YsftQGF0asr1/sLZTTxMs +# MwagghYTMIIDBjCCAe6gAwIBAgIQZS3tjlHZZI1KI1qdjoozTzANBgkqhkiG9w0B +# AQsFADAbMRkwFwYDVQQDDBBBVEEgQXV0aGVudGljb2RlMB4XDTI0MDcyOTE1MzQw +# MVoXDTI1MDcyOTE1NTQwMVowGzEZMBcGA1UEAwwQQVRBIEF1dGhlbnRpY29kZTCC +# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1pkoUO+MhwhPi5SQZpXOT9 +# vrKdLo5CofebPe8S0nLi1E5/ZmCOiE3nek2s9gK1fODhXiB4SbeEFaGIJOJJzHtJ +# Pc+6o0iKFpBbsmP+XrzcNHq7+ymtlMmPtfyKWRd7BsNXXfOsT1NnSsf7M3u54l9O +# suJkzq6bEur0iftK6X0T7IneQuuN91r4Nx0UM4AiakwXKsyN7axQzmZZWfSx4M1G +# NvIbUF6qt8cp5KPXJUIDsDhIyE93EZtxJ2A2sXotrl3tcSHis0IVCOSSj+JQtX+J +# 8AugzNGTsWL/V4Cnz5Yw4WzmmtPe0PGzlY7UHv11a9meFwl//ZnOZaqajmpJD90C +# AwEAAaNGMEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0G +# A1UdDgQWBBQh5V3P5qDcFYqqWfrqmABgjBnWiDANBgkqhkiG9w0BAQsFAAOCAQEA +# ibxFp6+qii/Cze5Au+444NZc0Kp5ryuYlMnSS9raRzJOXnl1pv/s71MM8wmaHyAu +# exZeexeK//8ABSn/HXMoR4hqq/WNtjmhgmDkpTjMos3byAdrPphCA7Rx9XZquUjQ +# glvEzaje2M0f6gSz1t3/Op2pU5KuzxB4998acdZuQuezwI9/tLgn8srC0kLQS1CM +# 4gw0uR7JmH6H4yYgEEG9JaRJbPy9pXlQij2DWMZ8cfpXm0ju3vfM0F/QFeAkvAO2 +# ZqBbD+/btr7D1qDrCuNpzzFdOeTVOaA4O1175Dz6VBeT15nqCzWLXKTAs4bP315e +# 0pDus7eJRvKLkAUrhpFnEzCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFow +# DQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 +# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNl +# cnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIz +# NTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG +# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3Rl +# ZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2je +# u+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bG +# l20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBE +# EC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/N +# rDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A +# 2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8 +# IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfB +# aYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaa +# RBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZi +# fvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXe +# eqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g +# /KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB +# /wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQY +# MBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEF +# BQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBD +# BggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 +# QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3Js +# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1Ud +# IAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22 +# Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih +# 9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYD +# E3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c +# 2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88n +# q2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5 +# lDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAw +# YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ +# d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290 +# IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMC +# VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBU +# cnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJ +# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh +# 1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+Feo +# An39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1 +# decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxnd +# X7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6 +# Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPj +# Q2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlREr +# WHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JM +# q++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh +# 3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8j +# u2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnS +# DmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud +# DgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzf +# Lmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgw +# dwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy +# dC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E +# aWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6 +# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAG +# A1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOC +# AgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp +# /GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40B +# IiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2d +# fNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibB +# t94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7 +# T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZA +# myEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdB +# eHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnK +# cPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/ +# pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yY +# lvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbCMIIEqqADAgEC +# AhAFRK/zlJ0IOaa/2z9f5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVT +# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 +# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0 +# MDAwMDAwWhcNMzQxMDEzMjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO +# RGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIz +# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DP +# n9fl0uddoQ4J3C9Io5d6OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX +# 65RQjxwg6seaOy+WZuNp52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSk +# ghraarrYO8pd3hkYhftF6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2 +# SC1eRXWWdf7dEKEbg8G45lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG +# 7rSkIWRw69XloNpjsy7pBe6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k +# /XtzPjLuUjT71Lvr1KAsNJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP +# 4FhB+9ixLOFRr7StFQYU6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7 +# Qv1zfe7dCv95NBB+plwKWEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOupr +# AbD3+yqG7HtSOKmYCaFxsmxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBx +# PZySkwS0aXAnDU+3tTbRyV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E4 +# 2FEHypS34lCh8zrTioPLQHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAM +# BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcw +# CAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91 +# jGogj57IbzAdBgNVHQ4EFgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMw +# UTBPoE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3Rl +# ZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEE +# gYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggr +# BgEFBQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 +# c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0B +# AQsFAAOCAgEAgRrW3qCptZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn +# 48XtJoKKcS8Y3U623mzX4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S +# 2sJAOJ9dyKAuJXglnSoFeoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedt +# QVyMadG5K8TGe8+c+njikxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6 +# ubzBaRm6zxbygzc0brBBJt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXh +# RsUo063nQwBw3syYnhmJA+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC +# 1o/jF5HRqsBV44a/rCcsQdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH +# 5U81PAC9vpwqbHkB3NpE5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLK +# O+uagjVXKBbLafIymrLS2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc +# 9bahuEMs305MfR5ocMB3CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdE +# Qcm4RtNsMnxYL2dHZeUbc7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYoxggT0MIIE +# 8AIBATAvMBsxGTAXBgNVBAMMEEFUQSBBdXRoZW50aWNvZGUCEGUt7Y5R2WSNSiNa +# nY6KM08wCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJ +# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB +# gjcCARUwIwYJKoZIhvcNAQkEMRYEFGLR9rFga1Zf6A4LBVZHoTSojXTnMA0GCSqG +# SIb3DQEBAQUABIIBAFpWh5G+zycDPKysvH57ymdn7jfLd6JniX5KGQGubRRkMezG +# Is5bEeKKs9U0cPGRl/w2CQJptyC7R6Idj+cy3rs56839nP3cm9rc4hY2/e57UbZj +# 7LMmeK9rskXIAqCkcSyh9pMoslSb8DOLmfEPAwHD71oUUGKW+x3Z/v3rgGZ+/Nxr +# zK15BWEI7+8/u+B1NYeeWPfxDWXCSTpLEXWAoRj9/4RY0HlX4WFUiKo7KErb0xxT +# 2TrZXEI5bqP28+5Qhf71Y5WOlRt9mthS2rMloaoV97T+v4MeKVhgidk3VJWmpPhv +# ZacJNc8+D6d/UEOm4uuOYpaHP25/dEfGov7qZ2WhggMgMIIDHAYJKoZIhvcNAQkG +# MYIDDTCCAwkCAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs +# IEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEy +# NTYgVGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQC +# AQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcN +# MjQwNzI5MTU1MjIxWjAvBgkqhkiG9w0BCQQxIgQgTle8ifnTPQjvsKSWEzoFYw3B +# BNHaIW9C4LZ+Aq1dZhMwDQYJKoZIhvcNAQEBBQAEggIAGgtN+wq2NbTXDghn6LsH +# 8nLJVVHoqEUILtIYdAz40rwMqTRe9AOaXEYpyVkEGDqE9WE2U1ZVouxixYdE/lnF +# F16v+NlaJPE1E/A64g0v86EwZSz76PemxDfDlvG2bl+B2wY1e3FmqTbVytpFY3wr +# Jt3qUSR85t6Srj33Zj6wI9OUvsIYFH/mnKX8yXe1KotuayfvDClgjmVTmMwyzEY2 +# CHgRQHR9LAJyDd19IquVRG2JzwIJCy00iJlEnwae2WpKQ+mehF3ltPWNmsbB0WEu +# l7VZZdeEz1qt5c+b+fv3M5xLxoBTUvGFPnYM+rBfX7E6wQAa9j72f3yY6e+y9Art +# t1ccsqlVVE8BnA1wicfGm8uWeTpp59yZI8AyxE0ZFQ1GdYKQQ77SqE+I5anzkGzc +# zF+cqHRZzv3kfwkb0WfccmoOAy2R1lYakE7eY9La7CaZFEG0XU1Tppl9t5UZLJKV +# 1sCou6N/FkVQfa7tt1j8aqrcQocFFDOqpOn4ACAxM7k09xjiN3szniLeVRD2DI+J +# N/RciH8k/NE+HFnKYrTasWtHWArZESdIWWleiwDOLfnv5KAlgxvT0OPrE2q2ezeU +# zM3rfCWBnrJyZZLa0LAD6MtWC/O+v+HGg8v3zCcDfjfoKFrEu/D4HnR3fTgChGMx +# NdqDO05kTtoQ+FUsbSdSmm8= +# SIG # End signature block diff --git a/IISU/RemoteSettings.cs b/IISU/RemoteSettings.cs new file mode 100644 index 0000000..38794ea --- /dev/null +++ b/IISU/RemoteSettings.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + internal class RemoteSettings + { + public string ClientMachineName { get; set; } + public string Protocol{ get; set; } + public string Port { get; set; } + public bool IncludePortInSPN { get; set; } + + public string ServerUserName { get; set; } + public string ServerPassword { get; set; } + + } + +} diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index 6bedf26..8a85584 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -40,6 +40,27 @@ PreserveNewest + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + diff --git a/WinCertTestConsole/Program.cs b/WinCertTestConsole/Program.cs index dc2c756..f62244d 100644 --- a/WinCertTestConsole/Program.cs +++ b/WinCertTestConsole/Program.cs @@ -14,10 +14,14 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Management.Automation; +using System.Net; using System.Threading; using System.Threading.Tasks; using Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Moq; @@ -75,25 +79,59 @@ private static async Task Main(string[] args) // Display message to user to provide parameters. Console.WriteLine("Running"); + // Short-cut settings for WinCert + CaseName = "Inventory"; + UserName = "user4"; + Password = "Password1"; + StorePath = "My"; + ClientMachine = "192.168.230.170"; + WinRmPort = "5985"; + string StoreType = "WinCert"; + // + switch (CaseName) { case "Inventory": - Console.WriteLine("Running Inventory"); - InventoryJobConfiguration invJobConfig; - invJobConfig = GetInventoryJobConfiguration(); - Console.WriteLine("Got Inventory Config"); - SubmitInventoryUpdate sui = GetItems; - var secretResolver = new Mock(); - secretResolver.Setup(m => m.Resolve(It.Is(s => s == invJobConfig.ServerUsername))) - .Returns(() => invJobConfig.ServerUsername); - secretResolver.Setup(m => m.Resolve(It.Is(s => s == invJobConfig.ServerPassword))) - .Returns(() => invJobConfig.ServerPassword); - var inv = new Inventory(secretResolver.Object); - Console.WriteLine("Created Inventory Object With Constructor"); - var invResponse = inv.ProcessJob(invJobConfig, sui); - Console.WriteLine("Back From Inventory"); - Console.Write(JsonConvert.SerializeObject(invResponse)); - Console.ReadLine(); + if (StoreType == "WinIIS") + { + Console.WriteLine("Running WinIIS Inventory"); + InventoryJobConfiguration invJobConfig; + invJobConfig = GetInventoryJobConfiguration(StoreType); + Console.WriteLine("Got Inventory Config"); + SubmitInventoryUpdate sui = GetItems; + var secretResolver = new Mock(); + secretResolver.Setup(m => m.Resolve(It.Is(s => s == invJobConfig.ServerUsername))) + .Returns(() => invJobConfig.ServerUsername); + secretResolver.Setup(m => m.Resolve(It.Is(s => s == invJobConfig.ServerPassword))) + .Returns(() => invJobConfig.ServerPassword); + var inv = new Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU.Inventory(secretResolver.Object); + Console.WriteLine("Created Inventory Object With Constructor"); + var invResponse = inv.ProcessJob(invJobConfig, sui); + Console.WriteLine("Back From Inventory"); + Console.Write(JsonConvert.SerializeObject(invResponse)); + Console.ReadLine(); + } + else if(StoreType == "WinCert") + { + Console.WriteLine("Running WinCert Inventory"); + InventoryJobConfiguration invJobConfig; + invJobConfig = GetInventoryJobConfiguration(StoreType); + Console.WriteLine("Got Inventory Config"); + SubmitInventoryUpdate sui = GetItems; + var secretResolver = new Mock(); + secretResolver.Setup(m => m.Resolve(It.Is(s => s == invJobConfig.ServerUsername))) + .Returns(() => invJobConfig.ServerUsername); + secretResolver.Setup(m => m.Resolve(It.Is(s => s == invJobConfig.ServerPassword))) + .Returns(() => invJobConfig.ServerPassword); + var inv = new Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert.Inventory(secretResolver.Object); + Console.WriteLine("Created Inventory Object With Constructor"); + var invResponse = inv.ProcessJob(invJobConfig, sui); + Console.WriteLine("Back From Inventory"); + Console.Write(JsonConvert.SerializeObject(invResponse)); + Console.ReadLine(); + + } + break; case "Management": @@ -162,7 +200,7 @@ private static void ProcessManagementJob(string jobType) mgmtSecretResolver .Setup(m => m.Resolve(It.Is(s => s == jobConfiguration.ServerPassword))) .Returns(() => jobConfiguration.ServerPassword); - var mgmt = new Management(mgmtSecretResolver.Object); + var mgmt = new Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU.Management(mgmtSecretResolver.Object); var result = mgmt.ProcessJob(jobConfiguration); Console.Write(JsonConvert.SerializeObject(result)); Console.ReadLine(); @@ -174,9 +212,19 @@ public static bool GetItems(IEnumerable items) } - public static InventoryJobConfiguration GetInventoryJobConfiguration() + public static InventoryJobConfiguration GetInventoryJobConfiguration(string storeType) { - var fileContent = File.ReadAllText("Inventory.json").Replace("UserNameGoesHere", UserName) + string myFileName = string.Empty; + + if (storeType == "WinIIS") + { + myFileName = "WinIISInventory.json"; + }else if (storeType == "WinCert") + { + myFileName = "WinCertInventory.json"; + } + + var fileContent = File.ReadAllText(myFileName).Replace("UserNameGoesHere", UserName) .Replace("PasswordGoesHere", Password).Replace("StorePathGoesHere", StorePath) .Replace("ClientMachineGoesHere", ClientMachine); var result = diff --git a/WinCertTestConsole/Properties/launchSettings.json b/WinCertTestConsole/Properties/launchSettings.json index 33504c9..475bbf5 100644 --- a/WinCertTestConsole/Properties/launchSettings.json +++ b/WinCertTestConsole/Properties/launchSettings.json @@ -3,6 +3,9 @@ "WSL": { "commandName": "WSL2", "distributionName": "" + }, + "WinCertTestConsole": { + "commandName": "Project" } } } \ No newline at end of file diff --git a/WinCertTestConsole/WinCertInventory.json b/WinCertTestConsole/WinCertInventory.json new file mode 100644 index 0000000..40510f3 --- /dev/null +++ b/WinCertTestConsole/WinCertInventory.json @@ -0,0 +1,29 @@ +{ + "LastInventory": [ + { + "Alias": "479D92068614E33B3CB84123AF76F1C40DF4B6F6", + "PrivateKeyEntry": true, + "Thumbprints": [ + "479D92068614E33B3CB84123AF76F1C40DF4B6F6" + ] + } + ], + "CertificateStoreDetails": { + "ClientMachine": "vmlabsvr1", + "StorePath": "My", + "StorePassword": "", + "Properties": "{\"spnwithport\":\"false\",\"WinRm Protocol\":\"ssh\",\"WinRm Port\":\"5985\",\"ServerUsername\":null,\"ServerPassword\":null,\"ServerUseSsl\":\"true\"}", + "Type": 104 + }, + "JobCancelled": false, + "ServerError": null, + "JobHistoryId": 26010, + "RequestStatus": 1, + "ServerUsername": "UserNameGoesHere", + "ServerPassword": "PasswordGoesHere", + "UseSSL": true, + "JobProperties": null, + "JobTypeId": "00000000-0000-0000-0000-000000000000", + "JobId": "e92f7350-251c-4c0a-9e5d-9b3fdb745ca9", + "Capability": "CertStores.WinCert.Inventory" +} \ No newline at end of file diff --git a/WinCertTestConsole/WinCertTestConsole.csproj b/WinCertTestConsole/WinCertTestConsole.csproj index e75510b..6de159c 100644 --- a/WinCertTestConsole/WinCertTestConsole.csproj +++ b/WinCertTestConsole/WinCertTestConsole.csproj @@ -18,7 +18,10 @@ - + + Always + + Always diff --git a/WinCertTestConsole/Inventory.json b/WinCertTestConsole/WinIISInventory.json similarity index 80% rename from WinCertTestConsole/Inventory.json rename to WinCertTestConsole/WinIISInventory.json index 1d7f961..3c7d8a1 100644 --- a/WinCertTestConsole/Inventory.json +++ b/WinCertTestConsole/WinIISInventory.json @@ -9,10 +9,10 @@ } ], "CertificateStoreDetails": { - "ClientMachine": "iisbindingstest.command.local", + "ClientMachine": "192.168.230.170", "StorePath": "My", "StorePassword": "", - "Properties": "{\"spnwithport\":\"false\",\"WinRm Protocol\":\"https\",\"WinRm Port\":\"5986\",\"ServerUsername\":null,\"ServerPassword\":null,\"ServerUseSsl\":\"true\"}", + "Properties": "{\"spnwithport\":\"false\",\"WinRm Protocol\":\"http\",\"WinRm Port\":\"5985\",\"ServerUsername\":null,\"ServerPassword\":null,\"ServerUseSsl\":\"true\"}", "Type": 104 }, "JobCancelled": false, From b44f2f4443c8f0647fb32738815ef273e9876cb0 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Fri, 18 Oct 2024 11:19:20 -0500 Subject: [PATCH 02/21] Just moving code to cloud --- IISU/Certificate.cs | 27 +- .../WinIIS/IISCertificateInfo.cs | 29 + .../ImplementedStoreTypes/WinIIS/Inventory.cs | 113 ++-- .../WinSQL/SQLServerInventory.cs | 126 ++-- IISU/PSHelper.cs | 116 +++- IISU/PowerShellScripts/WinCertFull.ps1 | 575 ++++++++++++++++++ IISU/RemoteSettings.cs | 2 +- IISU/WindowsCertStore.csproj | 3 + WinCertUnitTests/UnitTestIISBinding.cs | 115 ++-- WinCertUnitTests/WinCertUnitTests.cs | 27 + 10 files changed, 966 insertions(+), 167 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs create mode 100644 IISU/PowerShellScripts/WinCertFull.ps1 create mode 100644 WinCertUnitTests/WinCertUnitTests.cs diff --git a/IISU/Certificate.cs b/IISU/Certificate.cs index 81b89a4..2af3035 100644 --- a/IISU/Certificate.cs +++ b/IISU/Certificate.cs @@ -12,7 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; +using Newtonsoft.Json; using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.RegularExpressions; @@ -29,6 +33,28 @@ public class Certificate public class Utilities { + public static List DeserializeCertificates(string jsonResults) + { + if (string.IsNullOrEmpty(jsonResults)) + { + // Handle no objects returned + return new List(); + } + + // Determine if the JSON is an array or a single object + if (jsonResults.TrimStart().StartsWith("[")) + { + // It's an array, deserialize as list + return JsonConvert.DeserializeObject>(jsonResults); + } + else + { + // It's a single object, wrap it in a list + var singleObject = JsonConvert.DeserializeObject(jsonResults); + return new List { singleObject }; + } + } + public static string FormatSAN(string san) { // Use regular expression to extract key-value pairs @@ -52,7 +78,6 @@ private static string NormalizeKey(string key) _ => key.ToLower() // For other types, keep them as-is }; } - } } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs new file mode 100644 index 0000000..81fd1fd --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS +{ + public class IISCertificateInfo + { + public string SiteName { get; set; } // + public string Binding { get; set; } // + public string IPAddress { get; set; } // + public string Port { get; set; } // + public string Protocol { get; set; } // + public string HostName { get; set; } // + public string SNI { get; set; } // + public string Certificate { get; set; } // + public DateTime ExpiryDate { get; set; } // + public string Issuer { get; set; } // + public string Thumbprint { get; set; } // + public bool HasPrivateKey { get; set; } // + public string SAN { get; set; } // + public string ProviderName { get; set; } // + public string CertificateBase64 { get; set; } // + public string FriendlyName { get; set; } // + + } +} diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 7fa2d55..6af8c00 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; @@ -21,15 +22,22 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { public class Inventory : WinCertJobTypeBase, IInventoryJobExtension { private ILogger _logger; + Collection? results = null; public string ExtensionName => "WinIISUInventory"; + public Inventory() + { + + } public Inventory(IPAMSecretResolver resolver) { _resolver = resolver; @@ -40,30 +48,24 @@ public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitIn _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); - - return PerformInventory(jobConfiguration, submitInventoryUpdate); - } - - private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) - { try { var inventoryItems = new List(); - string myConfig = config.ToString(); + string myConfig = jobConfiguration.ToString(); - _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(config)); + _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(jobConfiguration)); - string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", jobConfiguration.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", jobConfiguration.ServerPassword); // Deserialize specific job properties - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + var jobProperties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); string protocol = jobProperties.WinRmProtocol; string port = jobProperties.WinRmPort; bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; + string clientMachineName = jobConfiguration.CertificateStoreDetails.ClientMachine; + string storePath = jobConfiguration.CertificateStoreDetails.StorePath; if (storePath != null) { @@ -71,30 +73,24 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven // Create the remote connection class to pass to Inventory Class RemoteSettings settings = new(); - settings.ClientMachineName = config.CertificateStoreDetails.ClientMachine; + settings.ClientMachineName = jobConfiguration.CertificateStoreDetails.ClientMachine; settings.Protocol = jobProperties.WinRmProtocol; settings.Port = jobProperties.WinRmPort; settings.IncludePortInSPN = jobProperties.SpnPortFlag; settings.ServerUserName = serverUserName; settings.ServerPassword = serverPassword; - _logger.LogTrace($"Attempting to read bound IIS certificates from cert store: {storePath}"); - WinIISInventory winIISInventory = new(_logger); - inventoryItems = winIISInventory.GetInventoryItems(settings, storePath); - _logger.LogTrace($"A total of {inventoryItems.Count} bound certificate(s) were found"); - - _logger.LogTrace("Invoking Inventory..."); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); + _logger.LogTrace("Querying IIS Inventory.."); + inventoryItems = QueryIISCertificates(settings); _logger.LogTrace("Invoking submitInventory.."); - submitInventory.Invoke(inventoryItems); + submitInventoryUpdate.Invoke(inventoryItems); _logger.LogTrace($"submitInventory Invoked... {inventoryItems.Count} Items"); return new JobResult { Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, + JobHistoryId = jobConfiguration.JobHistoryId, FailureMessage = "" }; } @@ -102,36 +98,77 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven return new JobResult { Result = OrchestratorJobStatusJobResult.Warning, - JobHistoryId = config.JobHistoryId, + JobHistoryId = jobConfiguration.JobHistoryId, FailureMessage = $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" }; } - catch (CertificateStoreException psEx) - { - _logger.LogTrace(psEx.Message); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Unable to open remote certificate store: {LogHandler.FlattenException(psEx)}" - }; - } catch (Exception ex) { _logger.LogTrace(LogHandler.FlattenException(ex)); - var failureMessage = $"Inventory job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + var failureMessage = $"Inventory job failed for Site '{jobConfiguration.CertificateStoreDetails.StorePath}' on server '{jobConfiguration.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; _logger.LogWarning(failureMessage); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, + JobHistoryId = jobConfiguration.JobHistoryId, FailureMessage = failureMessage }; } } + + public List QueryIISCertificates(RemoteSettings settings) + { + List Inventory = new(); + + string command = string.Empty; + + using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + { + ps.Initialize(); + + command = "Get-KFIISBoundCertificates"; + results = ps.ExecuteFunction(command); + + // If there are certificates, deserialize the results and send them back to command + if (results != null && results.Count > 0) + { + var jsonResults = results[0].ToString(); + var certInfoList = Certificate.Utilities.DeserializeCertificates(jsonResults); // JsonConvert.DeserializeObject>(jsonResults); + + foreach (IISCertificateInfo cert in certInfoList) + { + var siteSettingsDict = new Dictionary + { + { "SiteName", cert.SiteName }, + { "Port", cert.Port }, + { "IPAddress", cert.IPAddress }, + { "HostName", cert.HostName }, + { "SniFlag", cert.SNI }, + { "Protocol", cert.Protocol }, + { "ProviderName", cert.ProviderName }, + { "SAN", cert.SAN } + }; + + Inventory.Add( + new CurrentInventoryItem + { + Certificates = new[] {cert.CertificateBase64 }, + Alias = cert.Thumbprint, + PrivateKeyEntry = cert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = siteSettingsDict + } + ); + } + } + ps.Terminate(); + } + + return Inventory; + } } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs index 0c54ac5..6c278b8 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs @@ -35,74 +35,74 @@ public SQLServerInventory(ILogger logger) : base(logger) public List GetInventoryItems(RemoteSettings settings, InventoryJobConfiguration jobConfig) { - var jobProperties = JsonConvert.DeserializeObject(jobConfig.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - List certificates = base.GetCertificatesFromStore(settings, jobConfig.CertificateStoreDetails.StorePath); + //var jobProperties = JsonConvert.DeserializeObject(jobConfig.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + //List certificates = base.GetCertificatesFromStore(settings, jobConfig.CertificateStoreDetails.StorePath); List myBoundCerts = new List(); - _logger.LogTrace("Attempting to establish PowerShell connection."); - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) - { - // Get the list of SQL Instances on the machine - var instances = ps.ExecuteCommand(PSHelper.LoadScript("GetSQLInstances.ps1")); - if (instances != null && instances[0] != null) - { - //var psSqlManager = new ClientPsSqlManager(jobConfig, runSpace); - var commonInstances = new Dictionary(); - - foreach (var instance in instances) - { - var regLocation = psSqlManager.GetSqlCertRegistryLocation(instance.ToString(), ps2); - - funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate"); - ps2.AddScript(funcScript); - //_logger.LogTrace("funcScript added..."); - var thumbprint = ps2.Invoke()[0].ToString(); - ps2.Commands.Clear(); - if (string.IsNullOrEmpty(thumbprint)) continue; - thumbprint = thumbprint.ToUpper(); - - if (!commonInstances.ContainsKey(thumbprint)) - { - commonInstances.Add(thumbprint, instance.ToString()); - } - else - { - commonInstances[thumbprint] = commonInstances[thumbprint] + "," + instance.ToString(); - } - } - - foreach (var kp in commonInstances) - { - Certificate foundCert = certificates.Find(m => m.Thumbprint.ToUpper().Equals(kp.Key)); - - if (foundCert == null) continue; - - var sqlSettingsDict = new Dictionary - { - { "InstanceName", kp.Value.ToString() }, - { "ProviderName", foundCert.CryptoServiceProvider } - }; - - myBoundCerts.Add( - new CurrentInventoryItem - { - Certificates = new[] { foundCert.CertificateData }, - Alias = kp.Key, - PrivateKeyEntry = foundCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = sqlSettingsDict - }); - } + //_logger.LogTrace("Attempting to establish PowerShell connection."); + //using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + //{ + // // Get the list of SQL Instances on the machine + // var instances = ps.ExecuteCommand(PSHelper.LoadScript("GetSQLInstances.ps1")); + // if (instances != null && instances[0] != null) + // { + // //var psSqlManager = new ClientPsSqlManager(jobConfig, runSpace); + // var commonInstances = new Dictionary(); + + // foreach (var instance in instances) + // { + // var regLocation = psSqlManager.GetSqlCertRegistryLocation(instance.ToString(), ps2); + + // funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate"); + // ps2.AddScript(funcScript); + // //_logger.LogTrace("funcScript added..."); + // var thumbprint = ps2.Invoke()[0].ToString(); + // ps2.Commands.Clear(); + // if (string.IsNullOrEmpty(thumbprint)) continue; + // thumbprint = thumbprint.ToUpper(); + + // if (!commonInstances.ContainsKey(thumbprint)) + // { + // commonInstances.Add(thumbprint, instance.ToString()); + // } + // else + // { + // commonInstances[thumbprint] = commonInstances[thumbprint] + "," + instance.ToString(); + // } + // } + + // foreach (var kp in commonInstances) + // { + // Certificate foundCert = certificates.Find(m => m.Thumbprint.ToUpper().Equals(kp.Key)); + + // if (foundCert == null) continue; + + // var sqlSettingsDict = new Dictionary + // { + // { "InstanceName", kp.Value.ToString() }, + // { "ProviderName", foundCert.CryptoServiceProvider } + // }; + + // myBoundCerts.Add( + // new CurrentInventoryItem + // { + // Certificates = new[] { foundCert.CertificateData }, + // Alias = kp.Key, + // PrivateKeyEntry = foundCert.HasPrivateKey, + // UseChainLevel = false, + // ItemStatus = OrchestratorInventoryItemStatus.Unknown, + // Parameters = sqlSettingsDict + // }); + // } return myBoundCerts; - } - else - { - return null; - } + // } + // else + // { + // return null; + // } - } + //} } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 0bc6f62..95c291f 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -26,8 +26,6 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Net; -using System.Runtime.InteropServices.Marshalling; -using System.ServiceModel.Security; using System.Text.Json; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore @@ -71,17 +69,18 @@ private set argument = parts.Length > 1 ? parts[1] : null; // Determine if this is truly a local connection - isLocalMachine = (machineName.ToLower() == "localhost") || (argument != null && argument.ToLower() == "localmachine"); - if (isLocalMachine) clientMachineName = argument; else clientMachineName = machineName; + isLocalMachine = (machineName != null && (machineName.ToLower() == "localhost" || machineName.ToLower() == "localmachine")) || + (argument != null && argument.ToLower() == "localmachine"); + clientMachineName = isLocalMachine ? argument ?? machineName : machineName; } } - public PSHelper(string protocol, string port, bool useSPN, string clientMachineName, string serverUserName, string serverPassword) + public PSHelper(string protocol, string port, bool useSPN, string clientMachineName, string serverUserName, string serverPassword) { this.protocol = protocol.ToLower(); this.port = port; this.useSPN = useSPN; - this.clientMachineName = clientMachineName; + ClientMachineName = clientMachineName; this.serverUserName = serverUserName; this.serverPassword = serverPassword; @@ -131,21 +130,110 @@ public void Initialize() .AddParameter("Credential", myCreds) .AddParameter("SessionOption", sessionOption); } + + _logger.LogTrace("Attempting to invoke PS-Session command."); + _PSSession = PS.Invoke(); + if (PS.HadErrors) + { + foreach (var error in PS.Streams.Error) + { + _logger.LogError($"Error: {error}"); + } + throw new Exception($"An error occurred while attempting to connect to the client machine: {clientMachineName}"); + } + + PS.Commands.Clear(); + _logger.LogTrace("PS-Session established"); + + } + + // Load KF PowerShell Scripts + if (!IsLocalMachine) + { + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) + .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript("WinCertFull.ps1"))); + + var results = PS.Invoke(); + } + } + + public void Terminate() + { + PS.Commands.Clear(); + PS.Dispose(); + } + + public Collection? ExecuteFunction(string functionName) + { + return ExecutePowerShell(functionName); + } + + public Collection? ExecuteScriptBlock(string scriptBlock, Dictionary? parameters = null) + { + return ExecutePowerShell(scriptBlock, parameters); + } + + private Collection? ExecutePowerShell(string scriptBlock, Dictionary? parameters = null) + { + if (isLocalMachine) + { + PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope Process -Force"); + PS.Invoke(); // Ensure the script is invoked and loaded + PS.Commands.Clear(); // Clear commands after loading functions + + string scriptFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PowerShellScripts", "WinCertFull.ps1"); + PS.AddScript(". '" + scriptFilePath + "'"); + PS.Invoke(); // Ensure the script is invoked and loaded + PS.Commands.Clear(); // Clear commands after loading functions } - _logger.LogTrace("Attempting to invoke PS-Session command."); - _PSSession = PS.Invoke(); - if (PS.HadErrors) + // Add parameters to the script block + var argList = new List(); + if (parameters != null) { - foreach (var error in PS.Streams.Error) + foreach (var parameter in parameters.Values) { - _logger.LogError($"Error: {error}"); + argList.Add(parameter); } - throw new Exception($"An error occurred while attempting to connect to the client machine: {clientMachineName}"); } - PS.Commands.Clear(); - _logger.LogTrace("PS-Session established"); + if (!isLocalMachine) + { + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) // send session only when necessary (remote) + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) + .AddParameter("ArgumentList", argList.ToArray()); + } + else + { + PS.AddCommand("Invoke-Command") + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) + .AddParameter("ArgumentList", argList.ToArray()); + } + + try + { + var results = PS.Invoke(); + if (PS.HadErrors) + { + foreach (var error in PS.Streams.Error) + { + Console.WriteLine($"Error: {error}"); + } + } + + return results; + } + catch (Exception ex) + { + Console.WriteLine($"Exception: {ex.Message}"); + return null; + } + finally + { + PS.Commands.Clear(); + } } public Collection? ExecuteCommand(string scriptBlock, Dictionary parameters = null) diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 new file mode 100644 index 0000000..2e8ef2a --- /dev/null +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -0,0 +1,575 @@ +function Get-KFCertificate +{ + param ( + [string]$StoreName = "My" # Default store name is "My" (Personal) + ) + + # Get all certificates from the specified store + $certificates = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" + + # Initialize an array to store the results + $certInfoList = @() + + foreach ($cert in $certificates) { + # Create a custom object to store the certificate information + $certInfo = [PSCustomObject]@{ + StoreName = $StoreName + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + SAN = Get-KFSAN $cert + ProviderName = Get-CertificateCSP $cert + Base64Data = [System.Convert]::ToBase64String($cert.RawData) + } + + # Add the certificate information to the array + $certInfoList += $certInfo + } + + # Output the results + if ($certInfoList) { + $certInfoList | ConvertTo-Json + } +} + +function Get-KFIISBoundCertificates +{ + # Import the WebAdministration module + Import-Module IISAdministration + Import-Module WebAdministration + + # Get all websites + $websites = Get-Website + + # Initialize an array to store the results + $certificates = @() + + foreach ($site in $websites) { + # Get the site name + $siteName = $site.name + + # Get the bindings for the site + $bindings = Get-WebBinding -Name $siteName + + foreach ($binding in $bindings) { + # Check if the binding has an SSL certificate + if ($binding.protocol -eq 'https') { + # Get the certificate hash + $certHash = $binding.certificateHash + + # Get the certificate store + $StoreName = $binding.certificateStoreName + + # Get the certificate details from the certificate store + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName\$certHash" + + $certBase64 = [Convert]::ToBase64String($cert.RawData) + + # Create a custom object to store the results + $certInfo = [PSCustomObject]@{ + SiteName = $siteName + Binding = $binding.bindingInformation + IPAddress = ($binding.bindingInformation -split ":")[0] + Port = ($binding.bindingInformation -split ":")[1] + Hostname = ($binding.bindingInformation -split ":")[2] + Protocol = $binding.protocol + SNI = $binding.sslFlags -eq 1 + ProviderName = Get-CertificateCSP $cert + SAN = Get-KFSAN $cert + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + CertificateBase64 = $certBase64 + } + + # Add the certificate information to the array + $certificates += $certInfo + } + } + } + + # Output the results + if ($certificates) { + $certificates | ConvertTo-Json + } + +} + +function Get-KFSQLBoundCertificate +{ + param ( + [string]$SQLInstanceName = "MSSQLSERVER" # Default instance name + ) + + # Initialize an array to store the results + $certificates = @() + + try { + $SQLInstancePath = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SQLInstanceName -ErrorAction Stop + + # Define the registry path to look for certificate binding information + $sqlRegPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$SQLInstancePath\MSSQLServer\SuperSocketNetLib" + + try { + # Check if the registry path exists + if (Test-Path $sqlRegPath) { + # Get the certificate thumbprint from the registry + $certThumbprint = (Get-ItemProperty -Path $sqlRegPath -Name "Certificate").Certificate + + if ($certThumbprint) { + # Clean up the thumbprint (remove any leading/trailing spaces) + $certThumbprint = $certThumbprint.Trim() + + # Retrieve the certificate details from the certificate store + $cert = Get-ChildItem -Path "Cert:\LocalMachine\My\$certThumbprint" + + if ($cert) { + # Create a custom object to store the result + $certInfo = [PSCustomObject]@{ + StoreName = $StoreName + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + SAN = Get-KFSAN $cert + ProviderName = Get-CertificateCSP $cert + Base64Data = [System.Convert]::ToBase64String($cert.RawData) + } + + # Add the certificate information to the array + $certificates += $certInfo + } + } + + # Output the results + if ($certificates) { + $certificates | ConvertTo-Json + } + } + else { + Write-Information "INFO: Certificate is not bound to SQL Server instance: $SQLInstanceName" + } + } + catch { + Write-Information "ERROR: An error occurred while retrieving certificate information: $_" + } + + } + catch { + Write-Host "WARN: Unable to find the SQL Instance: $SQLInstanceName" + Write-Information "WARN: Unable to find the SQL Instance: $SQLInstanceName" + } +} + +function Add-KFCertificateToStore +{ + param ( + [Parameter(Mandatory = $true)] + [string]$Base64Cert, + + [Parameter(Mandatory = $false)] + [string]$PrivateKeyPassword, + + [Parameter(Mandatory = $true)] + [string]$StoreName, + + [Parameter(Mandatory = $false)] + [string]$CryptoServiceProvider + ) + + try { + # Before we get started, if a CSP has been provided, make sure it exists + if($CryptoServiceProvider) + { + if(-not (Test-CryptoServiceProvider -CSPName $CryptoServiceProvider)){ + Write-Host "The CSP $CryptoServiceProvider was not found on the system." + return + } + } + + # Convert Base64 string to byte array + $certBytes = [Convert]::FromBase64String($Base64Cert) + + # Create a temporary file to store the certificate + $tempStoreName = [System.IO.Path]::GetTempFileName() + [System.IO.File]::WriteAllBytes($tempStoreName, $certBytes) + + $thumbprint = $null + + if ($CryptoServiceProvider) { + # Create a temporary PFX file + $tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") + $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } + + # Create the PFX from the certificate + $pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + + # Export the PFX to the temporary file + $pfxBytes = $pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) + [System.IO.File]::WriteAllBytes($tempPfxPath, $pfxBytes) + + #$pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + #$pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) | Set-Content -Encoding Byte -Path $tempPfxPath + + # Use certutil to import the PFX with the specified CSP + $importCmd = "certutil -f -importpfx -csp '$CryptoServiceProvider' -p '$pfxPassword' $StoreName $tempPfxPath" + write-host $importCmd + Invoke-Expression $importCmd + + #$importCmd = "certutil -f -importpfx $tempPfxPath -p $pfxPassword -csp `"$CryptoServiceProvider`"" + #Invoke-Expression $importCmd + + # Retrieve the thumbprint after the import + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" | Where-Object { $_.Subject -like "*$($pfxCert.Subject)*" } | Sort-Object NotAfter -Descending | Select-Object -First 1 + if ($cert) { + $thumbprint = $cert.Thumbprint + } + + # Clean up the temporary PFX file + Remove-Item $tempPfxPath + } else { + # Load the certificate from the temporary file + if ($PrivateKeyPassword) { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName, $PrivateKeyPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) + } else { + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName) + } + + # Open the certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + + # Add the certificate to the store + $store.Add($cert) + + # Get the thumbprint so it can be returned to the calling function + $thumbprint = $cert.Thumbprint + + # Close the store + $store.Close() + } + + # Clean up the temporary file + Remove-Item $tempStoreName + + Write-Host "Certificate added successfully to $StoreName." + return $thumbprint + } catch { + Write-Error "An error occurred: $_" + return $null + } +} + +function Remove-KFCertificateFromStore +{ + param ( + [string]$Thumbprint, + [string]$StorePath, + + [parameter(ParameterSetName = $false)] + [switch]$IsAlias + ) + + try { + # Open the certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StorePath, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + + # Find the certificate by thumbprint or alias + if ($IsAlias) { + $cert = $store.Certificates | Where-Object { $_.FriendlyName -eq $Thumbprint } + } else { + $cert = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } + } + + if ($cert) { + # Remove the certificate from the store + $store.Remove($cert) + Write-Host "Certificate removed successfully from $StorePath." + } else { + Write-Error "Certificate not found in $StorePath." + } + + # Close the store + $store.Close() + } catch { + Write-Error "An error occurred: $_" + } +} + +function New-KFIISSiteBinding +{ + param ( + [Parameter(Mandatory = $true)] + [string]$Thumbprint, + [Parameter(Mandatory = $true)] + [string]$WebSite, + [Parameter(Mandatory = $true)] + [string]$Protocol, + [Parameter(Mandatory = $true)] + [string]$IPAddress, + [Parameter(Mandatory = $true)] + [int32]$Port, + [Parameter(Mandatory = $False)] + [string]$HostName, + [Parameter(Mandatory = $true)] + [string]$SNIFlag, + [Parameter(Mandatory = $true)] + [string]$StoreName + ) + + Write-Information "INFO: Entered Add-CertificateToWebsites." + Write-Information "Thumbprint: $Thumbprint" + Write-Information "Website: $WebSite" + Write-Information "IPAddress: $IPAddress" + Write-Information "Port: $Port" + Write-Information "HostName: $HostName" + Write-Information "Store Path: $StoreName" + + Write-Information "Attempting to load WebAdministration" + Import-Module WebAdministration -ErrorAction Stop + Write-Information "Web Administration module has been loaded and will be used" + + try { + # Get the certificate from the store from the supplied thumbprint + $certificate = Get-KFCertificateByThumbprint -Thumbprint $Thumbprint -StoreName $StoreName + if (-not $certificate) { + Write-Error "Certificate with thumbprint: $thumbprint could not be retrieved. Exiting IIS Binding." + return + } + + $bindingInfo = "$($IPAddress):$($Port):$($HostName)" + $Binding = Get-WebBinding -Name $Website | Where-Object {$_.bindingInformation -eq $bindingInfo} + + if ($binding) { + $bindingInfo = "*:{$Port}:$HostName" + $bindingItem = Get-Item "IIS:\SslBindings\$bindingInfo" + + # Check if the binding already has a certificate thumbprint + $existingThumbprint = (Get-ItemProperty -Path $bindingItem.PSPath -Name CertificateThumbprint).CertificateThumbprint + + if ($existingThumbprint -ne $cert.Thumbprint) { + # Update the binding with the new SSL certificate's thumbprint + Set-ItemProperty -Path $bindingItem.PSPath -Name CertificateThumbprint -Value $cert.Thumbprint + Write-Output "Updated binding with new certificate thumbprint." + } else { + Write-Output "The binding already has the correct certificate thumbprint." + } + } else { + # If the binding doesn't exist, create it + New-WebBinding -Name $Website -Protocol $Protocol -Port $Port -IPAddress $IPAddress -HostHeader $HostName -SslFlags $SNIFlag + $NewBinding = Get-WebBinding -Name $Website -Protocol $Protocol -Port $port -IPAddress $IPAddress + $NewBinding.AddSslCertificate($Certificate.Thumbprint, $StorePath) + Write-Output "Created new binding and assigned certificate thumbprint." + } + + } catch { + Write-Host "ERROR: An error occurred while binding the certificate: $_" + } +} + +function Remove-KFIISBinding +{ + param ( + [Parameter(Mandatory=$true)] + [string]$SiteName, # The name of the IIS website + + [Parameter(Mandatory=$true)] + [string]$IPAddress, # The IP address of the binding + + [Parameter(Mandatory=$true)] + [int]$Port, # The port number (e.g., 443 for HTTPS) + + [Parameter(Mandatory=$false)] + [string]$Hostname # The hostname (empty string for binding without hostname) + ) + + # Import WebAdministration module if it's not already imported + if (-not (Get-Module -ListAvailable -Name WebAdministration)) { + Import-Module WebAdministration + } + + try { + # Build the Binding Information format (IP:Port:Hostname) + $bindingInfo = "$($IPAddress):$($Port):$($HostName)" + + # Get all HTTPS bindings for the site + $bindings = Get-WebBinding -Name $SiteName -Protocol "https" + + if (-not $bindings) { + Write-Host "No HTTPS bindings found for site '$SiteName'." + return + } + + # Find the binding that matches the provided IP, Port, and Hostname + $bindingToRemove = $bindings | Where-Object { + $_.bindingInformation -eq $bindingInfo + } + + if (-not $bindingToRemove) { + Write-Host "No matching HTTPS binding found with IP '$IPAddress', Port '$Port', and Hostname '$Hostname' for site '$SiteName'." + return + } + + # Remove the binding from IIS + Remove-WebBinding -Name $SiteName -IPAddress $IPAddress -Port $Port -HostHeader $Hostname -Protocol "https" + Write-Host "Removed HTTPS binding from site '$SiteName' (IP: $IPAddress, Port: $Port, Hostname: $Hostname)." + + } catch { + Write-Error "An error occurred while trying to remove the certificate binding: $_" + } +} + +function New-KFSQLBinding +{ + param ( + [Parameter(Mandatory=$true)] + [string]$SqlInstanceName, # The name of the SQL Server instance (e.g., "MSSQLSERVER" for default) + + [Parameter(Mandatory=$true)] + [string]$CertificateThumbprint, # The thumbprint of the certificate to be bound + + [Parameter(Mandatory=$false)] + [string]$CertStoreLocation = "Cert:\LocalMachine\My" # Certificate store location (defaults to LocalMachine\My) + ) + + # Load the SQL Server WMI provider + $wmiNamespace = "root\Microsoft\SqlServer\ComputerManagement14" # For SQL Server 2017 and later + if (-not (Get-WmiObject -Namespace $wmiNamespace -List | Out-Null)) { + throw "Could not load WMI namespace for SQL Server. Ensure SQL Server WMI provider is installed." + } + + try { + # Get the SQL Server instance using WMI + $sqlService = Get-WmiObject -Namespace $wmiNamespace -Class SqlService | Where-Object { + $_.ServiceName -eq $SqlInstanceName -and $_.SQLServiceType -eq 1 + } + + if (-not $sqlService) { + throw "SQL Server instance '$SqlInstanceName' not found." + } + + # Ensure the certificate exists in the specified store + $certificate = Get-ChildItem -Path "$CertStoreLocation" | Where-Object { $_.Thumbprint -eq $CertificateThumbprint } + if (-not $certificate) { + throw "Certificate with thumbprint '$CertificateThumbprint' not found in store '$CertStoreLocation'." + } + + # Bind the certificate to the SQL Server instance using WMI + $sqlService.SetEncryption($CertificateThumbprint) + + Write-Host "Certificate with thumbprint '$CertificateThumbprint' successfully bound to SQL Server instance '$SqlInstanceName'." + + } catch { + Write-Error "An error occurred: $_" + } +} + +# Shared Functions +# Function to get SAN (Subject Alternative Names) from a certificate +function Get-KFSAN($cert) +{ + $san = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } + if ($san) { + return ($san.Format(1) -split ", " -join "; ") + } + return $null +} + +#Function to verify if the given CSP is found on the computer +function Test-CryptoServiceProvider +{ + param( + [Parameter(Mandatory = $true)] + [string]$CSPName + ) + + # Function to get the list of installed Cryptographic Service Providers from the registry + function Get-CryptoServiceProviders { + $cspRegistryPath = "HKLM:\SOFTWARE\Microsoft\Cryptography\Defaults\Provider" + + # Retrieve all CSP names from the registry + $providers = Get-ChildItem -Path $cspRegistryPath | Select-Object -ExpandProperty PSChildName + + return $providers + } + + # Get the list of installed CSPs + $installedCSPs = Get-CryptoServiceProviders + + # Check if the user-provided CSP exists in the list + if ($installedCSPs -contains $CSPName) { + return $true + } + else { + return $false + } +} + +# Function that takes an x509 certificate object and returns the csp +function Get-CertificateCSP +{ + param ( + [Parameter(Mandatory = $true)] + [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert + ) + + # Check if the certificate has a private key + if ($Cert -and $Cert.HasPrivateKey) { + try { + # Access the certificate's private key to get CSP info + $key = $Cert.PrivateKey + + # Retrieve the Provider Name from the private key + $cspName = $key.Key.Provider.Provider + + # Return the CSP name + return $cspName + } catch { + return $null + } + } else { + return $null + } +} + +# This function returns a certificate object based upon the store and thumbprint received +function Get-KFCertificateByThumbprint +{ + param ( + [Parameter(Mandatory = $true)] + [string]$Thumbprint, + + [Parameter(Mandatory = $true)] + [string]$StoreName + ) + + try { + + [System.Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine + + # Open the specified certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::$StoreLocation) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) + + # Find the certificate by thumbprint + $certificate = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } + + # Close the store after retrieving the certificate + $store.Close() + + if (-not $certificate) { + throw "No certificate found with thumbprint $Thumbprint in store $StoreName" + } + + return $certificate + } catch { + Write-Error "An error occurred while retrieving the certificate: $_" + return $null + } +} \ No newline at end of file diff --git a/IISU/RemoteSettings.cs b/IISU/RemoteSettings.cs index 38794ea..5a5ab33 100644 --- a/IISU/RemoteSettings.cs +++ b/IISU/RemoteSettings.cs @@ -7,7 +7,7 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - internal class RemoteSettings + public class RemoteSettings { public string ClientMachineName { get; set; } public string Protocol{ get; set; } diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index 8a85584..adc1683 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -61,6 +61,9 @@ Always + + Always + diff --git a/WinCertUnitTests/UnitTestIISBinding.cs b/WinCertUnitTests/UnitTestIISBinding.cs index 7c17a65..f50dcd8 100644 --- a/WinCertUnitTests/UnitTestIISBinding.cs +++ b/WinCertUnitTests/UnitTestIISBinding.cs @@ -36,104 +36,119 @@ public UnitTestIISBinding() [TestMethod] public void RenewBindingCertificate() { - X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); + //X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", cert.Thumbprint, "My", "0"); - JobResult result = IIS.BindCertificate(cert); - Assert.AreEqual("Success", result.Result.ToString()); + //ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", cert.Thumbprint, "My", "0"); + //JobResult result = IIS.BindCertificate(cert); + //Assert.AreEqual("Success", result.Result.ToString()); } [TestMethod] public void UnBindCertificate() { - X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); + //X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - BindingNewCertificate(); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //BindingNewCertificate(); - string sslFlag = "0"; - ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", "", "My", sslFlag); - JobResult result = IIS.UnBindCertificate(); + //string sslFlag = "0"; + //ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", "", "My", sslFlag); + //JobResult result = IIS.UnBindCertificate(); - Assert.AreEqual("Success", result.Result.ToString()); + //Assert.AreEqual("Success", result.Result.ToString()); } [TestMethod] public void BindingNewCertificate() { - X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); + //X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - string sslFlag = "0"; + //string sslFlag = "0"; - ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", "", "My", sslFlag); - JobResult result = IIS.BindCertificate(cert); + //ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", "", "My", sslFlag); + //JobResult result = IIS.BindCertificate(cert); - Assert.AreEqual("Success", result.Result.ToString()); + //Assert.AreEqual("Success", result.Result.ToString()); } [TestMethod] public void BindingNewCertificateBadSslFlag() { - X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); + //X509Certificate2 cert = new X509Certificate2(pfxPath, certPassword); - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - string sslFlag = "909"; // known bad value + //string sslFlag = "909"; // known bad value - ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", "", "My", sslFlag); - JobResult result = IIS.BindCertificate(cert); + //ClientPSIIManager IIS = new ClientPSIIManager(rs, "Default Web Site", "https", "*", "443", "", "", "My", sslFlag); + //JobResult result = IIS.BindCertificate(cert); - Assert.AreEqual("Failure", result.Result.ToString()); + //Assert.AreEqual("Failure", result.Result.ToString()); } [TestMethod] public void AddCertificate() { - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - rs.Open(); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //rs.Open(); - ClientPSCertStoreManager certStoreManager = new ClientPSCertStoreManager(rs); - JobResult result = certStoreManager.ImportPFXFile(pfxPath, certPassword, "", "My"); - rs.Close(); + //ClientPSCertStoreManager certStoreManager = new ClientPSCertStoreManager(rs); + //JobResult result = certStoreManager.ImportPFXFile(pfxPath, certPassword, "", "My"); + //rs.Close(); - Assert.AreEqual("Success", result.Result.ToString()); + //Assert.AreEqual("Success", result.Result.ToString()); } [TestMethod] public void RemoveCertificate() { - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - rs.Open(); - - ClientPSCertStoreManager certStoreManager = new ClientPSCertStoreManager(rs); - try - { - certStoreManager.RemoveCertificate("0a3f880aa17c03ef2c75493497d89756cfafa165", "My"); - Assert.IsTrue(true, "Certificate was successfully removed."); - } - catch (Exception e) - { - Assert.IsFalse(false, e.Message); - } - rs.Close(); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //rs.Open(); + + //ClientPSCertStoreManager certStoreManager = new ClientPSCertStoreManager(rs); + //try + //{ + // certStoreManager.RemoveCertificate("0a3f880aa17c03ef2c75493497d89756cfafa165", "My"); + // Assert.IsTrue(true, "Certificate was successfully removed."); + //} + //catch (Exception e) + //{ + // Assert.IsFalse(false, e.Message); + //} + //rs.Close(); } [TestMethod] public void GetBoundCertificates() { - Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); - rs.Open(); - WinIISInventory IISInventory = new WinIISInventory(); - List certs = IISInventory.GetInventoryItems(rs, "My"); - rs.Close(); + //Runspace rs = PsHelper.GetClientPsRunspace("", "localhost", "", false, "", ""); + //rs.Open(); + //WinIISInventory IISInventory = new WinIISInventory(); + //List certs = IISInventory.GetInventoryItems(rs, "My"); + //rs.Close(); + + //Assert.IsNotNull(certs); - Assert.IsNotNull(certs); + } + [TestMethod] + public void GetIISInventory() + { + Inventory inv = new(); + RemoteSettings settings = new(); + settings.ClientMachineName = "localMachine"; + settings.Protocol = "ssh"; + settings.Port = "443"; + settings.IncludePortInSPN = false; + settings.ServerUserName = "administrator"; + settings.ServerPassword = "@dminP@ssword@"; + + Listcerts = inv.QueryIISCertificates(settings); } [TestMethod] diff --git a/WinCertUnitTests/WinCertUnitTests.cs b/WinCertUnitTests/WinCertUnitTests.cs new file mode 100644 index 0000000..6c5e9e9 --- /dev/null +++ b/WinCertUnitTests/WinCertUnitTests.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace WinCertUnitTests +{ + [TestClass] + public class WinCertUnitTests + { + [TestMethod] + public void TestGetInventory() + { + Inventory inv = new(); + RemoteSettings settings = new(); + settings.ClientMachineName = "localMachine"; + settings.Protocol = "ssh"; + settings.Port = "443"; + settings.IncludePortInSPN = false; + settings.ServerUserName = "administrator"; + settings.ServerPassword = "@dminP@ssword@"; + + List certs = inv.QueryIISCertificates(settings); + } + } +} From d09af3672f98ff3f77ebc8ac6ff54990eec8a965 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 22 Oct 2024 14:53:25 -0500 Subject: [PATCH 03/21] Debugging IIS Scripts --- IISU/Certificate.cs | 10 +- IISU/ImplementedStoreTypes/Win/Inventory.cs | 104 ++++-- IISU/ImplementedStoreTypes/Win/Management.cs | 187 ++++++----- .../Win/WinCertCertificateInfo.cs | 23 ++ .../ImplementedStoreTypes/WinIIS/Inventory.cs | 9 +- .../WinIIS/Management.cs | 310 +++++++++++------- IISU/PSHelper.cs | 136 ++++++-- IISU/PowerShellScripts/WinCertFull.ps1 | 15 +- IISU/WindowsCertStore.csproj | 7 - WinCertUnitTests/WinCertUnitTests.cs | 20 +- 10 files changed, 527 insertions(+), 294 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs diff --git a/IISU/Certificate.cs b/IISU/Certificate.cs index 2af3035..360c5f8 100644 --- a/IISU/Certificate.cs +++ b/IISU/Certificate.cs @@ -33,25 +33,25 @@ public class Certificate public class Utilities { - public static List DeserializeCertificates(string jsonResults) + public static List DeserializeCertificates(string jsonResults) { if (string.IsNullOrEmpty(jsonResults)) { // Handle no objects returned - return new List(); + return new List(); } // Determine if the JSON is an array or a single object if (jsonResults.TrimStart().StartsWith("[")) { // It's an array, deserialize as list - return JsonConvert.DeserializeObject>(jsonResults); + return JsonConvert.DeserializeObject>(jsonResults); } else { // It's a single object, wrap it in a list - var singleObject = JsonConvert.DeserializeObject(jsonResults); - return new List { singleObject }; + var singleObject = JsonConvert.DeserializeObject(jsonResults); + return new List { singleObject }; } } diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index 54fe6e9..e0dade0 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -20,6 +20,8 @@ using System.Management.Automation.Runspaces; using System.Net; using System.Security.Cryptography.X509Certificates; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.Win; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -33,6 +35,13 @@ public class Inventory : WinCertJobTypeBase, IInventoryJobExtension { private ILogger _logger; public string ExtensionName => "WinCertInventory"; + + Collection? results = null; + + public Inventory() + { + + } public Inventory(IPAMSecretResolver resolver) { @@ -44,51 +53,46 @@ public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitIn _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); - return PerformInventory(jobConfiguration, submitInventoryUpdate); - } - - private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) - { try { var inventoryItems = new List(); - _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(config)); + _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(jobConfiguration)); - string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", jobConfiguration.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", jobConfiguration.ServerPassword); // Deserialize specific job properties - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + var jobProperties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); string protocol = jobProperties.WinRmProtocol; string port = jobProperties.WinRmPort; bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; + string clientMachineName = jobConfiguration.CertificateStoreDetails.ClientMachine; + string storePath = jobConfiguration.CertificateStoreDetails.StorePath; if (storePath != null) { // Create the remote connection class to pass to Inventory Class RemoteSettings settings = new(); - settings.ClientMachineName = config.CertificateStoreDetails.ClientMachine; + settings.ClientMachineName = jobConfiguration.CertificateStoreDetails.ClientMachine; settings.Protocol = jobProperties.WinRmProtocol; settings.Port = jobProperties.WinRmPort; settings.IncludePortInSPN = jobProperties.SpnPortFlag; settings.ServerUserName = serverUserName; settings.ServerPassword = serverPassword; - WinInventory winInventory = new(_logger); - inventoryItems = winInventory.GetInventoryItems(settings, storePath); + _logger.LogTrace($"Querying Window certificate in store: {storePath}"); + inventoryItems = QueryWinCertCertificates(settings, storePath); - _logger.LogTrace("Invoking Inventory..."); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked ... {inventoryItems.Count} Items"); + _logger.LogTrace("Invoking submitInventory.."); + submitInventoryUpdate.Invoke(inventoryItems); + _logger.LogTrace($"submitInventory Invoked... {inventoryItems.Count} Items"); return new JobResult { Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, + JobHistoryId = jobConfiguration.JobHistoryId, FailureMessage = "" }; } @@ -96,36 +100,72 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven return new JobResult { Result = OrchestratorJobStatusJobResult.Warning, - JobHistoryId = config.JobHistoryId, + JobHistoryId = jobConfiguration.JobHistoryId, FailureMessage = $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" }; } - catch (CertificateStoreException psEx) - { - _logger.LogTrace(psEx.Message); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Unable to open remote certificate store: {LogHandler.FlattenException(psEx)}" - }; - } catch (Exception ex) { _logger.LogTrace(LogHandler.FlattenException(ex)); - var failureMessage = $"Inventory job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + var failureMessage = $"Inventory job failed for Site '{jobConfiguration.CertificateStoreDetails.StorePath}' on server '{jobConfiguration.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; _logger.LogWarning(failureMessage); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, + JobHistoryId = jobConfiguration.JobHistoryId, FailureMessage = failureMessage }; } + + } + + public List QueryWinCertCertificates(RemoteSettings settings, string StoreName) + { + List Inventory = new(); + + string command = string.Empty; + + using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + { + ps.Initialize(); + + command = $"Get-KFCertificates -StoreName '{StoreName}'"; + results = ps.ExecuteFunction(command); + + // If there are certificates, deserialize the results and send them back to command + if (results != null && results.Count > 0) + { + var jsonResults = results[0].ToString(); + var certInfoList = Certificate.Utilities.DeserializeCertificates(jsonResults); // JsonConvert.DeserializeObject>(jsonResults); + + foreach (WinCertCertificateInfo cert in certInfoList) + { + var siteSettingsDict = new Dictionary + { + { "ProviderName", cert.ProviderName}, + { "SAN", cert.SAN } + }; + + Inventory.Add( + new CurrentInventoryItem + { + Certificates = new[] { cert.Base64Data}, + Alias = cert.Thumbprint, + PrivateKeyEntry = cert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = siteSettingsDict + } + ); + } + } + ps.Terminate(); + } + + return Inventory; } } } diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index cc2e64f..232695d 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -20,24 +20,28 @@ using Microsoft.Extensions.Logging; using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Management.Automation.Runspaces; using System.Management.Automation; -using System.Net; using Keyfactor.Logging; -using System.IO; +using System.Collections.ObjectModel; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { public class Management : WinCertJobTypeBase, IManagementJobExtension { + public string ExtensionName => "WinCertManagement"; private ILogger _logger; - public string ExtensionName => "WinCertManagement"; + private string command = string.Empty; + private PSHelper _psHelper; + private Collection? _results = null; - private Runspace myRunspace; + // Function wide config values + private string _clientMachineName = string.Empty; + private string _storePath = string.Empty; + private long _jobHistoryID = 0; + private CertStoreOperationType _operationType; - private string _thumbprint = string.Empty; + //private Runspace myRunspace; public Management(IPAMSecretResolver resolver) { @@ -48,6 +52,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { try { + // Do some setup stuff _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); @@ -60,20 +65,6 @@ public JobResult ProcessJob(ManagementJobConfiguration config) _logger.LogTrace(e.Message); } - string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string protocol = jobProperties.WinRmProtocol; - string port = jobProperties.WinRmPort; - bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; - long JobHistoryID = config.JobHistoryId; - - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - var complete = new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -82,124 +73,148 @@ public JobResult ProcessJob(ManagementJobConfiguration config) "Invalid Management Operation" }; - switch (config.OperationType) + // Start parsing config information and establishing PS Session + _jobHistoryID = config.JobHistoryId; + _storePath = config.CertificateStoreDetails.StorePath; + _clientMachineName = config.CertificateStoreDetails.ClientMachine; + _operationType = config.OperationType; + + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + string protocol = jobProperties?.WinRmProtocol; + string port = jobProperties?.WinRmPort; + bool includePortInSPN = (bool)jobProperties?.SpnPortFlag; + + _psHelper = new(protocol, port, includePortInSPN, _clientMachineName, serverUserName, serverPassword); + + switch (_operationType) { case CertStoreOperationType.Add: { - myRunspace.Open(); - _logger.LogTrace("runSpace Opened"); - - complete = performAddition(config); + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); - myRunspace.Close(); - _logger.LogTrace($"RunSpace was closed..."); + complete = AddCertificate(certificateContents, privateKeyPassword, _storePath, cryptoProvider); + _logger.LogTrace($"Completed adding the certificate to the store"); break; } case CertStoreOperationType.Remove: { - myRunspace.Open(); - _logger.LogTrace("runSpace Opened"); + string thumbprint = config.JobCertificate.Alias; - complete = performRemove(config); - - myRunspace.Close(); - _logger.LogTrace($"RunSpace was closed..."); + complete = RemoveCertificate(thumbprint, _storePath); + _logger.LogTrace($"Completed removing the certificate from the store"); break; } } + _logger.MethodExit(); return complete; } - catch (Exception e) + catch (Exception ex) { - _logger.LogError($"Error Occurred in Management.PerformManagement: {e.Message}"); - throw; + _logger.LogTrace(LogHandler.FlattenException(ex)); + + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = failureMessage + }; } } - private JobResult performAddition(ManagementJobConfiguration config) + public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath, string cryptoProvider) { try { -#nullable enable - string certificateContents = config.JobCertificate.Contents; - string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; - string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); -#nullable disable - - // If a crypto provider was provided, check to see if it exists - if (cryptoProvider != null) + using (_psHelper) { - _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); - if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) - { throw new Exception($"The Crypto Provider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } + _psHelper.Initialize(); + + _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); + command = $"Add-KFCertificateToStore -Base64Cert '{certificateContents}' -PrivateKeyPassword '{privateKeyPassword}' -StoreName '{storePath}' -CryptoServiceProvider '{cryptoProvider}'"; + _results = _psHelper.ExecuteFunction(command); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + var thumbprint = _results[0].ToString(); + _logger.LogTrace($"Added certificate to store {storePath}, returned with the thumbprint {thumbprint}"); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + } + _psHelper.Terminate(); } - if (storePath != null) + return new JobResult { - _logger.LogInformation($"Attempting to add WinCert certificate to cert store: {storePath}"); - - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - - // Write the certificate contents to a temporary file on the remote computer, returning the filename. - _logger.LogTrace($"Creating temporary pfx file."); - string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); - - // Using certutil on the remote computer, import the pfx file using a supplied csp if any. - _logger.LogTrace($"Importing temporary PFX File: {filePath}."); - JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); - - // Delete the temporary file - _logger.LogTrace($"Deleting temporary PFX File: {filePath}."); - manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; - return result; - } - else - { - throw new Exception($"The store path is empty or null."); - } } - catch (Exception e) + catch (Exception ex) { + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Management/Add {e.Message}" + JobHistoryId = _jobHistoryID, + FailureMessage = failureMessage }; } } - private JobResult performRemove(ManagementJobConfiguration config) + public JobResult RemoveCertificate(string thumbprint, string storePath) { try { - _logger.LogTrace($"Removing Certificate with Alias: {config.JobCertificate.Alias}"); - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, config.JobHistoryId); - manager.RemoveCertificate(config.JobCertificate.Alias, config.CertificateStoreDetails.StorePath); - _logger.LogTrace($"Removed Certificate with Alias: {config.JobCertificate.Alias}"); + using (_psHelper) + { + _psHelper.Initialize(); + + _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {storePath}"); + command = $"Remove-KFCertificateFromStore -Thumbprint '{thumbprint}' -StorePath '{storePath}'"; + _psHelper.ExecuteFunction(command); + _logger.LogTrace("Returned from executing PS function (Remove-KFCertificateFromStore)"); + + _psHelper.Terminate(); + } return new JobResult { Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, + JobHistoryId = _jobHistoryID, FailureMessage = "" }; } - catch (Exception e) + catch (Exception ex) { + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = $"Error Occurred while attempting to remove certificate: {LogHandler.FlattenException(e)}" + JobHistoryId = _jobHistoryID, + FailureMessage = failureMessage }; } } diff --git a/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs b/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs new file mode 100644 index 0000000..0f69d13 --- /dev/null +++ b/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.Win +{ + public class WinCertCertificateInfo + { + public string StoreName { get; set; } + public string Certificate { get; set; } + public string ExpiryDate { get; set; } + public string Issuer { get; set; } + public string Thumbprint { get; set; } + public bool HasPrivateKey { get; set; } + public string SAN { get; set; } + public string ProviderName { get; set; } + public string Base64Data { get; set; } + } +} diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 6af8c00..58b51b6 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -129,6 +129,11 @@ public List QueryIISCertificates(RemoteSettings settings) { ps.Initialize(); + // Check if IISAdministration is available and loaded + command = "Get-Module -ListAvailable"; + Dictionary parameters = new Dictionary(); + results = ps.ExecuteScriptBlock(command, parameters); + command = "Get-KFIISBoundCertificates"; results = ps.ExecuteFunction(command); @@ -136,7 +141,7 @@ public List QueryIISCertificates(RemoteSettings settings) if (results != null && results.Count > 0) { var jsonResults = results[0].ToString(); - var certInfoList = Certificate.Utilities.DeserializeCertificates(jsonResults); // JsonConvert.DeserializeObject>(jsonResults); + var certInfoList = Certificate.Utilities.DeserializeCertificates(jsonResults); // JsonConvert.DeserializeObject>(jsonResults); foreach (IISCertificateInfo cert in certInfoList) { @@ -156,7 +161,7 @@ public List QueryIISCertificates(RemoteSettings settings) new CurrentInventoryItem { Certificates = new[] {cert.CertificateBase64 }, - Alias = cert.Thumbprint, + Alias = cert.Thumbprint + ":" + cert.Binding?.ToString(), PrivateKeyEntry = cert.HasPrivateKey, UseChainLevel = false, ItemStatus = OrchestratorInventoryItemStatus.Unknown, diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 9047ea0..d0199f2 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -13,28 +13,34 @@ // limitations under the License. using System; +using System.Collections.ObjectModel; using System.IO; -using System.Linq; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; -using Microsoft.PowerShell.Commands; using Newtonsoft.Json; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { public class Management : WinCertJobTypeBase, IManagementJobExtension { + public string ExtensionName => "WinIISUManagement"; private ILogger _logger; - public string ExtensionName => "WinIISUManagement"; + private string command = string.Empty; + private PSHelper _psHelper; + private Collection? _results = null; - private Runspace myRunspace; + // Function wide config values + private string _clientMachineName = string.Empty; + private string _storePath = string.Empty; + private long _jobHistoryID = 0; + private CertStoreOperationType _operationType; + + //private Runspace myRunspace; public Management(IPAMSecretResolver resolver) { @@ -57,20 +63,6 @@ public JobResult ProcessJob(ManagementJobConfiguration config) _logger.LogTrace(e.Message); } - string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string protocol = jobProperties.WinRmProtocol; - string port = jobProperties.WinRmPort; - bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; - long JobHistoryID = config.JobHistoryId; - - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - var complete = new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -78,139 +70,227 @@ public JobResult ProcessJob(ManagementJobConfiguration config) "Invalid Management Operation" }; - switch (config.OperationType) + // Start parsing config information and establishing PS Session + _jobHistoryID = config.JobHistoryId; + _storePath = config.CertificateStoreDetails.StorePath; + _clientMachineName = config.CertificateStoreDetails.ClientMachine; + _operationType = config.OperationType; + + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + string protocol = jobProperties?.WinRmProtocol; + string port = jobProperties?.WinRmPort; + bool includePortInSPN = (bool)jobProperties?.SpnPortFlag; + + _psHelper = new(protocol, port, includePortInSPN, _clientMachineName, serverUserName, serverPassword); + + using (_psHelper) + + _psHelper.Initialize(); + + switch (_operationType) { case CertStoreOperationType.Add: - _logger.LogTrace("Entering Add..."); + { + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); + + // Add Certificate to Cert Store + using (_psHelper) + { - myRunspace.Open(); - complete = PerformAddCertificate(config, serverUserName, serverPassword); - myRunspace.Close(); + string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, _storePath, cryptoProvider); + _logger.LogTrace($"Completed adding the certificate to the store"); - _logger.LogTrace("After Perform Addition..."); - break; + // Bind Certificate to IIS Site + if (newThumbprint != null) + { + + } + + } + + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } case CertStoreOperationType.Remove: - _logger.LogTrace("Entering Remove..."); + { + string thumbprint = config.JobCertificate.Alias; - _logger.LogTrace("After PerformRemoval..."); - myRunspace.Open(); - complete = PerformRemoveCertificate(config, serverUserName, serverPassword); - myRunspace.Close(); + //complete = RemoveCertificate(thumbprint, _storePath); + _logger.LogTrace($"Completed removing the certificate from the store"); - _logger.LogTrace("After Perform Removal..."); - break; + break; + } } + _psHelper.Terminate(); + _logger.MethodExit(); return complete; } catch (Exception ex) { - _logger.LogTrace(LogHandler.FlattenException(ex)); - - var failureMessage = $"Management job {config.OperationType} failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; - _logger.LogWarning(failureMessage); + _logger.LogTrace(ex.Message); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message }; } } - private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) + public string AddCertificate(string certificateContents, string privateKeyPassword, string storePath, string cryptoProvider) { try { -#nullable enable - string certificateContents = config.JobCertificate.Contents; - string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; - string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); -#nullable disable - - // If a crypto provider was provided, check to see if it exists - if (cryptoProvider != null) - { - _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); - if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) - { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } - } + string newThumbprint = string.Empty; - if (storePath != null) + using (_psHelper) { - _logger.LogInformation($"Attempting to add IISU certificate to cert store: {storePath}"); + _psHelper.Initialize(); + + _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); + command = $"Add-KFCertificateToStore -Base64Cert '{certificateContents}' -PrivateKeyPassword '{privateKeyPassword}' -StoreName '{storePath}' -CryptoServiceProvider '{cryptoProvider}'"; + _results = _psHelper.ExecuteFunction(command); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + newThumbprint= _results[0].ToString(); + _logger.LogTrace($"Added certificate to store {storePath}, returned with the thumbprint {newThumbprint}"); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + } + _psHelper.Terminate(); } - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - - // This method is retired - //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); - - // Write the certificate contents to a temporary file on the remote computer, returning the filename. - string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); - _logger.LogTrace($"{filePath} was created."); - - // Using certutil on the remote computer, import the pfx file using a supplied csp if any. - JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); - - // Delete the temporary file - manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); - - if (result.Result == OrchestratorJobStatusJobResult.Success) - { - // Bind to IIS - _logger.LogInformation("Attempting to bind certificate to website."); - ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); - result = iisManager.BindCertificate(manager.X509Cert); - - // Provide logging information - if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the website."); } - else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the website. Check the logs for more information."); } - - return result; - } - else return result; + return newThumbprint; } - catch (Exception e) + catch (Exception ex) { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Management/Add {e.Message}" - }; + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogError(failureMessage); + + throw new Exception (failureMessage); } } - private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) + public void BindCertificate(string siteName, string protocol, string ipAddress, string port, string sniFlag, string storeName, string thumbPrint, string hostName = "") { - _logger.LogTrace("Before Remove Certificate..."); + /* + function New-KFIISSiteBinding + */ - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; + } - // First we need to unbind the certificate from IIS before we remove it from the store - ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); - JobResult result = iisManager.UnBindCertificate(); - if (result.Result == OrchestratorJobStatusJobResult.Success) - { - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - manager.RemoveCertificate(config.JobCertificate.Alias, storePath); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; - } - else return result; - } +// private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) +// { +// try +// { +//#nullable enable +// string certificateContents = config.JobCertificate.Contents; +// string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; +// string storePath = config.CertificateStoreDetails.StorePath; +// long jobNumber = config.JobHistoryId; +// string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); +//#nullable disable + +// // If a crypto provider was provided, check to see if it exists +// if (cryptoProvider != null) +// { +// _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); +// if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) +// { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } +// } + +// if (storePath != null) +// { +// _logger.LogInformation($"Attempting to add IISU certificate to cert store: {storePath}"); +// } + +// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + +// // This method is retired +// //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + +// // Write the certificate contents to a temporary file on the remote computer, returning the filename. +// string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); +// _logger.LogTrace($"{filePath} was created."); + +// // Using certutil on the remote computer, import the pfx file using a supplied csp if any. +// JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); + +// // Delete the temporary file +// manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + +// if (result.Result == OrchestratorJobStatusJobResult.Success) +// { +// // Bind to IIS +// _logger.LogInformation("Attempting to bind certificate to website."); +// ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); +// result = iisManager.BindCertificate(manager.X509Cert); + +// // Provide logging information +// if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the website."); } +// else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the website. Check the logs for more information."); } + +// return result; +// } +// else return result; +// } +// catch (Exception e) +// { +// return new JobResult +// { +// Result = OrchestratorJobStatusJobResult.Failure, +// JobHistoryId = config.JobHistoryId, +// FailureMessage = +// $"Management/Add {e.Message}" +// }; +// } +// } + +// private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) +// { +// _logger.LogTrace("Before Remove Certificate..."); + +// string storePath = config.CertificateStoreDetails.StorePath; +// long jobNumber = config.JobHistoryId; + +// // First we need to unbind the certificate from IIS before we remove it from the store +// ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); +// JobResult result = iisManager.UnBindCertificate(); + +// if (result.Result == OrchestratorJobStatusJobResult.Success) +// { +// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); +// manager.RemoveCertificate(config.JobCertificate.Alias, storePath); + +// return new JobResult +// { +// Result = OrchestratorJobStatusJobResult.Success, +// JobHistoryId = config.JobHistoryId, +// FailureMessage = "" +// }; +// } +// else return result; +// } } } \ No newline at end of file diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 95c291f..bed7bd5 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -37,6 +37,8 @@ internal class PSHelper : IDisposable private PowerShell PS; private Collection _PSSession = new Collection(); + private string scriptFileLocation = string.Empty; + private string protocol; private string port; private bool useSPN; @@ -102,6 +104,9 @@ public void Initialize() _logger.LogDebug($"isLocalMachine flag set to: {isLocalMachine}"); _logger.LogDebug($"Protocol is set to: {protocol}"); + scriptFileLocation = FindPSLocation(AppDomain.CurrentDomain.BaseDirectory, "WinCertFull.ps1"); + _logger.LogTrace($"Script file located here: {scriptFileLocation}"); + if (!isLocalMachine) { if (protocol == "ssh") @@ -131,7 +136,7 @@ public void Initialize() .AddParameter("SessionOption", sessionOption); } - _logger.LogTrace("Attempting to invoke PS-Session command."); + _logger.LogTrace("Attempting to invoke PS-Session command on remote machine."); _PSSession = PS.Invoke(); if (PS.HadErrors) { @@ -145,17 +150,25 @@ public void Initialize() PS.Commands.Clear(); _logger.LogTrace("PS-Session established"); - } - - // Load KF PowerShell Scripts - if (!IsLocalMachine) - { PS.AddCommand("Invoke-Command") .AddParameter("Session", _PSSession) - .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript("WinCertFull.ps1"))); + .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript(scriptFileLocation))); var results = PS.Invoke(); } + else + { + _logger.LogTrace("Setting Execution Policy to Unrestricted"); + // PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope Process -Force"); + PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force"); + PS.Invoke(); // Ensure the script is invoked and loaded + PS.Commands.Clear(); // Clear commands after loading functions + + _logger.LogTrace("Setting script file into memory"); + PS.AddScript(". '" + scriptFileLocation + "'"); + PS.Invoke(); // Ensure the script is invoked and loaded + PS.Commands.Clear(); // Clear commands after loading functions + } } public void Terminate() @@ -171,23 +184,38 @@ public void Terminate() public Collection? ExecuteScriptBlock(string scriptBlock, Dictionary? parameters = null) { + // THIS IS ONLY FOR TESTING + //using (PS) + //{ + PS.AddCommand(scriptBlock); + foreach (var param in parameters) + { + PS.AddParameter(param.Key, param.Value); + } + + var results = PS.Invoke(); + + // Display the results + foreach (var result in results) + { + Console.WriteLine(result); + } + + // Handle any errors + if (PS.Streams.Error.Count > 0) + { + foreach (var error in PS.Streams.Error) + { + Console.WriteLine($"Error: {error}"); + } + } + //} + return ExecutePowerShell(scriptBlock, parameters); } private Collection? ExecutePowerShell(string scriptBlock, Dictionary? parameters = null) { - if (isLocalMachine) - { - PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope Process -Force"); - PS.Invoke(); // Ensure the script is invoked and loaded - PS.Commands.Clear(); // Clear commands after loading functions - - string scriptFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PowerShellScripts", "WinCertFull.ps1"); - PS.AddScript(". '" + scriptFilePath + "'"); - PS.Invoke(); // Ensure the script is invoked and loaded - PS.Commands.Clear(); // Clear commands after loading functions - } - // Add parameters to the script block var argList = new List(); if (parameters != null) @@ -198,36 +226,44 @@ public void Terminate() } } - if (!isLocalMachine) - { - PS.AddCommand("Invoke-Command") - .AddParameter("Session", _PSSession) // send session only when necessary (remote) - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - .AddParameter("ArgumentList", argList.ToArray()); - } - else - { - PS.AddCommand("Invoke-Command") - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - .AddParameter("ArgumentList", argList.ToArray()); - } - try { + if (!isLocalMachine) + { + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) // send session only when necessary (remote) + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) + .AddParameter("ArgumentList", argList.ToArray()); + } + else + { + PS.AddCommand("Invoke-Command") + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) + .AddParameter("ArgumentList", argList.ToArray()); + } + + bool hadErrors = false; + string errorList = string.Empty; + _logger.LogTrace($"Script block:\n{scriptBlock}"); + var results = PS.Invoke(); if (PS.HadErrors) { + errorList = string.Empty; foreach (var error in PS.Streams.Error) { - Console.WriteLine($"Error: {error}"); + errorList += error + "\n"; + _logger.LogError($"Error: {error}"); } - } + throw new ApplicationException(errorList); + } + return results; } catch (Exception ex) { - Console.WriteLine($"Exception: {ex.Message}"); + _logger.LogError($"Error while executing script: {ex.Message}"); return null; } finally @@ -296,6 +332,34 @@ public static string LoadScript(string scriptFileName) { throw new Exception($"File: {scriptFilePath} was not found."); } } + private static string FindPSLocation(string directory, string fileName) + { + try + { + foreach (string file in Directory.GetFiles(directory)) + { + if (Path.GetFileName(file).Equals(fileName, StringComparison.OrdinalIgnoreCase)) + { + return Path.GetFullPath(file); + } + } + + foreach (string subDir in Directory.GetDirectories(directory)) + { + string result = FindPSLocation(subDir, fileName); + if (!string.IsNullOrEmpty(result)) + { + return result; + } + } + } + catch (UnauthorizedAccessException) + { + } + + return null; + } + public static void ProcessPowerShellScriptEvent(object? sender, DataAddedEventArgs e) { if (sender != null) diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index 2e8ef2a..d0f5621 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -1,4 +1,4 @@ -function Get-KFCertificate +function Get-KFCertificates { param ( [string]$StoreName = "My" # Default store name is "My" (Personal) @@ -38,10 +38,11 @@ function Get-KFIISBoundCertificates { # Import the WebAdministration module Import-Module IISAdministration - Import-Module WebAdministration + #Import-Module WebAdministration # Get all websites - $websites = Get-Website + #$websites = Get-Website + $websites = Get-IISSite # Initialize an array to store the results $certificates = @() @@ -51,13 +52,15 @@ function Get-KFIISBoundCertificates $siteName = $site.name # Get the bindings for the site - $bindings = Get-WebBinding -Name $siteName + #$bindings = Get-WebBinding -Name $siteName + $bindings = Get-IISSiteBinding -Name $siteName foreach ($binding in $bindings) { # Check if the binding has an SSL certificate if ($binding.protocol -eq 'https') { # Get the certificate hash - $certHash = $binding.certificateHash + #$certHash = $binding.certificateHash + $certHash = $binding.RawAttributes.certificateHash # Get the certificate store $StoreName = $binding.certificateStoreName @@ -290,7 +293,7 @@ function Remove-KFCertificateFromStore if ($cert) { # Remove the certificate from the store $store.Remove($cert) - Write-Host "Certificate removed successfully from $StorePath." + Write-Info "Certificate removed successfully from $StorePath." } else { Write-Error "Certificate not found in $StorePath." } diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index adc1683..95b9d06 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -65,11 +65,4 @@ Always - - - - 7.4.5 - - - diff --git a/WinCertUnitTests/WinCertUnitTests.cs b/WinCertUnitTests/WinCertUnitTests.cs index 6c5e9e9..5f99e05 100644 --- a/WinCertUnitTests/WinCertUnitTests.cs +++ b/WinCertUnitTests/WinCertUnitTests.cs @@ -3,6 +3,9 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert; +using Keyfactor.Orchestrators.Extensions; namespace WinCertUnitTests { @@ -14,14 +17,21 @@ public void TestGetInventory() { Inventory inv = new(); RemoteSettings settings = new(); - settings.ClientMachineName = "localMachine"; - settings.Protocol = "ssh"; - settings.Port = "443"; + settings.ClientMachineName = "vmlabsvr1"; + settings.Protocol = "http"; + settings.Port = "5985"; settings.IncludePortInSPN = false; settings.ServerUserName = "administrator"; - settings.ServerPassword = "@dminP@ssword@"; + settings.ServerPassword = "@dminP@ssword%"; + + // This function calls the Get-KFCertificates function and take the StoreName argument + List certs = inv.QueryWinCertCertificates(settings, "My"); + } + + [TestMethod] + public void TestAddCertificateToStore() + { - List certs = inv.QueryIISCertificates(settings); } } } From 3a4559ab37a4b4f8d1b08c5633a9117467263c91 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 22 Oct 2024 16:24:19 -0700 Subject: [PATCH 04/21] Added PowerShell runspace for localMachines --- .../ImplementedStoreTypes/WinIIS/Inventory.cs | 5 - IISU/PSHelper.cs | 219 ++++++++++-------- 2 files changed, 121 insertions(+), 103 deletions(-) diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 58b51b6..da633f2 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -129,11 +129,6 @@ public List QueryIISCertificates(RemoteSettings settings) { ps.Initialize(); - // Check if IISAdministration is available and loaded - command = "Get-Module -ListAvailable"; - Dictionary parameters = new Dictionary(); - results = ps.ExecuteScriptBlock(command, parameters); - command = "Get-KFIISBoundCertificates"; results = ps.ExecuteFunction(command); diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index bed7bd5..27999ac 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -27,6 +27,7 @@ using System.Management.Automation.Runspaces; using System.Net; using System.Text.Json; +using System.Xml.Serialization; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -105,75 +106,107 @@ public void Initialize() _logger.LogDebug($"Protocol is set to: {protocol}"); scriptFileLocation = FindPSLocation(AppDomain.CurrentDomain.BaseDirectory, "WinCertFull.ps1"); + if (scriptFileLocation == null) { throw new Exception("Unable to find the accompanying PowerShell Script file: WinCertFull.ps1"); } + _logger.LogTrace($"Script file located here: {scriptFileLocation}"); if (!isLocalMachine) { - if (protocol == "ssh") - { - // TODO: Need to add logic when using keyfilePath - // TODO: Need to add keyfilePath parameter - PS.AddCommand("New-PSSession") - .AddParameter("HostName", ClientMachineName) - .AddParameter("UserName", serverUserName); + InitializeRemoteSession(); + } + else + { + InitializeLocalSession(); + } + } - } - else - { - var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; - PSCredential myCreds = new PSCredential(serverUserName, pw); + private void InitializeRemoteSession() + { + if (protocol == "ssh") + { + // TODO: Need to add logic when using keyfilePath + // TODO: Need to add keyfilePath parameter + PS.AddCommand("New-PSSession") + .AddParameter("HostName", ClientMachineName) + .AddParameter("UserName", serverUserName); - // Create the PSSessionOption object - var sessionOption = new PSSessionOption - { - IncludePortInSPN = useSPN - }; - - PS.AddCommand("New-PSSession") - .AddParameter("ComputerName", ClientMachineName) - .AddParameter("Port", port) - .AddParameter("Credential", myCreds) - .AddParameter("SessionOption", sessionOption); - } + } + else + { + var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; + PSCredential myCreds = new PSCredential(serverUserName, pw); - _logger.LogTrace("Attempting to invoke PS-Session command on remote machine."); - _PSSession = PS.Invoke(); - if (PS.HadErrors) + // Create the PSSessionOption object + var sessionOption = new PSSessionOption { - foreach (var error in PS.Streams.Error) - { - _logger.LogError($"Error: {error}"); - } - throw new Exception($"An error occurred while attempting to connect to the client machine: {clientMachineName}"); - } + IncludePortInSPN = useSPN + }; + + PS.AddCommand("New-PSSession") + .AddParameter("ComputerName", ClientMachineName) + .AddParameter("Port", port) + .AddParameter("Credential", myCreds) + .AddParameter("SessionOption", sessionOption); + } - PS.Commands.Clear(); - _logger.LogTrace("PS-Session established"); + _logger.LogTrace("Attempting to invoke PS-Session command on remote machine."); + _PSSession = PS.Invoke(); + CheckErrors(); - PS.AddCommand("Invoke-Command") - .AddParameter("Session", _PSSession) - .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript(scriptFileLocation))); + PS.Commands.Clear(); + _logger.LogTrace("PS-Session established"); - var results = PS.Invoke(); - } - else - { - _logger.LogTrace("Setting Execution Policy to Unrestricted"); - // PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope Process -Force"); - PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope CurrentUser -Force"); - PS.Invoke(); // Ensure the script is invoked and loaded - PS.Commands.Clear(); // Clear commands after loading functions - - _logger.LogTrace("Setting script file into memory"); - PS.AddScript(". '" + scriptFileLocation + "'"); - PS.Invoke(); // Ensure the script is invoked and loaded - PS.Commands.Clear(); // Clear commands after loading functions - } + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) + .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript(scriptFileLocation))); + + var results = PS.Invoke(); + CheckErrors(); } + private void InitializeLocalSession() + { + _logger.LogTrace("Setting Execution Policy to Unrestricted"); + PS.AddScript("Set-ExecutionPolicy Unrestricted -Scope Process -Force"); + PS.Invoke(); // Ensure the script is invoked and loaded + CheckErrors(); + + PS.Commands.Clear(); // Clear commands after loading functions + + // Trying this to get IISAdministration loaded!! + PowerShellProcessInstance psInstance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); + Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), psInstance); + rs.Open(); + + PS.Runspace = rs; + + _logger.LogTrace("Setting script file into memory"); + PS.AddScript(". '" + scriptFileLocation + "'"); + PS.Invoke(); // Ensure the script is invoked and loaded + CheckErrors(); + + PS.Commands.Clear(); // Clear commands after loading functions + } + + public void Terminate() { PS.Commands.Clear(); + if (_PSSession.Count > 0) + { + PS.AddCommand("Remove-Session").AddParameter("Session", _PSSession); + PS.Invoke(); + CheckErrors(); + } + + try + { + PS.Runspace.Close(); + } + catch (Exception) + { + } + PS.Dispose(); } @@ -187,28 +220,20 @@ public void Terminate() // THIS IS ONLY FOR TESTING //using (PS) //{ - PS.AddCommand(scriptBlock); - foreach (var param in parameters) - { - PS.AddParameter(param.Key, param.Value); - } - - var results = PS.Invoke(); - - // Display the results - foreach (var result in results) - { - Console.WriteLine(result); - } + PS.AddCommand(scriptBlock); + foreach (var param in parameters) + { + PS.AddParameter(param.Key, param.Value); + } - // Handle any errors - if (PS.Streams.Error.Count > 0) - { - foreach (var error in PS.Streams.Error) - { - Console.WriteLine($"Error: {error}"); - } - } + var results = PS.Invoke(); + CheckErrors(); + + // Display the results + foreach (var result in results) + { + Console.WriteLine(result); + } //} return ExecutePowerShell(scriptBlock, parameters); @@ -237,9 +262,10 @@ public void Terminate() } else { - PS.AddCommand("Invoke-Command") - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - .AddParameter("ArgumentList", argList.ToArray()); + PS.AddScript(scriptBlock).AddArgument(argList.ToArray()); + //PS.AddCommand("Invoke-Command") + // .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) + // .AddParameter("ArgumentList", argList.ToArray()); } bool hadErrors = false; @@ -247,18 +273,8 @@ public void Terminate() _logger.LogTrace($"Script block:\n{scriptBlock}"); var results = PS.Invoke(); - if (PS.HadErrors) - { - errorList = string.Empty; - foreach (var error in PS.Streams.Error) - { - errorList += error + "\n"; - _logger.LogError($"Error: {error}"); - } + CheckErrors(); - throw new ApplicationException(errorList); - } - return results; } catch (Exception ex) @@ -295,16 +311,7 @@ public void Terminate() { _logger.LogTrace("Ready to invoke the script"); var results = PS.Invoke(); - if (PS.HadErrors) - { - _logger.LogTrace("There were errors detected."); - foreach (var error in PS.Streams.Error) - { - _logger.LogError($"Error: {error}"); - } - - throw new Exception("An error occurred when executing a PowerShell script. Please review the logs for more information."); - } + CheckErrors(); var jsonResults = results[0].ToString(); var certInfoList = JsonSerializer.Deserialize>(jsonResults); @@ -320,6 +327,22 @@ public void Terminate() } } + private void CheckErrors() + { + string errorList = string.Empty; + if (PS.HadErrors) + { + errorList = string.Empty; + foreach (var error in PS.Streams.Error) + { + errorList += error + "\n"; + _logger.LogError($"Error: {error}"); + } + + throw new ApplicationException(errorList); + } + } + public static string LoadScript(string scriptFileName) { string scriptFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PowerShellScripts", scriptFileName); From 816f195ddf1b2934cbbc0e24ee40dee95fc5a993 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 22 Oct 2024 20:42:22 -0500 Subject: [PATCH 05/21] Added null check for PAM object --- IISU/PAMUtilities.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/IISU/PAMUtilities.cs b/IISU/PAMUtilities.cs index ae9c15b..3c5bd87 100644 --- a/IISU/PAMUtilities.cs +++ b/IISU/PAMUtilities.cs @@ -22,7 +22,11 @@ internal class PAMUtilities internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logger, string name, string key) { logger.LogDebug($"Attempting to resolve PAM eligible field {name}"); - return string.IsNullOrEmpty(key) ? key : resolver.Resolve(key); + if (resolver == null) return key; + else + { + return string.IsNullOrEmpty(key) ? key : resolver.Resolve(key); + } } } } From 677d2069cd1bd9ab809d26744cf416a8a7bc7a50 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 23 Oct 2024 19:20:21 -0500 Subject: [PATCH 06/21] Updated code for WinCert and WinIIS --- IISU/ImplementedStoreTypes/Win/Inventory.cs | 10 +- IISU/ImplementedStoreTypes/Win/Management.cs | 40 ++- .../WinIIS/IISBindingInfo.cs | 55 ++++ .../ImplementedStoreTypes/WinIIS/Inventory.cs | 5 +- .../WinIIS/Management.cs | 296 ++++++++++-------- IISU/PSHelper.cs | 58 +--- 6 files changed, 272 insertions(+), 192 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index e0dade0..fe8d9f6 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -126,14 +126,16 @@ public List QueryWinCertCertificates(RemoteSettings settin { List Inventory = new(); - string command = string.Empty; - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) { ps.Initialize(); - command = $"Get-KFCertificates -StoreName '{StoreName}'"; - results = ps.ExecuteFunction(command); + var parameters = new Dictionary + { + { "StoreName", StoreName } + }; + + results = ps.ExecutePowerShell("Get-KFCertificates", parameters); // If there are certificates, deserialize the results and send them back to command if (results != null && results.Count > 0) diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index 232695d..f38c3cc 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -23,6 +23,8 @@ using System.Management.Automation; using Keyfactor.Logging; using System.Collections.ObjectModel; +using System.Collections.Generic; +using System.Management.Automation.Runspaces; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { @@ -31,7 +33,6 @@ public class Management : WinCertJobTypeBase, IManagementJobExtension public string ExtensionName => "WinCertManagement"; private ILogger _logger; - private string command = string.Empty; private PSHelper _psHelper; private Collection? _results = null; @@ -98,7 +99,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); - complete = AddCertificate(certificateContents, privateKeyPassword, _storePath, cryptoProvider); + complete = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); _logger.LogTrace($"Completed adding the certificate to the store"); break; @@ -107,7 +108,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { string thumbprint = config.JobCertificate.Alias; - complete = RemoveCertificate(thumbprint, _storePath); + complete = RemoveCertificate(thumbprint); _logger.LogTrace($"Completed removing the certificate from the store"); break; @@ -134,7 +135,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } } - public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string storePath, string cryptoProvider) + public JobResult AddCertificate(string certificateContents, string privateKeyPassword, string cryptoProvider) { try { @@ -143,15 +144,26 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas _psHelper.Initialize(); _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - command = $"Add-KFCertificateToStore -Base64Cert '{certificateContents}' -PrivateKeyPassword '{privateKeyPassword}' -StoreName '{storePath}' -CryptoServiceProvider '{cryptoProvider}'"; - _results = _psHelper.ExecuteFunction(command); + + // Manditory parameters + var parameters = new Dictionary + { + { "Base64Cert", certificateContents }, + { "StorePath", _storePath }, + }; + + // Optional parameters + if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } + if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } + + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); // This should return the thumbprint of the certificate if (_results != null && _results.Count > 0) { var thumbprint = _results[0].ToString(); - _logger.LogTrace($"Added certificate to store {storePath}, returned with the thumbprint {thumbprint}"); + _logger.LogTrace($"Added certificate to store {_storePath}, returned with the thumbprint {thumbprint}"); } else { @@ -182,7 +194,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas } } - public JobResult RemoveCertificate(string thumbprint, string storePath) + public JobResult RemoveCertificate(string thumbprint) { try { @@ -190,9 +202,15 @@ public JobResult RemoveCertificate(string thumbprint, string storePath) { _psHelper.Initialize(); - _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {storePath}"); - command = $"Remove-KFCertificateFromStore -Thumbprint '{thumbprint}' -StorePath '{storePath}'"; - _psHelper.ExecuteFunction(command); + _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {_storePath}"); + + var parameters = new Dictionary() + { + { "Thumbprint", thumbprint }, + { "StorePath", _storePath } + }; + + _psHelper.ExecutePowerShell("Remove-KFCertificateFromStore", parameters); _logger.LogTrace("Returned from executing PS function (Remove-KFCertificateFromStore)"); _psHelper.Terminate(); diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs new file mode 100644 index 0000000..9fca009 --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS +{ + public class IISBindingInfo + { + public string SiteName { get; set; } + public string Protocol { get; set; } + public string IPAddress { get; set; } + public string Port { get; set; } + public string HostName { get; set; } + public string SniFlag { get; set; } + + public IISBindingInfo(Dictionary bindingInfo) + { + SiteName = bindingInfo["SiteName"].ToString(); + Protocol = bindingInfo["Protocol"].ToString(); + IPAddress = bindingInfo["IPAddress"].ToString(); + Port = bindingInfo["Port"].ToString(); + HostName = bindingInfo["HostName"].ToString(); + SniFlag = MigrateSNIFlag(bindingInfo["SniFlag"].ToString()); + } + + private string MigrateSNIFlag(string input) + { + // Check if the input is numeric, if so, just return it as an integer + if (int.TryParse(input, out int numericValue)) + { + return numericValue.ToString(); + } + + if (string.IsNullOrEmpty(input)) { throw new ArgumentNullException("SNI/SSL Flag", "The SNI or SSL Flag flag must not be empty or null."); } + + // Handle the string cases + switch (input.ToLower()) + { + case "0 - no sni": + return "0"; + case "1 - sni enabled": + return "1"; + case "2 - non sni binding": + return "2"; + case "3 - sni binding": + return "3"; + default: + throw new ArgumentOutOfRangeException($"Received an invalid value '{input}' for sni/ssl Flag value"); + } + } + } +} diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index da633f2..eb4d8fb 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -123,14 +123,11 @@ public List QueryIISCertificates(RemoteSettings settings) { List Inventory = new(); - string command = string.Empty; - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) { ps.Initialize(); - command = "Get-KFIISBoundCertificates"; - results = ps.ExecuteFunction(command); + results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); // If there are certificates, deserialize the results and send them back to command if (results != null && results.Count > 0) diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index d0199f2..26adfab 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -13,9 +13,11 @@ // limitations under the License. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -30,7 +32,6 @@ public class Management : WinCertJobTypeBase, IManagementJobExtension public string ExtensionName => "WinIISUManagement"; private ILogger _logger; - private string command = string.Empty; private PSHelper _psHelper; private Collection? _results = null; @@ -100,42 +101,67 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); // Add Certificate to Cert Store - using (_psHelper) + try { - - string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, _storePath, cryptoProvider); + string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); _logger.LogTrace($"Completed adding the certificate to the store"); // Bind Certificate to IIS Site if (newThumbprint != null) { - + IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); + BindCertificate(bindingInfo, newThumbprint); + + complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; } - + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; } + _logger.LogTrace($"Completed adding and binding the certificate to the store"); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobHistoryID, - FailureMessage = "" - }; + break; } case CertStoreOperationType.Remove: { + // Removing a certificate involves two steps: UnBind the certificate, then delete the cert from the store + string thumbprint = config.JobCertificate.Alias; + try + { + if (UnBindCertificate(new IISBindingInfo(config.JobProperties))) + { + complete = RemoveCertificate(thumbprint); + } + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; + } - //complete = RemoveCertificate(thumbprint, _storePath); _logger.LogTrace($"Completed removing the certificate from the store"); break; } } - _psHelper.Terminate(); - - _logger.MethodExit(); return complete; } catch (Exception ex) @@ -149,34 +175,44 @@ public JobResult ProcessJob(ManagementJobConfiguration config) FailureMessage = ex.Message }; } + finally + { + _psHelper.Terminate(); + _logger.MethodExit(); + } } - public string AddCertificate(string certificateContents, string privateKeyPassword, string storePath, string cryptoProvider) + public string AddCertificate(string certificateContents, string privateKeyPassword, string cryptoProvider) { try { string newThumbprint = string.Empty; - using (_psHelper) + _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); + + // Manditory parameters + var parameters = new Dictionary { - _psHelper.Initialize(); + { "Base64Cert", certificateContents }, + { "StorePath", _storePath }, + }; - _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - command = $"Add-KFCertificateToStore -Base64Cert '{certificateContents}' -PrivateKeyPassword '{privateKeyPassword}' -StoreName '{storePath}' -CryptoServiceProvider '{cryptoProvider}'"; - _results = _psHelper.ExecuteFunction(command); - _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + // Optional parameters + if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } + if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } - // This should return the thumbprint of the certificate - if (_results != null && _results.Count > 0) - { - newThumbprint= _results[0].ToString(); - _logger.LogTrace($"Added certificate to store {storePath}, returned with the thumbprint {newThumbprint}"); - } - else - { - _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); - } - _psHelper.Terminate(); + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + newThumbprint= _results[0].ToString(); + _logger.LogTrace($"Added certificate to store {_storePath}, returned with the thumbprint {newThumbprint}"); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); } return newThumbprint; @@ -190,107 +226,107 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo } } - public void BindCertificate(string siteName, string protocol, string ipAddress, string port, string sniFlag, string storeName, string thumbPrint, string hostName = "") + public JobResult RemoveCertificate(string thumbprint) { - /* - function New-KFIISSiteBinding - */ + try + { + using (_psHelper) + { + _psHelper.Initialize(); + + _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {_storePath}"); + + var parameters = new Dictionary() + { + { "Thumbprint", thumbprint }, + { "StorePath", _storePath } + }; + _psHelper.ExecutePowerShell("Remove-KFCertificateFromStore", parameters); + _logger.LogTrace("Returned from executing PS function (Remove-KFCertificateFromStore)"); + + _psHelper.Terminate(); + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } + catch (Exception ex) + { + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = failureMessage + }; + } } + public void BindCertificate(IISBindingInfo bindingInfo, string thumbprint) + { + _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); + + // Manditory parameters + var parameters = new Dictionary + { + { "Thumbprint", thumbprint }, + { "WebSite", bindingInfo.SiteName }, + { "Protocol", bindingInfo.Protocol }, + { "IPAddress", bindingInfo.IPAddress }, + { "Port", bindingInfo.Port }, + { "SNIFlag", bindingInfo.SniFlag }, + { "StorePath", _storePath }, + }; + + // Optional parameters + if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + + _results = _psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + } + } + + public bool UnBindCertificate(IISBindingInfo bindingInfo) + { + _logger.LogTrace("Attempting to UnBind and execute PS function (Remove-KFIISBinding)"); + + // Manditory parameters + var parameters = new Dictionary + { + { "SiteName", bindingInfo.SiteName }, + { "IPAddress", bindingInfo.IPAddress }, + { "Port", bindingInfo.Port }, + }; + // Optional parameters + if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } -// private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) -// { -// try -// { -//#nullable enable -// string certificateContents = config.JobCertificate.Contents; -// string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; -// string storePath = config.CertificateStoreDetails.StorePath; -// long jobNumber = config.JobHistoryId; -// string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); -//#nullable disable - -// // If a crypto provider was provided, check to see if it exists -// if (cryptoProvider != null) -// { -// _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); -// if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) -// { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } -// } - -// if (storePath != null) -// { -// _logger.LogInformation($"Attempting to add IISU certificate to cert store: {storePath}"); -// } - -// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - -// // This method is retired -// //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); - -// // Write the certificate contents to a temporary file on the remote computer, returning the filename. -// string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); -// _logger.LogTrace($"{filePath} was created."); - -// // Using certutil on the remote computer, import the pfx file using a supplied csp if any. -// JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); - -// // Delete the temporary file -// manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); - -// if (result.Result == OrchestratorJobStatusJobResult.Success) -// { -// // Bind to IIS -// _logger.LogInformation("Attempting to bind certificate to website."); -// ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); -// result = iisManager.BindCertificate(manager.X509Cert); - -// // Provide logging information -// if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the website."); } -// else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the website. Check the logs for more information."); } - -// return result; -// } -// else return result; -// } -// catch (Exception e) -// { -// return new JobResult -// { -// Result = OrchestratorJobStatusJobResult.Failure, -// JobHistoryId = config.JobHistoryId, -// FailureMessage = -// $"Management/Add {e.Message}" -// }; -// } -// } - -// private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) -// { -// _logger.LogTrace("Before Remove Certificate..."); - -// string storePath = config.CertificateStoreDetails.StorePath; -// long jobNumber = config.JobHistoryId; - -// // First we need to unbind the certificate from IIS before we remove it from the store -// ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUsername, serverPassword); -// JobResult result = iisManager.UnBindCertificate(); - -// if (result.Result == OrchestratorJobStatusJobResult.Success) -// { -// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); -// manager.RemoveCertificate(config.JobCertificate.Alias, storePath); - -// return new JobResult -// { -// Result = OrchestratorJobStatusJobResult.Success, -// JobHistoryId = config.JobHistoryId, -// FailureMessage = "" -// }; -// } -// else return result; -// } + try + { + _results = _psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); + _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); + return true; + } + catch (Exception) + { + return false; + } + } } } \ No newline at end of file diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 27999ac..e7c940f 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -20,6 +20,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; using System.Linq; using System.Management.Automation; @@ -215,62 +216,33 @@ public void Terminate() return ExecutePowerShell(functionName); } - public Collection? ExecuteScriptBlock(string scriptBlock, Dictionary? parameters = null) + public Collection? ExecutePowerShell(string commandName, Dictionary? parameters = null) { - // THIS IS ONLY FOR TESTING - //using (PS) - //{ - PS.AddCommand(scriptBlock); - foreach (var param in parameters) - { - PS.AddParameter(param.Key, param.Value); - } - - var results = PS.Invoke(); - CheckErrors(); - - // Display the results - foreach (var result in results) - { - Console.WriteLine(result); - } - //} - - return ExecutePowerShell(scriptBlock, parameters); - } - - private Collection? ExecutePowerShell(string scriptBlock, Dictionary? parameters = null) - { - // Add parameters to the script block - var argList = new List(); - if (parameters != null) - { - foreach (var parameter in parameters.Values) - { - argList.Add(parameter); - } - } - try { if (!isLocalMachine) { PS.AddCommand("Invoke-Command") .AddParameter("Session", _PSSession) // send session only when necessary (remote) - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - .AddParameter("ArgumentList", argList.ToArray()); + .AddParameter("ScriptBlock", ScriptBlock.Create(commandName)) + .AddParameter("ArgumentList", parameters?.Values.ToArray()); } else { - PS.AddScript(scriptBlock).AddArgument(argList.ToArray()); - //PS.AddCommand("Invoke-Command") - // .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - // .AddParameter("ArgumentList", argList.ToArray()); + PS.AddCommand(commandName); + + if (parameters != null) + { + foreach (var parameter in parameters) + { + PS.AddParameter(parameter.Key, parameter.Value); + } + } } bool hadErrors = false; string errorList = string.Empty; - _logger.LogTrace($"Script block:\n{scriptBlock}"); + _logger.LogTrace($"Script block:\n{commandName}"); var results = PS.Invoke(); CheckErrors(); @@ -339,7 +311,7 @@ private void CheckErrors() _logger.LogError($"Error: {error}"); } - throw new ApplicationException(errorList); + throw new Exception(errorList); } } From bdf9c93f1c15d4dc5db1d799927a80f294e92700 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Thu, 24 Oct 2024 20:31:21 -0700 Subject: [PATCH 07/21] This commit fixes WinCert Add and WinIIS Add and Bind (both for localMachine). --- IISU/ImplementedStoreTypes/Win/Management.cs | 2 +- .../WinIIS/IISBindingInfo.cs | 4 +- .../WinIIS/Management.cs | 114 +++++++++--------- IISU/PowerShellScripts/WinCertFull.ps1 | 51 +++++--- 4 files changed, 95 insertions(+), 76 deletions(-) diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index f38c3cc..3abf1c4 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -149,7 +149,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas var parameters = new Dictionary { { "Base64Cert", certificateContents }, - { "StorePath", _storePath }, + { "StoreName", _storePath }, }; // Optional parameters diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs index 9fca009..f5a7d7c 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs @@ -13,7 +13,7 @@ public class IISBindingInfo public string Protocol { get; set; } public string IPAddress { get; set; } public string Port { get; set; } - public string HostName { get; set; } + public string? HostName { get; set; } public string SniFlag { get; set; } public IISBindingInfo(Dictionary bindingInfo) @@ -22,7 +22,7 @@ public IISBindingInfo(Dictionary bindingInfo) Protocol = bindingInfo["Protocol"].ToString(); IPAddress = bindingInfo["IPAddress"].ToString(); Port = bindingInfo["Port"].ToString(); - HostName = bindingInfo["HostName"].ToString(); + HostName = bindingInfo["HostName"]?.ToString(); SniFlag = MigrateSNIFlag(bindingInfo["SniFlag"].ToString()); } diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 26adfab..3e0235b 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -88,81 +88,83 @@ public JobResult ProcessJob(ManagementJobConfiguration config) _psHelper = new(protocol, port, includePortInSPN, _clientMachineName, serverUserName, serverPassword); - using (_psHelper) - _psHelper.Initialize(); - switch (_operationType) + using (_psHelper) { - case CertStoreOperationType.Add: - { - string certificateContents = config.JobCertificate.Contents; - string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; - string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); - - // Add Certificate to Cert Store - try + switch (_operationType) + { + case CertStoreOperationType.Add: { - string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); - _logger.LogTrace($"Completed adding the certificate to the store"); + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); - // Bind Certificate to IIS Site - if (newThumbprint != null) + // Add Certificate to Cert Store + try { - IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); - BindCertificate(bindingInfo, newThumbprint); + string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); + _logger.LogTrace($"Completed adding the certificate to the store"); - complete = new JobResult + // Bind Certificate to IIS Site + if (newThumbprint != null) + { + IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); + BindCertificate(bindingInfo, newThumbprint); + + complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } + } + catch (Exception ex) + { + return new JobResult { - Result = OrchestratorJobStatusJobResult.Success, + Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = _jobHistoryID, - FailureMessage = "" + FailureMessage = ex.Message }; } - } - catch (Exception ex) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobHistoryID, - FailureMessage = ex.Message - }; - } - _logger.LogTrace($"Completed adding and binding the certificate to the store"); + _logger.LogTrace($"Completed adding and binding the certificate to the store"); - break; - } - case CertStoreOperationType.Remove: - { - // Removing a certificate involves two steps: UnBind the certificate, then delete the cert from the store - - string thumbprint = config.JobCertificate.Alias; - try + break; + } + case CertStoreOperationType.Remove: { - if (UnBindCertificate(new IISBindingInfo(config.JobProperties))) + // Removing a certificate involves two steps: UnBind the certificate, then delete the cert from the store + + string thumbprint = config.JobCertificate.Alias; + try { - complete = RemoveCertificate(thumbprint); + if (UnBindCertificate(new IISBindingInfo(config.JobProperties))) + { + complete = RemoveCertificate(thumbprint); + } } - } - catch (Exception ex) - { - return new JobResult + catch (Exception ex) { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobHistoryID, - FailureMessage = ex.Message - }; - } + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; + } - _logger.LogTrace($"Completed removing the certificate from the store"); + _logger.LogTrace($"Completed removing the certificate from the store"); - break; - } + break; + } + } } return complete; + } catch (Exception ex) { @@ -194,7 +196,7 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo var parameters = new Dictionary { { "Base64Cert", certificateContents }, - { "StorePath", _storePath }, + { "StoreName", _storePath }, }; // Optional parameters @@ -207,7 +209,7 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo // This should return the thumbprint of the certificate if (_results != null && _results.Count > 0) { - newThumbprint= _results[0].ToString(); + newThumbprint = _results[0].ToString(); _logger.LogTrace($"Added certificate to store {_storePath}, returned with the thumbprint {newThumbprint}"); } else @@ -282,7 +284,7 @@ public void BindCertificate(IISBindingInfo bindingInfo, string thumbprint) { "IPAddress", bindingInfo.IPAddress }, { "Port", bindingInfo.Port }, { "SNIFlag", bindingInfo.SniFlag }, - { "StorePath", _storePath }, + { "StoreName", _storePath }, }; // Optional parameters diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index d0f5621..90ba54f 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -44,6 +44,8 @@ function Get-KFIISBoundCertificates #$websites = Get-Website $websites = Get-IISSite + Write-Information "There were ${websites}.count found" + # Initialize an array to store the results $certificates = @() @@ -190,7 +192,8 @@ function Add-KFCertificateToStore if($CryptoServiceProvider) { if(-not (Test-CryptoServiceProvider -CSPName $CryptoServiceProvider)){ - Write-Host "The CSP $CryptoServiceProvider was not found on the system." + Write-Information "INFO: The CSP $CryptoServiceProvider was not found on the system." + Write-Warning "WARN: CSP $CryptoServiceProvider was not found on the system." return } } @@ -202,9 +205,12 @@ function Add-KFCertificateToStore $tempStoreName = [System.IO.Path]::GetTempFileName() [System.IO.File]::WriteAllBytes($tempStoreName, $certBytes) + $tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") + $thumbprint = $null if ($CryptoServiceProvider) { + Write-Information "Adding certificate with the CSP '$CryptoServiceProvider'" # Create a temporary PFX file $tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } @@ -237,33 +243,44 @@ function Add-KFCertificateToStore Remove-Item $tempPfxPath } else { # Load the certificate from the temporary file - if ($PrivateKeyPassword) { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName, $PrivateKeyPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - } else { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName) + if ($PrivateKeyPassword) + { + Write-Information "Writing the certificate using a Private Key Password." + $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certBytes, $PrivateKeyPassword, 18 + }else + { + Write-Information "No Private Key Password is present." + $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certBytes, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet } - # Open the certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) - - # Add the certificate to the store - $store.Add($cert) - - # Get the thumbprint so it can be returned to the calling function - $thumbprint = $cert.Thumbprint + Write-Information "Store Name: '$StoreName'" + $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $StoreName, "LocalMachine" + $certStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite -bor [System.Security.Cryptography.X509Certificates.OpenFlags]::OpenExistingOnly) + + Write-Information "Adding certificate." + $certStore.Add($cert) + Write-Information "Certificate added successfully." # Close the store - $store.Close() + $certStore.Close() + Write-Information "Certificate store closed." + } # Clean up the temporary file Remove-Item $tempStoreName - Write-Host "Certificate added successfully to $StoreName." + Write-Information "Certificate added successfully to $StoreName." + + $newCert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" | Where-Object { $_.Subject -like "*$($cert.Subject)*" } | Sort-Object NotAfter -Descending | Select-Object -First 1 + if ($newCert) { + $thumbprint = $newCert.Thumbprint + } else { $thumbprint = $null } + return $thumbprint + } catch { - Write-Error "An error occurred: $_" + Write-Error "An error occurred: $_" 6>&1 return $null } } From 7eee6ab4cbc50b18250b58da5ae57d6c8fa6d207 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 30 Oct 2024 10:38:49 -0700 Subject: [PATCH 08/21] Testing --- IISU/ImplementedStoreTypes/Win/Management.cs | 11 +- .../WinIIS/Management.cs | 10 +- .../WinSQL/Management.cs | 404 +++++++++++++----- IISU/PowerShellScripts/WinCertFull.ps1 | 343 +++++++++------ IISU/WindowsCertStore.csproj | 1 + 5 files changed, 532 insertions(+), 237 deletions(-) diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index 3abf1c4..a83717c 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -42,8 +42,6 @@ public class Management : WinCertJobTypeBase, IManagementJobExtension private long _jobHistoryID = 0; private CertStoreOperationType _operationType; - //private Runspace myRunspace; - public Management(IPAMSecretResolver resolver) { _resolver= resolver; @@ -70,8 +68,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, - FailureMessage = - "Invalid Management Operation" + FailureMessage = "Invalid Management Operation" }; // Start parsing config information and establishing PS Session @@ -80,11 +77,11 @@ public JobResult ProcessJob(ManagementJobConfiguration config) _clientMachineName = config.CertificateStoreDetails.ClientMachine; _operationType = config.OperationType; - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string protocol = jobProperties?.WinRmProtocol; string port = jobProperties?.WinRmPort; bool includePortInSPN = (bool)jobProperties?.SpnPortFlag; @@ -167,7 +164,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas } else { - _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell information."); } _psHelper.Terminate(); } diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 3e0235b..43a87de 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -138,7 +138,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { // Removing a certificate involves two steps: UnBind the certificate, then delete the cert from the store - string thumbprint = config.JobCertificate.Alias; + string thumbprint = config.JobCertificate.Alias.Split(':')[0]; try { if (UnBindCertificate(new IISBindingInfo(config.JobProperties))) @@ -192,7 +192,7 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - // Manditory parameters + // Mandatory parameters var parameters = new Dictionary { { "Base64Cert", certificateContents }, @@ -214,19 +214,19 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo } else { - _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell information."); } return newThumbprint; } - catch (Exception ex) + catch (Exception ex) { var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; _logger.LogError(failureMessage); throw new Exception (failureMessage); } - } +} public JobResult RemoveCertificate(string thumbprint) { diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index c1d4198..7261922 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -13,8 +13,13 @@ // limitations under the License. using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Drawing.Text; using System.IO; +using System.Management.Automation; using System.Management.Automation.Runspaces; +using System.Numerics; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -27,13 +32,21 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql { public class Management : WinCertJobTypeBase, IManagementJobExtension { + public string ExtensionName => "WinSqlManagement"; private ILogger _logger; - public string ExtensionName => "WinSqlManagement"; + private PSHelper _psHelper; + private Collection? _results = null; - private Runspace myRunspace; + // Function wide config values + private string _clientMachineName = string.Empty; + private string _storePath = string.Empty; + private long _jobHistoryID = 0; + private CertStoreOperationType _operationType; - private string RenewalThumbprint; + private string RenewalThumbprint = string.Empty; + private string SQLInstanceNames = "MSSQLSERVER"; + private bool RestartSQLService = false; public Management(IPAMSecretResolver resolver) { @@ -56,48 +69,133 @@ public JobResult ProcessJob(ManagementJobConfiguration config) _logger.LogTrace(e.Message); } + var complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = "Invalid Management Operation" + }; + + // Start parsing config information and establishing PS Session + _jobHistoryID = config.JobHistoryId; + _storePath = config.CertificateStoreDetails.StorePath; + _clientMachineName = config.CertificateStoreDetails.ClientMachine; + _operationType = config.OperationType; + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string protocol = jobProperties.WinRmProtocol; - string port = jobProperties.WinRmPort; - bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; - long JobHistoryID = config.JobHistoryId; - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + string protocol = jobProperties?.WinRmProtocol; + string port = jobProperties?.WinRmPort; + bool includePortInSPN = (bool)jobProperties?.SpnPortFlag; + + RestartSQLService = jobProperties.RestartService; - var complete = new JobResult + if (config.JobProperties.ContainsKey("InstanceName")) { - Result = OrchestratorJobStatusJobResult.Failure, - FailureMessage = - "Invalid Management Operation" - }; + SQLInstanceNames = config.JobProperties["InstanceName"]?.ToString() ?? "MSSQLSERVER"; + } - switch (config.OperationType) + if (config.JobProperties.ContainsKey("RenewalThumbprint")) { - case CertStoreOperationType.Add: - _logger.LogTrace("Entering Add..."); - myRunspace.Open(); - complete = PerformAddCertificate(config, serverUserName, serverPassword); - myRunspace.Close(); - _logger.LogTrace("After Perform Addition..."); - break; - case CertStoreOperationType.Remove: - _logger.LogTrace("Entering Remove..."); - _logger.LogTrace("After PerformRemoval..."); - myRunspace.Open(); - complete = PerformRemoveCertificate(config, serverUserName, serverPassword); - myRunspace.Close(); - _logger.LogTrace("After Perform Removal..."); - break; + RenewalThumbprint = config.JobProperties["RenewalThumbprint"]?.ToString(); + } + + _psHelper = new(protocol, port, includePortInSPN, _clientMachineName, serverUserName, serverPassword); + + _psHelper.Initialize(); + + using (_psHelper) + { + switch (_operationType) + { + case CertStoreOperationType.Add: + { + string certificateContents = config.JobCertificate.Contents; + string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; + string cryptoProvider = config.JobProperties["ProviderName"]?.ToString() ?? string.Empty; + + // Add Certificate to Cert Store + try + { + string newThumbprint = AddCertificate(certificateContents, privateKeyPassword, cryptoProvider); + _logger.LogTrace($"Completed adding the certificate to the store"); + + // Bind Certificate to SQL Instance + if (newThumbprint != null) + { + complete = BindSQLCertificate(newThumbprint, RenewalThumbprint); + // Check the RenewalThumbprint. If there is a value, this is a renewal + if (config.JobProperties.ContainsKey("RenewalThumbprint")) + { + // This is a renewal. + // Check if there is an existing certificate. If there is, replace it with the new one. + string renewalThumbprint = config.JobProperties["RenewalThumbprint"]?.ToString() ?? string.Empty; + } + else + { + // This is a new certificate - just bind it. + } + + complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; + } + + _logger.LogTrace($"Completed adding and binding the certificate to the store"); + + break; + } + case CertStoreOperationType.Remove: + { + string thumbprint = config.JobCertificate.Alias; + + complete = RemoveCertificate(thumbprint); + _logger.LogTrace($"Completed removing the certificate from the store"); + + break; + } + } } - _logger.MethodExit(); return complete; + + //switch (config.OperationType) + //{ + // case CertStoreOperationType.Add: + // _logger.LogTrace("Entering Add..."); + // myRunspace.Open(); + // complete = PerformAddCertificate(config, serverUserName, serverPassword); + // myRunspace.Close(); + // _logger.LogTrace("After Perform Addition..."); + // break; + // case CertStoreOperationType.Remove: + // _logger.LogTrace("Entering Remove..."); + // _logger.LogTrace("After PerformRemoval..."); + // myRunspace.Open(); + // complete = PerformRemoveCertificate(config, serverUserName, serverPassword); + // myRunspace.Close(); + // _logger.LogTrace("After Perform Removal..."); + // break; + //} + + //_logger.MethodExit(); + //return complete; } catch (Exception ex) { @@ -115,105 +213,203 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } } - private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) + public JobResult RemoveCertificate(string thumbprint) { - _logger.LogTrace("Before PerformAddition..."); + return new JobResult + { Result= OrchestratorJobStatusJobResult.Success, JobHistoryId = _jobHistoryID, FailureMessage = "" }; + } + + public string AddCertificate(string certificateContents, string privateKeyPassword, string cryptoProvider) + { try { -#nullable enable - string certificateContents = config.JobCertificate.Contents; - string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; - string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); -#nullable disable - - // If a crypto provider was provided, check to see if it exists - if (cryptoProvider != null) - { - _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); - if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) - { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } - } + string newThumbprint = string.Empty; - if (storePath != null) - { - _logger.LogInformation($"Attempting to add WinSql certificate to cert store: {storePath}"); - } + _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + // Mandatory parameters + var parameters = new Dictionary + { + { "Base64Cert", certificateContents }, + { "StoreName", _storePath }, + }; - // This method is retired - //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + // Optional parameters + if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } + if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } - // Write the certificate contents to a temporary file on the remote computer, returning the filename. - string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); - _logger.LogTrace($"{filePath} was created."); + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); - // Using certutil on the remote computer, import the pfx file using a supplied csp if any. - JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + newThumbprint = _results[0].ToString(); + _logger.LogTrace($"Added certificate to store {_storePath}, returned with the thumbprint {newThumbprint}"); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell information."); + } - // Delete the temporary file - manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + return newThumbprint; + } + catch (Exception ex) + { + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogError(failureMessage); - if (result.Result == OrchestratorJobStatusJobResult.Success) - { + throw new Exception(failureMessage); + } + } - if (config.JobProperties.ContainsKey("RenewalThumbprint")) - { - RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); - _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); - } + private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbprint) + { + bool hadError = false; + var instances = SQLInstanceNames.Split(","); - // Bind to SQL Server - ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); - result = sqlManager.BindCertificates(RenewalThumbprint, manager.X509Cert); + foreach (var instanceName in instances) + { + var parameters = new Dictionary + { + { "Thumbprint", newThumbprint }, + { "SqlInstanceName", instanceName }, + { "StoreName", _storePath }, + { "RestartService", RestartSQLService } + }; - // Provide logging information - if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } - else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } - - return result; + try + { + _results = _psHelper.ExecutePowerShell("Bind-CertificateToSqlInstance", parameters); + _logger.LogTrace("Return from executing PS function (Bind-CertificateToSqlInstance)"); + } + catch (Exception ex) + { + _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); + hadError= true; } - else return result; } - catch (Exception e) + + if (hadError) { return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Management/Add {e.Message}" + JobHistoryId = _jobHistoryID, + FailureMessage = "Unable to bind one or more certificates to the SQL Instances." }; - } - } - - private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) - { - _logger.LogTrace("Before Remove Certificate..."); - - string storePath = config.CertificateStoreDetails.StorePath; - long jobNumber = config.JobHistoryId; - - // Clear registry entry for SQL Server - ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); - JobResult result = sqlManager.UnBindCertificate(); - - if (result.Result == OrchestratorJobStatusJobResult.Success) + } else { - ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - manager.RemoveCertificate(config.JobCertificate.Alias, storePath); - return new JobResult { Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, + JobHistoryId = _jobHistoryID, FailureMessage = "" }; } - else return result; } + + +// private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) +// { +// _logger.LogTrace("Before PerformAddition..."); + +// try +// { +//#nullable enable +// string certificateContents = config.JobCertificate.Contents; +// string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; +// string storePath = config.CertificateStoreDetails.StorePath; +// long jobNumber = config.JobHistoryId; +// string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); +//#nullable disable + +// // If a crypto provider was provided, check to see if it exists +// if (cryptoProvider != null) +// { +// _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); +// if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) +// { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } +// } + +// if (storePath != null) +// { +// _logger.LogInformation($"Attempting to add WinSql certificate to cert store: {storePath}"); +// } + +// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + +// // This method is retired +// //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); + +// // Write the certificate contents to a temporary file on the remote computer, returning the filename. +// string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); +// _logger.LogTrace($"{filePath} was created."); + +// // Using certutil on the remote computer, import the pfx file using a supplied csp if any. +// JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); + +// // Delete the temporary file +// manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); + +// if (result.Result == OrchestratorJobStatusJobResult.Success) +// { + +// if (config.JobProperties.ContainsKey("RenewalThumbprint")) +// { +// RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); +// _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); +// } + +// // Bind to SQL Server +// ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); +// result = sqlManager.BindCertificates(RenewalThumbprint, manager.X509Cert); + +// // Provide logging information +// if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } +// else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } + +// return result; +// } +// else return result; +// } +// catch (Exception e) +// { +// return new JobResult +// { +// Result = OrchestratorJobStatusJobResult.Failure, +// JobHistoryId = config.JobHistoryId, +// FailureMessage = +// $"Management/Add {e.Message}" +// }; +// } +// } + + //private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) + //{ + // _logger.LogTrace("Before Remove Certificate..."); + + // string storePath = config.CertificateStoreDetails.StorePath; + // long jobNumber = config.JobHistoryId; + + // // Clear registry entry for SQL Server + // ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); + // JobResult result = sqlManager.UnBindCertificate(); + + // if (result.Result == OrchestratorJobStatusJobResult.Success) + // { + // ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); + // manager.RemoveCertificate(config.JobCertificate.Alias, storePath); + + // return new JobResult + // { + // Result = OrchestratorJobStatusJobResult.Success, + // JobHistoryId = config.JobHistoryId, + // FailureMessage = "" + // }; + // } + // else return result; + //} } } \ No newline at end of file diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index 90ba54f..dfab61f 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -104,73 +104,6 @@ function Get-KFIISBoundCertificates } -function Get-KFSQLBoundCertificate -{ - param ( - [string]$SQLInstanceName = "MSSQLSERVER" # Default instance name - ) - - # Initialize an array to store the results - $certificates = @() - - try { - $SQLInstancePath = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SQLInstanceName -ErrorAction Stop - - # Define the registry path to look for certificate binding information - $sqlRegPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$SQLInstancePath\MSSQLServer\SuperSocketNetLib" - - try { - # Check if the registry path exists - if (Test-Path $sqlRegPath) { - # Get the certificate thumbprint from the registry - $certThumbprint = (Get-ItemProperty -Path $sqlRegPath -Name "Certificate").Certificate - - if ($certThumbprint) { - # Clean up the thumbprint (remove any leading/trailing spaces) - $certThumbprint = $certThumbprint.Trim() - - # Retrieve the certificate details from the certificate store - $cert = Get-ChildItem -Path "Cert:\LocalMachine\My\$certThumbprint" - - if ($cert) { - # Create a custom object to store the result - $certInfo = [PSCustomObject]@{ - StoreName = $StoreName - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - SAN = Get-KFSAN $cert - ProviderName = Get-CertificateCSP $cert - Base64Data = [System.Convert]::ToBase64String($cert.RawData) - } - - # Add the certificate information to the array - $certificates += $certInfo - } - } - - # Output the results - if ($certificates) { - $certificates | ConvertTo-Json - } - } - else { - Write-Information "INFO: Certificate is not bound to SQL Server instance: $SQLInstanceName" - } - } - catch { - Write-Information "ERROR: An error occurred while retrieving certificate information: $_" - } - - } - catch { - Write-Host "WARN: Unable to find the SQL Instance: $SQLInstanceName" - Write-Information "WARN: Unable to find the SQL Instance: $SQLInstanceName" - } -} - function Add-KFCertificateToStore { param ( @@ -203,9 +136,10 @@ function Add-KFCertificateToStore # Create a temporary file to store the certificate $tempStoreName = [System.IO.Path]::GetTempFileName() - [System.IO.File]::WriteAllBytes($tempStoreName, $certBytes) - $tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") + [System.IO.File]::WriteAllBytes($tempPfxPath, $certBytes) + + #$tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") $thumbprint = $null @@ -243,44 +177,43 @@ function Add-KFCertificateToStore Remove-Item $tempPfxPath } else { # Load the certificate from the temporary file - if ($PrivateKeyPassword) - { - Write-Information "Writing the certificate using a Private Key Password." - $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certBytes, $PrivateKeyPassword, 18 - }else - { - Write-Information "No Private Key Password is present." - $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certBytes, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet + if ($PrivateKeyPassword) { + Write-Information "Writing the certificate using a Private Key Password." + Write-Information "Temp Store Name: $tempStoreName" + Write-Information "Temp PFX Name: $tempPfxPath" + #$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempPfxPath, $PrivateKeyPassword, 18) + } else { + Write-Information "No Private Key Password was provided." + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempPfxPath) } - Write-Information "Store Name: '$StoreName'" - $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $StoreName, "LocalMachine" - $certStore.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite -bor [System.Security.Cryptography.X509Certificates.OpenFlags]::OpenExistingOnly) - - Write-Information "Adding certificate." - $certStore.Add($cert) - Write-Information "Certificate added successfully." + # Open the certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + Write-Information "Store '$StoreName' is open." - # Close the store - $certStore.Close() - Write-Information "Certificate store closed." + # Add the certificate to the store + Write-Information "About to add the certificate to the store." + $store.Add($cert) + Write-Information "Certificate was added." + # Get the thumbprint so it can be returned to the calling function + $thumbprint = $cert.Thumbprint + Write-Information "The thumbprint '$thumbprint' was created." + + # Close the store + $store.Close() + Write-Information "Store is closed." } # Clean up the temporary file Remove-Item $tempStoreName - Write-Information "Certificate added successfully to $StoreName." - - $newCert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" | Where-Object { $_.Subject -like "*$($cert.Subject)*" } | Sort-Object NotAfter -Descending | Select-Object -First 1 - if ($newCert) { - $thumbprint = $newCert.Thumbprint - } else { $thumbprint = $null } - + Write-Host "Certificate added successfully to $StoreName." return $thumbprint - } catch { - Write-Error "An error occurred: $_" 6>&1 + Write-Error "An error occurred: $_" return $null } } @@ -445,51 +378,219 @@ function Remove-KFIISBinding } } -function New-KFSQLBinding -{ +# Function to get certificate information for a SQL Server instance +function Get-SQLInstanceCertInfo { param ( - [Parameter(Mandatory=$true)] - [string]$SqlInstanceName, # The name of the SQL Server instance (e.g., "MSSQLSERVER" for default) + [string]$instanceName + ) + + $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$instanceName\MSSQLServer\SuperSocketNetLib" + $certInfo = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue + + if ($certInfo) { + $certHash = $certInfo.Certificate + $certStore = "My" # Certificates are typically stored in the Personal store - [Parameter(Mandatory=$true)] - [string]$CertificateThumbprint, # The thumbprint of the certificate to be bound + if ($certHash) { + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$certStore\$certHash" -ErrorAction SilentlyContinue + + if ($cert) { + return [PSCustomObject]@{ + InstanceName = $instanceName + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + SAN = ($cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" }).Format(1) + } + } + } + } +} - [Parameter(Mandatory=$false)] - [string]$CertStoreLocation = "Cert:\LocalMachine\My" # Certificate store location (defaults to LocalMachine\My) +function GET-KFSQLInventory +{ + # Get all SQL Server instances + $sqlInstances = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server").InstalledInstances + + # Initialize an array to store the results + $certificates = @() + + foreach ($instance in $sqlInstances) { + # Get the SQL Full Instance Name + $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance + + $certInfo = Get-SQLInstanceCertInfo -instanceName $fullInstanceName + $certificates += $certInfo + } + + # Output the results + $certificates | Format-Table -AutoSize +} + +function Set-SQLCertificateAcl { + param ( + [Parameter(Mandatory = $true)] + [string]$Thumbprint, + + [Parameter(Mandatory = $true)] + [string]$SqlServiceUser ) - # Load the SQL Server WMI provider - $wmiNamespace = "root\Microsoft\SqlServer\ComputerManagement14" # For SQL Server 2017 and later - if (-not (Get-WmiObject -Namespace $wmiNamespace -List | Out-Null)) { - throw "Could not load WMI namespace for SQL Server. Ensure SQL Server WMI provider is installed." + # Get the certificate from the LocalMachine store + $certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } + + if (-not $certificate) { + Write-Error "Certificate with thumbprint $Thumbprint not found in LocalMachine\My store." + return $null } + # Retrieve the private key information + $privKey = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + $keyPath = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\" + $privKeyPath = (Get-Item "$keyPath\$privKey") + + # Retrieve the current ACL for the private key + $Acl = Get-Acl $privKeyPath + $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($SqlServiceUser, "Read", "Allow") + + # Add the new access rule + $Acl.SetAccessRule($Ar) + + # Set the new ACL on the private key file + Set-Acl -Path $privKeyPath.FullName -AclObject $Acl + + Write-Output "ACL updated successfully for the private key." +} + +function Bind-CertificateToSqlInstance { + param ( + [Parameter(Mandatory = $true)] + [string]$Thumbprint, # Thumbprint of the certificate to bind + [Parameter(Mandatory = $true)] + [string]$SqlInstanceName, # Name of the SQL Server instance (e.g., 'MSSQLSERVER' or 'SQLInstanceName') + [string]$StoreName = "My", # Certificate store name (default is 'My') + [ValidateSet("LocalMachine", "CurrentUser")] + [string]$StoreLocation = "LocalMachine", # Store location (default is 'LocalMachine') + [Parameter(Mandatory = $false)] + [switch]$RestartService # Optional, restart sql instance if set to true + ) + try { - # Get the SQL Server instance using WMI - $sqlService = Get-WmiObject -Namespace $wmiNamespace -Class SqlService | Where-Object { - $_.ServiceName -eq $SqlInstanceName -and $_.SQLServiceType -eq 1 - } + # Open the certificate store + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::$StoreLocation) + $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) - if (-not $sqlService) { - throw "SQL Server instance '$SqlInstanceName' not found." - } + # Find the certificate by thumbprint + $certificate = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } - # Ensure the certificate exists in the specified store - $certificate = Get-ChildItem -Path "$CertStoreLocation" | Where-Object { $_.Thumbprint -eq $CertificateThumbprint } if (-not $certificate) { - throw "Certificate with thumbprint '$CertificateThumbprint' not found in store '$CertStoreLocation'." + throw "Certificate with thumbprint $Thumbprint not found in store $StoreLocation\$StoreName." } - # Bind the certificate to the SQL Server instance using WMI - $sqlService.SetEncryption($CertificateThumbprint) + # Get the SQL Full Instance Name + $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SqlInstanceName - Write-Host "Certificate with thumbprint '$CertificateThumbprint' successfully bound to SQL Server instance '$SqlInstanceName'." + # Update ACL for the private key, giving the SQL service user read access + $SQLServiceUser = Get-SQLServiceUser $fullInstanceName + Set-SQLCertificalACL -Thumbprint $Thumbprint -SQLServiceUser $SQLServiceUser + # Get the SQL Server instance registry path + $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\${fullInstanceName}\MSSQLServer\SuperSocketNetLib" + if (-not (Test-Path $regPath)) { + throw "Could not find registry path for SQL instance: $fullInstanceName." + } + + # Set the certificate thumbprint in SQL Server's registry + Set-ItemProperty -Path $regPath -Name "Certificate" -Value $Thumbprint + Write-Information "Certificate thumbprint $Thumbprint successfully bound to SQL Server instance $SqlInstanceName." + + # Close the certificate store + $store.Close() + + # Restart SQL Server for changes to take effect + if ($RestartService.IsPresent) { + Write-Information "Restarting SQL Server service..." + Restart-Service -Name $SQLServiceUser -Force + Write-Information "SQL Server service restarted." + } else { + Write-Information "Please restart SQL Server service manually for changes to take effect." + } + } catch { Write-Error "An error occurred: $_" } } +# Example usage: +# Bind-CertificateToSqlInstance -Thumbprint "123ABC456DEF" -SqlInstanceName "MSSQLSERVER" + +function UnBind-KFSqlServerCertificate { + param ( + [Parameter(Mandatory = $true)] + [string]$SqlInstanceName # Name of the SQL Server instance (e.g., 'MSSQLSERVER' or 'SQLInstanceName') + ) + + try { + + # Get the SQL Full Instance Name + $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SqlInstanceName + + # Get the SQL Server instance registry path + $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\${fullInstanceName}\MSSQLServer\SuperSocketNetLib" + if (-not (Test-Path $regPath)) { + throw "Could not find registry path for SQL instance: $fullInstanceName." + } + + # Check if the registry contains the certificate thumbprint + $certificateThumbprint = (Get-ItemProperty -Path $regPath -Name "Certificate").Certificate + + if ($certificateThumbprint) + { + Write-Host "Found certificate thumbprint: $certificateThumbprint bound to SQL Server instance $SqlInstanceName." + Remove-ItemProperty -Path $regPath -Name "Certificate" + Write-Output "Certificate thumbprint unbound from SQL Server instance $SqlInstanceName." + return + } else { + Write-Output "No certificate is bound to SQL Server instance $SqlInstanceName." + return + } + + } catch { + Write-Error "An error occurred: $_" + } +} + +# Example usage: +# Clear-SqlServerCertificate -SqlInstanceName "MSSQLSERVER" + + +function Get-SQLServiceUser { + param ( + [Parameter(Mandatory = $true)] + [string]$SQLInstanceName + ) + + # Construct the SQL service name (assuming default MSSQL naming convention) + $serviceName = if ($SQLInstanceName -eq "MSSQLSERVER") { "MSSQLSERVER" } else { "MSSQL$SQLInstanceName" } + + # Use Get-CimInstance instead of Get-WmiObject + $serviceUser = (Get-CimInstance -ClassName Win32_Service -Filter "Name='$serviceName'").StartName + + if ($serviceUser) { + return $serviceUser + } else { + Write-Error "SQL Server instance '$SQLInstanceName' not found or no service user associated." + return $null + } +} + +# Example usage: +# Get-SQLServiceUser -SQLInstanceName "MSSQLSERVER" + + + # Shared Functions # Function to get SAN (Subject Alternative Names) from a certificate function Get-KFSAN($cert) diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index 95b9d06..fd51802 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -25,6 +25,7 @@ + From d7a8136277497a187fb46abba96fd5c7c8ce0464 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Mon, 4 Nov 2024 14:38:08 -0800 Subject: [PATCH 09/21] testing --- .../ImplementedStoreTypes/WinSQL/Inventory.cs | 126 ++++++++- .../WinSQL/Management.cs | 104 +------ .../WinSQL/WinSQLCertificateInfo.cs | 22 ++ IISU/PowerShellScripts/WinCertFull.ps1 | 254 ++++++++++-------- 4 files changed, 293 insertions(+), 213 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs diff --git a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs index 69174cb..f191aab 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinSQL; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -20,6 +21,9 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation; +using System.Security.Cryptography.X509Certificates; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql @@ -27,9 +31,15 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql public class Inventory : WinCertJobTypeBase, IInventoryJobExtension { private ILogger _logger; - public string ExtensionName => "WinSqlInventory"; + Collection? results = null; + + public Inventory() + { + + } + public Inventory(IPAMSecretResolver resolver) { _resolver = resolver; @@ -40,10 +50,122 @@ public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitIn _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); + try + { + var inventoryItems = new List(); + + _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(jobConfiguration)); + + string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", jobConfiguration.ServerUsername); + string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", jobConfiguration.ServerPassword); + + // Deserialize specific job properties + var jobProperties = JsonConvert.DeserializeObject(jobConfiguration.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + string protocol = jobProperties.WinRmProtocol; + string port = jobProperties.WinRmPort; + bool IncludePortInSPN = jobProperties.SpnPortFlag; + string clientMachineName = jobConfiguration.CertificateStoreDetails.ClientMachine; + string storePath = jobConfiguration.CertificateStoreDetails.StorePath; + + if (storePath != null) + { + // Create the remote connection class to pass to Inventory Class + RemoteSettings settings = new(); + settings.ClientMachineName = jobConfiguration.CertificateStoreDetails.ClientMachine; + settings.Protocol = jobProperties.WinRmProtocol; + settings.Port = jobProperties.WinRmPort; + settings.IncludePortInSPN = jobProperties.SpnPortFlag; + settings.ServerUserName = serverUserName; + settings.ServerPassword = serverPassword; + + _logger.LogTrace($"Attempting to read bound SQL Server certificates from cert store: {storePath}"); + inventoryItems = QuerySQLCertificates(settings, storePath); + + _logger.LogTrace("Invoking submitInventory.."); + submitInventoryUpdate.Invoke(inventoryItems); + _logger.LogTrace($"submitInventory Invoked... {inventoryItems.Count} Items"); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = jobConfiguration.JobHistoryId, + FailureMessage = "" + }; + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Warning, + JobHistoryId = jobConfiguration.JobHistoryId, + FailureMessage = $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" + }; + } + + catch (Exception ex) + { + _logger.LogTrace(LogHandler.FlattenException(ex)); + + var failureMessage = $"SQL Inventory job failed for Site '{jobConfiguration.CertificateStoreDetails.StorePath}' on server '{jobConfiguration.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = jobConfiguration.JobHistoryId, + FailureMessage = failureMessage + }; + } + } + + public List QuerySQLCertificates(RemoteSettings settings, string storeName) + { + List Inventory = new(); - return PerformInventory(jobConfiguration, submitInventoryUpdate); + using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) + { + ps.Initialize(); + + var parameters = new Dictionary + { + { "StoreName", storeName } + }; + + results = ps.ExecutePowerShell("GET-KFSQLInventory", parameters); + + // If there are certificates, deserialize the results and send them back to command + if (results != null && results.Count > 0) + { + var jsonResults = results[0].ToString(); + var certInfoList = Certificate.Utilities.DeserializeCertificates(jsonResults); // JsonConvert.DeserializeObject>(jsonResults); + + foreach (WinSQLCertificateInfo cert in certInfoList) + { + var siteSettingsDict = new Dictionary + { + { "InstanceName", cert.InstanceName}, + { "ProviderName", cert.ProviderName} + }; + + Inventory.Add( + new CurrentInventoryItem + { + Certificates = new[] { cert.Base64Data }, + Alias = cert.Thumbprint, + PrivateKeyEntry = cert.HasPrivateKey, + UseChainLevel = false, + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Parameters = siteSettingsDict + } + ); + } + } + ps.Terminate(); + } + + return Inventory; } + private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) { try diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index 7261922..61843d5 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -274,7 +274,7 @@ private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbpr var parameters = new Dictionary { { "Thumbprint", newThumbprint }, - { "SqlInstanceName", instanceName }, + { "SqlInstanceName", instanceName.Trim() }, { "StoreName", _storePath }, { "RestartService", RestartSQLService } }; @@ -309,107 +309,5 @@ private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbpr }; } } - - -// private JobResult PerformAddCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) -// { -// _logger.LogTrace("Before PerformAddition..."); - -// try -// { -//#nullable enable -// string certificateContents = config.JobCertificate.Contents; -// string privateKeyPassword = config.JobCertificate.PrivateKeyPassword; -// string storePath = config.CertificateStoreDetails.StorePath; -// long jobNumber = config.JobHistoryId; -// string? cryptoProvider = config.JobProperties["ProviderName"]?.ToString(); -//#nullable disable - -// // If a crypto provider was provided, check to see if it exists -// if (cryptoProvider != null) -// { -// _logger.LogInformation($"Checking the server for the crypto provider: {cryptoProvider}"); -// if (!PSHelper.IsCSPFound(PSHelper.GetCSPList(myRunspace), cryptoProvider)) -// { throw new Exception($"The Crypto Profider: {cryptoProvider} was not found. Please check the spelling and accuracy of the Crypto Provider Name provided. If unsure which provider to use, leave the field blank and the default crypto provider will be used."); } -// } - -// if (storePath != null) -// { -// _logger.LogInformation($"Attempting to add WinSql certificate to cert store: {storePath}"); -// } - -// ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - -// // This method is retired -// //JobResult result = manager.AddCertificate(certificateContents, privateKeyPassword, storePath); - -// // Write the certificate contents to a temporary file on the remote computer, returning the filename. -// string filePath = manager.CreatePFXFile(certificateContents, privateKeyPassword); -// _logger.LogTrace($"{filePath} was created."); - -// // Using certutil on the remote computer, import the pfx file using a supplied csp if any. -// JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); - -// // Delete the temporary file -// manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); - -// if (result.Result == OrchestratorJobStatusJobResult.Success) -// { - -// if (config.JobProperties.ContainsKey("RenewalThumbprint")) -// { -// RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); -// _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); -// } - -// // Bind to SQL Server -// ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); -// result = sqlManager.BindCertificates(RenewalThumbprint, manager.X509Cert); - -// // Provide logging information -// if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } -// else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } - -// return result; -// } -// else return result; -// } -// catch (Exception e) -// { -// return new JobResult -// { -// Result = OrchestratorJobStatusJobResult.Failure, -// JobHistoryId = config.JobHistoryId, -// FailureMessage = -// $"Management/Add {e.Message}" -// }; -// } -// } - - //private JobResult PerformRemoveCertificate(ManagementJobConfiguration config, string serverUsername, string serverPassword) - //{ - // _logger.LogTrace("Before Remove Certificate..."); - - // string storePath = config.CertificateStoreDetails.StorePath; - // long jobNumber = config.JobHistoryId; - - // // Clear registry entry for SQL Server - // ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUsername, serverPassword); - // JobResult result = sqlManager.UnBindCertificate(); - - // if (result.Result == OrchestratorJobStatusJobResult.Success) - // { - // ClientPSCertStoreManager manager = new ClientPSCertStoreManager(_logger, myRunspace, jobNumber); - // manager.RemoveCertificate(config.JobCertificate.Alias, storePath); - - // return new JobResult - // { - // Result = OrchestratorJobStatusJobResult.Success, - // JobHistoryId = config.JobHistoryId, - // FailureMessage = "" - // }; - // } - // else return result; - //} } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs new file mode 100644 index 0000000..fe36a5d --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinSQL +{ + public class WinSQLCertificateInfo + { + public string InstanceName { get; set; } + public string StoreName { get; set; } + public string Certificate { get; set; } + public string ExpiryDate { get; set; } + public string Issuer { get; set; } + public string Thumbprint { get; set; } + public bool HasPrivateKey { get; set; } + public string SAN { get; set; } + public string ProviderName { get; set; } + public string Base64Data { get; set; } + } +} diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index dfab61f..bda1946 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -121,95 +121,87 @@ function Add-KFCertificateToStore ) try { - # Before we get started, if a CSP has been provided, make sure it exists - if($CryptoServiceProvider) + + $thumbprint = $null + + if ($CryptoServiceProvider) { - if(-not (Test-CryptoServiceProvider -CSPName $CryptoServiceProvider)){ + # Test to see if CSP exists + if(-not (Test-CryptoServiceProvider -CSPName $CryptoServiceProvider)) + { Write-Information "INFO: The CSP $CryptoServiceProvider was not found on the system." Write-Warning "WARN: CSP $CryptoServiceProvider was not found on the system." return } - } - - # Convert Base64 string to byte array - $certBytes = [Convert]::FromBase64String($Base64Cert) - # Create a temporary file to store the certificate - $tempStoreName = [System.IO.Path]::GetTempFileName() - $tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") - [System.IO.File]::WriteAllBytes($tempPfxPath, $certBytes) + Write-Information "Adding certificate with the CSP '$CryptoServiceProvider'" - #$tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") + # Write certificate to a temporary PFX file + $tempFileName = [System.IO.Path]::GetTempFileName() + '.pfx' + [System.IO.File]::WriteAllBytes($tempFileName, [System.Convert]::FromBase64String($Base64Cert)) - $thumbprint = $null + # Initialize output variable + $output = "" - if ($CryptoServiceProvider) { - Write-Information "Adding certificate with the CSP '$CryptoServiceProvider'" - # Create a temporary PFX file - $tempPfxPath = [System.IO.Path]::ChangeExtension($tempStoreName, ".pfx") - $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } - - # Create the PFX from the certificate - $pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - - # Export the PFX to the temporary file - $pfxBytes = $pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) - [System.IO.File]::WriteAllBytes($tempPfxPath, $pfxBytes) + # Execute certutil based on whether a private key password was supplied + try { + if ($PrivateKeyPassword) { + $output = certutil -f -csp $CryptoServiceProvider -p $PrivateKeyPassword $StoreName $tempFileName + } + else { + $output = certutil -f -importpfx -csp $CryptoServiceProvider -p $PrivateKeyPassword $StoreName $tempFileName + } - #$pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempStoreName, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - #$pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) | Set-Content -Encoding Byte -Path $tempPfxPath + # Check for errors based on the last exit code + if ($LASTEXITCODE -ne 0) { + throw "Certutil failed with exit code $LASTEXITCODE. Output: $output" + } - # Use certutil to import the PFX with the specified CSP - $importCmd = "certutil -f -importpfx -csp '$CryptoServiceProvider' -p '$pfxPassword' $StoreName $tempPfxPath" - write-host $importCmd - Invoke-Expression $importCmd + # Additional check for known error keywords in output (optional) + if ($output -match "(ERROR|FAILED|EXCEPTION)") { + throw "Certutil output indicates an error: $output" + } - #$importCmd = "certutil -f -importpfx $tempPfxPath -p $pfxPassword -csp `"$CryptoServiceProvider`"" - #Invoke-Expression $importCmd + # Retrieve the certificate thumbprint from the store + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" | Sort-Object -Property NotAfter -Descending | Select-Object -First 1 + if ($cert) { + $thumbprint = $cert.Thumbprint + Write-Output "Certificate imported successfully. Thumbprint: $thumbprint" + } + else { + throw "Certificate import succeeded, but no certificate was found in the $StoreName store." + } - # Retrieve the thumbprint after the import - $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" | Where-Object { $_.Subject -like "*$($pfxCert.Subject)*" } | Sort-Object NotAfter -Descending | Select-Object -First 1 - if ($cert) { - $thumbprint = $cert.Thumbprint + } catch { + # Handle any errors and log the exception message + Write-Error "Error during certificate import: $_" + $output = "Error: $_" + } finally { + # Ensure the temporary file is deleted + if (Test-Path $tempFileName) { + Remove-Item $tempFileName -Force + } } - # Clean up the temporary PFX file - Remove-Item $tempPfxPath - } else { - # Load the certificate from the temporary file - if ($PrivateKeyPassword) { - Write-Information "Writing the certificate using a Private Key Password." - Write-Information "Temp Store Name: $tempStoreName" - Write-Information "Temp PFX Name: $tempPfxPath" - #$flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempPfxPath, $PrivateKeyPassword, 18) - } else { - Write-Information "No Private Key Password was provided." - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempPfxPath) - } + # Output the final result + $output - # Open the certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) + } else { + $bytes = [System.Convert]::FromBase64String($Base64Cert) + $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $storeName, "LocalMachine" Write-Information "Store '$StoreName' is open." + $certStore.Open(5) - # Add the certificate to the store - Write-Information "About to add the certificate to the store." - $store.Add($cert) - Write-Information "Certificate was added." + $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $bytes, $PrivateKeyPassword, 18 <# Persist, Machine #> + $certStore.Add($cert) + $certStore.Close(); + Write-Information "Store '$StoreName' is closed." # Get the thumbprint so it can be returned to the calling function $thumbprint = $cert.Thumbprint Write-Information "The thumbprint '$thumbprint' was created." - - # Close the store - $store.Close() - Write-Information "Store is closed." } - # Clean up the temporary file - Remove-Item $tempStoreName - Write-Host "Certificate added successfully to $StoreName." return $thumbprint } catch { @@ -379,56 +371,62 @@ function Remove-KFIISBinding } # Function to get certificate information for a SQL Server instance -function Get-SQLInstanceCertInfo { - param ( - [string]$instanceName - ) - - $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$instanceName\MSSQLServer\SuperSocketNetLib" - $certInfo = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue - - if ($certInfo) { - $certHash = $certInfo.Certificate - $certStore = "My" # Certificates are typically stored in the Personal store - - if ($certHash) { - $cert = Get-ChildItem -Path "Cert:\LocalMachine\$certStore\$certHash" -ErrorAction SilentlyContinue - - if ($cert) { - return [PSCustomObject]@{ - InstanceName = $instanceName - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - SAN = ($cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" }).Format(1) - } - } - } - } -} - function GET-KFSQLInventory { # Get all SQL Server instances $sqlInstances = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server").InstalledInstances + Write-Information "There are $sqlInstances.Count instances that will be checked for certificates." # Initialize an array to store the results $certificates = @() foreach ($instance in $sqlInstances) { + Write-Information "Checking instance: $instance for Certificates." + # Get the SQL Full Instance Name $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance - $certInfo = Get-SQLInstanceCertInfo -instanceName $fullInstanceName - $certificates += $certInfo + $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstanceName\MSSQLServer\SuperSocketNetLib" + $certInfo = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue + + if ($certInfo) { + $certHash = $certInfo.Certificate + $certStore = "My" # Certificates are typically stored in the Personal store + + if ($certHash) { + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$certStore\$certHash" -ErrorAction SilentlyContinue + + if ($cert) { + $certInfo = [PSCustomObject]@{ + InstanceName = $instance + StoreName = $certStore + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + SAN = Get-KFSAN $cert + ProviderName = Get-CertificateCSP $cert + Base64Data = [System.Convert]::ToBase64String($cert.RawData) + } + + Write-Information "Certificate found for $instance." + + # Add the certificate information to the array + $certificates += $certInfo + } + } + } } # Output the results - $certificates | Format-Table -AutoSize + if ($certificates) { + $certificates | ConvertTo-Json + } } + + function Set-SQLCertificateAcl { param ( [Parameter(Mandatory = $true)] @@ -437,7 +435,7 @@ function Set-SQLCertificateAcl { [Parameter(Mandatory = $true)] [string]$SqlServiceUser ) - + Write-Information "Entered Set-SQLCertificateAcl" # Get the certificate from the LocalMachine store $certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } @@ -445,20 +443,27 @@ function Set-SQLCertificateAcl { Write-Error "Certificate with thumbprint $Thumbprint not found in LocalMachine\My store." return $null } + Write-Information "Obtained the certificate" # Retrieve the private key information $privKey = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + Write-Information "Private Key: '$privKey'" + $keyPath = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\" $privKeyPath = (Get-Item "$keyPath\$privKey") + Write-Information "Private Key Path is: $privKeyPath" # Retrieve the current ACL for the private key $Acl = Get-Acl $privKeyPath $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($SqlServiceUser, "Read", "Allow") # Add the new access rule + Write-Information "Attempting to add new Access Rule" $Acl.SetAccessRule($Ar) + Write-Information "Access Rule has been added" # Set the new ACL on the private key file + Write-Information "Attaching the ACL on the private key file" Set-Acl -Path $privKeyPath.FullName -AclObject $Acl Write-Output "ACL updated successfully for the private key." @@ -478,23 +483,31 @@ function Bind-CertificateToSqlInstance { ) try { + Write-Information "Entered Bind-CertificateToSqlInstance" + # Open the certificate store $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::$StoreLocation) $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) # Find the certificate by thumbprint $certificate = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } + Write-Information "Obtained Certificate using thumbprint: $Thumbprint" if (-not $certificate) { throw "Certificate with thumbprint $Thumbprint not found in store $StoreLocation\$StoreName." } + # Get the SQL Full Instance Name $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SqlInstanceName + Write-Information "Obtained SQL Full Instance Name: $fullInstanceName" + + $SQLServiceName = Get-SQLServiceName $fullInstanceName # Update ACL for the private key, giving the SQL service user read access - $SQLServiceUser = Get-SQLServiceUser $fullInstanceName - Set-SQLCertificalACL -Thumbprint $Thumbprint -SQLServiceUser $SQLServiceUser + $SQLServiceUser = Get-SQLServiceUser $SQLServiceName + Set-SQLCertificateAcl -Thumbprint $Thumbprint -SQLServiceUser $SQLServiceUser + Write-Information "Updated ACL For SQL Service User: $SQLServiceUser" # Get the SQL Server instance registry path $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\${fullInstanceName}\MSSQLServer\SuperSocketNetLib" @@ -508,11 +521,14 @@ function Bind-CertificateToSqlInstance { # Close the certificate store $store.Close() + Write-Information "Store Closed" # Restart SQL Server for changes to take effect + Write-Information "Checking if restart has been authorized" + if ($RestartService.IsPresent) { - Write-Information "Restarting SQL Server service..." - Restart-Service -Name $SQLServiceUser -Force + Write-Information "Attempting to restart SQL Service Name: $SQLServiceName" + Restart-Service -Name $SQLServiceName -Force Write-Information "SQL Server service restarted." } else { Write-Information "Please restart SQL Server service manually for changes to take effect." @@ -565,18 +581,39 @@ function UnBind-KFSqlServerCertificate { # Example usage: # Clear-SqlServerCertificate -SqlInstanceName "MSSQLSERVER" - -function Get-SQLServiceUser { +function Get-SQLServiceName +{ param ( [Parameter(Mandatory = $true)] [string]$SQLInstanceName ) - # Construct the SQL service name (assuming default MSSQL naming convention) - $serviceName = if ($SQLInstanceName -eq "MSSQLSERVER") { "MSSQLSERVER" } else { "MSSQL$SQLInstanceName" } + # Return an empty string if the instance value is null or empty + if ([string]::IsNullOrEmpty($SQLInstanceName)) { + return "" + } + + # Split the instance value by '.' and retrieve the second part + $instanceName = $SQLInstanceName.Split('.')[1] + + # Determine the service name based on the instance name + if ($instanceName -eq "MSSQLSERVER") { + $serviceName = "MSSQLSERVER" + } else { + $serviceName = "MSSQL`$$instanceName" + } + + return $serviceName +} + +function Get-SQLServiceUser { + param ( + [Parameter(Mandatory = $true)] + [string]$SQLServiceName + ) # Use Get-CimInstance instead of Get-WmiObject - $serviceUser = (Get-CimInstance -ClassName Win32_Service -Filter "Name='$serviceName'").StartName + $serviceUser = (Get-CimInstance -ClassName Win32_Service -Filter "Name='$SQLServiceName'").StartName if ($serviceUser) { return $serviceUser @@ -591,6 +628,7 @@ function Get-SQLServiceUser { + # Shared Functions # Function to get SAN (Subject Alternative Names) from a certificate function Get-KFSAN($cert) From b45a6c621cdca7d25e5a7c7d632d49547e76e8b7 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Thu, 21 Nov 2024 20:48:33 -0800 Subject: [PATCH 10/21] Testing --- IISU/ClientPSCertStoreReEnrollment.cs | 733 +++++++++++------- .../ImplementedStoreTypes/WinSQL/Inventory.cs | 10 +- .../WinSQL/Management.cs | 114 ++- .../WinSQL/WinSQLCertificateInfo.cs | 36 +- IISU/PSHelper.cs | 2 +- IISU/PowerShellScripts/WinCertFull.ps1 | 275 ++++++- 6 files changed, 888 insertions(+), 282 deletions(-) diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index ea87440..ca56765 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -1,61 +1,274 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation.Runspaces; -using System.Management.Automation; -using System.Management.Automation.Remoting; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using Microsoft.Extensions.Logging; -using Keyfactor.Orchestrators.Extensions.Interfaces; -using System.Linq; -using System.IO; +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Management.Automation.Runspaces; +using System.Management.Automation; +using System.Management.Automation.Remoting; +using System.Net; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using Microsoft.Extensions.Logging; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using System.Linq; +using System.IO; using Microsoft.PowerShell; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - internal class ClientPSCertStoreReEnrollment - { - private readonly ILogger _logger; - private readonly IPAMSecretResolver _resolver; - - public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver) - { - _logger = logger; - _resolver = resolver; - } - - public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, CertStoreBindingTypeENUM bindingType) - { - bool hasError = false; - - try - { - _logger.MethodEntry(); - var serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - var serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - - // Extract values necessary to create remote PS connection - JobProperties jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore +{ + internal class ClientPSCertStoreReEnrollment + { + private readonly ILogger _logger; + private readonly IPAMSecretResolver _resolver; + + private PSHelper _psHelper; + private Collection? _results = null; + + public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver) + { + _logger = logger; + _resolver = resolver; + } + + public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, CertStoreBindingTypeENUM bindingType) + { + JobResult jobResult = null; + + try + { + _logger.MethodEntry(); + + var serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + var serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + // Get JobProperties from Config + var subjectText = config.JobProperties["subjectText"] as string; + var providerName = config.JobProperties["ProviderName"] as string; + var keyType = config.JobProperties["keyType"] as string; + var SAN = config.JobProperties["SAN"] as string; + + int keySize = 0; + if (config.JobProperties["keySize"] is not null && int.TryParse(config.JobProperties["keySize"].ToString(), out int size)) + { + keySize = size; + } + + // Extract values necessary to create remote PS connection + JobProperties jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, + new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + + string protocol = jobProperties.WinRmProtocol; + string port = jobProperties.WinRmPort; + bool includePortInSPN = jobProperties.SpnPortFlag; + string clientMachineName = config.CertificateStoreDetails.ClientMachine; + string storePath = config.CertificateStoreDetails.StorePath; + + //_psHelper = new(protocol, port, includePortInSPN, clientMachineName, serverUserName, serverPassword); + + _psHelper = new(protocol, port, includePortInSPN, clientMachineName, serverUserName, serverPassword); + _psHelper.Initialize(); + + using (_psHelper) + { + // First create and return the CSR + string csr = CreateCSR(subjectText, providerName, keyType, keySize, SAN); + + if (csr != string.Empty) + { + // Submit and Sign the CSR in Command + _logger.LogTrace("Attempting to sign CSR"); + X509Certificate2 myCert = submitReenrollment.Invoke(csr); + + // Import the certificate + string thumbprint = ImportCertificate(myCert.RawData, storePath); + + // If there is binding, bind it to the correct store type + switch (bindingType) + { + case CertStoreBindingTypeENUM.WinIIS: + break; + case CertStoreBindingTypeENUM.WinSQL: + break; + } + + jobResult = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; + + } + else + { + jobResult = new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = "No CSR was generated to perform a reenrollment. Please check the logs for further details." + }; + + } + } + + return jobResult; + + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = ex.Message + }; + } + finally + { + _psHelper.Terminate(); + } + + } + + private string CreateCSR(string subjectText, string providerName, string keyType, int keySize, string SAN) + { + string errorMsg = ""; + + try + { + string myCSR = ""; + + _logger.LogTrace("Entering ReEnrollment function: CreateCSR"); + + // Set the parameters for the function + var parameters = new Dictionary + { + { "subjectText", subjectText }, + { "providerName", providerName }, + { "keyType", keyType }, + { "keyLength", keySize }, + { "SAN", SAN } + }; + _logger.LogTrace("Attempting to execute PS function (New-CsrEnrollment)"); + _results = _psHelper.ExecutePowerShell("New-CsrEnrollment", parameters); + _logger.LogTrace("Returned from executing PS function (New-CsrEnrollment)"); + + // This should return the CSR that was generated + if (_results == null || _results.Count == 0) + { + _logger.LogError("No results were returned, resulting in no CSR created."); + } + else if (_results.Count == 1) + { + myCSR = _results[0]?.ToString(); + if (!string.IsNullOrEmpty(myCSR)) + { + _logger.LogTrace("Created a CSR."); + } + else + { + _logger.LogError("The returned result is empty, resulting in no CSR created."); + } + } + else // _results.Count > 1 + { + var messages = string.Join(Environment.NewLine, _results.Select(r => r?.ToString())); + errorMsg = "Multiple results returned, indicating potential errors and no CSR was created.\n"; + errorMsg += $"Details:{Environment.NewLine}{messages}"; + _logger.LogError(errorMsg); + + throw new ApplicationException(errorMsg); + + } + + return myCSR; + } + catch (ApplicationException appEx) + { + throw new Exception(appEx.Message); + } + catch (Exception ex) + { + var failureMessage = $"ReEnrollment error at Creating CSR with error: '{ex.Message}'"; + _logger.LogError(LogHandler.FlattenException(ex)); + + throw new Exception(failureMessage); + } + } + + private string ImportCertificate(byte[] certificateRawData, string storeName) + { + try + { + string myThumbprint = ""; + + _logger.LogTrace("Entering ReEnrollment function: ImportCertificate"); + + // Set the parameters for the function + var parameters = new Dictionary + { + { "rawData", certificateRawData }, + { "storeName", storeName } + }; + + _logger.LogTrace("Attempting to execute PS function (Import-SignedCertificate)"); + _results = _psHelper.ExecutePowerShell("Import-SignedCertificate", parameters); + _logger.LogTrace("Returned from executing PS function (Import-SignedCertificate)"); + + // This should return the CSR that was generated + if (_results != null && _results.Count > 0) + { + myThumbprint = _results[0].ToString(); + _logger.LogTrace($"Imported the CSR and returned the following thumbprint: {myThumbprint}"); + } + else + { + _logger.LogError("No results were returned, resulting in no CSR created."); + } + + return myThumbprint; + + } + catch (Exception ex) + { + var failureMessage = $"ReEnrollment error while attempting to import the certificate with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogError(failureMessage); + + throw new Exception(failureMessage); + } + } + + + + public JobResult PerformReEnrollmentORIG(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, CertStoreBindingTypeENUM bindingType) + { + bool hasError = false; + + try + { + _logger.MethodEntry(); + var serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); + var serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); + + // Extract values necessary to create remote PS connection + JobProperties jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); string protocol = jobProperties.WinRmProtocol; @@ -65,168 +278,168 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit string storePath = config.CertificateStoreDetails.StorePath; _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var runSpace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - - _logger.LogTrace("Runspace created"); - runSpace.Open(); - _logger.LogTrace("Runspace opened"); - - PowerShell ps = PowerShell.Create(); - ps.Runspace = runSpace; - - string CSR = string.Empty; - - var subjectText = config.JobProperties["subjectText"]; - var providerName = config.JobProperties["ProviderName"]; - var keyType = config.JobProperties["keyType"]; - var keySize = config.JobProperties["keySize"]; - var SAN = config.JobProperties["SAN"]; - - Collection results; - - // If the provider name is null, default it to the Microsoft CA - providerName ??= "Microsoft Strong Cryptographic Provider"; - - // Create the script file - ps.AddScript("$infFilename = New-TemporaryFile"); - ps.AddScript("$csrFilename = New-TemporaryFile"); - - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); - - ps.AddScript($"Set-Content $infFilename -Value [NewRequest]"); - ps.AddScript($"Add-Content $infFilename -Value 'Subject = \"{subjectText}\"'"); - ps.AddScript($"Add-Content $infFilename -Value 'ProviderName = \"{providerName}\"'"); - ps.AddScript($"Add-Content $infFilename -Value 'MachineKeySet = True'"); - ps.AddScript($"Add-Content $infFilename -Value 'HashAlgorithm = SHA256'"); - ps.AddScript($"Add-Content $infFilename -Value 'KeyAlgorithm = {keyType}'"); - ps.AddScript($"Add-Content $infFilename -Value 'KeyLength={keySize}'"); - ps.AddScript($"Add-Content $infFilename -Value 'KeySpec = 0'"); - - if (SAN != null) - { - ps.AddScript($"Add-Content $infFilename -Value '[Extensions]'"); - ps.AddScript(@"Add-Content $infFilename -Value '2.5.29.17 = ""{text}""'"); - - foreach (string s in SAN.ToString().Split("&")) - { - ps.AddScript($"Add-Content $infFilename -Value '_continue_ = \"{s + "&"}\"'"); - } - } - - try - { - // Get INF file for debugging - ps.AddScript("$name = $infFilename.FullName"); - ps.AddScript("$name"); - results = ps.Invoke(); - - string fname = results[0].ToString(); - string infContent = File.ReadAllText(fname); - - _logger.LogDebug($"Contents of {fname}:"); - _logger.LogDebug(infContent); - } - catch (Exception) - { - } - - // Execute the -new command - ps.AddScript($"certreq -new -q $infFilename $csrFilename"); - _logger.LogDebug($"Subject Text: {subjectText}"); - _logger.LogDebug($"SAN: {SAN}"); - _logger.LogDebug($"Provider Name: {providerName}"); - _logger.LogDebug($"Key Type: {keyType}"); - _logger.LogDebug($"Key Size: {keySize}"); - _logger.LogTrace("Attempting to create the CSR by Invoking the script."); - - results = ps.Invoke(); - _logger.LogTrace("Completed the attempt in creating the CSR."); - - ps.Commands.Clear(); - - try - { - ps.AddScript($"$CSR = Get-Content $csrFilename -Raw"); - _logger.LogTrace("Attempting to get the contents of the CSR file."); - results = ps.Invoke(); - _logger.LogTrace("Finished getting the CSR Contents."); - } - catch (Exception) - { - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - - hasError = true; - - throw new CertificateStoreException($"Error creating CSR File. {psError}"); - } - finally - { - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - _logger.LogTrace("Attempt to delete the temporary files."); - results = ps.Invoke(); - - if (hasError) runSpace.Close(); + using var runSpace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); + + _logger.LogTrace("Runspace created"); + runSpace.Open(); + _logger.LogTrace("Runspace opened"); + + PowerShell ps = PowerShell.Create(); + ps.Runspace = runSpace; + + string CSR = string.Empty; + + var subjectText = config.JobProperties["subjectText"]; + var providerName = config.JobProperties["ProviderName"]; + var keyType = config.JobProperties["keyType"]; + var keySize = config.JobProperties["keySize"]; + var SAN = config.JobProperties["SAN"]; + + Collection results; + + // If the provider name is null, default it to the Microsoft CA + providerName ??= "Microsoft Strong Cryptographic Provider"; + + // Create the script file + ps.AddScript("$infFilename = New-TemporaryFile"); + ps.AddScript("$csrFilename = New-TemporaryFile"); + + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); + + ps.AddScript($"Set-Content $infFilename -Value [NewRequest]"); + ps.AddScript($"Add-Content $infFilename -Value 'Subject = \"{subjectText}\"'"); + ps.AddScript($"Add-Content $infFilename -Value 'ProviderName = \"{providerName}\"'"); + ps.AddScript($"Add-Content $infFilename -Value 'MachineKeySet = True'"); + ps.AddScript($"Add-Content $infFilename -Value 'HashAlgorithm = SHA256'"); + ps.AddScript($"Add-Content $infFilename -Value 'KeyAlgorithm = {keyType}'"); + ps.AddScript($"Add-Content $infFilename -Value 'KeyLength={keySize}'"); + ps.AddScript($"Add-Content $infFilename -Value 'KeySpec = 0'"); + + if (SAN != null) + { + ps.AddScript($"Add-Content $infFilename -Value '[Extensions]'"); + ps.AddScript(@"Add-Content $infFilename -Value '2.5.29.17 = ""{text}""'"); + + foreach (string s in SAN.ToString().Split("&")) + { + ps.AddScript($"Add-Content $infFilename -Value '_continue_ = \"{s + "&"}\"'"); + } + } + + try + { + // Get INF file for debugging + ps.AddScript("$name = $infFilename.FullName"); + ps.AddScript("$name"); + results = ps.Invoke(); + + string fname = results[0].ToString(); + string infContent = File.ReadAllText(fname); + + _logger.LogDebug($"Contents of {fname}:"); + _logger.LogDebug(infContent); + } + catch (Exception) + { + } + + // Execute the -new command + ps.AddScript($"certreq -new -q $infFilename $csrFilename"); + _logger.LogDebug($"Subject Text: {subjectText}"); + _logger.LogDebug($"SAN: {SAN}"); + _logger.LogDebug($"Provider Name: {providerName}"); + _logger.LogDebug($"Key Type: {keyType}"); + _logger.LogDebug($"Key Size: {keySize}"); + _logger.LogTrace("Attempting to create the CSR by Invoking the script."); + + results = ps.Invoke(); + _logger.LogTrace("Completed the attempt in creating the CSR."); + + ps.Commands.Clear(); + + try + { + ps.AddScript($"$CSR = Get-Content $csrFilename -Raw"); + _logger.LogTrace("Attempting to get the contents of the CSR file."); + results = ps.Invoke(); + _logger.LogTrace("Finished getting the CSR Contents."); + } + catch (Exception) + { + var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); + + hasError = true; + + throw new CertificateStoreException($"Error creating CSR File. {psError}"); + } + finally + { + ps.Commands.Clear(); + + // Delete the temp files + ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); + _logger.LogTrace("Attempt to delete the temporary files."); + results = ps.Invoke(); + + if (hasError) runSpace.Close(); } // Get the byte array - var RawContent = runSpace.SessionStateProxy.GetVariable("CSR"); - - // Sign CSR in Keyfactor - _logger.LogTrace("Get the signed CSR from KF."); - X509Certificate2 myCert = submitReenrollment.Invoke(RawContent.ToString()); - - if (myCert != null) - { - // Get the cert data into string format - string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); - - _logger.LogTrace("Creating the text version of the certificate."); - - // Write out the cert file - StringBuilder sb = new StringBuilder(); - sb.AppendLine("-----BEGIN CERTIFICATE-----"); - sb.AppendLine(csrData); - sb.AppendLine("-----END CERTIFICATE-----"); - - ps.AddScript("$cerFilename = New-TemporaryFile"); - ps.AddScript($"Set-Content $cerFilename '{sb}'"); - - results = ps.Invoke(); - ps.Commands.Clear(); - - // Accept the signed cert - _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); - - ps.AddScript($"Set-Location -Path Cert:\\localmachine\\'{config.CertificateStoreDetails.StorePath}'"); - ps.AddScript($"Import-Certificate -Filepath $cerFilename"); - ps.Invoke(); - _logger.LogTrace("Successfully bound the certificate."); - - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); - _logger.LogTrace("Removing temporary files."); - results = ps.Invoke(); - - ps.Commands.Clear(); - runSpace.Close(); - - // Default results + var RawContent = runSpace.SessionStateProxy.GetVariable("CSR"); + + // Sign CSR in Keyfactor + _logger.LogTrace("Get the signed CSR from KF."); + X509Certificate2 myCert = submitReenrollment.Invoke(RawContent.ToString()); + + if (myCert != null) + { + // Get the cert data into string format + string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); + + _logger.LogTrace("Creating the text version of the certificate."); + + // Write out the cert file + StringBuilder sb = new StringBuilder(); + sb.AppendLine("-----BEGIN CERTIFICATE-----"); + sb.AppendLine(csrData); + sb.AppendLine("-----END CERTIFICATE-----"); + + ps.AddScript("$cerFilename = New-TemporaryFile"); + ps.AddScript($"Set-Content $cerFilename '{sb}'"); + + results = ps.Invoke(); + ps.Commands.Clear(); + + // Accept the signed cert + _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); + + ps.AddScript($"Set-Location -Path Cert:\\localmachine\\'{config.CertificateStoreDetails.StorePath}'"); + ps.AddScript($"Import-Certificate -Filepath $cerFilename"); + ps.Invoke(); + _logger.LogTrace("Successfully bound the certificate."); + + ps.Commands.Clear(); + + // Delete the temp files + ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); + ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); + ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); + _logger.LogTrace("Removing temporary files."); + results = ps.Invoke(); + + ps.Commands.Clear(); + runSpace.Close(); + + // Default results JobResult result = new JobResult { Result = OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId, FailureMessage = "" - }; - - // Do specific bindings + }; + + // Do specific bindings switch (bindingType) { case CertStoreBindingTypeENUM.WinIIS: @@ -235,9 +448,9 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit result = iisManager.BindCertificate(myCert); // Provide logging information if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the IIS Server."); } - else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the IIS Server. Check the logs for more information."); } - break; - + else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the IIS Server. Check the logs for more information."); } + break; + case CertStoreBindingTypeENUM.WinSQL: // Bind to SQL Server @@ -247,51 +460,51 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit // Provide logging information if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } - break; - - } - - ps.Commands.Clear(); - runSpace.Close(); - - return result; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." - }; - } - - } - catch (PSRemotingTransportException psEx) + break; + + } + + ps.Commands.Clear(); + runSpace.Close(); + + return result; + } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." + }; + } + + } + catch (PSRemotingTransportException psEx) { - var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with a PowerShell Transport Exception: {psEx.Message}"; - _logger.LogError(failureMessage + LogHandler.FlattenException(psEx)); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage - }; - - } - catch (Exception ex) - { - var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; - _logger.LogWarning(failureMessage); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage - }; - } - } - } -} + var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with a PowerShell Transport Exception: {psEx.Message}"; + _logger.LogError(failureMessage + LogHandler.FlattenException(psEx)); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = failureMessage + }; + + } + catch (Exception ex) + { + var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = config.JobHistoryId, + FailureMessage = failureMessage + }; + } + } + } +} diff --git a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs index f191aab..248370e 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs @@ -142,16 +142,16 @@ public List QuerySQLCertificates(RemoteSettings settings, { var siteSettingsDict = new Dictionary { - { "InstanceName", cert.InstanceName}, - { "ProviderName", cert.ProviderName} + { "InstanceName", cert.Parameters.InstanceName}, + { "ProviderName", cert.Parameters.ProviderName} }; Inventory.Add( new CurrentInventoryItem { - Certificates = new[] { cert.Base64Data }, - Alias = cert.Thumbprint, - PrivateKeyEntry = cert.HasPrivateKey, + Certificates = new[] { cert.Certificates }, + Alias = cert.Alias, + PrivateKeyEntry = cert.PrivateKeyEntry, UseChainLevel = false, ItemStatus = OrchestratorInventoryItemStatus.Unknown, Parameters = siteSettingsDict diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index 61843d5..d6a7b8e 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Ignore Spelling: thumbprint + using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -163,6 +165,32 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } case CertStoreOperationType.Remove: { + try + { + // Unbind the certificates + if (UnBindSQLCertificate().Result == OrchestratorJobStatusJobResult.Success) + { + // Remove the certificate from the cert store + complete = RemoveCertificate(config.JobCertificate.Alias); + _logger.LogTrace($"Completed removing the certificate from the store"); + + break; + } + } + catch (Exception ex) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = ex.Message + }; + } + + _logger.LogTrace($"Completed unbinding and removing the certificate from the store"); + + break; + string thumbprint = config.JobCertificate.Alias; complete = RemoveCertificate(thumbprint); @@ -215,10 +243,46 @@ public JobResult ProcessJob(ManagementJobConfiguration config) public JobResult RemoveCertificate(string thumbprint) { - return new JobResult - { Result= OrchestratorJobStatusJobResult.Success, JobHistoryId = _jobHistoryID, FailureMessage = "" }; - } + try + { + using (_psHelper) + { + _psHelper.Initialize(); + + _logger.LogTrace($"Attempting to remove thumbprint {thumbprint} from store {_storePath}"); + + var parameters = new Dictionary() + { + { "Thumbprint", thumbprint }, + { "StorePath", _storePath } + }; + + _psHelper.ExecutePowerShell("Remove-KFCertificateFromStore", parameters); + _logger.LogTrace("Returned from executing PS function (Remove-KFCertificateFromStore)"); + + _psHelper.Terminate(); + } + + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } + catch (Exception ex) + { + var failureMessage = $"Management job {_operationType} failed on Store '{_storePath}' on server '{_clientMachineName}' with error: '{LogHandler.FlattenException(ex)}'"; + _logger.LogWarning(failureMessage); + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = failureMessage + }; + } + } public string AddCertificate(string certificateContents, string privateKeyPassword, string cryptoProvider) { @@ -309,5 +373,49 @@ private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbpr }; } } + + private JobResult UnBindSQLCertificate() + { + bool hadError = false; + var instances = SQLInstanceNames.Split(","); + + foreach (var instanceName in instances) + { + var parameters = new Dictionary + { + { "SqlInstanceName", instanceName.Trim() } + }; + + try + { + _results = _psHelper.ExecutePowerShell("UnBind-KFSqlServerCertificate", parameters); + _logger.LogTrace("Returned from executing PS function (UnBind-KFSqlServerCertificate)"); + } + catch (Exception ex) + { + _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); + hadError = true; + } + } + + if (hadError) + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = "Unable to unbind one or more certificates from the SQL Instances." + }; + } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; + } + } } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs index fe36a5d..0c772c0 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs @@ -6,17 +6,33 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinSQL { - public class WinSQLCertificateInfo + //public class WinSQLCertificateInfo + //{ + // public string InstanceName { get; set; } + // public string StoreName { get; set; } + // public string Certificate { get; set; } + // public string ExpiryDate { get; set; } + // public string Issuer { get; set; } + // public string Thumbprint { get; set; } + // public bool HasPrivateKey { get; set; } + // public string SAN { get; set; } + // public string ProviderName { get; set; } + // public string Base64Data { get; set; } + //} + + public class Parameters { public string InstanceName { get; set; } - public string StoreName { get; set; } - public string Certificate { get; set; } - public string ExpiryDate { get; set; } - public string Issuer { get; set; } - public string Thumbprint { get; set; } - public bool HasPrivateKey { get; set; } - public string SAN { get; set; } - public string ProviderName { get; set; } - public string Base64Data { get; set; } + public object ProviderName { get; set; } + } + + public class WinSQLCertificateInfo + { + public string Certificates { get; set; } + public string Alias { get; set; } + public bool PrivateKeyEntry { get; set; } + public bool UseChainLevel { get; set; } + public string ItemStatus { get; set; } + public Parameters Parameters { get; set; } } } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index e7c940f..0925ca1 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -252,7 +252,7 @@ public void Terminate() catch (Exception ex) { _logger.LogError($"Error while executing script: {ex.Message}"); - return null; + throw new Exception(ex.Message); } finally { diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index bda1946..102f458 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -1,4 +1,9 @@ -function Get-KFCertificates +# Set preferences globally at the script level +$DebugPreference = "Continue" +$VerbosePreference = "Continue" +$InformationPreference = "Continue" + +function Get-KFCertificates { param ( [string]$StoreName = "My" # Default store name is "My" (Personal) @@ -234,8 +239,9 @@ function Remove-KFCertificateFromStore if ($cert) { # Remove the certificate from the store + Write-Information "Attempting to remove certificate from store '$StorePath' with the thumbprint: $Thumbprint" $store.Remove($cert) - Write-Info "Certificate removed successfully from $StorePath." + Write-Information "Certificate removed successfully from store '$StorePath'" } else { Write-Error "Certificate not found in $StorePath." } @@ -371,7 +377,79 @@ function Remove-KFIISBinding } # Function to get certificate information for a SQL Server instance -function GET-KFSQLInventory +function GET-KFSQLInventory { + # Retrieve all SQL Server instances + $sqlInstances = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server").InstalledInstances + Write-Information "There are $($sqlInstances.Count) instances that will be checked for certificates." + + # Dictionary to store instance names by thumbprint + $commonInstances = @{} + + # First loop: gather thumbprints for each instance + foreach ($instance in $sqlInstances) { + Write-Information "Checking instance: $instance for Certificates." + + # Get the registry path for the SQL instance + $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance + $regLocation = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstanceName\MSSQLServer\SuperSocketNetLib" + + try { + # Retrieve the certificate thumbprint from the registry + $thumbprint = (Get-ItemPropertyValue -Path $regLocation -Name "Certificate" -ErrorAction Stop).ToUpper() + + if ($thumbprint) { + # Store instance names by thumbprint + if ($commonInstances.ContainsKey($thumbprint)) { + $commonInstances[$thumbprint] += ",$instance" + } else { + $commonInstances[$thumbprint] = $instance + } + } + } catch { + Write-Information "No certificate found for instance: $instance." + } + } + + # Array to store results + $myBoundCerts = @() + + # Second loop: process each unique thumbprint and gather certificate data + foreach ($kp in $commonInstances.GetEnumerator()) { + $thumbprint = $kp.Key + $instanceNames = $kp.Value + + # Find the certificate in the local machine store + $certStore = "My" + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$certStore\$thumbprint" -ErrorAction SilentlyContinue + + if ($cert) { + # Create a hashtable with the certificate parameters + $sqlSettingsDict = @{ + InstanceName = $instanceNames + ProviderName = $cert.PrivateKey.CspKeyContainerInfo.ProviderName + } + + # Build the inventory item for this certificate + $inventoryItem = [PSCustomObject]@{ + Certificates = [Convert]::ToBase64String($cert.RawData) + Alias = $thumbprint + PrivateKeyEntry = $cert.HasPrivateKey + UseChainLevel = $false + ItemStatus = "Unknown" # OrchestratorInventoryItemStatus.Unknown equivalent + Parameters = $sqlSettingsDict + } + + # Add the inventory item to the results array + $myBoundCerts += $inventoryItem + } + } + + # Return the array of inventory items + return $myBoundCerts | ConvertTo-Json +} + + +function GET-KFSQLInventoryOLD { # Get all SQL Server instances $sqlInstances = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server").InstalledInstances @@ -626,6 +704,153 @@ function Get-SQLServiceUser { # Example usage: # Get-SQLServiceUser -SQLInstanceName "MSSQLSERVER" +##### ReEnrollment functions +function New-CSREnrollment +{ + param ( + [string]$SubjectText, + [string]$ProviderName = "Microsoft Strong Cryptographic Provider", + [string]$KeyType, + [string]$KeyLength, + [string]$SAN + ) + + # Validate the Crypto Service Provider + Validate-CryptoProvider -ProviderName $ProviderName + + # Build the SAN entries if provided + $sanContent = "" + if ($SAN) { + $sanEntries = $SAN -split "&" + $sanDirectives = $sanEntries | ForEach-Object { "_continue_ = `"$($_)`"" } + $sanContent = @" +[Extensions] +2.5.29.17 = `"{text}`" +$($sanDirectives -join "`n") +"@ + } + + # Generate INF file content for the CSR + $infContent = @" +[Version] +Signature=`"$`Windows NT$`" + +[NewRequest] +Subject = "$SubjectText" +ProviderName = "$ProviderName" +MachineKeySet = True +HashAlgorithm = SHA256 +KeyAlgorithm = $KeyType +KeyLength = $KeyLength +KeySpec = 0 + +$sanContent +"@ + + Write-Verbose "INF Contents: $infContent" + + # Path to temporary INF file + $infFile = [System.IO.Path]::GetTempFileName() + ".inf" + $csrOutputFile = [System.IO.Path]::GetTempFileName() + ".csr" + + Set-Content -Path $infFile -Value $infContent + Write-Information "Generated INF file at: $infFile" + + try { + # Run certreq to generate CSR + $certReqCommand = "certreq -new -q `"$infFile`" `"$csrOutputFile`"" + Write-Information "Running certreq: $certReqCommand" + + # Capture the output and errors + $certReqOutput = & certreq -new -q $infFile $csrOutputFile 2>&1 + + # Check the exit code of the command + if ($LASTEXITCODE -ne 0) { + Write-Error "Certreq failed with exit code $LASTEXITCODE. Output: $certReqOutput" + throw "Failed to create CSR file due to certreq error." + } + + # If successful, proceed + Write-Information "Certreq completed successfully." + + # Read CSR file + if (Test-Path $csrOutputFile) { + $csrContent = Get-Content -Path $csrOutputFile -Raw + Write-Information "CSR successfully created at: $csrOutputFile" + return $csrContent + } else { + throw "Failed to create CSR file." + } + } catch { + Write-Error "An error occurred: $_" + } finally { + # Clean up temporary files + if (Test-Path $infFile) { + Remove-Item -Path $infFile -Force + Write-Information "Deleted temporary INF file." + } + + if (Test-Path $csrOutputFile) { + Remove-Item -Path $csrOutputFile -Force + Write-Information "Deleted temporary CSR file." + } + } +} + +function Import-SignedCertificate { + param ( + [Parameter(Mandatory = $true)] + [byte[]]$RawData, # RawData from the certificate + + [Parameter(Mandatory = $true)] + [ValidateSet("My", "Root", "CA", "TrustedPublisher", "TrustedPeople")] + [string]$StoreName # Store to which the certificate should be imported + ) + + try { + # Step 1: Convert raw certificate data to Base64 string with line breaks + Write-Verbose "Converting raw certificate data to Base64 string." + $csrData = [System.Convert]::ToBase64String($RawData, [System.Base64FormattingOptions]::InsertLineBreaks) + + # Step 2: Create PEM-formatted certificate content + Write-Verbose "Creating PEM-formatted certificate content." + $certContent = @( + "-----BEGIN CERTIFICATE-----" + $csrData + "-----END CERTIFICATE-----" + ) -join "`n" + + # Step 3: Create a temporary file for the certificate + Write-Verbose "Creating a temporary file for the certificate." + $cerFilename = [System.IO.Path]::GetTempFileName() + Set-Content -Path $cerFilename -Value $certContent -Force + Write-Verbose "Temporary certificate file created at: $cerFilename" + + # Step 4: Import the certificate into the specified store + Write-Verbose "Importing the certificate to the store: Cert:\LocalMachine\$StoreName" + Set-Location -Path "Cert:\LocalMachine\$StoreName" + + $importResult = Import-Certificate -FilePath $cerFilename + if ($importResult) { + Write-Verbose "Certificate successfully imported to Cert:\LocalMachine\$StoreName." + } else { + throw "Certificate import failed." + } + + # Step 5: Cleanup temporary file + if (Test-Path $cerFilename) { + Remove-Item -Path $cerFilename -Force + Write-Verbose "Temporary file deleted: $cerFilename" + } + + # Step 6: Return the imported certificate's thumbprint + return $importResult.Thumbprint + + } catch { + Write-Error "An error occurred during the certificate export and import process: $_" + } +} +##### @@ -731,4 +956,48 @@ function Get-KFCertificateByThumbprint Write-Error "An error occurred while retrieving the certificate: $_" return $null } +} + +function Get-CryptoProviders { + # Retrieves the list of available Crypto Service Providers using certutil + try { + Write-Verbose "Retrieving Crypto Service Providers using certutil..." + $certUtilOutput = certutil -csplist + + # Parse the output to extract CSP names + $cspInfoList = @() + foreach ($line in $certUtilOutput) { + if ($line -match "Provider Name:") { + $cspName = ($line -split ":")[1].Trim() + $cspInfoList += $cspName + } + } + + if ($cspInfoList.Count -eq 0) { + throw "No Crypto Service Providers were found. Ensure certutil is functioning properly." + } + + Write-Verbose "Retrieved the following CSPs:" + $cspInfoList | ForEach-Object { Write-Verbose $_ } + + return $cspInfoList + } catch { + throw "Failed to retrieve Crypto Service Providers: $_" + } +} + +function Validate-CryptoProvider { + param ( + [Parameter(Mandatory)] + [string]$ProviderName + ) + Write-Verbose "Validating CSP: $ProviderName" + + $availableProviders = Get-CryptoProviders + + if (-not ($availableProviders -contains $ProviderName)) { + throw "Crypto Service Provider '$ProviderName' is either invalid or not found on this system." + } + + Write-Verbose "Crypto Service Provider '$ProviderName' is valid." } \ No newline at end of file From 7eb21c5411ead14956d3ed2f7619b3fac4fbc748 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Mon, 11 Nov 2024 09:51:56 -0600 Subject: [PATCH 11/21] testing --- IISU/WindowsCertStore.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index fd51802..c6f9c75 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -62,8 +62,5 @@ Always - - Always - From ace79125778d8e0d9c10049d2f4bc8e297485852 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Fri, 22 Nov 2024 12:25:23 -0800 Subject: [PATCH 12/21] Testing ReEnrollment --- IISU/ClientPSCertStoreReEnrollment.cs | 4 ++-- IISU/PowerShellScripts/WinCertFull.ps1 | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index ca56765..1290663 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -88,13 +88,13 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit using (_psHelper) { // First create and return the CSR - string csr = CreateCSR(subjectText, providerName, keyType, keySize, SAN); + var csr = CreateCSR(subjectText, providerName, keyType, keySize, SAN); if (csr != string.Empty) { // Submit and Sign the CSR in Command _logger.LogTrace("Attempting to sign CSR"); - X509Certificate2 myCert = submitReenrollment.Invoke(csr); + X509Certificate2 myCert = submitReenrollment.Invoke(csr.ToString()); // Import the certificate string thumbprint = ImportCertificate(myCert.RawData, storePath); diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index 102f458..500c7e5 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -722,7 +722,7 @@ function New-CSREnrollment $sanContent = "" if ($SAN) { $sanEntries = $SAN -split "&" - $sanDirectives = $sanEntries | ForEach-Object { "_continue_ = `"$($_)`"" } + $sanDirectives = $sanEntries | ForEach-Object { "_continue_ = `"$($_)&`"" } $sanContent = @" [Extensions] 2.5.29.17 = `"{text}`" From 0918766949a5a89a9ec7b132a589a7de7af74797 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Mon, 25 Nov 2024 16:07:32 -0800 Subject: [PATCH 13/21] Testing SQL ReEnrollment --- IISU/Certificate.cs | 2 - IISU/ClientPSCertStoreReEnrollment.cs | 37 ++- IISU/ImplementedStoreTypes/Win/Inventory.cs | 6 - IISU/ImplementedStoreTypes/Win/Management.cs | 2 +- .../Win/WinCertCertificateInfo.cs | 2 +- .../WinIIS/IISBindingInfo.cs | 10 +- .../WinIIS/IISCertificateInfo.cs | 2 +- .../ImplementedStoreTypes/WinIIS/Inventory.cs | 2 - .../WinIIS/Management.cs | 122 ++++----- .../WinIIS/WinIISBinding.cs | 79 ++++++ .../ImplementedStoreTypes/WinSQL/Inventory.cs | 3 +- .../WinSQL/Management.cs | 251 ++++++++---------- .../WinSQL/ReEnrollment.cs | 2 +- .../WinSQL/WinSQLCertificateInfo.cs | 19 +- .../WinSQL/WinSqlBinding.cs | 80 ++++++ IISU/PSHelper.cs | 2 +- 16 files changed, 377 insertions(+), 244 deletions(-) create mode 100644 IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs create mode 100644 IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs diff --git a/IISU/Certificate.cs b/IISU/Certificate.cs index 360c5f8..04d4926 100644 --- a/IISU/Certificate.cs +++ b/IISU/Certificate.cs @@ -11,8 +11,6 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. - -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Newtonsoft.Json; using System; using System.Collections.Generic; diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index 1290663..4137d6e 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Ignore Spelling: Keyfactor + using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -19,17 +21,16 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Management.Automation.Runspaces; using System.Management.Automation; using System.Management.Automation.Remoting; -using System.Net; using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.Extensions.Logging; using Keyfactor.Orchestrators.Extensions.Interfaces; using System.Linq; using System.IO; -using Microsoft.PowerShell; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -39,7 +40,7 @@ internal class ClientPSCertStoreReEnrollment private readonly IPAMSecretResolver _resolver; private PSHelper _psHelper; - private Collection? _results = null; + private Collection? _results; public ClientPSCertStoreReEnrollment(ILogger logger, IPAMSecretResolver resolver) { @@ -100,14 +101,30 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit string thumbprint = ImportCertificate(myCert.RawData, storePath); // If there is binding, bind it to the correct store type - switch (bindingType) + if (thumbprint != null) { - case CertStoreBindingTypeENUM.WinIIS: - break; - case CertStoreBindingTypeENUM.WinSQL: - break; + switch (bindingType) + { + case CertStoreBindingTypeENUM.WinIIS: + // Bind Certificate to IIS Site + IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); + WinIISBinding.BindCertificate(_psHelper, bindingInfo, thumbprint, "", storePath); + break; + case CertStoreBindingTypeENUM.WinSQL: + // Bind Certificate to SQL Instance + string sqlInstanceNames = "MSSQLSERVER"; + if (config.JobProperties.ContainsKey("InstanceName")) + { + sqlInstanceNames = config.JobProperties["InstanceName"]?.ToString() ?? "MSSQLSERVER"; + } + WinSqlBinding.BindSQLCertificate(_psHelper, sqlInstanceNames, thumbprint, "", storePath, false); + break; + } + } + + jobResult = new JobResult { Result = OrchestratorJobStatusJobResult.Success, @@ -255,8 +272,6 @@ private string ImportCertificate(byte[] certificateRawData, string storeName) } } - - public JobResult PerformReEnrollmentORIG(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, CertStoreBindingTypeENUM bindingType) { bool hasError = false; diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index fe8d9f6..7ec296f 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -15,13 +15,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.Win; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index a83717c..eb811f8 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -142,7 +142,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas _logger.LogTrace("Attempting to execute PS function (Add-KFCertificateToStore)"); - // Manditory parameters + // Mandatory parameters var parameters = new Dictionary { { "Base64Cert", certificateContents }, diff --git a/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs b/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs index 0f69d13..91a0efc 100644 --- a/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs @@ -6,7 +6,7 @@ using System.Text; using System.Threading.Tasks; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.Win +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { public class WinCertCertificateInfo { diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs index f5a7d7c..329f911 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs @@ -1,11 +1,9 @@ -using System; +// Ignore Spelling: Keyfactor IISU + +using System; using System.Collections.Generic; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { public class IISBindingInfo { diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs index 81fd1fd..89133a8 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { public class IISCertificateInfo { diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index eb4d8fb..ab33f43 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index 43a87de..e916054 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -17,7 +17,6 @@ using System.Collections.ObjectModel; using System.IO; using System.Management.Automation; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinIIS; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -109,8 +108,9 @@ public JobResult ProcessJob(ManagementJobConfiguration config) // Bind Certificate to IIS Site if (newThumbprint != null) { + // TODO: Need to check/test IISU renewal thumbprint IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); - BindCertificate(bindingInfo, newThumbprint); + WinIISBinding.BindCertificate(_psHelper, bindingInfo, newThumbprint, "", _storePath); complete = new JobResult { @@ -141,7 +141,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) string thumbprint = config.JobCertificate.Alias.Split(':')[0]; try { - if (UnBindCertificate(new IISBindingInfo(config.JobProperties))) + if (WinIISBinding.UnBindCertificate(_psHelper, new IISBindingInfo(config.JobProperties))) { complete = RemoveCertificate(thumbprint); } @@ -271,64 +271,64 @@ public JobResult RemoveCertificate(string thumbprint) } } - public void BindCertificate(IISBindingInfo bindingInfo, string thumbprint) - { - _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); + //public void BindCertificate(IISBindingInfo bindingInfo, string thumbprint) + //{ + // _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); - // Manditory parameters - var parameters = new Dictionary - { - { "Thumbprint", thumbprint }, - { "WebSite", bindingInfo.SiteName }, - { "Protocol", bindingInfo.Protocol }, - { "IPAddress", bindingInfo.IPAddress }, - { "Port", bindingInfo.Port }, - { "SNIFlag", bindingInfo.SniFlag }, - { "StoreName", _storePath }, - }; - - // Optional parameters - if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } - - _results = _psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); - _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); - - // This should return the thumbprint of the certificate - if (_results != null && _results.Count > 0) - { - _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); - } - else - { - _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); - } - } - - public bool UnBindCertificate(IISBindingInfo bindingInfo) - { - _logger.LogTrace("Attempting to UnBind and execute PS function (Remove-KFIISBinding)"); - - // Manditory parameters - var parameters = new Dictionary - { - { "SiteName", bindingInfo.SiteName }, - { "IPAddress", bindingInfo.IPAddress }, - { "Port", bindingInfo.Port }, - }; - - // Optional parameters - if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } - - try - { - _results = _psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); - _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); - return true; - } - catch (Exception) - { - return false; - } - } + // // Manditory parameters + // var parameters = new Dictionary + // { + // { "Thumbprint", thumbprint }, + // { "WebSite", bindingInfo.SiteName }, + // { "Protocol", bindingInfo.Protocol }, + // { "IPAddress", bindingInfo.IPAddress }, + // { "Port", bindingInfo.Port }, + // { "SNIFlag", bindingInfo.SniFlag }, + // { "StoreName", _storePath }, + // }; + + // // Optional parameters + // if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + + // _results = _psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); + // _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // // This should return the thumbprint of the certificate + // if (_results != null && _results.Count > 0) + // { + // _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); + // } + // else + // { + // _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + // } + //} + + //public bool UnBindCertificate(IISBindingInfo bindingInfo) + //{ + // _logger.LogTrace("Attempting to UnBind and execute PS function (Remove-KFIISBinding)"); + + // // Mandatory parameters + // var parameters = new Dictionary + // { + // { "SiteName", bindingInfo.SiteName }, + // { "IPAddress", bindingInfo.IPAddress }, + // { "Port", bindingInfo.Port }, + // }; + + // // Optional parameters + // if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + + // try + // { + // _results = _psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); + // _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); + // return true; + // } + // catch (Exception) + // { + // return false; + // } + //} } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs new file mode 100644 index 0000000..7332016 --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs @@ -0,0 +1,79 @@ +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU +{ + public class WinIISBinding + { + private static ILogger _logger; + private static Collection? _results = null; + private static PSHelper _helper; + + public static void BindCertificate(PSHelper psHelper, IISBindingInfo bindingInfo, string thumbprint, string renewalThumbprint, string storePath) + { + _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); + + // Mandatory parameters + var parameters = new Dictionary + { + { "Thumbprint", thumbprint }, + { "WebSite", bindingInfo.SiteName }, + { "Protocol", bindingInfo.Protocol }, + { "IPAddress", bindingInfo.IPAddress }, + { "Port", bindingInfo.Port }, + { "SNIFlag", bindingInfo.SniFlag }, + { "StoreName", storePath }, + }; + + // Optional parameters + if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + + _results = psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); + _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + + // This should return the thumbprint of the certificate + if (_results != null && _results.Count > 0) + { + _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); + } + else + { + _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + } + } + + public static bool UnBindCertificate(PSHelper psHelper, IISBindingInfo bindingInfo) + { + _logger.LogTrace("Attempting to UnBind and execute PS function (Remove-KFIISBinding)"); + + // Mandatory parameters + var parameters = new Dictionary + { + { "SiteName", bindingInfo.SiteName }, + { "IPAddress", bindingInfo.IPAddress }, + { "Port", bindingInfo.Port }, + }; + + // Optional parameters + if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + + try + { + _results = psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); + _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); + return true; + } + catch (Exception) + { + return false; + } + } + } +} diff --git a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs index 248370e..46df5df 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinSQL; +// Ignore Spelling: Keyfactor Sql + using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index d6a7b8e..b321168 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Ignore Spelling: thumbprint +// Ignore Spelling: thumbprint Keyfactor sql using System; using System.Collections.Generic; @@ -22,6 +22,7 @@ using System.Management.Automation; using System.Management.Automation.Runspaces; using System.Numerics; +using Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -128,25 +129,25 @@ public JobResult ProcessJob(ManagementJobConfiguration config) // Bind Certificate to SQL Instance if (newThumbprint != null) { - complete = BindSQLCertificate(newThumbprint, RenewalThumbprint); - // Check the RenewalThumbprint. If there is a value, this is a renewal - if (config.JobProperties.ContainsKey("RenewalThumbprint")) + if (WinSqlBinding.BindSQLCertificate(_psHelper, SQLInstanceNames, newThumbprint, RenewalThumbprint, _storePath, RestartSQLService)) { - // This is a renewal. - // Check if there is an existing certificate. If there is, replace it with the new one. - string renewalThumbprint = config.JobProperties["RenewalThumbprint"]?.ToString() ?? string.Empty; + complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = _jobHistoryID, + FailureMessage = "" + }; } else { - // This is a new certificate - just bind it. + complete = new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = "Unable to Bind certificate to SQL Instance" + }; } - complete = new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobHistoryID, - FailureMessage = "" - }; } } catch (Exception ex) @@ -168,7 +169,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) try { // Unbind the certificates - if (UnBindSQLCertificate().Result == OrchestratorJobStatusJobResult.Success) + if (WinSqlBinding.UnBindSQLCertificate(_psHelper, SQLInstanceNames)) { // Remove the certificate from the cert store complete = RemoveCertificate(config.JobCertificate.Alias); @@ -176,6 +177,16 @@ public JobResult ProcessJob(ManagementJobConfiguration config) break; } + else + { + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Failure, + JobHistoryId = _jobHistoryID, + FailureMessage = "Unable to unbind one or more certificates from the SQL Instances." + }; + } + } catch (Exception ex) { @@ -188,42 +199,12 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } _logger.LogTrace($"Completed unbinding and removing the certificate from the store"); - - break; - - string thumbprint = config.JobCertificate.Alias; - - complete = RemoveCertificate(thumbprint); - _logger.LogTrace($"Completed removing the certificate from the store"); - - break; + return complete; } } } return complete; - - //switch (config.OperationType) - //{ - // case CertStoreOperationType.Add: - // _logger.LogTrace("Entering Add..."); - // myRunspace.Open(); - // complete = PerformAddCertificate(config, serverUserName, serverPassword); - // myRunspace.Close(); - // _logger.LogTrace("After Perform Addition..."); - // break; - // case CertStoreOperationType.Remove: - // _logger.LogTrace("Entering Remove..."); - // _logger.LogTrace("After PerformRemoval..."); - // myRunspace.Open(); - // complete = PerformRemoveCertificate(config, serverUserName, serverPassword); - // myRunspace.Close(); - // _logger.LogTrace("After Perform Removal..."); - // break; - //} - - //_logger.MethodExit(); - //return complete; } catch (Exception ex) { @@ -328,94 +309,94 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo } } - private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbprint) - { - bool hadError = false; - var instances = SQLInstanceNames.Split(","); - - foreach (var instanceName in instances) - { - var parameters = new Dictionary - { - { "Thumbprint", newThumbprint }, - { "SqlInstanceName", instanceName.Trim() }, - { "StoreName", _storePath }, - { "RestartService", RestartSQLService } - }; - - try - { - _results = _psHelper.ExecutePowerShell("Bind-CertificateToSqlInstance", parameters); - _logger.LogTrace("Return from executing PS function (Bind-CertificateToSqlInstance)"); - } - catch (Exception ex) - { - _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); - hadError= true; - } - } - - if (hadError) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobHistoryID, - FailureMessage = "Unable to bind one or more certificates to the SQL Instances." - }; - } else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobHistoryID, - FailureMessage = "" - }; - } - } - - private JobResult UnBindSQLCertificate() - { - bool hadError = false; - var instances = SQLInstanceNames.Split(","); - - foreach (var instanceName in instances) - { - var parameters = new Dictionary - { - { "SqlInstanceName", instanceName.Trim() } - }; - - try - { - _results = _psHelper.ExecutePowerShell("UnBind-KFSqlServerCertificate", parameters); - _logger.LogTrace("Returned from executing PS function (UnBind-KFSqlServerCertificate)"); - } - catch (Exception ex) - { - _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); - hadError = true; - } - } - - if (hadError) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobHistoryID, - FailureMessage = "Unable to unbind one or more certificates from the SQL Instances." - }; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobHistoryID, - FailureMessage = "" - }; - } - } + //private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbprint) + //{ + // bool hadError = false; + // var instances = SQLInstanceNames.Split(","); + + // foreach (var instanceName in instances) + // { + // var parameters = new Dictionary + // { + // { "Thumbprint", newThumbprint }, + // { "SqlInstanceName", instanceName.Trim() }, + // { "StoreName", _storePath }, + // { "RestartService", RestartSQLService } + // }; + + // try + // { + // _results = _psHelper.ExecutePowerShell("Bind-CertificateToSqlInstance", parameters); + // _logger.LogTrace("Return from executing PS function (Bind-CertificateToSqlInstance)"); + // } + // catch (Exception ex) + // { + // _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); + // hadError= true; + // } + // } + + // if (hadError) + // { + // return new JobResult + // { + // Result = OrchestratorJobStatusJobResult.Failure, + // JobHistoryId = _jobHistoryID, + // FailureMessage = "Unable to bind one or more certificates to the SQL Instances." + // }; + // } else + // { + // return new JobResult + // { + // Result = OrchestratorJobStatusJobResult.Success, + // JobHistoryId = _jobHistoryID, + // FailureMessage = "" + // }; + // } + //} + + //private JobResult UnBindSQLCertificate() + //{ + // bool hadError = false; + // var instances = SQLInstanceNames.Split(","); + + // foreach (var instanceName in instances) + // { + // var parameters = new Dictionary + // { + // { "SqlInstanceName", instanceName.Trim() } + // }; + + // try + // { + // _results = _psHelper.ExecutePowerShell("UnBind-KFSqlServerCertificate", parameters); + // _logger.LogTrace("Returned from executing PS function (UnBind-KFSqlServerCertificate)"); + // } + // catch (Exception ex) + // { + // _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); + // hadError = true; + // } + // } + + // if (hadError) + // { + // return new JobResult + // { + // Result = OrchestratorJobStatusJobResult.Failure, + // JobHistoryId = _jobHistoryID, + // FailureMessage = "Unable to unbind one or more certificates from the SQL Instances." + // }; + // } + // else + // { + // return new JobResult + // { + // Result = OrchestratorJobStatusJobResult.Success, + // JobHistoryId = _jobHistoryID, + // FailureMessage = "" + // }; + // } + //} } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs index a96782d..8ee0df6 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs @@ -36,7 +36,7 @@ public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollm ClientPSCertStoreReEnrollment myReEnrollment = new ClientPSCertStoreReEnrollment(_logger, _resolver); - // SQL ReEnrollment performs a different type of binding. Set the bindcertificate to false and call SQL Binding + // SQL ReEnrollment performs a different type of binding. Set the bind certificate to false and call SQL Binding return myReEnrollment.PerformReEnrollment(config, submitReEnrollmentUpdate, CertStoreBindingTypeENUM.WinSQL); } } diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs index 0c772c0..13cf61d 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs @@ -1,24 +1,13 @@ -using System; +// Ignore Spelling: Keyfactor sql + +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.ImplementedStoreTypes.WinSQL +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql { - //public class WinSQLCertificateInfo - //{ - // public string InstanceName { get; set; } - // public string StoreName { get; set; } - // public string Certificate { get; set; } - // public string ExpiryDate { get; set; } - // public string Issuer { get; set; } - // public string Thumbprint { get; set; } - // public bool HasPrivateKey { get; set; } - // public string SAN { get; set; } - // public string ProviderName { get; set; } - // public string Base64Data { get; set; } - //} public class Parameters { diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs new file mode 100644 index 0000000..a4c4d3f --- /dev/null +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs @@ -0,0 +1,80 @@ +// Ignore Spelling: Keyfactor Sql + +using Keyfactor.Orchestrators.Common.Enums; +using Keyfactor.Orchestrators.Extensions; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Management.Automation; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql +{ + public class WinSqlBinding + { + private static ILogger _logger; + private static Collection? _results = null; + + public static bool BindSQLCertificate(PSHelper psHelper, string SQLInstanceNames, string newThumbprint, string renewalThumbprint, string storePath, bool restartSQLService) + { + bool hadError = false; + var instances = SQLInstanceNames.Split(","); + + foreach (var instanceName in instances) + { + var parameters = new Dictionary + { + { "Thumbprint", newThumbprint }, + { "SqlInstanceName", instanceName.Trim() }, + { "StoreName", storePath }, + { "RestartService", restartSQLService } + }; + + try + { + _results = psHelper.ExecutePowerShell("Bind-CertificateToSqlInstance", parameters); + _logger.LogTrace("Return from executing PS function (Bind-CertificateToSqlInstance)"); + } + catch (Exception ex) + { + _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); + hadError = true; + } + } + + if (hadError) return false; + else return true; + } + + public static bool UnBindSQLCertificate(PSHelper psHelper, string SQLInstanceNames) + { + bool hadError = false; + var instances = SQLInstanceNames.Split(","); + + foreach (var instanceName in instances) + { + var parameters = new Dictionary + { + { "SqlInstanceName", instanceName.Trim() } + }; + + try + { + _results = psHelper.ExecutePowerShell("UnBind-KFSqlServerCertificate", parameters); + _logger.LogTrace("Returned from executing PS function (UnBind-KFSqlServerCertificate)"); + } + catch (Exception ex) + { + _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); + hadError = true; + } + } + + if (hadError) return false; + else return true; + } + } +} diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 0925ca1..81fe6af 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -32,7 +32,7 @@ namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { - internal class PSHelper : IDisposable + public class PSHelper : IDisposable { private static ILogger _logger; From 8bd2d1d851eb7832d70783382e0ecb2951c16dde Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 26 Nov 2024 07:30:50 -0800 Subject: [PATCH 14/21] Testing SQL ReEnrollment --- IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs | 9 ++++++++- IISU/PowerShellScripts/WinCertFull.ps1 | 14 ++++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs index a4c4d3f..84ea53d 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs @@ -1,5 +1,6 @@ // Ignore Spelling: Keyfactor Sql +using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; @@ -18,6 +19,12 @@ public class WinSqlBinding private static ILogger _logger; private static Collection? _results = null; + public WinSqlBinding() + { + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); + } + public static bool BindSQLCertificate(PSHelper psHelper, string SQLInstanceNames, string newThumbprint, string renewalThumbprint, string storePath, bool restartSQLService) { bool hadError = false; @@ -27,7 +34,7 @@ public static bool BindSQLCertificate(PSHelper psHelper, string SQLInstanceNames { var parameters = new Dictionary { - { "Thumbprint", newThumbprint }, + { "Thumbprint", newThumbprint.ToLower() }, { "SqlInstanceName", instanceName.Trim() }, { "StoreName", storePath }, { "RestartService", restartSQLService } diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index 500c7e5..d7c7101 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -150,12 +150,14 @@ function Add-KFCertificateToStore # Execute certutil based on whether a private key password was supplied try { - if ($PrivateKeyPassword) { - $output = certutil -f -csp $CryptoServiceProvider -p $PrivateKeyPassword $StoreName $tempFileName - } - else { - $output = certutil -f -importpfx -csp $CryptoServiceProvider -p $PrivateKeyPassword $StoreName $tempFileName - } + # Generate the appropriate certutil command based on the parameters + $cryptoProviderPart = if ($CryptoServiceProvider) { "-csp `"$CryptoServiceProvider`" " } else { "" } + $passwordPart = if ($PrivateKeyPassword) { "-p `"$PrivateKeyPassword`" " } else { "" } + $action = if ($PrivateKeyPassword) { "importpfx" } else { "addstore" } + + # Construct the full certutil command + $command = "certutil -f $cryptoProviderPart$passwordPart-$action $StorePath `"$tempFileName`"" + $output = Invoke-Expression $command # Check for errors based on the last exit code if ($LASTEXITCODE -ne 0) { From 3f24d6576a7a58f45ade543e491e1076ac5b6105 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Thu, 12 Dec 2024 21:09:45 -0800 Subject: [PATCH 15/21] ssh changes --- IISU/PSHelper.cs | 56 ++++++++++++++++++++++++++++++++---- IISU/WindowsCertStore.csproj | 3 ++ 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 81fe6af..67a0ba5 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -40,6 +40,7 @@ public class PSHelper : IDisposable private Collection _PSSession = new Collection(); private string scriptFileLocation = string.Empty; + private string tempKeyFilePath; private string protocol; private string port; @@ -89,11 +90,17 @@ public PSHelper(string protocol, string port, bool useSPN, string clientMachineN this.serverPassword = serverPassword; _logger = LogHandler.GetClassLogger(); - _logger.MethodEntry(); + _logger.LogTrace("Entered PSHelper Constructor"); + _logger.LogTrace($"Protocol: {this.protocol}"); + _logger.LogTrace($"Port: {this.port}"); + _logger.LogTrace($"UseSPN: {this.useSPN}"); + _logger.LogTrace($"ClientMachineName: {ClientMachineName}"); + _logger.LogTrace("Constructor Completed"); } public void Initialize() { + _logger.LogTrace("Entered PSHelper.Initialize()"); PS = PowerShell.Create(); // Add listeners to raise events @@ -125,15 +132,38 @@ private void InitializeRemoteSession() { if (protocol == "ssh") { - // TODO: Need to add logic when using keyfilePath - // TODO: Need to add keyfilePath parameter - PS.AddCommand("New-PSSession") - .AddParameter("HostName", ClientMachineName) - .AddParameter("UserName", serverUserName); + _logger.LogTrace("Initializing SSH connection"); + + try + { + tempKeyFilePath = Path.GetTempFileName(); + _logger.LogTrace($"Created temporary KeyFilePath: {tempKeyFilePath}"); + + File.WriteAllText(tempKeyFilePath, serverPassword); + File.SetAttributes(tempKeyFilePath, FileAttributes.ReadOnly); + PS.AddCommand("New-PSSession") + .AddParameter("HostName", ClientMachineName) + .AddParameter("UserName", serverUserName); + + // TODO: THIS IS FOR TESTING ONLY + if (serverPassword != null) + { + // TODO: Need to write out to file and pass file name. For right now, the password is the filename. + _logger.LogTrace($"Current KeyFilePath: {tempKeyFilePath}"); + PS.AddParameter("KeyFilePath", tempKeyFilePath); + } + + } + catch (Exception ex) + { + _logger.LogError($"Error while creating temporary KeyFilePath: {ex.Message}"); + throw new Exception("Error while creating temporary KeyFilePath."); + } } else { + _logger.LogTrace("Initializing WinRM connection"); var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; PSCredential myCreds = new PSCredential(serverUserName, pw); @@ -152,6 +182,7 @@ private void InitializeRemoteSession() _logger.LogTrace("Attempting to invoke PS-Session command on remote machine."); _PSSession = PS.Invoke(); + _logger.LogTrace("Session Invoked...Checking for errors."); CheckErrors(); PS.Commands.Clear(); @@ -200,6 +231,19 @@ public void Terminate() CheckErrors(); } + if (File.Exists(tempKeyFilePath)) + { + try + { + //File.Delete(tempKeyFilePath); + _logger.LogTrace($"Temporary KeyFilePath deleted: {tempKeyFilePath}"); + } + catch (Exception) + { + _logger.LogError($"Error while deleting KeyFilePath."); + } + } + try { PS.Runspace.Close(); diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index c6f9c75..fc16e1d 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -59,6 +59,9 @@ Always + + Always + Always From 2355833a61bef3fa04ee1039ee6eca3dd77473cb Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Fri, 3 Jan 2025 13:48:55 -0800 Subject: [PATCH 16/21] Minor Script changes/improvements --- IISU/PSHelper.cs | 1 + IISU/PowerShellScripts/WinCertFull.ps1 | 183 ++++++++++++++++--------- 2 files changed, 119 insertions(+), 65 deletions(-) diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 67a0ba5..3f8ae52 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -304,6 +304,7 @@ public void Terminate() } } + [Obsolete] public Collection? ExecuteCommand(string scriptBlock, Dictionary parameters = null) { _logger.LogTrace("Executing PowerShell Script"); diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index d7c7101..8182283 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -3,112 +3,148 @@ $DebugPreference = "Continue" $VerbosePreference = "Continue" $InformationPreference = "Continue" -function Get-KFCertificates -{ +function Get-KFCertificates { param ( [string]$StoreName = "My" # Default store name is "My" (Personal) ) - # Get all certificates from the specified store - $certificates = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" + # Define the store path using the provided StoreName parameter + $storePath = "Cert:\LocalMachine\$StoreName" - # Initialize an array to store the results + # Check if the store path exists to ensure the store is valid + if (-not (Test-Path $storePath)) { + # Write an error message and exit the function if the store path is invalid + Write-Error "The certificate store path '$storePath' does not exist. Please provide a valid store name." + return + } + + # Retrieve all certificates from the specified store + $certificates = Get-ChildItem -Path $storePath + + # Initialize an empty array to store certificate information objects $certInfoList = @() foreach ($cert in $certificates) { - # Create a custom object to store the certificate information - $certInfo = [PSCustomObject]@{ - StoreName = $StoreName - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - SAN = Get-KFSAN $cert - ProviderName = Get-CertificateCSP $cert - Base64Data = [System.Convert]::ToBase64String($cert.RawData) + try { + # Create a custom object to store details about the current certificate + $certInfo = [PSCustomObject]@{ + StoreName = $StoreName # Name of the certificate store + Certificate = $cert.Subject # Subject of the certificate + ExpiryDate = $cert.NotAfter # Expiration date of the certificate + Issuer = $cert.Issuer # Issuer of the certificate + Thumbprint = $cert.Thumbprint # Unique thumbprint of the certificate + HasPrivateKey = $cert.HasPrivateKey # Indicates if the certificate has a private key + SAN = Get-KFSAN $cert # Subject Alternative Names (if available) + ProviderName = Get-CertificateCSP $cert # Provider of the certificate + Base64Data = [System.Convert]::ToBase64String($cert.RawData) # Encoded raw certificate data + } + + # Add the certificate information object to the results array + $certInfoList += $certInfo + } catch { + # Write a warning message if there is an error processing the current certificate + Write-Warning "An error occurred while processing the certificate: $_" } - - # Add the certificate information to the array - $certInfoList += $certInfo } - # Output the results + # Output the results in JSON format if certificates were found if ($certInfoList) { - $certInfoList | ConvertTo-Json + $certInfoList | ConvertTo-Json -Depth 10 + } else { + # Write a warning if no certificates were found in the specified store + Write-Warning "No certificates were found in the store '$StoreName'." } } -function Get-KFIISBoundCertificates -{ - # Import the WebAdministration module +function Get-KFIISBoundCertificates { + # Import the IISAdministration module Import-Module IISAdministration - #Import-Module WebAdministration # Get all websites - #$websites = Get-Website $websites = Get-IISSite - Write-Information "There were ${websites}.count found" + # Write the count of websites found + Write-Information "There were $($websites.Count) websites found." # Initialize an array to store the results $certificates = @() + # Initialize a counter for the total number of bindings with certificates + $totalBoundCertificates = 0 + foreach ($site in $websites) { # Get the site name $siteName = $site.name - + # Get the bindings for the site - #$bindings = Get-WebBinding -Name $siteName $bindings = Get-IISSiteBinding -Name $siteName - + + # Initialize a counter for bindings with certificates for the current site + $siteBoundCertificateCount = 0 + foreach ($binding in $bindings) { # Check if the binding has an SSL certificate - if ($binding.protocol -eq 'https') { + if ($binding.protocol -eq 'https' -and $binding.RawAttributes.certificateHash) { # Get the certificate hash - #$certHash = $binding.certificateHash $certHash = $binding.RawAttributes.certificateHash - + # Get the certificate store $StoreName = $binding.certificateStoreName - - # Get the certificate details from the certificate store - $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName\$certHash" - - $certBase64 = [Convert]::ToBase64String($cert.RawData) - - # Create a custom object to store the results - $certInfo = [PSCustomObject]@{ - SiteName = $siteName - Binding = $binding.bindingInformation - IPAddress = ($binding.bindingInformation -split ":")[0] - Port = ($binding.bindingInformation -split ":")[1] - Hostname = ($binding.bindingInformation -split ":")[2] - Protocol = $binding.protocol - SNI = $binding.sslFlags -eq 1 - ProviderName = Get-CertificateCSP $cert - SAN = Get-KFSAN $cert - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - CertificateBase64 = $certBase64 + + try { + # Get the certificate details from the certificate store + $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName\$certHash" + + # Convert certificate data to Base64 + $certBase64 = [Convert]::ToBase64String($cert.RawData) + + # Create a custom object to store the results + $certInfo = [PSCustomObject]@{ + SiteName = $siteName + Binding = $binding.bindingInformation + IPAddress = ($binding.bindingInformation -split ":")[0] + Port = ($binding.bindingInformation -split ":")[1] + Hostname = ($binding.bindingInformation -split ":")[2] + Protocol = $binding.protocol + SNI = $binding.sslFlags -eq 1 + ProviderName = Get-CertificateCSP $cert + SAN = Get-KFSAN $cert + Certificate = $cert.Subject + ExpiryDate = $cert.NotAfter + Issuer = $cert.Issuer + Thumbprint = $cert.Thumbprint + HasPrivateKey = $cert.HasPrivateKey + CertificateBase64 = $certBase64 + } + + # Add the certificate information to the array + $certificates += $certInfo + + # Increment the counters + $siteBoundCertificateCount++ + $totalBoundCertificates++ + } catch { + Write-Warning "Could not retrieve certificate details for hash $certHash in store $StoreName." } - - # Add the certificate information to the array - $certificates += $certInfo } } + + # Write the count of bindings with certificates for the current site + Write-Information "Website: $siteName has $siteBoundCertificateCount bindings with certificates." } - # Output the results - if ($certificates) { + # Write the total count of bindings with certificates + Write-Information "A total of $totalBoundCertificates bindings with valid certificates were found." + + # Output the results in JSON format or indicate no certificates found + if ($totalBoundCertificates -gt 0) { $certificates | ConvertTo-Json + } else { + Write-Information "No valid certificates were found bound to websites." } - } + function Add-KFCertificateToStore { param ( @@ -172,7 +208,7 @@ function Add-KFCertificateToStore # Retrieve the certificate thumbprint from the store $cert = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" | Sort-Object -Property NotAfter -Descending | Select-Object -First 1 if ($cert) { - $thumbprint = $cert.Thumbprint + $output = $cert.Thumbprint Write-Output "Certificate imported successfully. Thumbprint: $thumbprint" } else { @@ -191,7 +227,7 @@ function Add-KFCertificateToStore } # Output the final result - $output + return $output } else { $bytes = [System.Convert]::FromBase64String($Base64Cert) @@ -227,6 +263,9 @@ function Remove-KFCertificateFromStore [switch]$IsAlias ) + # Initialize a variable to track success + $isSuccessful = $false + try { # Open the certificate store $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StorePath, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) @@ -244,17 +283,31 @@ function Remove-KFCertificateFromStore Write-Information "Attempting to remove certificate from store '$StorePath' with the thumbprint: $Thumbprint" $store.Remove($cert) Write-Information "Certificate removed successfully from store '$StorePath'" + + # Mark success + $isSuccessful = $true } else { - Write-Error "Certificate not found in $StorePath." + throw [System.Exception]::new("Certificate not found in $StorePath.") } # Close the store $store.Close() } catch { + # Log and rethrow the exception Write-Error "An error occurred: $_" + throw $_ + } finally { + # Ensure the store is closed + if ($store) { + $store.Close() + } } + + # Return the success status + return $isSuccessful } + function New-KFIISSiteBinding { param ( From 1e4cf3cb197d5838e84cbf582b37953d243cd20d Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Tue, 21 Jan 2025 12:19:16 -0800 Subject: [PATCH 17/21] Testing SSH on Linux --- IISU/ClientPSCertStoreReEnrollment.cs | 34 ++- IISU/ImplementedStoreTypes/Win/Inventory.cs | 4 +- IISU/ImplementedStoreTypes/Win/Management.cs | 3 +- IISU/PSHelper.cs | 244 ++++++++++++++++--- IISU/PowerShellScripts/WinCertFull.ps1 | 11 +- 5 files changed, 252 insertions(+), 44 deletions(-) diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index 4137d6e..1bf5517 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -89,7 +89,27 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit using (_psHelper) { // First create and return the CSR - var csr = CreateCSR(subjectText, providerName, keyType, keySize, SAN); + _logger.LogTrace($"Subject Text: {subjectText}"); + _logger.LogTrace($"Provider Name: {providerName}"); + _logger.LogTrace($"Key Type: {keyType}"); + _logger.LogTrace($"Key Size: {keySize}"); + _logger.LogTrace($"SAN: {SAN}"); + + string csr = string.Empty; + + try + { + _logger.LogTrace("Attempting to Create CSR"); + csr = CreateCSR(subjectText, providerName, keyType, keySize, SAN); + _logger.LogTrace("Returned from creating CSR"); + } + catch (Exception ex) + { + _logger.LogError($"Error while attempting to create the CSR: {ex.Message}"); + throw new Exception("Unable to create the CSR file. Check the Orchestrator Logs for more information"); + } + + _logger.LogTrace($"CSR Contents: '{csr}'"); if (csr != string.Empty) { @@ -158,10 +178,12 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit }; } finally - { - _psHelper.Terminate(); + { + if (_psHelper != null) + { + _psHelper.Terminate(); + } } - } private string CreateCSR(string subjectText, string providerName, string keyType, int keySize, string SAN) @@ -183,9 +205,9 @@ private string CreateCSR(string subjectText, string providerName, string keyType { "keyLength", keySize }, { "SAN", SAN } }; - _logger.LogTrace("Attempting to execute PS function (New-CsrEnrollment)"); + _logger.LogInformation("Attempting to execute PS function (New-CsrEnrollment)"); _results = _psHelper.ExecutePowerShell("New-CsrEnrollment", parameters); - _logger.LogTrace("Returned from executing PS function (New-CsrEnrollment)"); + _logger.LogInformation("Returned from executing PS function (New-CsrEnrollment)"); // This should return the CSR that was generated if (_results == null || _results.Count == 0) diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index 7ec296f..1cbfd77 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -128,7 +128,7 @@ public List QueryWinCertCertificates(RemoteSettings settin { { "StoreName", StoreName } }; - + results = ps.ExecutePowerShell("Get-KFCertificates", parameters); // If there are certificates, deserialize the results and send them back to command @@ -148,7 +148,7 @@ public List QueryWinCertCertificates(RemoteSettings settin Inventory.Add( new CurrentInventoryItem { - Certificates = new[] { cert.Base64Data}, + Certificates = new[] { cert.Base64Data }, Alias = cert.Thumbprint, PrivateKeyEntry = cert.HasPrivateKey, UseChainLevel = false, diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index eb811f8..f32cefa 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -25,6 +25,7 @@ using System.Collections.ObjectModel; using System.Collections.Generic; using System.Management.Automation.Runspaces; +using System.Security.Cryptography.X509Certificates; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { @@ -153,7 +154,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } - _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore ", parameters); _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); // This should return the thumbprint of the certificate diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 3f8ae52..2fb23dc 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -27,6 +27,8 @@ using System.Management.Automation.Remoting; using System.Management.Automation.Runspaces; using System.Net; +using System.Runtime.InteropServices; +using System.Text; using System.Text.Json; using System.Xml.Serialization; @@ -101,6 +103,7 @@ public PSHelper(string protocol, string port, bool useSPN, string clientMachineN public void Initialize() { _logger.LogTrace("Entered PSHelper.Initialize()"); + PS = PowerShell.Create(); // Add listeners to raise events @@ -136,30 +139,35 @@ private void InitializeRemoteSession() try { - tempKeyFilePath = Path.GetTempFileName(); - _logger.LogTrace($"Created temporary KeyFilePath: {tempKeyFilePath}"); - - File.WriteAllText(tempKeyFilePath, serverPassword); - File.SetAttributes(tempKeyFilePath, FileAttributes.ReadOnly); - - PS.AddCommand("New-PSSession") - .AddParameter("HostName", ClientMachineName) - .AddParameter("UserName", serverUserName); - - // TODO: THIS IS FOR TESTING ONLY - if (serverPassword != null) - { - // TODO: Need to write out to file and pass file name. For right now, the password is the filename. - _logger.LogTrace($"Current KeyFilePath: {tempKeyFilePath}"); - PS.AddParameter("KeyFilePath", tempKeyFilePath); - } - + _logger.LogInformation("Attempting to create a temporary key file"); + tempKeyFilePath = createPrivateKeyFile(); } catch (Exception ex) { _logger.LogError($"Error while creating temporary KeyFilePath: {ex.Message}"); throw new Exception("Error while creating temporary KeyFilePath."); } + + + Hashtable options = new Hashtable + { + { "StrictHostKeyChecking", "No" }, + { "UserKnownHostsFile", "/dev/null" } + }; + + PS.AddCommand("New-PSSession") + .AddParameter("Options", options) + .AddParameter("HostName", ClientMachineName) + .AddParameter("UserName", serverUserName); + + // TODO: THIS IS FOR TESTING ONLY + if (serverPassword != null) + { + // TODO: Need to write out to file and pass file name. For right now, the password is the filename. + _logger.LogTrace($"Current KeyFilePath: {tempKeyFilePath}"); + PS.AddParameter("KeyFilePath", tempKeyFilePath); + } + } else { @@ -182,18 +190,25 @@ private void InitializeRemoteSession() _logger.LogTrace("Attempting to invoke PS-Session command on remote machine."); _PSSession = PS.Invoke(); - _logger.LogTrace("Session Invoked...Checking for errors."); - CheckErrors(); - PS.Commands.Clear(); - _logger.LogTrace("PS-Session established"); + if (_PSSession.Count > 0) + { + _logger.LogTrace("Session Invoked...Checking for errors."); + PS.Commands.Clear(); + _logger.LogTrace("PS-Session established"); - PS.AddCommand("Invoke-Command") - .AddParameter("Session", _PSSession) - .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript(scriptFileLocation))); + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) + .AddParameter("ScriptBlock", ScriptBlock.Create(PSHelper.LoadScript(scriptFileLocation))); + + var results = PS.Invoke(); + CheckErrors(); + } + else + { + throw new Exception("Failed to create the remote PowerShell Session."); + } - var results = PS.Invoke(); - CheckErrors(); } private void InitializeLocalSession() @@ -224,9 +239,9 @@ private void InitializeLocalSession() public void Terminate() { PS.Commands.Clear(); - if (_PSSession.Count > 0) + if (PS != null) { - PS.AddCommand("Remove-Session").AddParameter("Session", _PSSession); + PS.AddCommand("Remove-PSSession").AddParameter("Session", _PSSession); PS.Invoke(); CheckErrors(); } @@ -235,7 +250,7 @@ public void Terminate() { try { - //File.Delete(tempKeyFilePath); + File.Delete(tempKeyFilePath); _logger.LogTrace($"Temporary KeyFilePath deleted: {tempKeyFilePath}"); } catch (Exception) @@ -261,15 +276,105 @@ public void Terminate() } public Collection? ExecutePowerShell(string commandName, Dictionary? parameters = null) + { + using (PowerShell PS = PowerShell.Create()) + { + try + { + string scriptBlock; + + if (parameters != null && parameters.Count > 0) + { + _logger.LogTrace("Creating script block with parameters."); + string paramBlock = string.Join(", ", parameters.Select(p => $"[{p.Value.GetType().Name}] ${p.Key}")); + string paramUsage = string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}")); + + scriptBlock = $@" + param({paramBlock}) + {commandName} {paramUsage} + "; + } + else + { + _logger.LogTrace("Creating script block with no parameters."); + scriptBlock = commandName; + } + + PS.AddCommand("Invoke-Command") + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)); + + if (!isLocalMachine) + { + PS.AddParameter("Session", _PSSession); + } + + if (parameters != null && parameters.Count > 0) + { + PS.AddParameter("ArgumentList", parameters.Values.ToArray()); + } + + _logger.LogTrace($"Executing script block:\n{scriptBlock}"); + + var results = PS.Invoke(); + + if (PS.HadErrors) + { + string errorMessages = string.Join("; ", PS.Streams.Error.Select(e => e.ToString())); + _logger.LogError($"{errorMessages}"); + throw new Exception($"PowerShell execution errors: {errorMessages}"); + } + + return results; + } + catch (Exception ex) + { + _logger.LogError($"Error while executing script: {ex.Message}"); + throw new Exception($"An error occurred while attempting to execute the PowerShell script: {ex.Message}."); + } + finally + { + PS.Commands.Clear(); + } + } + } + + + public Collection? ExecutePowerShellV2(string commandName, Dictionary? parameters = null) { try { if (!isLocalMachine) { + string scriptBlock; + + if (parameters != null && parameters.Count > 0) + { + _logger.LogTrace("Creating script block with parameters."); + string paramBlock = string.Join(", ", parameters.Keys.Select(key => $"[{parameters[key].GetType().Name}] ${key}")); + string paramUsage = string.Join(" ", parameters.Keys.Select(key => $"-{key} ${key}")); + + scriptBlock = $@" + param({paramBlock}) + {commandName} {paramUsage} + "; + } + else + { + _logger.LogTrace("Creating script block with no parameters."); + scriptBlock = $@" + {commandName} + "; + } + + PS.AddCommand("Invoke-Command") .AddParameter("Session", _PSSession) // send session only when necessary (remote) - .AddParameter("ScriptBlock", ScriptBlock.Create(commandName)) - .AddParameter("ArgumentList", parameters?.Values.ToArray()); + .AddParameter("ScriptBlock", ScriptBlock.Create(commandName)); + + if (parameters != null && parameters.Count > 0) + { + PS.AddParameter("ArgumentList", parameters.Values.ToArray()); + } } else { @@ -346,6 +451,8 @@ public void Terminate() private void CheckErrors() { + _logger.LogTrace("Checking PowerShell session for errors."); + string errorList = string.Empty; if (PS.HadErrors) { @@ -577,6 +684,79 @@ public static bool IsCSPFound(IEnumerable cspList, string userCSP) return false; } + private string createPrivateKeyFile() + { + string tmpFile = Path.GetTempFileName(); // "logs/AdminFile"; + _logger.LogTrace($"Created temporary KeyFilePath: {tmpFile}, writing bytes."); + + File.WriteAllText(tmpFile, formatPrivateKey(serverPassword)); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + _logger.LogTrace($"Changing permissions on Windows temp file: {tmpFile}."); + File.SetAttributes(tmpFile, FileAttributes.ReadOnly); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + ProcessStartInfo chmodInfo = new ProcessStartInfo() + { + FileName = "/bin/chmod", + Arguments = "600 " + tmpFile, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + using (Process chmodProcess = new Process() { StartInfo = chmodInfo }) + { + chmodProcess.Start(); + chmodProcess.WaitForExit(); + if (chmodProcess.ExitCode == 0) + { + _logger.LogInformation("File permissions set to 600."); + } + else + { + _logger.LogWarning("Failed to set file permissions."); + } + } + } + + return tmpFile; + } + + private static string formatPrivateKey(string privateKey) + { + String keyType = privateKey.Contains("OPENSSH PRIVATE KEY") ? "OPENSSH" : "RSA"; + + return privateKey.Replace($" {keyType} PRIVATE ", "^^^").Replace(" ", System.Environment.NewLine).Replace("^^^", $" {keyType} PRIVATE ") + System.Environment.NewLine; + } + + //private string formatPrivateKey(string privateKey) + //{ + // // Identify the markers in the private key + // string beginMarker = "-----BEGIN OPENSSH PRIVATE KEY-----"; + // string endMarker = "-----END OPENSSH PRIVATE KEY-----"; + + // // Locate the positions of the markers + // int beginIndex = privateKey.IndexOf(beginMarker); + // int endIndex = privateKey.IndexOf(endMarker); + + // // Split the string into three parts: before, key content, and after + // string beforeKey = privateKey.Substring(0, beginIndex + beginMarker.Length); + // string keyContent = privateKey.Substring(beginIndex + beginMarker.Length, endIndex - beginIndex - beginMarker.Length); + // string afterKey = privateKey.Substring(endIndex); + + // // Replace spaces with actual carriage return and line feed in key content + // keyContent = keyContent.Replace(" ", "\r\n"); + + // // Construct the final string with the correctly formatted key + // string replacedFile = beforeKey + keyContent + afterKey + "\r\n"; + + // // Log the modified string + // _logger.LogTrace(replacedFile); + + // return replacedFile; + //} } } diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index 8182283..2721b4f 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -163,6 +163,11 @@ function Add-KFCertificateToStore try { + Write-Information "Base64Cert: $Base64Cert" + Write-Information "PrivateKeyPassword: $PrivateKeyPassword" + Write-Information "StoreName: $StoreName" + Write-Information "CryptoServiceProvider: $CryptoServiceProvider" + $thumbprint = $null if ($CryptoServiceProvider) @@ -821,8 +826,8 @@ $sanContent # Check the exit code of the command if ($LASTEXITCODE -ne 0) { - Write-Error "Certreq failed with exit code $LASTEXITCODE. Output: $certReqOutput" - throw "Failed to create CSR file due to certreq error." + $errMsg = "Certreq failed with exit code $LASTEXITCODE. Output: $certReqOutput" + throw $errMsg } # If successful, proceed @@ -837,7 +842,7 @@ $sanContent throw "Failed to create CSR file." } } catch { - Write-Error "An error occurred: $_" + Write-Error $_ } finally { # Clean up temporary files if (Test-Path $infFile) { From f1cbd4c4864c4aaeec4bce940e926d50e13a3bc9 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 12 Feb 2025 08:50:03 -0800 Subject: [PATCH 18/21] Finished testing; cleaning up code. --- IISU/ClientPSCertStoreReEnrollment.cs | 6 +- IISU/ImplementedStoreTypes/Win/Management.cs | 2 +- .../ImplementedStoreTypes/WinIIS/Inventory.cs | 11 +- .../WinIIS/WinIISBinding.cs | 61 +- .../WinSQL/Management.cs | 4 +- .../WinSQL/WinSqlBinding.cs | 79 ++- IISU/PSHelper.cs | 320 +++++++---- IISU/PowerShellScripts/WinCertFull.ps1 | 523 ++++++++++++++++-- 8 files changed, 821 insertions(+), 185 deletions(-) diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index 1bf5517..6ab7688 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -106,7 +106,7 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit catch (Exception ex) { _logger.LogError($"Error while attempting to create the CSR: {ex.Message}"); - throw new Exception("Unable to create the CSR file. Check the Orchestrator Logs for more information"); + throw new Exception($"Unable to create the CSR file. {ex.Message}"); } _logger.LogTrace($"CSR Contents: '{csr}'"); @@ -115,7 +115,9 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit { // Submit and Sign the CSR in Command _logger.LogTrace("Attempting to sign CSR"); - X509Certificate2 myCert = submitReenrollment.Invoke(csr.ToString()); + X509Certificate2 myCert = submitReenrollment.Invoke(csr); + + if (myCert == null) { throw new Exception("Command was unable to sign the CSR."); } // Import the certificate string thumbprint = ImportCertificate(myCert.RawData, storePath); diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index f32cefa..0688ea5 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -154,7 +154,7 @@ public JobResult AddCertificate(string certificateContents, string privateKeyPas if (!string.IsNullOrEmpty(privateKeyPassword)) { parameters.Add("PrivateKeyPassword", privateKeyPassword); } if (!string.IsNullOrEmpty(cryptoProvider)) { parameters.Add("CryptoServiceProvider", cryptoProvider); } - _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore ", parameters); + _results = _psHelper.ExecutePowerShell("Add-KFCertificateToStore", parameters); _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); // This should return the thumbprint of the certificate diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index ab33f43..ec4f401 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -125,7 +125,16 @@ public List QueryIISCertificates(RemoteSettings settings) { ps.Initialize(); - results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); + if (ps.IsLocalMachine) + { + _logger.LogTrace("Executing function locally"); + results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); + } + else + { + _logger.LogTrace("Executing function remotely"); + results = ps.InvokeFunction("Get-KFIISBoundCertificates"); + } // If there are certificates, deserialize the results and send them back to command if (results != null && results.Count > 0) diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs index 7332016..2ec96e0 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs @@ -5,8 +5,11 @@ using System.Collections.ObjectModel; using System.Linq; using System.Management.Automation; +using System.Net; +using System.Security.Cryptography.X509Certificates; using System.Text; using System.Threading.Tasks; +using System.Web.Services.Description; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { @@ -18,35 +21,43 @@ public class WinIISBinding public static void BindCertificate(PSHelper psHelper, IISBindingInfo bindingInfo, string thumbprint, string renewalThumbprint, string storePath) { + _logger = LogHandler.GetClassLogger(typeof(WinIISBinding)); _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); // Mandatory parameters var parameters = new Dictionary { - { "Thumbprint", thumbprint }, - { "WebSite", bindingInfo.SiteName }, - { "Protocol", bindingInfo.Protocol }, + { "SiteName", bindingInfo.SiteName }, { "IPAddress", bindingInfo.IPAddress }, { "Port", bindingInfo.Port }, - { "SNIFlag", bindingInfo.SniFlag }, + { "Protocol", bindingInfo.Protocol }, + { "Thumbprint", thumbprint }, { "StoreName", storePath }, + { "SslFlags", bindingInfo.SniFlag } }; // Optional parameters if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } - _results = psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); - _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); + _results = psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); // returns true if successful + _logger.LogTrace("Returned from executing PS function (New-KFIISSiteBinding)"); // This should return the thumbprint of the certificate if (_results != null && _results.Count > 0) { - _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); - } - else - { - _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); + bool success = _results[0].BaseObject is bool value && value; + if (success) + { + _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}' successfully."); + return; + } + else + { + _logger.LogTrace("Something happened and the binding failed."); + } } + + throw new Exception($"An unknown error occurred while attempting to bind thumbprint: {thumbprint} to site: '{bindingInfo.SiteName}'. \nCheck the UO Logs for more information."); } public static bool UnBindCertificate(PSHelper psHelper, IISBindingInfo bindingInfo) @@ -62,16 +73,36 @@ public static bool UnBindCertificate(PSHelper psHelper, IISBindingInfo bindingIn }; // Optional parameters - if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } + if (!string.IsNullOrEmpty(bindingInfo.HostName)) + { + parameters.Add("HostName", bindingInfo.HostName); + } try { - _results = psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); + var results = psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); - return true; + + if (results == null || results.Count == 0) + { + _logger.LogWarning("PowerShell function returned no results."); + return false; + } + + if (results[0].BaseObject is bool success) + { + _logger.LogTrace($"Returned from unbinding as {success}."); + return success; + } + else + { + _logger.LogWarning("Unexpected result type from PowerShell function."); + return false; + } } - catch (Exception) + catch (Exception ex) { + _logger.LogError(ex, "An error occurred while attempting to unbind the certificate."); return false; } } diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index b321168..bf5ad4e 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -169,7 +169,9 @@ public JobResult ProcessJob(ManagementJobConfiguration config) try { // Unbind the certificates - if (WinSqlBinding.UnBindSQLCertificate(_psHelper, SQLInstanceNames)) + _logger.LogTrace($"SQL Instance Names: {SQLInstanceNames}"); + _logger.LogTrace("Attempting to unbind certificates."); + if (WinSqlBinding.UnBindSQLCertificate(_psHelper, SQLInstanceNames, RestartSQLService)) { // Remove the certificate from the cert store complete = RemoveCertificate(config.JobCertificate.Alias); diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs index 84ea53d..9f649c8 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs @@ -27,61 +27,80 @@ public WinSqlBinding() public static bool BindSQLCertificate(PSHelper psHelper, string SQLInstanceNames, string newThumbprint, string renewalThumbprint, string storePath, bool restartSQLService) { - bool hadError = false; - var instances = SQLInstanceNames.Split(","); + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); - foreach (var instanceName in instances) + try { var parameters = new Dictionary { - { "Thumbprint", newThumbprint.ToLower() }, - { "SqlInstanceName", instanceName.Trim() }, - { "StoreName", storePath }, - { "RestartService", restartSQLService } + { "SQLInstance", SQLInstanceNames }, + { "RenewalThumbprint", renewalThumbprint.ToLower() }, + { "NewThumbprint", newThumbprint.ToLower() } }; - try + if (restartSQLService) { - _results = psHelper.ExecutePowerShell("Bind-CertificateToSqlInstance", parameters); - _logger.LogTrace("Return from executing PS function (Bind-CertificateToSqlInstance)"); + parameters["RestartService"] = restartSQLService; } - catch (Exception ex) + + _results = psHelper.ExecutePowerShell("Bind-KFSqlCertificate", parameters); + if (_results != null && _results.Count > 0) { - _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); - hadError = true; + // Extract value from PSObject and convert to bool + if (bool.TryParse(_results[0]?.BaseObject?.ToString(), out bool result)) + { + _logger.LogTrace($"PowerShell function Bind-KFSqlCertificate returned: {result}"); + return result; + } } - } - if (hadError) return false; - else return true; + _logger.LogWarning("PowerShell function Bind-KFSqlCertificate did not return a valid boolean result."); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error executing PowerShell function: Bind-KFSqlCertificate"); + return false; + } } - public static bool UnBindSQLCertificate(PSHelper psHelper, string SQLInstanceNames) + public static bool UnBindSQLCertificate(PSHelper psHelper, string SQLInstanceNames, bool restartSQLService) { - bool hadError = false; - var instances = SQLInstanceNames.Split(","); + _logger = LogHandler.GetClassLogger(); + _logger.MethodEntry(); - foreach (var instanceName in instances) + _logger.LogTrace("Entered method UnBindSQLCertificate"); + try { var parameters = new Dictionary { - { "SqlInstanceName", instanceName.Trim() } + { "SQLInstanceNames", SQLInstanceNames.Trim() } // Send full list at once }; - try + if (restartSQLService) { - _results = psHelper.ExecutePowerShell("UnBind-KFSqlServerCertificate", parameters); - _logger.LogTrace("Returned from executing PS function (UnBind-KFSqlServerCertificate)"); + parameters["RestartService"] = restartSQLService; } - catch (Exception ex) + + _results = psHelper.ExecutePowerShell("Unbind-KFSqlCertificate", parameters); + if (_results != null && _results.Count > 0) { - _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); - hadError = true; + if (bool.TryParse(_results[0]?.BaseObject?.ToString(), out bool result)) + { + _logger.LogTrace($"PowerShell function Unbind-KFSqlCertificate returned: {result}"); + return result; + } } - } - if (hadError) return false; - else return true; + _logger.LogWarning("PowerShell function Unbind-KFSqlCertificate did not return a valid boolean result."); + return false; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error occurred while unbinding certificate(s) from SQL instance(s)"); + return false; + } } } } diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 2fb23dc..0619382 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -28,8 +28,10 @@ using System.Management.Automation.Runspaces; using System.Net; using System.Runtime.InteropServices; +using System.Security.AccessControl; using System.Text; using System.Text.Json; +using System.Threading; using System.Xml.Serialization; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore @@ -129,6 +131,24 @@ public void Initialize() { InitializeLocalSession(); } + + // Display Hosting information + string psInfo = @" + $psVersion = $PSVersionTable.PSVersion + $os = [System.Environment]::OSVersion + $hostName = [System.Net.Dns]::GetHostName() + + [PSCustomObject]@{ + PowerShellVersion = $psVersion + OperatingSystem = $os + HostName = $hostName + } | ConvertTo-Json + "; + var results = ExecutePowerShellScript(psInfo); + foreach (var result in results) + { + _logger.LogTrace($"{result}"); + } } private void InitializeRemoteSession() @@ -148,26 +168,18 @@ private void InitializeRemoteSession() throw new Exception("Error while creating temporary KeyFilePath."); } - Hashtable options = new Hashtable { { "StrictHostKeyChecking", "No" }, - { "UserKnownHostsFile", "/dev/null" } + { "UserKnownHostsFile", "/dev/null" }, }; PS.AddCommand("New-PSSession") - .AddParameter("Options", options) .AddParameter("HostName", ClientMachineName) - .AddParameter("UserName", serverUserName); - - // TODO: THIS IS FOR TESTING ONLY - if (serverPassword != null) - { - // TODO: Need to write out to file and pass file name. For right now, the password is the filename. - _logger.LogTrace($"Current KeyFilePath: {tempKeyFilePath}"); - PS.AddParameter("KeyFilePath", tempKeyFilePath); - } - + .AddParameter("UserName", serverUserName) + .AddParameter("KeyFilePath", tempKeyFilePath) + .AddParameter("ConnectingTimeout", 10000) + .AddParameter("Options", options); } else { @@ -188,6 +200,8 @@ private void InitializeRemoteSession() .AddParameter("SessionOption", sessionOption); } + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); + _logger.LogTrace("Attempting to invoke PS-Session command on remote machine."); _PSSession = PS.Invoke(); @@ -203,6 +217,7 @@ private void InitializeRemoteSession() var results = PS.Invoke(); CheckErrors(); + _logger.LogTrace("Script loaded into remote session successfully."); } else { @@ -241,9 +256,15 @@ public void Terminate() PS.Commands.Clear(); if (PS != null) { - PS.AddCommand("Remove-PSSession").AddParameter("Session", _PSSession); - PS.Invoke(); - CheckErrors(); + try + { + PS.AddCommand("Remove-PSSession").AddParameter("Session", _PSSession); + PS.Invoke(); + CheckErrors(); + } + catch (Exception) + { + } } if (File.Exists(tempKeyFilePath)) @@ -275,67 +296,152 @@ public void Terminate() return ExecutePowerShell(functionName); } - public Collection? ExecutePowerShell(string commandName, Dictionary? parameters = null) + public Collection? InvokeFunction(string functionName, Dictionary? parameters = null) { - using (PowerShell PS = PowerShell.Create()) + PS.Commands.Clear(); + + // Prepare the command + PS.AddCommand("Invoke-Command") + .AddParameter("ScriptBlock", ScriptBlock.Create(functionName)); + + if (!isLocalMachine) { - try - { - string scriptBlock; + PS.AddParameter("Session", _PSSession); + } - if (parameters != null && parameters.Count > 0) - { - _logger.LogTrace("Creating script block with parameters."); - string paramBlock = string.Join(", ", parameters.Select(p => $"[{p.Value.GetType().Name}] ${p.Key}")); - string paramUsage = string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}")); + // Add parameters + if (parameters != null) + { + PS.AddParameter("ArgumentList", parameters.Values.ToArray()); + } - scriptBlock = $@" - param({paramBlock}) - {commandName} {paramUsage} - "; + _logger.LogTrace($"Attempting to InvokeFunction: {functionName}"); + var results = PS.Invoke(); + + if (PS.HadErrors) + { + string errorMessages = string.Join("; ", PS.Streams.Error.Select(e => e.ToString())); + throw new Exception($"Error executing function '{functionName}': {errorMessages}"); + } + + return results; + } + + public Collection ExecutePowerShellScript(string script) + { + PS.AddScript(script); + return PS.Invoke(); + } + + + public Collection? ExecutePowerShell(string commandOrScript, Dictionary? parameters = null, bool isScript = false) + { + try + { + PS.Commands.Clear(); + + // Handle Local or Remote Execution + if (isLocalMachine) + { + if (isScript) + { + // Add script content directly for local execution + PS.AddScript(commandOrScript); } else { - _logger.LogTrace("Creating script block with no parameters."); - scriptBlock = commandName; + // Add command for local execution + PS.AddCommand(commandOrScript); } + } + else + { + // For remote execution, use Invoke-Command + var scriptBlock = isScript + ? ScriptBlock.Create(commandOrScript) // Use the script as a ScriptBlock + : ScriptBlock.Create($"& {{ {commandOrScript} }}"); // Wrap commands in ScriptBlock PS.AddCommand("Invoke-Command") - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)); + .AddParameter("Session", _PSSession) + .AddParameter("ScriptBlock", scriptBlock); + } - if (!isLocalMachine) + // Add Parameters if provided + if (parameters != null) + { + if (isLocalMachine || isScript) { - PS.AddParameter("Session", _PSSession); + foreach (var param in parameters) + { + PS.AddParameter(param.Key, param.Value); + } } - - if (parameters != null && parameters.Count > 0) + else { - PS.AddParameter("ArgumentList", parameters.Values.ToArray()); - } - - _logger.LogTrace($"Executing script block:\n{scriptBlock}"); + // Remote execution: Use ArgumentList for parameters + var paramBlock = string.Join(", ", parameters.Select(p => $"[{p.Value.GetType().Name}] ${p.Key}")); + var paramUsage = string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}")); - var results = PS.Invoke(); + string scriptBlockWithParams = $@" + param({paramBlock}) + {commandOrScript} {paramUsage} + "; - if (PS.HadErrors) - { - string errorMessages = string.Join("; ", PS.Streams.Error.Select(e => e.ToString())); - _logger.LogError($"{errorMessages}"); - throw new Exception($"PowerShell execution errors: {errorMessages}"); + PS.Commands.Clear(); // Clear previous commands + PS.AddCommand("Invoke-Command") + .AddParameter("Session", _PSSession) + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlockWithParams)) + .AddParameter("ArgumentList", parameters.Values.ToArray()); } - - return results; } - catch (Exception ex) + + // Log and execute + _logger.LogTrace($"Executing PowerShell: {commandOrScript}"); + var results = PS.Invoke(); + + // Check for errors + if (PS.HadErrors) { - _logger.LogError($"Error while executing script: {ex.Message}"); - throw new Exception($"An error occurred while attempting to execute the PowerShell script: {ex.Message}."); + string errorMessages = string.Join("; ", PS.Streams.Error.Select(e => e.ToString())); + throw new Exception($"PowerShell execution errors: {errorMessages}"); } - finally + + return results; + } + catch (Exception ex) + { + _logger.LogError($"Error executing PowerShell: {ex.Message}"); + throw; + } + finally + { + PS.Commands.Clear(); + } + } + + + public Collection? ExecutePowerShellV3(string commandName, Dictionary? parameters = null) + { + PS.Commands.Clear(); + PS.AddCommand(commandName); + + if (parameters != null) + { + foreach (var param in parameters) { - PS.Commands.Clear(); + PS.AddParameter(param.Key, param.Value); } } + + if (!isLocalMachine) + { + PS.AddParameter("Session", _PSSession); + } + + var results = PS.Invoke(); + CheckErrors(); + + return results; } @@ -343,64 +449,68 @@ public void Terminate() { try { - if (!isLocalMachine) + string scriptBlock; + + if (parameters != null && parameters.Count > 0) { - string scriptBlock; + _logger.LogTrace("Creating script block with parameters."); + string paramBlock = string.Join(", ", parameters.Select(p => $"[{p.Value.GetType().Name}] ${p.Key}")); + string paramValues = string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}")); + + //scriptBlock = $@" + // param({paramBlock}) + // {commandName} {paramUsage} + //"; + + scriptBlock = $@" + param({paramBlock}) + {paramValues} + {commandName} {string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}"))} + "; + } + else + { + _logger.LogTrace("Creating script block with no parameters."); + scriptBlock = commandName; + } - if (parameters != null && parameters.Count > 0) - { - _logger.LogTrace("Creating script block with parameters."); - string paramBlock = string.Join(", ", parameters.Keys.Select(key => $"[{parameters[key].GetType().Name}] ${key}")); - string paramUsage = string.Join(" ", parameters.Keys.Select(key => $"-{key} ${key}")); - - scriptBlock = $@" - param({paramBlock}) - {commandName} {paramUsage} - "; - } - else - { - _logger.LogTrace("Creating script block with no parameters."); - scriptBlock = $@" - {commandName} - "; - } + //PS.AddCommand("Invoke-Command") + // .AddParameter("ScriptBlock", scriptBlock); + //.AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)); + if (!isLocalMachine) + { PS.AddCommand("Invoke-Command") - .AddParameter("Session", _PSSession) // send session only when necessary (remote) - .AddParameter("ScriptBlock", ScriptBlock.Create(commandName)); - - if (parameters != null && parameters.Count > 0) - { - PS.AddParameter("ArgumentList", parameters.Values.ToArray()); - } + .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) + .AddParameter("Session", _PSSession); } else { - PS.AddCommand(commandName); + PS.AddScript(scriptBlock); + } - if (parameters != null) - { - foreach (var parameter in parameters) - { - PS.AddParameter(parameter.Key, parameter.Value); - } - } + if (parameters != null && parameters.Count > 0) + { + PS.AddParameter("ArgumentList", parameters.Values.ToArray()); } - bool hadErrors = false; - string errorList = string.Empty; - _logger.LogTrace($"Script block:\n{commandName}"); + _logger.LogTrace($"Executing script block:\n{scriptBlock}"); var results = PS.Invoke(); - CheckErrors(); + + if (PS.HadErrors) + { + string errorMessages = string.Join("; ", PS.Streams.Error.Select(e => e.ToString())); + _logger.LogError($"{errorMessages}"); + throw new Exception($"PowerShell execution errors: {errorMessages}"); + } return results; } catch (Exception ex) { - _logger.LogError($"Error while executing script: {ex.Message}"); + _logger.LogError(ex.Message); throw new Exception(ex.Message); } finally @@ -409,6 +519,7 @@ public void Terminate() } } + [Obsolete] public Collection? ExecuteCommand(string scriptBlock, Dictionary parameters = null) { @@ -694,7 +805,26 @@ private string createPrivateKeyFile() if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { _logger.LogTrace($"Changing permissions on Windows temp file: {tmpFile}."); - File.SetAttributes(tmpFile, FileAttributes.ReadOnly); + + // Create a FileInfo object for the file + FileInfo fileInfo = new FileInfo(tmpFile); + + // Get the current access control settings of the file + FileSecurity fileSecurity = fileInfo.GetAccessControl(); + + // Remove existing permissions + fileSecurity.RemoveAccessRuleAll(new FileSystemAccessRule("Everyone", FileSystemRights.FullControl, AccessControlType.Allow)); + + // Grant read permissions to the current user + string currentUser = Environment.UserName; + fileSecurity.AddAccessRule(new FileSystemAccessRule(currentUser, FileSystemRights.Read, AccessControlType.Allow)); + + // Deny all access to others (this is optional, depending on your use case) + fileSecurity.AddAccessRule(new FileSystemAccessRule("Everyone", FileSystemRights.Read, AccessControlType.Deny)); + + // Apply the modified permissions to the file + fileInfo.SetAccessControl(fileSecurity); + //File.SetAttributes(tmpFile, FileAttributes.ReadOnly); } else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertFull.ps1 index 2721b4f..0a54b3a 100644 --- a/IISU/PowerShellScripts/WinCertFull.ps1 +++ b/IISU/PowerShellScripts/WinCertFull.ps1 @@ -144,9 +144,7 @@ function Get-KFIISBoundCertificates { } } - -function Add-KFCertificateToStore -{ +function Add-KFCertificateToStore{ param ( [Parameter(Mandatory = $true)] [string]$Base64Cert, @@ -258,8 +256,7 @@ function Add-KFCertificateToStore } } -function Remove-KFCertificateFromStore -{ +function Remove-KFCertificateFromStore { param ( [string]$Thumbprint, [string]$StorePath, @@ -312,9 +309,106 @@ function Remove-KFCertificateFromStore return $isSuccessful } +function New-KFIISSiteBinding{ + param ( + [Parameter(Mandatory = $True)] $SiteName, # The name of the IIS site + [Parameter(Mandatory = $True)] $IPAddress, # The IP Address for the binding + [Parameter(Mandatory = $True)] $Port, # The port number for the binding + [Parameter(Mandatory = $False)] $Hostname, # Host name for the binding (if any) + [Parameter(Mandatory = $True)] $Protocol, # Protocol (e.g., HTTP, HTTPS) + [Parameter(Mandatory = $True)] $Thumbprint, # Certificate thumbprint for HTTPS bindings + [Parameter(Mandatory = $True)] $StoreName, # Certificate store location (e.g., ""My"" for personal certs) + [Parameter(Mandatory = $True)] $SslFlags # SSL flags (if any) + ) -function New-KFIISSiteBinding -{ + Write-Debug "Attempting to import modules WebAdministration and IISAdministration" + try { + Import-Module WebAdministration -ErrorAction Stop + } + catch { + throw "Failed to load the WebAdministration module. Ensure it is installed and available." + } + + # Check if the IISAdministration module is already loaded + if (-not (Get-Module -Name IISAdministration )) { + try { + # Attempt to import the IISAdministration module + Import-Module IISAdministration -ErrorAction Stop + } + catch { + throw "Failed to load the IISAdministration module. This function requires IIS Develpment and SCripting tools. Please ensure these tools have been installed on the IIS Server." + } + } + + Write-Debug "Finished importing required modules" + + Write-Verbose "INFO: Entered New-KFIISSiteBinding." + Write-Verbose "SiteName: $SiteName" + Write-Verbose "IPAddress: $IPAddress" + Write-Verbose "Port: $Port" + Write-Verbose "HostName: $HostName" + Write-Verbose "Protocol: $Protocol" + Write-Verbose "Thumbprint: $Thumbprint" + Write-Verbose "Store Path: $StoreName" + Write-Verbose "SslFlags: $SslFlags" + + # Retrieve the existing binding information + $myBinding = "${IPAddress}:${Port}:${Hostname}" #*:443:MyHostName1 : *:443:ManualHostName + Write-Verbose "Formatted binding information: $myBinding" + + # Check if the binding exists (NOTE: Bindings always occurr using https) + try { + Write-Verbose "Attempting to get binding information for Site: '$SiteName' with bindings: $myBinding" + $existingBinding = Get-IISSiteBinding -Name $SiteName -Protocol $Protocol + } + catch { + Write-Verbose "Error occurred while attempting to get the bindings for: '$SiteName'" + Write-Verbose $_ + throw $_ + } + + if ($null -ne $existingBinding) { + Write-Verbose "A binding for: $myBinding was found." + + $currentThumbprint = ($existingBinding.CertificateHash | ForEach-Object { $_.ToString("X2") }) -join "" + if ($thumbprint -eq $currentThumbprint) + { + Write-Verbose "The existing binding's thumbprint matches the new thumbprint - skipping." + Write-Information "The thumbprint for this binding is the same as the new one." + Write-Information "Since they are the same thumbprint, thins binding will be skipped." + return + } + Write-Verbose "Current thumbprint: $currentThumbprint" + Write-Verbose "Will remove the existing binding prior to replacing it with new thumbprint." + + # Remove the existing binding + Remove-IISSiteBinding -Name $SiteName -BindingInformation $existingBinding.BindingInformation -Protocol $existingBinding.Protocol -Confirm:$false + Write-Verbose "Removed existing binding: $($existingBinding.BindingInformation)" + }else{ + Write-Verbose "No binding was found for: $myBinding." + } + + # Create the new binding with modified properties + try + { + Write-Verbose "Attempting to add IISSiteBinding" + New-IISSiteBinding -Name $SiteName ` + -BindingInformation $myBinding ` + -Protocol $Protocol ` + -CertificateThumbprint $Thumbprint ` + -CertStoreLocation $StoreName ` + -SslFlag $SslFlags + + Write-Verbose "New Site Binding for '$SiteName' has been created with bindings: $myBinding and thumbprint: $thumbprint." + } + catch { + throw $_ + }; + + return $True +} + +function New-KFIISSiteBindingOLD{ param ( [Parameter(Mandatory = $true)] [string]$Thumbprint, @@ -344,6 +438,7 @@ function New-KFIISSiteBinding Write-Information "Attempting to load WebAdministration" Import-Module WebAdministration -ErrorAction Stop + Write-Information "Web Administration module has been loaded and will be used" try { @@ -384,8 +479,64 @@ function New-KFIISSiteBinding } } -function Remove-KFIISBinding -{ +function Remove-KFIISSiteBinding{ + param ( + [Parameter(Mandatory=$true)] $SiteName, # The name of the IIS website + [Parameter(Mandatory=$true)] $IPAddress, # The IP address of the binding + [Parameter(Mandatory=$true)] $Port, # The port number (e.g., 443 for HTTPS) + [Parameter(Mandatory=$false)] $Hostname # The hostname (empty string for binding without hostname) + ) + + Write-Debug "Attempting to import modules WebAdministration and IISAdministration" + try { + Import-Module WebAdministration -ErrorAction Stop + } + catch { + throw "Failed to load the WebAdministration module. Ensure it is installed and available." + } + + # Check if the IISAdministration module is already loaded + if (-not (Get-Module -Name IISAdministration )) { + try { + # Attempt to import the IISAdministration module + Import-Module IISAdministration -ErrorAction Stop + } + catch { + throw "Failed to load the IISAdministration module. This function requires IIS Develpment and SCripting tools. Please ensure these tools have been installed on the IIS Server." + } + } + + Write-Debug "Finished importing required modules" + + try { + # Construct the binding information string correctly + $bindingInfo = if ($HostName) { "$IPAddress`:$Port`:$HostName" } else { "$IPAddress`:$Port`:" } + Write-Verbose "Checking for existing binding: $bindingInfo" + + # Get the existing binding based on the constructed binding information + $bindings = Get-IISSiteBinding -Name $SiteName -Protocol "https" | Where-Object { $_.BindingInformation -eq $bindingInfo } + + if ($bindings) { + Write-Verbose "Found binding: $bindingInfo for site: '$SiteName'. Removing..." + + # Remove the binding + Remove-IISSiteBinding -Name $SiteName -BindingInformation $bindingInfo -Protocol "https" -Confirm:$false + + Write-Verbose "Successfully removed binding: $bindingInfo" + return $true + }else{ + Write-Verbose "No binding was found for: $bindingInfo." + return $false + } + + } catch { + Write-Error "An error occurred while attempting to remove bindings for site: $SiteName" + Write-Error $_ + throw $_ + } +} + +function Remove-KFIISBindingOLD{ param ( [Parameter(Mandatory=$true)] [string]$SiteName, # The name of the IIS website @@ -508,9 +659,82 @@ function GET-KFSQLInventory { return $myBoundCerts | ConvertTo-Json } +function Set-SQLCertificateAclOLD { + param ( + [Parameter(Mandatory = $true)] + [string]$Thumbprint, + + [Parameter(Mandatory = $true)] + [string]$SqlServiceUser + ) -function GET-KFSQLInventoryOLD -{ + Write-Information "Entered Set-SQLCertificateAcl" + + # Get the certificate from the LocalMachine\My store + try { + $certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } + } catch { + Write-Error "Error retrieving certificate: $_" + return + } + + if (-not $certificate) { + Write-Error "Certificate with thumbprint $Thumbprint not found in LocalMachine\My store." + return + } + + Write-Information "Obtained the certificate" + + # Retrieve the private key information + try { + if (-not $certificate.HasPrivateKey) { + throw "Certificate does not have a private key." + } + + # Use new method to retrieve the private key container name if needed + $privKey = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + if (-not $privKey) { + throw "Failed to retrieve private key container name." + } + } catch { + Write-Error "Error retrieving private key: $_" + return + } + + Write-Information "Private Key: '$privKey'" + + # Determine the key path + $keyPath = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\" + $privKeyPath = "$keyPath$privKey" + + if (-not (Test-Path $privKeyPath)) { + Write-Error "Private key file does not exist at path: $privKeyPath" + return + } + + Write-Information "Private Key Path is: $privKeyPath" + + # Retrieve the current ACL for the private key + try { + $Acl = Get-Acl -Path $privKeyPath + $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($SqlServiceUser, "Read", "Allow") + + # Add the new access rule + Write-Information "Attempting to add new Access Rule" + $Acl.SetAccessRule($Ar) + Write-Information "Access Rule has been added" + + # Set the new ACL on the private key file + Write-Information "Attaching the ACL on the private key file" + Set-Acl -Path $privKeyPath -AclObject $Acl + + Write-Output "ACL updated successfully for the private key." + } catch { + Write-Error "Error updating ACL: $_" + } +} + +function GET-KFSQLInventoryOLD{ # Get all SQL Server instances $sqlInstances = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server").InstalledInstances Write-Information "There are $sqlInstances.Count instances that will be checked for certificates." @@ -563,9 +787,7 @@ function GET-KFSQLInventoryOLD } } - - -function Set-SQLCertificateAcl { +function Set-SQLCertificateAclOLD { param ( [Parameter(Mandatory = $true)] [string]$Thumbprint, @@ -607,47 +829,55 @@ function Set-SQLCertificateAcl { Write-Output "ACL updated successfully for the private key." } -function Bind-CertificateToSqlInstance { +function Bind-CertificateToSqlInstanceOLD { param ( [Parameter(Mandatory = $true)] [string]$Thumbprint, # Thumbprint of the certificate to bind [Parameter(Mandatory = $true)] [string]$SqlInstanceName, # Name of the SQL Server instance (e.g., 'MSSQLSERVER' or 'SQLInstanceName') [string]$StoreName = "My", # Certificate store name (default is 'My') - [ValidateSet("LocalMachine", "CurrentUser")] - [string]$StoreLocation = "LocalMachine", # Store location (default is 'LocalMachine') [Parameter(Mandatory = $false)] [switch]$RestartService # Optional, restart sql instance if set to true ) + Write-Information "Starting Bind-CertificateToSqlInstance for SQL Instance: $SqlInstanceName" + $store = $null try { - Write-Information "Entered Bind-CertificateToSqlInstance" - # Open the certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::$StoreLocation) + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::"LocalMachine") $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) # Find the certificate by thumbprint $certificate = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } - Write-Information "Obtained Certificate using thumbprint: $Thumbprint" - - if (-not $certificate) { - throw "Certificate with thumbprint $Thumbprint not found in store $StoreLocation\$StoreName." + if (-not $certificate){ + throw "Certificate with thumbprint $Thumbprint not found in store LocalMachine\$StoreName" } + Write-Information "Found Certificate using thumbprint: $Thumbprint" # Get the SQL Full Instance Name $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SqlInstanceName + if (-not $fullInstanceName) { + throw "Count not retrieve the SQL Full Instance Name for $SqlInstanceName." + } + Write-Information "Obtained SQL Full Instance Name: $fullInstanceName" + #Determine SQL Service name and user $SQLServiceName = Get-SQLServiceName $fullInstanceName - - # Update ACL for the private key, giving the SQL service user read access $SQLServiceUser = Get-SQLServiceUser $SQLServiceName + if (-not $SQLServiceName -or -not $SQLServiceUser) { + throw "Failed to determine SQL Server service details." + } + + Write-Information "SQL Service Name: $SQLServiceName" + Write-Information "SQL Service User: $SQLServiceUser" + + # Update ACL for private key access Set-SQLCertificateAcl -Thumbprint $Thumbprint -SQLServiceUser $SQLServiceUser Write-Information "Updated ACL For SQL Service User: $SQLServiceUser" - # Get the SQL Server instance registry path + # Locate the SQL Server instance registry path $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\${fullInstanceName}\MSSQLServer\SuperSocketNetLib" if (-not (Test-Path $regPath)) { throw "Could not find registry path for SQL instance: $fullInstanceName." @@ -655,14 +885,7 @@ function Bind-CertificateToSqlInstance { # Set the certificate thumbprint in SQL Server's registry Set-ItemProperty -Path $regPath -Name "Certificate" -Value $Thumbprint - Write-Information "Certificate thumbprint $Thumbprint successfully bound to SQL Server instance $SqlInstanceName." - - # Close the certificate store - $store.Close() - Write-Information "Store Closed" - - # Restart SQL Server for changes to take effect - Write-Information "Checking if restart has been authorized" + Write-Information "Bound certificate to SQL Server instance $SqlInstanceName." if ($RestartService.IsPresent) { Write-Information "Attempting to restart SQL Service Name: $SQLServiceName" @@ -673,14 +896,225 @@ function Bind-CertificateToSqlInstance { } } catch { + Write-Error "SQL Binding error: $_" + } finally { + if ($store) { + # Close the certificate store + $store.Close() + Write-Information "Closed certificate store." + } + } + +} + +function Bind-KFSqlCertificate { + param ( + [string]$SQLInstance, + [string]$RenewalThumbprint, + [string]$NewThumbprint, + [switch]$RestartService = $false + ) + + function Get-SqlCertRegistryLocation($InstanceName) { + return "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$InstanceName\MSSQLServer\SuperSocketNetLib" + } + + $bindingSuccess = $true # Track success/failure + + try { + $SQLInstances = $SQLInstance -split ',' | ForEach-Object { $_.Trim() } + + foreach ($instance in $SQLInstances) { + try { + $fullInstance = Get-ItemPropertyValue -Path "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance -ErrorAction Stop + $regLocation = Get-SqlCertRegistryLocation -InstanceName $fullInstance + + if (-not (Test-Path $regLocation)) { + Write-Error "Registry location not found: $regLocation" + $bindingSuccess = $false + continue + } + Write-Verbose "Instance: $instance" + Write-Verbose "Full Instance: $fullInstance" + Write-Verbose "Registry Location: $regLocation" + Write-Verbose "Current Thumbprint: $currentThumbprint" + + $currentThumbprint = Get-ItemPropertyValue -Path $regLocation -Name "Certificate" -ErrorAction SilentlyContinue + + if ($RenewalThumbprint -and $RenewalThumbprint -contains $currentThumbprint) { + Write-Information "Renewal thumbprint matches for instance: $fullInstance" + $result = Set-KFCertificateBinding -InstanceName $instance -NewThumbprint $NewThumbprint -RestartService:$RestartService + } elseif (-not $RenewalThumbprint) { + Write-Information "No renewal thumbprint provided. Binding certificate to instance: $fullInstance" + $result = Set-KFCertificateBinding -InstanceName $instance -NewThumbprint $NewThumbprint -RestartService:$RestartService + } + + if (-not $result) { + Write-Error "Failed to bind certificate for instance: $instance" + $bindingSuccess = $false + } + } + catch { + Write-Error "Error processing instance '$instance': $($_.Exception.Message)" + $bindingSuccess = $false + } + } + } + catch { + Write-Error "An unexpected error occurred: $($_.Exception.Message)" + return $false + } + + return $bindingSuccess +} + +function Set-KFCertificateBinding { + param ( + [string]$InstanceName, + [string]$NewThumbprint, + [switch]$RestartService + ) + + Write-Information "Binding certificate with thumbprint $NewThumbprint to instance $InstanceName..." + + try { + # Get the full SQL instance name from the registry + $fullInstance = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $InstanceName -ErrorAction Stop + $RegistryPath = Get-SqlCertRegistryLocation -InstanceName $fullInstance + + Write-Verbose "Full instance: $fullInstance" + Write-Verbose "Registry Path: $RegistryPath" + + # Attempt to update the registry + try { + Set-ItemProperty -Path $RegistryPath -Name "Certificate" -Value $NewThumbprint -ErrorAction Stop + Write-Information "Updated registry for instance $InstanceName with new thumbprint." + } + catch { + Write-Error "Failed to update registry at {$RegistryPath}: $_" + throw $_ # Rethrow the error to ensure it's caught at a higher level + } + + # Retrieve SQL Server service user + $serviceName = Get-SqlServiceName -InstanceName $InstanceName + $serviceInfo = Get-CimInstance -ClassName Win32_Service -Filter "Name='$serviceName'" -ErrorAction Stop + $SqlServiceUser = $serviceInfo.StartName + + if (-not $SqlServiceUser) { + throw "Unable to retrieve service account for SQL Server instance: $InstanceName" + } + + Write-Verbose "Service Name: $serviceName" + Write-Verbose "SQL Service User: $SqlServiceUser" + Write-Information "SQL Server service account for ${InstanceName}: $SqlServiceUser" + + # Retrieve the certificate + $Cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object { $_.Thumbprint -eq $NewThumbprint } + if (-not $Cert) { + throw "Certificate with thumbprint $NewThumbprint not found in LocalMachine\My store." + } + + # Retrieve private key path + $privKey = $Cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName + $keyPath = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\" + $privKeyPath = Get-Item "$keyPath\$privKey" -ErrorAction Stop + Write-Information "Private Key Path is: $privKeyPath" + + try { + # Set ACL for the certificate private key + $Acl = Get-Acl -Path $privKeyPath -ErrorAction Stop + $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($SqlServiceUser, "Read", "Allow") + $Acl.SetAccessRule($Ar) + + Set-Acl -Path $privKeyPath.FullName -AclObject $Acl -ErrorAction Stop + Write-Information "Updated ACL for private key at $privKeyPath." + } + catch { + Write-Error "Failed to update ACL on the private key: $_" + throw $_ + } + + # Optionally restart the SQL Server service + if ($RestartService) { + try { + Write-Information "Restarting SQL Server service: $serviceName..." + Restart-Service -Name $serviceName -Force -ErrorAction Stop + Write-Information "SQL Server service restarted successfully." + } + catch { + Write-Error "Failed to restart SQL Server service: $_" + throw $_ + } + } + + Write-Information "Certificate binding completed for instance $InstanceName." + } + catch { Write-Error "An error occurred: $_" + return $false + } + + return $true +} + +function Unbind-KFSqlCertificate { + param ( + [string]$SQLInstanceNames, # Comma-separated list of SQL instances + [switch]$RestartService # Restart SQL Server after unbinding + ) + + $unBindingSuccess = $true # Track success/failure + + try { + + $instances = $SQLInstanceNames -split ',' | ForEach-Object { $_.Trim() } + + foreach ($instance in $instances) { + try { + # Resolve full instance name from registry + $fullInstance = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance + $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstance\MSSQLServer\SuperSocketNetLib" + + # Get current thumbprint + $certificateThumbprint = Get-ItemPropertyValue -Path $regPath -Name "Certificate" -ErrorAction SilentlyContinue + + if ($certificateThumbprint) { + Write-Information "Unbinding certificate from SQL Server instance: $instance (Thumbprint: $certificateThumbprint)" + + # Instead of deleting, set to an empty string to prevent SQL startup issues + Set-ItemProperty -Path $regPath -Name "Certificate" -Value "" + + Write-Information "Successfully unbound certificate from SQL Server instance: $instance." + } else { + Write-Warning "No certificate bound to SQL Server instance: $instance." + } + + # Restart service if required + if ($RestartService) { + $serviceName = Get-SqlServiceName -InstanceName $instance + Write-Information "Restarting SQL Server service: $serviceName..." + Restart-Service -Name $serviceName -Force + Write-Information "SQL Server service restarted successfully." + } + } + catch { + Write-Error "Failed to unbind certificate from instance: $instance. Error: $_" + $unBindingSuccess = $false + } + } + } + catch { + Write-Error "An unexpected error occurred: $($_.Exception.Message)" + return $false } + + return $unBindingSuccess } # Example usage: # Bind-CertificateToSqlInstance -Thumbprint "123ABC456DEF" -SqlInstanceName "MSSQLSERVER" -function UnBind-KFSqlServerCertificate { +function UnBind-KFSqlServerCertificateOLD { param ( [Parameter(Mandatory = $true)] [string]$SqlInstanceName # Name of the SQL Server instance (e.g., 'MSSQLSERVER' or 'SQLInstanceName') @@ -719,8 +1153,18 @@ function UnBind-KFSqlServerCertificate { # Example usage: # Clear-SqlServerCertificate -SqlInstanceName "MSSQLSERVER" -function Get-SQLServiceName -{ +function Get-SqlServiceName { + param ( + [string]$InstanceName + ) + if ($InstanceName -eq "MSSQLSERVER") { + return "MSSQLSERVER" # Default instance + } else { + return "MSSQL`$$InstanceName" # Named instance (escape $ for PowerShell strings) + } +} + +function Get-SQLServiceNameOLD{ param ( [Parameter(Mandatory = $true)] [string]$SQLInstanceName @@ -912,8 +1356,6 @@ function Import-SignedCertificate { } ##### - - # Shared Functions # Function to get SAN (Subject Alternative Names) from a certificate function Get-KFSAN($cert) @@ -983,6 +1425,7 @@ function Get-CertificateCSP } # This function returns a certificate object based upon the store and thumbprint received +# NOT BEING USED function Get-KFCertificateByThumbprint { param ( From ad1d64ddcfe5d53af8a88eef138561a9300fac95 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 12 Feb 2025 09:26:42 -0800 Subject: [PATCH 19/21] Started removing unnecessary code and files. --- IISU/ClientPSCertStoreReEnrollment.cs | 251 +----------------- IISU/ImplementedStoreTypes/Win/Inventory.cs | 2 + IISU/ImplementedStoreTypes/Win/Management.cs | 2 + .../ImplementedStoreTypes/Win/ReEnrollment.cs | 3 + IISU/PowerShellScripts/GetSQLInstances.ps1 | 14 - .../NonSignedWinCertAddCert.ps1 | 72 ----- .../NonSignedWinCertInventory.ps1 | 39 --- .../ReadBoundCertificates.ps1 | 49 ---- IISU/PowerShellScripts/ReadCertificates.ps1 | 44 --- .../PowerShellScripts/ReadSQLCertificates.ps1 | 18 -- IISU/PowerShellScripts/ReadSQLInstances.ps1 | 14 - IISU/PowerShellScripts/WinCertAddCert.ps1 | 223 ---------------- IISU/PowerShellScripts/WinCertInventory.ps1 | 190 ------------- IISU/WindowsCertStore.csproj | 21 -- 14 files changed, 9 insertions(+), 933 deletions(-) delete mode 100644 IISU/PowerShellScripts/GetSQLInstances.ps1 delete mode 100644 IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 delete mode 100644 IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 delete mode 100644 IISU/PowerShellScripts/ReadBoundCertificates.ps1 delete mode 100644 IISU/PowerShellScripts/ReadCertificates.ps1 delete mode 100644 IISU/PowerShellScripts/ReadSQLCertificates.ps1 delete mode 100644 IISU/PowerShellScripts/ReadSQLInstances.ps1 delete mode 100644 IISU/PowerShellScripts/WinCertAddCert.ps1 delete mode 100644 IISU/PowerShellScripts/WinCertInventory.ps1 diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index 6ab7688..9e105b8 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -14,6 +14,8 @@ // Ignore Spelling: Keyfactor +// 021225 rcp Cleaned up and removed unnecessary code + using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -296,254 +298,5 @@ private string ImportCertificate(byte[] certificateRawData, string storeName) } } - public JobResult PerformReEnrollmentORIG(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment, CertStoreBindingTypeENUM bindingType) - { - bool hasError = false; - - try - { - _logger.MethodEntry(); - var serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - var serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - - // Extract values necessary to create remote PS connection - JobProperties jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - - string protocol = jobProperties.WinRmProtocol; - string port = jobProperties.WinRmPort; - bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; - - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var runSpace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - - _logger.LogTrace("Runspace created"); - runSpace.Open(); - _logger.LogTrace("Runspace opened"); - - PowerShell ps = PowerShell.Create(); - ps.Runspace = runSpace; - - string CSR = string.Empty; - - var subjectText = config.JobProperties["subjectText"]; - var providerName = config.JobProperties["ProviderName"]; - var keyType = config.JobProperties["keyType"]; - var keySize = config.JobProperties["keySize"]; - var SAN = config.JobProperties["SAN"]; - - Collection results; - - // If the provider name is null, default it to the Microsoft CA - providerName ??= "Microsoft Strong Cryptographic Provider"; - - // Create the script file - ps.AddScript("$infFilename = New-TemporaryFile"); - ps.AddScript("$csrFilename = New-TemporaryFile"); - - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item $csrFilename }"); - - ps.AddScript($"Set-Content $infFilename -Value [NewRequest]"); - ps.AddScript($"Add-Content $infFilename -Value 'Subject = \"{subjectText}\"'"); - ps.AddScript($"Add-Content $infFilename -Value 'ProviderName = \"{providerName}\"'"); - ps.AddScript($"Add-Content $infFilename -Value 'MachineKeySet = True'"); - ps.AddScript($"Add-Content $infFilename -Value 'HashAlgorithm = SHA256'"); - ps.AddScript($"Add-Content $infFilename -Value 'KeyAlgorithm = {keyType}'"); - ps.AddScript($"Add-Content $infFilename -Value 'KeyLength={keySize}'"); - ps.AddScript($"Add-Content $infFilename -Value 'KeySpec = 0'"); - - if (SAN != null) - { - ps.AddScript($"Add-Content $infFilename -Value '[Extensions]'"); - ps.AddScript(@"Add-Content $infFilename -Value '2.5.29.17 = ""{text}""'"); - - foreach (string s in SAN.ToString().Split("&")) - { - ps.AddScript($"Add-Content $infFilename -Value '_continue_ = \"{s + "&"}\"'"); - } - } - - try - { - // Get INF file for debugging - ps.AddScript("$name = $infFilename.FullName"); - ps.AddScript("$name"); - results = ps.Invoke(); - - string fname = results[0].ToString(); - string infContent = File.ReadAllText(fname); - - _logger.LogDebug($"Contents of {fname}:"); - _logger.LogDebug(infContent); - } - catch (Exception) - { - } - - // Execute the -new command - ps.AddScript($"certreq -new -q $infFilename $csrFilename"); - _logger.LogDebug($"Subject Text: {subjectText}"); - _logger.LogDebug($"SAN: {SAN}"); - _logger.LogDebug($"Provider Name: {providerName}"); - _logger.LogDebug($"Key Type: {keyType}"); - _logger.LogDebug($"Key Size: {keySize}"); - _logger.LogTrace("Attempting to create the CSR by Invoking the script."); - - results = ps.Invoke(); - _logger.LogTrace("Completed the attempt in creating the CSR."); - - ps.Commands.Clear(); - - try - { - ps.AddScript($"$CSR = Get-Content $csrFilename -Raw"); - _logger.LogTrace("Attempting to get the contents of the CSR file."); - results = ps.Invoke(); - _logger.LogTrace("Finished getting the CSR Contents."); - } - catch (Exception) - { - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - - hasError = true; - - throw new CertificateStoreException($"Error creating CSR File. {psError}"); - } - finally - { - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - _logger.LogTrace("Attempt to delete the temporary files."); - results = ps.Invoke(); - - if (hasError) runSpace.Close(); - } - - // Get the byte array - var RawContent = runSpace.SessionStateProxy.GetVariable("CSR"); - - // Sign CSR in Keyfactor - _logger.LogTrace("Get the signed CSR from KF."); - X509Certificate2 myCert = submitReenrollment.Invoke(RawContent.ToString()); - - if (myCert != null) - { - // Get the cert data into string format - string csrData = Convert.ToBase64String(myCert.RawData, Base64FormattingOptions.InsertLineBreaks); - - _logger.LogTrace("Creating the text version of the certificate."); - - // Write out the cert file - StringBuilder sb = new StringBuilder(); - sb.AppendLine("-----BEGIN CERTIFICATE-----"); - sb.AppendLine(csrData); - sb.AppendLine("-----END CERTIFICATE-----"); - - ps.AddScript("$cerFilename = New-TemporaryFile"); - ps.AddScript($"Set-Content $cerFilename '{sb}'"); - - results = ps.Invoke(); - ps.Commands.Clear(); - - // Accept the signed cert - _logger.LogTrace("Attempting to accept or bind the certificate to the HSM."); - - ps.AddScript($"Set-Location -Path Cert:\\localmachine\\'{config.CertificateStoreDetails.StorePath}'"); - ps.AddScript($"Import-Certificate -Filepath $cerFilename"); - ps.Invoke(); - _logger.LogTrace("Successfully bound the certificate."); - - ps.Commands.Clear(); - - // Delete the temp files - ps.AddScript("if (Test-Path $infFilename) { Remove-Item -Path $infFilename }"); - ps.AddScript("if (Test-Path $csrFilename) { Remove-Item -Path $csrFilename }"); - ps.AddScript("if (Test-Path $cerFilename) { Remove-Item -Path $cerFilename }"); - _logger.LogTrace("Removing temporary files."); - results = ps.Invoke(); - - ps.Commands.Clear(); - runSpace.Close(); - - // Default results - JobResult result = new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; - - // Do specific bindings - switch (bindingType) - { - case CertStoreBindingTypeENUM.WinIIS: - // Bind the certificate to IIS - ClientPSIIManager iisManager = new ClientPSIIManager(config, serverUserName, serverPassword); - result = iisManager.BindCertificate(myCert); - // Provide logging information - if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the IIS Server."); } - else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the IIS Server. Check the logs for more information."); } - break; - - case CertStoreBindingTypeENUM.WinSQL: - - // Bind to SQL Server - ClientPsSqlManager sqlManager = new ClientPsSqlManager(config, serverUserName, serverPassword); - result = sqlManager.BindCertificates("", myCert); - - // Provide logging information - if (result.Result == OrchestratorJobStatusJobResult.Success) { _logger.LogInformation("Certificate was successfully bound to the SQL Server."); } - else { _logger.LogInformation("There was an issue while attempting to bind the certificate to the SQL Server. Check the logs for more information."); } - break; - - } - - ps.Commands.Clear(); - runSpace.Close(); - - return result; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = "The ReEnrollment job was unable to sign the CSR. Please check the formatting of the SAN and other ReEnrollment properties." - }; - } - - } - catch (PSRemotingTransportException psEx) - { - var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with a PowerShell Transport Exception: {psEx.Message}"; - _logger.LogError(failureMessage + LogHandler.FlattenException(psEx)); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage - }; - - } - catch (Exception ex) - { - var failureMessage = $"ReEnrollment job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; - _logger.LogWarning(failureMessage); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage - }; - } - } } } diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index 1cbfd77..75a1aca 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// 021225 rcp Updated and cleaned up unnecessary code + using System; using System.Collections.Generic; using System.Collections.ObjectModel; diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index 0688ea5..a132813 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -14,6 +14,8 @@ // Ignore Spelling: Keyfactor +// 021225 rcp Cleaned up and removed unnecessary code + using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; diff --git a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs index 8f0c4de..50e9fa5 100644 --- a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs @@ -11,6 +11,9 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +// 021225 rcp Cleaned up and removed unnecessary code + using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; diff --git a/IISU/PowerShellScripts/GetSQLInstances.ps1 b/IISU/PowerShellScripts/GetSQLInstances.ps1 deleted file mode 100644 index 79693d9..0000000 --- a/IISU/PowerShellScripts/GetSQLInstances.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -# Summary: -# Gets a list of SQL instances defined in the Windows Registry - -# Setting Preference Variables can have an impact on how the PowerShell Script runs -# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend -# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables - -$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection -$ErrorActionPreference = 'Continue' # Default = 'Continue' -$InformationPreference = 'Continue' # Default = 'SilentlyContinue' -$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection -$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection - -Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server" InstalledInstances \ No newline at end of file diff --git a/IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 b/IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 deleted file mode 100644 index eca30bb..0000000 --- a/IISU/PowerShellScripts/NonSignedWinCertAddCert.ps1 +++ /dev/null @@ -1,72 +0,0 @@ -param ( - [Parameter(Mandatory = $true)] - [string]$Base64Cert, - - [Parameter(Mandatory = $false)] - [string]$PrivateKeyPassword, - - [Parameter(Mandatory = $true)] - [string]$StorePath, - - [Parameter(Mandatory = $false)] - [string]$CryptoServiceProvider -) - -function Add-CertificateToStore { - param ( - [string]$Base64Cert, - [string]$PrivateKeyPassword, - [string]$StorePath, - [string]$CryptoServiceProvider - ) - - try { - # Convert Base64 string to byte array - $certBytes = [Convert]::FromBase64String($Base64Cert) - - # Create a temporary file to store the certificate - $tempCertPath = [System.IO.Path]::GetTempFileName() - [System.IO.File]::WriteAllBytes($tempCertPath, $certBytes) - - if ($CryptoServiceProvider) { - # Create a temporary PFX file - $tempPfxPath = [System.IO.Path]::ChangeExtension($tempCertPath, ".pfx") - $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } - $pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) | Set-Content -Encoding Byte -Path $tempPfxPath - - # Use certutil to import the PFX with the specified CSP - $importCmd = "certutil -f -importpfx $tempPfxPath -p $pfxPassword -csp `"$CryptoServiceProvider`"" - Invoke-Expression $importCmd - - # Clean up the temporary PFX file - Remove-Item $tempPfxPath - } else { - # Load the certificate from the temporary file - if ($PrivateKeyPassword) { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $PrivateKeyPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - } else { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath) - } - - # Open the certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StorePath, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) - - # Add the certificate to the store - $store.Add($cert) - - # Close the store - $store.Close() - } - - # Clean up the temporary file - Remove-Item $tempCertPath - - Write-Host "Certificate added successfully to $StorePath." - } catch { - Write-Error "An error occurred: $_" - } -} - -Add-CertificateToStore -Base64Cert $Base64Cert -PrivateKeyPassword $PrivateKeyPassword -StorePath $StorePath -CryptoServiceProvider $CryptoServiceProvider diff --git a/IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 b/IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 deleted file mode 100644 index 2417ed7..0000000 --- a/IISU/PowerShellScripts/NonSignedWinCertInventory.ps1 +++ /dev/null @@ -1,39 +0,0 @@ -param ( - [string]$StoreName = "My" # Default store name is "My" (Personal) -) - -# Function to get SAN (Subject Alternative Names) from a certificate -function Get-SAN($cert) { - $san = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } - if ($san) { - return ($san.Format(1) -split ", " -join "; ") - } - return $null -} - -# Get all certificates from the specified store -$certificates = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" - -# Initialize an array to store the results -$certInfoList = @() - -foreach ($cert in $certificates) { - # Create a custom object to store the certificate information - $certInfo = [PSCustomObject]@{ - StoreName = $StoreName - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - SAN = Get-SAN $cert - ProviderName = $cert.ProviderName - Base64Data = [System.Convert]::ToBase64String($cert.RawData) - } - - # Add the certificate information to the array - $certInfoList += $certInfo -} - -# Output the results -$certInfoList | ConvertTo-Json diff --git a/IISU/PowerShellScripts/ReadBoundCertificates.ps1 b/IISU/PowerShellScripts/ReadBoundCertificates.ps1 deleted file mode 100644 index 36ee3ea..0000000 --- a/IISU/PowerShellScripts/ReadBoundCertificates.ps1 +++ /dev/null @@ -1,49 +0,0 @@ -# Summary: -# Args: $isRemote - flag to indicate whether the connection is being executed on a remote machine. -# Sets additional modules based on this flag. - -param( - [bool]$isRemote -) - -# Example of setting $isRemote directly for testing purposes -# In actual use, you would pass this parameter when calling the script -#$isRemote = $true - -# Setting Preference Variables can have an impact on how the PowerShell Script runs -# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend -# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables - -$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection -$ErrorActionPreference = 'Continue' # Default = 'Continue' -$InformationPreference = 'Continue' # Default = 'SilentlyContinue' -$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection -$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection - -# Import modules based on the $isRemote flag -if ($isRemote) -{ - Write-Information "Running in remote mode. Importing remote modules." - Import-Module -Name 'WebAdministration' -} -else -{ - Write-Information "Running in local mode. Setting execution policy and importing local modules." - Set-ExecutionPolicy RemoteSigned -Scope Process -Force - Import-Module WebAdministration -} - -# Iterate through websites and their bindings -Foreach ($Site in Get-Website) -{ - Foreach ($Bind in $Site.bindings.collection) - { - [pscustomobject]@{ - Name = $Site.name - Protocol = $Bind.Protocol - Bindings = $Bind.BindingInformation - Thumbprint = $Bind.certificateHash - SniFlg = $Bind.sslFlags - } - } -} diff --git a/IISU/PowerShellScripts/ReadCertificates.ps1 b/IISU/PowerShellScripts/ReadCertificates.ps1 deleted file mode 100644 index 8cc8a69..0000000 --- a/IISU/PowerShellScripts/ReadCertificates.ps1 +++ /dev/null @@ -1,44 +0,0 @@ -# Summary: This script gets the certificates from the LocalMachine for the given store path. -# Args: $storePath - Contains the cert path to read the certificates -# -# Return Value: $certs - contains the specific cert details for each certificate in the given cert path - -param( - [string]$storePath -) - -# Setting Preference Variables can have an impact on how the PowerShell Script runs -# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend -# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables -$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection -$ErrorActionPreference = 'Continue' # Default = 'Continue' -$InformationPreference = 'Continue' # Default = 'SilentlyContinue' -$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection -$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection -# - -Write-Information "WARN: Store path passed from extension: $storePath" - -$certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store($storePath,'LocalMachine') -$certStore.Open('ReadOnly') - -$certs = $certStore.Certificates - -$certStore.Close() -$certStore.Dispose() - -$certs | ForEach-Object { - $certDetails = @{ - Subject = $_.Subject - Thumbprint = $_.Thumbprint - HasPrivateKey = $_.HasPrivateKey - RawData = $_.RawData - san = $_.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } | ForEach-Object { $_.Format($false) } - } - - if ($_.HasPrivateKey) { - $certDetails.CSP = $_.PrivateKey.CspKeyContainerInfo.ProviderName - } - - New-Object PSObject -Property $certDetails -} \ No newline at end of file diff --git a/IISU/PowerShellScripts/ReadSQLCertificates.ps1 b/IISU/PowerShellScripts/ReadSQLCertificates.ps1 deleted file mode 100644 index dadc004..0000000 --- a/IISU/PowerShellScripts/ReadSQLCertificates.ps1 +++ /dev/null @@ -1,18 +0,0 @@ -# Setting Preference Variables can have an impact on how the PowerShell Script runs -# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend -# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables - - -$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection -$ErrorActionPreference = 'Continue' # Default = 'Continue' -$InformationPreference = 'Continue' # Default = 'SilentlyContinue' -$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection -$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection - -# - -param( - [string]$regLocation -) - -Get-ItemPropertyValue $regLocation -Name Certificate \ No newline at end of file diff --git a/IISU/PowerShellScripts/ReadSQLInstances.ps1 b/IISU/PowerShellScripts/ReadSQLInstances.ps1 deleted file mode 100644 index 8820dac..0000000 --- a/IISU/PowerShellScripts/ReadSQLInstances.ps1 +++ /dev/null @@ -1,14 +0,0 @@ -# Setting Preference Variables can have an impact on how the PowerShell Script runs -# Available Preference Variables include: Break, Continue, Ignore, Inquire, SilentlyContinue, Stop, Suspend -# NOTE: Please refer to the PowerShell documentation to learn more about setting and using Preference Variables - - -$DebugPreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Debug messages do not get returned over a Remote Connection -$ErrorActionPreference = 'Continue' # Default = 'Continue' -$InformationPreference = 'Continue' # Default = 'SilentlyContinue' -$VerbosePreference = 'SilentlyContinue' # Default = 'SilentlyContinue' NOTE: Verbose messages do not get returned over a Remote Connection -$WarningPreference = 'Continue' # Default = 'Continue' NOTE: Warning messages do not get returned over a Remote Connection - -# - -Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server" .InstalledInstances" \ No newline at end of file diff --git a/IISU/PowerShellScripts/WinCertAddCert.ps1 b/IISU/PowerShellScripts/WinCertAddCert.ps1 deleted file mode 100644 index 86657c4..0000000 --- a/IISU/PowerShellScripts/WinCertAddCert.ps1 +++ /dev/null @@ -1,223 +0,0 @@ -param ( - [Parameter(Mandatory = $true)] - [string]$Base64Cert, - - [Parameter(Mandatory = $false)] - [string]$PrivateKeyPassword, - - [Parameter(Mandatory = $true)] - [string]$StorePath, - - [Parameter(Mandatory = $false)] - [string]$CryptoServiceProvider -) - -function Add-CertificateToStore { - param ( - [string]$Base64Cert, - [string]$PrivateKeyPassword, - [string]$StorePath, - [string]$CryptoServiceProvider - ) - - try { - # Convert Base64 string to byte array - $certBytes = [Convert]::FromBase64String($Base64Cert) - - # Create a temporary file to store the certificate - $tempCertPath = [System.IO.Path]::GetTempFileName() - [System.IO.File]::WriteAllBytes($tempCertPath, $certBytes) - - if ($CryptoServiceProvider) { - # Create a temporary PFX file - $tempPfxPath = [System.IO.Path]::ChangeExtension($tempCertPath, ".pfx") - $pfxPassword = if ($PrivateKeyPassword) { $PrivateKeyPassword } else { "" } - $pfxCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $pfxPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - $pfxCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx, $pfxPassword) | Set-Content -Encoding Byte -Path $tempPfxPath - - # Use certutil to import the PFX with the specified CSP - $importCmd = "certutil -f -importpfx $tempPfxPath -p $pfxPassword -csp `"$CryptoServiceProvider`"" - Invoke-Expression $importCmd - - # Clean up the temporary PFX file - Remove-Item $tempPfxPath - } else { - # Load the certificate from the temporary file - if ($PrivateKeyPassword) { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath, $PrivateKeyPassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable) - } else { - $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($tempCertPath) - } - - # Open the certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StorePath, [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine) - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadWrite) - - # Add the certificate to the store - $store.Add($cert) - - # Close the store - $store.Close() - } - - # Clean up the temporary file - Remove-Item $tempCertPath - - Write-Host "Certificate added successfully to $StorePath." - } catch { - Write-Error "An error occurred: $_" - } -} - -Add-CertificateToStore -Base64Cert $Base64Cert -PrivateKeyPassword $PrivateKeyPassword -StorePath $StorePath -CryptoServiceProvider $CryptoServiceProvider - -# SIG # Begin signature block -# MIIbnQYJKoZIhvcNAQcCoIIbjjCCG4oCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB -# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR -# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUY71rrjro6KAhSWXsIyR37p9d -# 6tagghYTMIIDBjCCAe6gAwIBAgIQZS3tjlHZZI1KI1qdjoozTzANBgkqhkiG9w0B -# AQsFADAbMRkwFwYDVQQDDBBBVEEgQXV0aGVudGljb2RlMB4XDTI0MDcyOTE1MzQw -# MVoXDTI1MDcyOTE1NTQwMVowGzEZMBcGA1UEAwwQQVRBIEF1dGhlbnRpY29kZTCC -# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1pkoUO+MhwhPi5SQZpXOT9 -# vrKdLo5CofebPe8S0nLi1E5/ZmCOiE3nek2s9gK1fODhXiB4SbeEFaGIJOJJzHtJ -# Pc+6o0iKFpBbsmP+XrzcNHq7+ymtlMmPtfyKWRd7BsNXXfOsT1NnSsf7M3u54l9O -# suJkzq6bEur0iftK6X0T7IneQuuN91r4Nx0UM4AiakwXKsyN7axQzmZZWfSx4M1G -# NvIbUF6qt8cp5KPXJUIDsDhIyE93EZtxJ2A2sXotrl3tcSHis0IVCOSSj+JQtX+J -# 8AugzNGTsWL/V4Cnz5Yw4WzmmtPe0PGzlY7UHv11a9meFwl//ZnOZaqajmpJD90C -# AwEAAaNGMEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0G -# A1UdDgQWBBQh5V3P5qDcFYqqWfrqmABgjBnWiDANBgkqhkiG9w0BAQsFAAOCAQEA -# ibxFp6+qii/Cze5Au+444NZc0Kp5ryuYlMnSS9raRzJOXnl1pv/s71MM8wmaHyAu -# exZeexeK//8ABSn/HXMoR4hqq/WNtjmhgmDkpTjMos3byAdrPphCA7Rx9XZquUjQ -# glvEzaje2M0f6gSz1t3/Op2pU5KuzxB4998acdZuQuezwI9/tLgn8srC0kLQS1CM -# 4gw0uR7JmH6H4yYgEEG9JaRJbPy9pXlQij2DWMZ8cfpXm0ju3vfM0F/QFeAkvAO2 -# ZqBbD+/btr7D1qDrCuNpzzFdOeTVOaA4O1175Dz6VBeT15nqCzWLXKTAs4bP315e -# 0pDus7eJRvKLkAUrhpFnEzCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFow -# DQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 -# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNl -# cnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIz -# NTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG -# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3Rl -# ZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2je -# u+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bG -# l20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBE -# EC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/N -# rDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A -# 2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8 -# IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfB -# aYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaa -# RBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZi -# fvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXe -# eqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g -# /KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB -# /wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQY -# MBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEF -# BQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBD -# BggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 -# QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3Js -# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1Ud -# IAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22 -# Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih -# 9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYD -# E3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c -# 2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88n -# q2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5 -# lDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAw -# YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ -# d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290 -# IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMC -# VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBU -# cnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJ -# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh -# 1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+Feo -# An39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1 -# decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxnd -# X7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6 -# Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPj -# Q2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlREr -# WHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JM -# q++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh -# 3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8j -# u2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnS -# DmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud -# DgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzf -# Lmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgw -# dwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy -# dC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E -# aWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6 -# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAG -# A1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOC -# AgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp -# /GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40B -# IiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2d -# fNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibB -# t94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7 -# T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZA -# myEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdB -# eHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnK -# cPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/ -# pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yY -# lvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbCMIIEqqADAgEC -# AhAFRK/zlJ0IOaa/2z9f5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVT -# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 -# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0 -# MDAwMDAwWhcNMzQxMDEzMjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO -# RGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIz -# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DP -# n9fl0uddoQ4J3C9Io5d6OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX -# 65RQjxwg6seaOy+WZuNp52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSk -# ghraarrYO8pd3hkYhftF6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2 -# SC1eRXWWdf7dEKEbg8G45lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG -# 7rSkIWRw69XloNpjsy7pBe6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k -# /XtzPjLuUjT71Lvr1KAsNJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP -# 4FhB+9ixLOFRr7StFQYU6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7 -# Qv1zfe7dCv95NBB+plwKWEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOupr -# AbD3+yqG7HtSOKmYCaFxsmxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBx -# PZySkwS0aXAnDU+3tTbRyV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E4 -# 2FEHypS34lCh8zrTioPLQHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAM -# BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcw -# CAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91 -# jGogj57IbzAdBgNVHQ4EFgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMw -# UTBPoE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3Rl -# ZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEE -# gYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggr -# BgEFBQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 -# c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0B -# AQsFAAOCAgEAgRrW3qCptZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn -# 48XtJoKKcS8Y3U623mzX4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S -# 2sJAOJ9dyKAuJXglnSoFeoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedt -# QVyMadG5K8TGe8+c+njikxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6 -# ubzBaRm6zxbygzc0brBBJt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXh -# RsUo063nQwBw3syYnhmJA+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC -# 1o/jF5HRqsBV44a/rCcsQdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH -# 5U81PAC9vpwqbHkB3NpE5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLK -# O+uagjVXKBbLafIymrLS2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc -# 9bahuEMs305MfR5ocMB3CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdE -# Qcm4RtNsMnxYL2dHZeUbc7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYoxggT0MIIE -# 8AIBATAvMBsxGTAXBgNVBAMMEEFUQSBBdXRoZW50aWNvZGUCEGUt7Y5R2WSNSiNa -# nY6KM08wCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJ -# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB -# gjcCARUwIwYJKoZIhvcNAQkEMRYEFOk4p/p5IbDHLUVRn1QQ09uVofSNMA0GCSqG -# SIb3DQEBAQUABIIBAASDMEd/7ePVCI/F57CXse3NAYCkeNapzhZdVIchuyd+V2H0 -# st7P5ut6KHqTHQGqAHH5Lmm60EAJ8n+SlIUNWvZq8BVh8N79gLoVyy8cGUXX8oZ2 -# 1wTIImdXlHqn8W89xP/5lqx5ZyW3eaKt9ov3TdfSAPj4F0MmOW50fmln1xr6eg/K -# QRmVp3MjH0qn4ssWRnuQubQ+vugxY071NW8qp3fT6rp/r1QWKOo8g7eP3i6I5S+D -# h0mr2c6pQWd5dB+UaESk4kAElKXywRvWJRLjh4Sbp5QQ4Rje6PtOUVMBRNmEk9aP -# QzXwpqDNixw2ns2gMBXzfjtuyd3DOAy8S8eloFehggMgMIIDHAYJKoZIhvcNAQkG -# MYIDDTCCAwkCAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs -# IEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEy -# NTYgVGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQC -# AQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcN -# MjQwNzI5MTU1MjIxWjAvBgkqhkiG9w0BCQQxIgQgsbb8WIi6yJQ7OSi7v/5CaS/z -# ByfSsM13cXsO5LMO0q4wDQYJKoZIhvcNAQEBBQAEggIAWo2/oqH+2qOYkFCR2kOj -# RCgQ6NRDPUv+4PP6uJKxwQwp8IxiEEsmTFojrcKntDVxQKPqWfEo3eoJVfeQNLm0 -# VvxLvbj5UXNMNbWFCNtQhBcp4Tgzpb0BXiRwEHtnoskt59YkKA3g5TOO9Wl7ADtf -# fkjoRBzCzdMRRDzT4LByMEybK+JpEOD1RoNAzHp2LY3gnj1d9S530MwXGqawfTKB -# jWkWSPCy+7blCc4B5LEL+lqZStUqzE4C06RguSfEub7oumdgxoPQH8hgofEt5MSm -# xDUHyI0+4wWIroTgU6UCZAFWy5+5C0f0BLOuDXhuu5PLR/eHv/4A89z9ckvDcFuc -# 0NugIn8Q+XVRlQAouKcxZxUj0DUjJUEbe9EavFnxKx7Uu98h8ZX9XJ0BusouHHOJ -# YkUFo9mK16Rn7PATu+V4NyC0fRX9Oc64sAQGlrGIJ0IKdN6hkgYED8WfxxZJCF/w -# /yoBRlSvaQu7KEyMvFwOg20PBST1zOa84B9P52dHXex/HHVRU7+wwcO9dTJGDcCN -# t4FprYI/ahlGEctOj6Sh+bG7RkZNtCB1LJ2y0v96u2fCP7ElBGQYecnPGrUhzwX3 -# OizLTlzKnlI4jkP2vlKKpDLGuJObfUxQ4pVOtzIa+fc4dMETvzcOkWaTLECevR1D -# swmRbNMufhgJaWDKtyvnjBc= -# SIG # End signature block diff --git a/IISU/PowerShellScripts/WinCertInventory.ps1 b/IISU/PowerShellScripts/WinCertInventory.ps1 deleted file mode 100644 index b8d2717..0000000 --- a/IISU/PowerShellScripts/WinCertInventory.ps1 +++ /dev/null @@ -1,190 +0,0 @@ -param ( - [string]$StoreName = "My" # Default store name is "My" (Personal) -) - -# Function to get SAN (Subject Alternative Names) from a certificate -function Get-SAN($cert) { - $san = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } - if ($san) { - return ($san.Format(1) -split ", " -join "; ") - } - return $null -} - -# Get all certificates from the specified store -$certificates = Get-ChildItem -Path "Cert:\LocalMachine\$StoreName" - -# Initialize an array to store the results -$certInfoList = @() - -foreach ($cert in $certificates) { - # Create a custom object to store the certificate information - $certInfo = [PSCustomObject]@{ - StoreName = $StoreName - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - SAN = Get-SAN $cert - ProviderName = $cert.ProviderName - Base64Data = [System.Convert]::ToBase64String($cert.RawData) - } - - # Add the certificate information to the array - $certInfoList += $certInfo -} - -# Output the results -$certInfoList | ConvertTo-Json - -# SIG # Begin signature block -# MIIbnQYJKoZIhvcNAQcCoIIbjjCCG4oCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB -# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR -# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUFe5YsftQGF0asr1/sLZTTxMs -# MwagghYTMIIDBjCCAe6gAwIBAgIQZS3tjlHZZI1KI1qdjoozTzANBgkqhkiG9w0B -# AQsFADAbMRkwFwYDVQQDDBBBVEEgQXV0aGVudGljb2RlMB4XDTI0MDcyOTE1MzQw -# MVoXDTI1MDcyOTE1NTQwMVowGzEZMBcGA1UEAwwQQVRBIEF1dGhlbnRpY29kZTCC -# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK1pkoUO+MhwhPi5SQZpXOT9 -# vrKdLo5CofebPe8S0nLi1E5/ZmCOiE3nek2s9gK1fODhXiB4SbeEFaGIJOJJzHtJ -# Pc+6o0iKFpBbsmP+XrzcNHq7+ymtlMmPtfyKWRd7BsNXXfOsT1NnSsf7M3u54l9O -# suJkzq6bEur0iftK6X0T7IneQuuN91r4Nx0UM4AiakwXKsyN7axQzmZZWfSx4M1G -# NvIbUF6qt8cp5KPXJUIDsDhIyE93EZtxJ2A2sXotrl3tcSHis0IVCOSSj+JQtX+J -# 8AugzNGTsWL/V4Cnz5Yw4WzmmtPe0PGzlY7UHv11a9meFwl//ZnOZaqajmpJD90C -# AwEAAaNGMEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0G -# A1UdDgQWBBQh5V3P5qDcFYqqWfrqmABgjBnWiDANBgkqhkiG9w0BAQsFAAOCAQEA -# ibxFp6+qii/Cze5Au+444NZc0Kp5ryuYlMnSS9raRzJOXnl1pv/s71MM8wmaHyAu -# exZeexeK//8ABSn/HXMoR4hqq/WNtjmhgmDkpTjMos3byAdrPphCA7Rx9XZquUjQ -# glvEzaje2M0f6gSz1t3/Op2pU5KuzxB4998acdZuQuezwI9/tLgn8srC0kLQS1CM -# 4gw0uR7JmH6H4yYgEEG9JaRJbPy9pXlQij2DWMZ8cfpXm0ju3vfM0F/QFeAkvAO2 -# ZqBbD+/btr7D1qDrCuNpzzFdOeTVOaA4O1175Dz6VBeT15nqCzWLXKTAs4bP315e -# 0pDus7eJRvKLkAUrhpFnEzCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFow -# DQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 -# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNl -# cnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIz -# NTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG -# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3Rl -# ZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2je -# u+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bG -# l20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBE -# EC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/N -# rDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A -# 2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8 -# IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfB -# aYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaa -# RBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZi -# fvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXe -# eqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g -# /KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB -# /wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQY -# MBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEF -# BQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBD -# BggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 -# QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3Js -# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1Ud -# IAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22 -# Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih -# 9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYD -# E3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c -# 2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88n -# q2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5 -# lDCCBq4wggSWoAMCAQICEAc2N7ckVHzYR6z9KGYqXlswDQYJKoZIhvcNAQELBQAw -# YjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ -# d3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290 -# IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3MDMyMjIzNTk1OVowYzELMAkGA1UEBhMC -# VVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBU -# cnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJ -# KoZIhvcNAQEBBQADggIPADCCAgoCggIBAMaGNQZJs8E9cklRVcclA8TykTepl1Gh -# 1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjzaPp985yJC3+dH54PMx9QEwsmc5Zt+Feo -# An39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3EF3+rGSs+QtxnjupRPfDWVtTnKC3r07G1 -# decfBmWNlCnT2exp39mQh0YAe9tEQYncfGpXevA3eZ9drMvohGS0UvJ2R/dhgxnd -# X7RUCyFobjchu0CsX7LeSn3O9TkSZ+8OpWNs5KbFHc02DVzV5huowWR0QKfAcsW6 -# Th+xtVhNef7Xj3OTrCw54qVI1vCwMROpVymWJy71h6aPTnYVVSZwmCZ/oBpHIEPj -# Q2OAe3VuJyWQmDo4EbP29p7mO1vsgd4iFNmCKseSv6De4z6ic/rnH1pslPJSlREr -# WHRAKKtzQ87fSqEcazjFKfPKqpZzQmiftkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JM -# q++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh -# 3pP+OcD5sjClTNfpmEpYPtMDiP6zj9NeS3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8j -# u2TjY+Cm4T72wnSyPx4JduyrXUZ14mCjWAkBKAAOhFTuzuldyF4wEr1GnrXTdrnS -# DmuZDNIztM2xAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud -# DgQWBBS6FtltTYUvcyl2mi91jGogj57IbzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzf -# Lmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgw -# dwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy -# dC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E -# aWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6 -# Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAG -# A1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOC -# AgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAYLhBNE88wU86/GPvHUF3iSyn7cIoNqilp -# /GnBzx0H6T5gyNgL5Vxb122H+oQgJTQxZ822EpZvxFBMYh0MCIKoFr2pVs8Vc40B -# IiXOlWk/R3f7cnQU1/+rT4osequFzUNf7WC2qk+RZp4snuCKrOX9jLxkJodskr2d -# fNBwCnzvqLx1T7pa96kQsl3p/yhUifDVinF2ZdrM8HKjI/rAJ4JErpknG6skHibB -# t94q6/aesXmZgaNWhqsKRcnfxI2g55j7+6adcq/Ex8HBanHZxhOACcS2n82HhyS7 -# T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJD5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZA -# myEhQNC3EyTN3B14OuSereU0cZLXJmvkOHOrpgFPvT87eK1MrfvElXvtCl8zOYdB -# eHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJGnXUsHicsJttvFXseGYs2uJPU5vIXmVnK -# cPA3v5gA3yAWTyf7YGcWoWa63VXAOimGsJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/ -# pNHzV9m8BPqC3jLfBInwAM1dwvnQI38AC+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yY -# lvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d2zc4GqEr9u3WfPwwggbCMIIEqqADAgEC -# AhAFRK/zlJ0IOaa/2z9f5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVT -# MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1 -# c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0 -# MDAwMDAwWhcNMzQxMDEzMjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMO -# RGlnaUNlcnQsIEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIz -# MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DP -# n9fl0uddoQ4J3C9Io5d6OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX -# 65RQjxwg6seaOy+WZuNp52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSk -# ghraarrYO8pd3hkYhftF6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2 -# SC1eRXWWdf7dEKEbg8G45lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG -# 7rSkIWRw69XloNpjsy7pBe6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k -# /XtzPjLuUjT71Lvr1KAsNJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP -# 4FhB+9ixLOFRr7StFQYU6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7 -# Qv1zfe7dCv95NBB+plwKWEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOupr -# AbD3+yqG7HtSOKmYCaFxsmxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBx -# PZySkwS0aXAnDU+3tTbRyV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E4 -# 2FEHypS34lCh8zrTioPLQHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAM -# BgNVHRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcw -# CAYGZ4EMAQQCMAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91 -# jGogj57IbzAdBgNVHQ4EFgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMw -# UTBPoE2gS4ZJaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3Rl -# ZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEE -# gYMwgYAwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggr -# BgEFBQcwAoZMaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1 -# c3RlZEc0UlNBNDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0B -# AQsFAAOCAgEAgRrW3qCptZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn -# 48XtJoKKcS8Y3U623mzX4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S -# 2sJAOJ9dyKAuJXglnSoFeoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedt -# QVyMadG5K8TGe8+c+njikxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6 -# ubzBaRm6zxbygzc0brBBJt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXh -# RsUo063nQwBw3syYnhmJA+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC -# 1o/jF5HRqsBV44a/rCcsQdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH -# 5U81PAC9vpwqbHkB3NpE5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLK -# O+uagjVXKBbLafIymrLS2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc -# 9bahuEMs305MfR5ocMB3CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdE -# Qcm4RtNsMnxYL2dHZeUbc7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYoxggT0MIIE -# 8AIBATAvMBsxGTAXBgNVBAMMEEFUQSBBdXRoZW50aWNvZGUCEGUt7Y5R2WSNSiNa -# nY6KM08wCQYFKw4DAhoFAKB4MBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJ -# KoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQB -# gjcCARUwIwYJKoZIhvcNAQkEMRYEFGLR9rFga1Zf6A4LBVZHoTSojXTnMA0GCSqG -# SIb3DQEBAQUABIIBAFpWh5G+zycDPKysvH57ymdn7jfLd6JniX5KGQGubRRkMezG -# Is5bEeKKs9U0cPGRl/w2CQJptyC7R6Idj+cy3rs56839nP3cm9rc4hY2/e57UbZj -# 7LMmeK9rskXIAqCkcSyh9pMoslSb8DOLmfEPAwHD71oUUGKW+x3Z/v3rgGZ+/Nxr -# zK15BWEI7+8/u+B1NYeeWPfxDWXCSTpLEXWAoRj9/4RY0HlX4WFUiKo7KErb0xxT -# 2TrZXEI5bqP28+5Qhf71Y5WOlRt9mthS2rMloaoV97T+v4MeKVhgidk3VJWmpPhv -# ZacJNc8+D6d/UEOm4uuOYpaHP25/dEfGov7qZ2WhggMgMIIDHAYJKoZIhvcNAQkG -# MYIDDTCCAwkCAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs -# IEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEy -# NTYgVGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFlAwQC -# AQUAoGkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcN -# MjQwNzI5MTU1MjIxWjAvBgkqhkiG9w0BCQQxIgQgTle8ifnTPQjvsKSWEzoFYw3B -# BNHaIW9C4LZ+Aq1dZhMwDQYJKoZIhvcNAQEBBQAEggIAGgtN+wq2NbTXDghn6LsH -# 8nLJVVHoqEUILtIYdAz40rwMqTRe9AOaXEYpyVkEGDqE9WE2U1ZVouxixYdE/lnF -# F16v+NlaJPE1E/A64g0v86EwZSz76PemxDfDlvG2bl+B2wY1e3FmqTbVytpFY3wr -# Jt3qUSR85t6Srj33Zj6wI9OUvsIYFH/mnKX8yXe1KotuayfvDClgjmVTmMwyzEY2 -# CHgRQHR9LAJyDd19IquVRG2JzwIJCy00iJlEnwae2WpKQ+mehF3ltPWNmsbB0WEu -# l7VZZdeEz1qt5c+b+fv3M5xLxoBTUvGFPnYM+rBfX7E6wQAa9j72f3yY6e+y9Art -# t1ccsqlVVE8BnA1wicfGm8uWeTpp59yZI8AyxE0ZFQ1GdYKQQ77SqE+I5anzkGzc -# zF+cqHRZzv3kfwkb0WfccmoOAy2R1lYakE7eY9La7CaZFEG0XU1Tppl9t5UZLJKV -# 1sCou6N/FkVQfa7tt1j8aqrcQocFFDOqpOn4ACAxM7k09xjiN3szniLeVRD2DI+J -# N/RciH8k/NE+HFnKYrTasWtHWArZESdIWWleiwDOLfnv5KAlgxvT0OPrE2q2ezeU -# zM3rfCWBnrJyZZLa0LAD6MtWC/O+v+HGg8v3zCcDfjfoKFrEu/D4HnR3fTgChGMx -# NdqDO05kTtoQ+FUsbSdSmm8= -# SIG # End signature block diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index fc16e1d..306e34a 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -41,29 +41,8 @@ PreserveNewest - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - Always - - Always - From 7742186e0ea005b3a3284e8db25cb992f31fa3d0 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 12 Feb 2025 17:03:39 -0800 Subject: [PATCH 20/21] rebase 48867-Add_SSH onto main --- CHANGELOG.md | 5 + IISU/Certificate.cs | 30 +- IISU/CertificateStore.cs | 211 ------ IISU/CertificateStoreException.cs | 39 - IISU/ClientPSCertStoreInventory.cs | 150 ---- IISU/ClientPSCertStoreManager.cs | 367 ---------- IISU/ClientPSCertStoreReEnrollment.cs | 5 - IISU/ClientPSIIManager.cs | 687 ------------------ IISU/ClientPsSqlManager.cs | 421 ----------- IISU/ImplementedStoreTypes/Win/Inventory.cs | 2 +- IISU/ImplementedStoreTypes/Win/Management.cs | 4 +- .../ImplementedStoreTypes/Win/ReEnrollment.cs | 2 +- .../Win/WinCertCertificateInfo.cs | 22 +- .../WinIIS/IISBindingInfo.cs | 18 +- .../WinIIS/IISCertificateInfo.cs | 22 +- .../ImplementedStoreTypes/WinIIS/Inventory.cs | 2 + .../WinIIS/Management.cs | 62 -- .../WinIIS/ReEnrollment.cs | 2 + .../WinIIS/WinIISBinding.cs | 24 +- .../WinIIS/WinIISInventory.cs | 228 ------ .../ImplementedStoreTypes/WinSQL/Inventory.cs | 114 +-- .../WinSQL/Management.cs | 98 +-- .../WinSQL/ReEnrollment.cs | 2 + .../WinSQL/SQLServerInventory.cs | 190 ----- .../WinSQL/WinSQLCertificateInfo.cs | 22 +- .../WinSQL/WinSqlBinding.cs | 23 +- IISU/JobConfigurationParser.cs | 10 +- IISU/PSHelper.cs | 296 +------- .../{WinCertFull.ps1 => WinCertScripts.ps1} | 0 IISU/RemoteSettings.cs | 21 +- IISU/Scripts/PowerShellScripts.cs | 140 ---- IISU/WinCertJobTypeBase.cs | 4 +- IISU/WindowsCertStore.csproj | 2 +- WinCertTestConsole/WinCertTestConsole.csproj | 68 +- WinCertUnitTests/UnitTestIISBinding.cs | 15 - WindowsCertStore.sln | 10 - 36 files changed, 181 insertions(+), 3137 deletions(-) delete mode 100644 IISU/CertificateStore.cs delete mode 100644 IISU/CertificateStoreException.cs delete mode 100644 IISU/ClientPSCertStoreInventory.cs delete mode 100644 IISU/ClientPSCertStoreManager.cs delete mode 100644 IISU/ClientPSIIManager.cs delete mode 100644 IISU/ClientPsSqlManager.cs delete mode 100644 IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs delete mode 100644 IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs rename IISU/PowerShellScripts/{WinCertFull.ps1 => WinCertScripts.ps1} (100%) delete mode 100644 IISU/Scripts/PowerShellScripts.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index a26d151..b0af976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +2.6.0 +* Added the ability to run the extension in a Linux environment. To utilize this change, for each Cert Store Types (WinCert/WinIIS/WinSQL), add ssh to the Custom Field WinRM Protocol. When using ssh as a protocol, make sure to enter the appropriate ssh port number under WinRM Port. +* NOTE: For legacy purposes the Display names WinRM Protocol and WinRM Port are maintained although the type of protocols now includes ssh. +* Moved all inventory and management jobs to external PowerShell script file .\PowerShellScripts\WinCertScripts.ps1 + 2.5.1 * Fixed WinSQL service name when InstanceID differs from InstanceName diff --git a/IISU/Certificate.cs b/IISU/Certificate.cs index 04d4926..f8d28c0 100644 --- a/IISU/Certificate.cs +++ b/IISU/Certificate.cs @@ -11,12 +11,12 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. + +// 021225 rcp 2.6.0 Cleaned up and verified code + using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Text.RegularExpressions; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -52,30 +52,6 @@ public static List DeserializeCertificates(string jsonResults) return new List { singleObject }; } } - - public static string FormatSAN(string san) - { - // Use regular expression to extract key-value pairs - var regex = new Regex(@"(?DNS Name|Email|IP Address)=(?[^=,\s]+)"); - var matches = regex.Matches(san); - - // Format matches into the desired format - string result = string.Join("&", matches.Cast() - .Select(m => $"{NormalizeKey(m.Groups["key"].Value)}={m.Groups["value"].Value}")); - - return result; - } - - private static string NormalizeKey(string key) - { - return key.ToLower() switch - { - "dns name" => "dns", - "email" => "email", - "ip address" => "ip", - _ => key.ToLower() // For other types, keep them as-is - }; - } } } } \ No newline at end of file diff --git a/IISU/CertificateStore.cs b/IISU/CertificateStore.cs deleted file mode 100644 index 75c1d0a..0000000 --- a/IISU/CertificateStore.cs +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - public class CertificateStore - { - public CertificateStore(string serverName, string storePath, Runspace runSpace) - { - ServerName = serverName; - StorePath = storePath; - RunSpace = runSpace; - Initalize(); - } - - public string ServerName { get; set; } - public string StorePath { get; set; } - public Runspace RunSpace { get; set; } - public List Certificates { get; set; } - - public void RemoveCertificate(string thumbprint) - { - using var ps = PowerShell.Create(); - ps.Runspace = RunSpace; - - // Open with value of 5 means: Open existing only (4) + Open ReadWrite (1) - var removeScript = $@" - $ErrorActionPreference = 'Stop' - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') - $certStore.Open(5) - $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) - if($certToRemove.Count -gt 0) {{ - $certStore.Remove($certToRemove[0]) - }} - $certStore.Close() - $certStore.Dispose() - "; - - ps.AddScript(removeScript); - - var _ = ps.Invoke(); - if (ps.HadErrors) - throw new CertificateStoreException($"Error removing certificate in {StorePath} store on {ServerName}."); - } - - private void Initalize() - { - Certificates = new List(); - try - { - using var ps = PowerShell.Create(); - ps.Runspace = RunSpace; - - var certStoreScript = $@" - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{StorePath}','LocalMachine') - $certStore.Open('ReadOnly') - $certs = $certStore.Certificates - $certStore.Close() - $certStore.Dispose() - foreach ( $cert in $certs){{ - $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey - }}"; - - ps.AddScript(certStoreScript); - - var certs = ps.Invoke(); - - foreach (var c in certs) - Certificates.Add(new Certificate - { - Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", - HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[])c.Properties["RawData"]?.Value - }); - } - catch (Exception ex) - { - throw new CertificateStoreException( - $"Error listing certificate in {StorePath} store on {ServerName}: {ex.Message}"); - } - } - - private static List PerformGetCertificateInvenotory(Runspace runSpace, string storePath) - { - List myCertificates = new List(); - try - { - using var ps = PowerShell.Create(); - ps.Runspace = runSpace; - - var certStoreScript = $@" - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') - $certStore.Open('ReadOnly') - $certs = $certStore.Certificates - $certStore.Close() - $certStore.Dispose() - foreach ( $cert in $certs){{ - $cert | Select-Object -Property Thumbprint, RawData, HasPrivateKey - }}"; - - ps.AddScript(certStoreScript); - - var certs = ps.Invoke(); - - foreach (var c in certs) - myCertificates.Add(new Certificate - { - Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", - HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[])c.Properties["RawData"]?.Value - }); - - return myCertificates; - } - catch (Exception ex) - { - throw new CertificateStoreException( - $"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}"); - } - - } - - public static List GetCertificatesFromStore(Runspace runSpace, string storePath) - { - return PerformGetCertificateInvenotory(runSpace, storePath); - } - - public static List GetIISBoundCertificates(Runspace runSpace, string storePath) - { - List myCertificates = PerformGetCertificateInvenotory(runSpace, storePath); - List myBoundCerts = new List(); - - using (var ps = PowerShell.Create()) - { - ps.Runspace = runSpace; - - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - - var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; - ps.AddScript(searchScript).AddStatement(); - var iisBindings = ps.Invoke(); // Responsible for getting all bound certificates for each website - - if (ps.HadErrors) - { - var psError = ps.Streams.Error.ReadAll().Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); - } - - if (iisBindings.Count == 0) - { - return myBoundCerts; - } - - //in theory should only be one, but keeping for future update to chance inventory - foreach (var binding in iisBindings) - { - var thumbPrint = $"{binding.Properties["thumbprint"]?.Value}"; - if (string.IsNullOrEmpty(thumbPrint)) continue; - - Certificate foundCert = myCertificates.Find(m => m.Thumbprint.Equals(thumbPrint)); - - if (foundCert == null) continue; - - var siteSettingsDict = new Dictionary - { - { "SiteName", binding.Properties["Name"]?.Value }, - { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, - { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, - { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, - { "SniFlag", binding.Properties["sniFlg"]?.Value }, - { "Protocol", binding.Properties["Protocol"]?.Value } - }; - - myBoundCerts.Add( - new CurrentInventoryItem - { - Certificates = new[] { foundCert.CertificateData }, - Alias = thumbPrint + ":" + binding.Properties["Bindings"]?.Value.ToString(), - PrivateKeyEntry = foundCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = siteSettingsDict - } - ); - } - - return myBoundCerts; - } - } - } -} \ No newline at end of file diff --git a/IISU/CertificateStoreException.cs b/IISU/CertificateStoreException.cs deleted file mode 100644 index f207566..0000000 --- a/IISU/CertificateStoreException.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using System; -using System.Runtime.Serialization; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - [Serializable] - public class CertificateStoreException : Exception - { - public CertificateStoreException() - { - } - - public CertificateStoreException(string message) : base(message) - { - } - - public CertificateStoreException(string message, Exception innerException) : base(message, innerException) - { - } - - protected CertificateStoreException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } - } -} \ No newline at end of file diff --git a/IISU/ClientPSCertStoreInventory.cs b/IISU/ClientPSCertStoreInventory.cs deleted file mode 100644 index c0c0b11..0000000 --- a/IISU/ClientPSCertStoreInventory.cs +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU; -using Keyfactor.Logging; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Runtime.ConstrainedExecution; -using System.Text; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - public abstract class ClientPSCertStoreInventory - { - private ILogger _logger; - - protected ClientPSCertStoreInventory() - { - _logger = LogHandler.GetClassLogger(); - } - - public ClientPSCertStoreInventory(ILogger logger) - { - _logger = logger; - } - - public List GetCertificatesFromStore(RemoteSettings settings, string storePath) - { - try - { - ILogger _logger = LogHandler.GetClassLogger(this.GetType()); - - List myCertificates = new(); - - _logger.LogTrace("Attempting to establish PowerShell connection."); - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) - { - _logger.LogTrace("Initializing connection"); - ps.Initialize(); - - var scriptParameters = new Dictionary - { - { "StoreName", storePath } - }; - - var results = ps.ExecuteCommand(PSHelper.LoadScript("WinCertInventory.ps1"), scriptParameters); - - foreach (var c in results) - { - myCertificates.Add(new Certificate - { - Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", - HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[])c.Properties["RawData"]?.Value, - CryptoServiceProvider = $"{c.Properties["CSP"]?.Value}", - SAN = Certificate.Utilities.FormatSAN($"{c.Properties["san"]?.Value}") - }); - } - } - - _logger.LogTrace($"found: {myCertificates.Count} certificate(s), exiting GetCertificatesFromStore()"); - return myCertificates; - - } - catch (Exception ex) - { - throw new Exception ("An error occurred while attempting to read the certificates from the store.\n" + ex.Message.ToString()); - } - } - - // ORIG - public List GetCertificatesFromStore(Runspace runSpace, string storePath) - { - ILogger _logger = LogHandler.GetClassLogger(this.GetType()); - - List myCertificates = new List(); - try - { - using var ps = PowerShell.Create(); - - _logger.MethodEntry(); - - ps.Runspace = runSpace; - - var certStoreScript = $@" - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') - $certStore.Open('ReadOnly') - $certs = $certStore.Certificates - $certStore.Close() - $certStore.Dispose() - $certs | ForEach-Object {{ - $certDetails = @{{ - Subject = $_.Subject - Thumbprint = $_.Thumbprint - HasPrivateKey = $_.HasPrivateKey - RawData = $_.RawData - san = $_.Extensions | Where-Object {{ $_.Oid.FriendlyName -eq ""Subject Alternative Name"" }} | ForEach-Object {{ $_.Format($false) }} - }} - - if ($_.HasPrivateKey) {{ - $certDetails.CSP = $_.PrivateKey.CspKeyContainerInfo.ProviderName - }} - - New-Object PSObject -Property $certDetails - }}"; - - ps.AddScript(certStoreScript); - - _logger.LogTrace($"Executing the following script:\n{certStoreScript}"); - - var certs = ps.Invoke(); - - foreach (var c in certs) - { - myCertificates.Add(new Certificate - { - Thumbprint = $"{c.Properties["Thumbprint"]?.Value}", - HasPrivateKey = bool.Parse($"{c.Properties["HasPrivateKey"]?.Value}"), - RawData = (byte[])c.Properties["RawData"]?.Value, - CryptoServiceProvider = $"{c.Properties["CSP"]?.Value }", - SAN = Certificate.Utilities.FormatSAN($"{c.Properties["san"]?.Value}") - }); - } - _logger.LogTrace($"found: {myCertificates.Count} certificate(s), exiting GetCertificatesFromStore()"); - return myCertificates; - } - catch (Exception ex) - { - _logger.LogTrace($"An error occurred in the WinCert GetCertificatesFromStore method:\n{ex.Message}"); - - throw new CertificateStoreException( - $"Error listing certificate in {storePath} store on {runSpace.ConnectionInfo.ComputerName}: {ex.Message}"); - } - } - } -} diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs deleted file mode 100644 index e768f08..0000000 --- a/IISU/ClientPSCertStoreManager.cs +++ /dev/null @@ -1,367 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.FlowAnalysis; -using Microsoft.Extensions.Logging; -using System; -using System.IO; -using System.Linq.Expressions; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Security.Cryptography.X509Certificates; -using System.Text; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - public class ClientPSCertStoreManager - { - private ILogger _logger; - private Runspace _runspace; - private long _jobNumber = 0; - - private X509Certificate2 x509Cert; - - public X509Certificate2 X509Cert - { - get { return x509Cert; } - } - - public ClientPSCertStoreManager(Runspace runSpace) - { - _logger = LogHandler.GetClassLogger(); - _runspace = runSpace; - } - - public ClientPSCertStoreManager(ILogger logger, Runspace runSpace, long jobNumber) - { - _logger = logger; - _runspace = runSpace; - _jobNumber = jobNumber; - } - - public string CreatePFXFile(string certificateContents, string privateKeyPassword) - { - _logger.LogTrace("Entering CreatePFXFile"); - if (!string.IsNullOrEmpty(privateKeyPassword)) { _logger.LogTrace("privateKeyPassword was present"); } - else _logger.LogTrace("No privateKeyPassword Presented"); - - try - { - // Create the x509 certificate - x509Cert = new X509Certificate2 - ( - Convert.FromBase64String(certificateContents), - privateKeyPassword, - X509KeyStorageFlags.MachineKeySet | - X509KeyStorageFlags.PersistKeySet | - X509KeyStorageFlags.Exportable - ); - - using (PowerShell ps = PowerShell.Create()) - { - ps.Runspace = _runspace; - - // Add script to write certificate contents to a temporary file - string script = @" - param($certificateContents) - $filePath = [System.IO.Path]::GetTempFileName() + '.pfx' - [System.IO.File]::WriteAllBytes($filePath, [System.Convert]::FromBase64String($certificateContents)) - $filePath - "; - - ps.AddScript(script); - ps.AddParameter("certificateContents", certificateContents); // Convert.ToBase64String(x509Cert.Export(X509ContentType.Pkcs12))); - - // Invoke the script on the remote computer - var results = ps.Invoke(); - - // Get the result (temporary file path) returned by the script - _logger.LogTrace($"Results after creating PFX File: {results[0].ToString()}"); - return results[0].ToString(); - } - } - catch (Exception ex) - { - _logger.LogError(ex.ToString()); - throw new Exception("An error occurred while attempting to create and write the X509 contents."); - } - } - - public void DeletePFXFile(string filePath, string fileName) - { - using (PowerShell ps = PowerShell.Create()) - { - ps.Runspace = _runspace; - - // Add script to delete the temporary file - string deleteScript = @" - param($filePath) - Remove-Item -Path $filePath -Force - "; - - ps.AddScript(deleteScript); - ps.AddParameter("filePath", Path.Combine(filePath, fileName) + "*"); - - // Invoke the script to delete the file - var results = ps.Invoke(); - } - } - - public JobResult ImportPFXFile(string filePath, string privateKeyPassword, string cryptoProviderName, string storePath) - { - try - { - _logger.LogTrace("Entering ImportPFX"); - - using (PowerShell ps = PowerShell.Create()) - { - ps.Runspace = _runspace; - - if (string.IsNullOrEmpty(cryptoProviderName)) - { - if (string.IsNullOrEmpty(privateKeyPassword)) - { - // If no private key password is provided, import the pfx file directory to the store using addstore argument - string script = @" - param($pfxFilePath, $storePath) - $output = certutil -f -addstore $storePath $pfxFilePath 2>&1 - $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - - if ($output.GetType().Name -eq ""String"") - { - $output = @($output, $exit_message) - } - else - { - $output += $exit_message - } - $output - "; - - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("storePath", storePath); - } - else - { - // Use ImportPFX to import the pfx file with private key password to the appropriate cert store - - string script = @" - param($pfxFilePath, $privateKeyPassword, $storePath) - $output = certutil -f -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 - $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - - if ($output.GetType().Name -eq ""String"") - { - $output = @($output, $exit_message) - } - else - { - $output += $exit_message - } - $output - "; - - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("privateKeyPassword", privateKeyPassword); - ps.AddParameter("storePath", storePath); - } - } - else - { - if (string.IsNullOrEmpty(privateKeyPassword)) - { - string script = @" - param($pfxFilePath, $cspName, $storePath) - $output = certutil -f -csp $cspName -addstore $storePath $pfxFilePath 2>&1 - $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - - if ($output.GetType().Name -eq ""String"") - { - $output = @($output, $exit_message) - } - else - { - $output += $exit_message - } - $output - "; - - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("cspName", cryptoProviderName); - ps.AddParameter("storePath", storePath); - } - else - { - string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName, $storePath) - $output = certutil -f -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 - $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - - if ($output.GetType().Name -eq ""String"") - { - $output = @($output, $exit_message) - } - else - { - $output += $exit_message - } - $output - "; - - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("privateKeyPassword", privateKeyPassword); - ps.AddParameter("cspName", cryptoProviderName); - ps.AddParameter("storePath", storePath); - } - } - - // Invoke the script - _logger.LogTrace("Attempting to import the PFX"); - var results = ps.Invoke(); - - // Get the last exist code returned from the script - int lastExitCode = 0; - try - { - lastExitCode = GetLastExitCode(results[^1].ToString()); - _logger.LogTrace($"Last exit code: {lastExitCode}"); - } - catch (Exception ex) - { - _logger.LogTrace(ex.Message); - } - - - bool isError = false; - if (lastExitCode != 0) - { - isError = true; - string outputMsg = ""; - - foreach (var result in results) - { - string outputLine = result.ToString(); - if (!string.IsNullOrEmpty(outputLine)) - { - outputMsg += "\n" + outputLine; - } - } - _logger.LogError(outputMsg); - } - else - { - // Check for errors in the output - foreach (var result in results) - { - string outputLine = result.ToString(); - - _logger.LogTrace(outputLine); - - if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error") || outputLine.Contains("permissions are needed")) - { - isError = true; - _logger.LogError(outputLine); - } - } - } - - if (isError) - { - throw new Exception("Error occurred while attempting to import the pfx file."); - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = _jobNumber, - FailureMessage = "" - }; - } - } - } - catch (Exception e) - { - _logger.LogError($"Error Occurred in ClientPSCertStoreManager.ImportPFXFile(): {e.Message}"); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = _jobNumber, - FailureMessage = $"Error Occurred in ImportPFXFile {LogHandler.FlattenException(e)}" - }; - } - } - - private int GetLastExitCode(string result) - { - // Split the string by colon - string[] parts = result.Split(':'); - - // Ensure the split result has the expected parts - if (parts.Length == 2 && parts[0] == "LASTEXITCODE") - { - // Parse the second part into an integer - if (int.TryParse(parts[1], out int lastExitCode)) - { - return lastExitCode; - } - else - { - throw new Exception("Failed to parse the LASTEXITCODE value."); - } - } - else - { - throw new Exception("The last element does not contain the expected format."); - } - } - - public void RemoveCertificate(string thumbprint, string storePath) - { - using var ps = PowerShell.Create(); - - _logger.MethodEntry(); - - ps.Runspace = _runspace; - - // Open with value of 5 means: Open existing only (4) + Open ReadWrite (1) - var removeScript = $@" - $ErrorActionPreference = 'Stop' - $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{storePath}','LocalMachine') - $certStore.Open(5) - $certToRemove = $certStore.Certificates.Find(0,'{thumbprint}',$false) - if($certToRemove.Count -gt 0) {{ - $certStore.Remove($certToRemove[0]) - }} - $certStore.Close() - $certStore.Dispose() - "; - - ps.AddScript(removeScript); - - var _ = ps.Invoke(); - if (ps.HadErrors) - throw new CertificateStoreException($"Error removing certificate in {storePath} store on {_runspace.ConnectionInfo.ComputerName}."); - - } - } -} diff --git a/IISU/ClientPSCertStoreReEnrollment.cs b/IISU/ClientPSCertStoreReEnrollment.cs index 9e105b8..86dc118 100644 --- a/IISU/ClientPSCertStoreReEnrollment.cs +++ b/IISU/ClientPSCertStoreReEnrollment.cs @@ -24,13 +24,10 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; -using System.Management.Automation.Remoting; using System.Security.Cryptography.X509Certificates; -using System.Text; using Microsoft.Extensions.Logging; using Keyfactor.Orchestrators.Extensions.Interfaces; using System.Linq; -using System.IO; using Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU; using Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql; @@ -147,8 +144,6 @@ public JobResult PerformReEnrollment(ReenrollmentJobConfiguration config, Submit } - - jobResult = new JobResult { Result = OrchestratorJobStatusJobResult.Success, diff --git a/IISU/ClientPSIIManager.cs b/IISU/ClientPSIIManager.cs deleted file mode 100644 index 255fedf..0000000 --- a/IISU/ClientPSIIManager.cs +++ /dev/null @@ -1,687 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.Scripts; -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Security.Cryptography.X509Certificates; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - public class ClientPSIIManager - { - private string SiteName { get; set; } - private string Port { get; set; } - private string Protocol { get; set; } - private string HostName { get; set; } - private string SniFlag { get; set; } - private string IPAddress { get; set; } - - private string RenewalThumbprint { get; set; } = ""; - - private string CertContents { get; set; } = ""; - - private string PrivateKeyPassword { get; set; } = ""; - - private string ClientMachineName { get; set; } - private string StorePath { get; set; } - - private long JobHistoryID { get; set; } - - private readonly ILogger _logger; - private readonly Runspace _runSpace; - - private PowerShell ps; - - /// - /// This constructor is used for unit testing - /// - /// - /// - /// - /// - /// - /// - /// - public ClientPSIIManager(Runspace runSpace, string SiteName, string Protocol, string IPAddress, string Port, string HostName, string Thumbprint, string StorePath, string sniFlag) - { - _logger = LogHandler.GetClassLogger(); - _runSpace = runSpace; - - this.SiteName = SiteName; - this.Protocol = Protocol; - this.IPAddress = IPAddress; - this.Port = Port; - this.HostName = HostName; - this.RenewalThumbprint = Thumbprint; - this.StorePath = StorePath; - this.SniFlag = sniFlag; - } - - public ClientPSIIManager(ReenrollmentJobConfiguration config, string serverUsername, string serverPassword) - { - _logger = LogHandler.GetClassLogger(); - - try - { - SiteName = config.JobProperties["SiteName"].ToString(); - Port = config.JobProperties["Port"].ToString(); - HostName = config.JobProperties["HostName"]?.ToString(); - Protocol = config.JobProperties["Protocol"].ToString(); - SniFlag = MigrateSNIFlag(config.JobProperties["SniFlag"]?.ToString()); - IPAddress = config.JobProperties["IPAddress"].ToString(); - - PrivateKeyPassword = ""; // A reenrollment does not have a PFX Password - RenewalThumbprint = ""; // A reenrollment will always be empty - CertContents = ""; // Not needed for a reenrollment - - ClientMachineName = config.CertificateStoreDetails.ClientMachine; - StorePath = config.CertificateStoreDetails.StorePath; - - JobHistoryID = config.JobHistoryId; - - // Establish PowerShell Runspace - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string winRmProtocol = jobProperties.WinRmProtocol; - string winRmPort = jobProperties.WinRmPort; - bool includePortInSPN = jobProperties.SpnPortFlag; - - _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); - } - catch (Exception e) - { - throw new Exception($"Error when initiating an IIS ReEnrollment Job: {e.Message}", e.InnerException); - } - } - - public ClientPSIIManager(ManagementJobConfiguration config, string serverUsername, string serverPassword) - { - _logger = LogHandler.GetClassLogger(); - - try - { - _logger.LogTrace("Setting Job Properties"); - SiteName = config.JobProperties["SiteName"].ToString(); - Port = config.JobProperties["Port"].ToString(); - HostName = config.JobProperties["HostName"]?.ToString(); - Protocol = config.JobProperties["Protocol"].ToString(); - SniFlag = MigrateSNIFlag(config.JobProperties["SniFlag"]?.ToString()); - IPAddress = config.JobProperties["IPAddress"].ToString(); - - PrivateKeyPassword = ""; // A reenrollment does not have a PFX Password - RenewalThumbprint = ""; // This property will not be used for renewals starting in version 2.5 - CertContents = ""; // Not needed for a reenrollment - - _logger.LogTrace("Certificate details"); - ClientMachineName = config.CertificateStoreDetails.ClientMachine; - StorePath = config.CertificateStoreDetails.StorePath; - - JobHistoryID = config.JobHistoryId; - - if (config.JobProperties.ContainsKey("RenewalThumbprint")) - { - RenewalThumbprint = config.JobProperties["RenewalThumbprint"].ToString(); - _logger.LogTrace($"Found Thumbprint Will Renew all Certs with this thumbprint: {RenewalThumbprint}"); - } - else - { - _logger.LogTrace("No renewal Thumbprint was provided."); - } - - // Establish PowerShell Runspace - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - _logger.LogTrace($"Job properties value: {jobProperties}"); - - string winRmProtocol = jobProperties.WinRmProtocol; - string winRmPort = jobProperties.WinRmPort; - bool includePortInSPN = jobProperties.SpnPortFlag; - - _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); - } - catch (Exception e) - { - throw new Exception($"Error when initiating an IIS Management Job: {e.Message}", e.InnerException); - } - } - - public JobResult BindCertificate(X509Certificate2 x509Cert) - { - try - { - _logger.MethodEntry(); - - _runSpace.Open(); - ps = PowerShell.Create(); - ps.Runspace = _runSpace; - - bool hadError = false; - string errorMessage = string.Empty; - - try - { - Collection results = (Collection)PerformIISBinding(SiteName, Protocol, IPAddress, Port, HostName, SniFlag, x509Cert.Thumbprint, StorePath); - - if (ps.HadErrors) - { - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => - current + (error.ErrorDetails != null && !string.IsNullOrEmpty(error.ErrorDetails.Message) - ? error.ErrorDetails.Message - : error.Exception != null - ? error.Exception.Message - : error.ToString()) + Environment.NewLine); - - errorMessage = psError; - hadError = true; - - } - } - catch (Exception e) - { - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - errorMessage = $"Binding attempt failed on Site {SiteName} on {computerName}, Application error: {e.Message}"; - hadError = true; - _logger.LogTrace(errorMessage); - } - - if (hadError) - { - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = $"Binding attempt failed on Site {SiteName} on {computerName}: {errorMessage}" - }; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryID, - FailureMessage = "" - }; - } - } - catch (Exception e) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = $"Application Error Occurred in BindCertificate: {LogHandler.FlattenException(e)}" - }; - } - finally - { - _runSpace.Close(); - ps.Runspace.Close(); - ps.Dispose(); - } - } - - public JobResult UnBindCertificate() - { -#if NET8_0_OR_GREATER - bool hadError = false; - string errorMessage = string.Empty; - - try - { - _logger.MethodEntry(); - - _runSpace.Open(); - ps = PowerShell.Create(); - ps.Runspace = _runSpace; - - Collection results = (Collection)PerformIISUnBinding(SiteName, Protocol, IPAddress, Port, HostName); - - if (ps.HadErrors) - { - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => - current + (error.ErrorDetails != null && !string.IsNullOrEmpty(error.ErrorDetails.Message) - ? error.ErrorDetails.Message - : error.Exception != null - ? error.Exception.Message - : error.ToString()) + Environment.NewLine); - - errorMessage = psError; - hadError = true; - - } - } - catch (Exception e) - { - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - errorMessage = $"Binding attempt failed on Site {SiteName} on {computerName}, Application error: {e.Message}"; - hadError = true; - _logger.LogTrace(errorMessage); - } - finally - { - _runSpace.Close(); - ps.Runspace.Close(); - ps.Dispose(); - } - - if (hadError) - { - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = $"Binding attempt failed on Site {SiteName} on {computerName}: {errorMessage}" - }; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryID, - FailureMessage = "" - }; - } -#endif - try - { - _logger.MethodEntry(); - - _runSpace.Open(); - ps = PowerShell.Create(); - ps.Runspace = _runSpace; - - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - - _logger.LogTrace("WebAdministration Imported"); - - ps.AddCommand("Get-WebBinding") - .AddParameter("Protocol", Protocol) - .AddParameter("Name", SiteName) - .AddParameter("Port", Port) - .AddParameter("HostHeader", HostName) - .AddParameter("IPAddress", IPAddress) - .AddStatement(); - - _logger.LogTrace("Get-WebBinding Set"); - var foundBindings = ps.Invoke(); - _logger.LogTrace("foundBindings Invoked"); - - if (foundBindings.Count == 0) - { - _logger.LogTrace($"{foundBindings.Count} Bindings Found..."); - - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryID, - FailureMessage = - $"No bindings we found for Site {SiteName} on {computerName}." - }; - } - - //Log Commands out for debugging purposes - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - - foreach (var binding in foundBindings) - { - ps.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - - _logger.LogTrace("Imported WebAdministration Module"); - - ps.AddCommand("Remove-WebBinding") - .AddParameter("Name", SiteName) - .AddParameter("BindingInformation", - $"{binding.Properties["bindingInformation"]?.Value}") - .AddStatement(); - - //Log Commands out for debugging purposes - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - var _ = ps.Invoke(); - _logger.LogTrace("Invoked Remove-WebBinding"); - - if (ps.HadErrors) - { - _logger.LogTrace("PowerShell Had Errors"); - var psError = ps.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + error.ErrorDetails.Message); - //var psError = ps.Streams.Error.ReadAll() - // .Aggregate(string.Empty, (current, error) => - // current + (error.ErrorDetails != null && !string.IsNullOrEmpty(error.ErrorDetails.Message) - // ? error.ErrorDetails.Message - // : error.Exception != null - // ? error.Exception.Message - // : error.ToString()) + Environment.NewLine); - - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = - $"Failed to remove {Protocol} binding for Site {SiteName} on {computerName} not found, error {psError}" - }; - } - ps.Commands.Clear(); - } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryID, - FailureMessage = "" - }; - - } - catch (Exception ex) - { - string computerName = string.Empty; - if (_runSpace.ConnectionInfo is null) - { - computerName = "localMachine"; - } - else { computerName = "Server: " + _runSpace.ConnectionInfo.ComputerName; } - - var failureMessage = $"Unbinding for Site '{StorePath}' on {computerName} with error: {LogHandler.FlattenException(ex)}"; - _logger.LogWarning(failureMessage); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = failureMessage - }; - } - finally - { - _runSpace.Close(); - ps.Runspace.Close(); - ps.Dispose(); - } - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - private object PerformIISUnBinding(string webSiteName, string protocol, string ipAddress, string port, string hostName) - { - string funcScript = @" - param ( - [string]$SiteName, # Name of the site - [string]$IPAddress, # IP Address of the binding - [string]$Port, # Port number of the binding - [string]$Hostname, # Hostname (optional) - [string]$Protocol = ""https"" # Protocol (default to """"https"""") - ) - - # Set Execution Policy (optional, depending on your environment) - Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force - - # Check if the IISAdministration module is already loaded - if (-not (Get-Module -Name IISAdministration)) { - try { - # Attempt to import the IISAdministration module - Import-Module IISAdministration -ErrorAction Stop - } - catch { - throw ""Failed to load the IISAdministration module. Ensure it is installed and available."" - } - } - - try { - # Get the bindings for the specified site - $bindings = Get-IISSiteBinding -Name $SiteName - - # Check if any bindings match the specified criteria - $matchingBindings = $bindings | Where-Object { - ($_.bindingInformation -eq ""${IPAddress}:${Port}:${Hostname}"") -and - ($_.protocol -eq $Protocol) - } - - if ($matchingBindings) { - # Unbind the matching certificates - foreach ($binding in $matchingBindings) { - Write-Host """"Removing binding: $($binding.bindingInformation) with protocol: $($binding.protocol)"""" - Write-Host """"Binding information: - Remove-IISSiteBinding -Name $SiteName -BindingInformation $binding.bindingInformation -Protocol $binding.protocol -confirm:$false - } - Write-Host """"Successfully removed the matching bindings from the site: $SiteName"""" - } else { - Write-Host """"No matching bindings found for site: $SiteName"""" - } - } - catch { - throw ""An error occurred while unbinding the certificate from site ${SiteName}: $_"" - } - "; - - ps.AddScript(funcScript); - ps.AddParameter("SiteName", webSiteName); - ps.AddParameter("IPAddress", ipAddress); - ps.AddParameter("Port", port); - ps.AddParameter("Hostname", hostName); - ps.AddParameter("Protocol", protocol); - - _logger.LogTrace("funcScript added..."); - var results = ps.Invoke(); - _logger.LogTrace("funcScript Invoked..."); - - return results; - } - - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - /// - private object PerformIISBinding(string webSiteName, string protocol, string ipAddress, string port, string hostName, string sslFlags, string thumbprint, string storeName) - { - //string funcScript = @" - // param ( - // $SiteName, # The name of the IIS site - // $IPAddress, # The IP Address for the binding - // $Port, # The port number for the binding - // $Hostname, # Hostname for the binding (if any) - // $Protocol, # Protocol (e.g., HTTP, HTTPS) - // $Thumbprint, # Certificate thumbprint for HTTPS bindings - // $StoreName, # Certificate store location (e.g., ""My"" for personal certs) - // $SslFlags # SSL flags (if any) - // ) - - // # Set Execution Policy (optional, depending on your environment) - // Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force - - // ## Check if the IISAdministration module is available - // #$module = Get-Module -Name IISAdministration -ListAvailable - - // #if (-not $module) { - // # throw ""The IISAdministration module is not installed on this system."" - // #} - - // # Check if the IISAdministration module is already loaded - // if (-not (Get-Module -Name IISAdministration)) { - // try { - // # Attempt to import the IISAdministration module - // Import-Module IISAdministration -ErrorAction Stop - // } - // catch { - // throw ""Failed to load the IISAdministration module. Ensure it is installed and available."" - // } - // } - - // # Retrieve the existing binding information - // $myBinding = ""${IPAddress}:${Port}:${Hostname}"" - // Write-Host ""myBinding: "" $myBinding - - // $siteBindings = Get-IISSiteBinding -Name $SiteName - // $existingBinding = $siteBindings | Where-Object { $_.bindingInformation -eq $myBinding -and $_.protocol -eq $Protocol } - - // Write-Host ""Binding:"" $existingBinding - - // if ($null -ne $existingBinding) { - // # Remove the existing binding - // Remove-IISSiteBinding -Name $SiteName -BindingInformation $existingBinding.BindingInformation -Protocol $existingBinding.Protocol -Confirm:$false - - // Write-Host ""Removed existing binding: $($existingBinding.BindingInformation)"" - // } - - // # Create the new binding with modified properties - // $newBindingInfo = ""${IPAddress}:${Port}:${Hostname}"" - - // try - // { - // New-IISSiteBinding -Name $SiteName ` - // -BindingInformation $newBindingInfo ` - // -Protocol $Protocol ` - // -CertificateThumbprint $Thumbprint ` - // -CertStoreLocation $StoreName ` - // -SslFlag $SslFlags - - // Write-Host ""New binding added: $newBindingInfo"" - // } - // catch { - // throw $_ - // } - //"; -#if NET6_0 - string funcScript = PowerShellScripts.UpdateIISBindingsV6; -#elif NET8_0_OR_GREATER - string funcScript = PowerShellScripts.UpdateIISBindingsV8; -#endif - - ps.AddScript(funcScript); - ps.AddParameter("SiteName", webSiteName); - ps.AddParameter("IPAddress", ipAddress); - ps.AddParameter("Port", port); - ps.AddParameter("Hostname", hostName); - ps.AddParameter("Protocol", protocol); - ps.AddParameter("Thumbprint", thumbprint); - ps.AddParameter("StoreName", storeName); - ps.AddParameter("SslFlags", sslFlags); - - _logger.LogTrace("funcScript added..."); - var results = ps.Invoke(); - _logger.LogTrace("funcScript Invoked..."); - - return results; - } - - public static string MigrateSNIFlag(string input) - { - // Check if the input is numeric, if so, just return it as an integer - if (int.TryParse(input, out int numericValue)) - { - return numericValue.ToString(); - } - - if (string.IsNullOrEmpty(input)) { throw new ArgumentNullException("SNI/SSL Flag", "The SNI or SSL Flag flag must not be empty or null."); } - - // Handle the string cases - switch (input.ToLower()) - { - case "0 - no sni": - return "0"; - case "1 - sni enabled": - return "1"; - case "2 - non sni binding": - return "2"; - case "3 - sni binding": - return "3"; - default: - throw new ArgumentOutOfRangeException($"Received an invalid value '{input}' for sni/ssl Flag value"); - } - } - } -} - diff --git a/IISU/ClientPsSqlManager.cs b/IISU/ClientPsSqlManager.cs deleted file mode 100644 index 454cb12..0000000 --- a/IISU/ClientPsSqlManager.cs +++ /dev/null @@ -1,421 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Management.Infrastructure.Serialization; -using Newtonsoft.Json; -using System; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Web.Services.Description; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore -{ - internal class ClientPsSqlManager - { - private string SqlServiceUser { get; set; } - private string SqlInstanceName { get; set; } - private bool RestartService { get; set; } - private string RegistryPath { get; set; } - private string RenewalThumbprint { get; set; } = ""; - private string ClientMachineName { get; set; } - private long JobHistoryID { get; set; } - - private readonly ILogger _logger; - private readonly Runspace _runSpace; - - private PowerShell ps; - - public ClientPsSqlManager(ManagementJobConfiguration config, string serverUsername, string serverPassword) - { - _logger = LogHandler.GetClassLogger(); - - try - { - ClientMachineName = config.CertificateStoreDetails.ClientMachine; - JobHistoryID = config.JobHistoryId; - - if (config.JobProperties.ContainsKey("InstanceName")) - { - var instanceRef = config.JobProperties["InstanceName"]?.ToString(); - SqlInstanceName = string.IsNullOrEmpty(instanceRef) ? "MSSQLSERVER":instanceRef; - } - - // Establish PowerShell Runspace - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string winRmProtocol = jobProperties.WinRmProtocol; - string winRmPort = jobProperties.WinRmPort; - bool includePortInSPN = jobProperties.SpnPortFlag; - RestartService = jobProperties.RestartService; - - _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); - } - catch (Exception e) - { - throw new Exception($"Error when initiating a SQL Management Job: {e.Message}", e.InnerException); - } - } - - public ClientPsSqlManager(InventoryJobConfiguration config,Runspace runSpace) - { - _logger = LogHandler.GetClassLogger(); - - try - { - ClientMachineName = config.CertificateStoreDetails.ClientMachine; - JobHistoryID = config.JobHistoryId; - - // Establish PowerShell Runspace - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string winRmProtocol = jobProperties.WinRmProtocol; - string winRmPort = jobProperties.WinRmPort; - bool includePortInSPN = jobProperties.SpnPortFlag; - - _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = runSpace; - } - catch (Exception e) - { - throw new Exception($"Error when initiating a SQL Inventory Job: {e.Message}", e.InnerException); - } - } - - public ClientPsSqlManager(ReenrollmentJobConfiguration config, string serverUsername, string serverPassword) - { - _logger = LogHandler.GetClassLogger(); - - try - { - ClientMachineName = config.CertificateStoreDetails.ClientMachine; - JobHistoryID = config.JobHistoryId; - - if (config.JobProperties.ContainsKey("InstanceName")) - { - var instanceRef = config.JobProperties["InstanceName"]?.ToString(); - SqlInstanceName = string.IsNullOrEmpty(instanceRef) ? "MSSQLSERVER" : instanceRef; - } - - // Establish PowerShell Runspace - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string winRmProtocol = jobProperties.WinRmProtocol; - string winRmPort = jobProperties.WinRmPort; - bool includePortInSPN = jobProperties.SpnPortFlag; - RestartService = jobProperties.RestartService; - - _logger.LogTrace($"Establishing runspace on client machine: {ClientMachineName}"); - _runSpace = PSHelper.GetClientPsRunspace(winRmProtocol, ClientMachineName, winRmPort, includePortInSPN, serverUsername, serverPassword); - } - catch (Exception e) - { - throw new Exception($"Error when initiating a SQL ReEnrollment Job: {e.Message}", e.InnerException); - } - } - - public JobResult UnBindCertificate() - { - try - { - _logger.MethodEntry(); - - _runSpace.Open(); - ps = PowerShell.Create(); - ps.Runspace = _runSpace; - - RegistryPath = GetSqlCertRegistryLocation(SqlInstanceName, ps); - - var funcScript = string.Format($"Clear-ItemProperty -Path \"{RegistryPath}\" -Name Certificate"); - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - _logger.LogTrace($"funcScript {funcScript}"); - ps.AddScript(funcScript); - _logger.LogTrace("funcScript added..."); - ps.Invoke(); - _logger.LogTrace("funcScript Invoked..."); - - if (ps.HadErrors) - { - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => current + error.ErrorDetails.Message); - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = $"Unable to unbind certificate to Sql Server" - }; - } - } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryID, - FailureMessage = "" - }; - } - catch (Exception e) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = $"Error Occurred in unbind {LogHandler.FlattenException(e)}" - }; - } - finally - { - _runSpace.Close(); - ps.Runspace.Close(); - ps.Dispose(); - } - } - - public string GetSqlInstanceValue(string instanceName,PowerShell ps) - { - try - { - var funcScript = string.Format(@$"Get-ItemPropertyValue ""HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL"" -Name {instanceName}"); - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - _logger.LogTrace($"funcScript {funcScript}"); - ps.AddScript(funcScript); - _logger.LogTrace("funcScript added..."); - var SqlInstanceValue = ps.Invoke()[0].ToString(); - _logger.LogTrace("funcScript Invoked..."); - ps.Commands.Clear(); - - if (!ps.HadErrors) - { - return SqlInstanceValue; - } - return null; - } - catch (ArgumentOutOfRangeException) - { - throw new Exception($"There were no SQL instances with the name: {instanceName}. Please check the spelling of the SQL instance."); - } - catch (Exception e) - { - throw new Exception($"Error when initiating getting instance name from registry: {e.Message}", e.InnerException); - } - } - - public string GetSqlCertRegistryLocation(string instanceName,PowerShell ps) - { - return $"HKLM:\\SOFTWARE\\Microsoft\\Microsoft SQL Server\\{GetSqlInstanceValue(instanceName,ps)}\\MSSQLServer\\SuperSocketNetLib\\"; - } - - public string GetSqlServerServiceName(string instanceName) - { - if(string.IsNullOrEmpty(instanceName)) - return string.Empty; - - //Default SQL Instance has this format - if (instanceName == "MSSQLSERVER") - return "MSSQLSERVER"; - - //Named Instance service has this format - return $"MSSQL`${instanceName}"; - } - - public JobResult BindCertificates(string renewalThumbprint, X509Certificate2 x509Cert) - { - try - { - var bindingError = string.Empty; - RenewalThumbprint = renewalThumbprint; - - _runSpace.Open(); - ps = PowerShell.Create(); - ps.Runspace = _runSpace; - if (!string.IsNullOrEmpty(renewalThumbprint)) - { - var funcScript = string.Format(@$"(Get-ItemProperty ""HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server"").InstalledInstances"); - ps.AddScript(funcScript); - _logger.LogTrace("funcScript added..."); - var instances = ps.Invoke(); - ps.Commands.Clear(); - foreach (var instance in instances) - { - var regLocation = GetSqlCertRegistryLocation(instance.ToString(), ps); - - funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate"); - ps.AddScript(funcScript); - _logger.LogTrace("funcScript added..."); - var thumbprint = ps.Invoke()[0].ToString(); - ps.Commands.Clear(); - - if (RenewalThumbprint.Contains(thumbprint, StringComparison.CurrentCultureIgnoreCase)) - { - bindingError=BindCertificate(x509Cert, ps); - } - } - } - else - { - bindingError=BindCertificate(x509Cert, ps); - } - - if (bindingError.Length == 0) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = JobHistoryID, - FailureMessage = "" - }; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = bindingError - }; - } - - } - catch (Exception e) - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = JobHistoryID, - FailureMessage = $"Error Occurred in BindCertificates {LogHandler.FlattenException(e)}" - }; - } - finally - { - _runSpace.Close(); - ps.Runspace.Close(); - ps.Dispose(); - } - - } - public string BindCertificate(X509Certificate2 x509Cert,PowerShell ps) - { - try - { - _logger.MethodEntry(); - - - //If they comma separated the instance entry param, they are trying to install to more than 1 instance - var instances = SqlInstanceName.Split(','); - - foreach (var instanceName in instances) - { - RegistryPath = GetSqlCertRegistryLocation(instanceName, ps); - - var thumbPrint = string.Empty; - if (x509Cert != null) - thumbPrint = x509Cert.Thumbprint.ToLower(); //sql server config mgr expects lower - - var funcScript = string.Format($"Set-ItemProperty -Path \"{RegistryPath}\" -Name Certificate {thumbPrint}"); - foreach (var cmd in ps.Commands.Commands) - { - _logger.LogTrace("Logging PowerShell Command"); - _logger.LogTrace(cmd.CommandText); - } - - ps.AddScript(funcScript); - _logger.LogTrace($"Running script: {funcScript}"); - ps.Invoke(); - _logger.LogTrace("funcScript Invoked..."); - - _logger.LogTrace("Setting up Acl Access for Manage Private Keys"); - ps.Commands.Clear(); - - //Get the SqlServer Service User Name - var serviceName = GetSqlServerServiceName(instanceName); - if (serviceName != "") - { - _logger.LogTrace($"Service Name: {serviceName} was returned."); - - funcScript = @$"(Get-WmiObject Win32_Service -Filter ""Name='{serviceName}'"").StartName"; - ps.AddScript(funcScript); - _logger.LogTrace($"Running script: {funcScript}"); - SqlServiceUser = ps.Invoke()[0].ToString(); - - _logger.LogTrace($"SqlServiceUser: {SqlServiceUser}"); - _logger.LogTrace("Got service login user for ACL Permissions"); - ps.Commands.Clear(); - - funcScript = $@"$thumbprint = '{thumbPrint}' - $Cert = Get-ChildItem Cert:\LocalMachine\My | Where-Object {{ $_.Thumbprint -eq $thumbprint }} - $privKey = $Cert.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName - $keyPath = ""$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\"" - $privKeyPath = (Get-Item ""$keyPath\$privKey"") - $Acl = Get-Acl $privKeyPath - $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule(""{SqlServiceUser.Replace("$", "`$")}"", ""Read"", ""Allow"") - $Acl.SetAccessRule($Ar) - Set-Acl $privKeyPath.FullName $Acl"; - - ps.AddScript(funcScript); - ps.Invoke(); - _logger.LogTrace("ACL FuncScript Invoked..."); - - } - else - { - _logger.LogTrace("No Service User has been returned. Skipping ACL update."); - } - - //If user filled in a service name in the store then restart the SQL Server Services - if (RestartService) - { - _logger.LogTrace("Starting to Restart SQL Server Service..."); - ps.Commands.Clear(); - funcScript = $@"Restart-Service -Name ""{serviceName}"" -Force"; - - ps.AddScript(funcScript); - ps.Invoke(); - _logger.LogTrace("Invoked Restart SQL Server Service...."); - } - - if (ps.HadErrors) - { - var psError = ps.Streams.Error.ReadAll() - .Aggregate(string.Empty, (current, error) => current + error?.Exception.Message); - { - return psError; - } - } - } - return ""; - } - catch (Exception e) - { - return LogHandler.FlattenException(e); - } - - } - } -} - diff --git a/IISU/ImplementedStoreTypes/Win/Inventory.cs b/IISU/ImplementedStoreTypes/Win/Inventory.cs index 75a1aca..9f0dd2a 100644 --- a/IISU/ImplementedStoreTypes/Win/Inventory.cs +++ b/IISU/ImplementedStoreTypes/Win/Inventory.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// 021225 rcp Updated and cleaned up unnecessary code +// 021225 rcp 2.6.0 Cleaned up and verified code using System; using System.Collections.Generic; diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index a132813..0a0be75 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -14,7 +14,7 @@ // Ignore Spelling: Keyfactor -// 021225 rcp Cleaned up and removed unnecessary code +// 021225 rcp 2.6.0 Cleaned up and verified code using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -26,8 +26,6 @@ using Keyfactor.Logging; using System.Collections.ObjectModel; using System.Collections.Generic; -using System.Management.Automation.Runspaces; -using System.Security.Cryptography.X509Certificates; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { diff --git a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs index 50e9fa5..0b5610f 100644 --- a/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/Win/ReEnrollment.cs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// 021225 rcp Cleaned up and removed unnecessary code +// 021225 rcp 2.6.0 Cleaned up and verified code using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; diff --git a/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs b/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs index 91a0efc..27d5e00 100644 --- a/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/Win/WinCertCertificateInfo.cs @@ -1,10 +1,18 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.ConstrainedExecution; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; +// Copyright 2023 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// 021225 rcp 2.6.0 Cleaned up and verified code namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinCert { diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs index 329f911..b7711cd 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISBindingInfo.cs @@ -1,4 +1,20 @@ -// Ignore Spelling: Keyfactor IISU +// Copyright 2025 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Ignore Spelling: Keyfactor IISU + +// 021225 rcp 2.6.0 Cleaned up and verified code using System; using System.Collections.Generic; diff --git a/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs index 89133a8..4146468 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/IISCertificateInfo.cs @@ -1,8 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright 2025 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// 021225 rcp 2.6.0 Cleaned up and verified code + +using System; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index ec4f401..083b302 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index e916054..ac33c4c 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -15,7 +15,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.IO; using System.Management.Automation; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; @@ -108,7 +107,6 @@ public JobResult ProcessJob(ManagementJobConfiguration config) // Bind Certificate to IIS Site if (newThumbprint != null) { - // TODO: Need to check/test IISU renewal thumbprint IISBindingInfo bindingInfo = new IISBindingInfo(config.JobProperties); WinIISBinding.BindCertificate(_psHelper, bindingInfo, newThumbprint, "", _storePath); @@ -270,65 +268,5 @@ public JobResult RemoveCertificate(string thumbprint) }; } } - - //public void BindCertificate(IISBindingInfo bindingInfo, string thumbprint) - //{ - // _logger.LogTrace("Attempting to bind and execute PS function (New-KFIISSiteBinding)"); - - // // Manditory parameters - // var parameters = new Dictionary - // { - // { "Thumbprint", thumbprint }, - // { "WebSite", bindingInfo.SiteName }, - // { "Protocol", bindingInfo.Protocol }, - // { "IPAddress", bindingInfo.IPAddress }, - // { "Port", bindingInfo.Port }, - // { "SNIFlag", bindingInfo.SniFlag }, - // { "StoreName", _storePath }, - // }; - - // // Optional parameters - // if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } - - // _results = _psHelper.ExecutePowerShell("New-KFIISSiteBinding", parameters); - // _logger.LogTrace("Returned from executing PS function (Add-KFCertificateToStore)"); - - // // This should return the thumbprint of the certificate - // if (_results != null && _results.Count > 0) - // { - // _logger.LogTrace($"Bound certificate with the thumbprint: '{thumbprint}' to site: '{bindingInfo.SiteName}'."); - // } - // else - // { - // _logger.LogTrace("No results were returned. There could have been an error while adding the certificate. Look in the trace logs for PowerShell informaiton."); - // } - //} - - //public bool UnBindCertificate(IISBindingInfo bindingInfo) - //{ - // _logger.LogTrace("Attempting to UnBind and execute PS function (Remove-KFIISBinding)"); - - // // Mandatory parameters - // var parameters = new Dictionary - // { - // { "SiteName", bindingInfo.SiteName }, - // { "IPAddress", bindingInfo.IPAddress }, - // { "Port", bindingInfo.Port }, - // }; - - // // Optional parameters - // if (!string.IsNullOrEmpty(bindingInfo.HostName)) { parameters.Add("HostName", bindingInfo.HostName); } - - // try - // { - // _results = _psHelper.ExecutePowerShell("Remove-KFIISBinding", parameters); - // _logger.LogTrace("Returned from executing PS function (Remove-KFIISBinding)"); - // return true; - // } - // catch (Exception) - // { - // return false; - // } - //} } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs index 109b554..e96fbe9 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/ReEnrollment.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs index 2ec96e0..4988ada 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/WinIISBinding.cs @@ -1,15 +1,25 @@ -using Keyfactor.Logging; +// Copyright 2025 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// 021225 rcp 2.6.0 Cleaned up and verified code + +using Keyfactor.Logging; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Management.Automation; -using System.Net; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; -using System.Web.Services.Description; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU { diff --git a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs b/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs deleted file mode 100644 index dc5926d..0000000 --- a/IISU/ImplementedStoreTypes/WinIIS/WinIISInventory.cs +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.PowerShell; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Text; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU -{ - public class WinIISInventory : ClientPSCertStoreInventory - { - private ILogger _logger; - - public WinIISInventory() - { - _logger = LogHandler.GetClassLogger(); - } - - public WinIISInventory(ILogger logger) : base(logger) - { - _logger = logger; - } - - public List GetInventoryItems(RemoteSettings settings, string storePath) - { - _logger.LogTrace("Entering IISU GetInventoryItems"); - - // Get the raw certificate inventory from cert store - List certificates = base.GetCertificatesFromStore(settings, storePath); - - // Contains the inventory items to be sent back to KF - List myBoundCerts = new List(); - - _logger.LogTrace("Attempting to establish PowerShell connection."); - using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) - { - _logger.LogTrace("Initializing connection"); - ps.Initialize(); - - var scriptParameters = new Dictionary - { - { "isRemote", false} - }; - try - { - var iisBindings = ps.ExecuteCommand(PSHelper.LoadScript("ReadBoundCertificates.ps1"), scriptParameters); - if (iisBindings.Count == 0) - { - _logger.LogTrace("No binding certificates were found. Exiting IISU GetInventoryItems."); - return myBoundCerts; - } - - foreach (var binding in iisBindings) - { - var thumbPrint = $"{(binding.Properties["thumbprint"]?.Value)}"; - if (string.IsNullOrEmpty(thumbPrint)) continue; - - Certificate foundCert = certificates.Find(m => m.Thumbprint.Equals(thumbPrint)); - - if (foundCert == null) continue; - - var sniValue = ""; - switch (Convert.ToInt16(binding.Properties["sniFlg"]?.Value)) - { - case 0: - sniValue = "0 - No SNI"; - break; - case 1: - sniValue = "1 - SNI Enabled"; - break; - case 2: - sniValue = "2 - Non SNI Binding"; - break; - case 3: - sniValue = "3 - SNI Binding"; - break; - } - - var siteSettingsDict = new Dictionary - { - { "SiteName", binding.Properties["Name"]?.Value }, - { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, - { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, - { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, - { "SniFlag", sniValue }, - { "Protocol", binding.Properties["Protocol"]?.Value }, - { "ProviderName", foundCert.CryptoServiceProvider }, - { "SAN", foundCert.SAN } - }; - - myBoundCerts.Add( - new CurrentInventoryItem - { - Certificates = new[] { foundCert.CertificateData }, - Alias = thumbPrint, - PrivateKeyEntry = foundCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = siteSettingsDict - } - ); - } - - _logger.LogTrace($"Found {myBoundCerts.Count} bound certificates. Exiting IISU GetInventoryItems."); - return myBoundCerts; - } - catch (Exception) - { - _logger.LogError($"An error occurred while attempting to execute script: ReadBoundCertificates.ps1"); - return myBoundCerts; - } - } - } - - public List GetInventoryItems(Runspace runSpace, string storePath) - { - _logger.LogTrace("Entering IISU GetInventoryItems"); - // Get the raw certificate inventory from cert store - List certificates = base.GetCertificatesFromStore(runSpace, storePath); - - // Contains the inventory items to be sent back to KF - List myBoundCerts = new List(); - - using (PowerShell ps2 = PowerShell.Create()) - { - ps2.Runspace = runSpace; - - if (runSpace.RunspaceIsRemote) - { - ps2.AddCommand("Import-Module") - .AddParameter("Name", "WebAdministration") - .AddStatement(); - } - else - { - ps2.AddScript("Set-ExecutionPolicy RemoteSigned -Scope Process -Force"); - ps2.AddScript("Import-Module WebAdministration"); - } - - var searchScript = "Foreach($Site in get-website) { Foreach ($Bind in $Site.bindings.collection) {[pscustomobject]@{name=$Site.name;Protocol=$Bind.Protocol;Bindings=$Bind.BindingInformation;thumbprint=$Bind.certificateHash;sniFlg=$Bind.sslFlags}}}"; - ps2.AddScript(searchScript); - - _logger.LogTrace($"Attempting to initiate the following script:\n{searchScript}"); - - var iisBindings = ps2.Invoke(); - - if (ps2.HadErrors) - { - _logger.LogTrace("The previous script encountered errors. See below for more info."); - string psError = string.Empty; - try - { - psError = ps2.Streams.Error.ReadAll().Aggregate(String.Empty, (current, error) => current + (error.ErrorDetails?.Message ?? error.Exception.ToString())); - } - catch - { - } - - if (psError != null) { throw new Exception(psError); } - - } - - if (iisBindings.Count == 0) - { - _logger.LogTrace("No binding certificates were found. Exiting IISU GetInventoryItems."); - return myBoundCerts; - } - - //in theory should only be one, but keeping for future update to chance inventory - foreach (var binding in iisBindings) - { - var thumbPrint = $"{(binding.Properties["thumbprint"]?.Value)}"; - if (string.IsNullOrEmpty(thumbPrint)) continue; - - Certificate foundCert = certificates.Find(m => m.Thumbprint.Equals(thumbPrint)); - - if (foundCert == null) continue; - - var siteSettingsDict = new Dictionary - { - { "SiteName", binding.Properties["Name"]?.Value }, - { "Port", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[1] }, - { "IPAddress", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[0] }, - { "HostName", binding.Properties["Bindings"]?.Value.ToString()?.Split(':')[2] }, - { "SniFlag", binding.Properties["sniFlg"]?.Value.ToString() }, - { "Protocol", binding.Properties["Protocol"]?.Value }, - { "ProviderName", foundCert.CryptoServiceProvider }, - { "SAN", foundCert.SAN } - }; - - myBoundCerts.Add( - new CurrentInventoryItem - { - Certificates = new[] { foundCert.CertificateData }, - Alias = thumbPrint + ":" + binding.Properties["Bindings"]?.Value.ToString(), - PrivateKeyEntry = foundCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = siteSettingsDict - } - ); - } - } - - _logger.LogTrace($"Found {myBoundCerts.Count} bound certificates. Exiting IISU GetInventoryItems."); - return myBoundCerts; - } - } -} diff --git a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs index 46df5df..eb50428 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Inventory.cs @@ -14,6 +14,8 @@ // Ignore Spelling: Keyfactor Sql +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; @@ -24,7 +26,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Management.Automation; -using System.Security.Cryptography.X509Certificates; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql @@ -165,116 +166,5 @@ public List QuerySQLCertificates(RemoteSettings settings, return Inventory; } - - - private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) - { - try - { - var inventoryItems = new List(); - - string myConfig = config.ToString(); - - _logger.LogTrace(JobConfigurationParser.ParseInventoryJobConfiguration(config)); - - string serverUserName = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server UserName", config.ServerUsername); - string serverPassword = PAMUtilities.ResolvePAMField(_resolver, _logger, "Server Password", config.ServerPassword); - - // Deserialize specific job properties - var jobProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - string protocol = jobProperties.WinRmProtocol; - string port = jobProperties.WinRmPort; - bool IncludePortInSPN = jobProperties.SpnPortFlag; - string clientMachineName = config.CertificateStoreDetails.ClientMachine; - string storePath = config.CertificateStoreDetails.StorePath; - - if (storePath != null) - { - // Create the remote connection class to pass to Inventory Class - RemoteSettings settings = new(); - settings.ClientMachineName = config.CertificateStoreDetails.ClientMachine; - settings.Protocol = jobProperties.WinRmProtocol; - settings.Port = jobProperties.WinRmPort; - settings.IncludePortInSPN = jobProperties.SpnPortFlag; - settings.ServerUserName = serverUserName; - settings.ServerPassword = serverPassword; - - //SQLServerInventory sqlInventory = new SQLServerInventory(_logger); - //inventoryItems = sqlInventory.GetInventoryItems(myRunspace, config); - - - - // - _logger.LogTrace($"Establishing runspace on client machine: {clientMachineName}"); - using var myRunspace = PSHelper.GetClientPsRunspace(protocol, clientMachineName, port, IncludePortInSPN, serverUserName, serverPassword); - myRunspace.Open(); - - _logger.LogTrace("Runspace is now open"); - _logger.LogTrace($"Attempting to read bound SQL Server certificates from cert store: {storePath}"); - - SQLServerInventory sqlInventory = new SQLServerInventory(_logger); - inventoryItems = sqlInventory.GetInventoryItems(myRunspace, config); - if (inventoryItems != null) - { - _logger.LogTrace($"A total of {inventoryItems.Count} were found"); - _logger.LogTrace("Closing runspace..."); - myRunspace.Close(); - - _logger.LogTrace("Invoking Inventory.."); - submitInventory.Invoke(inventoryItems); - _logger.LogTrace($"Inventory Invoked... {inventoryItems.Count} Items"); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; - } - else - { - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = "Inventory Items was null, ensure sql server is installed on the machine." - }; - } - } - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Warning, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"No certificates were found in the Certificate Store Path: {storePath} on server: {clientMachineName}" - }; - } - catch (CertificateStoreException psEx) - { - _logger.LogTrace(psEx.Message); - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = - $"Unable to open remote certificate store: {LogHandler.FlattenException(psEx)}" - }; - } - catch (Exception ex) - { - _logger.LogTrace(LogHandler.FlattenException(ex)); - - var failureMessage = $"Inventory job failed for Site '{config.CertificateStoreDetails.StorePath}' on server '{config.CertificateStoreDetails.ClientMachine}' with error: '{LogHandler.FlattenException(ex)}'"; - _logger.LogWarning(failureMessage); - - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = failureMessage - }; - } - } } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index bf5ad4e..e5b1470 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -14,20 +14,16 @@ // Ignore Spelling: thumbprint Keyfactor sql +// 021225 rcp 2.6.0 Cleaned up and verified code + using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Drawing.Text; -using System.IO; using System.Management.Automation; -using System.Management.Automation.Runspaces; -using System.Numerics; -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.IISU; using Keyfactor.Logging; using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.Logging; using Newtonsoft.Json; @@ -310,95 +306,5 @@ public string AddCertificate(string certificateContents, string privateKeyPasswo throw new Exception(failureMessage); } } - - //private JobResult BindSQLCertificate(string newThumbprint, string renewalThumbprint) - //{ - // bool hadError = false; - // var instances = SQLInstanceNames.Split(","); - - // foreach (var instanceName in instances) - // { - // var parameters = new Dictionary - // { - // { "Thumbprint", newThumbprint }, - // { "SqlInstanceName", instanceName.Trim() }, - // { "StoreName", _storePath }, - // { "RestartService", RestartSQLService } - // }; - - // try - // { - // _results = _psHelper.ExecutePowerShell("Bind-CertificateToSqlInstance", parameters); - // _logger.LogTrace("Return from executing PS function (Bind-CertificateToSqlInstance)"); - // } - // catch (Exception ex) - // { - // _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); - // hadError= true; - // } - // } - - // if (hadError) - // { - // return new JobResult - // { - // Result = OrchestratorJobStatusJobResult.Failure, - // JobHistoryId = _jobHistoryID, - // FailureMessage = "Unable to bind one or more certificates to the SQL Instances." - // }; - // } else - // { - // return new JobResult - // { - // Result = OrchestratorJobStatusJobResult.Success, - // JobHistoryId = _jobHistoryID, - // FailureMessage = "" - // }; - // } - //} - - //private JobResult UnBindSQLCertificate() - //{ - // bool hadError = false; - // var instances = SQLInstanceNames.Split(","); - - // foreach (var instanceName in instances) - // { - // var parameters = new Dictionary - // { - // { "SqlInstanceName", instanceName.Trim() } - // }; - - // try - // { - // _results = _psHelper.ExecutePowerShell("UnBind-KFSqlServerCertificate", parameters); - // _logger.LogTrace("Returned from executing PS function (UnBind-KFSqlServerCertificate)"); - // } - // catch (Exception ex) - // { - // _logger.LogError($"Error occurred while binding certificate to SQL Instance {instanceName}", ex); - // hadError = true; - // } - // } - - // if (hadError) - // { - // return new JobResult - // { - // Result = OrchestratorJobStatusJobResult.Failure, - // JobHistoryId = _jobHistoryID, - // FailureMessage = "Unable to unbind one or more certificates from the SQL Instances." - // }; - // } - // else - // { - // return new JobResult - // { - // Result = OrchestratorJobStatusJobResult.Success, - // JobHistoryId = _jobHistoryID, - // FailureMessage = "" - // }; - // } - //} } } \ No newline at end of file diff --git a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs index 8ee0df6..1154a5a 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/ReEnrollment.cs @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; diff --git a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs b/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs deleted file mode 100644 index 6c278b8..0000000 --- a/IISU/ImplementedStoreTypes/WinSQL/SQLServerInventory.cs +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2022 Keyfactor -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Microsoft.Extensions.Logging; -using Microsoft.Management.Infrastructure; -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Management.Automation; -using System.Management.Automation.Runspaces; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql -{ - internal class SQLServerInventory : ClientPSCertStoreInventory - { - private string SqlInstanceName { get; set; } - private ILogger _logger; - - public SQLServerInventory(ILogger logger) : base(logger) - { - _logger = logger; - } - - public List GetInventoryItems(RemoteSettings settings, InventoryJobConfiguration jobConfig) - { - //var jobProperties = JsonConvert.DeserializeObject(jobConfig.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - //List certificates = base.GetCertificatesFromStore(settings, jobConfig.CertificateStoreDetails.StorePath); - - List myBoundCerts = new List(); - - //_logger.LogTrace("Attempting to establish PowerShell connection."); - //using (PSHelper ps = new(settings.Protocol, settings.Port, settings.IncludePortInSPN, settings.ClientMachineName, settings.ServerUserName, settings.ServerPassword)) - //{ - // // Get the list of SQL Instances on the machine - // var instances = ps.ExecuteCommand(PSHelper.LoadScript("GetSQLInstances.ps1")); - // if (instances != null && instances[0] != null) - // { - // //var psSqlManager = new ClientPsSqlManager(jobConfig, runSpace); - // var commonInstances = new Dictionary(); - - // foreach (var instance in instances) - // { - // var regLocation = psSqlManager.GetSqlCertRegistryLocation(instance.ToString(), ps2); - - // funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate"); - // ps2.AddScript(funcScript); - // //_logger.LogTrace("funcScript added..."); - // var thumbprint = ps2.Invoke()[0].ToString(); - // ps2.Commands.Clear(); - // if (string.IsNullOrEmpty(thumbprint)) continue; - // thumbprint = thumbprint.ToUpper(); - - // if (!commonInstances.ContainsKey(thumbprint)) - // { - // commonInstances.Add(thumbprint, instance.ToString()); - // } - // else - // { - // commonInstances[thumbprint] = commonInstances[thumbprint] + "," + instance.ToString(); - // } - // } - - // foreach (var kp in commonInstances) - // { - // Certificate foundCert = certificates.Find(m => m.Thumbprint.ToUpper().Equals(kp.Key)); - - // if (foundCert == null) continue; - - // var sqlSettingsDict = new Dictionary - // { - // { "InstanceName", kp.Value.ToString() }, - // { "ProviderName", foundCert.CryptoServiceProvider } - // }; - - // myBoundCerts.Add( - // new CurrentInventoryItem - // { - // Certificates = new[] { foundCert.CertificateData }, - // Alias = kp.Key, - // PrivateKeyEntry = foundCert.HasPrivateKey, - // UseChainLevel = false, - // ItemStatus = OrchestratorInventoryItemStatus.Unknown, - // Parameters = sqlSettingsDict - // }); - // } - return myBoundCerts; - // } - // else - // { - // return null; - // } - - //} - - } - - public List GetInventoryItems(Runspace runSpace, InventoryJobConfiguration jobConfig) - { - var jobProperties = JsonConvert.DeserializeObject(jobConfig.CertificateStoreDetails.Properties, new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); - //SqlInstanceName = jobProperties.SqlInstanceName; - - // Get the raw certificate inventory from cert store - List certificates = base.GetCertificatesFromStore(runSpace, jobConfig.CertificateStoreDetails.StorePath); - - // Contains the inventory items to be sent back to KF - List myBoundCerts = new List(); - using (PowerShell ps2 = PowerShell.Create()) - { - //runSpace.Open(); - ps2.Runspace = runSpace; - - //Get all the installed instances of Sql Server - var funcScript = string.Format(@$"(Get-ItemProperty ""HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server"").InstalledInstances"); - ps2.AddScript(funcScript); - //.LogTrace("funcScript added..."); - var instances = ps2.Invoke(); - ps2.Commands.Clear(); - var psSqlManager = new ClientPsSqlManager(jobConfig, runSpace); - var commonInstances=new Dictionary(); - - if (instances != null && instances[0] != null) - { - foreach (var instance in instances) - { - var regLocation = psSqlManager.GetSqlCertRegistryLocation(instance.ToString(), ps2); - - funcScript = string.Format(@$"Get-ItemPropertyValue ""{regLocation}"" -Name Certificate"); - ps2.AddScript(funcScript); - //_logger.LogTrace("funcScript added..."); - var thumbprint = ps2.Invoke()[0].ToString(); - ps2.Commands.Clear(); - if (string.IsNullOrEmpty(thumbprint)) continue; - thumbprint = thumbprint.ToUpper(); - - if (!commonInstances.ContainsKey(thumbprint)) - { - commonInstances.Add(thumbprint, instance.ToString()); - } - else - { - commonInstances[thumbprint] = commonInstances[thumbprint] + "," + instance.ToString(); - } - } - - foreach (var kp in commonInstances) - { - Certificate foundCert = certificates.Find(m => m.Thumbprint.ToUpper().Equals(kp.Key)); - - if (foundCert == null) continue; - - var sqlSettingsDict = new Dictionary - { - { "InstanceName", kp.Value.ToString() }, - { "ProviderName", foundCert.CryptoServiceProvider } - }; - - myBoundCerts.Add( - new CurrentInventoryItem - { - Certificates = new[] { foundCert.CertificateData }, - Alias = kp.Key, - PrivateKeyEntry = foundCert.HasPrivateKey, - UseChainLevel = false, - ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Parameters = sqlSettingsDict - }); - } - return myBoundCerts; - } - else - { - return null; - } - } - - } - } -} diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs index 13cf61d..c9c0588 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSQLCertificateInfo.cs @@ -1,10 +1,20 @@ -// Ignore Spelling: Keyfactor sql +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// 021225 rcp 2.6.0 Cleaned up and verified code + +// Ignore Spelling: Keyfactor sql namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql { diff --git a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs index 9f649c8..2a0be33 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/WinSqlBinding.cs @@ -1,16 +1,27 @@ -// Ignore Spelling: Keyfactor Sql +// Copyright 2022 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// 021225 rcp 2.6.0 Cleaned up and verified code + +// Ignore Spelling: Keyfactor Sql using Keyfactor.Logging; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Linq; using System.Management.Automation; -using System.Text; -using System.Threading.Tasks; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.WinSql { diff --git a/IISU/JobConfigurationParser.cs b/IISU/JobConfigurationParser.cs index 4d64749..91bfb98 100644 --- a/IISU/JobConfigurationParser.cs +++ b/IISU/JobConfigurationParser.cs @@ -12,17 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Orchestrators.Extensions; -using Microsoft.PowerShell.Commands; using Newtonsoft.Json; using System; -using System.Collections.Generic; -using System.Configuration.Internal; -using System.Diagnostics.Contracts; -using System.Linq; -using System.Management.Automation.Remoting; -using System.Net; -using System.Text; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { diff --git a/IISU/PSHelper.cs b/IISU/PSHelper.cs index 0619382..09e2341 100644 --- a/IISU/PSHelper.cs +++ b/IISU/PSHelper.cs @@ -1,4 +1,4 @@ -// Copyright 2022 Keyfactor +// Copyright 2025 Keyfactor // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -using Keyfactor.Extensions.Orchestrator.WindowsCertStore.Models; +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Logging; -using Keyfactor.Orchestrators.Extensions; using Microsoft.Extensions.Logging; using System; using System.Collections; @@ -29,10 +29,7 @@ using System.Net; using System.Runtime.InteropServices; using System.Security.AccessControl; -using System.Text; -using System.Text.Json; using System.Threading; -using System.Xml.Serialization; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { @@ -118,8 +115,8 @@ public void Initialize() _logger.LogDebug($"isLocalMachine flag set to: {isLocalMachine}"); _logger.LogDebug($"Protocol is set to: {protocol}"); - scriptFileLocation = FindPSLocation(AppDomain.CurrentDomain.BaseDirectory, "WinCertFull.ps1"); - if (scriptFileLocation == null) { throw new Exception("Unable to find the accompanying PowerShell Script file: WinCertFull.ps1"); } + scriptFileLocation = FindPSLocation(AppDomain.CurrentDomain.BaseDirectory, "WinCertScripts.ps1"); + if (scriptFileLocation == null) { throw new Exception("Unable to find the accompanying PowerShell Script file: WinCertScripts.ps1"); } _logger.LogTrace($"Script file located here: {scriptFileLocation}"); @@ -144,7 +141,7 @@ public void Initialize() HostName = $hostName } | ConvertTo-Json "; - var results = ExecutePowerShellScript(psInfo); + var results = ExecutePowerShell(psInfo,isScript:true); foreach (var result in results) { _logger.LogTrace($"{result}"); @@ -250,7 +247,6 @@ private void InitializeLocalSession() PS.Commands.Clear(); // Clear commands after loading functions } - public void Terminate() { PS.Commands.Clear(); @@ -291,11 +287,6 @@ public void Terminate() PS.Dispose(); } - public Collection? ExecuteFunction(string functionName) - { - return ExecutePowerShell(functionName); - } - public Collection? InvokeFunction(string functionName, Dictionary? parameters = null) { PS.Commands.Clear(); @@ -419,147 +410,6 @@ public Collection ExecutePowerShellScript(string script) } } - - public Collection? ExecutePowerShellV3(string commandName, Dictionary? parameters = null) - { - PS.Commands.Clear(); - PS.AddCommand(commandName); - - if (parameters != null) - { - foreach (var param in parameters) - { - PS.AddParameter(param.Key, param.Value); - } - } - - if (!isLocalMachine) - { - PS.AddParameter("Session", _PSSession); - } - - var results = PS.Invoke(); - CheckErrors(); - - return results; - } - - - public Collection? ExecutePowerShellV2(string commandName, Dictionary? parameters = null) - { - try - { - string scriptBlock; - - if (parameters != null && parameters.Count > 0) - { - _logger.LogTrace("Creating script block with parameters."); - string paramBlock = string.Join(", ", parameters.Select(p => $"[{p.Value.GetType().Name}] ${p.Key}")); - string paramValues = string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}")); - - //scriptBlock = $@" - // param({paramBlock}) - // {commandName} {paramUsage} - //"; - - scriptBlock = $@" - param({paramBlock}) - {paramValues} - {commandName} {string.Join(" ", parameters.Select(p => $"-{p.Key} ${p.Key}"))} - "; - } - else - { - _logger.LogTrace("Creating script block with no parameters."); - scriptBlock = commandName; - } - - //PS.AddCommand("Invoke-Command") - // .AddParameter("ScriptBlock", scriptBlock); - - //.AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)); - - if (!isLocalMachine) - { - PS.AddCommand("Invoke-Command") - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)) - .AddParameter("Session", _PSSession); - } - else - { - PS.AddScript(scriptBlock); - } - - if (parameters != null && parameters.Count > 0) - { - PS.AddParameter("ArgumentList", parameters.Values.ToArray()); - } - - _logger.LogTrace($"Executing script block:\n{scriptBlock}"); - - var results = PS.Invoke(); - - if (PS.HadErrors) - { - string errorMessages = string.Join("; ", PS.Streams.Error.Select(e => e.ToString())); - _logger.LogError($"{errorMessages}"); - throw new Exception($"PowerShell execution errors: {errorMessages}"); - } - - return results; - } - catch (Exception ex) - { - _logger.LogError(ex.Message); - throw new Exception(ex.Message); - } - finally - { - PS.Commands.Clear(); - } - } - - - [Obsolete] - public Collection? ExecuteCommand(string scriptBlock, Dictionary parameters = null) - { - _logger.LogTrace("Executing PowerShell Script"); - - using (PS) - { - PS.AddCommand("Invoke-Command") - .AddParameter("Session", _PSSession) // send session only when necessary (remote) - .AddParameter("ScriptBlock", ScriptBlock.Create(scriptBlock)); - - // Add parameters to the script block - if (parameters != null) - { - foreach (var parameter in parameters) - { - PS.AddParameter("ArgumentList", parameters.Values.ToArray()); - } - } - - try - { - _logger.LogTrace("Ready to invoke the script"); - var results = PS.Invoke(); - CheckErrors(); - - var jsonResults = results[0].ToString(); - var certInfoList = JsonSerializer.Deserialize>(jsonResults); - - return results; - } - catch (Exception ex) - { - _logger.LogError($"Exception: {ex.Message}"); - return null; - } - - } - } - private void CheckErrors() { _logger.LogTrace("Checking PowerShell session for errors."); @@ -689,112 +539,6 @@ public void Dispose() } } - - // Code below is ORIGINAL - public static Runspace GetClientPsRunspace(string winRmProtocol, string clientMachineName, string winRmPort, bool includePortInSpn, string serverUserName, string serverPassword) - { - _logger = LogHandler.GetClassLogger(); - _logger.MethodEntry(); - - // 2.4 - Client Machine Name now follows the naming conventions of {clientMachineName}|{localMachine} - // If the clientMachineName is just 'localhost', it will maintain that as locally only (as previously) - // If there is no 2nd part to the clientMachineName, a remote PowerShell session will be created - - // Break the clientMachineName into parts - string[] parts = clientMachineName.Split('|'); - - // Extract the client machine name and arguments based upon the number of parts - string machineName = parts.Length > 1 ? parts[0] : clientMachineName; - string argument = parts.Length > 1 ? parts[1] : null; - - // Determine if this is truly a local connection - bool isLocal = (machineName.ToLower() == "localhost") || (argument != null && argument.ToLower() == "localmachine"); - - _logger.LogInformation($"Full clientMachineName={clientMachineName} | machineName={machineName} | argument={argument} | isLocal={isLocal}"); - - if (isLocal) - { -#if NET6_0 - PowerShellProcessInstance instance = new PowerShellProcessInstance(new Version(5, 1), null, null, false); - Runspace rs = RunspaceFactory.CreateOutOfProcessRunspace(new TypeTable(Array.Empty()), instance); - return rs; -#elif NET8_0_OR_GREATER - try - { - InitialSessionState iss = InitialSessionState.CreateDefault(); - Runspace rs = RunspaceFactory.CreateRunspace(iss); - return rs; - } - catch (global::System.Exception) - { - throw new Exception($"An error occurred while attempting to create the PowerShell instance. This version requires .Net8 and PowerShell SDK 7.2 or greater. Please verify the version of .Net8 and PowerShell installed on your machine."); - } -#endif - } - else - { - var connInfo = new WSManConnectionInfo(new Uri($"{winRmProtocol}://{clientMachineName}:{winRmPort}/wsman")); - connInfo.IncludePortInSPN = includePortInSpn; - - _logger.LogTrace($"Creating remote session at: {connInfo.ConnectionUri}"); - - if (!string.IsNullOrEmpty(serverUserName)) - { - _logger.LogTrace($"Credentials Specified"); - var pw = new NetworkCredential(serverUserName, serverPassword).SecurePassword; - connInfo.Credential = new PSCredential(serverUserName, pw); - } - return RunspaceFactory.CreateRunspace(connInfo); - } - } - - public static IEnumerable GetCSPList(Runspace myRunspace) - { - _logger.LogTrace("Getting the list of Crypto Service Providers"); - - using var ps = PowerShell.Create(); - - ps.Runspace = myRunspace; - - var certStoreScript = $@" - $certUtilOutput = certutil -csplist - - $cspInfoList = @() - foreach ($line in $certUtilOutput) {{ - if ($line -match ""Provider Name:"") {{ - $cspName = ($line -split "":"")[1].Trim() - $cspInfoList += $cspName - }} - }} - - $cspInfoList"; - - ps.AddScript(certStoreScript); - - foreach (var result in ps.Invoke()) - { - var cspName = result?.BaseObject?.ToString(); - if (cspName != null) { yield return cspName; } - } - - _logger.LogInformation("No Crypto Service Providers were found"); - yield return null; - } - - public static bool IsCSPFound(IEnumerable cspList, string userCSP) - { - foreach (var csp in cspList) - { - if (string.Equals(csp, userCSP, StringComparison.OrdinalIgnoreCase)) - { - _logger.LogTrace($"CSP found: {csp}"); - return true; - } - } - _logger.LogTrace($"CSP: {userCSP} was not found"); - return false; - } - private string createPrivateKeyFile() { string tmpFile = Path.GetTempFileName(); // "logs/AdminFile"; @@ -860,33 +604,5 @@ private static string formatPrivateKey(string privateKey) return privateKey.Replace($" {keyType} PRIVATE ", "^^^").Replace(" ", System.Environment.NewLine).Replace("^^^", $" {keyType} PRIVATE ") + System.Environment.NewLine; } - - //private string formatPrivateKey(string privateKey) - //{ - // // Identify the markers in the private key - // string beginMarker = "-----BEGIN OPENSSH PRIVATE KEY-----"; - // string endMarker = "-----END OPENSSH PRIVATE KEY-----"; - - // // Locate the positions of the markers - // int beginIndex = privateKey.IndexOf(beginMarker); - // int endIndex = privateKey.IndexOf(endMarker); - - // // Split the string into three parts: before, key content, and after - // string beforeKey = privateKey.Substring(0, beginIndex + beginMarker.Length); - // string keyContent = privateKey.Substring(beginIndex + beginMarker.Length, endIndex - beginIndex - beginMarker.Length); - // string afterKey = privateKey.Substring(endIndex); - - // // Replace spaces with actual carriage return and line feed in key content - // keyContent = keyContent.Replace(" ", "\r\n"); - - // // Construct the final string with the correctly formatted key - // string replacedFile = beforeKey + keyContent + afterKey + "\r\n"; - - // // Log the modified string - // _logger.LogTrace(replacedFile); - - // return replacedFile; - //} - } } diff --git a/IISU/PowerShellScripts/WinCertFull.ps1 b/IISU/PowerShellScripts/WinCertScripts.ps1 similarity index 100% rename from IISU/PowerShellScripts/WinCertFull.ps1 rename to IISU/PowerShellScripts/WinCertScripts.ps1 diff --git a/IISU/RemoteSettings.cs b/IISU/RemoteSettings.cs index 5a5ab33..43465b8 100644 --- a/IISU/RemoteSettings.cs +++ b/IISU/RemoteSettings.cs @@ -1,9 +1,18 @@ -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +// Copyright 2025 Keyfactor +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// 021225 rcp 2.6.0 Cleaned up and verified code namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore { diff --git a/IISU/Scripts/PowerShellScripts.cs b/IISU/Scripts/PowerShellScripts.cs deleted file mode 100644 index 3da10e3..0000000 --- a/IISU/Scripts/PowerShellScripts.cs +++ /dev/null @@ -1,140 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore.Scripts -{ - public class PowerShellScripts - { - public const string UpdateIISBindingsV6 = @" - param ( - $SiteName, # The name of the IIS site - $IPAddress, # The IP Address for the binding - $Port, # The port number for the binding - $Hostname, # Hostname for the binding (if any) - $Protocol, # Protocol (e.g., HTTP, HTTPS) - $Thumbprint, # Certificate thumbprint for HTTPS bindings - $StoreName, # Certificate store location (e.g., ""My"" for personal certs) - $SslFlags # SSL flags (if any) - ) - - # Set Execution Policy (optional, depending on your environment) - Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force - - # Check if the WebAdministration module is available - $module = Get-Module -Name WebAdministration -ListAvailable - - if (-not $module) { - throw ""The WebAdministration module is not installed on this system."" - } - - # Check if the WebAdministration module is already loaded - if (-not (Get-Module -Name WebAdministration)) { - try { - # Attempt to import the WebAdministration module - Import-Module WebAdministration -ErrorAction Stop - } - catch { - throw ""Failed to load the WebAdministration module. Ensure it is installed and available."" - } - } - - # Retrieve the existing binding information - $myBinding = ""${IPAddress}:${Port}:${Hostname}"" - Write-Host ""myBinding: "" $myBinding - - $siteBindings = Get-IISSiteBinding -Name $SiteName - $existingBinding = $siteBindings | Where-Object { $_.bindingInformation -eq $myBinding -and $_.protocol -eq $Protocol } - - Write-Host ""Binding:"" $existingBinding - - if ($null -ne $existingBinding) { - # Remove the existing binding - Remove-IISSiteBinding -Name $SiteName -BindingInformation $existingBinding.BindingInformation -Protocol $existingBinding.Protocol -Confirm:$false - - Write-Host ""Removed existing binding: $($existingBinding.BindingInformation)"" - } - - # Create the new binding with modified properties - $newBindingInfo = ""${IPAddress}:${Port}:${Hostname}"" - - New-IISSiteBinding -Name $SiteName ` - -BindingInformation $newBindingInfo ` - -Protocol $Protocol ` - -CertificateThumbprint $Thumbprint ` - -CertStoreLocation $StoreName ` - -SslFlag $SslFlags - - Write-Host ""New binding added: $newBindingInfo"""; - - public const string UpdateIISBindingsV8 = @" - param ( - $SiteName, # The name of the IIS site - $IPAddress, # The IP Address for the binding - $Port, # The port number for the binding - $Hostname, # Hostname for the binding (if any) - $Protocol, # Protocol (e.g., HTTP, HTTPS) - $Thumbprint, # Certificate thumbprint for HTTPS bindings - $StoreName, # Certificate store location (e.g., ""My"" for personal certs) - $SslFlags # SSL flags (if any) - ) - - # Set Execution Policy (optional, depending on your environment) - Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force - - ## Check if the IISAdministration module is available - #$module = Get-Module -Name IISAdministration -ListAvailable - - #if (-not $module) { - # throw ""The IISAdministration module is not installed on this system."" - #} - - # Check if the IISAdministration module is already loaded - if (-not (Get-Module -Name IISAdministration)) { - try { - # Attempt to import the IISAdministration module - Import-Module IISAdministration -ErrorAction Stop - } - catch { - throw ""Failed to load the IISAdministration module. Ensure it is installed and available."" - } - } - - # Retrieve the existing binding information - $myBinding = ""${IPAddress}:${Port}:${Hostname}"" - Write-Host ""myBinding: "" $myBinding - - $siteBindings = Get-IISSiteBinding -Name $SiteName - $existingBinding = $siteBindings | Where-Object { $_.bindingInformation -eq $myBinding -and $_.protocol -eq $Protocol } - - Write-Host ""Binding:"" $existingBinding - - if ($null -ne $existingBinding) { - # Remove the existing binding - Remove-IISSiteBinding -Name $SiteName -BindingInformation $existingBinding.BindingInformation -Protocol $existingBinding.Protocol -Confirm:$false - - Write-Host ""Removed existing binding: $($existingBinding.BindingInformation)"" - } - - # Create the new binding with modified properties - $newBindingInfo = ""${IPAddress}:${Port}:${Hostname}"" - - try - { - New-IISSiteBinding -Name $SiteName ` - -BindingInformation $newBindingInfo ` - -Protocol $Protocol ` - -CertificateThumbprint $Thumbprint ` - -CertStoreLocation $StoreName ` - -SslFlag $SslFlags - - Write-Host ""New binding added: $newBindingInfo"" - } - catch { - throw $_ - }"; - - } -} diff --git a/IISU/WinCertJobTypeBase.cs b/IISU/WinCertJobTypeBase.cs index 0baf794..792adf3 100644 --- a/IISU/WinCertJobTypeBase.cs +++ b/IISU/WinCertJobTypeBase.cs @@ -1,4 +1,4 @@ -// Copyright 2022 Keyfactor +// Copyright 2025 Keyfactor // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// 021225 rcp 2.6.0 Cleaned up and verified code + using Keyfactor.Orchestrators.Extensions.Interfaces; namespace Keyfactor.Extensions.Orchestrator.WindowsCertStore diff --git a/IISU/WindowsCertStore.csproj b/IISU/WindowsCertStore.csproj index 306e34a..a62026c 100644 --- a/IISU/WindowsCertStore.csproj +++ b/IISU/WindowsCertStore.csproj @@ -41,7 +41,7 @@ PreserveNewest - + Always diff --git a/WinCertTestConsole/WinCertTestConsole.csproj b/WinCertTestConsole/WinCertTestConsole.csproj index 6de159c..520e844 100644 --- a/WinCertTestConsole/WinCertTestConsole.csproj +++ b/WinCertTestConsole/WinCertTestConsole.csproj @@ -1,38 +1,38 @@ - - - - Exe - net8.0 - AnyCPU - - - - - - - - - - - - - - + + + + Exe + net8.0 + AnyCPU + + + + + + + + + + + + + + Always - - - Always - - - Always - - - Always - + + + Always + + + Always + + + Always + Always - - - - + + + + diff --git a/WinCertUnitTests/UnitTestIISBinding.cs b/WinCertUnitTests/UnitTestIISBinding.cs index f50dcd8..35d06c3 100644 --- a/WinCertUnitTests/UnitTestIISBinding.cs +++ b/WinCertUnitTests/UnitTestIISBinding.cs @@ -151,21 +151,6 @@ public void GetIISInventory() Listcerts = inv.QueryIISCertificates(settings); } - [TestMethod] - public void OrigSNIFlagZeroReturnsZero() - { - string expectedResult = "32"; - string result = ClientPSIIManager.MigrateSNIFlag("32"); - Assert.AreEqual(expectedResult, result); - } - - [TestMethod] - [ExpectedException(typeof(ArgumentOutOfRangeException))] - public void InvalidSNIFlagThrowException() - { - string result = ClientPSIIManager.MigrateSNIFlag("Bad value"); - } - static bool TestValidSslFlag(int sslFlag) { try diff --git a/WindowsCertStore.sln b/WindowsCertStore.sln index 7d2f736..883ef0b 100644 --- a/WindowsCertStore.sln +++ b/WindowsCertStore.sln @@ -36,8 +36,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{630203 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinCertTestConsole", "WinCertTestConsole\WinCertTestConsole.csproj", "{D0F4A3CC-5236-4393-9C97-AE55ACE319F2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinCertUnitTests", "WinCertUnitTests\WinCertUnitTests.csproj", "{AEE85A79-8614-447A-B14A-FD5A389C510B}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -61,14 +59,6 @@ Global {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|Any CPU.ActiveCfg = Release|Any CPU {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|Any CPU.Build.0 = Release|Any CPU {D0F4A3CC-5236-4393-9C97-AE55ACE319F2}.Release|x64.ActiveCfg = Release|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Debug|x64.ActiveCfg = Debug|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Debug|x64.Build.0 = Debug|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Release|Any CPU.Build.0 = Release|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Release|x64.ActiveCfg = Release|Any CPU - {AEE85A79-8614-447A-B14A-FD5A389C510B}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From e204db872aae6d8750b90b07a4102d415e0082c2 Mon Sep 17 00:00:00 2001 From: Bob Pokorny Date: Wed, 12 Feb 2025 20:47:42 -0800 Subject: [PATCH 21/21] Completed cleaning up code. Ready for Pre-Release. --- .../ImplementedStoreTypes/WinIIS/Inventory.cs | 22 +- IISU/PowerShellScripts/WinCertScripts.ps1 | 486 +----------------- 2 files changed, 18 insertions(+), 490 deletions(-) diff --git a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs index 083b302..f0a46d2 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Inventory.cs @@ -127,16 +127,18 @@ public List QueryIISCertificates(RemoteSettings settings) { ps.Initialize(); - if (ps.IsLocalMachine) - { - _logger.LogTrace("Executing function locally"); - results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); - } - else - { - _logger.LogTrace("Executing function remotely"); - results = ps.InvokeFunction("Get-KFIISBoundCertificates"); - } + //if (ps.IsLocalMachine) + //{ + // _logger.LogTrace("Executing function locally"); + // results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); + //} + //else + //{ + // _logger.LogTrace("Executing function remotely"); + // results = ps.InvokeFunction("Get-KFIISBoundCertificates"); + //} + + results = ps.ExecutePowerShell("Get-KFIISBoundCertificates"); // If there are certificates, deserialize the results and send them back to command if (results != null && results.Count > 0) diff --git a/IISU/PowerShellScripts/WinCertScripts.ps1 b/IISU/PowerShellScripts/WinCertScripts.ps1 index 0a54b3a..0bed17f 100644 --- a/IISU/PowerShellScripts/WinCertScripts.ps1 +++ b/IISU/PowerShellScripts/WinCertScripts.ps1 @@ -356,10 +356,10 @@ function New-KFIISSiteBinding{ $myBinding = "${IPAddress}:${Port}:${Hostname}" #*:443:MyHostName1 : *:443:ManualHostName Write-Verbose "Formatted binding information: $myBinding" - # Check if the binding exists (NOTE: Bindings always occurr using https) + # Check if the binding exists (NOTE: Bindings always occur using https) try { Write-Verbose "Attempting to get binding information for Site: '$SiteName' with bindings: $myBinding" - $existingBinding = Get-IISSiteBinding -Name $SiteName -Protocol $Protocol + $existingBinding = Get-IISSiteBinding -Name $SiteName -Protocol $Protocol -BindingInformation $myBinding } catch { Write-Verbose "Error occurred while attempting to get the bindings for: '$SiteName'" @@ -408,77 +408,6 @@ function New-KFIISSiteBinding{ return $True } -function New-KFIISSiteBindingOLD{ - param ( - [Parameter(Mandatory = $true)] - [string]$Thumbprint, - [Parameter(Mandatory = $true)] - [string]$WebSite, - [Parameter(Mandatory = $true)] - [string]$Protocol, - [Parameter(Mandatory = $true)] - [string]$IPAddress, - [Parameter(Mandatory = $true)] - [int32]$Port, - [Parameter(Mandatory = $False)] - [string]$HostName, - [Parameter(Mandatory = $true)] - [string]$SNIFlag, - [Parameter(Mandatory = $true)] - [string]$StoreName - ) - - Write-Information "INFO: Entered Add-CertificateToWebsites." - Write-Information "Thumbprint: $Thumbprint" - Write-Information "Website: $WebSite" - Write-Information "IPAddress: $IPAddress" - Write-Information "Port: $Port" - Write-Information "HostName: $HostName" - Write-Information "Store Path: $StoreName" - - Write-Information "Attempting to load WebAdministration" - Import-Module WebAdministration -ErrorAction Stop - - Write-Information "Web Administration module has been loaded and will be used" - - try { - # Get the certificate from the store from the supplied thumbprint - $certificate = Get-KFCertificateByThumbprint -Thumbprint $Thumbprint -StoreName $StoreName - if (-not $certificate) { - Write-Error "Certificate with thumbprint: $thumbprint could not be retrieved. Exiting IIS Binding." - return - } - - $bindingInfo = "$($IPAddress):$($Port):$($HostName)" - $Binding = Get-WebBinding -Name $Website | Where-Object {$_.bindingInformation -eq $bindingInfo} - - if ($binding) { - $bindingInfo = "*:{$Port}:$HostName" - $bindingItem = Get-Item "IIS:\SslBindings\$bindingInfo" - - # Check if the binding already has a certificate thumbprint - $existingThumbprint = (Get-ItemProperty -Path $bindingItem.PSPath -Name CertificateThumbprint).CertificateThumbprint - - if ($existingThumbprint -ne $cert.Thumbprint) { - # Update the binding with the new SSL certificate's thumbprint - Set-ItemProperty -Path $bindingItem.PSPath -Name CertificateThumbprint -Value $cert.Thumbprint - Write-Output "Updated binding with new certificate thumbprint." - } else { - Write-Output "The binding already has the correct certificate thumbprint." - } - } else { - # If the binding doesn't exist, create it - New-WebBinding -Name $Website -Protocol $Protocol -Port $Port -IPAddress $IPAddress -HostHeader $HostName -SslFlags $SNIFlag - $NewBinding = Get-WebBinding -Name $Website -Protocol $Protocol -Port $port -IPAddress $IPAddress - $NewBinding.AddSslCertificate($Certificate.Thumbprint, $StorePath) - Write-Output "Created new binding and assigned certificate thumbprint." - } - - } catch { - Write-Host "ERROR: An error occurred while binding the certificate: $_" - } -} - function Remove-KFIISSiteBinding{ param ( [Parameter(Mandatory=$true)] $SiteName, # The name of the IIS website @@ -536,57 +465,6 @@ function Remove-KFIISSiteBinding{ } } -function Remove-KFIISBindingOLD{ - param ( - [Parameter(Mandatory=$true)] - [string]$SiteName, # The name of the IIS website - - [Parameter(Mandatory=$true)] - [string]$IPAddress, # The IP address of the binding - - [Parameter(Mandatory=$true)] - [int]$Port, # The port number (e.g., 443 for HTTPS) - - [Parameter(Mandatory=$false)] - [string]$Hostname # The hostname (empty string for binding without hostname) - ) - - # Import WebAdministration module if it's not already imported - if (-not (Get-Module -ListAvailable -Name WebAdministration)) { - Import-Module WebAdministration - } - - try { - # Build the Binding Information format (IP:Port:Hostname) - $bindingInfo = "$($IPAddress):$($Port):$($HostName)" - - # Get all HTTPS bindings for the site - $bindings = Get-WebBinding -Name $SiteName -Protocol "https" - - if (-not $bindings) { - Write-Host "No HTTPS bindings found for site '$SiteName'." - return - } - - # Find the binding that matches the provided IP, Port, and Hostname - $bindingToRemove = $bindings | Where-Object { - $_.bindingInformation -eq $bindingInfo - } - - if (-not $bindingToRemove) { - Write-Host "No matching HTTPS binding found with IP '$IPAddress', Port '$Port', and Hostname '$Hostname' for site '$SiteName'." - return - } - - # Remove the binding from IIS - Remove-WebBinding -Name $SiteName -IPAddress $IPAddress -Port $Port -HostHeader $Hostname -Protocol "https" - Write-Host "Removed HTTPS binding from site '$SiteName' (IP: $IPAddress, Port: $Port, Hostname: $Hostname)." - - } catch { - Write-Error "An error occurred while trying to remove the certificate binding: $_" - } -} - # Function to get certificate information for a SQL Server instance function GET-KFSQLInventory { # Retrieve all SQL Server instances @@ -659,254 +537,6 @@ function GET-KFSQLInventory { return $myBoundCerts | ConvertTo-Json } -function Set-SQLCertificateAclOLD { - param ( - [Parameter(Mandatory = $true)] - [string]$Thumbprint, - - [Parameter(Mandatory = $true)] - [string]$SqlServiceUser - ) - - Write-Information "Entered Set-SQLCertificateAcl" - - # Get the certificate from the LocalMachine\My store - try { - $certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } - } catch { - Write-Error "Error retrieving certificate: $_" - return - } - - if (-not $certificate) { - Write-Error "Certificate with thumbprint $Thumbprint not found in LocalMachine\My store." - return - } - - Write-Information "Obtained the certificate" - - # Retrieve the private key information - try { - if (-not $certificate.HasPrivateKey) { - throw "Certificate does not have a private key." - } - - # Use new method to retrieve the private key container name if needed - $privKey = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName - if (-not $privKey) { - throw "Failed to retrieve private key container name." - } - } catch { - Write-Error "Error retrieving private key: $_" - return - } - - Write-Information "Private Key: '$privKey'" - - # Determine the key path - $keyPath = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\" - $privKeyPath = "$keyPath$privKey" - - if (-not (Test-Path $privKeyPath)) { - Write-Error "Private key file does not exist at path: $privKeyPath" - return - } - - Write-Information "Private Key Path is: $privKeyPath" - - # Retrieve the current ACL for the private key - try { - $Acl = Get-Acl -Path $privKeyPath - $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($SqlServiceUser, "Read", "Allow") - - # Add the new access rule - Write-Information "Attempting to add new Access Rule" - $Acl.SetAccessRule($Ar) - Write-Information "Access Rule has been added" - - # Set the new ACL on the private key file - Write-Information "Attaching the ACL on the private key file" - Set-Acl -Path $privKeyPath -AclObject $Acl - - Write-Output "ACL updated successfully for the private key." - } catch { - Write-Error "Error updating ACL: $_" - } -} - -function GET-KFSQLInventoryOLD{ - # Get all SQL Server instances - $sqlInstances = (Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server").InstalledInstances - Write-Information "There are $sqlInstances.Count instances that will be checked for certificates." - - # Initialize an array to store the results - $certificates = @() - - foreach ($instance in $sqlInstances) { - Write-Information "Checking instance: $instance for Certificates." - - # Get the SQL Full Instance Name - $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $instance - - $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\$fullInstanceName\MSSQLServer\SuperSocketNetLib" - $certInfo = Get-ItemProperty -Path $regPath -ErrorAction SilentlyContinue - - if ($certInfo) { - $certHash = $certInfo.Certificate - $certStore = "My" # Certificates are typically stored in the Personal store - - if ($certHash) { - $cert = Get-ChildItem -Path "Cert:\LocalMachine\$certStore\$certHash" -ErrorAction SilentlyContinue - - if ($cert) { - $certInfo = [PSCustomObject]@{ - InstanceName = $instance - StoreName = $certStore - Certificate = $cert.Subject - ExpiryDate = $cert.NotAfter - Issuer = $cert.Issuer - Thumbprint = $cert.Thumbprint - HasPrivateKey = $cert.HasPrivateKey - SAN = Get-KFSAN $cert - ProviderName = Get-CertificateCSP $cert - Base64Data = [System.Convert]::ToBase64String($cert.RawData) - } - - Write-Information "Certificate found for $instance." - - # Add the certificate information to the array - $certificates += $certInfo - } - } - } - } - - # Output the results - if ($certificates) { - $certificates | ConvertTo-Json - } -} - -function Set-SQLCertificateAclOLD { - param ( - [Parameter(Mandatory = $true)] - [string]$Thumbprint, - - [Parameter(Mandatory = $true)] - [string]$SqlServiceUser - ) - Write-Information "Entered Set-SQLCertificateAcl" - # Get the certificate from the LocalMachine store - $certificate = Get-ChildItem -Path "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $Thumbprint } - - if (-not $certificate) { - Write-Error "Certificate with thumbprint $Thumbprint not found in LocalMachine\My store." - return $null - } - Write-Information "Obtained the certificate" - - # Retrieve the private key information - $privKey = $certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName - Write-Information "Private Key: '$privKey'" - - $keyPath = "$($env:ProgramData)\Microsoft\Crypto\RSA\MachineKeys\" - $privKeyPath = (Get-Item "$keyPath\$privKey") - Write-Information "Private Key Path is: $privKeyPath" - - # Retrieve the current ACL for the private key - $Acl = Get-Acl $privKeyPath - $Ar = New-Object System.Security.AccessControl.FileSystemAccessRule($SqlServiceUser, "Read", "Allow") - - # Add the new access rule - Write-Information "Attempting to add new Access Rule" - $Acl.SetAccessRule($Ar) - Write-Information "Access Rule has been added" - - # Set the new ACL on the private key file - Write-Information "Attaching the ACL on the private key file" - Set-Acl -Path $privKeyPath.FullName -AclObject $Acl - - Write-Output "ACL updated successfully for the private key." -} - -function Bind-CertificateToSqlInstanceOLD { - param ( - [Parameter(Mandatory = $true)] - [string]$Thumbprint, # Thumbprint of the certificate to bind - [Parameter(Mandatory = $true)] - [string]$SqlInstanceName, # Name of the SQL Server instance (e.g., 'MSSQLSERVER' or 'SQLInstanceName') - [string]$StoreName = "My", # Certificate store name (default is 'My') - [Parameter(Mandatory = $false)] - [switch]$RestartService # Optional, restart sql instance if set to true - ) - Write-Information "Starting Bind-CertificateToSqlInstance for SQL Instance: $SqlInstanceName" - $store = $null - - try { - # Open the certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::"LocalMachine") - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) - - # Find the certificate by thumbprint - $certificate = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } - if (-not $certificate){ - throw "Certificate with thumbprint $Thumbprint not found in store LocalMachine\$StoreName" - } - - Write-Information "Found Certificate using thumbprint: $Thumbprint" - - # Get the SQL Full Instance Name - $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SqlInstanceName - if (-not $fullInstanceName) { - throw "Count not retrieve the SQL Full Instance Name for $SqlInstanceName." - } - - Write-Information "Obtained SQL Full Instance Name: $fullInstanceName" - - #Determine SQL Service name and user - $SQLServiceName = Get-SQLServiceName $fullInstanceName - $SQLServiceUser = Get-SQLServiceUser $SQLServiceName - if (-not $SQLServiceName -or -not $SQLServiceUser) { - throw "Failed to determine SQL Server service details." - } - - Write-Information "SQL Service Name: $SQLServiceName" - Write-Information "SQL Service User: $SQLServiceUser" - - # Update ACL for private key access - Set-SQLCertificateAcl -Thumbprint $Thumbprint -SQLServiceUser $SQLServiceUser - Write-Information "Updated ACL For SQL Service User: $SQLServiceUser" - - # Locate the SQL Server instance registry path - $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\${fullInstanceName}\MSSQLServer\SuperSocketNetLib" - if (-not (Test-Path $regPath)) { - throw "Could not find registry path for SQL instance: $fullInstanceName." - } - - # Set the certificate thumbprint in SQL Server's registry - Set-ItemProperty -Path $regPath -Name "Certificate" -Value $Thumbprint - Write-Information "Bound certificate to SQL Server instance $SqlInstanceName." - - if ($RestartService.IsPresent) { - Write-Information "Attempting to restart SQL Service Name: $SQLServiceName" - Restart-Service -Name $SQLServiceName -Force - Write-Information "SQL Server service restarted." - } else { - Write-Information "Please restart SQL Server service manually for changes to take effect." - } - - } catch { - Write-Error "SQL Binding error: $_" - } finally { - if ($store) { - # Close the certificate store - $store.Close() - Write-Information "Closed certificate store." - } - } - -} - function Bind-KFSqlCertificate { param ( [string]$SQLInstance, @@ -1114,45 +744,6 @@ function Unbind-KFSqlCertificate { # Example usage: # Bind-CertificateToSqlInstance -Thumbprint "123ABC456DEF" -SqlInstanceName "MSSQLSERVER" -function UnBind-KFSqlServerCertificateOLD { - param ( - [Parameter(Mandatory = $true)] - [string]$SqlInstanceName # Name of the SQL Server instance (e.g., 'MSSQLSERVER' or 'SQLInstanceName') - ) - - try { - - # Get the SQL Full Instance Name - $fullInstanceName = Get-ItemPropertyValue "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\Instance Names\SQL" -Name $SqlInstanceName - - # Get the SQL Server instance registry path - $regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\${fullInstanceName}\MSSQLServer\SuperSocketNetLib" - if (-not (Test-Path $regPath)) { - throw "Could not find registry path for SQL instance: $fullInstanceName." - } - - # Check if the registry contains the certificate thumbprint - $certificateThumbprint = (Get-ItemProperty -Path $regPath -Name "Certificate").Certificate - - if ($certificateThumbprint) - { - Write-Host "Found certificate thumbprint: $certificateThumbprint bound to SQL Server instance $SqlInstanceName." - Remove-ItemProperty -Path $regPath -Name "Certificate" - Write-Output "Certificate thumbprint unbound from SQL Server instance $SqlInstanceName." - return - } else { - Write-Output "No certificate is bound to SQL Server instance $SqlInstanceName." - return - } - - } catch { - Write-Error "An error occurred: $_" - } -} - -# Example usage: -# Clear-SqlServerCertificate -SqlInstanceName "MSSQLSERVER" - function Get-SqlServiceName { param ( [string]$InstanceName @@ -1164,30 +755,6 @@ function Get-SqlServiceName { } } -function Get-SQLServiceNameOLD{ - param ( - [Parameter(Mandatory = $true)] - [string]$SQLInstanceName - ) - - # Return an empty string if the instance value is null or empty - if ([string]::IsNullOrEmpty($SQLInstanceName)) { - return "" - } - - # Split the instance value by '.' and retrieve the second part - $instanceName = $SQLInstanceName.Split('.')[1] - - # Determine the service name based on the instance name - if ($instanceName -eq "MSSQLSERVER") { - $serviceName = "MSSQLSERVER" - } else { - $serviceName = "MSSQL`$$instanceName" - } - - return $serviceName -} - function Get-SQLServiceUser { param ( [Parameter(Mandatory = $true)] @@ -1209,8 +776,7 @@ function Get-SQLServiceUser { # Get-SQLServiceUser -SQLInstanceName "MSSQLSERVER" ##### ReEnrollment functions -function New-CSREnrollment -{ +function New-CSREnrollment { param ( [string]$SubjectText, [string]$ProviderName = "Microsoft Strong Cryptographic Provider", @@ -1358,8 +924,7 @@ function Import-SignedCertificate { # Shared Functions # Function to get SAN (Subject Alternative Names) from a certificate -function Get-KFSAN($cert) -{ +function Get-KFSAN($cert) { $san = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq "Subject Alternative Name" } if ($san) { return ($san.Format(1) -split ", " -join "; ") @@ -1368,8 +933,7 @@ function Get-KFSAN($cert) } #Function to verify if the given CSP is found on the computer -function Test-CryptoServiceProvider -{ +function Test-CryptoServiceProvider { param( [Parameter(Mandatory = $true)] [string]$CSPName @@ -1398,8 +962,7 @@ function Test-CryptoServiceProvider } # Function that takes an x509 certificate object and returns the csp -function Get-CertificateCSP -{ +function Get-CertificateCSP { param ( [Parameter(Mandatory = $true)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert @@ -1424,43 +987,6 @@ function Get-CertificateCSP } } -# This function returns a certificate object based upon the store and thumbprint received -# NOT BEING USED -function Get-KFCertificateByThumbprint -{ - param ( - [Parameter(Mandatory = $true)] - [string]$Thumbprint, - - [Parameter(Mandatory = $true)] - [string]$StoreName - ) - - try { - - [System.Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine - - # Open the specified certificate store - $store = New-Object System.Security.Cryptography.X509Certificates.X509Store($StoreName, [System.Security.Cryptography.X509Certificates.StoreLocation]::$StoreLocation) - $store.Open([System.Security.Cryptography.X509Certificates.OpenFlags]::ReadOnly) - - # Find the certificate by thumbprint - $certificate = $store.Certificates | Where-Object { $_.Thumbprint -eq $Thumbprint } - - # Close the store after retrieving the certificate - $store.Close() - - if (-not $certificate) { - throw "No certificate found with thumbprint $Thumbprint in store $StoreName" - } - - return $certificate - } catch { - Write-Error "An error occurred while retrieving the certificate: $_" - return $null - } -} - function Get-CryptoProviders { # Retrieves the list of available Crypto Service Providers using certutil try {