Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[KeyVault] Supported importing pem certificate by Import-AzKeyVaultCertificate #18644

Merged
merged 5 commits into from
Jun 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filePath is the method parameter, FilePath is a property.

Suggested change
return ".pem".Equals(Path.GetExtension(FilePath), StringComparison.OrdinalIgnoreCase);
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