diff --git a/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj b/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj
index 07ed849ce7a6..d8d000df5ae5 100644
--- a/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj
+++ b/src/KeyVault/KeyVault.Test/KeyVault.Test.csproj
@@ -14,6 +14,7 @@
+
diff --git a/src/KeyVault/KeyVault.Test/UnitTests/SecurityDomainTests.cs b/src/KeyVault/KeyVault.Test/UnitTests/SecurityDomainTests.cs
new file mode 100644
index 000000000000..c7dff9826cce
--- /dev/null
+++ b/src/KeyVault/KeyVault.Test/UnitTests/SecurityDomainTests.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 SecurityDomainTests
+ {
+ [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/KeyVault.sln b/src/KeyVault/KeyVault.sln
index dccac43f4a32..f7b226f2fcbc 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
@@ -52,12 +52,17 @@ 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
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 399c0e1951cf..bbe7627d564a 100644
--- a/src/KeyVault/KeyVault/Az.KeyVault.psd1
+++ b/src/KeyVault/KeyVault/Az.KeyVault.psd1
@@ -122,7 +122,8 @@ CmdletsToExport = 'Add-AzManagedHsmKey', 'Get-AzManagedHsmKey', 'Remove-AzManage
'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/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/KeyVault.csproj b/src/KeyVault/KeyVault/KeyVault.csproj
index 07ec908b56c6..556c14e346eb 100644
--- a/src/KeyVault/KeyVault/KeyVault.csproj
+++ b/src/KeyVault/KeyVault/KeyVault.csproj
@@ -1,4 +1,4 @@
-
+
KeyVault
@@ -12,10 +12,12 @@
-
+
+
+
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 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/Properties/AssemblyInfo.cs b/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs
index ddc0070abd8b..d712573951f0 100644
--- a/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs
+++ b/src/KeyVault/KeyVault/Properties/AssemblyInfo.cs
@@ -33,4 +33,5 @@
[assembly: AssemblyFileVersion("2.2.1")]
#if !SIGN
[assembly: InternalsVisibleTo("Microsoft.Azure.PowerShell.Cmdlets.KeyVault.Test")]
+[assembly: InternalsVisibleTo("SecurityDomain.Test")]
#endif
diff --git a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs
index 0d7cbfd42369..68089478d334 100644
--- a/src/KeyVault/KeyVault/Properties/Resources.Designer.cs
+++ b/src/KeyVault/KeyVault/Properties/Resources.Designer.cs
@@ -315,6 +315,24 @@ internal static string CreateKeyVault {
}
}
+ ///
+ /// 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..
+ ///
+ internal static string DecryptSecurityDomainFailure {
+ get {
+ return ResourceManager.GetString("DecryptSecurityDomainFailure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Not enough keys to decrypt security domain backup. {0} required, {0} provided..
+ ///
+ internal static string DecryptSecurityDomainKeyNotEnough {
+ get {
+ return ResourceManager.GetString("DecryptSecurityDomainKeyNotEnough", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Cannot find deleted vault '{0}' in location '{1}'.
///
@@ -333,6 +351,24 @@ internal static string DownloadNotSupported {
}
}
+ ///
+ /// Looks up a localized string similar to Failed to download security domain backup data..
+ ///
+ internal static string DownloadSecurityDomainFail {
+ get {
+ return ResourceManager.GetString("DownloadSecurityDomainFail", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to download security domain exchange key..
+ ///
+ internal static string DownloadSecurityDomainKeyFail {
+ get {
+ return ResourceManager.GetString("DownloadSecurityDomainKeyFail", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Overwrite File ?.
///
@@ -352,6 +388,14 @@ internal static string FileOverwriteMessage {
}
///
+ /// Looks up a localized string similar to To encrypt the security domain data, please provide at least {0} and at most {1} certificates..
+ ///
+ internal static string HsmCertRangeWarning {
+ get {
+ return ResourceManager.GetString("HsmCertRangeWarning", resourceCulture);
+ }
+ }
+
/// Looks up a localized string similar to The specified HSM already exists..
///
internal static string HsmAlreadyExists {
@@ -684,6 +728,15 @@ internal static string KeyOpsImportIsExclusive {
}
}
+ ///
+ /// 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..
+ ///
+ internal static string LoadSecurityDomainFileFailed {
+ get {
+ return ResourceManager.GetString("LoadSecurityDomainFileFailed", resourceCulture);
+ }
+ }
+
///
/// 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..
///
@@ -1098,6 +1151,33 @@ internal static string RestoreSecret {
}
}
+ ///
+ /// Looks up a localized string similar to "PublicKey" and "PrivateKey" are mandatory properties in each object in "Keys"..
+ ///
+ internal static string RestoreSecurityDomainBadKey {
+ get {
+ return ResourceManager.GetString("RestoreSecurityDomainBadKey", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Failed to restore security domain from backup..
+ ///
+ internal static string RestoreSecurityDomainFailure {
+ get {
+ return ResourceManager.GetString("RestoreSecurityDomainFailure", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to There need to be at least {0} keys to decrypt security domain backup data..
+ ///
+ internal static string RestoreSecurityDomainNotEnoughKey {
+ get {
+ return ResourceManager.GetString("RestoreSecurityDomainNotEnoughKey", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Set certificate attribute.
///
diff --git a/src/KeyVault/KeyVault/Properties/Resources.resx b/src/KeyVault/KeyVault/Properties/Resources.resx
index 47768e7475c4..30842c8202e5 100644
--- a/src/KeyVault/KeyVault/Properties/Resources.resx
+++ b/src/KeyVault/KeyVault/Properties/Resources.resx
@@ -501,6 +501,33 @@ You can find the object ID using Azure Active Directory Module for Windows Power
The "import" operation is exclusive, it cannot be combined with any other value(s).
+
+ To encrypt the security domain data, please provide at least {0} and at most {1} certificates.
+
+
+ Failed to load security domain data from {0}. Please make sure the file exists and is not modified.
+
+
+ "PublicKey" and "PrivateKey" are mandatory properties in each object in "Keys".
+
+
+ There need to be at least {0} keys to decrypt security domain backup data.
+
+
+ Failed to decrypt security domain data. Please make sure the file is not modified and the keys / passwords are correct.
+
+
+ Not enough keys to decrypt security domain backup. {0} required, {0} provided.
+
+
+ Failed to download security domain backup data.
+
+
+ Failed to download security domain exchange key.
+
+
+ Failed to restore security domain from backup.
+
Invalid key properties
diff --git a/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs
new file mode 100644
index 000000000000..2a460cab5feb
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs
@@ -0,0 +1,62 @@
+using Microsoft.Azure.Commands.Common.Authentication;
+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)]
+ [ValidateNotNullOrEmpty()]
+ 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.")]
+ public SwitchParameter Force { get; set; }
+
+ [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(Common.Constants.MinQuorum, Common.Constants.MaxQuorum)]
+ public int Quorum { get; set; }
+
+ public override void DoExecuteCmdlet()
+ {
+ ValidateParameters();
+
+ 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))
+ {
+ AzureSession.Instance.DataStore.WriteFile(OutputPath, securityDomain);
+ WriteDebug($"Security domain data of managed HSM '{Name}' downloaded to '{OutputPath}'.");
+ if (PassThru)
+ {
+ WriteObject(true);
+ }
+ }
+ }
+ }
+
+ private void ValidateParameters()
+ {
+ if (Certificates.Length < Common.Constants.MinCert || Certificates.Length > Common.Constants.MaxCert)
+ {
+ 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
new file mode 100644
index 000000000000..ba3eb0ff78a3
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs
@@ -0,0 +1,79 @@
+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.Linq;
+using System.Management.Automation;
+
+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. 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 = "When specified, a boolean will be returned when cmdlet succeeds.")]
+ public SwitchParameter PassThru { get; set; }
+
+ 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);
+ var encryptedSecurityDomain = Client.EncryptForRestore(rawSecurityDomain, exchangeKey);
+ Client.RestoreSecurityDomain(Name, encryptedSecurityDomain);
+
+ if (PassThru)
+ {
+ WriteObject(true);
+ }
+ }
+ }
+
+ private void ValidateParameters()
+ {
+ if (Keys.Length < 2)
+ {
+ throw new ArgumentException(string.Format(Resources.RestoreSecurityDomainNotEnoughKey, Common.Constants.MinQuorum));
+ }
+ if (Keys.Any(key => string.IsNullOrEmpty(key.PublicKey) || string.IsNullOrEmpty(key.PrivateKey)))
+ {
+ throw new ArgumentException(Resources.RestoreSecurityDomainBadKey);
+ }
+ }
+
+ private SecurityDomainData LoadSdFromFile(string path)
+ {
+ try
+ {
+ string content = Utils.FileToString(path);
+ if (string.IsNullOrWhiteSpace(content))
+ {
+ throw new ArgumentException(nameof(SecurityDomainPath));
+ }
+ return JsonConvert.DeserializeObject(content);
+ }
+ catch (Exception ex)
+ {
+ 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
new file mode 100644
index 000000000000..c62ce01a98db
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Cmdlets/SecurityDomainCmdlet.cs
@@ -0,0 +1,64 @@
+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 abstract class SecurityDomainCmdlet: AzureRMCmdlet
+ {
+ 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")]
+ [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
+ {
+ get
+ {
+ if (_client == null)
+ {
+ _client = new SecurityDomainClient(AzureSession.Instance.AuthenticationFactory, DefaultContext, s => WriteDebug(s));
+ }
+ return _client;
+ }
+ set => _client = value;
+ }
+
+
+ private ISecurityDomainClient _client;
+
+ ///
+ /// Sub-classes should not override this method, but instead.
+ /// This is call-super pattern. See https://www.martinfowler.com/bliki/CallSuper.html
+ ///
+ public override void ExecuteCmdlet()
+ {
+ PreprocessParameterSets();
+ DoExecuteCmdlet();
+ }
+
+ ///
+ /// Unifies different parameter sets. Sub-classes need only to care about Name.
+ ///
+ private void PreprocessParameterSets()
+ {
+ if (this.IsParameterBound(c => c.InputObject))
+ {
+ Name = InputObject.VaultName;
+ }
+ }
+
+ public abstract 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;
+ }
+}
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/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 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 make_shares(byte[] plaintext)
+ {
+ // Output will have size of share count, each share vector will have an entry for every input byte
+ List share_arrays = new List();
+
+ 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 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 tmp_array = new List();
+
+ 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 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/CertKey.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs
new file mode 100644
index 000000000000..e32ec92b308e
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKey.cs
@@ -0,0 +1,85 @@
+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 void Load(KeyPath path)
+ {
+ _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[] GetThumbprint() { return _thumbprint; }
+ public RSA GetKey() { return _key; }
+ public X509Certificate2 GetCert() { 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
+ {
+ 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);
+ 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 readonly string _password;
+
+ public PasswordFinder(string password)
+ {
+ _password = password;
+ }
+
+ public char[] GetPassword()
+ {
+ return _password.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..eae5de3268ec
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Models/CertKeys.cs
@@ -0,0 +1,46 @@
+using Microsoft.IdentityModel.Tokens;
+using System;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models
+{
+ internal class CertKeys
+ {
+ public CertKeys()
+ {
+ _keys = new Dictionary();
+ }
+
+ public void LoadKeys(KeyPath[] paths)
+ {
+ foreach (var path in paths)
+ {
+ try { LoadKey(path); }
+ catch (Exception ex)
+ {
+ throw new Exception($"Could not load public and private key from {path.PublicKey} and {path.PrivateKey}", ex);
+ }
+ }
+ }
+
+ public void LoadKey(KeyPath path)
+ {
+ CertKey certKey = new CertKey();
+ certKey.Load(path);
+ string encodedThumbprint = Base64UrlEncoder.Encode(certKey.GetThumbprint());
+ _keys.Add(encodedThumbprint, certKey);
+ }
+
+ public CertKey Find(string encoded_thumbprint)
+ {
+ if (!_keys.TryGetValue(encoded_thumbprint, out CertKey certKey))
+ return null;
+
+ return certKey;
+ }
+
+ public int Count() { return _keys.Count; }
+
+ private readonly Dictionary _keys;
+ }
+}
diff --git a/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs b/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs
new file mode 100644
index 000000000000..a7657a65db8d
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Models/DownloadRequest.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+using System.Collections.Generic;
+
+namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models
+{
+ class DownloadRequest
+ {
+ public DownloadRequest()
+ {
+ Certificates = new List();
+ }
+
+ [JsonProperty("required")]
+ public int Required;
+
+ [JsonProperty("certificates")]
+ 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..64fb66d9eaf4
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Models/ISecurityDomainClient.cs
@@ -0,0 +1,18 @@
+using System.Collections.Generic;
+using System.Security.Cryptography.X509Certificates;
+
+namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models
+{
+ public interface ISecurityDomainClient
+ {
+ string DownloadSecurityDomain(string hsmName, IEnumerable certificates, int required);
+
+ X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName);
+
+ PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] paths);
+
+ SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert);
+
+ void RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData 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..f038ed9a24e0
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Models/KeyPath.cs
@@ -0,0 +1,11 @@
+using System.Security;
+
+namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models
+{
+ public class KeyPath
+ {
+ public string PublicKey { get; set; }
+ public string PrivateKey { get; set; }
+ public SecureString Password { get; set; }
+ }
+}
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 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 keys { get; set; }
+ }
+
+ public class SharedKeys
+ {
+ public string key_algorithm { get; set; }
+ public UInt32 required { get; set; }
+ public IList 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..294398691d7b
--- /dev/null
+++ b/src/KeyVault/KeyVault/SecurityDomain/Models/SecurityDomainClient.cs
@@ -0,0 +1,446 @@
+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.Crypto;
+using Microsoft.IdentityModel.Tokens;
+using Microsoft.Rest;
+using Microsoft.WindowsAzure.Commands.Utilities.Common;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Security.Authentication;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using static Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureEnvironment;
+
+namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models
+{
+ internal class SecurityDomainClient : ServiceClient, ISecurityDomainClient
+ {
+ public SecurityDomainClient(IAuthenticationFactory authenticationFactory, IAzureContext defaultContext, Action debugWriter)
+ {
+ _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 readonly DataServiceCredential _credentials;
+ private readonly VaultUriHelper _uriHelper;
+ private readonly JsonSerializerSettings _serializationSettings = new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore };
+ private readonly Action _writeDebug;
+
+ ///
+ /// Download security domain data for restore.
+ /// Data is encrypted with the certificates (public keys) user passes in.
+ ///
+ /// Name of the HSM
+ /// Certificates used to encrypt the security domain data
+ /// Specify how many keys are required to decrypt the data
+ /// Encrypted HSM security domain data in string
+ public string DownloadSecurityDomain(string hsmName, IEnumerable certificates, int quorum)
+ {
+ var downloadRequest = new DownloadRequest
+ {
+ Required = quorum
+ };
+ certificates.ForEach(cert => downloadRequest.Certificates.Add(new JWK(cert)));
+
+ string requestBody = JsonConvert.SerializeObject(
+ downloadRequest,
+ Formatting.None,
+ _serializationSettings);
+
+ var httpRequest = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post,
+ RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName))
+ {
+ Path = $"/{_securityDomainPathFragment}/download"
+ }.Uri,
+ Content = new StringContent(requestBody)
+ };
+
+ PrepareRequest(httpRequest);
+
+ var httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult();
+
+ if (httpResponseMessage.IsSuccessStatusCode)
+ {
+ string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
+ var securityDomainWrapper = JsonConvert.DeserializeObject(response);
+ ValidateDownloadSecurityDomainResponse(securityDomainWrapper);
+ return securityDomainWrapper.value;
+ }
+ else
+ {
+ string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
+ _writeDebug($"Invalid security domain response: {response}");
+ throw new Exception(Resources.DownloadSecurityDomainFail);
+ }
+ }
+
+ private void ValidateDownloadSecurityDomainResponse(SecurityDomainWrapper securityDomainWrapper)
+ {
+ if (string.IsNullOrEmpty(securityDomainWrapper.value) || !ValidateSecurityDomainData(securityDomainWrapper.value))
+ {
+ _writeDebug($"Invalid security domain response: {securityDomainWrapper.value}");
+ throw new Exception(Resources.DownloadSecurityDomainFail);
+ }
+ }
+
+ ///
+ /// Prepare common headers for the request.
+ /// Such as content-type and authorization.
+ ///
+ ///
+ private void PrepareRequest(HttpRequestMessage httpRequest)
+ {
+ if (httpRequest.Content != null)
+ {
+ httpRequest.Content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json; charset=utf-8");
+ }
+
+ try
+ {
+ var token = _credentials.GetAccessToken();
+ token.AuthorizeRequest((tokenType, tokenValue) =>
+ {
+ httpRequest.Headers.Authorization = new AuthenticationHeaderValue(tokenType, tokenValue);
+ });
+ }
+ catch (Exception ex)
+ {
+ throw new AuthenticationException(Resources.InvalidSubscriptionState, ex);
+ }
+ }
+
+ private bool ValidateSecurityDomainData(string securityDomainData)
+ {
+ var securityDomain = JsonConvert.DeserializeObject(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;
+ }
+
+ ///
+ /// 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.
+ ///
+ ///
+ ///
+ public X509Certificate2 DownloadSecurityDomainExchangeKey(string hsmName)
+ {
+ try
+ {
+ var httpRequest = new HttpRequestMessage
+ {
+ Method = HttpMethod.Get,
+ RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName))
+ {
+ Path = $"/{_securityDomainPathFragment}/upload"
+ }.Uri,
+ };
+
+ PrepareRequest(httpRequest);
+
+ HttpResponseMessage httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult();
+
+ if (httpResponseMessage.IsSuccessStatusCode)
+ {
+ var response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
+ var key = JsonConvert.DeserializeObject(response);
+
+ switch (key.KeyFormat)
+ {
+ case "pem":
+ // Transitional, remove later
+ return Utils.CertficateFromPem(key.TransferKey);
+ case "jwk":
+ // handle below
+ break;
+ default:
+ 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(key.TransferKey);
+ return Utils.CertficateFromPem(jwk.GetX5cAsPem());
+ }
+ else
+ {
+ string response = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
+ _writeDebug($"Invalid security domain response: {response}");
+ throw new Exception(Resources.DownloadSecurityDomainKeyFail);
+ }
+
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(Resources.DownloadSecurityDomainKeyFail, ex);
+ }
+ }
+
+ ///
+ /// Decrypt security domain data.
+ /// User must specify public key / private key / password* groups to decrypt SD.
+ /// *password MAY be optional.
+ ///
+ ///
+ ///
+ ///
+ public PlaintextList DecryptSecurityDomain(SecurityDomainData data, KeyPath[] paths)
+ {
+ CertKeys certKeys = new CertKeys();
+ try
+ {
+ certKeys.LoadKeys(paths);
+ return Decrypt(data, certKeys);
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(Resources.DecryptSecurityDomainFailure, ex);
+ }
+ }
+
+ // Internal worker function
+ private PlaintextList Decrypt(SecurityDomainData data, CertKeys certKeys)
+ {
+ if (data.version == 2 && certKeys.Count() < data.SharedKeys.required)
+ {
+ throw new ArgumentException(string.Format(Resources.DecryptSecurityDomainKeyNotEnough, data.SharedKeys.required, certKeys.Count()));
+ }
+
+ 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")
+ {
+ throw new Exception($"Unknown SplitKey algorithm {data.SplitKeys.key_algorithm}.");
+ }
+
+ KeyPair decodeKeyPair = null;
+ CertKey certKey1 = null;
+ CertKey certKey2 = null;
+ foreach (KeyPair keyPair in data.SplitKeys.keys)
+ {
+ certKey1 = certKeys.Find(keyPair.key1.x5t_256);
+
+ if (certKey1 == null)
+ continue;
+
+ certKey2 = certKeys.Find(keyPair.key2.x5t_256);
+
+ if (certKey2 != null)
+ {
+ decodeKeyPair = keyPair;
+ break;
+ }
+ }
+
+ if (decodeKeyPair == null)
+ {
+ throw new Exception("Cannot find matching certs and keys for security domain");
+ }
+
+ masterKey = DecryptMasterKey(decodeKeyPair, certKey1, certKey2);
+ }
+ else if (data.version == 2)
+ {
+ if (data.SharedKeys.key_algorithm != "shamir_share")
+ {
+ throw new Exception($"Unknown SharedKeys algorithm {data.SharedKeys.key_algorithm}");
+ }
+
+ UInt32 shares_found = 0;
+ List share_arrays = new List();
+
+ 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.GetKey());
+
+ shares_found++;
+ share_arrays.Add(Utils.ConvertToUint16(share));
+ }
+
+ if (share_arrays.Count == data.SharedKeys.required)
+ break;
+ }
+
+ if (share_arrays.Count < data.SharedKeys.required)
+ {
+ throw new Exception($"Insufficient shares available. {data.SharedKeys.required} required, got {share_arrays.Count}.");
+ }
+
+ shared_secret secret = new shared_secret((UInt16)data.SharedKeys.required);
+ masterKey = secret.get_secret(share_arrays);
+ }
+ else
+ {
+ throw new Exception($"Unknown domain version {data.version}.");
+ }
+
+ 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(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;
+
+ 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.GetKey());
+
+ JWE jwe2 = new JWE(decode_key_pair.key2.enc_key);
+ byte[] derived_key = jwe2.Decrypt(certKey2.GetKey());
+
+ // 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;
+ }
+
+ ///
+ /// Encrypt SD data with exchange key.
+ ///
+ ///
+ /// Exchange key
+ ///
+ public SecurityDomainRestoreData EncryptForRestore(PlaintextList plaintextList, X509Certificate2 cert)
+ {
+ try
+ {
+ 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;
+ }
+ catch (Exception ex)
+ {
+ throw new Exception("Failed to encrypt security domain data for restoring.", ex);
+ }
+
+ }
+
+ ///
+ /// Upload security domain data and initiate restoring.
+ ///
+ ///
+ /// Encrypted by exchange key
+ public void RestoreSecurityDomain(string hsmName, SecurityDomainRestoreData securityDomainData)
+ {
+ string securityDomain = JsonConvert.SerializeObject(new SecurityDomainWrapper
+ {
+ value = JsonConvert.SerializeObject(securityDomainData)
+ });
+
+ try
+ {
+ var httpRequest = new HttpRequestMessage
+ {
+ Method = HttpMethod.Post,
+ RequestUri = new UriBuilder(_uriHelper.CreateManagedHsmUri(hsmName))
+ {
+ Path = $"/{_securityDomainPathFragment}/upload"
+ }.Uri,
+ Content = new StringContent(securityDomain)
+ };
+
+ PrepareRequest(httpRequest);
+
+ var httpResponseMessage = HttpClient.SendAsync(httpRequest).ConfigureAwait(false).GetAwaiter().GetResult();
+ var responseBody = httpResponseMessage.Content.ReadAsStringAsync().ConfigureAwait(false).GetAwaiter().GetResult();
+ if (httpResponseMessage.IsSuccessStatusCode)
+ {
+ if (string.IsNullOrEmpty(responseBody))
+ {
+ throw new Exception("Got empty response when restoring security domain.");
+ }
+ }
+ else
+ {
+ throw new Exception($"Got {httpResponseMessage.StatusCode}, {responseBody}");
+ }
+ }
+ catch (Exception ex)
+ {
+ throw new Exception(Resources.RestoreSecurityDomainFailure, ex);
+ }
+ }
+ }
+}
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/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/KeyVault/help/Az.KeyVault.md b/src/KeyVault/KeyVault/help/Az.KeyVault.md
index 37332c13328c..2cea498efdab 100644
--- a/src/KeyVault/KeyVault/help/Az.KeyVault.md
+++ b/src/KeyVault/KeyVault/help/Az.KeyVault.md
@@ -41,6 +41,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.
+
### [Backup-AzManagedHsmKey](Backup-AzManagedHsmKey.md)
Backs up a key in a managed HSM.
@@ -152,6 +155,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.
+
### [Restore-AzManagedHsmKey](Restore-AzManagedHsmKey.md)
Creates a key in a managed HSM from a backed-up key.
diff --git a/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md
new file mode 100644
index 000000000000..050b2998e243
--- /dev/null
+++ b/src/KeyVault/KeyVault/help/Backup-AzManagedHsmSecurityDomain.md
@@ -0,0 +1,208 @@
+---
+external help file: Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll-Help.xml
+Module Name: Az.KeyVault
+online version: https://docs.microsoft.com/en-us/powershell/module/az.keyvault/backup-azmanagedhsmsecuritydomain
+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 -OutputPath [-Force] [-PassThru]
+ -Quorum -Name [-DefaultProfile ] [-WhatIf] [-Confirm]
+ []
+```
+
+### By InputObject
+```
+Backup-AzManagedHsmSecurityDomain -Certificates -OutputPath [-Force] [-PassThru]
+ -Quorum -InputObject [-DefaultProfile ] [-WhatIf]
+ [-Confirm] []
+```
+
+## DESCRIPTION
+This cmdlet backs up the security domain data of a managed HSM for restoring.
+
+## EXAMPLES
+
+### 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
+
+```
+
+This command retrieves the managed HSM named testmhsm and saves a backup of that managed HSM security domain to the specified output file.
+
+## 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/New-AzKeyVault.md b/src/KeyVault/KeyVault/help/New-AzKeyVault.md
index 21cea2ed8011..3e798dfb8f73 100644
--- a/src/KeyVault/KeyVault/help/New-AzKeyVault.md
+++ b/src/KeyVault/KeyVault/help/New-AzKeyVault.md
@@ -13,6 +13,7 @@ Creates a key vault.
## SYNTAX
+### KeyVaultParameterSet (Default)
```
New-AzKeyVault [-Name] [-ResourceGroupName] [-Location] [-EnabledForDeployment]
[-EnabledForTemplateDeployment] [-EnabledForDiskEncryption] [-EnablePurgeProtection]
diff --git a/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md
new file mode 100644
index 000000000000..abfb98317f7b
--- /dev/null
+++ b/src/KeyVault/KeyVault/help/Restore-AzManagedHsmSecurityDomain.md
@@ -0,0 +1,179 @@
+---
+external help file: Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll-Help.xml
+Module Name: Az.KeyVault
+online version: https://docs.microsoft.com/en-us/powershell/module/az.keyvault/restore-azmanagedhsmsecuritydomain
+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 -SecurityDomainPath [-PassThru] -Name
+ [-DefaultProfile ] [-WhatIf] [-Confirm] []
+```
+
+### By InputObject
+```
+Restore-AzManagedHsmSecurityDomain -Keys -SecurityDomainPath [-PassThru]
+ -InputObject [-DefaultProfile ] [-WhatIf] [-Confirm]
+ []
+```
+
+## DESCRIPTION
+This cmdlet restores previous backed up security domain data to a managed HSM.
+
+## EXAMPLES
+
+### Example 1
+```powershell
+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.
+Then, The **Restore-AzManagedHsmSecurityDomain** command restores previous backed up security domain data to a managed HSM using these keys.
+
+## 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
+```
+
+### -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
diff --git a/tools/StaticAnalysis/Exceptions/Az.KeyVault/BreakingChangeIssues.csv b/tools/StaticAnalysis/Exceptions/Az.KeyVault/BreakingChangeIssues.csv
index 9bfad0acd7c1..fffc239a8835 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.NewAzureKeyVault","New-AzKeyVault","0","1050","The parameter set '__AllParameterSets' for cmdlet 'New-AzKeyVault' has been removed.","Add parameter set '__AllParameterSets' back to cmdlet 'New-AzKeyVault'."
"Microsoft.Azure.PowerShell.Cmdlets.KeyVault.dll","Microsoft.Azure.Commands.KeyVault.UpdateTopLevelResourceCommand","Update-AzKeyVault","0","1050","The parameter set 'UpdateByNameParameterSet' for cmdlet 'Update-AzKeyVault' has been removed.","Add parameter set 'UpdateByNameParameterSet' back to cmdlet 'Update-AzKeyVault'."
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."