1- // Copyright 2023 Keyfactor
2- // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
3- // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4- // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
5- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
6- // and limitations under the License.
1+
2+ // Copyright 2025 Keyfactor
3+ // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
4+ // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
5+ // Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS,
6+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions
7+ // and limitations under the License.
78
89using System ;
910using System . Collections . Generic ;
1011using System . Linq ;
11- using System . Security . Cryptography . X509Certificates ;
1212using System . Threading . Tasks ;
1313using Azure ;
1414using Azure . Core ;
1717using Azure . ResourceManager . KeyVault ;
1818using Azure . ResourceManager . KeyVault . Models ;
1919using Azure . ResourceManager . Resources ;
20+ using Azure . ResourceManager . Resources . Models ;
2021using Azure . Security . KeyVault . Certificates ;
2122using Keyfactor . Logging ;
2223using Keyfactor . Orchestrators . Common . Enums ;
2324using Keyfactor . Orchestrators . Extensions ;
2425using Microsoft . Extensions . Logging ;
26+ using Newtonsoft . Json ;
2527
2628namespace Keyfactor . Extensions . Orchestrator . AzureKeyVault
2729{
@@ -194,7 +196,7 @@ public virtual async Task<KeyVaultResource> CreateVault()
194196 }
195197 }
196198
197- public virtual async Task < KeyVaultCertificateWithPolicy > ImportCertificateAsync ( string certName , string contents , string pfxPassword )
199+ public virtual async Task < KeyVaultCertificateWithPolicy > ImportCertificateAsync ( string certName , string contents , string pfxPassword , string tags = null )
198200 {
199201 try
200202 {
@@ -206,22 +208,37 @@ public virtual async Task<KeyVaultCertificateWithPolicy> ImportCertificateAsync(
206208 RecoverDeletedCertificateOperation recovery = await CertClient . StartRecoverDeletedCertificateAsync ( certName ) ;
207209 recovery . WaitForCompletion ( ) ;
208210 }
209- logger . LogTrace ( "begin creating x509 certificate from contents." ) ;
210- var bytes = Convert . FromBase64String ( contents ) ;
211211
212- var x509Collection = new X509Certificate2Collection ( ) ; //(bytes, pfxPassword, X509KeyStorageFlags.Exportable);
212+ logger . LogTrace ( $ "converting to pkcs12 without password for importing to keyvault") ;
213+
214+ var p12bytes = Helpers . ConvertPfxToPasswordlessPkcs12 ( contents , pfxPassword ) ;
215+
216+ logger . LogTrace ( $ "got a byte array with length { p12bytes . Length } ") ;
217+
218+ logger . LogTrace ( $ "calling ImportCertificateAsync on the KeyVault certificate client to import certificate { certName } ") ;
213219
214- x509Collection . Import ( bytes , pfxPassword , X509KeyStorageFlags . Exportable ) ;
220+ var tagDict = new Dictionary < string , string > ( ) ;
215221
216- var certWithKey = x509Collection . Export ( X509ContentType . Pkcs12 ) ;
222+ if ( ! string . IsNullOrEmpty ( tags ) )
223+ {
224+ if ( ! tags . IsValidJson ( ) )
225+ {
226+ logger . LogError ( $ "the entry parameter provided for Certificate Tags: \" { tags } \" , does not seem to be valid JSON.") ;
227+ throw new Exception ( $ "the string \" { tags } \" is not a valid json string. Please enter a valid json string for CertificateTags in the entry parameter or leave empty for no tags to be applied.") ;
228+ }
229+ logger . LogTrace ( $ "converting the json value provided for tags into a Dictionary<string,string>") ;
230+ tagDict = JsonConvert . DeserializeObject < Dictionary < string , string > > ( tags ) ;
231+ logger . LogTrace ( $ "{ tagDict . Count } tag(s) will be associated with the certificate in Azure KeyVault") ;
232+ }
217233
234+ var options = new ImportCertificateOptions ( certName , p12bytes ) ;
218235
219- logger . LogTrace ( $ "importing created x509 certificate named { 1 } ", certName ) ;
220- logger . LogTrace ( $ "There are { x509Collection . Count } certificates in the chain.") ;
221- var cert = await CertClient . ImportCertificateAsync ( new ImportCertificateOptions ( certName , certWithKey ) ) ;
236+ foreach ( var tag in tagDict . Keys )
237+ {
238+ options . Tags . Add ( tag , tagDict [ tag ] ) ;
239+ }
222240
223- // var fullCert = _secretClient.GetSecret(certName);
224- // The certificate must be retrieved as a secret from AKV in order to have the full chain included.
241+ var cert = await CertClient . ImportCertificateAsync ( options ) ;
225242
226243 return cert ;
227244 }
@@ -278,8 +295,9 @@ public virtual async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesAsyn
278295 var fullInventoryList = new List < CertificateProperties > ( ) ;
279296 var failedCount = 0 ;
280297 Exception innerException = null ;
281-
282- await foreach ( var cert in inventory ) {
298+
299+ await foreach ( var cert in inventory )
300+ {
283301 logger . LogTrace ( $ "adding cert with ID: { cert . Id } to the list.") ;
284302 fullInventoryList . Add ( cert ) ; // convert to list from pages
285303 }
@@ -300,23 +318,26 @@ public virtual async Task<IEnumerable<CurrentInventoryItem>> GetCertificatesAsyn
300318 PrivateKeyEntry = true ,
301319 ItemStatus = OrchestratorInventoryItemStatus . Unknown ,
302320 UseChainLevel = true ,
303- Certificates = new List < string > ( ) { Convert . ToBase64String ( cert . Value . Cer ) }
321+ Certificates = new List < string > ( ) { Convert . ToBase64String ( cert . Value . Cer ) } ,
322+ Parameters = cert . Value . Properties . Tags as Dictionary < string , object >
304323 } ) ;
305324 }
306325 catch ( Exception ex )
307326 {
308327 failedCount ++ ;
309328 innerException = ex ;
310- logger . LogError ( $ "Failed to retreive details for certificate { certificate . Name } . Exception: { ex . Message } ") ;
329+ logger . LogError ( $ "Failed to retreive details for certificate { certificate . Name } . Exception: { ex . Message } ") ;
311330 // continuing with inventory instead of throwing, in case there's an issue with a single certificate
312331 }
313332 }
314333
315- if ( failedCount == fullInventoryList . Count ( ) ) {
334+ if ( failedCount == fullInventoryList . Count ( ) && failedCount > 0 )
335+ {
316336 throw new Exception ( "Unable to retreive details for certificates." , innerException ) ;
317337 }
318338
319- if ( failedCount > 0 ) {
339+ if ( failedCount > 0 )
340+ {
320341 logger . LogWarning ( $ "{ failedCount } of { fullInventoryList . Count ( ) } certificates were not able to be retreieved. Please review the errors.") ;
321342 }
322343
0 commit comments