Skip to content

Commit 22f8c20

Browse files
authored
Upgrade track2 cert API version to 7.5 and Add PolicyPath in Add-AzKeyVaultCertificate (#24642)
* use track2sdk version 7.5 to merge cert * code refactor * add PolicyPath in Add-AzKeyVaultCertificate * add default parameter set for add-azkeyvaultcertificate
1 parent c6f40b7 commit 22f8c20

13 files changed

+333
-141
lines changed

src/KeyVault/KeyVault.Test/PesterTests/Certificate.Tests.ps1

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,12 @@ Describe "Import Certificate with policy" {
3939
$cert = Import-AzKeyVaultCertificate -VaultName $vaultName -Name $certName -CertificateCollection $certCollection -PolicyPath $policyPath
4040
$cert.Policy.SecretContentType | Should -Be "application/x-pkcs12"
4141
}
42+
It "MergePendingCert" {
43+
Add-AzKeyVaultCertificate -VaultName bez-kv -Name bez-pending-cert0417 -PolicyPath C:\Azure\fork\azure-powershell\src\KeyVault\KeyVault.Test\Resources\pendingCertPolicy.json
44+
# openssl genrsa -out ca.key 2048
45+
# openssl req -x509 -new -nodes -key ca.key -days 360 -out ca.crt
46+
# openssl x509 -req -days 360 -in pending-cert01_7e2879b5255044c5aa624bdaab7e1d94.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
47+
Import-AzKeyVaultCertificate -VaultName bez-kv -Name bez-pending-cert0417 -FilePath C:\Azure\bezFile\20240417\server.crt
48+
}
4249
}
4350

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"attributes": {
3+
"enabled": true,
4+
"expires": null,
5+
"not_before": null
6+
},
7+
"issuer_parameters": {
8+
"name": "Unknown"
9+
},
10+
"key_properties": {
11+
"exportable": true,
12+
"key_size": 2048,
13+
"key_type": "RSA",
14+
"reuse_key": false
15+
},
16+
"secret_properties": {
17+
"content_type": "application/x-pkcs12"
18+
},
19+
"x509_certificate_properties": {
20+
"ekus": null,
21+
"key_usage": [
22+
"digitalSignature",
23+
"nonRepudiation",
24+
"keyEncipherment",
25+
"keyAgreement",
26+
"keyCertSign"
27+
],
28+
"subject": "C=US, ST=WA, L=Redmond, O=TestO, OU=TestOU, CN=www.bez.com",
29+
"subject_alternative_names": null,
30+
"validity_in_months": 50
31+
}
32+
}

