1414using Keyfactor . Logging ;
1515using Keyfactor . Orchestrators . Common . Enums ;
1616using Keyfactor . Orchestrators . Extensions ;
17+ using Microsoft . CodeAnalysis ;
1718using Microsoft . Extensions . Logging ;
1819using System ;
19- using System . Linq ;
20+ using System . IO ;
2021using System . Management . Automation ;
2122using System . Management . Automation . Runspaces ;
2223using System . Security . Cryptography . X509Certificates ;
@@ -45,99 +46,174 @@ public ClientPSCertStoreManager(ILogger logger, Runspace runSpace, long jobNumbe
4546 _jobNumber = jobNumber ;
4647 }
4748
48- public JobResult AddCertificate ( string certificateContents , string privateKeyPassword , string storePath )
49+ public string CreatePFXFile ( string certificateContents , string privateKeyPassword )
4950 {
5051 try
5152 {
52- using var ps = PowerShell . Create ( ) ;
53-
54- _logger . MethodEntry ( ) ;
55-
56- ps . Runspace = _runspace ;
57-
58- _logger . LogTrace ( $ "Creating X509 Cert from: { certificateContents } ") ;
53+ // Create the x509 certificate
5954 x509Cert = new X509Certificate2
6055 (
6156 Convert . FromBase64String ( certificateContents ) ,
6257 privateKeyPassword ,
63- X509KeyStorageFlags . MachineKeySet |
64- X509KeyStorageFlags . PersistKeySet |
58+ X509KeyStorageFlags . MachineKeySet |
59+ X509KeyStorageFlags . PersistKeySet |
6560 X509KeyStorageFlags . Exportable
6661 ) ;
6762
68- _logger . LogDebug ( $ "X509 Cert Created With Subject: { x509Cert . SubjectName } ") ;
69- _logger . LogDebug ( $ "Begin Add for Cert Store { $@ "\\{ _runspace . ConnectionInfo . ComputerName } \{ storePath } "} ") ;
63+ using ( PowerShell ps = PowerShell . Create ( ) )
64+ {
65+ ps . Runspace = _runspace ;
66+
67+ // Add script to write certificate contents to a temporary file
68+ string script = @"
69+ param($certificateContents)
70+ $filePath = [System.IO.Path]::GetTempFileName() + '.pfx'
71+ [System.IO.File]::WriteAllBytes($filePath, [System.Convert]::FromBase64String($certificateContents))
72+ $filePath
73+ " ;
7074
71- // Add Certificate
72- var funcScript = @"
73- $ErrorActionPreference = ""Stop""
75+ ps . AddScript ( script ) ;
76+ ps . AddParameter ( "certificateContents" , certificateContents ) ; // Convert.ToBase64String(x509Cert.Export(X509ContentType.Pkcs12)));
7477
75- function InstallPfxToMachineStore([byte[]]$bytes, [string]$password, [string]$storeName) {
76- $certStore = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -ArgumentList $storeName, ""LocalMachine""
77- $certStore.Open(5)
78- $cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $bytes, $password, 18 <# Persist, Machine #>
79- $certStore.Add($cert)
78+ // Invoke the script on the remote computer
79+ var results = ps . Invoke ( ) ;
8080
81- $certStore.Close();
82- }" ;
81+ // Get the result (temporary file path) returned by the script
82+ return results [ 0 ] . ToString ( ) ;
83+ }
84+ }
85+ catch ( Exception )
86+ {
87+ throw new Exception ( "An error occurred while attempting to create and write the X509 contents." ) ;
88+ }
89+ }
8390
84- ps . AddScript ( funcScript ) . AddStatement ( ) ;
85- _logger . LogDebug ( "InstallPfxToMachineStore Statement Added..." ) ;
91+ public void DeletePFXFile ( string filePath , string fileName )
92+ {
93+ using ( PowerShell ps = PowerShell . Create ( ) )
94+ {
95+ ps . Runspace = _runspace ;
8696
87- ps . AddCommand ( "InstallPfxToMachineStore" )
88- . AddParameter ( "bytes" , Convert . FromBase64String ( certificateContents ) )
89- . AddParameter ( "password" , privateKeyPassword )
90- . AddParameter ( "storeName" , $@ "\\{ _runspace . ConnectionInfo . ComputerName } \{ storePath } ") ;
91-
92- _logger . LogTrace ( "InstallPfxToMachineStore Command Added..." ) ;
97+ // Add script to delete the temporary file
98+ string deleteScript = @"
99+ param($filePath)
100+ Remove-Item -Path $filePath -Force
101+ " ;
93102
94- foreach ( var cmd in ps . Commands . Commands )
95- {
96- _logger . LogTrace ( "Logging PowerShell Command" ) ;
97- _logger . LogTrace ( cmd . CommandText ) ;
98- }
103+ ps . AddScript ( deleteScript ) ;
104+ ps . AddParameter ( "filePath" , Path . Combine ( filePath , fileName ) + "*" ) ;
99105
100- _logger . LogTrace ( "Invoking ps..." ) ;
101- ps . Invoke ( ) ;
102- _logger . LogTrace ( "ps Invoked..." ) ;
106+ // Invoke the script to delete the file
107+ var results = ps . Invoke ( ) ;
108+ }
109+ }
103110
104- if ( ps . HadErrors )
111+ public JobResult ImportPFXFile ( string filePath , string privateKeyPassword , string cryptoProviderName )
112+ {
113+ try
114+ {
115+ using ( PowerShell ps = PowerShell . Create ( ) )
105116 {
106- _logger . LogTrace ( "ps Has Errors" ) ;
107- var psError = ps . Streams . Error . ReadAll ( )
108- . Aggregate ( string . Empty , ( current , error ) => current + error ? . ErrorDetails . Message ) ;
117+ ps . Runspace = _runspace ;
118+
119+ if ( cryptoProviderName == null )
120+ {
121+ string script = @"
122+ param($pfxFilePath, $privateKeyPassword, $cspName)
123+ $output = certutil -importpfx -p $privateKeyPassword $pfxFilePath 2>&1
124+ $c = $LASTEXITCODE
125+ $output
126+ " ;
127+
128+ ps . AddScript ( script ) ;
129+ ps . AddParameter ( "pfxFilePath" , filePath ) ;
130+ ps . AddParameter ( "privateKeyPassword" , privateKeyPassword ) ;
131+ }
132+ else
133+ {
134+ string script = @"
135+ param($pfxFilePath, $privateKeyPassword, $cspName)
136+ $output = certutil -importpfx -csp $cspName -p $privateKeyPassword $pfxFilePath 2>&1
137+ $c = $LASTEXITCODE
138+ $output
139+ " ;
140+
141+ ps . AddScript ( script ) ;
142+ ps . AddParameter ( "pfxFilePath" , filePath ) ;
143+ ps . AddParameter ( "privateKeyPassword" , privateKeyPassword ) ;
144+ ps . AddParameter ( "cspName" , cryptoProviderName ) ;
145+ }
146+
147+ // Invoke the script
148+ var results = ps . Invoke ( ) ;
149+
150+ // Get the last exist code returned from the script
151+ // 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.
152+ // Due to security reasons and Windows architecture, retreiving values from a remote system is not supported.
153+ int lastExitCode = 0 ;
154+ try
155+ {
156+ lastExitCode = ( int ) ps . Runspace . SessionStateProxy . PSVariable . GetValue ( "c" ) ;
157+ }
158+ catch ( Exception )
159+ {
160+ }
161+
162+
163+ bool isError = false ;
164+ if ( lastExitCode != 0 )
165+ {
166+ isError = true ;
167+ string outputMsg = "" ;
168+
169+ foreach ( var result in results )
170+ {
171+ string outputLine = result . ToString ( ) ;
172+ if ( ! string . IsNullOrEmpty ( outputLine ) )
173+ {
174+ outputMsg += "\n " + outputLine ;
175+ }
176+ }
177+ _logger . LogError ( outputMsg ) ;
178+ }
179+ else
180+ {
181+ // Check for errors in the output
182+ foreach ( var result in results )
183+ {
184+ string outputLine = result . ToString ( ) ;
185+ if ( ! string . IsNullOrEmpty ( outputLine ) && outputLine . Contains ( "Error" ) )
186+ {
187+ isError = true ;
188+ _logger . LogError ( outputLine ) ;
189+ }
190+ }
191+ }
192+
193+ if ( isError )
194+ {
195+ throw new Exception ( "Error occurred while attempting to import the pfx file." ) ;
196+ }
197+ else
109198 {
110199 return new JobResult
111200 {
112- Result = OrchestratorJobStatusJobResult . Failure ,
201+ Result = OrchestratorJobStatusJobResult . Success ,
113202 JobHistoryId = _jobNumber ,
114- FailureMessage =
115- $ "Site { storePath } on server { _runspace . ConnectionInfo . ComputerName } : { psError } "
203+ FailureMessage = ""
116204 } ;
117205 }
118206 }
119-
120- _logger . LogTrace ( "Clearing Commands..." ) ;
121- ps . Commands . Clear ( ) ;
122- _logger . LogTrace ( "Commands Cleared.." ) ;
123- _logger . LogInformation ( $ "Certificate was successfully added to cert store: { storePath } ") ;
124-
125- return new JobResult
126- {
127- Result = OrchestratorJobStatusJobResult . Success ,
128- JobHistoryId = _jobNumber ,
129- FailureMessage = ""
130- } ;
131207 }
132208 catch ( Exception e )
133209 {
134- _logger . LogError ( $ "Error Occurred in ClientPSCertStoreManager.AddCertificate (): { e . Message } ") ;
210+ _logger . LogError ( $ "Error Occurred in ClientPSCertStoreManager.ImportPFXFile (): { e . Message } ") ;
135211
136212 return new JobResult
137213 {
138214 Result = OrchestratorJobStatusJobResult . Failure ,
139215 JobHistoryId = _jobNumber ,
140- FailureMessage = $ "Error Occurred in InstallCertificate { LogHandler . FlattenException ( e ) } "
216+ FailureMessage = $ "Error Occurred in ImportPFXFile { LogHandler . FlattenException ( e ) } "
141217 } ;
142218 }
143219 }
@@ -150,10 +226,11 @@ public void RemoveCertificate(string thumbprint, string storePath)
150226
151227 ps . Runspace = _runspace ;
152228
229+ // Open with value of 5 means: Open existing only (4) + Open ReadWrite (1)
153230 var removeScript = $@ "
154231 $ErrorActionPreference = 'Stop'
155232 $certStore = New-Object System.Security.Cryptography.X509Certificates.X509Store('{ storePath } ','LocalMachine')
156- $certStore.Open('MaxAllowed' )
233+ $certStore.Open(5 )
157234 $certToRemove = $certStore.Certificates.Find(0,'{ thumbprint } ',$false)
158235 if($certToRemove.Count -gt 0) {{
159236 $certStore.Remove($certToRemove[0])
0 commit comments