Skip to content

Commit

Permalink
[KeyVault] Supported importing pem certificate by Import-AzKeyVaultCe…
Browse files Browse the repository at this point in the history
…rtificate (#18644)

* [KeyVault] Supported importing pem certificate by Import-AzKeyVaultCertificate

* explicitly specify content type though unnessary

* Update Az.KeyVault.psd1

* Update src/KeyVault/KeyVault/Az.KeyVault.psd1
  • Loading branch information
BethanyZhou authored Jun 28, 2022
1 parent 31c2c05 commit 4a44c97
Show file tree
Hide file tree
Showing 31 changed files with 188 additions and 45 deletions.
1 change: 1 addition & 0 deletions src/KeyVault/KeyVault/Az.KeyVault.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ RequiredAssemblies = 'Microsoft.Azure.KeyVault.dll',
'Microsoft.Azure.KeyVault.WebKey.dll',
'Microsoft.Azure.Management.KeyVault.dll',
'Azure.Security.KeyVault.Keys.dll',
'Azure.Security.KeyVault.Certificates.dll',
'Azure.Security.KeyVault.Administration.dll',
'BouncyCastle.Crypto.dll'

Expand Down
1 change: 1 addition & 0 deletions src/KeyVault/KeyVault/ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- Additional information about change #1
-->
## Upcoming Release
* Supported importing pem certificate by `Import-AzKeyVaultCertificate` [#18494]
* Supported accepting rotation policy in a JSON file
* [Breaking Change] Changed parameter `ExpiresIn` in `Set-AzKeyVaultKeyRotationPolicy` from TimeSpan? to string. It must be an ISO 8601 duration like "P30D" for 30 days.
* [Breaking Change] Changed output properties `ExpiresIn`, `TimeAfterCreate` and `TimeBeforeExpiry` of `Set-AzKeyVaultKeyRotationPolicy` and `Get-AzKeyVaultKeyRotationPolicy` from TimeSpan? to string.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
using KeyVaultProperties = Microsoft.Azure.Commands.KeyVault.Properties;
using Microsoft.Azure.KeyVault.Models;
using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters;
using Microsoft.WindowsAzure.Commands.Utilities.Common;
using Microsoft.Azure.Commands.Common.Exceptions;
using Microsoft.Azure.Commands.KeyVault.Properties;

namespace Microsoft.Azure.Commands.KeyVault
{
Expand Down Expand Up @@ -116,51 +119,78 @@ public class ImportAzureKeyVaultCertificate : KeyVaultCmdletBase

#endregion

protected override void BeginProcessing()
{
FilePath = this.TryResolvePath(FilePath);
base.BeginProcessing();
}

private void ValidateParameters()
{
// Verify the FileNotFound whether exists
if (this.IsParameterBound(c => c.FilePath))
{
if (!File.Exists(FilePath))
{
throw new AzPSArgumentException(string.Format(Resources.FileNotFound, this.FilePath), nameof(FilePath));
}
}
}

public override void ExecuteCmdlet()
{
if (ShouldProcess(Name, Properties.Resources.ImportCertificate))
{
List<CertificateBundle> certBundleList = new List<CertificateBundle>();
ValidateParameters();

PSKeyVaultCertificate certBundle = null;

switch (ParameterSetName)
{
case ImportCertificateFromFileParameterSet:

bool doImport = false;
X509Certificate2Collection userProvidedCertColl = InitializeCertificateCollection();

// look for at least one certificate which contains a private key
foreach (var cert in userProvidedCertColl)
{
doImport |= cert.HasPrivateKey;
if (doImport)
break;
}

if (doImport)
// Pem file can't be handled by X509Certificate2Collection in dotnet standard
// Just read it as raw data and pass it to service side
if (IsPemFile(FilePath))
{
byte[] base64Bytes = userProvidedCertColl.Export(X509ContentType.Pfx, Password?.ConvertToString());
string base64CertCollection = Convert.ToBase64String(base64Bytes);
certBundle = this.DataServiceClient.ImportCertificate(VaultName, Name, base64CertCollection, Password, Tag == null ? null : Tag.ConvertToDictionary());
byte[] pemBytes = File.ReadAllBytes(FilePath);
certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, pemBytes, Password, Tag?.ConvertToDictionary(), Constants.PemContentType);
}
else
{
certBundle = this.DataServiceClient.MergeCertificate(
VaultName,
Name,
userProvidedCertColl,
Tag == null ? null : Tag.ConvertToDictionary());
bool doImport = false;
X509Certificate2Collection userProvidedCertColl = InitializeCertificateCollection();

// look for at least one certificate which contains a private key
foreach (var cert in userProvidedCertColl)
{
doImport |= cert.HasPrivateKey;
if (doImport)
break;
}

if (doImport)
{
byte[] base64Bytes = userProvidedCertColl.Export(X509ContentType.Pfx, Password?.ConvertToString());
certBundle = this.Track2DataClient.ImportCertificate(VaultName, Name, base64Bytes, Password, Tag?.ConvertToDictionary());
}
else
{
certBundle = this.DataServiceClient.MergeCertificate(
VaultName,
Name,
userProvidedCertColl,
Tag == null ? null : Tag.ConvertToDictionary());
}
}
break;

case ImportWithPrivateKeyFromCollectionParameterSet:
certBundle = this.DataServiceClient.ImportCertificate(VaultName, Name, CertificateCollection, Tag == null ? null : Tag.ConvertToDictionary());
certBundle = this.DataServiceClient.ImportCertificate(VaultName, Name, CertificateCollection, Tag?.ConvertToDictionary());

break;

case ImportWithPrivateKeyFromStringParameterSet:
certBundle = this.DataServiceClient.ImportCertificate(VaultName, Name, CertificateString, Password, Tag == null ? null : Tag.ConvertToDictionary());
certBundle = this.DataServiceClient.ImportCertificate(VaultName, Name, CertificateString, Password, Tag?.ConvertToDictionary());

break;
}
Expand All @@ -169,6 +199,11 @@ public override void ExecuteCmdlet()
}
}

private bool IsPemFile(string filePath)
{
return ".pem".Equals(Path.GetExtension(FilePath), StringComparison.OrdinalIgnoreCase);
}

internal X509Certificate2Collection InitializeCertificateCollection()
{
FileInfo certFile = new FileInfo(ResolveUserPath(this.FilePath));
Expand Down
1 change: 1 addition & 0 deletions src/KeyVault/KeyVault/KeyVault.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
<ItemGroup>
<PackageReference Include="Azure.Security.KeyVault.Administration" Version="4.0.0" />
<PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.3.0" />
<PackageReference Include="Azure.Security.KeyVault.Certificates" Version="4.3.0" />
<PackageReference Include="Portable.BouncyCastle" Version="1.8.8" />
<PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.1" />
<PackageReference Include="Microsoft.Azure.KeyVault.WebKey" Version="3.0.1" />
Expand Down
8 changes: 6 additions & 2 deletions src/KeyVault/KeyVault/Models/IKeyVaultDataServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,13 @@ public interface IKeyVaultDataServiceClient

PSKeyVaultCertificate MergeCertificate(string vaultName, string certName, X509Certificate2Collection certs, IDictionary<string, string> tags);

PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertColl, SecureString certPassword, IDictionary<string, string> tags);
PSKeyVaultCertificate MergeCertificate(string vaultName, string certName, byte[] certBytes, Dictionary<string, string> tags);

PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary<string, string> tags);
PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType);

PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertString, SecureString certPassword, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType);

PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType);

PSDeletedKeyVaultCertificate DeleteCertificate(string vaultName, string certName);

Expand Down
20 changes: 15 additions & 5 deletions src/KeyVault/KeyVault/Models/KeyVaultDataServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -786,6 +786,11 @@ public string BackupCertificate(string vaultName, string certificateName, string
return outputBlobPath;
}

public PSKeyVaultCertificate MergeCertificate(string vaultName, string name, byte[] certBytes, Dictionary<string, string> tags)
{
throw new NotImplementedException();
}

public PSKeyVaultCertificate MergeCertificate(string vaultName, string certName, X509Certificate2Collection certs, IDictionary<string, string> tags)
{
if (string.IsNullOrEmpty(vaultName))
Expand All @@ -812,7 +817,12 @@ public PSKeyVaultCertificate MergeCertificate(string vaultName, string certName,

}

public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertColl, SecureString certPassword, IDictionary<string, string> tags)
public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType)
{
return ImportCertificate(vaultName, certName, Convert.ToBase64String(certificate), certPassword, tags, contentType);
}

public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertColl, SecureString certPassword, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType)
{
if (string.IsNullOrEmpty(vaultName))
throw new ArgumentNullException(nameof(vaultName));
Expand All @@ -827,14 +837,13 @@ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName

var password = (certPassword == null) ? string.Empty : certPassword.ConvertToString();


try
{
certBundle = this.keyVaultClient.ImportCertificateAsync(vaultAddress, certName, base64CertColl, password, new CertificatePolicy
{
SecretProperties = new SecretProperties
{
ContentType = "application/x-pkcs12"
ContentType = contentType
}
}, null, tags).GetAwaiter().GetResult();
}
Expand All @@ -846,7 +855,7 @@ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName
return new PSKeyVaultCertificate(certBundle);
}

public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary<string, string> tags)
public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType)
{
if (string.IsNullOrEmpty(vaultName))
throw new ArgumentNullException(nameof(vaultName));
Expand All @@ -864,7 +873,7 @@ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName
{
SecretProperties = new SecretProperties
{
ContentType = "application/x-pkcs12"
ContentType = contentType
}
}, null, tags).GetAwaiter().GetResult();
}
Expand All @@ -875,6 +884,7 @@ public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName

return new PSKeyVaultCertificate(certBundle);
}