src/KeyVault/KeyVault/ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
- Additional information about change #1
1919
-->
2020
## Upcoming Release
21+
* Added parameter `PolicyPath` in `Add-AzKeyVaultCertificate` to support custom policy in the process of certificate enrollment.
22+
* Upgraded the API version of merging certificate to 7.5. [#24323]
2123

2224
## Version 5.2.2
2325
* Introduced secrets detection feature to safeguard sensitive data.

src/KeyVault/KeyVault/Commands/Certificate/AddAzureKeyVaultCertificate.cs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,26 @@
1616
using Microsoft.Azure.Commands.KeyVault.Models;
1717
using System.Management.Automation;
1818
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
19+
using Microsoft.Azure.Commands.Common.Exceptions;
20+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
21+
using System.IO;
22+
using Microsoft.Azure.Commands.KeyVault.Properties;
1923

2024
namespace Microsoft.Azure.Commands.KeyVault
2125
{
2226
/// <summary>
2327
/// Starts the process for enrolling for a certificate in Azure Key Vault
2428
/// </summary>
25-
[Cmdlet("Add", ResourceManager.Common.AzureRMConstants.AzurePrefix + "KeyVaultCertificate",SupportsShouldProcess = true)]
29+
[Cmdlet("Add", ResourceManager.Common.AzureRMConstants.AzurePrefix + "KeyVaultCertificate", SupportsShouldProcess = true, DefaultParameterSetName = EnrollCertWithPolicyPath)]
2630
[OutputType(typeof(PSKeyVaultCertificateOperation))]
2731
public class AddAzureKeyVaultCertificate : KeyVaultCmdletBase
2832
{
33+
#region ParameterSet definitions
34+
const string EnrollCertWithPolicyObject = "EnrollCertWithPolicyObject";
35+
const string EnrollCertWithPolicyPath = "EnrollCertWithPolicyPath";
36+
37+
#endregion
38+
2939
#region Input Parameter Definitions
3040

3141
/// <summary>
@@ -52,12 +62,24 @@ public class AddAzureKeyVaultCertificate : KeyVaultCmdletBase
5262
/// CertificatePolicy
5363
/// </summary>
5464
[Parameter(Mandatory = true,
65+
ParameterSetName = EnrollCertWithPolicyObject,
5566
ValueFromPipeline = true,
5667
Position = 2,
5768
HelpMessage = "Specifies the certificate policy.")]
5869
[ValidateNotNull]
5970
public PSKeyVaultCertificatePolicy CertificatePolicy { get; set; }
6071

72+
/// <summary>
73+
/// PolicyPath
74+
/// </summary>
75+
[Parameter(Mandatory = true,
76+
ParameterSetName = EnrollCertWithPolicyPath,
77+
ValueFromPipeline = true,
78+
Position = 2,
79+
HelpMessage = "A file path to specify management policy for the certificate that contains JSON encoded policy definition.")]
80+
[ValidateNotNull]
81+
public string PolicyPath { get; set; }
82+
6183
/// <summary>
6284
/// Certificate tags
6385
/// </summary>
@@ -67,9 +89,27 @@ public class AddAzureKeyVaultCertificate : KeyVaultCmdletBase
6789
public Hashtable Tag { get; set; }
6890
#endregion
6991

92+
protected override void BeginProcessing()
93+
{
94+
PolicyPath = this.TryResolvePath(PolicyPath);
95+
base.BeginProcessing();
96+
}
97+
98+
private void ValidateParameters()
99+
{ if (this.IsParameterBound(c => c.PolicyPath))
100+
{
101+
if (!File.Exists(PolicyPath))
102+
{
103+
throw new AzPSArgumentException(string.Format(Resources.FileNotFound, this.PolicyPath), nameof(PolicyPath));
104+
}
105+
CertificatePolicy = new PSKeyVaultCertificatePolicy(PolicyPath);
106+
}
107+
}
108+
70109
public override void ExecuteCmdlet()
71110
{
72111
if (ShouldProcess(Name, Properties.Resources.AddCertificate)) {
112+
ValidateParameters();
73113
var certificateOperation = this.DataServiceClient.EnrollCertificate(VaultName, Name, CertificatePolicy == null ? null : CertificatePolicy.ToCertificatePolicy(), Tag == null ? null : Tag.ConvertToDictionary());
74114
this.WriteObject(certificateOperation);
75115
}

src/KeyVault/KeyVault/Commands/Certificate/ImportAzureKeyVaultCertificate.cs

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,21 @@
1212
// limitations under the License.
1313
// ----------------------------------------------------------------------------------
1414

15+
using Microsoft.Azure.Commands.Common.Exceptions;
16+
using Microsoft.Azure.Commands.KeyVault.Models;
17+
using Microsoft.Azure.Commands.KeyVault.Properties;
18+
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
19+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
20+
1521
using System;
1622
using System.Collections;
17-
using System.IO;
18-
using System.Security;
1923
using System.Collections.Generic;
24+
using System.IO;
2025
using System.Management.Automation;
26+
using System.Security;
2127
using System.Security.Cryptography.X509Certificates;
22-
using Microsoft.Azure.Commands.KeyVault.Models;
28+
2329
using KeyVaultProperties = Microsoft.Azure.Commands.KeyVault.Properties;
24-
using Microsoft.Azure.KeyVault.Models;
25-
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
26-
using Microsoft.WindowsAzure.Commands.Utilities.Common;
27-
using Microsoft.Azure.Commands.Common.Exceptions;
28-
using Microsoft.Azure.Commands.KeyVault.Properties;
2930

3031
namespace Microsoft.Azure.Commands.KeyVault
3132
{
@@ -201,16 +202,16 @@ public override void ExecuteCmdlet()
201202
switch (ParameterSetName)
202203
{
203204
case ImportCertificateFromFileParameterSet:
204-
// Pem file can't be handled by X509Certificate2Collection in dotnet standard
205-
// Just read it as raw data and pass it to service side
205+
byte[] base64Bytes = File.ReadAllBytes(FilePath);
206+
bool doImport = false;
207+
206208
if (IsPemFile(FilePath))
207209
{
208-
byte[] pemBytes = File.ReadAllBytes(FilePath);
209-
certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, pemBytes, Password, Tag?.ConvertToDictionary(), Constants.PemContentType, certPolicy: PolicyObject);
210+
doImport = true;
210211
}
211212
else
212213
{
213-
bool doImport = false;
214+
214215
X509Certificate2Collection userProvidedCertColl = InitializeCertificateCollection();
215216

216217
// look for at least one certificate which contains a private key
@@ -220,22 +221,23 @@ public override void ExecuteCmdlet()
220221
if (doImport)
221222
break;
222223
}
224+
}
223225

224-
if (doImport)
225-
{
226-
227-
byte[] base64Bytes = userProvidedCertColl.Export(X509ContentType.Pfx, Password?.ConvertToString());
228-
certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, base64Bytes, Password, Tag?.ConvertToDictionary(), certPolicy: PolicyObject);
229-
}
230-
else
231-
{
232-
certBundle = this.DataServiceClient.MergeCertificate(
226+
certBundle = doImport ?
227+
this.Track2DataClient.ImportCertificate(
233228
VaultName,
234229
Name,
235-
userProvidedCertColl,
236-
Tag == null ? null : Tag.ConvertToDictionary());
237-
}
238-
}
230+
base64Bytes,
231+
Password,
232+
Tag?.ConvertToDictionary(),
233+
IsPemFile(FilePath) ? Constants.PemContentType : Constants.Pkcs12ContentType,
234+
PolicyObject) :
235+
this.Track2DataClient.MergeCertificate(
236+
VaultName,
237+
Name,
238+
new List<byte[]> { base64Bytes },
239+
Tag == null ? null : Tag.ConvertToDictionary());
240+
239241
break;
240242

241243
case ImportWithPrivateKeyFromCollectionParameterSet:
@@ -257,6 +259,12 @@ private bool IsPemFile(string filePath)
257259
return ".pem".Equals(Path.GetExtension(filePath), StringComparison.OrdinalIgnoreCase);
258260
}
259261

262+
/// <summary>
263+
/// Initialize certificate collection from FilePath
264+
/// </summary>
265+
/// <returns></returns>
266+
/// <exception cref="FileNotFoundException"></exception>
267+
260268
internal X509Certificate2Collection InitializeCertificateCollection()
261269
{
262270
FileInfo certFile = new FileInfo(ResolveUserPath(this.FilePath));

src/KeyVault/KeyVault/KeyVault.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<ItemGroup>
1515
<PackageReference Include="Azure.Security.KeyVault.Administration" Version="4.4.0-beta.1" />
1616
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.6.0-beta.1" />
17-
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.3.0" />
17+
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.6.0" />
1818
<PackageReference Include="Portable.BouncyCastle" Version="1.8.8" />
1919
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.1" />
2020
<PackageReference Include="Microsoft.Azure.KeyVault.WebKey" Version="3.0.1" />

src/KeyVault/KeyVault/Models/Certificate/PSKeyVaultCertificateOperation.cs

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313
// ----------------------------------------------------------------------------------
1414

1515
using Microsoft.Azure.KeyVault.Models;
16+
1617
using System;
1718

19+
using Track2Sdk = Azure.Security.KeyVault.Certificates;
20+
1821
namespace Microsoft.Azure.Commands.KeyVault.Models
1922
{
2023
public class PSKeyVaultCertificateOperation
@@ -33,36 +36,59 @@ public class PSKeyVaultCertificateOperation
3336
public string Name { get; set; }
3437
public string VaultName { get; set; }
3538

36-
internal static PSKeyVaultCertificateOperation FromCertificateOperation(CertificateOperation certificateOperation)
39+
public PSKeyVaultCertificateOperation(){}
40+
41+
internal PSKeyVaultCertificateOperation(Track2Sdk.CertificateOperation certificateOperation)
3742
{
38-
if (certificateOperation == null)
43+
if (certificateOperation == null) return;
44+
45+
Id = certificateOperation.Id;
46+
Status = certificateOperation.Properties?.Status;
47+
StatusDetails = certificateOperation.Properties?.StatusDetails;
48+
RequestId = certificateOperation.Properties.RequestId;
49+
Target = certificateOperation.Properties?.Target;
50+
Issuer = certificateOperation.Properties?.IssuerName;
51+
CancellationRequested = certificateOperation.Properties?.CancellationRequested;
52+
53+
if (certificateOperation.Properties?.Csr != null && certificateOperation.Properties?.Csr?.Length != 0)
3954
{
40-
return null;
55+
CertificateSigningRequest = Convert.ToBase64String(certificateOperation.Properties?.Csr);
4156
}
4257

43-
var kvCertificateOperation = new PSKeyVaultCertificateOperation
58+
if (certificateOperation.Properties?.Error != null)
4459
{
45-
Id = certificateOperation.Id,
46-
Status = certificateOperation.Status,
47-
StatusDetails = certificateOperation.StatusDetails,
48-
RequestId = certificateOperation.RequestId,
49-
Target = certificateOperation.Target,
50-
Issuer = certificateOperation.IssuerParameters.Name,
51-
CancellationRequested = certificateOperation.CancellationRequested,
52-
};
60+
ErrorCode = certificateOperation.Properties?.Error.Code;
61+
ErrorMessage = certificateOperation.Properties?.Error.Message;
62+
}
63+
}
64+
65+
internal PSKeyVaultCertificateOperation(CertificateOperation certificateOperation)
66+
{
67+
if (certificateOperation == null) return;
68+
69+
Id = certificateOperation.Id;
70+
Status = certificateOperation.Status;
71+
StatusDetails = certificateOperation.StatusDetails;
72+
RequestId = certificateOperation.RequestId;
73+
Target = certificateOperation.Target;
74+
Issuer = certificateOperation.IssuerParameters.Name;
75+
CancellationRequested = certificateOperation.CancellationRequested;
5376

5477
if (certificateOperation.Csr != null && certificateOperation.Csr.Length != 0)
5578
{
56-
kvCertificateOperation.CertificateSigningRequest = Convert.ToBase64String(certificateOperation.Csr);
79+
CertificateSigningRequest = Convert.ToBase64String(certificateOperation.Csr);
5780
}
5881

5982
if (certificateOperation.Error != null)
6083
{
61-
kvCertificateOperation.ErrorCode = certificateOperation.Error.Code;
62-
kvCertificateOperation.ErrorMessage = certificateOperation.Error.Message;
84+
ErrorCode = certificateOperation.Error.Code;
85+
ErrorMessage = certificateOperation.Error.Message;
6386
}
87+
}
6488

65-
return kvCertificateOperation;
89+
internal static PSKeyVaultCertificateOperation FromCertificateOperation(CertificateOperation certificateOperation)
90+
{
91+
return new PSKeyVaultCertificateOperation(certificateOperation);
6692
}
6793
}
6894
}

0 commit comments

Comments
 (0)