diff --git a/CHANGELOG.md b/CHANGELOG.md index ea7fd92..f4bda5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +2.4.1 +* Modified the CertUtil logic to use the -addstore argument when no password is sent with the certificate information. +* Added additional error trapping and trace logs + 2.4.0 * Changed the way certificates are added to cert stores. CertUtil is now used to import the PFX certificate into the associated store. The CSP is now considered when maintaining certificates, empty CSP values will result in using the machines default CSP. * Added the Crypto Service Provider and SAN Entry Parameters to be used on Inventory queries, Adding and ReEnrollments for the WinCert, WinSQL and IISU extensions. diff --git a/IISU/ClientPSCertStoreManager.cs b/IISU/ClientPSCertStoreManager.cs index 1a37864..df91f38 100644 --- a/IISU/ClientPSCertStoreManager.cs +++ b/IISU/ClientPSCertStoreManager.cs @@ -15,9 +15,11 @@ 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; @@ -48,6 +50,10 @@ public ClientPSCertStoreManager(ILogger logger, Runspace runSpace, long jobNumbe 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 @@ -79,11 +85,13 @@ public string CreatePFXFile(string certificateContents, string privateKeyPasswor 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) + catch (Exception ex) { + _logger.LogError(ex.ToString()); throw new Exception("An error occurred while attempting to create and write the X509 contents."); } } @@ -108,55 +116,137 @@ public void DeletePFXFile(string filePath, string fileName) } } - public JobResult ImportPFXFile(string filePath, string privateKeyPassword, string cryptoProviderName) + 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 (cryptoProviderName == null) { - string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1 - $c = $LASTEXITCODE - $output - "; + if (privateKeyPassword == null) + { + // 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 -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) + $output = certutil -importpfx -p $privateKeyPassword $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + $stuff = certutil -dump + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } + $output + $stuff + "; - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("privateKeyPassword", privateKeyPassword); + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("privateKeyPassword", privateKeyPassword); + ps.AddParameter("storePath", storePath); + } } else { - string script = @" - param($pfxFilePath, $privateKeyPassword, $cspName) - $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $pfxFilePath 2>&1 - $c = $LASTEXITCODE - $output - "; + if (privateKeyPassword == null) + { + string script = @" + param($pfxFilePath, $cspName, $storePath) + $output = certutil -csp $cspName -addstore $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" - ps.AddScript(script); - ps.AddParameter("pfxFilePath", filePath); - ps.AddParameter("privateKeyPassword", privateKeyPassword); - ps.AddParameter("cspName", cryptoProviderName); + $stuff = certutil -dump + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } + $output + $stuff + "; + + ps.AddScript(script); + ps.AddParameter("pfxFilePath", filePath); + ps.AddParameter("cspName", cryptoProviderName); + ps.AddParameter("storePath", storePath); + } + else + { + string script = @" + param($pfxFilePath, $privateKeyPassword, $cspName) + $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $storePath $pfxFilePath 2>&1 + $exit_message = ""LASTEXITCODE:$($LASTEXITCODE)"" + + $stuff = certutil -dump + if ($stuff.GetType().Name -eq ""String"") + { + $stuff = @($stuff, $exit_message) + } + else + { + $stuff += $exit_message + } + $output + $stuff + "; + + 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 - // This statement is in a try/catch block because PSVariable.GetValue() is not a valid method on a remote PS Session and throws an exception. - // Due to security reasons and Windows architecture, retreiving values from a remote system is not supported. int lastExitCode = 0; try { - lastExitCode = (int)ps.Runspace.SessionStateProxy.PSVariable.GetValue("c"); + lastExitCode = GetLastExitCode(results[^1].ToString()); + _logger.LogTrace($"Last exit code: {lastExitCode}"); } catch (Exception) { + _logger.LogTrace("Unable to get the last exit code."); } @@ -182,7 +272,10 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin foreach (var result in results) { string outputLine = result.ToString(); - if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error")) + + _logger.LogTrace(outputLine); + + if (!string.IsNullOrEmpty(outputLine) && outputLine.Contains("Error") || outputLine.Contains("permissions are needed")) { isError = true; _logger.LogError(outputLine); @@ -218,6 +311,30 @@ public JobResult ImportPFXFile(string filePath, string privateKeyPassword, strin } } + 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(); diff --git a/IISU/ImplementedStoreTypes/Win/Management.cs b/IISU/ImplementedStoreTypes/Win/Management.cs index ba624ed..f251c7f 100644 --- a/IISU/ImplementedStoreTypes/Win/Management.cs +++ b/IISU/ImplementedStoreTypes/Win/Management.cs @@ -145,7 +145,7 @@ private JobResult performAddition(ManagementJobConfiguration config) // 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); + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); // Delete the temporary file _logger.LogTrace($"Deleting temporary PFX File: {filePath}."); diff --git a/IISU/ImplementedStoreTypes/WinIIS/Management.cs b/IISU/ImplementedStoreTypes/WinIIS/Management.cs index b4a3c33..72efe5f 100644 --- a/IISU/ImplementedStoreTypes/WinIIS/Management.cs +++ b/IISU/ImplementedStoreTypes/WinIIS/Management.cs @@ -148,7 +148,7 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin _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); + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); // Delete the temporary file manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath)); diff --git a/IISU/ImplementedStoreTypes/WinSQL/Management.cs b/IISU/ImplementedStoreTypes/WinSQL/Management.cs index 028c6a6..e304f1e 100644 --- a/IISU/ImplementedStoreTypes/WinSQL/Management.cs +++ b/IISU/ImplementedStoreTypes/WinSQL/Management.cs @@ -145,7 +145,7 @@ private JobResult PerformAddCertificate(ManagementJobConfiguration config, strin _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); + JobResult result = manager.ImportPFXFile(filePath, privateKeyPassword, cryptoProvider, storePath); // Delete the temporary file manager.DeletePFXFile(Path.GetDirectoryName(filePath), Path.GetFileNameWithoutExtension(filePath));