public IEnumerable<PSKeyVaultCertificateContact> GetCertificateContacts(string vaultName)
{
if (string.IsNullOrEmpty(vaultName))
Expand Down
52 changes: 45 additions & 7 deletions src/KeyVault/KeyVault/Models/PSKeyVaultCertificate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
// limitations under the License.
// ----------------------------------------------------------------------------------

using Azure.Security.KeyVault.Certificates;

using Microsoft.Azure.Commands.KeyVault.Properties;
using Microsoft.Azure.KeyVault.Models;
using System;
Expand Down Expand Up @@ -42,9 +44,8 @@ internal PSKeyVaultCertificate(CertificateBundle certificateBundle, VaultUriHelp

SetObjectIdentifier(vaultUriHelper, certificateBundle.CertificateIdentifier);

// VaultName formatted incorrect in certificateBundle
var vaultUri = new Uri(certificateBundle.CertificateIdentifier.Vault);
VaultName = vaultUri.Host.Split('.').First();
// Vault formatted as "https://{vaultName}.vault.azure.net:443" in certificateBundle
VaultName = new Uri(certificateBundle.CertificateIdentifier.Vault).Host.Split('.').First();

if ( certificateBundle.Cer != null )
{
Expand Down Expand Up @@ -88,14 +89,12 @@ internal PSKeyVaultCertificate(CertificateBundle certificateBundle)
if (certificateBundle.CertificateIdentifier == null)
throw new ArgumentException(Resources.InvalidKeyIdentifier);

var vaultUri = new Uri(certificateBundle.CertificateIdentifier.Vault);

SetObjectIdentifier(new ObjectIdentifier
{
Id = certificateBundle.CertificateIdentifier.Identifier,
Name = certificateBundle.CertificateIdentifier.Name,
// VaultName formatted incorrect in certificateBundle
VaultName = vaultUri.Host.Split('.').First(),
// Vault formatted as "https://{vaultName}.vault.azure.net:443" in certificateBundle
VaultName = new Uri(certificateBundle.CertificateIdentifier.Vault).Host.Split('.').First(),
Version = certificateBundle.CertificateIdentifier.Version
});

Expand Down Expand Up @@ -131,6 +130,45 @@ internal PSKeyVaultCertificate(CertificateBundle certificateBundle)
}
}

internal PSKeyVaultCertificate(KeyVaultCertificateWithPolicy keyVaultCertificate)
{
if (keyVaultCertificate == null)
{
throw new ArgumentNullException(nameof(keyVaultCertificate));
}
if (keyVaultCertificate.Id == null)
throw new ArgumentException(Resources.InvalidKeyIdentifier);

SetObjectIdentifier(new ObjectIdentifier
{
Id = keyVaultCertificate.Id.ToString(),
Name = keyVaultCertificate.Name,
// Extract VaultName from VaultUri
VaultName = keyVaultCertificate.Properties?.VaultUri.Host.Split('.').First(),
Version = keyVaultCertificate.Properties?.Version
});

if (keyVaultCertificate.Cer != null)
{
Certificate = new X509Certificate2(keyVaultCertificate.Cer);
Thumbprint = Certificate.Thumbprint;
}

KeyId = keyVaultCertificate.KeyId?.ToString();
SecretId = keyVaultCertificate.SecretId?.ToString();

if (keyVaultCertificate.Properties != null)
{
Created = keyVaultCertificate.Properties.CreatedOn?.DateTime;
Expires = keyVaultCertificate.Properties.ExpiresOn?.DateTime;
NotBefore = keyVaultCertificate.Properties.NotBefore?.DateTime;
Enabled = keyVaultCertificate.Properties.Enabled;
Updated = keyVaultCertificate.Properties.UpdatedOn?.DateTime;
RecoveryLevel = keyVaultCertificate.Properties.RecoveryLevel;
Tags = keyVaultCertificate.Properties.Tags?.ConvertToHashtable();
}
}

internal static PSKeyVaultCertificate FromCertificateBundle(CertificateBundle certificateBundle)
{
if ( certificateBundle == null )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,12 +237,17 @@ public IEnumerable<PSDeletedKeyVaultCertificateIdentityItem> GetDeletedCertifica
throw new NotImplementedException();
}

public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string base64CertColl, SecureString certPassword, IDictionary<string, string> tags)
public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, string certificate, SecureString certPassword, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType)
{
throw new NotImplementedException();
}

public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary<string, string> tags)
public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, byte[] certificate, SecureString certPassword, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType)
{
return VaultClient.ImportCertificate(vaultName, certName, certificate, certPassword, tags, contentType);
}

public PSKeyVaultCertificate ImportCertificate(string vaultName, string certName, X509Certificate2Collection certificateCollection, IDictionary<string, string> tags, string contentType = Constants.Pkcs12ContentType)
{
throw new NotImplementedException();
}
Expand All @@ -252,6 +257,11 @@ public PSKeyVaultCertificate MergeCertificate(string vaultName, string certName,
throw new NotImplementedException();
}

public PSKeyVaultCertificate MergeCertificate(string vaultName, string name, byte[] certBytes, Dictionary<string, string> tags)
{
return VaultClient.MergeCertifcate(vaultName, name, certBytes, tags);
}

public void PurgeCertificate(string vaultName, string certName)
{
throw new NotImplementedException();
Expand Down
Loading

0 comments on commit 4a44c97

Please sign in to comment.