From 0abaf58b68e8630d3b738231fef513b0bde2631a Mon Sep 17 00:00:00 2001 From: Yeming Liu Date: Tue, 26 Jan 2021 12:30:56 +0800 Subject: [PATCH] enable import EC key via byok --- .../KeyVault.Test/PesterTests/Key.Tests.ps1 | 11 ++++++ src/KeyVault/KeyVault/ChangeLog.md | 1 + .../KeyVault/Commands/AddAzureKeyVaultKey.cs | 39 ++++++++++++++++++- src/KeyVault/KeyVault/Helpers/JwkHelper.cs | 4 ++ .../KeyVault/Models/ByokWebKeyConverter.cs | 34 ++++++++++------ .../KeyVault/Models/IWebKeyConverter.cs | 10 ++++- .../KeyVault/Models/PfxWebKeyConverter.cs | 11 +++--- .../KeyVault/Properties/Resources.Designer.cs | 18 +++++++++ .../KeyVault/Properties/Resources.resx | 6 +++ .../KeyVault/help/Add-AzKeyVaultKey.md | 28 +++++++++---- .../KeyVault/help/Get-AzKeyVaultKey.md | 8 ++-- 11 files changed, 137 insertions(+), 33 deletions(-) diff --git a/src/KeyVault/KeyVault.Test/PesterTests/Key.Tests.ps1 b/src/KeyVault/KeyVault.Test/PesterTests/Key.Tests.ps1 index 7e28e1be08e6..300737836edb 100644 --- a/src/KeyVault/KeyVault.Test/PesterTests/Key.Tests.ps1 +++ b/src/KeyVault/KeyVault.Test/PesterTests/Key.Tests.ps1 @@ -16,4 +16,15 @@ Describe "Update key" { Get-AzKeyVaultKey -VaultName $vaultName -Name $keyName -IncludeVersions | Update-AzKeyVaultKey -Enable $true Get-AzKeyVaultKey -VaultName $vaultName -Name $keyName -IncludeVersions | ForEach-Object { $_.Enabled | Should -BeTrue } } +} + +Describe "Add key" { + It "should throw when key type EC and curve name are not paired" { + { + Add-AzKeyVaultKey -VaultName veakkine-kv -Name PSECImportedKey -KeyFilePath E:\targetBlob.byok -KeyType EC -ErrorAction Stop + } | Should -Throw "CurveName" + { + Add-AzKeyVaultKey -VaultName veakkine-kv -Name PSECImportedKey -KeyFilePath E:\targetBlob.byok -CurveName P-256 -ErrorAction Stop + } | Should -Throw "KeyType" + } } \ No newline at end of file diff --git a/src/KeyVault/KeyVault/ChangeLog.md b/src/KeyVault/KeyVault/ChangeLog.md index 414ccec27be2..e16a1798805f 100644 --- a/src/KeyVault/KeyVault/ChangeLog.md +++ b/src/KeyVault/KeyVault/ChangeLog.md @@ -18,6 +18,7 @@ - Additional information about change #1 --> ## Upcoming Release +* Supported specifying key type and curve name when importing keys via a BYOK file ## Version 3.3.1 * Fixed an issue in Secret Management module diff --git a/src/KeyVault/KeyVault/Commands/AddAzureKeyVaultKey.cs b/src/KeyVault/KeyVault/Commands/AddAzureKeyVaultKey.cs index cdf43c829af7..a9c1c576c3fe 100644 --- a/src/KeyVault/KeyVault/Commands/AddAzureKeyVaultKey.cs +++ b/src/KeyVault/KeyVault/Commands/AddAzureKeyVaultKey.cs @@ -12,6 +12,8 @@ // limitations under the License. // ---------------------------------------------------------------------------------- +using Microsoft.Azure.Commands.Common.Exceptions; +using Microsoft.Azure.Commands.KeyVault.Helpers; using Microsoft.Azure.Commands.KeyVault.Models; using Microsoft.Azure.Commands.KeyVault.Properties; using Microsoft.Azure.Commands.ResourceManager.Common.ArgumentCompleters; @@ -271,11 +273,17 @@ public class AddAzureKeyVaultKey : KeyVaultCmdletBase [Parameter(Mandatory = true, ParameterSetName = HsmInteractiveCreateParameterSet, - HelpMessage = "Specifies the key type of this key.")] + HelpMessage = "Specifies the key type of this key. When importing BYOK keys, it defaults to 'RSA'.")] [Parameter(Mandatory = true, ParameterSetName = HsmInputObjectCreateParameterSet)] [Parameter(Mandatory = true, ParameterSetName = HsmResourceIdCreateParameterSet)] + [Parameter(Mandatory = false, + ParameterSetName = InteractiveImportParameterSet)] + [Parameter(Mandatory = false, + ParameterSetName = InputObjectImportParameterSet)] + [Parameter(Mandatory = false, + ParameterSetName = ResourceIdImportParameterSet)] [PSArgumentCompleter("RSA", "EC", "oct")] public string KeyType { get; set; } @@ -286,6 +294,12 @@ public class AddAzureKeyVaultKey : KeyVaultCmdletBase ParameterSetName = HsmInputObjectCreateParameterSet)] [Parameter(Mandatory = false, ParameterSetName = HsmResourceIdCreateParameterSet)] + [Parameter(Mandatory = false, + ParameterSetName = InteractiveImportParameterSet)] + [Parameter(Mandatory = false, + ParameterSetName = InputObjectImportParameterSet)] + [Parameter(Mandatory = false, + ParameterSetName = ResourceIdImportParameterSet)] [PSArgumentCompleter("P-256", "P-256K", "P-384", "P-521")] public string CurveName { get; set; } #endregion @@ -405,6 +419,8 @@ internal PSKeyVaultKeyAttributes CreateKeyAttributes() internal JsonWebKey CreateWebKeyFromFile() { + ValidateEcParameters(); + FileInfo keyFile = new FileInfo(this.GetUnresolvedProviderPathFromPSPath(this.KeyFilePath)); if (!keyFile.Exists) { @@ -412,7 +428,26 @@ internal JsonWebKey CreateWebKeyFromFile() } var converterChain = WebKeyConverterFactory.CreateConverterChain(); - return converterChain.ConvertKeyFromFile(keyFile, KeyFilePassword); + var converterExtraInfo = new WebKeyConverterExtraInfo() + { + KeyType = KeyType, + CurveName = CurveName + }; + + return converterChain.ConvertKeyFromFile(keyFile, KeyFilePassword, converterExtraInfo); + } + + private void ValidateEcParameters() + { + if (JwkHelper.IsEC(KeyType) && string.IsNullOrEmpty(CurveName)) + { + throw new AzPSArgumentException(Resources.EcButNoCurveName, nameof(CurveName)); + } + + if (!string.IsNullOrEmpty(CurveName) && !JwkHelper.IsEC(KeyType)) + { + throw new AzPSArgumentException(Resources.CurveNameButNotEc, nameof(KeyType)); + } } internal Track2Sdk.JsonWebKey CreateTrack2WebKeyFromFile() diff --git a/src/KeyVault/KeyVault/Helpers/JwkHelper.cs b/src/KeyVault/KeyVault/Helpers/JwkHelper.cs index 692c779f1b82..2b13f0c7d0e1 100644 --- a/src/KeyVault/KeyVault/Helpers/JwkHelper.cs +++ b/src/KeyVault/KeyVault/Helpers/JwkHelper.cs @@ -137,5 +137,9 @@ private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bo } } } + + internal static bool IsEC(string keyType) => + string.Equals(keyType, JsonWebKeyType.EllipticCurve, StringComparison.OrdinalIgnoreCase) || + string.Equals(keyType, JsonWebKeyType.EllipticCurveHsm, StringComparison.OrdinalIgnoreCase); } } diff --git a/src/KeyVault/KeyVault/Models/ByokWebKeyConverter.cs b/src/KeyVault/KeyVault/Models/ByokWebKeyConverter.cs index 5994f62039a3..b36b600343dd 100644 --- a/src/KeyVault/KeyVault/Models/ByokWebKeyConverter.cs +++ b/src/KeyVault/KeyVault/Models/ByokWebKeyConverter.cs @@ -19,6 +19,8 @@ using Track2Sdk = Azure.Security.KeyVault.Keys; using Track1Sdk = Microsoft.Azure.KeyVault.WebKey; using System.Security.Cryptography; +using Microsoft.Azure.KeyVault.WebKey; +using Microsoft.Azure.Commands.KeyVault.Helpers; namespace Microsoft.Azure.Commands.KeyVault.Models { @@ -32,12 +34,22 @@ public ByokWebKeyConverter(IWebKeyConverter next = null) this.next = next; } - public Track1Sdk.JsonWebKey ConvertKeyFromFile(FileInfo fileInfo, SecureString password) + public Track1Sdk.JsonWebKey ConvertKeyFromFile(FileInfo fileInfo, SecureString password, WebKeyConverterExtraInfo extraInfo = null) { if (CanProcess(fileInfo)) - return Convert(fileInfo.FullName); + { + var jwk = Convert(fileInfo.FullName); + + if (JwkHelper.IsEC(extraInfo?.KeyType)) + { + jwk.Kty = JsonWebKeyType.EllipticCurveHsm; // byok -> hsm + jwk.CurveName = extraInfo.CurveName; + } + + return jwk; + } else if (next != null) - return next.ConvertKeyFromFile(fileInfo, password); + return next.ConvertKeyFromFile(fileInfo, password, extraInfo); else throw new ArgumentException(string.Format(KeyVaultProperties.Resources.UnsupportedFileFormat, fileInfo.Name)); } @@ -72,19 +84,19 @@ private Track1Sdk.JsonWebKey Convert(string byokFileName) T = byokBlob, }; } - + private Track2Sdk.JsonWebKey ConvertToTrack2SdkJsonWebKey(string byokFileName) { byte[] byokBlob = File.ReadAllBytes(byokFileName); - if (byokBlob == null || byokBlob.Length == 0) - throw new ArgumentException(string.Format(KeyVaultProperties.Resources.InvalidKeyBlob, "BYOK")); + if (byokBlob == null || byokBlob.Length == 0) + throw new ArgumentException(string.Format(KeyVaultProperties.Resources.InvalidKeyBlob, "BYOK")); - return new Track2Sdk.JsonWebKey(new RSACryptoServiceProvider()) - { - KeyType = Track2Sdk.KeyType.RsaHsm, - T = byokBlob, - }; + return new Track2Sdk.JsonWebKey(new RSACryptoServiceProvider()) + { + KeyType = Track2Sdk.KeyType.RsaHsm, + T = byokBlob, + }; } private IWebKeyConverter next; diff --git a/src/KeyVault/KeyVault/Models/IWebKeyConverter.cs b/src/KeyVault/KeyVault/Models/IWebKeyConverter.cs index 37abcd1c6b93..1c8d36661f23 100644 --- a/src/KeyVault/KeyVault/Models/IWebKeyConverter.cs +++ b/src/KeyVault/KeyVault/Models/IWebKeyConverter.cs @@ -21,10 +21,16 @@ namespace Microsoft.Azure.Commands.KeyVault.Models { internal interface IWebKeyConverter { - Track1Sdk.JsonWebKey ConvertKeyFromFile(FileInfo fileInfo, SecureString password); + Track1Sdk.JsonWebKey ConvertKeyFromFile(FileInfo fileInfo, SecureString password, WebKeyConverterExtraInfo extraInfo = null); Track2Sdk.JsonWebKey ConvertToTrack2SdkKeyFromFile(FileInfo fileInfo, SecureString password); } - + /// + /// Extra information you may append to the converted JWK + /// + internal class WebKeyConverterExtraInfo { + public string KeyType; + public string CurveName; + } } diff --git a/src/KeyVault/KeyVault/Models/PfxWebKeyConverter.cs b/src/KeyVault/KeyVault/Models/PfxWebKeyConverter.cs index d4514d52f961..5f73a7ac3699 100644 --- a/src/KeyVault/KeyVault/Models/PfxWebKeyConverter.cs +++ b/src/KeyVault/KeyVault/Models/PfxWebKeyConverter.cs @@ -20,7 +20,6 @@ using KeyVaultProperties = Microsoft.Azure.Commands.KeyVault.Properties; using Track2Sdk = Azure.Security.KeyVault.Keys; using Track1Sdk = Microsoft.Azure.KeyVault.WebKey; -using Microsoft.Azure.KeyVault.WebKey; namespace Microsoft.Azure.Commands.KeyVault.Models { @@ -31,12 +30,12 @@ public PfxWebKeyConverter(IWebKeyConverter next = null) this.next = next; } - public Track1Sdk.JsonWebKey ConvertKeyFromFile(FileInfo fileInfo, SecureString password) + public Track1Sdk.JsonWebKey ConvertKeyFromFile(FileInfo fileInfo, SecureString password, WebKeyConverterExtraInfo extraInfo = null) { if (CanProcess(fileInfo)) return Convert(fileInfo.FullName, password); if (next != null) - return next.ConvertKeyFromFile(fileInfo, password); + return next.ConvertKeyFromFile(fileInfo, password, extraInfo); throw new ArgumentException(string.Format(KeyVaultProperties.Resources.UnsupportedFileFormat, fileInfo.Name)); } @@ -79,7 +78,7 @@ private Track1Sdk.JsonWebKey Convert(string pfxFileName, SecureString pfxPasswor return CreateJWK(key); } - + private Track2Sdk.JsonWebKey ConvertToTrack2SdkJsonWebKey(string pfxFileName, SecureString pfxPassword) { X509Certificate2 certificate; @@ -103,7 +102,7 @@ private Track2Sdk.JsonWebKey ConvertToTrack2SdkJsonWebKey(string pfxFileName, Se // to do: support converting oct to jsonwebKey throw new ArgumentException(string.Format(KeyVaultProperties.Resources.ImportNotSupported, "oct-HSM")); - + } private static Track1Sdk.JsonWebKey CreateJWK(RSA rsa) @@ -151,7 +150,7 @@ private static Track2Sdk.JsonWebKey CreateTrack2SdkJWK(RSA rsa) private static Track2Sdk.JsonWebKey CreateTrack2SdkJWK(ECDsa ecdSa) { - if (ecdSa == null) + if (ecdSa == null) { throw new ArgumentNullException("ecdSa"); } diff --git a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs index 2e2e5ebed8c8..0b538ed84f7a 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs +++ b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs @@ -333,6 +333,15 @@ internal static string CreateKeyVault { } } + /// + /// Looks up a localized string similar to When '-CurveName' is specified, '-KeyType' must be 'EC'.. + /// + internal static string CurveNameButNotEc { + get { + return ResourceManager.GetString("CurveNameButNotEc", resourceCulture); + } + } + /// /// Looks up a localized string similar to Failed to decrypt security domain data. Please make sure the file is not modified and the keys / passwords are correct.. /// @@ -405,6 +414,15 @@ internal static string DownloadSecurityDomainKeyFail { } } + /// + /// Looks up a localized string similar to Please input a valid 'CurveName' when KeyType is 'EC'.. + /// + internal static string EcButNoCurveName { + get { + return ResourceManager.GetString("EcButNoCurveName", resourceCulture); + } + } + /// /// Looks up a localized string similar to Overwrite File ?. /// diff --git a/src/KeyVault/KeyVault/Properties/Resources.resx b/src/KeyVault/KeyVault/Properties/Resources.resx index 15f9cf62b0a5..40df75218fb9 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.resx +++ b/src/KeyVault/KeyVault/Properties/Resources.resx @@ -585,4 +585,10 @@ You can find the object ID using Azure Active Directory Module for Windows Power Failed to selective restore key {0} of managed HSM {1}. + + Please input a valid 'CurveName' when KeyType is 'EC'. + + + When '-CurveName' is specified, '-KeyType' must be 'EC'. + \ No newline at end of file diff --git a/src/KeyVault/KeyVault/help/Add-AzKeyVaultKey.md b/src/KeyVault/KeyVault/help/Add-AzKeyVaultKey.md index 83d30e510e5b..dafaab931b67 100644 --- a/src/KeyVault/KeyVault/help/Add-AzKeyVaultKey.md +++ b/src/KeyVault/KeyVault/help/Add-AzKeyVaultKey.md @@ -24,8 +24,8 @@ Add-AzKeyVaultKey [-VaultName] [-Name] -Destination [ ``` Add-AzKeyVaultKey [-VaultName] [-Name] -KeyFilePath [-KeyFilePassword ] [-Destination ] [-Disable] [-KeyOps ] - [-Expires ] [-NotBefore ] [-Tag ] [-DefaultProfile ] - [-WhatIf] [-Confirm] [] + [-Expires ] [-NotBefore ] [-Tag ] [-KeyType ] [-CurveName ] + [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` ### HsmInteractiveCreate @@ -53,8 +53,8 @@ Add-AzKeyVaultKey [-InputObject] [-Name] -Destination [-Name] -KeyFilePath [-KeyFilePassword ] [-Destination ] [-Disable] [-KeyOps ] - [-Expires ] [-NotBefore ] [-Tag ] [-DefaultProfile ] - [-WhatIf] [-Confirm] [] + [-Expires ] [-NotBefore ] [-Tag ] [-KeyType ] [-CurveName ] + [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` ### HsmInputObjectCreate @@ -83,8 +83,8 @@ Add-AzKeyVaultKey [-ResourceId] [-Name] -Destination ``` Add-AzKeyVaultKey [-ResourceId] [-Name] -KeyFilePath [-KeyFilePassword ] [-Destination ] [-Disable] [-KeyOps ] - [-Expires ] [-NotBefore ] [-Tag ] [-DefaultProfile ] - [-WhatIf] [-Confirm] [] + [-Expires ] [-NotBefore ] [-Tag ] [-KeyType ] [-CurveName ] + [-DefaultProfile ] [-WhatIf] [-Confirm] [] ``` ### HsmResourceIdCreate @@ -294,7 +294,7 @@ Specifies the curve name of elliptic curve cryptography, this value is valid whe ```yaml Type: System.String -Parameter Sets: HsmInteractiveCreate, HsmInputObjectCreate, HsmResourceIdCreate +Parameter Sets: InteractiveImport, HsmInteractiveCreate, InputObjectImport, HsmInputObjectCreate, ResourceIdImport, HsmResourceIdCreate Aliases: Required: False @@ -518,7 +518,19 @@ Accept wildcard characters: False ``` ### -KeyType -Specifies the key type of this key. +Specifies the key type of this key. When importing BYOK keys, it defaults to 'RSA'. + +```yaml +Type: System.String +Parameter Sets: InteractiveImport, InputObjectImport, ResourceIdImport +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` ```yaml Type: System.String diff --git a/src/KeyVault/KeyVault/help/Get-AzKeyVaultKey.md b/src/KeyVault/KeyVault/help/Get-AzKeyVaultKey.md index 8625d293b9a8..b2088ecad244 100644 --- a/src/KeyVault/KeyVault/help/Get-AzKeyVaultKey.md +++ b/src/KeyVault/KeyVault/help/Get-AzKeyVaultKey.md @@ -87,19 +87,19 @@ Get-AzKeyVaultKey [-HsmObject] [-Name] [-IncludeVersions ### ByResourceIdVaultName ``` -Get-AzKeyVaultKey [-ResourceId] [[-Name] ] [-InRemovedState] [-OutFile ] +Get-AzKeyVaultKey -ResourceId [[-Name] ] [-InRemovedState] [-OutFile ] [-DefaultProfile ] [] ``` ### ByResourceIdKeyName ``` -Get-AzKeyVaultKey [-ResourceId] [-Name] [-Version] [-OutFile ] +Get-AzKeyVaultKey -ResourceId [-Name] [-Version] [-OutFile ] [-DefaultProfile ] [] ``` ### ByResourceIdKeyVersions ``` -Get-AzKeyVaultKey [-ResourceId] [-Name] [-IncludeVersions] [-OutFile ] +Get-AzKeyVaultKey -ResourceId [-Name] [-IncludeVersions] [-OutFile ] [-DefaultProfile ] [] ``` @@ -471,7 +471,7 @@ Parameter Sets: ByResourceIdVaultName, ByResourceIdKeyName, ByResourceIdKeyVersi Aliases: Required: True -Position: 0 +Position: Named Default value: None Accept pipeline input: True (ByPropertyName) Accept wildcard characters: False