Skip to content

Commit 615394a

Browse files
authored
Merge release 3.1.7
2 parents 5c381cf + b8d5157 commit 615394a

14 files changed

+238
-85
lines changed

AzureKeyVault/AkvProperties.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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

89
using System.Collections.Generic;
910

AzureKeyVault/AzureClient.cs

Lines changed: 45 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
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

89
using System;
910
using System.Collections.Generic;
1011
using System.Linq;
11-
using System.Security.Cryptography.X509Certificates;
1212
using System.Threading.Tasks;
1313
using Azure;
1414
using Azure.Core;
@@ -17,11 +17,13 @@
1717
using Azure.ResourceManager.KeyVault;
1818
using Azure.ResourceManager.KeyVault.Models;
1919
using Azure.ResourceManager.Resources;
20+
using Azure.ResourceManager.Resources.Models;
2021
using Azure.Security.KeyVault.Certificates;
2122
using Keyfactor.Logging;
2223
using Keyfactor.Orchestrators.Common.Enums;
2324
using Keyfactor.Orchestrators.Extensions;
2425
using Microsoft.Extensions.Logging;
26+
using Newtonsoft.Json;
2527

2628
namespace 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

AzureKeyVault/AzureKeyVault.csproj

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,24 @@
1818
</PropertyGroup>
1919

2020
<ItemGroup>
21-
<PackageReference Include="Azure.Core" Version="1.44.1" />
22-
<PackageReference Include="Azure.Identity" Version="1.13.1" />
21+
<PackageReference Include="Azure.Core" Version="1.45.0" />
22+
<PackageReference Include="Azure.Identity" Version="1.13.2" />
2323
<PackageReference Include="Azure.ResourceManager" Version="1.13.0" />
2424
<PackageReference Include="Azure.ResourceManager.KeyVault" Version="1.3.0" />
2525
<PackageReference Include="Azure.ResourceManager.Resources" Version="1.9.0" />
2626
<PackageReference Include="Azure.Security.KeyVault.Administration" Version="4.5.0" />
2727
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.7.0" />
2828
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.7.0" />
29-
<PackageReference Include="Azure.Storage.Blobs" Version="12.22.2" />
30-
<PackageReference Include="Keyfactor.Logging" Version="1.1.1" />
29+
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
30+
<PackageReference Include="BouncyCastle.NetCore" Version="2.2.1" />
31+
<PackageReference Include="Keyfactor.Logging" Version="1.1.2" />
3132
<PackageReference Include="Keyfactor.Orchestrators.Common" Version="3.2.0" />
3233
<PackageReference Include="Keyfactor.Orchestrators.IOrchestratorJobExtensions" Version="0.7.0" />
3334
<PackageReference Include="Keyfactor.Platform.IPAMProvider" Version="1.0.0" />
3435
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="6.0.0" />
35-
<PackageReference Include="Microsoft.Identity.Client" Version="4.66.1" />
36-
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.66.1" />
37-
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
36+
<PackageReference Include="Microsoft.Identity.Client" Version="4.68.0" />
37+
<PackageReference Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.68.0" />
38+
<PackageReference Include="System.Drawing.Common" Version="9.0.2" />
3839
<PackageReference Include="System.Linq" Version="4.3.0" />
3940
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
4041
</ItemGroup>

AzureKeyVault/Constants.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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

89
namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
910
{

AzureKeyVault/Helpers.cs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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.
8+
9+
using Org.BouncyCastle.Pkcs;
10+
using Org.BouncyCastle.Security;
11+
using System;
12+
using System.IO;
13+
using System.Text.Json.Nodes;
14+
15+
namespace Keyfactor.Extensions.Orchestrator.AzureKeyVault
16+
{
17+
public static class Helpers
18+
{
19+
public static bool IsValidJson(this string jsonString)
20+
{
21+
try
22+
{
23+
var unescapedJSON = System.Text.RegularExpressions.Regex.Unescape(jsonString);
24+
var tmpObj = JsonValue.Parse(unescapedJSON);
25+
}
26+
catch (FormatException fex)
27+
{
28+
//Invalid json format
29+
return false;
30+
}
31+
catch (Exception ex) //some other exception
32+
{
33+
return false;
34+
}
35+
return true;
36+
}
37+
public static byte[] ConvertPfxToPasswordlessPkcs12(string base64Pfx, string pfxPassword)
38+
{
39+
// Decode the Base64-encoded PFX data
40+
byte[] pfxBytes = Convert.FromBase64String(base64Pfx);
41+
using (var inputStream = new MemoryStream(pfxBytes))
42+
{
43+
var builder = new Pkcs12StoreBuilder();
44+
builder.SetUseDerEncoding(true);
45+
var store = builder.Build();
46+
store.Load(inputStream, pfxPassword.ToCharArray());
47+
48+
string alias = null;
49+
foreach (string a in store.Aliases)
50+
{
51+
if (store.IsKeyEntry(a))
52+
{
53+
alias = a;
54+
break;
55+
}
56+
}
57+
58+
using (var outputStream = new MemoryStream())
59+
{
60+
var newStore = builder.Build();
61+
62+
if (alias != null)
63+
{
64+
// Extract private key and certificate chain if available
65+
var keyEntry = store.GetKey(alias);
66+
var chain = store.GetCertificateChain(alias);
67+
newStore.SetKeyEntry("converted-key", keyEntry, chain);
68+
}
69+
else
70+
{
71+
// If no private key, include just the certificate chain
72+
foreach (string certAlias in store.Aliases)
73+
{
74+
if (store.IsCertificateEntry(certAlias))
75+
{
76+
var cert = store.GetCertificate(certAlias);
77+
newStore.SetCertificateEntry(certAlias, cert);
78+
}
79+
}
80+
}
81+
82+
// Save the new PKCS#12 store without a password
83+
newStore.Save(outputStream, null, new SecureRandom());
84+
return outputStream.ToArray();
85+
}
86+
}
87+
}
88+
}
89+
}

AzureKeyVault/JobAttribute.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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

89
using System;
910

AzureKeyVault/Jobs/AzureKeyVaultJob.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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

89
using System;
910
using System.Collections.Generic;

AzureKeyVault/Jobs/Discovery.cs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
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

89
using System;
910
using System.Collections.Generic;

0 commit comments

Comments
 (0)