diff --git a/src/KeyVault/KeyVault.Test/PesterTests/Certificate.Tests.ps1 b/src/KeyVault/KeyVault.Test/PesterTests/Certificate.Tests.ps1
new file mode 100644
index 000000000000..23332075323f
--- /dev/null
+++ b/src/KeyVault/KeyVault.Test/PesterTests/Certificate.Tests.ps1
@@ -0,0 +1,43 @@
+BeforeAll {
+ $vaultName = 'nori-kv765'
+ . "..\Scripts\Common.ps1"
+}
+
+Describe "Import Certificate with policy" {
+ It "ImportCertificateFromFileWithPolicyParameterSet" {
+ $certName = Get-CertificateName
+ $certFilePath = "..\Resources\importCertWithPolicy.pfx"
+ $policy = New-AzKeyVaultCertificatePolicy -SecretContentType "application/x-pkcs12" -SubjectName "CN=contoso.com" -IssuerName "Self" -ValidityInMonths 6 -ReuseKeyOnRenewal
+
+ $cert = Import-AzKeyVaultCertificate -VaultName $vaultName -Name $certName -FilePath $certFilePath -PolicyObject $policy
+ $cert.Policy.SecretContentType | Should -Be "application/x-pkcs12"
+ }
+ It "ImportCertificateFromFileWithPolicyFileParameterSet" {
+ $certName = Get-CertificateName
+ $certFilePath = "..\Resources\importCertWithPolicy.pfx"
+ $policyPath = "..\Resources\certPolicy.json"
+
+ $cert = Import-AzKeyVaultCertificate -VaultName $vaultName -Name $certName -FilePath $certFilePath -PolicyPath $policyPath
+ $cert.Policy.SecretContentType | Should -Be "application/x-pkcs12"
+ }
+ It "ImportWithPrivateKeyFromStringWithPolicyFileParameterSet" {
+ $certName = Get-CertificateName
+ $certFilePath = "..\Resources\importCertWithPolicy.pfx"
+ $policyPath = "..\Resources\certPolicy.json"
+ $Base64StringCertificate = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes($certFilePath))
+
+ $cert = Import-AzKeyVaultCertificate -VaultName $vaultName -Name $certName -CertificateString $Base64StringCertificate -PolicyPath $policyPath
+ $cert.Policy.SecretContentType | Should -Be "application/x-pkcs12"
+ }
+ It "ImportWithPrivateKeyFromCollectionWithPolicyFileParameterSet" {
+ $certName = Get-CertificateName
+ $certFilePath = "..\Resources\importCertWithPolicy.pfx"
+ $policyPath = "..\Resources\certPolicy.json"
+ $certCollection = [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]::new()
+ $certCollection.Import($certFilePath, $null, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
+
+ $cert = Import-AzKeyVaultCertificate -VaultName $vaultName -Name $certName -CertificateCollection $certCollection -PolicyPath $policyPath
+ $cert.Policy.SecretContentType | Should -Be "application/x-pkcs12"
+ }
+}
+
diff --git a/src/KeyVault/KeyVault.Test/Resources/certPolicy.json b/src/KeyVault/KeyVault.Test/Resources/certPolicy.json
new file mode 100644
index 000000000000..88af4f552bc7
--- /dev/null
+++ b/src/KeyVault/KeyVault.Test/Resources/certPolicy.json
@@ -0,0 +1,40 @@
+{
+ "id": "https://myvault.vault.azure.net/certificates/updateCert01/policy",
+ "key_props": {
+ "exportable": true,
+ "kty": "RSA",
+ "key_size": 2048,
+ "reuse_key": false
+ },
+
+ "secret_props": {
+ "contentType": "application/x-pkcs12"
+ },
+
+ "x509_props": {
+ "subject": "CN=KeyVaultTest",
+ "ekus": [],
+ "key_usage": [],
+ "validity_months": 297
+ },
+
+ "lifetime_actions": [
+ {
+ "trigger": {
+ "lifetime_percentage": 80
+ },
+ "action": {
+ "action_type": "EmailContacts"
+ }
+ }
+ ],
+
+ "issuer": {
+ "name": "Unknown"
+ },
+ "attributes": {
+ "enabled": true,
+ "created": 1482188947,
+ "updated": 1482188947
+ }
+}
\ No newline at end of file
diff --git a/src/KeyVault/KeyVault.Test/Resources/importCertWithPolicy.pfx b/src/KeyVault/KeyVault.Test/Resources/importCertWithPolicy.pfx
new file mode 100644
index 000000000000..f89f48815d0e
Binary files /dev/null and b/src/KeyVault/KeyVault.Test/Resources/importCertWithPolicy.pfx differ
diff --git a/src/KeyVault/KeyVault/ChangeLog.md b/src/KeyVault/KeyVault/ChangeLog.md
index 00d175567d07..d27796c6af38 100644
--- a/src/KeyVault/KeyVault/ChangeLog.md
+++ b/src/KeyVault/KeyVault/ChangeLog.md
@@ -18,6 +18,7 @@
- Additional information about change #1
-->
## Upcoming Release
+* Added parameter `PolicyPath` and `PolicyObject` in `Import-AzKeyVaultCertificate` to support custom policy [#20780]
## Version 4.9.2
* Updated Azure.Core to 1.28.0.
diff --git a/src/KeyVault/KeyVault/Commands/Certificate/ImportAzureKeyVaultCertificate.cs b/src/KeyVault/KeyVault/Commands/Certificate/ImportAzureKeyVaultCertificate.cs
index fd5db57e2bb0..8015ccf4a18a 100644
--- a/src/KeyVault/KeyVault/Commands/Certificate/ImportAzureKeyVaultCertificate.cs
+++ b/src/KeyVault/KeyVault/Commands/Certificate/ImportAzureKeyVaultCertificate.cs
@@ -108,6 +108,21 @@ public class ImportAzureKeyVaultCertificate : KeyVaultCmdletBase
HelpMessage = "Specifies the password for the certificate and private key base64 encoded string to import.")]
public SecureString Password { get; set; }
+ ///
+ /// File Path
+ ///
+ [Parameter(Mandatory = false,
+ HelpMessage = "A file path to specify management policy for the certificate that contains JSON encoded policy definition. Mutual-exclusive to PolicyObject.")]
+ public string PolicyPath { get; set; }
+
+ ///
+ /// File Path
+ ///
+ [Parameter(Mandatory = false,
+ ValueFromPipeline = true,
+ HelpMessage = "An in-memory object to specify management policy for the certificate. Mutual-exclusive to PolicyPath.")]
+ public PSKeyVaultCertificatePolicy PolicyObject { get; set; }
+
///
/// Certificate Collection
///
@@ -131,6 +146,7 @@ public class ImportAzureKeyVaultCertificate : KeyVaultCmdletBase
protected override void BeginProcessing()
{
FilePath = this.TryResolvePath(FilePath);
+ PolicyPath = this.TryResolvePath(PolicyPath);
base.BeginProcessing();
}
@@ -143,6 +159,34 @@ private void ValidateParameters()
{
throw new AzPSArgumentException(string.Format(Resources.FileNotFound, this.FilePath), nameof(FilePath));
}
+ if (IsPemFile(FilePath))
+ {
+ ContentType = Constants.PemContentType;
+ }
+ else
+ {
+ ContentType = Constants.Pkcs12ContentType;
+ }
+ }
+ if (this.IsParameterBound(c => c.CertificateCollection))
+ {
+ ContentType = Constants.Pkcs12ContentType;
+ }
+ if (this.IsParameterBound(c => c.PolicyPath) && this.IsParameterBound(c => c.PolicyObject))
+ {
+ throw new AzPSArgumentException($"Parameter {nameof(PolicyPath)} conflicts with Parameter {nameof(PolicyObject)}. Only one of these 2 parameters could be imported at once.", nameof(PolicyPath));
+ }
+ if (this.IsParameterBound(c => c.PolicyPath))
+ {
+ if (!File.Exists(PolicyPath))
+ {
+ throw new AzPSArgumentException(string.Format(Resources.FileNotFound, this.PolicyPath), nameof(PolicyPath));
+ }
+ PolicyObject = PSKeyVaultCertificatePolicy.FromJsonFile(PolicyPath);
+ }
+ if (PolicyObject != null && PolicyObject.SecretContentType != ContentType)
+ {
+ throw new AzPSArgumentException($"User input {ContentType} conflicts with the ContentType stated as {PolicyObject.SecretContentType} in Certificate Policy.", ContentType);
}
}
@@ -162,7 +206,7 @@ public override void ExecuteCmdlet()
if (IsPemFile(FilePath))
{
byte[] pemBytes = File.ReadAllBytes(FilePath);
- certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, pemBytes, Password, Tag?.ConvertToDictionary(), Constants.PemContentType);
+ certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, pemBytes, Password, Tag?.ConvertToDictionary(), Constants.PemContentType, certPolicy: PolicyObject);
}
else
{
@@ -179,8 +223,9 @@ public override void ExecuteCmdlet()
if (doImport)
{
+
byte[] base64Bytes = userProvidedCertColl.Export(X509ContentType.Pfx, Password?.ConvertToString());
- certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, base64Bytes, Password, Tag?.ConvertToDictionary());
+ certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, base64Bytes, Password, Tag?.ConvertToDictionary(), certPolicy: PolicyObject);
}
else
{
@@ -194,12 +239,11 @@ public override void ExecuteCmdlet()
break;
case ImportWithPrivateKeyFromCollectionParameterSet:
- certBundle = this.DataServiceClient.ImportCertificate(VaultName, Name, CertificateCollection, Tag?.ConvertToDictionary());
-
+ certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, CertificateCollection, null, Tag?.ConvertToDictionary(), certPolicy: PolicyObject);
break;
case ImportWithPrivateKeyFromStringParameterSet:
- certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, CertificateString, Password, Tag?.ConvertToDictionary(), ContentType);
+ certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, CertificateString, Password, Tag?.ConvertToDictionary(), ContentType, certPolicy: PolicyObject);
break;
}
diff --git a/src/KeyVault/KeyVault/Models/IKeyVaultDataServiceClient.cs b/src/KeyVault/KeyVault/Models/IKeyVaultDataServiceClient.cs
index a0d1584057b4..6e13e202d3c3 100644
--- a/src/KeyVault/KeyVault/Models/IKeyVaultDataServiceClient.cs
+++ b/src/KeyVault/KeyVault/Models/IKeyVaultDataServiceClient.cs
@@ -164,11 +164,11 @@ public interface IKeyVaultDataServiceClient
PSKeyVaultCertificate MergeCertificate(string vaultName, string certName, byte[] certBytes, Dictionary tags);
- PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType);
+ PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null);
- PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertString, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType);
+ PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertString, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null);
- PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary tags, string contentType = Constants.Pkcs12ContentType);
+ PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null);
PSDeletedKeyVaultCertificate DeleteCertificate(string vaultName, string certName);
diff --git a/src/KeyVault/KeyVault/Models/KeyVaultDataServiceClient.cs b/src/KeyVault/KeyVault/Models/KeyVaultDataServiceClient.cs
index 11798d35b8c1..77fa0982078c 100644
--- a/src/KeyVault/KeyVault/Models/KeyVaultDataServiceClient.cs
+++ b/src/KeyVault/KeyVault/Models/KeyVaultDataServiceClient.cs
@@ -817,12 +817,12 @@ public PSKeyVaultCertificate MergeCertificate(string vaultName, string certName,
}
- public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicyPath = null)
{
return ImportCertificate(vaultName, certName, Convert.ToBase64String(certificate), certPassword, tags, contentType);
}
- public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertColl, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertColl, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicyPath = null)
{
if (string.IsNullOrEmpty(vaultName))
throw new ArgumentNullException(nameof(vaultName));
@@ -855,7 +855,7 @@ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName
return new PSKeyVaultCertificate(certBundle);
}
- public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null)
{
if (string.IsNullOrEmpty(vaultName))
throw new ArgumentNullException(nameof(vaultName));
diff --git a/src/KeyVault/KeyVault/Models/PSKeyVaultCertificate.cs b/src/KeyVault/KeyVault/Models/PSKeyVaultCertificate.cs
index 467570c99782..8a2f495572d0 100644
--- a/src/KeyVault/KeyVault/Models/PSKeyVaultCertificate.cs
+++ b/src/KeyVault/KeyVault/Models/PSKeyVaultCertificate.cs
@@ -30,6 +30,7 @@ public class PSKeyVaultCertificate : PSKeyVaultCertificateIdentityItem
public string SecretId { get; internal set; }
public string Thumbprint { get; set; }
+ public PSKeyVaultCertificatePolicy Policy { get; set; }
public string RecoveryLevel { get; private set; }
internal PSKeyVaultCertificate(CertificateBundle certificateBundle, VaultUriHelper vaultUriHelper)
@@ -156,6 +157,7 @@ internal PSKeyVaultCertificate(KeyVaultCertificateWithPolicy keyVaultCertificate
KeyId = keyVaultCertificate.KeyId?.ToString();
SecretId = keyVaultCertificate.SecretId?.ToString();
+ Policy = PSKeyVaultCertificatePolicy.FromTrack2CertificatePolicy(keyVaultCertificate.Policy);
if (keyVaultCertificate.Properties != null)
{
diff --git a/src/KeyVault/KeyVault/Models/PSKeyVaultCertificatePolicy.cs b/src/KeyVault/KeyVault/Models/PSKeyVaultCertificatePolicy.cs
index fb1aa76bef37..793d233e8e8b 100644
--- a/src/KeyVault/KeyVault/Models/PSKeyVaultCertificatePolicy.cs
+++ b/src/KeyVault/KeyVault/Models/PSKeyVaultCertificatePolicy.cs
@@ -12,12 +12,18 @@
// limitations under the License.
// ----------------------------------------------------------------------------------
+using Microsoft.Azure.Commands.Common.Exceptions;
+using Microsoft.Azure.Commands.KeyVault.Properties;
using Microsoft.Azure.KeyVault.Models;
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Text.Json;
+using Track2CertificateSDK = Azure.Security.KeyVault.Certificates;
namespace Microsoft.Azure.Commands.KeyVault.Models
{
@@ -31,6 +37,8 @@ public class PSKeyVaultCertificatePolicy
public bool? ReuseKeyOnRenewal { get; set; }
public string SubjectName { get; set; }
public List DnsNames { get; set; }
+ public List Emails { get; set; }
+ public List UserPrincipalNames { get; set; }
public List KeyUsage { get; set; }
public List Ekus { get; set; }
public int? ValidityInMonths { get; set; }
@@ -274,6 +282,130 @@ internal CertificatePolicy ToCertificatePolicy()
return certificatePolicy;
}
+ internal bool HasSubjectAlternativeNames()
+ {
+ if ((DnsNames != null && DnsNames.Any())
+ || (Emails != null && Emails.Any())
+ || (UserPrincipalNames != null && UserPrincipalNames.Any()))
+ {
+ return true;
+ }
+ else
+ return false;
+ }
+
+ internal Track2CertificateSDK.SubjectAlternativeNames SetSubjectAlternativeNames(Track2CertificateSDK.SubjectAlternativeNames SubjectAlternativeNames)
+ {
+ if (DnsNames != null && DnsNames.Any())
+ foreach (var dnsName in DnsNames)
+ SubjectAlternativeNames.DnsNames.Add(dnsName);
+ if (Emails != null && Emails.Any())
+ foreach (var email in Emails)
+ SubjectAlternativeNames.Emails.Add(email);
+ if (UserPrincipalNames != null && UserPrincipalNames.Any())
+ foreach (var principalName in UserPrincipalNames)
+ SubjectAlternativeNames.UserPrincipalNames.Add(principalName);
+ return SubjectAlternativeNames;
+ }
+
+ internal Track2CertificateSDK.CertificatePolicy ToTrack2CertificatePolicy()
+ {
+ Track2CertificateSDK.CertificatePolicy certificatePolicy;
+ if (!string.IsNullOrWhiteSpace(IssuerName) || !string.IsNullOrWhiteSpace(SubjectName) || HasSubjectAlternativeNames())
+ {
+ if (!string.IsNullOrWhiteSpace(SubjectName) && HasSubjectAlternativeNames())
+ {
+ Track2CertificateSDK.SubjectAlternativeNames subjectAlternativeNames = new Track2CertificateSDK.SubjectAlternativeNames();
+ subjectAlternativeNames = SetSubjectAlternativeNames(subjectAlternativeNames);
+ certificatePolicy = new Track2CertificateSDK.CertificatePolicy(IssuerName, SubjectName, subjectAlternativeNames);
+
+ }
+ else if (!string.IsNullOrWhiteSpace(SubjectName))
+ {
+ certificatePolicy = new Track2CertificateSDK.CertificatePolicy(IssuerName, SubjectName);
+ }
+ else if (HasSubjectAlternativeNames())
+ {
+ Track2CertificateSDK.SubjectAlternativeNames subjectAlternativeNames = new Track2CertificateSDK.SubjectAlternativeNames();
+ subjectAlternativeNames = SetSubjectAlternativeNames(subjectAlternativeNames);
+ certificatePolicy = new Track2CertificateSDK.CertificatePolicy(IssuerName, subjectAlternativeNames);
+ }
+ else
+ certificatePolicy = new Track2CertificateSDK.CertificatePolicy(IssuerName, SubjectName);
+ }
+ else
+ {
+ certificatePolicy = new Track2CertificateSDK.CertificatePolicy();
+ }
+ certificatePolicy.ContentType = SecretContentType;
+ if ( !string.IsNullOrEmpty(Kty) )
+ certificatePolicy.KeyType = Kty;
+ certificatePolicy.KeySize = KeySize;
+ if (!string.IsNullOrEmpty(Curve))
+ certificatePolicy.KeyCurveName = Curve;
+ certificatePolicy.ReuseKey = ReuseKeyOnRenewal;
+ certificatePolicy.Exportable = Exportable;
+ certificatePolicy.CertificateTransparency = CertificateTransparency;
+ if (!string.IsNullOrWhiteSpace(CertificateType))
+ certificatePolicy.CertificateType = CertificateType;
+ certificatePolicy.Enabled = Enabled;
+ certificatePolicy.ValidityInMonths = ValidityInMonths;
+
+ if (RenewAtNumberOfDaysBeforeExpiry.HasValue ||
+ RenewAtPercentageLifetime.HasValue ||
+ EmailAtNumberOfDaysBeforeExpiry.HasValue ||
+ EmailAtPercentageLifetime.HasValue)
+ {
+ if ((RenewAtNumberOfDaysBeforeExpiry.HasValue ? 1 : 0) +
+ (RenewAtPercentageLifetime.HasValue ? 1 : 0) +
+ (EmailAtNumberOfDaysBeforeExpiry.HasValue ? 1 : 0) +
+ (EmailAtPercentageLifetime.HasValue ? 1 : 0) > 1)
+ {
+ throw new ArgumentException("Only one of the values for RenewAtNumberOfDaysBeforeExpiry, RenewAtPercentageLifetime, EmailAtNumberOfDaysBeforeExpiry, EmailAtPercentageLifetime can be set.");
+ }
+
+ if (RenewAtNumberOfDaysBeforeExpiry.HasValue)
+ {
+ certificatePolicy.LifetimeActions.Add(
+ new Track2CertificateSDK.LifetimeAction(Track2CertificateSDK.CertificatePolicyAction.AutoRenew)
+ {
+ DaysBeforeExpiry = RenewAtNumberOfDaysBeforeExpiry
+ }
+ );
+ }
+
+ if (RenewAtPercentageLifetime.HasValue)
+ {
+ certificatePolicy.LifetimeActions.Add(
+ new Track2CertificateSDK.LifetimeAction(Track2CertificateSDK.CertificatePolicyAction.AutoRenew)
+ {
+ LifetimePercentage = RenewAtPercentageLifetime
+ }
+ );
+ }
+ if (EmailAtNumberOfDaysBeforeExpiry.HasValue)
+ {
+ certificatePolicy.LifetimeActions.Add(
+ new Track2CertificateSDK.LifetimeAction(Track2CertificateSDK.CertificatePolicyAction.EmailContacts)
+ {
+ DaysBeforeExpiry = EmailAtNumberOfDaysBeforeExpiry
+ }
+ );
+ }
+
+ if (EmailAtPercentageLifetime.HasValue)
+ {
+ certificatePolicy.LifetimeActions.Add(
+ new Track2CertificateSDK.LifetimeAction(Track2CertificateSDK.CertificatePolicyAction.EmailContacts)
+ {
+ LifetimePercentage = EmailAtPercentageLifetime
+ }
+ );
+ }
+ }
+
+ return certificatePolicy;
+ }
internal static PSKeyVaultCertificatePolicy FromCertificatePolicy(CertificatePolicy certificatePolicy)
{
@@ -304,6 +436,258 @@ internal static PSKeyVaultCertificatePolicy FromCertificatePolicy(CertificatePol
};
}
+ internal static PSKeyVaultCertificatePolicy FromTrack2CertificatePolicy(Track2CertificateSDK.CertificatePolicy certificatePolicy)
+ {
+ return new PSKeyVaultCertificatePolicy
+ {
+ SecretContentType = certificatePolicy.ContentType?.ToString(),
+ Kty = certificatePolicy.KeyType?.ToString(),
+ KeySize = certificatePolicy.KeySize,
+ Curve = certificatePolicy.KeyCurveName?.ToString(),
+ Exportable = certificatePolicy.Exportable,
+ ReuseKeyOnRenewal = certificatePolicy.ReuseKey,
+ SubjectName = certificatePolicy.Subject,
+ DnsNames = certificatePolicy.SubjectAlternativeNames?.DnsNames?.ToList(),
+ Emails = certificatePolicy.SubjectAlternativeNames?.Emails?.ToList(),
+ UserPrincipalNames = certificatePolicy.SubjectAlternativeNames?.UserPrincipalNames?.ToList(),
+ KeyUsage = certificatePolicy.KeyUsage == null ? null : certificatePolicy.KeyUsage == null ? null : certificatePolicy.KeyUsage.Select(keyUsage => keyUsage.ToString()).ToList(),
+ Ekus = certificatePolicy.EnhancedKeyUsage == null ? null : new List(certificatePolicy.EnhancedKeyUsage),
+ ValidityInMonths = certificatePolicy.ValidityInMonths,
+ CertificateTransparency = certificatePolicy.CertificateTransparency,
+ IssuerName = certificatePolicy.IssuerName,
+ CertificateType = certificatePolicy.CertificateType,
+ RenewAtNumberOfDaysBeforeExpiry = certificatePolicy.LifetimeActions == null ? null : FindIntValueForAutoRenewAction(certificatePolicy.LifetimeActions),
+ RenewAtPercentageLifetime = certificatePolicy.LifetimeActions == null ? null : FindIntValueForAutoRenewAction(certificatePolicy.LifetimeActions),
+ EmailAtNumberOfDaysBeforeExpiry = certificatePolicy.LifetimeActions == null ? null : FindIntValueForEmailAction(certificatePolicy.LifetimeActions),
+ EmailAtPercentageLifetime = certificatePolicy.LifetimeActions == null ? null : FindIntValueForEmailAction(certificatePolicy.LifetimeActions),
+ Enabled = certificatePolicy.Enabled,
+ Created = certificatePolicy.CreatedOn.HasValue ? certificatePolicy.CreatedOn.Value.DateTime : (DateTime?)null,
+ Updated = certificatePolicy.UpdatedOn.HasValue ? certificatePolicy.UpdatedOn.Value.DateTime : (DateTime?)null,
+ };
+ }
+ private void ReadKeyProperties(JsonElement json)
+ {
+ foreach (JsonProperty item in json.EnumerateObject())
+ {
+ switch (item.Name)
+ {
+ case "kty":
+ Kty = item.Value.GetString();
+ break;
+ case "reuse_key":
+ ReuseKeyOnRenewal = item.Value.GetBoolean();
+ break;
+ case "exportable":
+ Exportable = item.Value.GetBoolean();
+ break;
+ case "crv":
+ Curve = item.Value.GetString();
+ break;
+ case "key_size":
+ KeySize = item.Value.GetInt32();
+ break;
+ }
+ }
+ }
+ private void ReadSecretProperties(JsonElement json)
+ {
+ if (json.TryGetProperty("contentType", out var value))
+ {
+ SecretContentType = value.GetString();
+ }
+ }
+
+ private void ReadSubjectAlternativeNames(JsonProperty json)
+ {
+ foreach (JsonProperty item in json.Value.EnumerateObject())
+ {
+ switch (item.Name)
+ {
+ case "dns_names":
+ DnsNames = new List();
+ foreach (JsonElement item2 in item.Value.EnumerateArray())
+ {
+ DnsNames.Add(item2.GetString());
+ }
+ break;
+ case "emails":
+ Emails = new List();
+ foreach (JsonElement item2 in item.Value.EnumerateArray())
+ {
+ Emails.Add(item2.GetString());
+ }
+ break;
+ case "upns":
+ UserPrincipalNames = new List();
+ foreach (JsonElement item2 in item.Value.EnumerateArray())
+ {
+ UserPrincipalNames.Add(item2.GetString());
+ }
+ break;
+ }
+ }
+ }
+
+ private void ReadX509CertificateProperties(JsonElement json)
+ {
+ foreach (JsonProperty item in json.EnumerateObject())
+ {
+ switch (item.Name)
+ {
+ case "subject":
+ SubjectName = item.Value.GetString();
+ break;
+ case "sans":
+ var SubjectAlternativeNames = new Track2CertificateSDK.SubjectAlternativeNames();
+ ReadSubjectAlternativeNames(item);
+ // SubjectAlternativeNames .ReadProperties(item.Value);
+ break;
+ case "key_usage":
+ foreach (JsonElement item2 in item.Value.EnumerateArray())
+ {
+ KeyUsage.Add(item2.GetString());
+ }
+
+ break;
+ case "ekus":
+ foreach (JsonElement item3 in item.Value.EnumerateArray())
+ {
+ Ekus.Add(item3.GetString());
+ }
+
+ break;
+ case "validity_months":
+ ValidityInMonths = item.Value.GetInt32();
+ break;
+ }
+ }
+ }
+
+ private void ReadIssuerProperties(JsonElement json)
+ {
+ foreach (JsonProperty item in json.EnumerateObject())
+ {
+ switch (item.Name)
+ {
+ case "cert_transparency":
+ CertificateTransparency = item.Value.GetBoolean();
+ break;
+
+ case "cty":
+ CertificateType = item.Value.GetString();
+ break;
+
+ case "name":
+ IssuerName = item.Value.GetString();
+ break;
+ }
+ }
+ }
+
+ private void ReadAttributesProperties(JsonElement json)
+ {
+ foreach (JsonProperty item in json.EnumerateObject())
+ {
+ switch (item.Name)
+ {
+ case "enabled":
+ Enabled = item.Value.GetBoolean();
+ break;
+ case "created":
+ Created = DateTimeOffset.FromUnixTimeSeconds(item.Value.GetInt64()).DateTime;
+ break;
+ case "updated":
+ Updated = DateTimeOffset.FromUnixTimeSeconds(item.Value.GetInt64()).DateTime;
+ break;
+ }
+ }
+ }
+ internal static PSKeyVaultCertificatePolicy FromJsonFile(string filePath)
+ {
+ PSKeyVaultCertificatePolicy policy = new PSKeyVaultCertificatePolicy();
+ JsonElement policyJson;
+ if (".json".Equals(Path.GetExtension(filePath), StringComparison.OrdinalIgnoreCase))
+ {
+ using (StreamReader r = new StreamReader(filePath))
+ {
+ string jsonContent = r.ReadToEnd();
+ policyJson = JsonDocument.Parse(jsonContent).RootElement;
+ }
+ }
+ else
+ {
+ throw new AzPSArgumentException(string.Format(Resources.UnsupportedFileFormat, filePath), nameof(filePath));
+ }
+ foreach (JsonProperty item in policyJson.EnumerateObject())
+ {
+ switch (item.Name)
+ {
+ case "key_props":
+ policy.ReadKeyProperties(item.Value);
+ break;
+ case "secret_props":
+ policy.ReadSecretProperties(item.Value);
+ break;
+ case "x509_props":
+ policy.ReadX509CertificateProperties(item.Value);
+ break;
+ case "issuer":
+ policy.ReadIssuerProperties(item.Value);
+ break;
+ case "attributes":
+ policy.ReadAttributesProperties(item.Value);
+ break;
+
+ case "lifetime_actions":
+ string actionType = null;
+ string triggerType = null;
+ int? triggerValue = null;
+
+ foreach (JsonElement item2 in item.Value.EnumerateArray())
+ {
+ if (item.Value.EnumerateArray().Count() > 1)
+ throw new AzPSArgumentException(string.Format("Json file property {0} exceed expected number 1.", item.Name), nameof(item.Name));
+ foreach (JsonProperty item3 in item2.EnumerateObject())
+ {
+ if (item3.Name == "trigger")
+ {
+ foreach (JsonProperty item4 in item3.Value.EnumerateObject())
+ {
+ triggerType = item4.Name;
+ triggerValue = item4.Value.GetInt32();
+ }
+ }
+ else if (item3.Name == "action")
+ {
+ foreach (JsonProperty item4 in item3.Value.EnumerateObject())
+ {
+ if (item4.Name == "action_type")
+ actionType = item4.Value.GetString();
+ }
+ }
+ }
+ }
+
+ if (actionType == ActionType.AutoRenew.ToString())
+ {
+ if (triggerType == "days_before_expiry")
+ policy.RenewAtNumberOfDaysBeforeExpiry = triggerValue;
+ else if (triggerType == "lifetime_percentage")
+ policy.RenewAtPercentageLifetime = triggerValue;
+ }
+ else if (actionType == ActionType.EmailContacts.ToString())
+ {
+ if (triggerType == "days_before_expiry")
+ policy.EmailAtNumberOfDaysBeforeExpiry = triggerValue;
+ else if (triggerType == "lifetime_percentage")
+ policy.EmailAtPercentageLifetime = triggerValue;
+ }
+ break;
+ }
+ }
+ return policy;
+ }
+
private static int? FindIntValueForAutoRenewAction(IEnumerable lifetimeActions, Func intValueGetter)
{
var lifetimeAction = lifetimeActions.FirstOrDefault(x => x.Action.ActionType.HasValue && 0 == string.Compare(x.Action.ActionType.Value.ToString(), ActionType.AutoRenew.ToString(), true) && intValueGetter(x.Trigger).HasValue);
@@ -316,6 +700,14 @@ internal static PSKeyVaultCertificatePolicy FromCertificatePolicy(CertificatePol
return intValueGetter(lifetimeAction.Trigger);
}
+ private static int? FindIntValueForAutoRenewAction(IList lifetimeActions)
+ {
+ var lifetimeAction =
+ lifetimeActions.FirstOrDefault(x => string.IsNullOrEmpty(x.Action.ToString()) && 0 == string.Compare(x.Action.ToString(), Track2CertificateSDK.CertificatePolicyAction.AutoRenew.ToString(), true)
+ && (x.DaysBeforeExpiry.HasValue || x.LifetimePercentage.HasValue));
+ return lifetimeAction == null ? null : (lifetimeAction.DaysBeforeExpiry ?? lifetimeAction.LifetimePercentage);
+ }
+
private static int? FindIntValueForEmailAction(IEnumerable lifetimeActions, Func intValueGetter)
{
var lifetimeAction = lifetimeActions.FirstOrDefault(x => x.Action.ActionType.HasValue && 0 == string.Compare(x.Action.ActionType.Value.ToString(), ActionType.EmailContacts.ToString(), true) && intValueGetter(x.Trigger).HasValue);
@@ -328,6 +720,14 @@ internal static PSKeyVaultCertificatePolicy FromCertificatePolicy(CertificatePol
return intValueGetter(lifetimeAction.Trigger);
}
+ private static int? FindIntValueForEmailAction(IList lifetimeActions)
+ {
+ var lifetimeAction =
+ lifetimeActions.FirstOrDefault(x => !string.IsNullOrEmpty(x.Action.ToString()) && 0 == string.Compare(x.Action.ToString(), Track2CertificateSDK.CertificatePolicyAction.EmailContacts.ToString(), true)
+ && (x.DaysBeforeExpiry.HasValue || x.LifetimePercentage.HasValue));
+ return lifetimeAction == null ? null : (lifetimeAction.DaysBeforeExpiry ?? lifetimeAction.LifetimePercentage);
+ }
+
private void ValidateInternal(
IList dnsNames,
IList ekus,
@@ -544,5 +944,19 @@ private void ValidateSubjectName(string subjectName)
throw new ArgumentException("The subject name provided is not a valid X500 name.", e);
}
}
+
+ public override string ToString()
+ {
+ if (this == null) return string.Empty;
+
+ var sb = new StringBuilder();
+ sb.AppendLine();
+ sb.AppendFormat("{0, -15}: {1}{2}", "Secret Content Type", SecretContentType, Environment.NewLine);
+ sb.AppendFormat("{0, -15}: {1}{2}", "Issuer Name", IssuerName, Environment.NewLine);
+ sb.AppendFormat("{0, -15}: {1}{2}", "Created On", Created, Environment.NewLine);
+ sb.AppendFormat("{0, -15}: {1}{2}", "Updated On", Updated, Environment.NewLine);
+ sb.AppendLine("...");
+ return sb.ToString();
+ }
}
}
diff --git a/src/KeyVault/KeyVault/Track2Models/Track2KeyVaultDataServiceClient.cs b/src/KeyVault/KeyVault/Track2Models/Track2KeyVaultDataServiceClient.cs
index 1c275a998d85..cb4fe26e63d2 100644
--- a/src/KeyVault/KeyVault/Track2Models/Track2KeyVaultDataServiceClient.cs
+++ b/src/KeyVault/KeyVault/Track2Models/Track2KeyVaultDataServiceClient.cs
@@ -239,19 +239,20 @@ public IEnumerable GetDeletedCertifica
throw new NotImplementedException();
}
- public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null)
{
- return VaultClient.ImportCertificate(vaultName, certName, Convert.FromBase64String(certificate), certPassword, tags, contentType);
+ return VaultClient.ImportCertificate(vaultName, certName, Convert.FromBase64String(certificate), certPassword, tags, contentType, certPolicy);
}
- public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null)
{
- return VaultClient.ImportCertificate(vaultName, certName, certificate, certPassword, tags, contentType);
+ return VaultClient.ImportCertificate(vaultName, certName, certificate, certPassword, tags, contentType, certPolicy);
}
- public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, SecureString certPassword, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null)
{
- throw new NotImplementedException();
+ // Export contentType ref: https://github.com/Azure/azure-sdk-for-net/blob/376b04164356dc9821923b75f2223163a2701669/sdk/keyvault/Microsoft.Azure.KeyVault/src/Customized/KeyVaultClientExtensions.cs#L605
+ return VaultClient.ImportCertificate(vaultName, certName, certificateCollection.Export(X509ContentType.Pfx), certPassword, tags, contentType, certPolicy);
}
public PSKeyVaultCertificate MergeCertificate(string vaultName, string certName, X509Certificate2Collection certs, IDictionary tags)
diff --git a/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs b/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs
index 92f206f5f2fe..c9eae3ec046a 100644
--- a/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs
+++ b/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs
@@ -3,12 +3,14 @@
using Azure.Security.KeyVault.Keys.Cryptography;
using Microsoft.Azure.Commands.Common.Authentication.Abstractions;
+using Microsoft.Azure.Commands.Common.Exceptions;
using Microsoft.Azure.Commands.KeyVault.Models;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using System;
using System.Collections;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Security;
namespace Microsoft.Azure.Commands.KeyVault.Track2Models
@@ -211,7 +213,7 @@ private PSKeyRotationPolicy SetKeyRotationPolicy(KeyClient client, string vaultN
#endregion
#region Certificate actions
- internal PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString password, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ internal PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString password, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null)
{
if (string.IsNullOrEmpty(vaultName))
throw new ArgumentNullException(nameof(vaultName));
@@ -221,17 +223,18 @@ internal PSKeyVaultCertificate ImportCertificate(string vaultName, string certNa
throw new ArgumentNullException(nameof(certificate));
var certClient = CreateCertificateClient(vaultName);
- return ImportCertificate(certClient, certName, certificate, password, tags, contentType);
+ return ImportCertificate(certClient, certName, certificate, password, tags, contentType, certPolicy);
}
- private PSKeyVaultCertificate ImportCertificate(CertificateClient certClient, string certName, byte[] certificate, SecureString password, IDictionary tags, string contentType = Constants.Pkcs12ContentType)
+ private PSKeyVaultCertificate ImportCertificate(CertificateClient certClient, string certName, byte[] certificate, SecureString password, IDictionary tags, string contentType = Constants.Pkcs12ContentType, PSKeyVaultCertificatePolicy certPolicy = null)
{
+ CertificatePolicy certificatePolicy = certPolicy?.ToTrack2CertificatePolicy() ?? new CertificatePolicy()
+ {
+ ContentType = contentType
+ };
var options = new ImportCertificateOptions(certName, certificate)
{
- Policy = new CertificatePolicy()
- {
- ContentType = contentType
- },
+ Policy = certificatePolicy,
Password = password?.ConvertToString()
};
tags?.ForEach((entry) =>
diff --git a/src/KeyVault/KeyVault/help/Import-AzKeyVaultCertificate.md b/src/KeyVault/KeyVault/help/Import-AzKeyVaultCertificate.md
index dc32a2f63008..db1d990f609a 100644
--- a/src/KeyVault/KeyVault/help/Import-AzKeyVaultCertificate.md
+++ b/src/KeyVault/KeyVault/help/Import-AzKeyVaultCertificate.md
@@ -16,22 +16,23 @@ Imports a certificate to a key vault.
### ImportCertificateFromFile (Default)
```
Import-AzKeyVaultCertificate [-VaultName] [-Name] -FilePath
- [-Password ] [-Tag ] [-DefaultProfile ] [-WhatIf] [-Confirm]
- []
+ [-Password ] [-PolicyPath ] [-PolicyObject ]
+ [-Tag ] [-DefaultProfile ] [-WhatIf] [-Confirm] []
```
### ImportWithPrivateKeyFromString
```
Import-AzKeyVaultCertificate [-VaultName] [-Name] -CertificateString
- [-ContentType ] [-Password ] [-Tag ]
- [-DefaultProfile ] [-WhatIf] [-Confirm] []
+ [-ContentType ] [-Password ] [-PolicyPath ]
+ [-PolicyObject ] [-Tag ] [-DefaultProfile ]
+ [-WhatIf] [-Confirm] []
```
### ImportWithPrivateKeyFromCollection
```
-Import-AzKeyVaultCertificate [-VaultName] [-Name]
- [-CertificateCollection] [-Tag ]
- [-DefaultProfile ] [-WhatIf] [-Confirm] []
+Import-AzKeyVaultCertificate [-VaultName] [-Name] [-PolicyPath ]
+ [-PolicyObject ] [-CertificateCollection]
+ [-Tag ] [-DefaultProfile ] [-WhatIf] [-Confirm] []
```
## DESCRIPTION
@@ -84,7 +85,6 @@ The second command imports the certificate named ImportCert01 into the CosotosoK
$Password = ConvertTo-SecureString -String "123" -AsPlainText -Force
$Base64String = [System.Convert]::ToBase64String([System.IO.File]::ReadAllBytes("import.pfx"))
Import-AzKeyVaultCertificate -VaultName "ContosoKV01" -Name "ImportCert01" -CertificateString $Base64String -Password $Password
-
```
```output
@@ -119,6 +119,59 @@ stores it in the $Password variable.
The second command reads a certificate as a Base64 encoded representation.
The third command imports the certificate named ImportCert01 into the CosotosoKV01 key vault.
+### Example 3: Import a key vault certificate with PolicyFile
+```powershell
+$Password = ConvertTo-SecureString -String "123" -AsPlainText -Force
+Import-AzKeyVaultCertificate -VaultName "ContosoKV01" -Name "ImportCert01" -FilePath "C:\Users\contosoUser\Desktop\import.pfx" -Password $Password -PolicyPath "C:\Users\contosoUser\Desktop\policy.json"
+```
+
+```output
+Name : importCert01
+Certificate : [Subject]
+ CN=contoso.com
+
+ [Issuer]
+ CN=contoso.com
+
+ [Serial Number]
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+
+ [Not Before]
+ 2/8/2016 3:11:45 PM
+
+ [Not After]
+ 8/8/2016 4:21:45 PM
+
+ [Thumbprint]
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+
+KeyId : https://ContosoKV01.vault.azure.net/keys/ImportCert01/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+SecretId : https://ContosoKV01.vault.azure.net/secrets/ImportCert01/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+Thumbprint : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+Policy :
+ Secret Content Type: application/x-pkcs12
+ Issuer Name : Unknown
+ Created On : 3/22/2023 6:00:52 AM
+ Updated On : 4/27/2023 9:52:53 AM
+ ...
+RecoveryLevel : Recoverable+Purgeable
+Enabled : True
+Expires : 6/9/2023 6:20:26 AM
+NotBefore : 3/11/2023 6:20:26 AM
+Created : 4/24/2023 9:05:51 AM
+Updated : 4/24/2023 9:05:51 AM
+Tags : {}
+VaultName : ContosoKV01
+Name : ImportCert01
+Version : XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+Id : https://ContosoKV01.vault.azure.net/certificates/ImportCert01/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+```
+
+The first command uses the ConvertTo-SecureString cmdlet to create a secure password, and then
+stores it in the $Password variable.
+The second command imports the certificate named ImportCert01 into the CosotosoKV01 key vault with
+a policy defined by file.
+
## PARAMETERS
### -CertificateCollection
@@ -227,6 +280,36 @@ Accept pipeline input: False
Accept wildcard characters: False
```
+### -PolicyObject
+An in-memory object to specify management policy for the certificate. Mutual-exclusive to PolicyPath.
+
+```yaml
+Type: Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultCertificatePolicy
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: True (ByValue)
+Accept wildcard characters: False
+```
+
+### -PolicyPath
+A file path to specify management policy for the certificate that contains JSON encoded policy definition. Mutual-exclusive to PolicyObject.
+
+```yaml
+Type: System.String
+Parameter Sets: (All)
+Aliases:
+
+Required: False
+Position: Named
+Default value: None
+Accept pipeline input: False
+Accept wildcard characters: False
+```
+
### -Tag
Key-value pairs in the form of a hash table. For example:
@{key0="value0";key1=$null;key2="value2"}