From 01c6c2d5f3655c423bfbf9f7060809e3e152634a Mon Sep 17 00:00:00 2001 From: Yeming Liu Date: Mon, 21 Sep 2020 13:34:38 +0800 Subject: [PATCH 01/18] wip --- .../KeyVault.Test/KeyVault.Test.csproj | 1 + src/KeyVault/KeyVault.sln | 17 +- src/KeyVault/KeyVault/Az.KeyVault.psd1 | 3 +- src/KeyVault/KeyVault/KeyVault.csproj | 8 +- .../KeyVault/Properties/AssemblyInfo.cs | 1 + .../Cmdlets/BackupSecurityDomain.cs | 51 ++ .../Cmdlets/RestoreSecurityDomain.cs | 76 +++ .../Cmdlets/SecurityDomainCmdlet.cs | 58 +++ .../Common/IOStreamExtensions.cs | 18 + .../KeyVault/SecurityDomain/Common/Utils.cs | 77 +++ .../KeyVault/SecurityDomain/Models/CertKey.cs | 92 ++++ .../SecurityDomain/Models/CertKeys.cs | 48 ++ .../SecurityDomain/Models/DownloadRequest.cs | 16 + .../Models/ISecurityDomainClient.cs | 17 + .../KeyVault/SecurityDomain/Models/JWE.cs | 321 ++++++++++++ .../KeyVault/SecurityDomain/Models/JWK.cs | 259 ++++++++++ .../KeyVault/SecurityDomain/Models/KDF.cs | 136 +++++ .../KeyVault/SecurityDomain/Models/KeyPath.cs | 13 + .../SecurityDomain/Models/SecurityDomain.cs | 85 ++++ .../Models/SecurityDomainClient.cs | 470 ++++++++++++++++++ .../Models/SecurityDomainRestoreData.cs | 17 + .../Models/SecurityDomainTransferKey.cs | 13 + src/KeyVault/SecurityDomain.Test/.gitignore | 1 + .../SecurityDomain.Test.csproj | 20 + src/KeyVault/SecurityDomain.Test/UnitTest1.cs | 24 + src/KeyVault/shamir_share/mod_math.cs | 184 +++++++ src/KeyVault/shamir_share/python/main.py | 8 + src/KeyVault/shamir_share/python/mod_math.py | 111 +++++ .../shamir_share/python/shared_secret.py | 78 +++ src/KeyVault/shamir_share/python/test.py | 163 ++++++ .../shamir_share/shamir_share_net.csproj | 12 + src/KeyVault/shamir_share/shared_secret.cs | 130 +++++ src/KeyVault/shamir_share/test/test.cs | 167 +++++++ 33 files changed, 2691 insertions(+), 4 deletions(-) create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Common/IOStreamExtensions.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/JWE.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/JWK.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/KDF.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomain.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainRestoreData.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainTransferKey.cs create mode 100644 src/KeyVault/SecurityDomain.Test/.gitignore create mode 100644 src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj create mode 100644 src/KeyVault/SecurityDomain.Test/UnitTest1.cs create mode 100644 src/KeyVault/shamir_share/mod_math.cs create mode 100644 src/KeyVault/shamir_share/python/main.py create mode 100644 src/KeyVault/shamir_share/python/mod_math.py create mode 100644 src/KeyVault/shamir_share/python/shared_secret.py create mode 100644 src/KeyVault/shamir_share/python/test.py create mode 100644 src/KeyVault/shamir_share/shamir_share_net.csproj create mode 100644 src/KeyVault/shamir_share/shared_secret.cs create mode 100644 src/KeyVault/shamir_share/test/test.cs diff --git a/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj b/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj index 4bbae8e7cc75..46ace00d5cd3 100644 --- a/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj +++ b/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj @@ -15,6 +15,7 @@ + diff --git a/src/KeyVault/KeyVault.sln b/src/KeyVault/KeyVault.sln index dccac43f4a32..77e9557f07eb 100644 --- a/src/KeyVault/KeyVault.sln +++ b/src/KeyVault/KeyVault.sln @@ -1,6 +1,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2042 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30413.136 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KeyVault", "KeyVault\KeyVault.csproj", "{9FFC40CC-A341-4D0C-A25D-DC6B78EF6C94}" EndProject @@ -18,6 +18,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScenarioTest.ResourceManage EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFx", "..\..\tools\TestFx\TestFx.csproj", "{BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecurityDomain.Test", "SecurityDomain.Test\SecurityDomain.Test.csproj", "{FDEE9611-2887-4933-AF88-B4EC782B2096}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "shamir_share_net", "shamir_share\shamir_share_net.csproj", "{035DF85F-5BB9-464B-94F6-2F9502A29807}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,12 +56,21 @@ Global {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Debug|Any CPU.Build.0 = Debug|Any CPU {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Release|Any CPU.ActiveCfg = Release|Any CPU {BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}.Release|Any CPU.Build.0 = Release|Any CPU + {FDEE9611-2887-4933-AF88-B4EC782B2096}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FDEE9611-2887-4933-AF88-B4EC782B2096}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FDEE9611-2887-4933-AF88-B4EC782B2096}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FDEE9611-2887-4933-AF88-B4EC782B2096}.Release|Any CPU.Build.0 = Release|Any CPU + {035DF85F-5BB9-464B-94F6-2F9502A29807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {035DF85F-5BB9-464B-94F6-2F9502A29807}.Debug|Any CPU.Build.0 = Debug|Any CPU + {035DF85F-5BB9-464B-94F6-2F9502A29807}.Release|Any CPU.ActiveCfg = Release|Any CPU + {035DF85F-5BB9-464B-94F6-2F9502A29807}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {080B0477-7E52-4455-90AB-23BD13D1B1CE} = {95C16AED-FD57-42A0-86C3-2CF4300A4817} + {FDEE9611-2887-4933-AF88-B4EC782B2096} = {95C16AED-FD57-42A0-86C3-2CF4300A4817} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5E85B4CC-D1A9-466B-98AC-E0AD0C5AE585} diff --git a/src/KeyVault/KeyVault/Az.KeyVault.psd1 b/src/KeyVault/KeyVault/Az.KeyVault.psd1 index bcca888d0273..f4e5b252ffcc 100644 --- a/src/KeyVault/KeyVault/Az.KeyVault.psd1 +++ b/src/KeyVault/KeyVault/Az.KeyVault.psd1 @@ -116,7 +116,8 @@ CmdletsToExport = 'Add-AzKeyVaultCertificate', 'Update-AzKeyVaultCertificate', 'Undo-AzKeyVaultManagedStorageSasDefinitionRemoval', 'Undo-AzKeyVaultManagedStorageAccountRemoval', 'Add-AzKeyVaultNetworkRule', 'Update-AzKeyVaultNetworkRuleSet', - 'Remove-AzKeyVaultNetworkRule' + 'Remove-AzKeyVaultNetworkRule', 'Backup-AzManagedHsmSecurityDomain', + 'Restore-AzManagedHsmSecurityDomain' # Variables to export from this module # VariablesToExport = @() diff --git a/src/KeyVault/KeyVault/KeyVault.csproj b/src/KeyVault/KeyVault/KeyVault.csproj index 07ec908b56c6..5b565a8d782e 100644 --- a/src/KeyVault/KeyVault/KeyVault.csproj +++ b/src/KeyVault/KeyVault/KeyVault.csproj @@ -1,4 +1,4 @@ - + KeyVault @@ -13,9 +13,15 @@ + + + + + + diff --git a/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs b/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs index 6396d23ec9d8..453a809749b2 100644 --- a/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs +++ b/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs @@ -33,4 +33,5 @@ [assembly: AssemblyFileVersion("2.0.0")] #if !SIGN [assembly: InternalsVisibleTo("Microsoft.Azure.PowerShell.Cmdlets.KeyVault.Test")] +[assembly: InternalsVisibleTo("SecurityDomain.Test")] #endif diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs new file mode 100644 index 000000000000..22b7aa71fdc5 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -0,0 +1,51 @@ +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.KeyVault.Properties; +using System; +using System.Linq; +using System.Management.Automation; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets +{ + [Cmdlet(VerbsData.Backup, ResourceManager.Common.AzureRMConstants.AzurePrefix + "ManagedHsmSecurityDomain", SupportsShouldProcess = true, DefaultParameterSetName = ByName)] + [OutputType(typeof(bool))] + public class BackupSecurityDomain: SecurityDomainCmdlet + { + [Parameter(HelpMessage = "Paths to the certificates that are used to encrypt the security domain data.", Mandatory = true)] + public string[] Certificates { get; set; } + + [Parameter(HelpMessage = "Specify the path where security domain data will be downloaded to.", Mandatory = true)] + public string OutputPath { get; set; } + + [Parameter(HelpMessage = "Specify whether to overwrite existing file.")] + public SwitchParameter Force { get; set; } + + [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] + public SwitchParameter PassThru { get; set; } + + public override void ExecuteCmdletCore() + { + if (Certificates?.Length < 3 || Certificates?.Length > 10) // todo: check + { + throw new ArgumentException($"Number of {nameof(Certificates)} should be between 3 and 10"); // todo: resource string; check + } + + var certificates = Certificates.Select(path => new X509Certificate2(path)); + + if (ShouldProcess($"managed HSM {Name}", $"download encrypted security domain data to '{OutputPath}'")) + { + var securityDomain = Client.DownloadSecurityDomainAsync(Name, certificates, 2).ConfigureAwait(false).GetAwaiter().GetResult(); // todo: remove required? + if (!AzureSession.Instance.DataStore.FileExists(OutputPath) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutputPath), Resources.FileOverwriteCaption)) + { + AzureSession.Instance.DataStore.WriteFile(OutputPath, securityDomain); + WriteVerbose($"Security domain data of managed HSM '{Name}' downloaded to '{OutputPath}'."); + if (PassThru) + { + WriteObject(true); + } + } + } + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs new file mode 100644 index 000000000000..f6502fad3326 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -0,0 +1,76 @@ +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Management.Automation; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets +{ + [Cmdlet(VerbsData.Restore, ResourceManager.Common.AzureRMConstants.AzurePrefix + "ManagedHsmSecurityDomain", SupportsShouldProcess = true, DefaultParameterSetName = ByName)] + [OutputType(typeof(bool))] + public class RestoreSecurityDomain : SecurityDomainCmdlet + { + [Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data.", Mandatory = true)] + public KeyPath[] Keys { get; set; } + + [Parameter(HelpMessage = "Specify the path to the encrypted security domain data.", Mandatory = true)] + [Alias("Path")] + public string SecurityDomainPath { get; set; } + + [Parameter(HelpMessage = "Specify whether to overwrite existing file.")] + public SwitchParameter Force { get; set; } + + [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] + public SwitchParameter PassThru { get; set; } + + public override void ExecuteCmdletCore() + { + ValidateParameters(); + if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file '{SecurityDomainPath}'")) + { + var securityDomainData = LoadFromFile(SecurityDomainPath); + var exchangeKey = Client.DownloadSecurityDomainExchangeKeyAsync(Name).ConfigureAwait(false).GetAwaiter().GetResult(); + var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, securityDomainData, exchangeKey); + if (Client.RestoreSecurityDomainAsync(Name, encryptedSecurityDomain).ConfigureAwait(false).GetAwaiter().GetResult()) + { + if (PassThru) + { + WriteObject(true); + } + } + } + } + + private void ValidateParameters() + { + if (Keys.Length < 2) + { + // todo: resource string + throw new ArgumentException(@"There need to be at least 2 keys to decrypt security domain data."); + } + if (Keys.Any(key => string.IsNullOrEmpty(key.PublicKey) || string.IsNullOrEmpty(key.PrivateKey))) + { + // todo: resource string + throw new ArgumentException(@"'PublicKey' and 'PrivateKey' are mandatory in each object in 'Keys'"); + } + } + + private SecurityDomainData LoadFromFile(string path) + { + try + { + string sec_domain = Utils.FileToString(path); + return JsonConvert.DeserializeObject(sec_domain); + } + catch (Exception err) + { + Console.WriteLine("Cannot load security domain from file"); + Console.WriteLine(err.Message); + return null; + } + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs new file mode 100644 index 000000000000..4e5eb9c165d9 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -0,0 +1,58 @@ +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.KeyVault.Models; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models; +using Microsoft.Azure.Commands.ResourceManager.Common; +using Microsoft.WindowsAzure.Commands.Utilities.Common; +using System.Management.Automation; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets +{ + public class SecurityDomainCmdlet: AzureRMCmdlet + { + protected const string ByName = "By Name"; + protected const string ByInputObject = "By InputObject"; + protected const string ByResourceId = "By Resource ID"; + + [Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = ByName)] + [Alias("HsmName")] + public string Name { get; set; } + + [Parameter(HelpMessage = "Object representing a managed HSM.", Mandatory = true, ParameterSetName = ByInputObject, ValueFromPipeline = true)] + public PSKeyVaultIdentityItem InputObject { get; set; } + + internal ISecurityDomainClient Client + { + get + { + if (_client == null) + { + _client = new SecurityDomainClient(AzureSession.Instance.AuthenticationFactory, DefaultContext); + } + return _client; + } + set => _client = value; + } + + + private ISecurityDomainClient _client; + + /// + /// Sealed for common logic of parameter set handling. Please override instead. + /// + public sealed override void ExecuteCmdlet() + { + PreprocessParameterSets(); + ExecuteCmdletCore(); + } + + private void PreprocessParameterSets() + { + if (this.IsParameterBound(c => c.InputObject)) + { + Name = InputObject.VaultName; + } + } + + public virtual void ExecuteCmdletCore() { } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Common/IOStreamExtensions.cs b/src/KeyVault/KeyVault/SecurityDomain/Common/IOStreamExtensions.cs new file mode 100644 index 000000000000..a6e3fcf66b71 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Common/IOStreamExtensions.cs @@ -0,0 +1,18 @@ +using System.Security.Cryptography; +using System.IO; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common +{ + internal static class IOStreamExtensions + { + public static void Write(this MemoryStream stream, byte[] bytes) + { + stream.Write(bytes, 0, bytes.Length); + } + + public static void Write(this CryptoStream stream, byte[] bytes) + { + stream.Write(bytes, 0, bytes.Length); + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs b/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs new file mode 100644 index 000000000000..afacf9e77683 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common +{ + class Utils + { + static public UInt16[] ConvertToUint16(byte[] b) + { + UInt16[] ret = new UInt16[b.Length / 2]; + + for (Int32 i = 0; i < b.Length; i += 2) + { + byte[] tmp = new byte[2]; + tmp[0] = b[i]; + tmp[1] = b[i + 1]; + + // It's already in the same byte order + // as the system that encrypted it, so don't reverse it + ret[i / 2] = BitConverter.ToUInt16(tmp, 0); + } + + return ret; + + } + static public byte[] Sha256Thumbprint(X509Certificate2 cert) + { + SHA256CryptoServiceProvider hash = new SHA256CryptoServiceProvider(); + return hash.ComputeHash(cert.RawData); + } + + static public string FileToString(string path) + { + string readContents; + using (StreamReader streamReader = new StreamReader(path, Encoding.ASCII)) + { + readContents = streamReader.ReadToEnd(); + } + + return readContents; + } + + static public void StringToFile(string path, string data) + { + using (StreamWriter streamWriter = new StreamWriter(path, false, Encoding.ASCII)) + { + streamWriter.Write(data); + streamWriter.Flush(); + } + } + + public static byte[] GetRandom(UInt32 cb) + { + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + byte[] random = new byte[cb]; + rng.GetBytes(random); + return random; + } + } + + public static X509Certificate2 CertficateFromPem(string certificatePem) + { + // Remove the header + string base64cert = certificatePem.Replace("-----BEGIN CERTIFICATE-----\n", ""); + + // And tidy up any trailing characters + var footerPosition = base64cert.IndexOf("\n-----END CERTIFICATE"); + X509Certificate2 cert = new X509Certificate2(Convert.FromBase64String(base64cert.Substring(0, footerPosition))); + return cert; + } + } + +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs new file mode 100644 index 000000000000..82dcc27386e5 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs @@ -0,0 +1,92 @@ +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.OpenSsl; +using System; +using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + internal class CertKey + { + public bool Load(KeyPath path) + { + try + { + cert = new X509Certificate2(path.PublicKey); + RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password); + key = RSA.Create(); + key.ImportParameters(parameters); + thumbprint = Utils.Sha256Thumbprint(cert); + } + catch (Exception e) + { + Console.WriteLine(e.Message); + return false; + } + return true; + } + + public byte[] get_thumbprint() { return thumbprint; } + public RSA get_key() { return key; } + public X509Certificate2 get_cert() { return cert; } + + static RSAParameters RsaParamsFromPem(string path, string password) + { + using (var stream = File.OpenText(path)) + { + var reader = string.IsNullOrEmpty(password) ? new PemReader(stream) : new PemReader(stream, new PasswordFinder(password)); + var keyParameters = reader.ReadObject() as RsaPrivateCrtKeyParameters; + + return ToRSAParameters(keyParameters); + } + } + + static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey) + { + RSAParameters rp = new RSAParameters(); + rp.Modulus = privKey.Modulus.ToByteArrayUnsigned(); + rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned(); + rp.P = privKey.P.ToByteArrayUnsigned(); + rp.Q = privKey.Q.ToByteArrayUnsigned(); + rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length); + rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length); + rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length); + rp.InverseQ = ConvertRSAParametersField(privKey.QInv, rp.Q.Length); + return rp; + } + + + static byte[] ConvertRSAParametersField(Org.BouncyCastle.Math.BigInteger n, int size) + { + byte[] bs = n.ToByteArrayUnsigned(); + if (bs.Length == size) + return bs; + if (bs.Length > size) + throw new ArgumentException("Specified size too small", "size"); + byte[] padded = new byte[size]; + Array.Copy(bs, 0, padded, size - bs.Length, bs.Length); + return padded; + } + + X509Certificate2 cert; + RSA key; + byte[] thumbprint; + + private class PasswordFinder : IPasswordFinder + { + private string v; + + public PasswordFinder(string v) + { + this.v = v; + } + + public char[] GetPassword() + { + return v.ToCharArray(); + } + } + } +} \ No newline at end of file diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs new file mode 100644 index 000000000000..1a8c281da9aa --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs @@ -0,0 +1,48 @@ +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + internal class CertKeys + { + public CertKeys() + { + keyValuePairs = new Dictionary(); + } + + public void LoadKeys(KeyPath[] paths) + { + foreach (var path in paths) + { + if (!LoadKey(path)) + Console.WriteLine("Could not load cert and key from " + path); + } + } + + public bool LoadKey(KeyPath path) + { + CertKey certKey = new CertKey(); + + if (!certKey.Load(path)) + return false; + + string encoded_string = Base64UrlEncoder.Encode(certKey.get_thumbprint()); + keyValuePairs.Add(encoded_string, certKey); + return true; + } + + public CertKey Find(string encoded_thumbprint) + { + CertKey certKey = null; + if (!keyValuePairs.TryGetValue(encoded_thumbprint, out certKey)) + return null; + + return certKey; + } + + public int Count() { return keyValuePairs.Count; } + + Dictionary keyValuePairs; + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs new file mode 100644 index 000000000000..378990eda203 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + class DownloadRequest + { + public DownloadRequest() + { + certificates = new List(); + } + + public int required; // todo: rename to Required + public IList certificates { get; set; } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs new file mode 100644 index 000000000000..8aec5812d9c2 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + public interface ISecurityDomainClient + { + Task DownloadSecurityDomainAsync(string hsmName, IEnumerable certificates, int required); + + Task DownloadSecurityDomainExchangeKeyAsync(string hsmName); + + string EncryptSecurityDomainByCert(KeyPath[] keys, SecurityDomainData data, X509Certificate2 restore_cert); + + Task RestoreSecurityDomainAsync(string hsmName, string securityDomainData); + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/JWE.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/JWE.cs new file mode 100644 index 000000000000..b4c6d2a9f149 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/JWE.cs @@ -0,0 +1,321 @@ +using System; +using System.Security.Cryptography; +using System.Text; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + /* + * In the JWE Compact Serialization, a JWE is represented as the +concatenation: + + BASE64URL(UTF8(JWE Protected Header)) || '.' || + BASE64URL(JWE Encrypted Key) || '.' || + BASE64URL(JWE Initialization Vector) || '.' || + BASE64URL(JWE Ciphertext) || '.' || + BASE64URL(JWE Authentication Tag) + */ + + // Sample header = {"alg":"RSA-OAEP-256","enc":"A256CBC-HS512","kid":"not used"} + + public class JWE_header + { + public string alg { get; set; } // algorithm + public string enc { get; set; } // encryption algorithm + public string zip { get; set; } // compression algorithm + public string jku { get; set; } // JWK set URL + public string jwk { get; set; } // JSON Web key + public string kid { get; set; } // Key ID + public string x5u { get; set; } // X509 certificate URL + public string x5c { get; set; } // X509 certificate chain + public string x5t { get; set; } // X.509 Certificate SHA-1 Thumbprint + + [JsonProperty("x5t#S256")] + public string x5t_S256 { get; set; } // X.509 Certificate SHA-256 Thumbprint + public string typ { get; set; } // Type + public string cty { get; set; } // Content type + public string crit { get; set; } // Critical + } + + public class JweDecode + { + public JweDecode(string compact_jwe) + { + string[] parts = compact_jwe.Split('.'); + + if (parts.Length != 5) + { + throw new Exception("Malformed input"); + } + + encoded_header = parts[0]; + string header = Base64UrlEncoder.Decode(encoded_header); + encrypted_key = Base64UrlEncoder.DecodeBytes(parts[1]); + init_vector = Base64UrlEncoder.DecodeBytes(parts[2]); + ciphertext = Base64UrlEncoder.DecodeBytes(parts[3]); + auth_tag = Base64UrlEncoder.DecodeBytes(parts[4]); + + protected_header = JsonConvert.DeserializeObject(header); + } + + // For encryption + public JweDecode() + { + encoded_header = ""; + encrypted_key = null; + init_vector = null; + ciphertext = null; + auth_tag = null; + protected_header = new JWE_header(); + } + + public void EncodeHeader() + { + string header_json = JsonConvert.SerializeObject( + protected_header, + Formatting.None, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + + encoded_header = Base64UrlEncoder.Encode(header_json); + } + + public string EncodeCompact() + { + string ret = encoded_header + "."; + + if (encrypted_key != null) + { + ret += Base64UrlEncoder.Encode(encrypted_key); + } + + ret += "."; + if (init_vector != null) + { + ret += Base64UrlEncoder.Encode(init_vector); + } + + ret += "."; + if (ciphertext != null) + { + ret += Base64UrlEncoder.Encode(ciphertext); + } + + ret += "."; + if (auth_tag != null) + { + ret += Base64UrlEncoder.Encode(auth_tag); + } + + return ret; + } + + public JWE_header protected_header; + public string encoded_header; + public byte[] encrypted_key; + public byte[] init_vector; + public byte[] ciphertext; + public byte[] auth_tag; + } + + public class JWE + { + public JWE(string compact_jwe) + { + jwe_decode = new JweDecode(compact_jwe); + } + + public JWE() + { + jwe_decode = new JweDecode(); + } + + public string EncodeCompact() + { + return jwe_decode.EncodeCompact(); + } + + RSAEncryptionPadding GetPaddingMode() + { + string alg = jwe_decode.protected_header.alg; + switch (alg) + { + case "RSA-OAEP-256": + return RSAEncryptionPadding.OaepSHA256; + case "RSA-OAEP": + return RSAEncryptionPadding.OaepSHA1; + case "RSA1_5": + return RSAEncryptionPadding.Pkcs1; + } + + return null; + } + + public byte[] GetCEK(RSA private_key) + { + return private_key.Decrypt(jwe_decode.encrypted_key, GetPaddingMode()); + } + + public void SetCEK(X509Certificate2 cert, byte[] cek) + { + RSA rsa = cert.GetRSAPublicKey(); + jwe_decode.encrypted_key = rsa.Encrypt(cek, GetPaddingMode()); + } + byte[] DekFromCek(byte[] cek) + { + byte[] dek = new byte[32]; + Array.Copy(cek, 32, dek, 0, 32); + return dek; + } + + byte[] HmacKeyFromCek(byte[] cek) + { + byte[] hk = new byte[32]; + Array.Copy(cek, 0, hk, 0, 32); + return hk; + } + + byte[] GetMac(byte[] hk) + { + HMACSHA512 hMAC = new HMACSHA512(hk); + byte[] header_bytes = Encoding.ASCII.GetBytes(jwe_decode.encoded_header); + + using (MemoryStream stm = new MemoryStream()) + { + UInt64 auth_bits = (UInt64)header_bytes.Length * 8; + stm.Write(header_bytes); + stm.Write(jwe_decode.init_vector); + stm.Write(jwe_decode.ciphertext); + // Add the associated_data_length bytes to the hash + stm.Write(KDF.to_big_endian(auth_bits)); + byte[] hash_data = stm.ToArray(); + + return hMAC.ComputeHash(hash_data); + } + } + + void Aes256HmacSha512Encrypt(byte[] cek, byte[] plain_text) + { + byte[] dek = DekFromCek(cek); + byte[] hk = HmacKeyFromCek(cek); + + using (Aes alg = Aes.Create()) + { + alg.Key = dek; + alg.IV = Utils.GetRandom(16); + ICryptoTransform encryptor = alg.CreateEncryptor(alg.Key, alg.IV); + + using (MemoryStream msEncrypt = new MemoryStream()) + { + using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) + { + csEncrypt.Write(plain_text); + csEncrypt.FlushFinalBlock(); + csEncrypt.Close(); + + // Have to wait to set hash once header is complete + jwe_decode.ciphertext = msEncrypt.ToArray(); + jwe_decode.init_vector = alg.IV; + } + } + } + byte[] mac_value = GetMac(hk); + jwe_decode.auth_tag = new byte[32]; + Array.Copy(mac_value, jwe_decode.auth_tag, 32); + } + + byte[] Aes256HmacSha512Decrypt(byte[] cek) + { + byte[] dek = DekFromCek(cek); + byte[] hk = HmacKeyFromCek(cek); + + byte[] mac_value = GetMac(hk); + // We're then going to truncate the MAC to 32 bytes, as per standard + int test = 0; + for (UInt32 i = 0; i < jwe_decode.auth_tag.Length && jwe_decode.auth_tag.Length == 32; ++i) + { + test |= (jwe_decode.auth_tag[i] ^ mac_value[i]); + } + + if (test != 0) + return null; + + // Nothing has been tampered with, decrypt + using (Aes alg = Aes.Create()) + { + alg.Key = dek; + alg.IV = jwe_decode.init_vector; + ICryptoTransform decryptor = alg.CreateDecryptor(alg.Key, alg.IV); + + using (MemoryStream msDecrypt = new MemoryStream(jwe_decode.ciphertext)) + { + using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read)) + { + using (BinaryReader srDecrypt = new BinaryReader(csDecrypt)) + { + return srDecrypt.ReadBytes(jwe_decode.ciphertext.Length); + } + } + } + } + } + + // Call this with the last parameter non-null to do direct encryption + public void Encrypt(byte[] cek, byte[] plain_text, string algId, string kid = null) + { + if (kid != null) + { + jwe_decode.protected_header.alg = "dir"; + jwe_decode.protected_header.kid = kid; + } + + switch (algId) + { + case "A256CBC-HS512": + jwe_decode.protected_header.enc = "A256CBC-HS512"; + jwe_decode.EncodeHeader(); + Aes256HmacSha512Encrypt(cek, plain_text); + return; + } + + } + + public byte[] Decrypt(byte[] cek) + { + switch (jwe_decode.protected_header.enc) + { + case "A256CBC-HS512": + return Aes256HmacSha512Decrypt(cek); + } + + return null; + } + + // Note - we don't have a true key wrap, for now + // just encrypt keys as if they were data + public void Encrypt(X509Certificate2 cert, byte[] plaintext) + { + // Only allow one encryption method right now + // and only 256-bit keys + jwe_decode.protected_header.alg = "RSA-OAEP-256"; + jwe_decode.protected_header.kid = "not used"; + + byte[] cek = Utils.GetRandom(64); + + SetCEK(cert, cek); + Encrypt(cek, plaintext, "A256CBC-HS512"); + } + + public byte[] Decrypt(RSA private_key) + { + byte[] cek = GetCEK(private_key); + return Decrypt(cek); + } + + JweDecode jwe_decode; + } + +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/JWK.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/JWK.cs new file mode 100644 index 000000000000..c8c365b0db99 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/JWK.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; +using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + public enum JwkKeyType + { + RSA // only type supported now + } + public enum JwkUse + { + enc, + sig + } + + /* + (Note that the "key_ops" values intentionally match the "KeyUsage" + values defined in the Web Cryptography API + [W3C.CR-WebCryptoAPI-20141211] specification.) + */ + public enum JwkKeyOps + { + sign, + verify, + encrypt, + decrypt, + wrapKey, + unwrapKey, + deriveKey, + deriveBits + } + + public enum JwkAlg + { + RSA_OAEP, + RSA_OAEP_256 + } + + public class JWK + { + public JWK() + { + key_ops = new List(); + x5c = new List(); + } + + public JWK(X509Certificate2 cert) + { + key_ops = new List(); + x5c = new List(); + + PublicKey publicKey = cert.PublicKey; + + if (publicKey.Key.KeyExchangeAlgorithm != "RSA" || publicKey.Key.KeySize < 2048) + throw new Exception("Invalid argument"); + + RSAParameters rsaParameters = cert.GetRSAPublicKey().ExportParameters(false); + SetExponent(rsaParameters.Exponent); + SetModulus(rsaParameters.Modulus); + + SetKeyType(JwkKeyType.RSA); + + // Figure out the key_ops + // Unsure what deriveKey, deriveBits requires, omit for now + if (cert.PrivateKey != null) + { + AddKeyOp(JwkKeyOps.sign); + AddKeyOp(JwkKeyOps.decrypt); + AddKeyOp(JwkKeyOps.unwrapKey); + } + + AddKeyOp(JwkKeyOps.verify); + AddKeyOp(JwkKeyOps.encrypt); + AddKeyOp(JwkKeyOps.wrapKey); + + SetAlg(JwkAlg.RSA_OAEP_256); + SetX5c(cert); + SetX5t(cert); + SetX5t256(cert); + } + public string ToJson() + { + return JsonConvert.SerializeObject( + this, + Formatting.None, + new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); + } + + void SetExponent(byte[] exp) + { + e = Base64UrlEncoder.Encode(exp); + } + + void SetModulus(byte[] modulus) + { + n = Base64UrlEncoder.Encode(modulus); + } + + void SetKeyType(JwkKeyType keyType) + { + // This is all we support right now + if (keyType == JwkKeyType.RSA) + kty = "RSA"; + else + throw new Exception("Invalid argument"); + } + + void SetUse(JwkUse _use) + { + if (_use == JwkUse.enc) + use = "enc"; + else if (_use == JwkUse.sig) + use = "sig"; + else + throw new Exception("Invalid argument"); + } + + void AddKeyOp(JwkKeyOps keyOp) + { + switch (keyOp) + { + case JwkKeyOps.sign: + key_ops.Add("sign"); + break; + case JwkKeyOps.verify: + key_ops.Add("verify"); + break; + case JwkKeyOps.encrypt: + key_ops.Add("encrypt"); + break; + case JwkKeyOps.decrypt: + key_ops.Add("decrypt"); + break; + case JwkKeyOps.wrapKey: + key_ops.Add("wrapKey"); + break; + case JwkKeyOps.unwrapKey: + key_ops.Add("unwrapKey"); + break; + case JwkKeyOps.deriveKey: + key_ops.Add("deriveKey"); + break; + case JwkKeyOps.deriveBits: + key_ops.Add("deriveBits"); + break; + } + } + + void SetKeyOps(JwkKeyOps[] keyOps) + { + foreach (JwkKeyOps op in keyOps) + { + AddKeyOp(op); + } + } + + public void SetAlg(JwkAlg _alg) + { + if (_alg == JwkAlg.RSA_OAEP) + alg = "RSA-OAEP"; + else + if (_alg == JwkAlg.RSA_OAEP_256) + alg = "RSA-OAEP-256"; + else + throw new Exception("Invalid argument"); + } + + public void SetKid(string _kid) + { + kid = _kid; + } + + void SetX5u() + { + // Just to document that this is not to be used + throw new Exception("Not supported"); + } + + void SetX5c(X509Certificate2 cert) + { + // TODO, note: does not support chain, just one cert + string base64cert = Convert.ToBase64String(cert.RawData); + x5c.Add(base64cert); + } + + public X509Certificate2 GetX5c() + { + if (x5c.Count < 1) + return null; + + string base64cert = x5c[0]; + byte[] rawCert = Convert.FromBase64String(base64cert); + X509Certificate2 cert = new X509Certificate2(rawCert); + return cert; + } + + public string GetX5cAsPem() + { + string base64cert = x5c[0]; + // Convert to PEM + string header = "-----BEGIN CERTIFICATE-----\n"; + string footer = "-----END CERTIFICATE-----"; + string pem = header; + + // Now grab 65-character pieces of the base64-encoded cert + for (Int32 i = 0; i < base64cert.Length; i += 65) + { + Int32 remaining = base64cert.Length - i; + string line = base64cert.Substring(i, remaining > 65 ? 65 : remaining); + pem += line + "\n"; + } + + pem += footer; + return pem; + } + + // TODO - move to utils + public static byte[] ToByteArray(String HexString) + { + int NumberChars = HexString.Length; + byte[] bytes = new byte[NumberChars / 2]; + for (int i = 0; i < NumberChars; i += 2) + { + bytes[i / 2] = Convert.ToByte(HexString.Substring(i, 2), 16); + } + return bytes; + } + + void SetX5t(X509Certificate2 cert) + { + x5t = Base64UrlEncoder.Encode(ToByteArray(cert.Thumbprint)); + } + + void SetX5t256(X509Certificate2 cert) + { + x5t_S256 = Base64UrlEncoder.Encode(Utils.Sha256Thumbprint(cert)); + } + + public string kty { get; set; } + public string use { get; set; } + public IList key_ops { get; set; } + + public string alg { get; set; } + public string kid { get; set; } + public string x5u { get; set; } + public IList x5c { get; set; } + public string x5t { get; set; } // X.509 Certificate SHA-1 Thumbprint + + [JsonProperty("x5t#S256")] + public string x5t_S256 { get; set; } // X.509 Certificate SHA-256 Thumbprint + public string n { get; set; } + public string e { get; set; } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/KDF.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/KDF.cs new file mode 100644 index 000000000000..8a4ded6d9570 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/KDF.cs @@ -0,0 +1,136 @@ +using System; +using System.Text; +using System.Security.Cryptography; +using System.IO; +using System.Linq; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + internal class KDF + { + static public byte[] to_big_endian(Int32 value) + { + byte[] result = new byte[4]; + result[3] = (byte)((value & 0x000000FF)); + result[2] = (byte)((value & 0x0000FF00) >> 8); + result[1] = (byte)((value & 0x00FF0000) >> 16); + result[0] = (byte)((value & 0xFF000000) >> 24); + return result; + } + static public byte[] to_big_endian(UInt64 value) + { + byte[] result = new byte[8]; + result[7] = (byte)((value & 0x00000000000000FF)); + result[6] = (byte)((value & 0x000000000000FF00) >> 8); + result[5] = (byte)((value & 0x0000000000FF0000) >> 16); + result[4] = (byte)((value & 0x00000000FF000000) >> 24); + result[3] = (byte)((value & 0x000000FF00000000) >> 32); + result[2] = (byte)((value & 0x0000FF0000000000) >> 40); + result[1] = (byte)((value & 0x00FF000000000000) >> 48); + result[0] = (byte)((value & 0xFF00000000000000) >> 56); + return result; + } + + static public bool self_test_sp800_108() + { + string label = "label"; + string context = "context"; + Int32 bitLength = 256; + string hex_result = "f0ca51f6308791404bf68b56024ee7c64d6c737716f81d47e1e68b5c4e399575"; + + byte[] key = Enumerable.Repeat((byte)0x41, 32).ToArray(); + HMACSHA512 hmac = new HMACSHA512(); + + byte[] new_key = sp800_108(key, label, context, hmac, bitLength); + + string hex = BitConverter.ToString(new_key).Replace("-", ""); + + return (hex.ToLower() == hex_result); + } + + // Note - initialize out to be the number of bytes of keying material that you need + // This implements SP 800-108 in counter mode, see section 5.1 + /* + Fixed values: + 1. h - The length of the output of the PRF in bits, and + 2. r - The length of the binary representation of the counter i. + + Input: KI, Label, Context, and L. + + Process: + 1. n := ⎡L/h⎤. + 2. If n > 2^(r-1), then indicate an error and stop. + 3. result(0):= ∅. + 4. For i = 1 to n, do + a. K(i) := PRF (KI, [i]2 || Label || 0x00 || Context || [L]2) + b. result(i) := result(i-1) || K(i). + + 5. Return: KO := the leftmost L bits of result(n). + */ + static public byte[] sp800_108(byte[] key_in, string label, string context, HMAC hMAC, Int32 bit_length) + { + if (bit_length <= 0 || bit_length % 8 != 0) + return null; + + Int32 L = bit_length; + Int32 bytes_needed = bit_length / 8; + Int32 n = 0; + Int32 hash_bits = 0; + + hash_bits = hMAC.HashSize; + + n = L / hash_bits; + + if (L % hash_bits != 0) + n++; + + Int32 hmac_data_size = 4 + label.Length + 1 + context.Length + 4; + byte[] hmac_data_suffix = null; + + using (MemoryStream mem = new MemoryStream()) + { + byte[] zero = new byte[1]; + zero[0] = 0; + + mem.Write(Encoding.UTF8.GetBytes(label)); + mem.Write(zero); + mem.Write(Encoding.UTF8.GetBytes(context)); + mem.Write(to_big_endian(bit_length)); + hmac_data_suffix = mem.ToArray(); + } + + using (MemoryStream out_stm = new MemoryStream()) + { + for (Int32 i = 0; i < n; ++i) + { + byte[] hmac_data = null; + + using (MemoryStream mem = new MemoryStream()) + { + mem.Write(to_big_endian(i + 1)); + mem.Write(hmac_data_suffix); + hmac_data = mem.ToArray(); + } + + hMAC.Key = key_in; + byte[] hash_value = hMAC.ComputeHash(hmac_data); + + if (bytes_needed > hash_value.Length) + { + out_stm.Write(hash_value); + bytes_needed -= hash_value.Length; + } + else + { + out_stm.Write(hash_value, (int)out_stm.Length, bytes_needed); + return out_stm.ToArray(); + } + // reset hmac for next round + hMAC.Initialize(); + } + } + return null; + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs new file mode 100644 index 000000000000..6eed2da9fb94 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + public class KeyPath + { + public string PublicKey; + public string PrivateKey; + public string Password; + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomain.cs new file mode 100644 index 000000000000..df7826236f2c --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomain.cs @@ -0,0 +1,85 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + public class Datum + { + public string compact_jwe { get; set; } + public string tag { get; set; } + } + + public class EncData + { + public EncData() + { + data = new List(); + } + + public IList data { get; set; } + public string kdf { get; set; } + } + + public class Plaintext + { + public byte[] plaintext; + public string tag; + } + + public class PlaintextList + { + public PlaintextList() + { + list = new List(); + } + + public void Add(Plaintext p) + { + list.Add(p); + } + + public List<Plaintext> list; + } + + public class Key + { + public string enc_key { get; set; } + public string x5t_256 { get; set; } + } + + public class KeyPair + { + public Key key1 { get; set; } + public Key key2 { get; set; } + } + + public class SplitKeys + { + public string key_algorithm { get; set; } + public IList<KeyPair> keys { get; set; } + } + + public class SharedKeys + { + public string key_algorithm { get; set; } + public UInt32 required { get; set; } + public IList<Key> enc_shares { get; set; } + } + + public class SecurityDomainData + { + public EncData EncData { get; set; } + + // Because the deserializer isn't very picky, the struct + // can contain both the new and the old members, and we can just use the one we need + public SplitKeys SplitKeys { get; set; } + public SharedKeys SharedKeys { get; set; } + public int version { get; set; } + } + + public class SecurityDomainWrapper + { + public string value { get; set; } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs new file mode 100644 index 000000000000..fe661076f06c --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -0,0 +1,470 @@ +using Hyak.Common.TransientFaultHandling; +using Microsoft.Azure.Commands.Common.Authentication; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.KeyVault.Models; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models; +using Microsoft.IdentityModel.Tokens; +using Microsoft.Rest; +using Microsoft.WindowsAzure.Commands.Utilities.Common; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using static Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureEnvironment; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + internal class SecurityDomainClient : ServiceClient<SecurityDomainClient>, ISecurityDomainClient + { + public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzureContext defaultContext) + { + //_credentials = new DataServiceCredential(authenticationFactory, defaultContext, ExtendedEndpoint.ManagedHsmServiceEndpointResourceId).GetServiceClientCredentials(); + _credentials = new DataServiceCredential(authenticationFactory, defaultContext, ExtendedEndpoint.ManagedHsmServiceEndpointResourceId); + + _uriHelper = new VaultUriHelper( + defaultContext.Environment.GetEndpoint(AzureEnvironment.Endpoint.AzureKeyVaultDnsSuffix), + defaultContext.Environment.GetEndpoint(ExtendedEndpoint.ManagedHsmServiceEndpointSuffix)); + } + + private DataServiceCredential _credentials; + private VaultUriHelper _uriHelper; + private JsonSerializerSettings _serializationSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + + /// <summary> + /// Download security domain data. + /// </summary> + /// <param name="hsmName">Name of the HSM</param> + /// <param name="certificates">Certificates used to encrypt the security domain data</param> + /// <param name="required">Specify how many keys are required to decrypt the data</param> + /// <returns>Encrypted HSM security domain data in string</returns> + public async Task<string> DownloadSecurityDomainAsync(string hsmName, IEnumerable<X509Certificate2> certificates, int required) + { + ValidateDownloadRequest(hsmName, certificates); + + try + { + var downloadRequest = new DownloadRequest + { + required = required + }; + certificates.ForEach(cert => downloadRequest.certificates.Add(new JWK(cert))); + + string requestBody = JsonConvert.SerializeObject( + downloadRequest, + Formatting.None, + _serializationSettings); + + HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; + + var httpRequest = new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) + { + Path = "/securitydomain/download" + }.Uri, + Content = new StringContent(requestBody) + }; + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + + var token = _credentials.GetTokenTemp(); + token.AuthorizeRequest((tokenType, tokenValue) => + { + httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); + }); + + var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + + if (httpResponseMessage.IsSuccessStatusCode) + { + string response = await httpResponseMessage.Content.ReadAsStringAsync(); + SecurityDomainWrapper securityDomainWrapper = JsonConvert.DeserializeObject<SecurityDomainWrapper>(response); + if (string.IsNullOrEmpty(securityDomainWrapper.value)) + { + Console.WriteLine("Response from server invalid"); + return null; + } + + if (!ValidateSecurityDomainData(securityDomainWrapper.value)) + { + Console.WriteLine("Unexpected security domain format"); + return null; + } + return securityDomainWrapper.value; + } + + return null; + } + catch (Exception err) + { + Console.WriteLine($"RequestSecurityDomain failed = {err.Message}"); + Console.WriteLine(err); + return null; + } + } + + private void ValidateDownloadRequest(string hsmName, IEnumerable<X509Certificate2> certificates) + { + if (certificates.Count() < 3) + { + throw new ArgumentException("Must have at least three certificates"); + } + if (string.IsNullOrWhiteSpace(hsmName)) + { + throw new ArgumentException(nameof(hsmName)); + } + } + + private bool ValidateSecurityDomainData(string securityDomainData) + { + var securityDomain = JsonConvert.DeserializeObject<SecurityDomainData>(securityDomainData); + + // DeserializeObject isn't very picky, need to validate further + bool valid = false; + + // Note - this is very rudimentary, should + // do more comprehensive checking. + if (securityDomain.EncData != null) + { + switch (securityDomain.version) + { + case 1: + if (securityDomain.SplitKeys != null) + valid = true; + break; + + case 2: + if (securityDomain.SharedKeys != null) + valid = true; + break; + + default: + break; + } + } + + return valid; + } + + public async Task<X509Certificate2> DownloadSecurityDomainExchangeKeyAsync(string hsmName) + { + if (string.IsNullOrWhiteSpace(hsmName)) + { + throw new ArgumentException(nameof(hsmName)); + } + + try + { + HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; + + var httpRequest = new HttpRequestMessage + { + Method = new HttpMethod("GET"), + RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) + { + Path = "/securitydomain/upload" + }.Uri, + }; + + var token = _credentials.GetTokenTemp(); + token.AuthorizeRequest((tokenType, tokenValue) => + { + httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); + }); + + HttpResponseMessage httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + + if (httpResponseMessage.IsSuccessStatusCode) + { + var response = await httpResponseMessage.Content.ReadAsStringAsync(); + var key = JsonConvert.DeserializeObject<SecurityDomainTransferKey>(response); + + switch (key.KeyFormat) + { + case "pem": + // Transitional, remove later + return Utils.CertficateFromPem(key.TransferKey); + case "jwk": + // handle below + break; + default: + return null; + } + + // The transfer key is a JWK, need to parse it, and return the cert + JWK jwk = JsonConvert.DeserializeObject<JWK>(key.TransferKey); + return Utils.CertficateFromPem(jwk.GetX5cAsPem()); + } + + return null; + } + catch (Exception err) + { + Console.WriteLine($"DownloadSecurityDomainTransferKey failed = {err.Message}"); + Console.WriteLine(err); + return null; + } + } + + public string EncryptSecurityDomainByCert(KeyPath[] keys, SecurityDomainData data, X509Certificate2 restore_cert) + { + try + { + string restore_data = GetSecurityDomainRestore(restore_cert, data, keys); + + if (restore_data == null) + { + Console.WriteLine("Unable to create security domain restore"); + return null; + } + + return restore_data; + } + catch (Exception err) + { + Console.WriteLine("Unable to decrypt security domain " + err.Message); + return null; + } + } + + private string GetSecurityDomainRestore(X509Certificate2 restoreCert, SecurityDomainData data, KeyPath[] keys) + { + PlaintextList plaintextList = Decrypt(data, keys); + SecurityDomainRestoreData restoreData = EncryptForRestore(restoreCert, plaintextList); + return JsonConvert.SerializeObject(restoreData); + } + + private PlaintextList Decrypt(SecurityDomainData data, KeyPath[] paths) + { + CertKeys certKeys = new CertKeys(); + certKeys.LoadKeys(paths); + + if (certKeys.Count() < 2) + { + Console.WriteLine("Cannot load two certificates and keys"); + return null; + } + + return Decrypt(data, certKeys); + } + + // Internal worker function + private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) + { + if (data == null || + certKeys.Count() < 2 || + (data.version == 2 && certKeys.Count() < data.SharedKeys.required)) + { + Console.WriteLine("Invalid arguments"); + return null; + } + + byte[] master_key = null; + + if (data.version == 1) + { + // ensure that the key splitting algorithm + // is known, currently only one we know about + if (data.SplitKeys.key_algorithm != "xor_split") + { + Console.WriteLine("Unknown SplitKey algorithm"); + return null; + } + + KeyPair decode_key_pair = null; + CertKey certKey1 = null; + CertKey certKey2 = null; + foreach (KeyPair key_pair in data.SplitKeys.keys) + { + certKey1 = certKeys.Find(key_pair.key1.x5t_256); + + if (certKey1 == null) + continue; + + certKey2 = certKeys.Find(key_pair.key2.x5t_256); + + if (certKey2 != null) + { + decode_key_pair = key_pair; + break; + } + } + + if (decode_key_pair == null) + { + Console.WriteLine("Cannot find matching certs and keys for security domain"); + return null; + } + + master_key = DecryptMasterKey(decode_key_pair, certKey1, certKey2); + } + else if (data.version == 2) + { + if (data.SharedKeys.key_algorithm != "shamir_share") + { + Console.WriteLine("Unknown SharedKeys algorithm"); + return null; + } + + UInt32 shares_found = 0; + List<UInt16[]> share_arrays = new List<UInt16[]>(); + + foreach (Key key in data.SharedKeys.enc_shares) + { + CertKey cert_key = certKeys.Find(key.x5t_256); + + if (cert_key != null) + { + JWE jwe = new JWE(key.enc_key); + byte[] share = jwe.Decrypt(cert_key.get_key()); + + shares_found++; + share_arrays.Add(Utils.ConvertToUint16(share)); + } + + if (share_arrays.Count == data.SharedKeys.required) + break; + } + + if (share_arrays.Count < data.SharedKeys.required) + { + Console.WriteLine("Insufficient shares available"); + return null; + } + + shamir_share_net.shared_secret secret = new shamir_share_net.shared_secret((UInt16)data.SharedKeys.required); + master_key = secret.get_secret(share_arrays); + } + else + { + Console.WriteLine("Unknown domain version"); + return null; + } + + PlaintextList plaintextList = new PlaintextList(); + + // Need to check KDF + foreach (Datum enc_data in data.EncData.data) + { + Plaintext p = new Plaintext(); + HMACSHA512 hmac = new HMACSHA512(); + byte[] enc_key = KDF.sp800_108(master_key, enc_data.tag, "", hmac, 512); + JWE jwe_data = new JWE(enc_data.compact_jwe); + p.plaintext = jwe_data.Decrypt(enc_key); + p.tag = enc_data.tag; + + plaintextList.Add(p); + } + + return plaintextList; + } + + private byte[] DecryptMasterKey(KeyPair decode_key_pair, CertKey certKey1, CertKey certKey2) + { + JWE jwe1 = new JWE(decode_key_pair.key1.enc_key); + byte[] xor_key = jwe1.Decrypt(certKey1.get_key()); + + JWE jwe2 = new JWE(decode_key_pair.key2.enc_key); + byte[] derived_key = jwe2.Decrypt(certKey2.get_key()); + + // Now, XOR to get the master key back + byte[] master_key = new byte[xor_key.Length]; + + for (Int32 i = 0; i < xor_key.Length; ++i) + { + master_key[i] = (byte)(xor_key[i] ^ derived_key[i]); + } + return master_key; + } + + private SecurityDomainRestoreData EncryptForRestore(X509Certificate2 cert, PlaintextList plaintextList) + { + SecurityDomainRestoreData securityDomainRestoreData = new SecurityDomainRestoreData(); + securityDomainRestoreData.EncData.kdf = "sp108_kdf"; + + byte[] master_key = Utils.GetRandom(32); + + foreach (Plaintext p in plaintextList.list) + { + Datum datum = new Datum(); + HMACSHA512 hmac = new HMACSHA512(); + byte[] enc_key = KDF.sp800_108(master_key, p.tag, "", hmac, 512); + + datum.tag = p.tag; + JWE jwe = new JWE(); + jwe.Encrypt(enc_key, p.plaintext, "A256CBC-HS512", p.tag); + datum.compact_jwe = jwe.EncodeCompact(); + securityDomainRestoreData.EncData.data.Add(datum); + } + + // Now go make the wrapped key + JWE jwe_wrapped = new JWE(); + jwe_wrapped.Encrypt(cert, master_key); + securityDomainRestoreData.WrappedKey.enc_key = jwe_wrapped.EncodeCompact(); + securityDomainRestoreData.WrappedKey.x5t_256 = Base64UrlEncoder.Encode(Utils.Sha256Thumbprint(cert)); + return securityDomainRestoreData; + } + + public async Task<bool> RestoreSecurityDomainAsync(string hsmName, string securityDomainData) + { + if (string.IsNullOrWhiteSpace(hsmName)) + { + throw new ArgumentException(nameof(hsmName)); + } + + if (string.IsNullOrEmpty(securityDomainData)) + throw new ArgumentNullException(nameof(securityDomainData)); + + string securityDomain = JsonConvert.SerializeObject(new SecurityDomainWrapper + { + value = securityDomainData + }); + + try + { + HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; + + var httpRequest = new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) + { + Path = "/securitydomain/upload" + }.Uri, + Content = new StringContent(securityDomain) + }; + + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + + var token = _credentials.GetTokenTemp(); + token.AuthorizeRequest((tokenType, tokenValue) => + { + httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); + }); + + var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + + if (httpResponseMessage.IsSuccessStatusCode) + { + return !string.IsNullOrEmpty(await httpResponseMessage.Content.ReadAsStringAsync()); + } + + return false; + } + catch (Exception err) + { + Console.WriteLine($"RequestSecurityDomain failed = {err.Message}"); + Console.WriteLine(err); + return false; + } + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainRestoreData.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainRestoreData.cs new file mode 100644 index 000000000000..f2036055fcc7 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainRestoreData.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + public class SecurityDomainRestoreData + { + public SecurityDomainRestoreData() + { + EncData = new EncData(); + WrappedKey = new Key(); + } + public EncData EncData { get; set; } + public Key WrappedKey { get; set; } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainTransferKey.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainTransferKey.cs new file mode 100644 index 000000000000..e251b04ab857 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainTransferKey.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models +{ + public class SecurityDomainTransferKey + { + [JsonProperty("transfer_key")] + public string TransferKey { get; set; } + + [JsonProperty("key_format")] + public string KeyFormat { get; set; } + } +} diff --git a/src/KeyVault/SecurityDomain.Test/.gitignore b/src/KeyVault/SecurityDomain.Test/.gitignore new file mode 100644 index 000000000000..3a7de2219da9 --- /dev/null +++ b/src/KeyVault/SecurityDomain.Test/.gitignore @@ -0,0 +1 @@ +Cert/ \ No newline at end of file diff --git a/src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj b/src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj new file mode 100644 index 000000000000..ae1184e7dcfa --- /dev/null +++ b/src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj @@ -0,0 +1,20 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.1</TargetFramework> + + <IsPackable>false</IsPackable> + </PropertyGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> + <PackageReference Include="xunit" Version="2.4.0" /> + <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> + <PackageReference Include="coverlet.collector" Version="1.2.0" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\KeyVault\KeyVault.csproj" /> + </ItemGroup> + +</Project> diff --git a/src/KeyVault/SecurityDomain.Test/UnitTest1.cs b/src/KeyVault/SecurityDomain.Test/UnitTest1.cs new file mode 100644 index 000000000000..36554cb677f9 --- /dev/null +++ b/src/KeyVault/SecurityDomain.Test/UnitTest1.cs @@ -0,0 +1,24 @@ +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models; +using System; +using System.Security.Cryptography.X509Certificates; +using Xunit; + +namespace SecurityDomain.Test +{ + public class UnitTest1 + { + [Fact] + public void X509Tests() + { + X509Certificate2 cert = new X509Certificate2(@"C:\yeming.liu.cer"); + Assert.NotNull(cert); + + JWK jwk = new JWK(cert); + Assert.NotNull(jwk); + + Assert.Equal(JwkKeyType.RSA.ToString(), jwk.kty); + } + } +} diff --git a/src/KeyVault/shamir_share/mod_math.cs b/src/KeyVault/shamir_share/mod_math.cs new file mode 100644 index 000000000000..8beca40a8302 --- /dev/null +++ b/src/KeyVault/shamir_share/mod_math.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace shamir_share_net +{ + class mod_math + { + public static UInt32 mod_invert(UInt32 x) + { + UInt32 ret = x; + + for (UInt32 i = 0; i < 7; ++i) + { + ret = mod_multiply(ret, ret); + ret = mod_multiply(ret, x); + } + + return ret; + } + + public static UInt32 mod_reduce(UInt32 x) + { + // Function to find x % 257 without side channels + UInt32 t = (x & 0xff) - (x >> 8); + t += (UInt32)((Int32)t >> 31) & 257; + return t; + } + + // Assumes a, b are within 0-256 + public static UInt32 mod_multiply(UInt32 a, UInt32 b) + { + return mod_reduce(a * b); + } + + public static UInt32 mod_add(UInt32 a, UInt32 b) + { + return mod_reduce(a + b); + } + + public static UInt32 mod_subtract(UInt32 a, UInt32 b) + { + // Must ensure that the difference is in the range of 0-256 + return mod_reduce(a - b + 257); + } + } + + class random_bits + { + public UInt16 get_mod_257() + { + UInt16 tmp = 0; + do + { + tmp = get_word(); + + if (tmp != 0) + return (UInt16)mod_math.mod_reduce(tmp); + + } while (tmp == 0); + + // Not actually reached + return 0; + } + + public UInt16 get_word() + { + Int32 remaining = random_bytes.Length - (Int32)current; + + if (remaining < 2) + load(); + + UInt16 ret = (UInt16)((random_bytes[current+1] << 8) | random_bytes[current]); + current += 2; + return ret; + } + + void load() + { + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + current = 0; + rng.GetBytes(random_bytes); + } + } + + public random_bits(UInt32 _chunk_size) + { + chunk_size = _chunk_size; + current = 0; + random_bytes = new byte[chunk_size]; + load(); + } + + UInt32 chunk_size; + UInt32 current; + byte[] random_bytes; + } + + struct share + { + public share(UInt16 w) + { + x = (UInt16)(w >> 9); + value = (UInt16)(w & 0x1ff); + } + + public share(UInt16 _x, UInt16 _value) + { + x = _x; + value = _value; + } + + public UInt16 to_uint16() + { + return (UInt16)(x << 9 | value); + } + + public UInt16 x; + public UInt16 value; + } + class shared_math + { + public static UInt16 get_secret(UInt16[] shares, UInt32 size) + { + UInt32 secret = 0; + + // Calculate numerator + for (UInt32 i = 0; i<size; ++i) + { + UInt32 numerator = 1; + UInt32 denominator = 1; + share si = new share(shares[i]); + + for (UInt32 j = 0; j<size; ++j) + { + if (i == j) + continue; + + share sj = new share(shares[j]); + numerator = mod_math.mod_multiply(numerator, sj.x); + UInt32 diff = mod_math.mod_subtract(sj.x, si.x); + denominator = mod_math.mod_multiply(diff, denominator); + } + + UInt32 invert = mod_math.mod_invert(denominator); + UInt32 ci = mod_math.mod_multiply(numerator, invert); + UInt32 tmp = mod_math.mod_multiply(ci, si.value); + secret = mod_math.mod_add(secret, tmp); + } + + return (UInt16)(secret); + } + + static public UInt16 make_share(UInt16[] coefficients, UInt16 x) + { + /* + When you evaluate + a*x^3 + b*x^2 + c*x + d + you compute + ((a*x + b)*x + c)*x + d + Also known as Horner’s rule. + */ + + if (coefficients.Length < 2) + { + throw new Exception("Invalid input"); + } + + UInt32 tmp = 0; + tmp = mod_math.mod_multiply(coefficients[0], x ); + tmp = mod_math.mod_add(tmp, coefficients[1] ); + + for (UInt32 i = 2; i < coefficients.Length; ++i) + { + tmp = mod_math.mod_multiply(tmp, x ); + tmp = mod_math.mod_add(tmp, coefficients[i] ); + } + + return (UInt16)(tmp ); + } + } +} diff --git a/src/KeyVault/shamir_share/python/main.py b/src/KeyVault/shamir_share/python/main.py new file mode 100644 index 000000000000..29f348f0d9b7 --- /dev/null +++ b/src/KeyVault/shamir_share/python/main.py @@ -0,0 +1,8 @@ +from test import * + +def main(): + print("Running all tests") + run_all_tests() + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/KeyVault/shamir_share/python/mod_math.py b/src/KeyVault/shamir_share/python/mod_math.py new file mode 100644 index 000000000000..acdf4c9de86f --- /dev/null +++ b/src/KeyVault/shamir_share/python/mod_math.py @@ -0,0 +1,111 @@ +import secrets +import array + +class mod_math: + + def mod_reduce(self, x): + t = (x & 0xff) - (x >> 8) + t += (t >> 31) & 257 + return t + + def mod_multiply(self, x, y): + return self.mod_reduce( x * y ) + + def mod_invert(self, x): + ret = x + for i in range(7): + ret = self.mod_multiply(ret, ret) + ret = self.mod_multiply(ret, x) + + return ret + + def mod_add(self, x, y): + return self.mod_reduce( x + y) + + def mod_subtract(self, x, y): + return self.mod_reduce( x - y + 257 ) + + def get_random(self): + r = secrets.randbits(16) + return self.mod_reduce(r) + +class share: + def __init__(self, x, v): + self.x = x + self.v = v + + def from_uint16(self, w): + self.x = w >> 9 + self.v = w & 0x1ff + return self + + def to_uint16(self): + return ((self.x << 9) | self.v) + +class mod_math_internal: + def share_from_uint16(self, w): + x = w >> 9 + v = w & 0x1ff + return share(x, v) + +class make_byte_shares: + def __init__(self, required, secret_byte): + self.mm = mod_math() + self.coefficients = self.init_coefficients(required, secret_byte) + + def init_coefficients(self, required, secret_byte): + coefficients = array.array('H') + + for i in range(required - 1): + coefficients.append(self.mm.get_random()) + + coefficients.append( secret_byte ) + return coefficients + + def set_secret_byte(self, secret_byte): + self.coefficients[ len(self.coefficients) - 1] = secret_byte + + def make_share(self, x): + tmp = 0 + tmp = self.mm.mod_multiply(self.coefficients[0], x ) + tmp = self.mm.mod_add(tmp, self.coefficients[1] ) + + for i in range( 2, len(self.coefficients) ): + tmp = self.mm.mod_multiply(tmp, x) + tmp = self.mm.mod_add(tmp, self.coefficients[i]) + i += 1 + + s = share(x, tmp) + return s + +class get_byte_secret: + def __init__(self): + self.mm = mod_math() + self.mmi = mod_math_internal() + + + def get_secret( self, shares, required ): + + secret = 0 + + for i in range( required ): + numerator = 1 + denominator = 1 + si = self.mmi.share_from_uint16(shares[i]) + + for j in range(required): + if i == j: + continue + + sj = self.mmi.share_from_uint16(shares[j]) + numerator = self.mm.mod_multiply(numerator, sj.x) + diff = self.mm.mod_subtract(sj.x, si.x) + denominator = self.mm.mod_multiply(diff, denominator) + + invert = self.mm.mod_invert(denominator) + ci = self.mm.mod_multiply(numerator, invert) + tmp = self.mm.mod_multiply(ci, si.v) + secret = self.mm.mod_add(secret, tmp) + + return secret + diff --git a/src/KeyVault/shamir_share/python/shared_secret.py b/src/KeyVault/shamir_share/python/shared_secret.py new file mode 100644 index 000000000000..770d9dd3518c --- /dev/null +++ b/src/KeyVault/shamir_share/python/shared_secret.py @@ -0,0 +1,78 @@ +import array +from mod_math import * + +max_shares = 126 + +class make_shared_secret: + def __init__(self, shares, required): + if shares > max_shares or required > shares or required < 2: + raise Exception("Incorrect share or required count") + + self.shares = shares + self.required = required + self.byte_shares = make_byte_shares( required, 0) + + def make_byte_shares(self, b): + share_array = [] + self.byte_shares.set_secret_byte(b) + + x = 1 + for i in range(self.shares): + s = self.byte_shares.make_share(x) + share_array.append(s.to_uint16()) + x = x + 1 + + return share_array + + def make_shares(self, plaintext): + share_arrays = [] + + for i in range( len(plaintext) ): + p = plaintext[i] + share_array = self.make_byte_shares(p) + + share_count = len(share_array) + + for j in range(share_count): + if i == 0: + tmp = array.array('H') + share_arrays.append(tmp) + + current_share_array = share_arrays[j] + current_share_array.append(share_array[j]) + + return share_arrays + +class get_secret: + def __init__(self, required): + self.required = required + self.gbs = get_byte_secret() + + def get_secret_byte(self, share_array): + if len(share_array) < self.required: + raise Exception("Insufficient shares") + + return self.gbs.get_secret(share_array, self.required) + + def get_plaintext(self, share_arrays): + plaintext = bytearray() + plaintext_len = len(share_arrays[0]) + + if len(share_arrays) < self.required: + raise Exception("Insufficient shares") + + for j in range(plaintext_len): + sv = array.array('H') + + for i in range(self.required): + sa = share_arrays[i] + sv.append(sa[j]) + + text = self.get_secret_byte(sv) + plaintext.append(text) + + return plaintext + + + + diff --git a/src/KeyVault/shamir_share/python/test.py b/src/KeyVault/shamir_share/python/test.py new file mode 100644 index 000000000000..d043aff8d4e0 --- /dev/null +++ b/src/KeyVault/shamir_share/python/test.py @@ -0,0 +1,163 @@ +import time +import secrets +from mod_math import * +from shared_secret import * + +def single_byte_test(shares, required): + for i in range(0x100): + secret = make_shared_secret(shares, required) + share_array = secret.make_byte_shares(i) + secret2 = get_secret(required) + result = secret2.get_secret_byte(share_array) + + if i != result: + raise Exception("single_byte_test failed") + + +def test_126_shares(): + i = 126 + print("Running 126 share test") + + # python is much slower than other languages, so only do every 4th number + # if you want to test every possible combination, then change inc to -1 + inc = -4 + for j in range(i, 1, inc): + shares = i + required = j + fmt = "\tshares = {}, required = {}" + print(fmt.format(shares, required)) + single_byte_test(shares, required) + + print("test_126_shares - success") + +def test_all_shares(): + # When testing other languages, this goes up to 127 shares, and 16 required + # to do full testing, change max_shares and max_required + max_shares = 30 + for i in range(2, max_shares): + max_required = 10 + print("\tshares = {}".format(i)) + + if i > max_required: + limit = max_required + else: + limit = i + + for j in range(limit, 1, -1): + shares = i + required = j + single_byte_test(shares, required) + + print("test_all_shares - success\n") + +def perf_test(): + shares = 40 + required = 16 + then = time.time_ns() + single_byte_test(shares, required) + print("mod_reduce_ticks = {}".format(mod_reduce_ticks)) + print("Total elapsed = {}".format(time.time_ns() - then)) + +def random_single_byte_test(): + # note - in C++ and C#, this is 126 shares, 16 required, and 100000 iterations + shares = 30 + required = 10 + iterations = 1000 + + for i in range(iterations): + # just use i % 256 as the secret + secret_value = i % 256 + secret = make_shared_secret(shares, required) + share_array = secret.make_byte_shares(secret_value) + + tmp_array = share_array + random_shares = [] + + for j in range(required): + r = secrets.randbits(8) + pos = r % len(tmp_array) + val = tmp_array[pos] + random_shares.append(val) + tmp_array.remove(val) + + secret2 = get_secret(required) + result = secret2.get_secret_byte(random_shares) + + if result != secret_value: + raise Exception("random_single_byte_test failed") + + print("random_single_byte_test - success") + +def check_result(a, b): + # not for cryptographic purposes + # assumes equal length inputs + + for i in range(len(a)): + if a[i] != b[i]: + return False + + return True + +def test_large_secret(): + # note, because Python is so much slower, only doing + # 1000 iterations, not 1 million + iterations = 1000 + shares = 11 + required = 5 + secret_size = 32 + + for i in range(iterations): + ss = make_shared_secret(shares, required) + + # in order to make debugging a bit easier, + # start with a known plaintext, not random + secret = bytearray(32) + if i == 0: + for x in range(32): + secret[x] = 0x41 + x + else: + # randbits returns an int, not an array of bytes + tmp = secrets.randbits(secret_size*8) + + # Let's convert it + secret = tmp.to_bytes(secret_size, 'little') + + share_arrays = ss.make_shares(secret) + + secret2 = get_secret(required) + plaintext = secret2.get_plaintext(share_arrays) + + if check_result(secret, plaintext) == False: + raise Exception("test_large_secret failed") + + if (i + 1) % int(iterations/10) == 0: + print("{0} iterations".format(i+1)) + + print("test_large_secret - success") + +def run_all_tests(): + + # As it turns out, the Python code is 274 times slower than + # the C# or C++ implementation. It's all in the math, which is a LOT + # slower than even managed code. + + # As a result, tests that run in 15 seconds take about an hour and 15 + # minutes, which is much too long. So we're going to cut back on the + # tests. If you want to run them all, feel free to uncomment below. + + print("Running single-byte tests") + # print("Testing 126 shares, 2-126 required") + # test_126_shares() + + print("\nTesting up to 30 shares, 2-10 required") + test_all_shares() + + # This can be used to do performance analysis + #perf_test() + + print("\nRandom single-byte test") + random_single_byte_test() + + print("\nTesting large secrets") + test_large_secret() + diff --git a/src/KeyVault/shamir_share/shamir_share_net.csproj b/src/KeyVault/shamir_share/shamir_share_net.csproj new file mode 100644 index 000000000000..c76142a23d76 --- /dev/null +++ b/src/KeyVault/shamir_share/shamir_share_net.csproj @@ -0,0 +1,12 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <OutputType>Library</OutputType> + <TargetFramework>netstandard2.0</TargetFramework> + <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> + <GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute> + <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> + <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute> + </PropertyGroup> + +</Project> diff --git a/src/KeyVault/shamir_share/shared_secret.cs b/src/KeyVault/shamir_share/shared_secret.cs new file mode 100644 index 000000000000..e0c1d54c567d --- /dev/null +++ b/src/KeyVault/shamir_share/shared_secret.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace shamir_share_net +{ + public class shared_secret + { + public shared_secret(UInt16 shares, UInt16 required) + { + if (shares > max_shares || required > shares || required < 2) + throw new Exception("Incorrect share or required count"); + + _shares = shares; + _required = required; + _coefficients = new UInt16[required]; + _rand_bits = new random_bits(64); + + } + + public shared_secret(UInt16 required) + { + if (required < 2) + throw new Exception("Incorrect share or required count"); + + _shares = 0; + _required = required; + _rand_bits = new random_bits(64); + } + + public List<UInt16[]> make_shares(byte[] plaintext) + { + // Output will have size of share count, each share vector will have an entry for every input byte + List<UInt16[]> share_arrays = new List<UInt16[]>(); + + for( UInt32 i = 0; i < plaintext.Length; ++i) + { + byte p = plaintext[i]; + UInt16[] share_array = make_shares(p); + + /* + We now have a share created for the total number of shares needed + Each share then needs to be distributed such that there's a share + for each byte of plaintext, effectively transposing the 2-dimensional + array. + */ + Int32 share_count = share_array.Length; + + for (Int32 j = 0; j < share_count; ++j) + { + if (i == 0) + share_arrays.Add(new UInt16[plaintext.Length]); + + UInt16[] current_share_array = share_arrays[j]; + current_share_array[i] = share_array[j]; + } + } + + return share_arrays; + } + + public UInt16[] make_shares(byte secret_byte) + { + UInt16[] share_array = new UInt16[_shares]; + + init_coefficients(); + _coefficients[(UInt32)(_required) - 1] = secret_byte; + + UInt16 x = 1; + for (UInt32 i = 0; i < _shares; ++i, ++x) + { + share s = new share(x, shared_math.make_share(_coefficients, x)); + share_array[i] = s.to_uint16(); + } + + return share_array; + } + + public byte[] get_secret(List<UInt16[]> share_arrays) + { + byte[] plaintext = new byte[share_arrays[0].Length]; + + if (share_arrays.Count < _required) + { + throw new Exception("Insufficient shares"); + } + + UInt16[] sv = new UInt16[_required]; + + // TODO - all the constants calculated in get_secret + // can be pulled out once and re-used, which will help perf + for (UInt32 j = 0; j < plaintext.Length; ++j) + { + for (Int32 i = 0; i< _required; ++i) + { + UInt16[] sa = share_arrays[i]; + sv[i] = sa[j]; + } + + UInt16 text = get_secret(sv); + plaintext[j] = (byte)(text); + } + + return plaintext; + } + + public UInt16 get_secret(UInt16[] share_array) + { + if (share_array.Length < _required) + throw new Exception("Insufficient shares"); + + return shared_math.get_secret(share_array, _required); + } + + void init_coefficients() + { + for (UInt32 i = 0; i < (UInt32)(_required) - 1; ++i) + { + _coefficients[i] = _rand_bits.get_mod_257(); + } + } + + UInt16 _shares; + UInt16 _required; + UInt16[] _coefficients; + random_bits _rand_bits; + + const UInt32 max_shares = 126; + } +} diff --git a/src/KeyVault/shamir_share/test/test.cs b/src/KeyVault/shamir_share/test/test.cs new file mode 100644 index 000000000000..70e5efd75780 --- /dev/null +++ b/src/KeyVault/shamir_share/test/test.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace shamir_share_net +{ + class test + { + static void single_byte_test(byte shares, UInt16 required) + { + for (UInt16 i = 0; i < (UInt16)0x100; ++i) + { + shared_secret secret = new shared_secret(shares, required); + + UInt16[] share_array = secret.make_shares((byte)i); + UInt16 result = secret.get_secret(share_array); + + if (i != result) + { + throw new Exception("single_byte_test failed"); + } + } + } + + static void random_single_byte_test() + { + byte shares = 126; + UInt16 required = 16; + + for (UInt32 i = 0; i < 100000; ++i) + { + // just use i % 256 as the secret + byte secret_value = (byte)(i % 256); + shared_secret secret = new shared_secret(shares, required); + UInt16[] share_array = secret.make_shares(secret_value); + + // Put them into a List so that we can pick them out + List<UInt16> tmp_array = new List<UInt16>(); + + foreach(UInt16 u in share_array) + { + tmp_array.Add(u); + } + + // Now need to randomly pick values + UInt16[] random_shares = new UInt16[required]; + + random_bits bits = new random_bits(required); + + for (UInt32 j = 0; j < required; ++j) + { + // Yes, I really only need a byte, but this is test code + UInt16 r = bits.get_word(); + Int32 pos = (Int32)(r % tmp_array.Count); + + random_shares[j] = tmp_array[pos]; + tmp_array.RemoveAt(pos); + } + + UInt16 result = secret.get_secret(random_shares); + + if (result != secret_value) + { + throw new Exception("random_single_byte_test failed"); + } + } + + Console.WriteLine("random_single_byte_test - success"); + } + + static void test_all_shares() + { + for (UInt16 i = 2; i < 127; ++i) + { + // It will work for larger numbers of required, but + // it takes a while. Can do some targeted tests for large + // share count + UInt16 max_required = 16; + for (UInt16 j = i > max_required ? max_required : i; j > 1; --j) + { + byte shares = (byte)(i); + byte required = (byte)(j); + + single_byte_test(shares, required); + } + } + + Console.WriteLine("test_all_shares - success"); + } + + static void test_126_shares() + { + UInt16 i = 126; + + Console.WriteLine("Running 126 share test"); + + for (UInt16 j = i; j > 1; --j) + { + byte shares = (byte)(i); + byte required = (byte)(j); + + single_byte_test(shares, required); + } + + Console.WriteLine("test_126_shares - success"); + } + + static bool check_result(byte[] a, byte[] b) + { + // Not for cryptographic purposes + // assumes equal lengths + for(Int32 i = 0; i < a.Length; ++i) + { + if (a[i] != b[i]) + return false; + } + return true; + } + + static void test_large_secret() + { + for (UInt32 i = 0; i < 1000000; ++i) + { + shared_secret ss = new shared_secret(11, 5); + + byte[] secret = new byte[32]; + + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(secret); + } + + List<UInt16[]> share_arrays = ss.make_shares(secret); + byte[] plaintext = ss.get_secret(share_arrays); + + if ( !check_result(plaintext, secret) ) + { + throw new Exception("test_large_secret failed"); + } + + if ((i + 1) % 100000 == 0) + { + Console.WriteLine("{0} iterations", i + 1); + } + } + + Console.WriteLine("test_large_secret - success"); + } + + static public void run_all_tests() + { + Console.WriteLine("Running single-byte tests"); + Console.WriteLine("Testing 2-126 shares, 2-16 required"); + test_126_shares(); + + Console.WriteLine("\nTesting 126 shares, 2-126 required"); + test_all_shares(); + + Console.WriteLine("\nTesting random shares"); + random_single_byte_test(); + + Console.WriteLine("\nRunning 32 byte secret tests"); + test_large_secret(); + } + } +} From 9b4d3c80a4425f5d9ff05b1b13db23e50e9ef339 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Mon, 21 Sep 2020 15:31:48 +0800 Subject: [PATCH 02/18] wip --- src/KeyVault/KeyVault/KeyVault.csproj | 2 +- .../KeyVault/Properties/Resources.Designer.cs | 9 +++++++++ .../KeyVault/Properties/Resources.resx | 3 +++ .../Cmdlets/BackupSecurityDomain.cs | 18 ++++++++++++++---- .../Cmdlets/SecurityDomainCmdlet.cs | 2 +- 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/KeyVault/KeyVault/KeyVault.csproj b/src/KeyVault/KeyVault/KeyVault.csproj index 5b565a8d782e..ac30802e9e14 100644 --- a/src/KeyVault/KeyVault/KeyVault.csproj +++ b/src/KeyVault/KeyVault/KeyVault.csproj @@ -12,7 +12,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.1.0" /> + <PackageReference Include="Azure.Security.KeyVault.Keys" Version="4.2.0-beta.1" /> <PackageReference Include="BouncyCastle.NetCore" Version="1.8.6" /> <PackageReference Include="Microsoft.Azure.KeyVault" Version="3.0.1" /> <PackageReference Include="Microsoft.Azure.KeyVault.WebKey" Version="3.0.1" /> diff --git a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs index 5465a4159c9e..54c915310058 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs +++ b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs @@ -342,6 +342,15 @@ internal static string FileOverwriteMessage { } } + /// <summary> + /// Looks up a localized string similar to To encrypt the security domain data, please provide at least {0} and at most {1} certificates.. + /// </summary> + internal static string HsmCertRangeWarning { + get { + return ResourceManager.GetString("HsmCertRangeWarning", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to BYOK key can not be imported as software key. /// </summary> diff --git a/src/KeyVault/KeyVault/Properties/Resources.resx b/src/KeyVault/KeyVault/Properties/Resources.resx index 7b4377973f87..4813eb2a1ff9 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.resx +++ b/src/KeyVault/KeyVault/Properties/Resources.resx @@ -498,4 +498,7 @@ You can find the object ID using Azure Active Directory Module for Windows Power <data name="KeyOpsImportIsExclusive" xml:space="preserve"> <value>The "import" operation is exclusive, it cannot be combined with any other value(s).</value> </data> + <data name="HsmCertRangeWarning" xml:space="preserve"> + <value>To encrypt the security domain data, please provide at least {0} and at most {1} certificates.</value> + </data> </root> \ No newline at end of file diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index 22b7aa71fdc5..384d2d8441c3 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -13,6 +13,7 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets public class BackupSecurityDomain: SecurityDomainCmdlet { [Parameter(HelpMessage = "Paths to the certificates that are used to encrypt the security domain data.", Mandatory = true)] + [ValidateNotNullOrEmpty()] public string[] Certificates { get; set; } [Parameter(HelpMessage = "Specify the path where security domain data will be downloaded to.", Mandatory = true)] @@ -24,12 +25,13 @@ public class BackupSecurityDomain: SecurityDomainCmdlet [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] public SwitchParameter PassThru { get; set; } + [Parameter(HelpMessage = "The minimum number of shares required to decrypt the security domain for recovery.", Mandatory = true)] + [ValidateRange(2, 10)] + public int Quorum { get; set; } + public override void ExecuteCmdletCore() { - if (Certificates?.Length < 3 || Certificates?.Length > 10) // todo: check - { - throw new ArgumentException($"Number of {nameof(Certificates)} should be between 3 and 10"); // todo: resource string; check - } + ValidateParameters(); var certificates = Certificates.Select(path => new X509Certificate2(path)); @@ -47,5 +49,13 @@ public override void ExecuteCmdletCore() } } } + + private void ValidateParameters() + { + if (Certificates.Length < 3 || Certificates.Length > 10) + { + throw new ArgumentException(string.Format(Resources.HsmCertRangeWarning, 3, 10)); // todo: resource string; check + } + } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs index 4e5eb9c165d9..d9437f4544f0 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -7,7 +7,7 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { - public class SecurityDomainCmdlet: AzureRMCmdlet + public abstract class SecurityDomainCmdlet: AzureRMCmdlet { protected const string ByName = "By Name"; protected const string ByInputObject = "By InputObject"; From 9dbc12d7c30251cafba8dec634a419e852730dfe Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Wed, 23 Sep 2020 17:26:30 +0800 Subject: [PATCH 03/18] wip --- .../KeyVault/Properties/Resources.Designer.cs | 27 +++++++++++++++++ .../KeyVault/Properties/Resources.resx | 9 ++++++ .../Cmdlets/BackupSecurityDomain.cs | 11 ++++--- .../Cmdlets/RestoreSecurityDomain.cs | 30 ++++++++----------- .../Cmdlets/SecurityDomainCmdlet.cs | 12 +++++--- .../SecurityDomain/Common/Constants.cs | 10 +++++++ 6 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Common/Constants.cs diff --git a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs index 54c915310058..16f3bf803655 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs +++ b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs @@ -639,6 +639,15 @@ internal static string KeyOpsImportIsExclusive { } } + /// <summary> + /// Looks up a localized string similar to Failed to load security domain data from {0}. Please make sure the file exists and is not modified.. + /// </summary> + internal static string LoadSecurityDomainFileFailed { + get { + return ResourceManager.GetString("LoadSecurityDomainFileFailed", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to There is no default user account associated with this subscription. Certificate accounts are not supported with Azure Key Vault.. /// </summary> @@ -1035,6 +1044,24 @@ internal static string RestoreSecret { } } + /// <summary> + /// Looks up a localized string similar to &quot;PublicKey&quot; and &quot;PrivateKey&quot; are mandatory properties in each object in &quot;Keys&quot;.. + /// </summary> + internal static string RestoreSecurityDomainBadKey { + get { + return ResourceManager.GetString("RestoreSecurityDomainBadKey", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to There need to be at least {0} keys to decrypt security domain data.. + /// </summary> + internal static string RestoreSecurityDomainNotEnoughKey { + get { + return ResourceManager.GetString("RestoreSecurityDomainNotEnoughKey", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Set certificate attribute. /// </summary> diff --git a/src/KeyVault/KeyVault/Properties/Resources.resx b/src/KeyVault/KeyVault/Properties/Resources.resx index 4813eb2a1ff9..75c8f3e61600 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.resx +++ b/src/KeyVault/KeyVault/Properties/Resources.resx @@ -501,4 +501,13 @@ You can find the object ID using Azure Active Directory Module for Windows Power <data name="HsmCertRangeWarning" xml:space="preserve"> <value>To encrypt the security domain data, please provide at least {0} and at most {1} certificates.</value> </data> + <data name="LoadSecurityDomainFileFailed" xml:space="preserve"> + <value>Failed to load security domain data from {0}. Please make sure the file exists and is not modified.</value> + </data> + <data name="RestoreSecurityDomainBadKey" xml:space="preserve"> + <value>"PublicKey" and "PrivateKey" are mandatory properties in each object in "Keys".</value> + </data> + <data name="RestoreSecurityDomainNotEnoughKey" xml:space="preserve"> + <value>There need to be at least {0} keys to decrypt security domain data.</value> + </data> </root> \ No newline at end of file diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index 384d2d8441c3..f7a124cd717a 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -1,5 +1,4 @@ using Microsoft.Azure.Commands.Common.Authentication; -using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Azure.Commands.KeyVault.Properties; using System; using System.Linq; @@ -26,10 +25,10 @@ public class BackupSecurityDomain: SecurityDomainCmdlet public SwitchParameter PassThru { get; set; } [Parameter(HelpMessage = "The minimum number of shares required to decrypt the security domain for recovery.", Mandatory = true)] - [ValidateRange(2, 10)] + [ValidateRange(Common.Constants.MinQuorum, Common.Constants.MaxQuorum)] public int Quorum { get; set; } - public override void ExecuteCmdletCore() + public override void DoExecuteCmdlet() { ValidateParameters(); @@ -37,7 +36,7 @@ public override void ExecuteCmdletCore() if (ShouldProcess($"managed HSM {Name}", $"download encrypted security domain data to '{OutputPath}'")) { - var securityDomain = Client.DownloadSecurityDomainAsync(Name, certificates, 2).ConfigureAwait(false).GetAwaiter().GetResult(); // todo: remove required? + var securityDomain = Client.DownloadSecurityDomainAsync(Name, certificates, Quorum).ConfigureAwait(false).GetAwaiter().GetResult(); if (!AzureSession.Instance.DataStore.FileExists(OutputPath) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutputPath), Resources.FileOverwriteCaption)) { AzureSession.Instance.DataStore.WriteFile(OutputPath, securityDomain); @@ -52,9 +51,9 @@ public override void ExecuteCmdletCore() private void ValidateParameters() { - if (Certificates.Length < 3 || Certificates.Length > 10) + if (Certificates.Length < Common.Constants.MinCert || Certificates.Length > Common.Constants.MaxCert) { - throw new ArgumentException(string.Format(Resources.HsmCertRangeWarning, 3, 10)); // todo: resource string; check + throw new ArgumentException(string.Format(Resources.HsmCertRangeWarning, Common.Constants.MinCert, Common.Constants.MaxCert)); } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index f6502fad3326..8f35c2289c3e 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -1,11 +1,10 @@ -using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; +using Microsoft.Azure.Commands.KeyVault.Properties; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models; using Newtonsoft.Json; using System; -using System.Collections.Generic; using System.Linq; using System.Management.Automation; -using System.Text; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -26,12 +25,12 @@ public class RestoreSecurityDomain : SecurityDomainCmdlet [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] public SwitchParameter PassThru { get; set; } - public override void ExecuteCmdletCore() + public override void DoExecuteCmdlet() { ValidateParameters(); - if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file '{SecurityDomainPath}'")) + if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file \"{SecurityDomainPath}\"")) { - var securityDomainData = LoadFromFile(SecurityDomainPath); + var securityDomainData = LoadSdFromFile(SecurityDomainPath); var exchangeKey = Client.DownloadSecurityDomainExchangeKeyAsync(Name).ConfigureAwait(false).GetAwaiter().GetResult(); var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, securityDomainData, exchangeKey); if (Client.RestoreSecurityDomainAsync(Name, encryptedSecurityDomain).ConfigureAwait(false).GetAwaiter().GetResult()) @@ -48,28 +47,25 @@ private void ValidateParameters() { if (Keys.Length < 2) { - // todo: resource string - throw new ArgumentException(@"There need to be at least 2 keys to decrypt security domain data."); + throw new ArgumentException(string.Format(Resources.RestoreSecurityDomainNotEnoughKey, Common.Constants.MinQuorum)); } if (Keys.Any(key => string.IsNullOrEmpty(key.PublicKey) || string.IsNullOrEmpty(key.PrivateKey))) { - // todo: resource string - throw new ArgumentException(@"'PublicKey' and 'PrivateKey' are mandatory in each object in 'Keys'"); + throw new ArgumentException(Resources.RestoreSecurityDomainBadKey); } } - private SecurityDomainData LoadFromFile(string path) + private SecurityDomainData LoadSdFromFile(string path) { try { - string sec_domain = Utils.FileToString(path); - return JsonConvert.DeserializeObject<SecurityDomainData>(sec_domain); + string content = Utils.FileToString(path); + return JsonConvert.DeserializeObject<SecurityDomainData>(content); } - catch (Exception err) + catch (Exception ex) { - Console.WriteLine("Cannot load security domain from file"); - Console.WriteLine(err.Message); - return null; + throw new Exception( + string.Format(Resources.LoadSecurityDomainFileFailed, path), ex); } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs index d9437f4544f0..d2f6d3181d20 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -37,14 +37,18 @@ internal ISecurityDomainClient Client private ISecurityDomainClient _client; /// <summary> - /// Sealed for common logic of parameter set handling. Please override <see cref="ExecuteCmdletCore"/> instead. + /// Sub-classes should not override this method, but <see cref="DoExecuteCmdlet"/> instead. + /// This is call-super pattern. See https://www.martinfowler.com/bliki/CallSuper.html /// </summary> - public sealed override void ExecuteCmdlet() + public override void ExecuteCmdlet() { PreprocessParameterSets(); - ExecuteCmdletCore(); + DoExecuteCmdlet(); } + /// <summary> + /// Unifies different parameter sets. Sub-classes need only to care about Name. + /// </summary> private void PreprocessParameterSets() { if (this.IsParameterBound(c => c.InputObject)) @@ -53,6 +57,6 @@ private void PreprocessParameterSets() } } - public virtual void ExecuteCmdletCore() { } + public virtual void DoExecuteCmdlet() { } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Common/Constants.cs b/src/KeyVault/KeyVault/SecurityDomain/Common/Constants.cs new file mode 100644 index 000000000000..5ab6f44d4eeb --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Common/Constants.cs @@ -0,0 +1,10 @@ +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common +{ + internal static class Constants + { + public const int MinQuorum = 2; + public const int MaxQuorum = 10; + public const int MinCert = 3; + public const int MaxCert = 10; + } +} From c0766f2c71c270f0d541fec7ae4e6e2540a98194 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Thu, 24 Sep 2020 14:59:21 +0800 Subject: [PATCH 04/18] wip --- .../KeyVault/Models/DataServiceCredential.cs | 6 +- .../Cmdlets/BackupSecurityDomain.cs | 5 +- .../Cmdlets/RestoreSecurityDomain.cs | 13 +- .../Cmdlets/SecurityDomainCmdlet.cs | 7 +- .../KeyVault/SecurityDomain/Common/Utils.cs | 9 + .../SecurityDomain/Models/DownloadRequest.cs | 9 +- .../Models/SecurityDomainClient.cs | 157 ++++++++---------- 7 files changed, 102 insertions(+), 104 deletions(-) diff --git a/src/KeyVault/KeyVault/Models/DataServiceCredential.cs b/src/KeyVault/KeyVault/Models/DataServiceCredential.cs index e3cfe726e612..03805857c1e3 100644 --- a/src/KeyVault/KeyVault/Models/DataServiceCredential.cs +++ b/src/KeyVault/KeyVault/Models/DataServiceCredential.cs @@ -70,12 +70,12 @@ public Task<string> OnAuthentication(string authority, string resource, string s public string GetToken() { - return GetTokenInternal(this.TenantId, this._authenticationFactory, this._context, this._endpointName).Item1.AccessToken; + return GetAccessToken().AccessToken; } - public IAccessToken GetTokenTemp() // todo rename / refactor + public IAccessToken GetAccessToken() { - return GetTokenInternal(this.TenantId, this._authenticationFactory, this._context, this._endpointName).Item1; + return GetTokenInternal(TenantId, _authenticationFactory, _context, _endpointName).Item1; } private static string GetTenantId(IAzureContext context) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index f7a124cd717a..d209e2c75bd8 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Management.Automation; using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -28,7 +29,7 @@ public class BackupSecurityDomain: SecurityDomainCmdlet [ValidateRange(Common.Constants.MinQuorum, Common.Constants.MaxQuorum)] public int Quorum { get; set; } - public override void DoExecuteCmdlet() + public async override Task DoExecuteCmdletAsync() { ValidateParameters(); @@ -36,7 +37,7 @@ public override void DoExecuteCmdlet() if (ShouldProcess($"managed HSM {Name}", $"download encrypted security domain data to '{OutputPath}'")) { - var securityDomain = Client.DownloadSecurityDomainAsync(Name, certificates, Quorum).ConfigureAwait(false).GetAwaiter().GetResult(); + var securityDomain = await Client.DownloadSecurityDomainAsync(Name, certificates, Quorum); if (!AzureSession.Instance.DataStore.FileExists(OutputPath) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutputPath), Resources.FileOverwriteCaption)) { AzureSession.Instance.DataStore.WriteFile(OutputPath, securityDomain); diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index 8f35c2289c3e..e52acf2e2f7b 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Management.Automation; +using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -25,14 +26,14 @@ public class RestoreSecurityDomain : SecurityDomainCmdlet [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] public SwitchParameter PassThru { get; set; } - public override void DoExecuteCmdlet() + public override async Task DoExecuteCmdletAsync() { ValidateParameters(); if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file \"{SecurityDomainPath}\"")) { - var securityDomainData = LoadSdFromFile(SecurityDomainPath); - var exchangeKey = Client.DownloadSecurityDomainExchangeKeyAsync(Name).ConfigureAwait(false).GetAwaiter().GetResult(); - var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, securityDomainData, exchangeKey); + var securityDomainData = LoadSdFromFileAsync(SecurityDomainPath); + var exchangeKey = Client.DownloadSecurityDomainExchangeKeyAsync(Name); + var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, await securityDomainData, await exchangeKey); if (Client.RestoreSecurityDomainAsync(Name, encryptedSecurityDomain).ConfigureAwait(false).GetAwaiter().GetResult()) { if (PassThru) @@ -55,11 +56,11 @@ private void ValidateParameters() } } - private SecurityDomainData LoadSdFromFile(string path) + private async Task<SecurityDomainData> LoadSdFromFileAsync(string path) { try { - string content = Utils.FileToString(path); + string content = await Utils.FileToStringAsync(path); return JsonConvert.DeserializeObject<SecurityDomainData>(content); } catch (Exception ex) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs index d2f6d3181d20..60092ae71dbe 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -4,6 +4,7 @@ using Microsoft.Azure.Commands.ResourceManager.Common; using Microsoft.WindowsAzure.Commands.Utilities.Common; using System.Management.Automation; +using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -26,7 +27,7 @@ internal ISecurityDomainClient Client { if (_client == null) { - _client = new SecurityDomainClient(AzureSession.Instance.AuthenticationFactory, DefaultContext); + _client = new SecurityDomainClient(AzureSession.Instance.AuthenticationFactory, DefaultContext, s => WriteDebug(s)); } return _client; } @@ -43,7 +44,7 @@ internal ISecurityDomainClient Client public override void ExecuteCmdlet() { PreprocessParameterSets(); - DoExecuteCmdlet(); + DoExecuteCmdletAsync().ConfigureAwait(false).GetAwaiter().GetResult(); } /// <summary> @@ -57,6 +58,6 @@ private void PreprocessParameterSets() } } - public virtual void DoExecuteCmdlet() { } + public abstract Task DoExecuteCmdletAsync(); } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs b/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs index afacf9e77683..3f43d1b0c728 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs @@ -3,6 +3,7 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; +using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common { @@ -43,6 +44,14 @@ static public string FileToString(string path) return readContents; } + static public async Task<string> FileToStringAsync(string path) + { + using (StreamReader streamReader = new StreamReader(path, Encoding.ASCII)) + { + return await streamReader.ReadToEndAsync(); + } + } + static public void StringToFile(string path, string data) { using (StreamWriter streamWriter = new StreamWriter(path, false, Encoding.ASCII)) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs index 378990eda203..a7657a65db8d 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs @@ -7,10 +7,13 @@ class DownloadRequest { public DownloadRequest() { - certificates = new List<JWK>(); + Certificates = new List<JWK>(); } - public int required; // todo: rename to Required - public IList<JWK> certificates { get; set; } + [JsonProperty("required")] + public int Required; + + [JsonProperty("certificates")] + public IList<JWK> Certificates { get; set; } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index fe661076f06c..5b752ef899ad 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -1,22 +1,18 @@ -using Hyak.Common.TransientFaultHandling; -using Microsoft.Azure.Commands.Common.Authentication; -using Microsoft.Azure.Commands.Common.Authentication.Abstractions; +using Microsoft.Azure.Commands.Common.Authentication.Abstractions; using Microsoft.Azure.Commands.KeyVault.Models; +using Microsoft.Azure.Commands.KeyVault.Properties; using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; -using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models; using Microsoft.IdentityModel.Tokens; using Microsoft.Rest; using Microsoft.WindowsAzure.Commands.Utilities.Common; using Newtonsoft.Json; using System; using System.Collections.Generic; -using System.IO; -using System.Linq; using System.Net.Http; using System.Net.Http.Headers; +using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Threading; using System.Threading.Tasks; using static Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureEnvironment; @@ -24,102 +20,103 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models { internal class SecurityDomainClient : ServiceClient<SecurityDomainClient>, ISecurityDomainClient { - public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzureContext defaultContext) + public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzureContext defaultContext, Action<string> debugWriter) { - //_credentials = new DataServiceCredential(authenticationFactory, defaultContext, ExtendedEndpoint.ManagedHsmServiceEndpointResourceId).GetServiceClientCredentials(); _credentials = new DataServiceCredential(authenticationFactory, defaultContext, ExtendedEndpoint.ManagedHsmServiceEndpointResourceId); _uriHelper = new VaultUriHelper( defaultContext.Environment.GetEndpoint(AzureEnvironment.Endpoint.AzureKeyVaultDnsSuffix), defaultContext.Environment.GetEndpoint(ExtendedEndpoint.ManagedHsmServiceEndpointSuffix)); + + HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; + + _writeDebug = debugWriter; } + private const string _securityDomainPathFragment = "SecurityDomain"; private DataServiceCredential _credentials; private VaultUriHelper _uriHelper; - private JsonSerializerSettings _serializationSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + private readonly JsonSerializerSettings _serializationSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; + private readonly Action<string> _writeDebug; /// <summary> /// Download security domain data. /// </summary> /// <param name="hsmName">Name of the HSM</param> /// <param name="certificates">Certificates used to encrypt the security domain data</param> - /// <param name="required">Specify how many keys are required to decrypt the data</param> + /// <param name="quorum">Specify how many keys are required to decrypt the data</param> /// <returns>Encrypted HSM security domain data in string</returns> - public async Task<string> DownloadSecurityDomainAsync(string hsmName, IEnumerable<X509Certificate2> certificates, int required) + public async Task<string> DownloadSecurityDomainAsync(string hsmName, IEnumerable<X509Certificate2> certificates, int quorum) { - ValidateDownloadRequest(hsmName, certificates); - - try + var downloadRequest = new DownloadRequest { - var downloadRequest = new DownloadRequest - { - required = required - }; - certificates.ForEach(cert => downloadRequest.certificates.Add(new JWK(cert))); - - string requestBody = JsonConvert.SerializeObject( - downloadRequest, - Formatting.None, - _serializationSettings); - - HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; + Required = quorum + }; + certificates.ForEach(cert => downloadRequest.Certificates.Add(new JWK(cert))); - var httpRequest = new HttpRequestMessage - { - Method = HttpMethod.Post, - RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) - { - Path = "/securitydomain/download" - }.Uri, - Content = new StringContent(requestBody) - }; - httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + string requestBody = JsonConvert.SerializeObject( + downloadRequest, + Formatting.None, + _serializationSettings); - var token = _credentials.GetTokenTemp(); - token.AuthorizeRequest((tokenType, tokenValue) => + var httpRequest = new HttpRequestMessage + { + Method = HttpMethod.Post, + RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) { - httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); - }); + Path = $"/{_securityDomainPathFragment}/download" + }.Uri, + Content = new StringContent(requestBody) + }; - var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + PrepareRequest(httpRequest); - if (httpResponseMessage.IsSuccessStatusCode) - { - string response = await httpResponseMessage.Content.ReadAsStringAsync(); - SecurityDomainWrapper securityDomainWrapper = JsonConvert.DeserializeObject<SecurityDomainWrapper>(response); - if (string.IsNullOrEmpty(securityDomainWrapper.value)) - { - Console.WriteLine("Response from server invalid"); - return null; - } + var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); - if (!ValidateSecurityDomainData(securityDomainWrapper.value)) - { - Console.WriteLine("Unexpected security domain format"); - return null; - } - return securityDomainWrapper.value; - } - - return null; + if (httpResponseMessage.IsSuccessStatusCode) + { + string response = await httpResponseMessage.Content.ReadAsStringAsync(); + var securityDomainWrapper = JsonConvert.DeserializeObject<SecurityDomainWrapper>(response); + ValidateDownloadSecurityDomainResponse(securityDomainWrapper); + return securityDomainWrapper.value; } - catch (Exception err) + else { - Console.WriteLine($"RequestSecurityDomain failed = {err.Message}"); - Console.WriteLine(err); - return null; + string response = await httpResponseMessage.Content.ReadAsStringAsync(); + //_writeDebug($"Invalid security domain response: {response}"); + throw new Exception("Failed to download security domain data."); } } - private void ValidateDownloadRequest(string hsmName, IEnumerable<X509Certificate2> certificates) + private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securityDomainWrapper) { - if (certificates.Count() < 3) + if (string.IsNullOrEmpty(securityDomainWrapper.value) || !ValidateSecurityDomainData(securityDomainWrapper.value)) { - throw new ArgumentException("Must have at least three certificates"); + //_writeDebug($"Invalid security domain response: {securityDomainWrapper.value}"); + throw new Exception("Failed to download security domain data."); } - if (string.IsNullOrWhiteSpace(hsmName)) + } + + /// <summary> + /// Prepare common headers for the request. + /// Such as content-type and authorization. + /// </summary> + /// <param name="httpRequest"></param> + private void PrepareRequest(HttpRequestMessage httpRequest) + { + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + + try { - throw new ArgumentException(nameof(hsmName)); + var token = _credentials.GetAccessToken(); + token.AuthorizeRequest((tokenType, tokenValue) => + { + httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); + }); + } + catch (Exception ex) + { + throw new AuthenticationException(Resources.InvalidSubscriptionState, ex); } } @@ -163,22 +160,16 @@ public async Task<X509Certificate2> DownloadSecurityDomainExchangeKeyAsync(strin try { - HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; - var httpRequest = new HttpRequestMessage { Method = new HttpMethod("GET"), RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) { - Path = "/securitydomain/upload" + Path = $"/{_securityDomainPathFragment}/upload" }.Uri, }; - var token = _credentials.GetTokenTemp(); - token.AuthorizeRequest((tokenType, tokenValue) => - { - httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); - }); + PrepareRequest(httpRequest); HttpResponseMessage httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); @@ -430,25 +421,17 @@ public async Task<bool> RestoreSecurityDomainAsync(string hsmName, string securi try { - HttpClient.DefaultRequestHeaders.TransferEncodingChunked = false; - var httpRequest = new HttpRequestMessage { Method = HttpMethod.Post, RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) { - Path = "/securitydomain/upload" + Path = $"/{_securityDomainPathFragment}/upload" }.Uri, Content = new StringContent(securityDomain) }; - httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); - - var token = _credentials.GetTokenTemp(); - token.AuthorizeRequest((tokenType, tokenValue) => - { - httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue); - }); + PrepareRequest(httpRequest); var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); From b83c55c27cae1d53699dd2a37d17bca10b1a3cc2 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Thu, 24 Sep 2020 15:33:02 +0800 Subject: [PATCH 05/18] wip --- .../Cmdlets/BackupSecurityDomain.cs | 5 ++-- .../Cmdlets/RestoreSecurityDomain.cs | 15 ++++++------ .../Cmdlets/SecurityDomainCmdlet.cs | 5 ++-- .../KeyVault/SecurityDomain/Common/Utils.cs | 9 -------- .../Models/ISecurityDomainClient.cs | 7 +++--- .../Models/SecurityDomainClient.cs | 23 +++++++++---------- 6 files changed, 25 insertions(+), 39 deletions(-) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index d209e2c75bd8..9891a450a754 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Management.Automation; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -29,7 +28,7 @@ public class BackupSecurityDomain: SecurityDomainCmdlet [ValidateRange(Common.Constants.MinQuorum, Common.Constants.MaxQuorum)] public int Quorum { get; set; } - public async override Task DoExecuteCmdletAsync() + public override void DoExecuteCmdlet() { ValidateParameters(); @@ -37,7 +36,7 @@ public async override Task DoExecuteCmdletAsync() if (ShouldProcess($"managed HSM {Name}", $"download encrypted security domain data to '{OutputPath}'")) { - var securityDomain = await Client.DownloadSecurityDomainAsync(Name, certificates, Quorum); + var securityDomain = Client.DownloadSecurityDomain(Name, certificates, Quorum); if (!AzureSession.Instance.DataStore.FileExists(OutputPath) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutputPath), Resources.FileOverwriteCaption)) { AzureSession.Instance.DataStore.WriteFile(OutputPath, securityDomain); diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index e52acf2e2f7b..39acd464da08 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using System.Management.Automation; -using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -26,15 +25,15 @@ public class RestoreSecurityDomain : SecurityDomainCmdlet [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] public SwitchParameter PassThru { get; set; } - public override async Task DoExecuteCmdletAsync() + public override void DoExecuteCmdlet() { ValidateParameters(); if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file \"{SecurityDomainPath}\"")) { - var securityDomainData = LoadSdFromFileAsync(SecurityDomainPath); - var exchangeKey = Client.DownloadSecurityDomainExchangeKeyAsync(Name); - var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, await securityDomainData, await exchangeKey); - if (Client.RestoreSecurityDomainAsync(Name, encryptedSecurityDomain).ConfigureAwait(false).GetAwaiter().GetResult()) + var securityDomainData = LoadSdFromFile(SecurityDomainPath); + var exchangeKey = Client.DownloadSecurityDomainExchangeKey(Name); + var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, securityDomainData, exchangeKey); + if (Client.RestoreSecurityDomain(Name, encryptedSecurityDomain)) { if (PassThru) { @@ -56,11 +55,11 @@ private void ValidateParameters() } } - private async Task<SecurityDomainData> LoadSdFromFileAsync(string path) + private SecurityDomainData LoadSdFromFile(string path) { try { - string content = await Utils.FileToStringAsync(path); + string content = Utils.FileToString(path); return JsonConvert.DeserializeObject<SecurityDomainData>(content); } catch (Exception ex) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs index 60092ae71dbe..1950b7f9ee24 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -4,7 +4,6 @@ using Microsoft.Azure.Commands.ResourceManager.Common; using Microsoft.WindowsAzure.Commands.Utilities.Common; using System.Management.Automation; -using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { @@ -44,7 +43,7 @@ internal ISecurityDomainClient Client public override void ExecuteCmdlet() { PreprocessParameterSets(); - DoExecuteCmdletAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + DoExecuteCmdlet(); } /// <summary> @@ -58,6 +57,6 @@ private void PreprocessParameterSets() } } - public abstract Task DoExecuteCmdletAsync(); + public abstract void DoExecuteCmdlet(); } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs b/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs index 3f43d1b0c728..afacf9e77683 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Common/Utils.cs @@ -3,7 +3,6 @@ using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; -using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common { @@ -44,14 +43,6 @@ static public string FileToString(string path) return readContents; } - static public async Task<string> FileToStringAsync(string path) - { - using (StreamReader streamReader = new StreamReader(path, Encoding.ASCII)) - { - return await streamReader.ReadToEndAsync(); - } - } - static public void StringToFile(string path, string data) { using (StreamWriter streamWriter = new StreamWriter(path, false, Encoding.ASCII)) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs index 8aec5812d9c2..b80cbc6f3d91 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs @@ -1,17 +1,16 @@ using System.Collections.Generic; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models { public interface ISecurityDomainClient { - Task<string> DownloadSecurityDomainAsync(string hsmName, IEnumerable<X509Certificate2> certificates, int required); + string DownloadSecurityDomain(string hsmName, IEnumerable<X509Certificate2> certificates, int required); - Task<X509Certificate2> DownloadSecurityDomainExchangeKeyAsync(string hsmName); + X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName); string EncryptSecurityDomainByCert(KeyPath[] keys, SecurityDomainData data, X509Certificate2 restore_cert); - Task<bool> RestoreSecurityDomainAsync(string hsmName, string securityDomainData); + bool RestoreSecurityDomain(string hsmName, string securityDomainData); } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index 5b752ef899ad..de70f44996c3 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -13,7 +13,6 @@ using System.Security.Authentication; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Threading.Tasks; using static Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureEnvironment; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models @@ -33,7 +32,7 @@ public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzure _writeDebug = debugWriter; } - private const string _securityDomainPathFragment = "SecurityDomain"; + private const string _securityDomainPathFragment = "securitydomain"; private DataServiceCredential _credentials; private VaultUriHelper _uriHelper; private readonly JsonSerializerSettings _serializationSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; @@ -46,7 +45,7 @@ public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzure /// <param name="certificates">Certificates used to encrypt the security domain data</param> /// <param name="quorum">Specify how many keys are required to decrypt the data</param> /// <returns>Encrypted HSM security domain data in string</returns> - public async Task<string> DownloadSecurityDomainAsync(string hsmName, IEnumerable<X509Certificate2> certificates, int quorum) + public string DownloadSecurityDomain(string hsmName, IEnumerable<X509Certificate2> certificates, int quorum) { var downloadRequest = new DownloadRequest { @@ -71,18 +70,18 @@ public async Task<string> DownloadSecurityDomainAsync(string hsmName, IEnumerabl PrepareRequest(httpRequest); - var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + var httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult(); if (httpResponseMessage.IsSuccessStatusCode) { - string response = await httpResponseMessage.Content.ReadAsStringAsync(); + string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); var securityDomainWrapper = JsonConvert.DeserializeObject<SecurityDomainWrapper>(response); ValidateDownloadSecurityDomainResponse(securityDomainWrapper); return securityDomainWrapper.value; } else { - string response = await httpResponseMessage.Content.ReadAsStringAsync(); + string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); //_writeDebug($"Invalid security domain response: {response}"); throw new Exception("Failed to download security domain data."); } @@ -151,7 +150,7 @@ private bool ValidateSecurityDomainData(string securityDomainData) return valid; } - public async Task<X509Certificate2> DownloadSecurityDomainExchangeKeyAsync(string hsmName) + public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) { if (string.IsNullOrWhiteSpace(hsmName)) { @@ -171,11 +170,11 @@ public async Task<X509Certificate2> DownloadSecurityDomainExchangeKeyAsync(strin PrepareRequest(httpRequest); - HttpResponseMessage httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + HttpResponseMessage httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult(); if (httpResponseMessage.IsSuccessStatusCode) { - var response = await httpResponseMessage.Content.ReadAsStringAsync(); + var response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); var key = JsonConvert.DeserializeObject<SecurityDomainTransferKey>(response); switch (key.KeyFormat) @@ -404,7 +403,7 @@ private SecurityDomainRestoreData EncryptForRestore(X509Certificate2 cert, Plain return securityDomainRestoreData; } - public async Task<bool> RestoreSecurityDomainAsync(string hsmName, string securityDomainData) + public bool RestoreSecurityDomain(string hsmName, string securityDomainData) { if (string.IsNullOrWhiteSpace(hsmName)) { @@ -433,11 +432,11 @@ public async Task<bool> RestoreSecurityDomainAsync(string hsmName, string securi PrepareRequest(httpRequest); - var httpResponseMessage = await HttpClient.SendAsync(httpRequest).ConfigureAwait(false); + var httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult(); if (httpResponseMessage.IsSuccessStatusCode) { - return !string.IsNullOrEmpty(await httpResponseMessage.Content.ReadAsStringAsync()); + return !string.IsNullOrEmpty(httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()); } return false; From fe1fa7aea283394da5cd6c4301be10e4dac48f29 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Thu, 24 Sep 2020 17:23:28 +0800 Subject: [PATCH 06/18] wip --- .../Cmdlets/RestoreSecurityDomain.cs | 16 +++---- .../Models/ISecurityDomainClient.cs | 6 ++- .../Models/SecurityDomainClient.cs | 42 ++++--------------- .../test/security-domain.ps1 | 42 +++++++++++++++++++ src/KeyVault/SecurityDomain.Test/test/test.md | 37 ++++++++++++++++ 5 files changed, 98 insertions(+), 45 deletions(-) create mode 100644 src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 create mode 100644 src/KeyVault/SecurityDomain.Test/test/test.md diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index 39acd464da08..ad90f23b9d2a 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -12,7 +12,7 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets [OutputType(typeof(bool))] public class RestoreSecurityDomain : SecurityDomainCmdlet { - [Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data.", Mandatory = true)] + [Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data. See examples for how it is constructed.", Mandatory = true)] public KeyPath[] Keys { get; set; } [Parameter(HelpMessage = "Specify the path to the encrypted security domain data.", Mandatory = true)] @@ -30,15 +30,15 @@ public override void DoExecuteCmdlet() ValidateParameters(); if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file \"{SecurityDomainPath}\"")) { - var securityDomainData = LoadSdFromFile(SecurityDomainPath); + var securityDomain = LoadSdFromFile(SecurityDomainPath); + var rawSecurityDomain = Client.DecryptSecurityDomain(securityDomain, Keys); var exchangeKey = Client.DownloadSecurityDomainExchangeKey(Name); - var encryptedSecurityDomain = Client.EncryptSecurityDomainByCert(Keys, securityDomainData, exchangeKey); - if (Client.RestoreSecurityDomain(Name, encryptedSecurityDomain)) + var encryptedSecurityDomain = Client.EncryptForRestore(rawSecurityDomain, exchangeKey); + Client.RestoreSecurityDomain(Name, encryptedSecurityDomain); + + if (PassThru) { - if (PassThru) - { - WriteObject(true); - } + WriteObject(true); } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs index b80cbc6f3d91..e4eed28e8aae 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs @@ -9,8 +9,10 @@ public interface ISecurityDomainClient X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName); - string EncryptSecurityDomainByCert(KeyPath[] keys, SecurityDomainData data, X509Certificate2 restore_cert); + PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] paths); - bool RestoreSecurityDomain(string hsmName, string securityDomainData); + SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert); + + bool RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData); } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index de70f44996c3..efe2100792a1 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -82,7 +82,7 @@ public string DownloadSecurityDomain(string hsmName, IEnumerable<X509Certificate else { string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); - //_writeDebug($"Invalid security domain response: {response}"); + _writeDebug($"Invalid security domain response: {response}"); throw new Exception("Failed to download security domain data."); } } @@ -91,7 +91,7 @@ private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securi { if (string.IsNullOrEmpty(securityDomainWrapper.value) || !ValidateSecurityDomainData(securityDomainWrapper.value)) { - //_writeDebug($"Invalid security domain response: {securityDomainWrapper.value}"); + _writeDebug($"Invalid security domain response: {securityDomainWrapper.value}"); throw new Exception("Failed to download security domain data."); } } @@ -204,35 +204,7 @@ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) } } - public string EncryptSecurityDomainByCert(KeyPath[] keys, SecurityDomainData data, X509Certificate2 restore_cert) - { - try - { - string restore_data = GetSecurityDomainRestore(restore_cert, data, keys); - - if (restore_data == null) - { - Console.WriteLine("Unable to create security domain restore"); - return null; - } - - return restore_data; - } - catch (Exception err) - { - Console.WriteLine("Unable to decrypt security domain " + err.Message); - return null; - } - } - - private string GetSecurityDomainRestore(X509Certificate2 restoreCert, SecurityDomainData data, KeyPath[] keys) - { - PlaintextList plaintextList = Decrypt(data, keys); - SecurityDomainRestoreData restoreData = EncryptForRestore(restoreCert, plaintextList); - return JsonConvert.SerializeObject(restoreData); - } - - private PlaintextList Decrypt(SecurityDomainData data, KeyPath[] paths) + public PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] paths) { CertKeys certKeys = new CertKeys(); certKeys.LoadKeys(paths); @@ -375,7 +347,7 @@ private byte[] DecryptMasterKey(KeyPair decode_key_pair, CertKey certKey1, CertK return master_key; } - private SecurityDomainRestoreData EncryptForRestore(X509Certificate2 cert, PlaintextList plaintextList) + public SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert) { SecurityDomainRestoreData securityDomainRestoreData = new SecurityDomainRestoreData(); securityDomainRestoreData.EncData.kdf = "sp108_kdf"; @@ -403,19 +375,19 @@ private SecurityDomainRestoreData EncryptForRestore(X509Certificate2 cert, Plain return securityDomainRestoreData; } - public bool RestoreSecurityDomain(string hsmName, string securityDomainData) + public bool RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData) { if (string.IsNullOrWhiteSpace(hsmName)) { throw new ArgumentException(nameof(hsmName)); } - if (string.IsNullOrEmpty(securityDomainData)) + if (securityDomainData == null) throw new ArgumentNullException(nameof(securityDomainData)); string securityDomain = JsonConvert.SerializeObject(new SecurityDomainWrapper { - value = securityDomainData + value = JsonConvert.SerializeObject(securityDomainData) }); try diff --git a/src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 b/src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 new file mode 100644 index 000000000000..fc85c426c7ba --- /dev/null +++ b/src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 @@ -0,0 +1,42 @@ +# Invoke-Pester @{ path = '.\src\KeyVault\SecurityDomain.Test\test\security-domain.ps1'; Parameters = @{sdPath = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json"}} -TestName 'Backup' + +param( + $rgName, + $hsmName, + $hsm2Name, + $sdPath +) + +function RandomName { + param ($prefix) + return $prefix + (Get-Random -Minimum 0 -Maximum 1000) +} + +$location = 'eastus2euap' +$rgName ??= RandomName 'rg' +$hsmName ??= RandomName 'hsm' +$hsm2Name = RandomName 'hsm' +$certificates = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer", "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer", "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer" +$sdPath ??= "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json1" + +BeforeAll { + New-AzResourceGroup -Name $rgName -Location $location + New-AzKeyVault -Hsm ResourceGroupName $rgName -Name $hsmName -Location $location -Administrator 'd7e17135-d5a7-4b8b-89e5-252aa15b7e01', 'c1be1392-39b8-4521-aafc-819a47008545' +} + +Describe "Backup" { + It "Should backup successfully" { + # $sdPath | Should -Not -Exist + + # Backup-AzManagedHsmSecurityDomain -Name $hsmName -Certificates $certificates -OutputPath $sdPath -Quorum 2 + + $sdPath | Should -Exist + Get-Content $sdPath | ConvertFrom-Json | Should -Not -BeNullOrEmpty + } +} + +Describe 'Restore' { + It 'Should restore correctly' { + Restore-AzManagedHsmSecurityDomain + } +} \ No newline at end of file diff --git a/src/KeyVault/SecurityDomain.Test/test/test.md b/src/KeyVault/SecurityDomain.Test/test/test.md new file mode 100644 index 000000000000..3b4b88d58fad --- /dev/null +++ b/src/KeyVault/SecurityDomain.Test/test/test.md @@ -0,0 +1,37 @@ +openssl req -x509 -newkey rsa:2048 -keyout sd1.key -out sd1.cer -days 365 -nodes +openssl req -x509 -newkey rsa:2048 -keyout sd2.key -out sd2.cer -days 365 -nodes +openssl req -x509 -newkey rsa:2048 -keyout sd3.key -out sd3.cer -days 365 -nodes + +openssl req -x509 -newkey rsa:2048 -keyout sd1.key -out sd1.cer -days 365 +openssl req -x509 -newkey rsa:2048 -keyout sd2.key -out sd2.cer -days 365 +openssl req -x509 -newkey rsa:2048 -keyout sd3.key -out sd3.cer -days 365 + +<!-- backup --> +Backup-AzManagedHsmSecurityDomain -Name yemingmhsm07 -Certificates C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer -OutputPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\2test\sd.ps.json -Quorum 2 + +az keyvault security-domain download --sd-quorum 2 --sd-wrapping-keys "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer" --security-domain-file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.cli.json" --hsm-name yemingmhsm03 + + +<!-- new & backup key --> +az keyvault key create --hsm-name yemingmhsm06 --name rsa +az keyvault key backup --hsm-name yemingmhsm06 --name rsa --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" +az keyvault key delete --hsm-name yemingmhsm06 --name rsa +az keyvault key purge --hsm-name yemingmhsm06 --name rsa +az keyvault key show --hsm-name yemingmhsm06 --name rsa +az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" +az keyvault key show --hsm-name yemingmhsm06 --name rsa + +<!-- remove --> +remove-azkeyvault -Hsm -ResourceGroupName yemingmhsm -Name yemingmhsm06 -Force + +<!-- new --> +new-azkeyvault -Hsm -Name yemingmhsm08 -ResourceGroupName yemingmhsm -Location eastus2euap -Administrator 'd7e17135-d5a7-4b8b-89e5-252aa15b7e01','c1be1392-39b8-4521-aafc-819a47008545' + +<!-- restore key: fail --> +az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" + +<!-- restore --> +Restore-AzManagedHsmSecurityDomain -Name yemingmhsm06 -PrivateKeyPathTemp C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\ -SecurityDomainPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json + +<!-- restore key: success --> +az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" \ No newline at end of file From b4cc391bda29569cad2474c723edb9a37b524b31 Mon Sep 17 00:00:00 2001 From: Yeming Liu <yeliu@microsoft.com> Date: Fri, 25 Sep 2020 10:38:51 +0800 Subject: [PATCH 07/18] support securestring --- .../KeyVault/Helpers/UtilityExtensions.cs | 23 +++++++++++++++++++ .../Cmdlets/BackupSecurityDomain.cs | 2 +- .../KeyVault/SecurityDomain/Models/CertKey.cs | 2 +- .../KeyVault/SecurityDomain/Models/KeyPath.cs | 6 ++--- .../Track2Models/Track2VaultClient.cs | 2 -- src/KeyVault/SecurityDomain.Test/test/test.md | 8 +++++-- 6 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 src/KeyVault/KeyVault/Helpers/UtilityExtensions.cs diff --git a/src/KeyVault/KeyVault/Helpers/UtilityExtensions.cs b/src/KeyVault/KeyVault/Helpers/UtilityExtensions.cs new file mode 100644 index 000000000000..0ac4c25ac649 --- /dev/null +++ b/src/KeyVault/KeyVault/Helpers/UtilityExtensions.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; +using System.Security; + +namespace Microsoft.Azure.Commands.KeyVault +{ + internal static class UtilityExtensions + { + public static string ToPlainText(this SecureString secureString) + { + IntPtr bstr = Marshal.SecureStringToBSTR(secureString); + + try + { + return Marshal.PtrToStringBSTR(bstr); + } + finally + { + Marshal.FreeBSTR(bstr); + } + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index 9891a450a754..06c71a374352 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -40,7 +40,7 @@ public override void DoExecuteCmdlet() if (!AzureSession.Instance.DataStore.FileExists(OutputPath) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutputPath), Resources.FileOverwriteCaption)) { AzureSession.Instance.DataStore.WriteFile(OutputPath, securityDomain); - WriteVerbose($"Security domain data of managed HSM '{Name}' downloaded to '{OutputPath}'."); + WriteDebug($"Security domain data of managed HSM '{Name}' downloaded to '{OutputPath}'."); if (PassThru) { WriteObject(true); diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs index 82dcc27386e5..4e4b2b3dec23 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs @@ -15,7 +15,7 @@ public bool Load(KeyPath path) try { cert = new X509Certificate2(path.PublicKey); - RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password); + RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password.ToPlainText()); key = RSA.Create(); key.ImportParameters(parameters); thumbprint = Utils.Sha256Thumbprint(cert); diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs index 6eed2da9fb94..6788e095f8f9 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System.Security; namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models { @@ -8,6 +6,6 @@ public class KeyPath { public string PublicKey; public string PrivateKey; - public string Password; + public SecureString Password; } } diff --git a/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs b/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs index 9cd484037069..9b9b94fd2cd1 100644 --- a/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs +++ b/src/KeyVault/KeyVault/Track2Models/Track2VaultClient.cs @@ -3,8 +3,6 @@ using Microsoft.Azure.Commands.KeyVault.Models; using System; using System.Collections; -using System.Collections.Generic; -using System.Text; namespace Microsoft.Azure.Commands.KeyVault.Track2Models { diff --git a/src/KeyVault/SecurityDomain.Test/test/test.md b/src/KeyVault/SecurityDomain.Test/test/test.md index 3b4b88d58fad..ee86e7cbbda6 100644 --- a/src/KeyVault/SecurityDomain.Test/test/test.md +++ b/src/KeyVault/SecurityDomain.Test/test/test.md @@ -7,7 +7,7 @@ openssl req -x509 -newkey rsa:2048 -keyout sd2.key -out sd2.cer -days 365 openssl req -x509 -newkey rsa:2048 -keyout sd3.key -out sd3.cer -days 365 <!-- backup --> -Backup-AzManagedHsmSecurityDomain -Name yemingmhsm07 -Certificates C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer -OutputPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\2test\sd.ps.json -Quorum 2 +Backup-AzManagedHsmSecurityDomain -Name yemingmhsm08 -Certificates C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer -OutputPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json -Quorum 2 az keyvault security-domain download --sd-quorum 2 --sd-wrapping-keys "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer" --security-domain-file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.cli.json" --hsm-name yemingmhsm03 @@ -31,7 +31,11 @@ new-azkeyvault -Hsm -Name yemingmhsm08 -ResourceGroupName yemingmhsm -Location e az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" <!-- restore --> -Restore-AzManagedHsmSecurityDomain -Name yemingmhsm06 -PrivateKeyPathTemp C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\ -SecurityDomainPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json +$keys = @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.key"}, +@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.key"}, +@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.key"} + +Restore-AzManagedHsmSecurityDomain -Name yemingmhsm09 -Keys $keys -SecurityDomainPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json <!-- restore key: success --> az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" \ No newline at end of file From 3babad22d6ce708024927f89fb5baa6766a44acc Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Fri, 25 Sep 2020 14:21:59 +0800 Subject: [PATCH 08/18] wip --- .../KeyVault/SecurityDomain/Models/CertKey.cs | 2 +- .../SecurityDomain/Models/SecurityDomainClient.cs | 4 +++- src/KeyVault/SecurityDomain.Test/test/test.md | 12 ++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs index 4e4b2b3dec23..95ef05c4b7df 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs @@ -15,7 +15,7 @@ public bool Load(KeyPath path) try { cert = new X509Certificate2(path.PublicKey); - RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password.ToPlainText()); + RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password?.ToPlainText()); key = RSA.Create(); key.ImportParameters(parameters); thumbprint = Utils.Sha256Thumbprint(cert); diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index efe2100792a1..e3897bf6272a 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -103,7 +103,9 @@ private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securi /// <param name="httpRequest"></param> private void PrepareRequest(HttpRequestMessage httpRequest) { - httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + if (httpRequest.Content != null) { + httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); + } try { diff --git a/src/KeyVault/SecurityDomain.Test/test/test.md b/src/KeyVault/SecurityDomain.Test/test/test.md index ee86e7cbbda6..2a9538964e93 100644 --- a/src/KeyVault/SecurityDomain.Test/test/test.md +++ b/src/KeyVault/SecurityDomain.Test/test/test.md @@ -7,7 +7,7 @@ openssl req -x509 -newkey rsa:2048 -keyout sd2.key -out sd2.cer -days 365 openssl req -x509 -newkey rsa:2048 -keyout sd3.key -out sd3.cer -days 365 <!-- backup --> -Backup-AzManagedHsmSecurityDomain -Name yemingmhsm08 -Certificates C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer -OutputPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json -Quorum 2 +Backup-AzManagedHsmSecurityDomain -Name yemingmhsm09 -Certificates C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer -OutputPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json -Quorum 2 az keyvault security-domain download --sd-quorum 2 --sd-wrapping-keys "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer" --security-domain-file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.cli.json" --hsm-name yemingmhsm03 @@ -25,7 +25,7 @@ az keyvault key show --hsm-name yemingmhsm06 --name rsa remove-azkeyvault -Hsm -ResourceGroupName yemingmhsm -Name yemingmhsm06 -Force <!-- new --> -new-azkeyvault -Hsm -Name yemingmhsm08 -ResourceGroupName yemingmhsm -Location eastus2euap -Administrator 'd7e17135-d5a7-4b8b-89e5-252aa15b7e01','c1be1392-39b8-4521-aafc-819a47008545' +new-azkeyvault -Hsm -Name yemingmhsm10 -ResourceGroupName yemingmhsm -Location eastus2euap -Administrator 'd7e17135-d5a7-4b8b-89e5-252aa15b7e01','c1be1392-39b8-4521-aafc-819a47008545' <!-- restore key: fail --> az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" @@ -35,7 +35,11 @@ $keys = @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\Securit @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.key"}, @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.key"} -Restore-AzManagedHsmSecurityDomain -Name yemingmhsm09 -Keys $keys -SecurityDomainPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json +Restore-AzManagedHsmSecurityDomain -Name yemingmhsm10 -Keys $keys -SecurityDomainPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json <!-- restore key: success --> -az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" \ No newline at end of file +az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" + + +manual: 10 +powershell: 09 \ No newline at end of file From b17aa2c38eb969884c341084cf516806cb4b1d7f Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Fri, 25 Sep 2020 16:22:59 +0800 Subject: [PATCH 09/18] wip --- .../KeyVault/Properties/Resources.Designer.cs | 27 ++++++++ .../KeyVault/Properties/Resources.resx | 9 +++ .../Cmdlets/RestoreSecurityDomain.cs | 4 ++ .../KeyVault/SecurityDomain/Models/CertKey.cs | 53 +++++++--------- .../SecurityDomain/Models/CertKeys.cs | 28 ++++----- .../Models/ISecurityDomainClient.cs | 2 +- .../KeyVault/SecurityDomain/Models/KeyPath.cs | 6 +- .../Models/SecurityDomainClient.cs | 61 ++++++++----------- 8 files changed, 105 insertions(+), 85 deletions(-) diff --git a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs index 16f3bf803655..fb23089fa363 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs +++ b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs @@ -306,6 +306,24 @@ internal static string CreateKeyVault { } } + /// <summary> + /// 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.. + /// </summary> + internal static string DecryptSecurityDomainFailure { + get { + return ResourceManager.GetString("DecryptSecurityDomainFailure", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Not enough keys to decrypt security domain backup. {0} required, {0} provided.. + /// </summary> + internal static string DecryptSecurityDomainKeyNotEnough { + get { + return ResourceManager.GetString("DecryptSecurityDomainKeyNotEnough", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Cannot find deleted vault &apos;{0}&apos; in location &apos;{1}&apos;. /// </summary> @@ -1053,6 +1071,15 @@ internal static string RestoreSecurityDomainBadKey { } } + /// <summary> + /// Looks up a localized string similar to Failed to restore security domain data.. + /// </summary> + internal static string RestoreSecurityDomainFailure { + get { + return ResourceManager.GetString("RestoreSecurityDomainFailure", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to There need to be at least {0} keys to decrypt security domain data.. /// </summary> diff --git a/src/KeyVault/KeyVault/Properties/Resources.resx b/src/KeyVault/KeyVault/Properties/Resources.resx index 75c8f3e61600..d2d85fd707f1 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.resx +++ b/src/KeyVault/KeyVault/Properties/Resources.resx @@ -510,4 +510,13 @@ You can find the object ID using Azure Active Directory Module for Windows Power <data name="RestoreSecurityDomainNotEnoughKey" xml:space="preserve"> <value>There need to be at least {0} keys to decrypt security domain data.</value> </data> + <data name="DecryptSecurityDomainFailure" xml:space="preserve"> + <value>Failed to decrypt security domain data. Please make sure the file is not modified and the keys / passwords are correct.</value> + </data> + <data name="DecryptSecurityDomainKeyNotEnough" xml:space="preserve"> + <value>Not enough keys to decrypt security domain backup. {0} required, {0} provided.</value> + </data> + <data name="RestoreSecurityDomainFailure" xml:space="preserve"> + <value>Failed to restore security domain data.</value> + </data> </root> \ No newline at end of file diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index ad90f23b9d2a..954a92a936bb 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -60,6 +60,10 @@ private SecurityDomainData LoadSdFromFile(string path) try { string content = Utils.FileToString(path); + if (string.IsNullOrWhiteSpace(content)) + { + throw new ArgumentException(nameof(SecurityDomainPath)); + } return JsonConvert.DeserializeObject<SecurityDomainData>(content); } catch (Exception ex) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs index 95ef05c4b7df..e32ec92b308e 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs @@ -10,27 +10,18 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models { internal class CertKey { - public bool Load(KeyPath path) + public void Load(KeyPath path) { - try - { - cert = new X509Certificate2(path.PublicKey); - RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password?.ToPlainText()); - key = RSA.Create(); - key.ImportParameters(parameters); - thumbprint = Utils.Sha256Thumbprint(cert); - } - catch (Exception e) - { - Console.WriteLine(e.Message); - return false; - } - return true; + _cert = new X509Certificate2(path.PublicKey); + RSAParameters parameters = RsaParamsFromPem(path.PrivateKey, path.Password?.ToPlainText()); + _key = RSA.Create(); + _key.ImportParameters(parameters); + _thumbprint = Utils.Sha256Thumbprint(_cert); } - public byte[] get_thumbprint() { return thumbprint; } - public RSA get_key() { return key; } - public X509Certificate2 get_cert() { return cert; } + public byte[] GetThumbprint() { return _thumbprint; } + public RSA GetKey() { return _key; } + public X509Certificate2 GetCert() { return _cert; } static RSAParameters RsaParamsFromPem(string path, string password) { @@ -45,11 +36,13 @@ static RSAParameters RsaParamsFromPem(string path, string password) static RSAParameters ToRSAParameters(RsaPrivateCrtKeyParameters privKey) { - RSAParameters rp = new RSAParameters(); - rp.Modulus = privKey.Modulus.ToByteArrayUnsigned(); - rp.Exponent = privKey.PublicExponent.ToByteArrayUnsigned(); - rp.P = privKey.P.ToByteArrayUnsigned(); - rp.Q = privKey.Q.ToByteArrayUnsigned(); + RSAParameters rp = new RSAParameters + { + Modulus = privKey.Modulus.ToByteArrayUnsigned(), + Exponent = privKey.PublicExponent.ToByteArrayUnsigned(), + P = privKey.P.ToByteArrayUnsigned(), + Q = privKey.Q.ToByteArrayUnsigned() + }; rp.D = ConvertRSAParametersField(privKey.Exponent, rp.Modulus.Length); rp.DP = ConvertRSAParametersField(privKey.DP, rp.P.Length); rp.DQ = ConvertRSAParametersField(privKey.DQ, rp.Q.Length); @@ -70,22 +63,22 @@ static byte[] ConvertRSAParametersField(Org.BouncyCastle.Math.BigInteger n, int return padded; } - X509Certificate2 cert; - RSA key; - byte[] thumbprint; + X509Certificate2 _cert; + RSA _key; + byte[] _thumbprint; private class PasswordFinder : IPasswordFinder { - private string v; + private readonly string _password; - public PasswordFinder(string v) + public PasswordFinder(string password) { - this.v = v; + _password = password; } public char[] GetPassword() { - return v.ToCharArray(); + return _password.ToCharArray(); } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs index 1a8c281da9aa..eae5de3268ec 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs @@ -8,41 +8,39 @@ internal class CertKeys { public CertKeys() { - keyValuePairs = new Dictionary<string, CertKey>(); + _keys = new Dictionary<string, CertKey>(); } public void LoadKeys(KeyPath[] paths) { foreach (var path in paths) { - if (!LoadKey(path)) - Console.WriteLine("Could not load cert and key from " + path); + try { LoadKey(path); } + catch (Exception ex) + { + throw new Exception($"Could not load public and private key from {path.PublicKey} and {path.PrivateKey}", ex); + } } } - public bool LoadKey(KeyPath path) + public void LoadKey(KeyPath path) { CertKey certKey = new CertKey(); - - if (!certKey.Load(path)) - return false; - - string encoded_string = Base64UrlEncoder.Encode(certKey.get_thumbprint()); - keyValuePairs.Add(encoded_string, certKey); - return true; + certKey.Load(path); + string encodedThumbprint = Base64UrlEncoder.Encode(certKey.GetThumbprint()); + _keys.Add(encodedThumbprint, certKey); } public CertKey Find(string encoded_thumbprint) { - CertKey certKey = null; - if (!keyValuePairs.TryGetValue(encoded_thumbprint, out certKey)) + if (!_keys.TryGetValue(encoded_thumbprint, out CertKey certKey)) return null; return certKey; } - public int Count() { return keyValuePairs.Count; } + public int Count() { return _keys.Count; } - Dictionary<string, CertKey> keyValuePairs; + private readonly Dictionary<string, CertKey> _keys; } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs index e4eed28e8aae..64fb66d9eaf4 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs @@ -13,6 +13,6 @@ public interface ISecurityDomainClient SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert); - bool RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData); + void RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData); } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs index 6788e095f8f9..a43220311db2 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs @@ -4,8 +4,8 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models { public class KeyPath { - public string PublicKey; - public string PrivateKey; - public SecureString Password; + public string PublicKey { get; } + public string PrivateKey { get; } + public SecureString Password { get; } } } diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index e3897bf6272a..1cc6c236ed99 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -33,8 +33,8 @@ public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzure } private const string _securityDomainPathFragment = "securitydomain"; - private DataServiceCredential _credentials; - private VaultUriHelper _uriHelper; + private readonly DataServiceCredential _credentials; + private readonly VaultUriHelper _uriHelper; private readonly JsonSerializerSettings _serializationSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }; private readonly Action<string> _writeDebug; @@ -209,30 +209,25 @@ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) public PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] paths) { CertKeys certKeys = new CertKeys(); - certKeys.LoadKeys(paths); - - if (certKeys.Count() < 2) + try { - Console.WriteLine("Cannot load two certificates and keys"); - return null; + certKeys.LoadKeys(paths); + return Decrypt(data, certKeys); + } catch (Exception ex) + { + throw new Exception(Resources.DecryptSecurityDomainFailure, ex); } - - return Decrypt(data, certKeys); } // Internal worker function private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) { - if (data == null || - certKeys.Count() < 2 || - (data.version == 2 && certKeys.Count() < data.SharedKeys.required)) + if (data.version == 2 && certKeys.Count() < data.SharedKeys.required) { - Console.WriteLine("Invalid arguments"); - return null; + throw new ArgumentException(string.Format(Resources.DecryptSecurityDomainKeyNotEnough, data.SharedKeys.required, certKeys.Count())); } - byte[] master_key = null; - + byte[] master_key; if (data.version == 1) { // ensure that the key splitting algorithm @@ -288,7 +283,7 @@ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) if (cert_key != null) { JWE jwe = new JWE(key.enc_key); - byte[] share = jwe.Decrypt(cert_key.get_key()); + byte[] share = jwe.Decrypt(cert_key.GetKey()); shares_found++; share_arrays.Add(Utils.ConvertToUint16(share)); @@ -334,10 +329,10 @@ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) private byte[] DecryptMasterKey(KeyPair decode_key_pair, CertKey certKey1, CertKey certKey2) { JWE jwe1 = new JWE(decode_key_pair.key1.enc_key); - byte[] xor_key = jwe1.Decrypt(certKey1.get_key()); + byte[] xor_key = jwe1.Decrypt(certKey1.GetKey()); JWE jwe2 = new JWE(decode_key_pair.key2.enc_key); - byte[] derived_key = jwe2.Decrypt(certKey2.get_key()); + byte[] derived_key = jwe2.Decrypt(certKey2.GetKey()); // Now, XOR to get the master key back byte[] master_key = new byte[xor_key.Length]; @@ -377,16 +372,8 @@ public SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, return securityDomainRestoreData; } - public bool RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData) + public void RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData) { - if (string.IsNullOrWhiteSpace(hsmName)) - { - throw new ArgumentException(nameof(hsmName)); - } - - if (securityDomainData == null) - throw new ArgumentNullException(nameof(securityDomainData)); - string securityDomain = JsonConvert.SerializeObject(new SecurityDomainWrapper { value = JsonConvert.SerializeObject(securityDomainData) @@ -407,19 +394,21 @@ public bool RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData secu PrepareRequest(httpRequest); var httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult(); - + var responseBody = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); if (httpResponseMessage.IsSuccessStatusCode) { - return !string.IsNullOrEmpty(httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult()); + if (string.IsNullOrEmpty(responseBody)) + { + throw new Exception("Got empty response when restoring security domain."); + } + } else + { + throw new Exception($"Got {httpResponseMessage.StatusCode}, {responseBody}"); } - - return false; } - catch (Exception err) + catch (Exception ex) { - Console.WriteLine($"RequestSecurityDomain failed = {err.Message}"); - Console.WriteLine(err); - return false; + throw new Exception(Resources.RestoreSecurityDomainFailure, ex); } } } From ad6451ecee3345a36a1d475a2dfcd4530cf926f0 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Sun, 27 Sep 2020 17:11:24 +0800 Subject: [PATCH 10/18] wip --- .../Cmdlets/BackupSecurityDomain.cs | 1 + .../Cmdlets/RestoreSecurityDomain.cs | 2 ++ .../Cmdlets/SecurityDomainCmdlet.cs | 2 ++ .../Models/SecurityDomainClient.cs | 36 +++++++++++++++---- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index 06c71a374352..7ef61691d714 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -16,6 +16,7 @@ public class BackupSecurityDomain: SecurityDomainCmdlet public string[] Certificates { get; set; } [Parameter(HelpMessage = "Specify the path where security domain data will be downloaded to.", Mandatory = true)] + [ValidateNotNullOrEmpty] public string OutputPath { get; set; } [Parameter(HelpMessage = "Specify whether to overwrite existing file.")] diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index 954a92a936bb..0170f98797b2 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -13,10 +13,12 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets public class RestoreSecurityDomain : SecurityDomainCmdlet { [Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data. See examples for how it is constructed.", Mandatory = true)] + [ValidateNotNullOrEmpty] public KeyPath[] Keys { get; set; } [Parameter(HelpMessage = "Specify the path to the encrypted security domain data.", Mandatory = true)] [Alias("Path")] + [ValidateNotNullOrEmpty] public string SecurityDomainPath { get; set; } [Parameter(HelpMessage = "Specify whether to overwrite existing file.")] diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs index 1950b7f9ee24..8e7750fe9cfb 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -15,9 +15,11 @@ public abstract class SecurityDomainCmdlet: AzureRMCmdlet [Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = ByName)] [Alias("HsmName")] + [ValidateNotNullOrEmpty] public string Name { get; set; } [Parameter(HelpMessage = "Object representing a managed HSM.", Mandatory = true, ParameterSetName = ByInputObject, ValueFromPipeline = true)] + [ValidateNotNull] public PSKeyVaultIdentityItem InputObject { get; set; } internal ISecurityDomainClient Client diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index 1cc6c236ed99..73ef7ed3d5a6 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -39,7 +39,8 @@ public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzure private readonly Action<string> _writeDebug; /// <summary> - /// Download security domain data. + /// Download security domain data for restore. + /// Data is encrypted with the certificates (public keys) user passes in. /// </summary> /// <param name="hsmName">Name of the HSM</param> /// <param name="certificates">Certificates used to encrypt the security domain data</param> @@ -83,6 +84,7 @@ public string DownloadSecurityDomain(string hsmName, IEnumerable<X509Certificate { string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); _writeDebug($"Invalid security domain response: {response}"); + // todo : resource string throw new Exception("Failed to download security domain data."); } } @@ -92,7 +94,7 @@ private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securi if (string.IsNullOrEmpty(securityDomainWrapper.value) || !ValidateSecurityDomainData(securityDomainWrapper.value)) { _writeDebug($"Invalid security domain response: {securityDomainWrapper.value}"); - throw new Exception("Failed to download security domain data."); + throw new Exception("Failed to download security domain data."); // todo: resource string } } @@ -152,13 +154,14 @@ private bool ValidateSecurityDomainData(string securityDomainData) return valid; } + /// <summary> + /// Download a security domain exchange key. + /// This key is used to encrypt SD data before uploading to the HSM where SD is going to be restored. + /// </summary> + /// <param name="hsmName"></param> + /// <returns></returns> public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) { - if (string.IsNullOrWhiteSpace(hsmName)) - { - throw new ArgumentException(nameof(hsmName)); - } - try { var httpRequest = new HttpRequestMessage @@ -206,6 +209,14 @@ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) } } + /// <summary> + /// Decrypt security domain data. + /// User must specify public key / private key / password* groups to decrypt SD. + /// *password MAY be optional. + /// </summary> + /// <param name="data"></param> + /// <param name="paths"></param> + /// <returns></returns> public PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] paths) { CertKeys certKeys = new CertKeys(); @@ -344,6 +355,12 @@ private byte[] DecryptMasterKey(KeyPair decode_key_pair, CertKey certKey1, CertK return master_key; } + /// <summary> + /// Encrypt SD data with exchange key. + /// </summary> + /// <param name="plaintextList"></param> + /// <param name="cert">Exchange key</param> + /// <returns></returns> public SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert) { SecurityDomainRestoreData securityDomainRestoreData = new SecurityDomainRestoreData(); @@ -372,6 +389,11 @@ public SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, return securityDomainRestoreData; } + /// <summary> + /// Upload security domain data and initiate restoring. + /// </summary> + /// <param name="hsmName"></param> + /// <param name="securityDomainData">Encrypted by exchange key</param> public void RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData) { string securityDomain = JsonConvert.SerializeObject(new SecurityDomainWrapper From aca55e1cb69a61d8d22d1f605f72581e800e20c5 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Mon, 28 Sep 2020 15:51:45 +0800 Subject: [PATCH 11/18] generate docs --- src/KeyVault/KeyVault/help/Az.KeyVault.md | 6 + .../help/Backup-AzManagedHsmSecurityDomain.md | 207 ++++++++++++++++++ .../Restore-AzManagedHsmSecurityDomain.md | 192 ++++++++++++++++ 3 files changed, 405 insertions(+) create mode 100644 src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md create mode 100644 src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md diff --git a/src/KeyVault/KeyVault/help/Az.KeyVault.md b/src/KeyVault/KeyVault/help/Az.KeyVault.md index c2e5eaf06ef3..5a6b0ae2a343 100644 --- a/src/KeyVault/KeyVault/help/Az.KeyVault.md +++ b/src/KeyVault/KeyVault/help/Az.KeyVault.md @@ -38,6 +38,9 @@ Backs up a KeyVault-managed storage account. ### [Backup-AzKeyVaultSecret](Backup-AzKeyVaultSecret.md) Backs up a secret in a key vault. +### [Backup-AzManagedHsmSecurityDomain](Backup-AzManagedHsmSecurityDomain.md) +Backs up the security domain data of a managed HSM for restoring. + ### [Get-AzKeyVault](Get-AzKeyVault.md) Gets key vaults. @@ -131,6 +134,9 @@ Restores a managed storage account in a key vault from a backup file. ### [Restore-AzKeyVaultSecret](Restore-AzKeyVaultSecret.md) Creates a secret in a key vault from a backed-up secret. +### [Restore-AzManagedHsmSecurityDomain](Restore-AzManagedHsmSecurityDomain.md) +Restores previous backed up security domain data to a managed HSM. + ### [Set-AzKeyVaultAccessPolicy](Set-AzKeyVaultAccessPolicy.md) Grants or modifies existing permissions for a user, application, or security group to perform operations with a key vault. diff --git a/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md new file mode 100644 index 000000000000..e34ec601074c --- /dev/null +++ b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md @@ -0,0 +1,207 @@ +--- +external help file: Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll-Help.xml +Module Name: Az.KeyVault +online version: +schema: 2.0.0 +--- + +# Backup-AzManagedHsmSecurityDomain + +## SYNOPSIS +Backs up the security domain data of a managed HSM for restoring. + +## SYNTAX + +### By Name (Default) +``` +Backup-AzManagedHsmSecurityDomain -Certificates <String[]> -OutputPath <String> [-Force] [-PassThru] + -Quorum <Int32> -Name <String> [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] + [<CommonParameters>] +``` + +### By InputObject +``` +Backup-AzManagedHsmSecurityDomain -Certificates <String[]> -OutputPath <String> [-Force] [-PassThru] + -Quorum <Int32> -InputObject <PSKeyVaultIdentityItem> [-DefaultProfile <IAzureContextContainer>] [-WhatIf] + [-Confirm] [<CommonParameters>] +``` + +## DESCRIPTION +This cmdlet backs up the security domain data of a managed HSM for restoring. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> {{ Add example code here }} +``` + +{{ Add example description here }} + +## PARAMETERS + +### -Certificates +Paths to the certificates that are used to encrypt the security domain data. + +```yaml +Type: System.String[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -DefaultProfile +The credentials, account, tenant, and subscription used for communication with Azure. + +```yaml +Type: Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer +Parameter Sets: (All) +Aliases: AzContext, AzureRmContext, AzureCredential + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +Specify whether to overwrite existing file. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject +Object representing a managed HSM. + +```yaml +Type: Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultIdentityItem +Parameter Sets: By InputObject +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Name +Name of the managed HSM. + +```yaml +Type: System.String +Parameter Sets: By Name +Aliases: HsmName + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -OutputPath +Specify the path where security domain data will be downloaded to. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +When specified, a boolean will be returned when cmdlet succeeds. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Quorum +The minimum number of shares required to decrypt the security domain for recovery. + +```yaml +Type: System.Int32 +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultIdentityItem + +## OUTPUTS + +### System.Boolean + +## NOTES + +## RELATED LINKS diff --git a/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md new file mode 100644 index 000000000000..7057bdee96f0 --- /dev/null +++ b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md @@ -0,0 +1,192 @@ +--- +external help file: Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll-Help.xml +Module Name: Az.KeyVault +online version: +schema: 2.0.0 +--- + +# Restore-AzManagedHsmSecurityDomain + +## SYNOPSIS +Restores previous backed up security domain data to a managed HSM. + +## SYNTAX + +### By Name (Default) +``` +Restore-AzManagedHsmSecurityDomain -Keys <KeyPath[]> -SecurityDomainPath <String> [-Force] [-PassThru] + -Name <String> [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>] +``` + +### By InputObject +``` +Restore-AzManagedHsmSecurityDomain -Keys <KeyPath[]> -SecurityDomainPath <String> [-Force] [-PassThru] + -InputObject <PSKeyVaultIdentityItem> [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] + [<CommonParameters>] +``` + +## DESCRIPTION +This cmdlet restores previous backed up security domain data to a managed HSM. + +## EXAMPLES + +### Example 1 +```powershell +PS C:\> {{ Add example code here }} +``` + +{{ Add example description here }} + +## PARAMETERS + +### -DefaultProfile +The credentials, account, tenant, and subscription used for communication with Azure. + +```yaml +Type: Microsoft.Azure.Commands.Common.Authentication.Abstractions.Core.IAzureContextContainer +Parameter Sets: (All) +Aliases: AzContext, AzureRmContext, AzureCredential + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Force +Specify whether to overwrite existing file. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -InputObject +Object representing a managed HSM. + +```yaml +Type: Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultIdentityItem +Parameter Sets: By InputObject +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: True (ByValue) +Accept wildcard characters: False +``` + +### -Keys +Information about the keys that are used to decrypt the security domain data. +See examples for how it is constructed. + +```yaml +Type: Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models.KeyPath[] +Parameter Sets: (All) +Aliases: + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Name +Name of the managed HSM. + +```yaml +Type: System.String +Parameter Sets: By Name +Aliases: HsmName + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -PassThru +When specified, a boolean will be returned when cmdlet succeeds. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -SecurityDomainPath +Specify the path to the encrypted security domain data. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: Path + +Required: True +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -Confirm +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.Azure.Commands.KeyVault.Models.PSKeyVaultIdentityItem + +## OUTPUTS + +### System.Boolean + +## NOTES + +## RELATED LINKS From 78c6d3981c6db7aec0a97f9340a6503ef01669cb Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Tue, 29 Sep 2020 11:15:28 +0800 Subject: [PATCH 12/18] docs & error handling --- .../Models/SecurityDomainClient.cs | 112 ++++++++++-------- .../help/Add-AzKeyVaultNetworkRule.md | 2 +- src/KeyVault/KeyVault/help/New-AzKeyVault.md | 2 +- .../KeyVault/help/Remove-AzKeyVault.md | 1 + 4 files changed, 64 insertions(+), 53 deletions(-) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index 73ef7ed3d5a6..66385e368876 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -105,7 +105,8 @@ private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securi /// <param name="httpRequest"></param> private void PrepareRequest(HttpRequestMessage httpRequest) { - if (httpRequest.Content != null) { + if (httpRequest.Content != null) + { httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8"); } @@ -166,7 +167,7 @@ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) { var httpRequest = new HttpRequestMessage { - Method = new HttpMethod("GET"), + Method = HttpMethod.Get, RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName)) { Path = $"/{_securityDomainPathFragment}/upload" @@ -189,23 +190,27 @@ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) return Utils.CertficateFromPem(key.TransferKey); case "jwk": // handle below - break; + break; default: - return null; + throw new Exception($"Unexpected key type {key.KeyFormat}"); } // The transfer key is a JWK, need to parse it, and return the cert JWK jwk = JsonConvert.DeserializeObject<JWK>(key.TransferKey); return Utils.CertficateFromPem(jwk.GetX5cAsPem()); } + else + { + string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + _writeDebug($"Invalid security domain response: {response}"); + // todo : resource string + throw new Exception("Failed to download security domain exchange key."); + } - return null; } - catch (Exception err) + catch (Exception ex) { - Console.WriteLine($"DownloadSecurityDomainTransferKey failed = {err.Message}"); - Console.WriteLine(err); - return null; + throw new Exception("Failed to download security domain exchange key.", ex); } } @@ -224,7 +229,8 @@ public PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] pa { certKeys.LoadKeys(paths); return Decrypt(data, certKeys); - } catch (Exception ex) + } + catch (Exception ex) { throw new Exception(Resources.DecryptSecurityDomainFailure, ex); } @@ -238,50 +244,47 @@ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) throw new ArgumentException(string.Format(Resources.DecryptSecurityDomainKeyNotEnough, data.SharedKeys.required, certKeys.Count())); } - byte[] master_key; + byte[] masterKey; if (data.version == 1) { // ensure that the key splitting algorithm // is known, currently only one we know about if (data.SplitKeys.key_algorithm != "xor_split") { - Console.WriteLine("Unknown SplitKey algorithm"); - return null; + throw new Exception($"Unknown SplitKey algorithm {data.SplitKeys.key_algorithm}."); } - KeyPair decode_key_pair = null; + KeyPair decodeKeyPair = null; CertKey certKey1 = null; CertKey certKey2 = null; - foreach (KeyPair key_pair in data.SplitKeys.keys) + foreach (KeyPair keyPair in data.SplitKeys.keys) { - certKey1 = certKeys.Find(key_pair.key1.x5t_256); + certKey1 = certKeys.Find(keyPair.key1.x5t_256); if (certKey1 == null) continue; - certKey2 = certKeys.Find(key_pair.key2.x5t_256); + certKey2 = certKeys.Find(keyPair.key2.x5t_256); if (certKey2 != null) { - decode_key_pair = key_pair; + decodeKeyPair = keyPair; break; } } - if (decode_key_pair == null) + if (decodeKeyPair == null) { - Console.WriteLine("Cannot find matching certs and keys for security domain"); - return null; + throw new Exception("Cannot find matching certs and keys for security domain"); } - master_key = DecryptMasterKey(decode_key_pair, certKey1, certKey2); + masterKey = DecryptMasterKey(decodeKeyPair, certKey1, certKey2); } else if (data.version == 2) { if (data.SharedKeys.key_algorithm != "shamir_share") { - Console.WriteLine("Unknown SharedKeys algorithm"); - return null; + throw new Exception($"Unknown SharedKeys algorithm {data.SharedKeys.key_algorithm}"); } UInt32 shares_found = 0; @@ -306,17 +309,15 @@ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) if (share_arrays.Count < data.SharedKeys.required) { - Console.WriteLine("Insufficient shares available"); - return null; + throw new Exception($"Insufficient shares available. {data.SharedKeys.required} required, got {share_arrays.Count}."); } shamir_share_net.shared_secret secret = new shamir_share_net.shared_secret((UInt16)data.SharedKeys.required); - master_key = secret.get_secret(share_arrays); + masterKey = secret.get_secret(share_arrays); } else { - Console.WriteLine("Unknown domain version"); - return null; + throw new Exception($"Unknown domain version {data.version}."); } PlaintextList plaintextList = new PlaintextList(); @@ -326,7 +327,7 @@ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) { Plaintext p = new Plaintext(); HMACSHA512 hmac = new HMACSHA512(); - byte[] enc_key = KDF.sp800_108(master_key, enc_data.tag, "", hmac, 512); + byte[] enc_key = KDF.sp800_108(masterKey, enc_data.tag, "", hmac, 512); JWE jwe_data = new JWE(enc_data.compact_jwe); p.plaintext = jwe_data.Decrypt(enc_key); p.tag = enc_data.tag; @@ -363,30 +364,38 @@ private byte[] DecryptMasterKey(KeyPair decode_key_pair, CertKey certKey1, CertK /// <returns></returns> public SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert) { - SecurityDomainRestoreData securityDomainRestoreData = new SecurityDomainRestoreData(); - securityDomainRestoreData.EncData.kdf = "sp108_kdf"; + try + { + SecurityDomainRestoreData securityDomainRestoreData = new SecurityDomainRestoreData(); + securityDomainRestoreData.EncData.kdf = "sp108_kdf"; - byte[] master_key = Utils.GetRandom(32); + byte[] master_key = Utils.GetRandom(32); - foreach (Plaintext p in plaintextList.list) - { - Datum datum = new Datum(); - HMACSHA512 hmac = new HMACSHA512(); - byte[] enc_key = KDF.sp800_108(master_key, p.tag, "", hmac, 512); + foreach (Plaintext p in plaintextList.list) + { + Datum datum = new Datum(); + HMACSHA512 hmac = new HMACSHA512(); + byte[] enc_key = KDF.sp800_108(master_key, p.tag, "", hmac, 512); + + datum.tag = p.tag; + JWE jwe = new JWE(); + jwe.Encrypt(enc_key, p.plaintext, "A256CBC-HS512", p.tag); + datum.compact_jwe = jwe.EncodeCompact(); + securityDomainRestoreData.EncData.data.Add(datum); + } - datum.tag = p.tag; - JWE jwe = new JWE(); - jwe.Encrypt(enc_key, p.plaintext, "A256CBC-HS512", p.tag); - datum.compact_jwe = jwe.EncodeCompact(); - securityDomainRestoreData.EncData.data.Add(datum); + // Now go make the wrapped key + JWE jwe_wrapped = new JWE(); + jwe_wrapped.Encrypt(cert, master_key); + securityDomainRestoreData.WrappedKey.enc_key = jwe_wrapped.EncodeCompact(); + securityDomainRestoreData.WrappedKey.x5t_256 = Base64UrlEncoder.Encode(Utils.Sha256Thumbprint(cert)); + return securityDomainRestoreData; } - - // Now go make the wrapped key - JWE jwe_wrapped = new JWE(); - jwe_wrapped.Encrypt(cert, master_key); - securityDomainRestoreData.WrappedKey.enc_key = jwe_wrapped.EncodeCompact(); - securityDomainRestoreData.WrappedKey.x5t_256 = Base64UrlEncoder.Encode(Utils.Sha256Thumbprint(cert)); - return securityDomainRestoreData; + catch (Exception ex) + { + throw new Exception("Failed to encrypt security domain data for restoring.", ex); + } + } /// <summary> @@ -423,7 +432,8 @@ public void RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData secu { throw new Exception("Got empty response when restoring security domain."); } - } else + } + else { throw new Exception($"Got {httpResponseMessage.StatusCode}, {responseBody}"); } diff --git a/src/KeyVault/KeyVault/help/Add-AzKeyVaultNetworkRule.md b/src/KeyVault/KeyVault/help/Add-AzKeyVaultNetworkRule.md index 1e315bdaec09..6c1d7806c035 100644 --- a/src/KeyVault/KeyVault/help/Add-AzKeyVaultNetworkRule.md +++ b/src/KeyVault/KeyVault/help/Add-AzKeyVaultNetworkRule.md @@ -36,7 +36,7 @@ Add-AzKeyVaultNetworkRule [-ResourceId] <String> [-IpAddressRange <String[]>] ## DESCRIPTION The **Add-AzKeyVaultNetworkRule** cmdlet grants or restricts access to a key vault to a set of caller designated by their IP addresses or the virtual network to which they belong. The rule has the potential to restrict access for other users, applications, or security groups which have been granted permissions via the access policy. -Please note that any IP range inside `10.0.0.0–10.255.255.255` (private IP addresses) cannot be used to add network rules. +Please note that any IP range inside `10.0.0.0-10.255.255.255` (private IP addresses) cannot be used to add network rules. ## EXAMPLES diff --git a/src/KeyVault/KeyVault/help/New-AzKeyVault.md b/src/KeyVault/KeyVault/help/New-AzKeyVault.md index 6b7c3b9ffc84..cc998cb32045 100644 --- a/src/KeyVault/KeyVault/help/New-AzKeyVault.md +++ b/src/KeyVault/KeyVault/help/New-AzKeyVault.md @@ -13,7 +13,7 @@ Creates a key vault. ## SYNTAX -### KeyVaultParameterSet +### KeyVaultParameterSet (Default) ``` New-AzKeyVault [-Name] <String> [-ResourceGroupName] <String> [-Location] <String> [-DisableSoftDelete] [-EnablePurgeProtection] [-SoftDeleteRetentionInDays <Int32>] [-Sku <String>] [-Tag <Hashtable>] diff --git a/src/KeyVault/KeyVault/help/Remove-AzKeyVault.md b/src/KeyVault/KeyVault/help/Remove-AzKeyVault.md index a6e64beac041..4aa64a5c6a41 100644 --- a/src/KeyVault/KeyVault/help/Remove-AzKeyVault.md +++ b/src/KeyVault/KeyVault/help/Remove-AzKeyVault.md @@ -81,6 +81,7 @@ PS C:\> Remove-AzKeyVault -Name "testManagedHsm" -Hsm -PassThru True ``` + This command removes the managed hsm named testManagedHsm from your current subscription. ## PARAMETERS From 8dd83b118c702969df4d55d0effc7bef12bc5b62 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Tue, 29 Sep 2020 11:20:00 +0800 Subject: [PATCH 13/18] move crypto alg inside security domain --- src/KeyVault/KeyVault.sln | 6 - src/KeyVault/KeyVault/KeyVault.csproj | 4 - .../SecurityDomain/Crypto/mod_math.cs | 184 ++++++++++++++++++ .../SecurityDomain/Crypto/shared_secret.cs | 130 +++++++++++++ .../SecurityDomain/Crypto/test/test.cs | 167 ++++++++++++++++ .../Models/SecurityDomainClient.cs | 3 +- 6 files changed, 483 insertions(+), 11 deletions(-) create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Crypto/mod_math.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Crypto/shared_secret.cs create mode 100644 src/KeyVault/KeyVault/SecurityDomain/Crypto/test/test.cs diff --git a/src/KeyVault/KeyVault.sln b/src/KeyVault/KeyVault.sln index 77e9557f07eb..57928c74fc7c 100644 --- a/src/KeyVault/KeyVault.sln +++ b/src/KeyVault/KeyVault.sln @@ -20,8 +20,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFx", "..\..\tools\TestF EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecurityDomain.Test", "SecurityDomain.Test\SecurityDomain.Test.csproj", "{FDEE9611-2887-4933-AF88-B4EC782B2096}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "shamir_share_net", "shamir_share\shamir_share_net.csproj", "{035DF85F-5BB9-464B-94F6-2F9502A29807}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,10 +58,6 @@ Global {FDEE9611-2887-4933-AF88-B4EC782B2096}.Debug|Any CPU.Build.0 = Debug|Any CPU {FDEE9611-2887-4933-AF88-B4EC782B2096}.Release|Any CPU.ActiveCfg = Release|Any CPU {FDEE9611-2887-4933-AF88-B4EC782B2096}.Release|Any CPU.Build.0 = Release|Any CPU - {035DF85F-5BB9-464B-94F6-2F9502A29807}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {035DF85F-5BB9-464B-94F6-2F9502A29807}.Debug|Any CPU.Build.0 = Debug|Any CPU - {035DF85F-5BB9-464B-94F6-2F9502A29807}.Release|Any CPU.ActiveCfg = Release|Any CPU - {035DF85F-5BB9-464B-94F6-2F9502A29807}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/KeyVault/KeyVault/KeyVault.csproj b/src/KeyVault/KeyVault/KeyVault.csproj index ac30802e9e14..556c14e346eb 100644 --- a/src/KeyVault/KeyVault/KeyVault.csproj +++ b/src/KeyVault/KeyVault/KeyVault.csproj @@ -20,10 +20,6 @@ <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="5.1.2" /> </ItemGroup> - <ItemGroup> - <ProjectReference Include="..\shamir_share\shamir_share_net.csproj" /> - </ItemGroup> - <ItemGroup> <Compile Update="Properties\Resources.Designer.cs"> <DesignTime>True</DesignTime> diff --git a/src/KeyVault/KeyVault/SecurityDomain/Crypto/mod_math.cs b/src/KeyVault/KeyVault/SecurityDomain/Crypto/mod_math.cs new file mode 100644 index 000000000000..54f30c87206f --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Crypto/mod_math.cs @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Crypto +{ + class mod_math + { + public static UInt32 mod_invert(UInt32 x) + { + UInt32 ret = x; + + for (UInt32 i = 0; i < 7; ++i) + { + ret = mod_multiply(ret, ret); + ret = mod_multiply(ret, x); + } + + return ret; + } + + public static UInt32 mod_reduce(UInt32 x) + { + // Function to find x % 257 without side channels + UInt32 t = (x & 0xff) - (x >> 8); + t += (UInt32)((Int32)t >> 31) & 257; + return t; + } + + // Assumes a, b are within 0-256 + public static UInt32 mod_multiply(UInt32 a, UInt32 b) + { + return mod_reduce(a * b); + } + + public static UInt32 mod_add(UInt32 a, UInt32 b) + { + return mod_reduce(a + b); + } + + public static UInt32 mod_subtract(UInt32 a, UInt32 b) + { + // Must ensure that the difference is in the range of 0-256 + return mod_reduce(a - b + 257); + } + } + + class random_bits + { + public UInt16 get_mod_257() + { + UInt16 tmp = 0; + do + { + tmp = get_word(); + + if (tmp != 0) + return (UInt16)mod_math.mod_reduce(tmp); + + } while (tmp == 0); + + // Not actually reached + return 0; + } + + public UInt16 get_word() + { + Int32 remaining = random_bytes.Length - (Int32)current; + + if (remaining < 2) + load(); + + UInt16 ret = (UInt16)((random_bytes[current+1] << 8) | random_bytes[current]); + current += 2; + return ret; + } + + void load() + { + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + current = 0; + rng.GetBytes(random_bytes); + } + } + + public random_bits(UInt32 _chunk_size) + { + chunk_size = _chunk_size; + current = 0; + random_bytes = new byte[chunk_size]; + load(); + } + + UInt32 chunk_size; + UInt32 current; + byte[] random_bytes; + } + + struct share + { + public share(UInt16 w) + { + x = (UInt16)(w >> 9); + value = (UInt16)(w & 0x1ff); + } + + public share(UInt16 _x, UInt16 _value) + { + x = _x; + value = _value; + } + + public UInt16 to_uint16() + { + return (UInt16)(x << 9 | value); + } + + public UInt16 x; + public UInt16 value; + } + class shared_math + { + public static UInt16 get_secret(UInt16[] shares, UInt32 size) + { + UInt32 secret = 0; + + // Calculate numerator + for (UInt32 i = 0; i<size; ++i) + { + UInt32 numerator = 1; + UInt32 denominator = 1; + share si = new share(shares[i]); + + for (UInt32 j = 0; j<size; ++j) + { + if (i == j) + continue; + + share sj = new share(shares[j]); + numerator = mod_math.mod_multiply(numerator, sj.x); + UInt32 diff = mod_math.mod_subtract(sj.x, si.x); + denominator = mod_math.mod_multiply(diff, denominator); + } + + UInt32 invert = mod_math.mod_invert(denominator); + UInt32 ci = mod_math.mod_multiply(numerator, invert); + UInt32 tmp = mod_math.mod_multiply(ci, si.value); + secret = mod_math.mod_add(secret, tmp); + } + + return (UInt16)(secret); + } + + static public UInt16 make_share(UInt16[] coefficients, UInt16 x) + { + /* + When you evaluate + a*x^3 + b*x^2 + c*x + d + you compute + ((a*x + b)*x + c)*x + d + Also known as Horner’s rule. + */ + + if (coefficients.Length < 2) + { + throw new Exception("Invalid input"); + } + + UInt32 tmp = 0; + tmp = mod_math.mod_multiply(coefficients[0], x ); + tmp = mod_math.mod_add(tmp, coefficients[1] ); + + for (UInt32 i = 2; i < coefficients.Length; ++i) + { + tmp = mod_math.mod_multiply(tmp, x ); + tmp = mod_math.mod_add(tmp, coefficients[i] ); + } + + return (UInt16)(tmp ); + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Crypto/shared_secret.cs b/src/KeyVault/KeyVault/SecurityDomain/Crypto/shared_secret.cs new file mode 100644 index 000000000000..45201ad1786c --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Crypto/shared_secret.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Crypto +{ + public class shared_secret + { + public shared_secret(UInt16 shares, UInt16 required) + { + if (shares > max_shares || required > shares || required < 2) + throw new Exception("Incorrect share or required count"); + + _shares = shares; + _required = required; + _coefficients = new UInt16[required]; + _rand_bits = new random_bits(64); + + } + + public shared_secret(UInt16 required) + { + if (required < 2) + throw new Exception("Incorrect share or required count"); + + _shares = 0; + _required = required; + _rand_bits = new random_bits(64); + } + + public List<UInt16[]> make_shares(byte[] plaintext) + { + // Output will have size of share count, each share vector will have an entry for every input byte + List<UInt16[]> share_arrays = new List<UInt16[]>(); + + for( UInt32 i = 0; i < plaintext.Length; ++i) + { + byte p = plaintext[i]; + UInt16[] share_array = make_shares(p); + + /* + We now have a share created for the total number of shares needed + Each share then needs to be distributed such that there's a share + for each byte of plaintext, effectively transposing the 2-dimensional + array. + */ + Int32 share_count = share_array.Length; + + for (Int32 j = 0; j < share_count; ++j) + { + if (i == 0) + share_arrays.Add(new UInt16[plaintext.Length]); + + UInt16[] current_share_array = share_arrays[j]; + current_share_array[i] = share_array[j]; + } + } + + return share_arrays; + } + + public UInt16[] make_shares(byte secret_byte) + { + UInt16[] share_array = new UInt16[_shares]; + + init_coefficients(); + _coefficients[(UInt32)(_required) - 1] = secret_byte; + + UInt16 x = 1; + for (UInt32 i = 0; i < _shares; ++i, ++x) + { + share s = new share(x, shared_math.make_share(_coefficients, x)); + share_array[i] = s.to_uint16(); + } + + return share_array; + } + + public byte[] get_secret(List<UInt16[]> share_arrays) + { + byte[] plaintext = new byte[share_arrays[0].Length]; + + if (share_arrays.Count < _required) + { + throw new Exception("Insufficient shares"); + } + + UInt16[] sv = new UInt16[_required]; + + // TODO - all the constants calculated in get_secret + // can be pulled out once and re-used, which will help perf + for (UInt32 j = 0; j < plaintext.Length; ++j) + { + for (Int32 i = 0; i< _required; ++i) + { + UInt16[] sa = share_arrays[i]; + sv[i] = sa[j]; + } + + UInt16 text = get_secret(sv); + plaintext[j] = (byte)(text); + } + + return plaintext; + } + + public UInt16 get_secret(UInt16[] share_array) + { + if (share_array.Length < _required) + throw new Exception("Insufficient shares"); + + return shared_math.get_secret(share_array, _required); + } + + void init_coefficients() + { + for (UInt32 i = 0; i < (UInt32)(_required) - 1; ++i) + { + _coefficients[i] = _rand_bits.get_mod_257(); + } + } + + UInt16 _shares; + UInt16 _required; + UInt16[] _coefficients; + random_bits _rand_bits; + + const UInt32 max_shares = 126; + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Crypto/test/test.cs b/src/KeyVault/KeyVault/SecurityDomain/Crypto/test/test.cs new file mode 100644 index 000000000000..af0156689d95 --- /dev/null +++ b/src/KeyVault/KeyVault/SecurityDomain/Crypto/test/test.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; + +namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Crypto.Test +{ + class test + { + static void single_byte_test(byte shares, UInt16 required) + { + for (UInt16 i = 0; i < (UInt16)0x100; ++i) + { + shared_secret secret = new shared_secret(shares, required); + + UInt16[] share_array = secret.make_shares((byte)i); + UInt16 result = secret.get_secret(share_array); + + if (i != result) + { + throw new Exception("single_byte_test failed"); + } + } + } + + static void random_single_byte_test() + { + byte shares = 126; + UInt16 required = 16; + + for (UInt32 i = 0; i < 100000; ++i) + { + // just use i % 256 as the secret + byte secret_value = (byte)(i % 256); + shared_secret secret = new shared_secret(shares, required); + UInt16[] share_array = secret.make_shares(secret_value); + + // Put them into a List so that we can pick them out + List<UInt16> tmp_array = new List<UInt16>(); + + foreach(UInt16 u in share_array) + { + tmp_array.Add(u); + } + + // Now need to randomly pick values + UInt16[] random_shares = new UInt16[required]; + + random_bits bits = new random_bits(required); + + for (UInt32 j = 0; j < required; ++j) + { + // Yes, I really only need a byte, but this is test code + UInt16 r = bits.get_word(); + Int32 pos = (Int32)(r % tmp_array.Count); + + random_shares[j] = tmp_array[pos]; + tmp_array.RemoveAt(pos); + } + + UInt16 result = secret.get_secret(random_shares); + + if (result != secret_value) + { + throw new Exception("random_single_byte_test failed"); + } + } + + Console.WriteLine("random_single_byte_test - success"); + } + + static void test_all_shares() + { + for (UInt16 i = 2; i < 127; ++i) + { + // It will work for larger numbers of required, but + // it takes a while. Can do some targeted tests for large + // share count + UInt16 max_required = 16; + for (UInt16 j = i > max_required ? max_required : i; j > 1; --j) + { + byte shares = (byte)(i); + byte required = (byte)(j); + + single_byte_test(shares, required); + } + } + + Console.WriteLine("test_all_shares - success"); + } + + static void test_126_shares() + { + UInt16 i = 126; + + Console.WriteLine("Running 126 share test"); + + for (UInt16 j = i; j > 1; --j) + { + byte shares = (byte)(i); + byte required = (byte)(j); + + single_byte_test(shares, required); + } + + Console.WriteLine("test_126_shares - success"); + } + + static bool check_result(byte[] a, byte[] b) + { + // Not for cryptographic purposes + // assumes equal lengths + for(Int32 i = 0; i < a.Length; ++i) + { + if (a[i] != b[i]) + return false; + } + return true; + } + + static void test_large_secret() + { + for (UInt32 i = 0; i < 1000000; ++i) + { + shared_secret ss = new shared_secret(11, 5); + + byte[] secret = new byte[32]; + + using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) + { + rng.GetBytes(secret); + } + + List<UInt16[]> share_arrays = ss.make_shares(secret); + byte[] plaintext = ss.get_secret(share_arrays); + + if ( !check_result(plaintext, secret) ) + { + throw new Exception("test_large_secret failed"); + } + + if ((i + 1) % 100000 == 0) + { + Console.WriteLine("{0} iterations", i + 1); + } + } + + Console.WriteLine("test_large_secret - success"); + } + + static public void run_all_tests() + { + Console.WriteLine("Running single-byte tests"); + Console.WriteLine("Testing 2-126 shares, 2-16 required"); + test_126_shares(); + + Console.WriteLine("\nTesting 126 shares, 2-126 required"); + test_all_shares(); + + Console.WriteLine("\nTesting random shares"); + random_single_byte_test(); + + Console.WriteLine("\nRunning 32 byte secret tests"); + test_large_secret(); + } + } +} diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index 66385e368876..2267c9d8ddab 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -2,6 +2,7 @@ using Microsoft.Azure.Commands.KeyVault.Models; using Microsoft.Azure.Commands.KeyVault.Properties; using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common; +using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Crypto; using Microsoft.IdentityModel.Tokens; using Microsoft.Rest; using Microsoft.WindowsAzure.Commands.Utilities.Common; @@ -312,7 +313,7 @@ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys) throw new Exception($"Insufficient shares available. {data.SharedKeys.required} required, got {share_arrays.Count}."); } - shamir_share_net.shared_secret secret = new shamir_share_net.shared_secret((UInt16)data.SharedKeys.required); + shared_secret secret = new shared_secret((UInt16)data.SharedKeys.required); masterKey = secret.get_secret(share_arrays); } else From 0b5b4593ce59dec5532418d01585c880dec440b2 Mon Sep 17 00:00:00 2001 From: Yeming Liu <yeliu@microsoft.com> Date: Thu, 8 Oct 2020 23:49:07 +0800 Subject: [PATCH 14/18] resource strings --- .../KeyVault/Properties/Resources.Designer.cs | 22 +++++++++++++++++-- .../KeyVault/Properties/Resources.resx | 10 +++++++-- .../Models/SecurityDomainClient.cs | 10 ++++----- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs index fb23089fa363..82228003611e 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs +++ b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs @@ -342,6 +342,24 @@ internal static string DownloadNotSupported { } } + /// <summary> + /// Looks up a localized string similar to Failed to download security domain backup data.. + /// </summary> + internal static string DownloadSecurityDomainFail { + get { + return ResourceManager.GetString("DownloadSecurityDomainFail", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Failed to download security domain exchange key.. + /// </summary> + internal static string DownloadSecurityDomainKeyFail { + get { + return ResourceManager.GetString("DownloadSecurityDomainKeyFail", resourceCulture); + } + } + /// <summary> /// Looks up a localized string similar to Overwrite File ?. /// </summary> @@ -1072,7 +1090,7 @@ internal static string RestoreSecurityDomainBadKey { } /// <summary> - /// Looks up a localized string similar to Failed to restore security domain data.. + /// Looks up a localized string similar to Failed to restore security domain from backup.. /// </summary> internal static string RestoreSecurityDomainFailure { get { @@ -1081,7 +1099,7 @@ internal static string RestoreSecurityDomainFailure { } /// <summary> - /// Looks up a localized string similar to There need to be at least {0} keys to decrypt security domain data.. + /// Looks up a localized string similar to There need to be at least {0} keys to decrypt security domain backup data.. /// </summary> internal static string RestoreSecurityDomainNotEnoughKey { get { diff --git a/src/KeyVault/KeyVault/Properties/Resources.resx b/src/KeyVault/KeyVault/Properties/Resources.resx index d2d85fd707f1..71c1edab5dbc 100644 --- a/src/KeyVault/KeyVault/Properties/Resources.resx +++ b/src/KeyVault/KeyVault/Properties/Resources.resx @@ -508,7 +508,7 @@ You can find the object ID using Azure Active Directory Module for Windows Power <value>"PublicKey" and "PrivateKey" are mandatory properties in each object in "Keys".</value> </data> <data name="RestoreSecurityDomainNotEnoughKey" xml:space="preserve"> - <value>There need to be at least {0} keys to decrypt security domain data.</value> + <value>There need to be at least {0} keys to decrypt security domain backup data.</value> </data> <data name="DecryptSecurityDomainFailure" xml:space="preserve"> <value>Failed to decrypt security domain data. Please make sure the file is not modified and the keys / passwords are correct.</value> @@ -516,7 +516,13 @@ You can find the object ID using Azure Active Directory Module for Windows Power <data name="DecryptSecurityDomainKeyNotEnough" xml:space="preserve"> <value>Not enough keys to decrypt security domain backup. {0} required, {0} provided.</value> </data> + <data name="DownloadSecurityDomainFail" xml:space="preserve"> + <value>Failed to download security domain backup data.</value> + </data> + <data name="DownloadSecurityDomainKeyFail" xml:space="preserve"> + <value>Failed to download security domain exchange key.</value> + </data> <data name="RestoreSecurityDomainFailure" xml:space="preserve"> - <value>Failed to restore security domain data.</value> + <value>Failed to restore security domain from backup.</value> </data> </root> \ No newline at end of file diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs index 2267c9d8ddab..294398691d7b 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs @@ -85,8 +85,7 @@ public string DownloadSecurityDomain(string hsmName, IEnumerable<X509Certificate { string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); _writeDebug($"Invalid security domain response: {response}"); - // todo : resource string - throw new Exception("Failed to download security domain data."); + throw new Exception(Resources.DownloadSecurityDomainFail); } } @@ -95,7 +94,7 @@ private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securi if (string.IsNullOrEmpty(securityDomainWrapper.value) || !ValidateSecurityDomainData(securityDomainWrapper.value)) { _writeDebug($"Invalid security domain response: {securityDomainWrapper.value}"); - throw new Exception("Failed to download security domain data."); // todo: resource string + throw new Exception(Resources.DownloadSecurityDomainFail); } } @@ -204,14 +203,13 @@ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName) { string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult(); _writeDebug($"Invalid security domain response: {response}"); - // todo : resource string - throw new Exception("Failed to download security domain exchange key."); + throw new Exception(Resources.DownloadSecurityDomainKeyFail); } } catch (Exception ex) { - throw new Exception("Failed to download security domain exchange key.", ex); + throw new Exception(Resources.DownloadSecurityDomainKeyFail, ex); } } From e28b3f8a5cec1709d80fee6414e97923c1117502 Mon Sep 17 00:00:00 2001 From: Yeming Liu <Yeming.Liu@microsoft.com> Date: Fri, 9 Oct 2020 13:15:53 +0800 Subject: [PATCH 15/18] remove extra code --- src/KeyVault/shamir_share/mod_math.cs | 184 ------------------ src/KeyVault/shamir_share/python/main.py | 8 - src/KeyVault/shamir_share/python/mod_math.py | 111 ----------- .../shamir_share/python/shared_secret.py | 78 -------- src/KeyVault/shamir_share/python/test.py | 163 ---------------- .../shamir_share/shamir_share_net.csproj | 12 -- src/KeyVault/shamir_share/shared_secret.cs | 130 ------------- src/KeyVault/shamir_share/test/test.cs | 167 ---------------- 8 files changed, 853 deletions(-) delete mode 100644 src/KeyVault/shamir_share/mod_math.cs delete mode 100644 src/KeyVault/shamir_share/python/main.py delete mode 100644 src/KeyVault/shamir_share/python/mod_math.py delete mode 100644 src/KeyVault/shamir_share/python/shared_secret.py delete mode 100644 src/KeyVault/shamir_share/python/test.py delete mode 100644 src/KeyVault/shamir_share/shamir_share_net.csproj delete mode 100644 src/KeyVault/shamir_share/shared_secret.cs delete mode 100644 src/KeyVault/shamir_share/test/test.cs diff --git a/src/KeyVault/shamir_share/mod_math.cs b/src/KeyVault/shamir_share/mod_math.cs deleted file mode 100644 index 8beca40a8302..000000000000 --- a/src/KeyVault/shamir_share/mod_math.cs +++ /dev/null @@ -1,184 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; - -namespace shamir_share_net -{ - class mod_math - { - public static UInt32 mod_invert(UInt32 x) - { - UInt32 ret = x; - - for (UInt32 i = 0; i < 7; ++i) - { - ret = mod_multiply(ret, ret); - ret = mod_multiply(ret, x); - } - - return ret; - } - - public static UInt32 mod_reduce(UInt32 x) - { - // Function to find x % 257 without side channels - UInt32 t = (x & 0xff) - (x >> 8); - t += (UInt32)((Int32)t >> 31) & 257; - return t; - } - - // Assumes a, b are within 0-256 - public static UInt32 mod_multiply(UInt32 a, UInt32 b) - { - return mod_reduce(a * b); - } - - public static UInt32 mod_add(UInt32 a, UInt32 b) - { - return mod_reduce(a + b); - } - - public static UInt32 mod_subtract(UInt32 a, UInt32 b) - { - // Must ensure that the difference is in the range of 0-256 - return mod_reduce(a - b + 257); - } - } - - class random_bits - { - public UInt16 get_mod_257() - { - UInt16 tmp = 0; - do - { - tmp = get_word(); - - if (tmp != 0) - return (UInt16)mod_math.mod_reduce(tmp); - - } while (tmp == 0); - - // Not actually reached - return 0; - } - - public UInt16 get_word() - { - Int32 remaining = random_bytes.Length - (Int32)current; - - if (remaining < 2) - load(); - - UInt16 ret = (UInt16)((random_bytes[current+1] << 8) | random_bytes[current]); - current += 2; - return ret; - } - - void load() - { - using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) - { - current = 0; - rng.GetBytes(random_bytes); - } - } - - public random_bits(UInt32 _chunk_size) - { - chunk_size = _chunk_size; - current = 0; - random_bytes = new byte[chunk_size]; - load(); - } - - UInt32 chunk_size; - UInt32 current; - byte[] random_bytes; - } - - struct share - { - public share(UInt16 w) - { - x = (UInt16)(w >> 9); - value = (UInt16)(w & 0x1ff); - } - - public share(UInt16 _x, UInt16 _value) - { - x = _x; - value = _value; - } - - public UInt16 to_uint16() - { - return (UInt16)(x << 9 | value); - } - - public UInt16 x; - public UInt16 value; - } - class shared_math - { - public static UInt16 get_secret(UInt16[] shares, UInt32 size) - { - UInt32 secret = 0; - - // Calculate numerator - for (UInt32 i = 0; i<size; ++i) - { - UInt32 numerator = 1; - UInt32 denominator = 1; - share si = new share(shares[i]); - - for (UInt32 j = 0; j<size; ++j) - { - if (i == j) - continue; - - share sj = new share(shares[j]); - numerator = mod_math.mod_multiply(numerator, sj.x); - UInt32 diff = mod_math.mod_subtract(sj.x, si.x); - denominator = mod_math.mod_multiply(diff, denominator); - } - - UInt32 invert = mod_math.mod_invert(denominator); - UInt32 ci = mod_math.mod_multiply(numerator, invert); - UInt32 tmp = mod_math.mod_multiply(ci, si.value); - secret = mod_math.mod_add(secret, tmp); - } - - return (UInt16)(secret); - } - - static public UInt16 make_share(UInt16[] coefficients, UInt16 x) - { - /* - When you evaluate - a*x^3 + b*x^2 + c*x + d - you compute - ((a*x + b)*x + c)*x + d - Also known as Horner’s rule. - */ - - if (coefficients.Length < 2) - { - throw new Exception("Invalid input"); - } - - UInt32 tmp = 0; - tmp = mod_math.mod_multiply(coefficients[0], x ); - tmp = mod_math.mod_add(tmp, coefficients[1] ); - - for (UInt32 i = 2; i < coefficients.Length; ++i) - { - tmp = mod_math.mod_multiply(tmp, x ); - tmp = mod_math.mod_add(tmp, coefficients[i] ); - } - - return (UInt16)(tmp ); - } - } -} diff --git a/src/KeyVault/shamir_share/python/main.py b/src/KeyVault/shamir_share/python/main.py deleted file mode 100644 index 29f348f0d9b7..000000000000 --- a/src/KeyVault/shamir_share/python/main.py +++ /dev/null @@ -1,8 +0,0 @@ -from test import * - -def main(): - print("Running all tests") - run_all_tests() - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/KeyVault/shamir_share/python/mod_math.py b/src/KeyVault/shamir_share/python/mod_math.py deleted file mode 100644 index acdf4c9de86f..000000000000 --- a/src/KeyVault/shamir_share/python/mod_math.py +++ /dev/null @@ -1,111 +0,0 @@ -import secrets -import array - -class mod_math: - - def mod_reduce(self, x): - t = (x & 0xff) - (x >> 8) - t += (t >> 31) & 257 - return t - - def mod_multiply(self, x, y): - return self.mod_reduce( x * y ) - - def mod_invert(self, x): - ret = x - for i in range(7): - ret = self.mod_multiply(ret, ret) - ret = self.mod_multiply(ret, x) - - return ret - - def mod_add(self, x, y): - return self.mod_reduce( x + y) - - def mod_subtract(self, x, y): - return self.mod_reduce( x - y + 257 ) - - def get_random(self): - r = secrets.randbits(16) - return self.mod_reduce(r) - -class share: - def __init__(self, x, v): - self.x = x - self.v = v - - def from_uint16(self, w): - self.x = w >> 9 - self.v = w & 0x1ff - return self - - def to_uint16(self): - return ((self.x << 9) | self.v) - -class mod_math_internal: - def share_from_uint16(self, w): - x = w >> 9 - v = w & 0x1ff - return share(x, v) - -class make_byte_shares: - def __init__(self, required, secret_byte): - self.mm = mod_math() - self.coefficients = self.init_coefficients(required, secret_byte) - - def init_coefficients(self, required, secret_byte): - coefficients = array.array('H') - - for i in range(required - 1): - coefficients.append(self.mm.get_random()) - - coefficients.append( secret_byte ) - return coefficients - - def set_secret_byte(self, secret_byte): - self.coefficients[ len(self.coefficients) - 1] = secret_byte - - def make_share(self, x): - tmp = 0 - tmp = self.mm.mod_multiply(self.coefficients[0], x ) - tmp = self.mm.mod_add(tmp, self.coefficients[1] ) - - for i in range( 2, len(self.coefficients) ): - tmp = self.mm.mod_multiply(tmp, x) - tmp = self.mm.mod_add(tmp, self.coefficients[i]) - i += 1 - - s = share(x, tmp) - return s - -class get_byte_secret: - def __init__(self): - self.mm = mod_math() - self.mmi = mod_math_internal() - - - def get_secret( self, shares, required ): - - secret = 0 - - for i in range( required ): - numerator = 1 - denominator = 1 - si = self.mmi.share_from_uint16(shares[i]) - - for j in range(required): - if i == j: - continue - - sj = self.mmi.share_from_uint16(shares[j]) - numerator = self.mm.mod_multiply(numerator, sj.x) - diff = self.mm.mod_subtract(sj.x, si.x) - denominator = self.mm.mod_multiply(diff, denominator) - - invert = self.mm.mod_invert(denominator) - ci = self.mm.mod_multiply(numerator, invert) - tmp = self.mm.mod_multiply(ci, si.v) - secret = self.mm.mod_add(secret, tmp) - - return secret - diff --git a/src/KeyVault/shamir_share/python/shared_secret.py b/src/KeyVault/shamir_share/python/shared_secret.py deleted file mode 100644 index 770d9dd3518c..000000000000 --- a/src/KeyVault/shamir_share/python/shared_secret.py +++ /dev/null @@ -1,78 +0,0 @@ -import array -from mod_math import * - -max_shares = 126 - -class make_shared_secret: - def __init__(self, shares, required): - if shares > max_shares or required > shares or required < 2: - raise Exception("Incorrect share or required count") - - self.shares = shares - self.required = required - self.byte_shares = make_byte_shares( required, 0) - - def make_byte_shares(self, b): - share_array = [] - self.byte_shares.set_secret_byte(b) - - x = 1 - for i in range(self.shares): - s = self.byte_shares.make_share(x) - share_array.append(s.to_uint16()) - x = x + 1 - - return share_array - - def make_shares(self, plaintext): - share_arrays = [] - - for i in range( len(plaintext) ): - p = plaintext[i] - share_array = self.make_byte_shares(p) - - share_count = len(share_array) - - for j in range(share_count): - if i == 0: - tmp = array.array('H') - share_arrays.append(tmp) - - current_share_array = share_arrays[j] - current_share_array.append(share_array[j]) - - return share_arrays - -class get_secret: - def __init__(self, required): - self.required = required - self.gbs = get_byte_secret() - - def get_secret_byte(self, share_array): - if len(share_array) < self.required: - raise Exception("Insufficient shares") - - return self.gbs.get_secret(share_array, self.required) - - def get_plaintext(self, share_arrays): - plaintext = bytearray() - plaintext_len = len(share_arrays[0]) - - if len(share_arrays) < self.required: - raise Exception("Insufficient shares") - - for j in range(plaintext_len): - sv = array.array('H') - - for i in range(self.required): - sa = share_arrays[i] - sv.append(sa[j]) - - text = self.get_secret_byte(sv) - plaintext.append(text) - - return plaintext - - - - diff --git a/src/KeyVault/shamir_share/python/test.py b/src/KeyVault/shamir_share/python/test.py deleted file mode 100644 index d043aff8d4e0..000000000000 --- a/src/KeyVault/shamir_share/python/test.py +++ /dev/null @@ -1,163 +0,0 @@ -import time -import secrets -from mod_math import * -from shared_secret import * - -def single_byte_test(shares, required): - for i in range(0x100): - secret = make_shared_secret(shares, required) - share_array = secret.make_byte_shares(i) - secret2 = get_secret(required) - result = secret2.get_secret_byte(share_array) - - if i != result: - raise Exception("single_byte_test failed") - - -def test_126_shares(): - i = 126 - print("Running 126 share test") - - # python is much slower than other languages, so only do every 4th number - # if you want to test every possible combination, then change inc to -1 - inc = -4 - for j in range(i, 1, inc): - shares = i - required = j - fmt = "\tshares = {}, required = {}" - print(fmt.format(shares, required)) - single_byte_test(shares, required) - - print("test_126_shares - success") - -def test_all_shares(): - # When testing other languages, this goes up to 127 shares, and 16 required - # to do full testing, change max_shares and max_required - max_shares = 30 - for i in range(2, max_shares): - max_required = 10 - print("\tshares = {}".format(i)) - - if i > max_required: - limit = max_required - else: - limit = i - - for j in range(limit, 1, -1): - shares = i - required = j - single_byte_test(shares, required) - - print("test_all_shares - success\n") - -def perf_test(): - shares = 40 - required = 16 - then = time.time_ns() - single_byte_test(shares, required) - print("mod_reduce_ticks = {}".format(mod_reduce_ticks)) - print("Total elapsed = {}".format(time.time_ns() - then)) - -def random_single_byte_test(): - # note - in C++ and C#, this is 126 shares, 16 required, and 100000 iterations - shares = 30 - required = 10 - iterations = 1000 - - for i in range(iterations): - # just use i % 256 as the secret - secret_value = i % 256 - secret = make_shared_secret(shares, required) - share_array = secret.make_byte_shares(secret_value) - - tmp_array = share_array - random_shares = [] - - for j in range(required): - r = secrets.randbits(8) - pos = r % len(tmp_array) - val = tmp_array[pos] - random_shares.append(val) - tmp_array.remove(val) - - secret2 = get_secret(required) - result = secret2.get_secret_byte(random_shares) - - if result != secret_value: - raise Exception("random_single_byte_test failed") - - print("random_single_byte_test - success") - -def check_result(a, b): - # not for cryptographic purposes - # assumes equal length inputs - - for i in range(len(a)): - if a[i] != b[i]: - return False - - return True - -def test_large_secret(): - # note, because Python is so much slower, only doing - # 1000 iterations, not 1 million - iterations = 1000 - shares = 11 - required = 5 - secret_size = 32 - - for i in range(iterations): - ss = make_shared_secret(shares, required) - - # in order to make debugging a bit easier, - # start with a known plaintext, not random - secret = bytearray(32) - if i == 0: - for x in range(32): - secret[x] = 0x41 + x - else: - # randbits returns an int, not an array of bytes - tmp = secrets.randbits(secret_size*8) - - # Let's convert it - secret = tmp.to_bytes(secret_size, 'little') - - share_arrays = ss.make_shares(secret) - - secret2 = get_secret(required) - plaintext = secret2.get_plaintext(share_arrays) - - if check_result(secret, plaintext) == False: - raise Exception("test_large_secret failed") - - if (i + 1) % int(iterations/10) == 0: - print("{0} iterations".format(i+1)) - - print("test_large_secret - success") - -def run_all_tests(): - - # As it turns out, the Python code is 274 times slower than - # the C# or C++ implementation. It's all in the math, which is a LOT - # slower than even managed code. - - # As a result, tests that run in 15 seconds take about an hour and 15 - # minutes, which is much too long. So we're going to cut back on the - # tests. If you want to run them all, feel free to uncomment below. - - print("Running single-byte tests") - # print("Testing 126 shares, 2-126 required") - # test_126_shares() - - print("\nTesting up to 30 shares, 2-10 required") - test_all_shares() - - # This can be used to do performance analysis - #perf_test() - - print("\nRandom single-byte test") - random_single_byte_test() - - print("\nTesting large secrets") - test_large_secret() - diff --git a/src/KeyVault/shamir_share/shamir_share_net.csproj b/src/KeyVault/shamir_share/shamir_share_net.csproj deleted file mode 100644 index c76142a23d76..000000000000 --- a/src/KeyVault/shamir_share/shamir_share_net.csproj +++ /dev/null @@ -1,12 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <OutputType>Library</OutputType> - <TargetFramework>netstandard2.0</TargetFramework> - <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> - <GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute> - <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> - <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute> - </PropertyGroup> - -</Project> diff --git a/src/KeyVault/shamir_share/shared_secret.cs b/src/KeyVault/shamir_share/shared_secret.cs deleted file mode 100644 index e0c1d54c567d..000000000000 --- a/src/KeyVault/shamir_share/shared_secret.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace shamir_share_net -{ - public class shared_secret - { - public shared_secret(UInt16 shares, UInt16 required) - { - if (shares > max_shares || required > shares || required < 2) - throw new Exception("Incorrect share or required count"); - - _shares = shares; - _required = required; - _coefficients = new UInt16[required]; - _rand_bits = new random_bits(64); - - } - - public shared_secret(UInt16 required) - { - if (required < 2) - throw new Exception("Incorrect share or required count"); - - _shares = 0; - _required = required; - _rand_bits = new random_bits(64); - } - - public List<UInt16[]> make_shares(byte[] plaintext) - { - // Output will have size of share count, each share vector will have an entry for every input byte - List<UInt16[]> share_arrays = new List<UInt16[]>(); - - for( UInt32 i = 0; i < plaintext.Length; ++i) - { - byte p = plaintext[i]; - UInt16[] share_array = make_shares(p); - - /* - We now have a share created for the total number of shares needed - Each share then needs to be distributed such that there's a share - for each byte of plaintext, effectively transposing the 2-dimensional - array. - */ - Int32 share_count = share_array.Length; - - for (Int32 j = 0; j < share_count; ++j) - { - if (i == 0) - share_arrays.Add(new UInt16[plaintext.Length]); - - UInt16[] current_share_array = share_arrays[j]; - current_share_array[i] = share_array[j]; - } - } - - return share_arrays; - } - - public UInt16[] make_shares(byte secret_byte) - { - UInt16[] share_array = new UInt16[_shares]; - - init_coefficients(); - _coefficients[(UInt32)(_required) - 1] = secret_byte; - - UInt16 x = 1; - for (UInt32 i = 0; i < _shares; ++i, ++x) - { - share s = new share(x, shared_math.make_share(_coefficients, x)); - share_array[i] = s.to_uint16(); - } - - return share_array; - } - - public byte[] get_secret(List<UInt16[]> share_arrays) - { - byte[] plaintext = new byte[share_arrays[0].Length]; - - if (share_arrays.Count < _required) - { - throw new Exception("Insufficient shares"); - } - - UInt16[] sv = new UInt16[_required]; - - // TODO - all the constants calculated in get_secret - // can be pulled out once and re-used, which will help perf - for (UInt32 j = 0; j < plaintext.Length; ++j) - { - for (Int32 i = 0; i< _required; ++i) - { - UInt16[] sa = share_arrays[i]; - sv[i] = sa[j]; - } - - UInt16 text = get_secret(sv); - plaintext[j] = (byte)(text); - } - - return plaintext; - } - - public UInt16 get_secret(UInt16[] share_array) - { - if (share_array.Length < _required) - throw new Exception("Insufficient shares"); - - return shared_math.get_secret(share_array, _required); - } - - void init_coefficients() - { - for (UInt32 i = 0; i < (UInt32)(_required) - 1; ++i) - { - _coefficients[i] = _rand_bits.get_mod_257(); - } - } - - UInt16 _shares; - UInt16 _required; - UInt16[] _coefficients; - random_bits _rand_bits; - - const UInt32 max_shares = 126; - } -} diff --git a/src/KeyVault/shamir_share/test/test.cs b/src/KeyVault/shamir_share/test/test.cs deleted file mode 100644 index 70e5efd75780..000000000000 --- a/src/KeyVault/shamir_share/test/test.cs +++ /dev/null @@ -1,167 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Security.Cryptography; -using System.Text; - -namespace shamir_share_net -{ - class test - { - static void single_byte_test(byte shares, UInt16 required) - { - for (UInt16 i = 0; i < (UInt16)0x100; ++i) - { - shared_secret secret = new shared_secret(shares, required); - - UInt16[] share_array = secret.make_shares((byte)i); - UInt16 result = secret.get_secret(share_array); - - if (i != result) - { - throw new Exception("single_byte_test failed"); - } - } - } - - static void random_single_byte_test() - { - byte shares = 126; - UInt16 required = 16; - - for (UInt32 i = 0; i < 100000; ++i) - { - // just use i % 256 as the secret - byte secret_value = (byte)(i % 256); - shared_secret secret = new shared_secret(shares, required); - UInt16[] share_array = secret.make_shares(secret_value); - - // Put them into a List so that we can pick them out - List<UInt16> tmp_array = new List<UInt16>(); - - foreach(UInt16 u in share_array) - { - tmp_array.Add(u); - } - - // Now need to randomly pick values - UInt16[] random_shares = new UInt16[required]; - - random_bits bits = new random_bits(required); - - for (UInt32 j = 0; j < required; ++j) - { - // Yes, I really only need a byte, but this is test code - UInt16 r = bits.get_word(); - Int32 pos = (Int32)(r % tmp_array.Count); - - random_shares[j] = tmp_array[pos]; - tmp_array.RemoveAt(pos); - } - - UInt16 result = secret.get_secret(random_shares); - - if (result != secret_value) - { - throw new Exception("random_single_byte_test failed"); - } - } - - Console.WriteLine("random_single_byte_test - success"); - } - - static void test_all_shares() - { - for (UInt16 i = 2; i < 127; ++i) - { - // It will work for larger numbers of required, but - // it takes a while. Can do some targeted tests for large - // share count - UInt16 max_required = 16; - for (UInt16 j = i > max_required ? max_required : i; j > 1; --j) - { - byte shares = (byte)(i); - byte required = (byte)(j); - - single_byte_test(shares, required); - } - } - - Console.WriteLine("test_all_shares - success"); - } - - static void test_126_shares() - { - UInt16 i = 126; - - Console.WriteLine("Running 126 share test"); - - for (UInt16 j = i; j > 1; --j) - { - byte shares = (byte)(i); - byte required = (byte)(j); - - single_byte_test(shares, required); - } - - Console.WriteLine("test_126_shares - success"); - } - - static bool check_result(byte[] a, byte[] b) - { - // Not for cryptographic purposes - // assumes equal lengths - for(Int32 i = 0; i < a.Length; ++i) - { - if (a[i] != b[i]) - return false; - } - return true; - } - - static void test_large_secret() - { - for (UInt32 i = 0; i < 1000000; ++i) - { - shared_secret ss = new shared_secret(11, 5); - - byte[] secret = new byte[32]; - - using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider()) - { - rng.GetBytes(secret); - } - - List<UInt16[]> share_arrays = ss.make_shares(secret); - byte[] plaintext = ss.get_secret(share_arrays); - - if ( !check_result(plaintext, secret) ) - { - throw new Exception("test_large_secret failed"); - } - - if ((i + 1) % 100000 == 0) - { - Console.WriteLine("{0} iterations", i + 1); - } - } - - Console.WriteLine("test_large_secret - success"); - } - - static public void run_all_tests() - { - Console.WriteLine("Running single-byte tests"); - Console.WriteLine("Testing 2-126 shares, 2-16 required"); - test_126_shares(); - - Console.WriteLine("\nTesting 126 shares, 2-126 required"); - test_all_shares(); - - Console.WriteLine("\nTesting random shares"); - random_single_byte_test(); - - Console.WriteLine("\nRunning 32 byte secret tests"); - test_large_secret(); - } - } -} From ab5a4a78248e8c4b31a85233a3c0a3babafd86dd Mon Sep 17 00:00:00 2001 From: Beisi Zhou <zazbs@qq.com> Date: Mon, 19 Oct 2020 14:05:52 +0800 Subject: [PATCH 16/18] write help markdown --- .../UnitTests/SecurityDomainTests.cs} | 2 +- src/KeyVault/KeyVault.sln | 2 - .../Cmdlets/RestoreSecurityDomain.cs | 3 -- .../help/Backup-AzManagedHsmSecurityDomain.md | 7 +-- .../Restore-AzManagedHsmSecurityDomain.md | 33 +++++--------- src/KeyVault/SecurityDomain.Test/.gitignore | 1 - .../SecurityDomain.Test.csproj | 20 --------- .../test/security-domain.ps1 | 42 ----------------- src/KeyVault/SecurityDomain.Test/test/test.md | 45 ------------------- 9 files changed, 16 insertions(+), 139 deletions(-) rename src/KeyVault/{SecurityDomain.Test/UnitTest1.cs => KeyVault.Test/UnitTests/SecurityDomainTests.cs} (94%) delete mode 100644 src/KeyVault/SecurityDomain.Test/.gitignore delete mode 100644 src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj delete mode 100644 src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 delete mode 100644 src/KeyVault/SecurityDomain.Test/test/test.md diff --git a/src/KeyVault/SecurityDomain.Test/UnitTest1.cs b/src/KeyVault/KeyVault.Test/UnitTests/SecurityDomainTests.cs similarity index 94% rename from src/KeyVault/SecurityDomain.Test/UnitTest1.cs rename to src/KeyVault/KeyVault.Test/UnitTests/SecurityDomainTests.cs index 36554cb677f9..c7dff9826cce 100644 --- a/src/KeyVault/SecurityDomain.Test/UnitTest1.cs +++ b/src/KeyVault/KeyVault.Test/UnitTests/SecurityDomainTests.cs @@ -7,7 +7,7 @@ namespace SecurityDomain.Test { - public class UnitTest1 + public class SecurityDomainTests { [Fact] public void X509Tests() diff --git a/src/KeyVault/KeyVault.sln b/src/KeyVault/KeyVault.sln index 57928c74fc7c..f7b226f2fcbc 100644 --- a/src/KeyVault/KeyVault.sln +++ b/src/KeyVault/KeyVault.sln @@ -18,8 +18,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ScenarioTest.ResourceManage EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestFx", "..\..\tools\TestFx\TestFx.csproj", "{BC80A1D0-FFA4-43D9-AA74-799F5CB54B58}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecurityDomain.Test", "SecurityDomain.Test\SecurityDomain.Test.csproj", "{FDEE9611-2887-4933-AF88-B4EC782B2096}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index 0170f98797b2..be4071423df8 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -21,9 +21,6 @@ public class RestoreSecurityDomain : SecurityDomainCmdlet [ValidateNotNullOrEmpty] public string SecurityDomainPath { get; set; } - [Parameter(HelpMessage = "Specify whether to overwrite existing file.")] - public SwitchParameter Force { get; set; } - [Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")] public SwitchParameter PassThru { get; set; } diff --git a/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md index e34ec601074c..2bbd32f379c4 100644 --- a/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md +++ b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md @@ -1,7 +1,7 @@ --- external help file: Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll-Help.xml Module Name: Az.KeyVault -online version: +online version: https://docs.microsoft.com/en-us/powershell/module/az.keyvault/backup-azmanagedhsmsecuritydomain schema: 2.0.0 --- @@ -33,10 +33,11 @@ This cmdlet backs up the security domain data of a managed HSM for restoring. ### Example 1 ```powershell -PS C:\> {{ Add example code here }} +PS C:\Users\username\> Backup-AzManagedHsmSecurityDomain -Name testmhsm -Certificates pathOfCertificates/sd1.cer, pathOfCertificates/sd2.cer, pathOfCertificates/sd3.cer -OutputPath pathOfOutput/sd.ps.json -Quorum 2 + ``` -{{ Add example description here }} +This command retrieves the managed HSM named testmhsm and saves a backup of that managed HSM to the specified output file. ## PARAMETERS diff --git a/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md index 7057bdee96f0..90836c64ab8b 100644 --- a/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md +++ b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md @@ -1,7 +1,7 @@ --- external help file: Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll-Help.xml Module Name: Az.KeyVault -online version: +online version: https://docs.microsoft.com/en-us/powershell/module/az.keyvault/restore-azmanagedhsmsecuritydomain schema: 2.0.0 --- @@ -14,13 +14,13 @@ Restores previous backed up security domain data to a managed HSM. ### By Name (Default) ``` -Restore-AzManagedHsmSecurityDomain -Keys <KeyPath[]> -SecurityDomainPath <String> [-Force] [-PassThru] - -Name <String> [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>] +Restore-AzManagedHsmSecurityDomain -Keys <KeyPath[]> -SecurityDomainPath <String> [-PassThru] -Name <String> + [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>] ``` ### By InputObject ``` -Restore-AzManagedHsmSecurityDomain -Keys <KeyPath[]> -SecurityDomainPath <String> [-Force] [-PassThru] +Restore-AzManagedHsmSecurityDomain -Keys <KeyPath[]> -SecurityDomainPath <String> [-PassThru] -InputObject <PSKeyVaultIdentityItem> [-DefaultProfile <IAzureContextContainer>] [-WhatIf] [-Confirm] [<CommonParameters>] ``` @@ -32,10 +32,14 @@ This cmdlet restores previous backed up security domain data to a managed HSM. ### Example 1 ```powershell -PS C:\> {{ Add example code here }} -``` +PS C:\> $keys = @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.key"}, +@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.key"}, +@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.key"} +PS C:\> Restore-AzManagedHsmSecurityDomain -Name testmhsm -Keys $keys -SecurityDomainPath pathOfBackup\sd.ps.json -{{ Add example description here }} +``` +First, the keys need be provided to decrypt the security domain data. +Then, The **Restore-AzManagedHsmSecurityDomain** command restores previous backed up security domain data to a managed HSM using these keys. ## PARAMETERS @@ -54,21 +58,6 @@ Accept pipeline input: False Accept wildcard characters: False ``` -### -Force -Specify whether to overwrite existing file. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - ### -InputObject Object representing a managed HSM. diff --git a/src/KeyVault/SecurityDomain.Test/.gitignore b/src/KeyVault/SecurityDomain.Test/.gitignore deleted file mode 100644 index 3a7de2219da9..000000000000 --- a/src/KeyVault/SecurityDomain.Test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -Cert/ \ No newline at end of file diff --git a/src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj b/src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj deleted file mode 100644 index ae1184e7dcfa..000000000000 --- a/src/KeyVault/SecurityDomain.Test/SecurityDomain.Test.csproj +++ /dev/null @@ -1,20 +0,0 @@ -<Project Sdk="Microsoft.NET.Sdk"> - - <PropertyGroup> - <TargetFramework>netcoreapp3.1</TargetFramework> - - <IsPackable>false</IsPackable> - </PropertyGroup> - - <ItemGroup> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" /> - <PackageReference Include="xunit" Version="2.4.0" /> - <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" /> - <PackageReference Include="coverlet.collector" Version="1.2.0" /> - </ItemGroup> - - <ItemGroup> - <ProjectReference Include="..\KeyVault\KeyVault.csproj" /> - </ItemGroup> - -</Project> diff --git a/src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 b/src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 deleted file mode 100644 index fc85c426c7ba..000000000000 --- a/src/KeyVault/SecurityDomain.Test/test/security-domain.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -# Invoke-Pester @{ path = '.\src\KeyVault\SecurityDomain.Test\test\security-domain.ps1'; Parameters = @{sdPath = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json"}} -TestName 'Backup' - -param( - $rgName, - $hsmName, - $hsm2Name, - $sdPath -) - -function RandomName { - param ($prefix) - return $prefix + (Get-Random -Minimum 0 -Maximum 1000) -} - -$location = 'eastus2euap' -$rgName ??= RandomName 'rg' -$hsmName ??= RandomName 'hsm' -$hsm2Name = RandomName 'hsm' -$certificates = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer", "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer", "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer" -$sdPath ??= "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json1" - -BeforeAll { - New-AzResourceGroup -Name $rgName -Location $location - New-AzKeyVault -Hsm ResourceGroupName $rgName -Name $hsmName -Location $location -Administrator 'd7e17135-d5a7-4b8b-89e5-252aa15b7e01', 'c1be1392-39b8-4521-aafc-819a47008545' -} - -Describe "Backup" { - It "Should backup successfully" { - # $sdPath | Should -Not -Exist - - # Backup-AzManagedHsmSecurityDomain -Name $hsmName -Certificates $certificates -OutputPath $sdPath -Quorum 2 - - $sdPath | Should -Exist - Get-Content $sdPath | ConvertFrom-Json | Should -Not -BeNullOrEmpty - } -} - -Describe 'Restore' { - It 'Should restore correctly' { - Restore-AzManagedHsmSecurityDomain - } -} \ No newline at end of file diff --git a/src/KeyVault/SecurityDomain.Test/test/test.md b/src/KeyVault/SecurityDomain.Test/test/test.md deleted file mode 100644 index 2a9538964e93..000000000000 --- a/src/KeyVault/SecurityDomain.Test/test/test.md +++ /dev/null @@ -1,45 +0,0 @@ -openssl req -x509 -newkey rsa:2048 -keyout sd1.key -out sd1.cer -days 365 -nodes -openssl req -x509 -newkey rsa:2048 -keyout sd2.key -out sd2.cer -days 365 -nodes -openssl req -x509 -newkey rsa:2048 -keyout sd3.key -out sd3.cer -days 365 -nodes - -openssl req -x509 -newkey rsa:2048 -keyout sd1.key -out sd1.cer -days 365 -openssl req -x509 -newkey rsa:2048 -keyout sd2.key -out sd2.cer -days 365 -openssl req -x509 -newkey rsa:2048 -keyout sd3.key -out sd3.cer -days 365 - -<!-- backup --> -Backup-AzManagedHsmSecurityDomain -Name yemingmhsm09 -Certificates C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer,C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer -OutputPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json -Quorum 2 - -az keyvault security-domain download --sd-quorum 2 --sd-wrapping-keys "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer" "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer" --security-domain-file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.cli.json" --hsm-name yemingmhsm03 - - -<!-- new & backup key --> -az keyvault key create --hsm-name yemingmhsm06 --name rsa -az keyvault key backup --hsm-name yemingmhsm06 --name rsa --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" -az keyvault key delete --hsm-name yemingmhsm06 --name rsa -az keyvault key purge --hsm-name yemingmhsm06 --name rsa -az keyvault key show --hsm-name yemingmhsm06 --name rsa -az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" -az keyvault key show --hsm-name yemingmhsm06 --name rsa - -<!-- remove --> -remove-azkeyvault -Hsm -ResourceGroupName yemingmhsm -Name yemingmhsm06 -Force - -<!-- new --> -new-azkeyvault -Hsm -Name yemingmhsm10 -ResourceGroupName yemingmhsm -Location eastus2euap -Administrator 'd7e17135-d5a7-4b8b-89e5-252aa15b7e01','c1be1392-39b8-4521-aafc-819a47008545' - -<!-- restore key: fail --> -az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" - -<!-- restore --> -$keys = @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.key"}, -@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.key"}, -@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.key"} - -Restore-AzManagedHsmSecurityDomain -Name yemingmhsm10 -Keys $keys -SecurityDomainPath C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd.ps.json - -<!-- restore key: success --> -az keyvault key restore --hsm-name yemingmhsm06 --file "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\backup\rsa.key" - - -manual: 10 -powershell: 09 \ No newline at end of file From b2568180d1966d439843a3eaa2dcf212d9a716c5 Mon Sep 17 00:00:00 2001 From: Beisi Zhou <zazbs@qq.com> Date: Mon, 19 Oct 2020 15:41:07 +0800 Subject: [PATCH 17/18] resolve relative path to absolute path --- .../KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs | 3 ++- .../SecurityDomain/Cmdlets/RestoreSecurityDomain.cs | 4 ++++ src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs index 7ef61691d714..2a460cab5feb 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs @@ -33,10 +33,11 @@ public override void DoExecuteCmdlet() { ValidateParameters(); - var certificates = Certificates.Select(path => new X509Certificate2(path)); + var certificates = Certificates.Select(path => new X509Certificate2(ResolveUserPath(path))); if (ShouldProcess($"managed HSM {Name}", $"download encrypted security domain data to '{OutputPath}'")) { + OutputPath = ResolveUserPath(OutputPath); var securityDomain = Client.DownloadSecurityDomain(Name, certificates, Quorum); if (!AzureSession.Instance.DataStore.FileExists(OutputPath) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutputPath), Resources.FileOverwriteCaption)) { diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs index be4071423df8..ba3eb0ff78a3 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs @@ -29,6 +29,10 @@ public override void DoExecuteCmdlet() ValidateParameters(); if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file \"{SecurityDomainPath}\"")) { + Keys = Keys.Select(key => new KeyPath() { + PublicKey = this.ResolveUserPath(key.PublicKey), + PrivateKey = this.ResolveUserPath(key.PrivateKey) + }).ToArray(); var securityDomain = LoadSdFromFile(SecurityDomainPath); var rawSecurityDomain = Client.DecryptSecurityDomain(securityDomain, Keys); var exchangeKey = Client.DownloadSecurityDomainExchangeKey(Name); diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs index a43220311db2..f038ed9a24e0 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs @@ -4,8 +4,8 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models { public class KeyPath { - public string PublicKey { get; } - public string PrivateKey { get; } - public SecureString Password { get; } + public string PublicKey { get; set; } + public string PrivateKey { get; set; } + public SecureString Password { get; set; } } } From acd1abe4b3fd8fa378846ca76d801d9ed79be136 Mon Sep 17 00:00:00 2001 From: Beisi Zhou <zazbs@qq.com> Date: Mon, 19 Oct 2020 17:55:07 +0800 Subject: [PATCH 18/18] suppress signature issues --- .../KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs | 6 +++--- .../KeyVault/help/Backup-AzManagedHsmSecurityDomain.md | 4 ++-- .../KeyVault/help/Restore-AzManagedHsmSecurityDomain.md | 6 ++---- .../Exceptions/Az.KeyVault/BreakingChangeIssues.csv | 1 + .../Exceptions/Az.KeyVault/SignatureIssues.csv | 2 ++ 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs index 8e7750fe9cfb..c62ce01a98db 100644 --- a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs +++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs @@ -9,9 +9,9 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets { public abstract class SecurityDomainCmdlet: AzureRMCmdlet { - protected const string ByName = "By Name"; - protected const string ByInputObject = "By InputObject"; - protected const string ByResourceId = "By Resource ID"; + protected const string ByName = "ByName"; + protected const string ByInputObject = "ByInputObject"; + protected const string ByResourceId = "ByResourceID"; [Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = ByName)] [Alias("HsmName")] diff --git a/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md index 2bbd32f379c4..050b2998e243 100644 --- a/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md +++ b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md @@ -33,11 +33,11 @@ This cmdlet backs up the security domain data of a managed HSM for restoring. ### Example 1 ```powershell -PS C:\Users\username\> Backup-AzManagedHsmSecurityDomain -Name testmhsm -Certificates pathOfCertificates/sd1.cer, pathOfCertificates/sd2.cer, pathOfCertificates/sd3.cer -OutputPath pathOfOutput/sd.ps.json -Quorum 2 +PS C:\Users\username\> Backup-AzManagedHsmSecurityDomain -Name testmhsm -Certificates {pathOfCertificates}/sd1.cer, {pathOfCertificates}/sd2.cer, {pathOfCertificates}/sd3.cer -OutputPath {pathOfOutput}/sd.ps.json -Quorum 2 ``` -This command retrieves the managed HSM named testmhsm and saves a backup of that managed HSM to the specified output file. +This command retrieves the managed HSM named testmhsm and saves a backup of that managed HSM security domain to the specified output file. ## PARAMETERS diff --git a/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md index 90836c64ab8b..abfb98317f7b 100644 --- a/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md +++ b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md @@ -32,10 +32,8 @@ This cmdlet restores previous backed up security domain data to a managed HSM. ### Example 1 ```powershell -PS C:\> $keys = @{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd1.key"}, -@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd2.key"}, -@{PublicKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.cer"; PrivateKey = "C:\Users\yeliu\code\azure-powershell\src\KeyVault\SecurityDomain.Test\test\sd3.key"} -PS C:\> Restore-AzManagedHsmSecurityDomain -Name testmhsm -Keys $keys -SecurityDomainPath pathOfBackup\sd.ps.json +PS C:\> $keys = @{PublicKey = "sd1.cer"; PrivateKey = "sd1.key"}, @{PublicKey = "sd2.cer"; PrivateKey = sd2.key"}, @{PublicKey = "sd3.cer"; PrivateKey = "sd3.key"} +PS C:\> Restore-AzManagedHsmSecurityDomain -Name testmhsm -Keys $keys -SecurityDomainPath {pathOfBackup}\sd.ps.json ``` First, the keys need be provided to decrypt the security domain data. diff --git a/tools/StaticAnalysis/Exceptions/Az.KeyVault/BreakingChangeIssues.csv b/tools/StaticAnalysis/Exceptions/Az.KeyVault/BreakingChangeIssues.csv index 2730f147b529..1904325aa5be 100644 --- a/tools/StaticAnalysis/Exceptions/Az.KeyVault/BreakingChangeIssues.csv +++ b/tools/StaticAnalysis/Exceptions/Az.KeyVault/BreakingChangeIssues.csv @@ -1,4 +1,5 @@ "AssemblyFileName","ClassName","Target","Severity","ProblemId","Description","Remediation" + "Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.NewAzureKeyVault","New-AzKeyVault","0","2020","The cmdlet 'New-AzKeyVault' no longer supports the type 'Microsoft.Azure.Management.KeyVault.Models.SkuName' for parameter 'Sku'.","Change the type for parameter 'Sku' back to 'Microsoft.Azure.Management.KeyVault.Models.SkuName'." "Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.RemoveAzureKeyVault","Remove-AzKeyVault","0","2020","The cmdlet 'Remove-AzKeyVault' no longer supports the type 'Microsoft.Azure.Commands.KeyVault.Models.PSKeyVault' for parameter 'InputObject'.","Change the type for parameter 'InputObject' back to 'Microsoft.Azure.Commands.KeyVault.Models.PSKeyVault'." "Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.UpdateTopLevelResourceCommand","Update-AzKeyVault","0","2020","The cmdlet 'Update-AzKeyVault' no longer supports the type 'Microsoft.Azure.Commands.KeyVault.Models.PSKeyVault' for parameter 'InputObject'.","Change the type for parameter 'InputObject' back to 'Microsoft.Azure.Commands.KeyVault.Models.PSKeyVault'." diff --git a/tools/StaticAnalysis/Exceptions/Az.KeyVault/SignatureIssues.csv b/tools/StaticAnalysis/Exceptions/Az.KeyVault/SignatureIssues.csv index d01bd03ea014..c77dde4efec1 100644 --- a/tools/StaticAnalysis/Exceptions/Az.KeyVault/SignatureIssues.csv +++ b/tools/StaticAnalysis/Exceptions/Az.KeyVault/SignatureIssues.csv @@ -1,4 +1,6 @@ "AssemblyFileName","ClassName","Target","Severity","ProblemId","Description","Remediation" +"Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets.BackupSecurityDomain","Backup-AzManagedHsmSecurityDomain","1","8410","Parameter Certificates of cmdlet Backup-AzManagedHsmSecurityDomain does not follow the enforced naming convention of using a singular noun for a parameter name.","Consider using a singular noun for the parameter name." +"Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets.RestoreSecurityDomain","Restore-AzManagedHsmSecurityDomain","1","8410","Parameter Keys of cmdlet Restore-AzManagedHsmSecurityDomain does not follow the enforced naming convention of using a singular noun for a parameter name.","Consider using a singular noun for the parameter name." "Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.GetAzureManagedHsmKey","Get-AzManagedHsmKey","1","8410","Parameter IncludeVersions of cmdlet Get-AzManagedHsmKey does not follow the enforced naming convention of using a singular noun for a parameter name.","Consider using a singular noun for the parameter name." "Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.UpdateAzureManagedHsmKey","Update-AzManagedHsmKey","1","8410","Parameter Expires of cmdlet Update-AzManagedHsmKey does not follow the enforced naming convention of using a singular noun for a parameter name.","Consider using a singular noun for the parameter name." "Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.UpdateAzureManagedHsmKey","Update-AzManagedHsmKey","1","8410","Parameter KeyOps of cmdlet Update-AzManagedHsmKey does not follow the enforced naming convention of using a singular noun for a parameter name.","Consider using a singular noun for the parameter name."