Skip to content

Commit 05fa715

Browse files
NoriZCBethanyZhou
andauthored
[Az.KeyVault] Security domain Upload Split (#22671)
* 0825 update * 0825 update2 * Security Domain Upload Split * Update ChangeLog * Polish code * Polish code * Polish code * Help files generate * Add online version in help files * Rename super class from SecurityDomainCmdletClient to SecurityDomainCmdletBase * Supress Static Analysis * Suppress Static Analysis * Suppress Static Analysis * Sytax in help files * Align the new design * Update changelog * Update Pester test * ByRestoredBlob * Resolve comments * SecurityDomainRestoredBlob * Update help files * Update src/KeyVault/KeyVault/ChangeLog.md Co-authored-by: Beisi Zhou <[email protected]> * Rename files * Update help file * suppress signature issue * suppress signature issue * ExchangeKey * ExchangeKeyPath, OutFile --------- Co-authored-by: Beisi Zhou <[email protected]>
1 parent be06c80 commit 05fa715

File tree

12 files changed

+441
-146
lines changed

12 files changed

+441
-146
lines changed

src/KeyVault/KeyVault.Test/PesterTests/ManagedHsmDataPlaneTests.Tests.ps1

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,37 @@ Describe 'Export Import Security domain' {
278278
# Import-AzKeyVaultSecurityDomain -Name $hsmName -Keys $certsKeys -SecurityDomainPath $sd.FullName
279279
}
280280

281+
Describe 'Export Import Security Domain by Restored Blob' {
282+
$sd = New-TemporaryFile
283+
284+
It 'Can export security domain' {
285+
Get-Content $sd | Should -BeNullOrEmpty
286+
Export-AzKeyVaultSecurityDomain -HsmName $hsmName -Certificates $certs -OutputPath $sd.FullName -Quorum 3 -Force
287+
Get-Content $sd | Should -Not -BeNullOrEmpty
288+
}
289+
290+
# Cannot test initializing because it needs a newly created HSM
291+
It 'Can Download Security Domain ExchangeKey' {
292+
$exchangeKeyOutputPath = "$PSScriptRoot/ExchangeKey.cer"
293+
Import-AzKeyVaultSecurityDomain -Name $hsmName -OutFile $exchangeKeyOutputPath -DownloadExchangeKey
294+
}
295+
296+
# This command can be executed offline
297+
It 'Can decrypt and encrypt Security Domain Data locally' {
298+
$exchangeKeyPath = "$PSScriptRoot/ExchangeKey.cer"
299+
$SecurityDomainRestoredBlob = "$PSScriptRoot/HsmRestoredBlob.json"
300+
Import-AzKeyVaultSecurityDomain -Keys $certsKeys -ExchangeKeyPath $exchangeKeyPath -SecurityDomainPath $sd.FullName -OutFile $SecurityDomainRestoredBlob -RestoreBlob
301+
}
302+
303+
It 'Can Import Security Domain by Restore Blob' {
304+
$SecurityDomainRestoredBlob = "$PSScriptRoot/HsmRestoredBlob.json"
305+
Import-AzKeyVaultSecurityDomain -Name $hsmName -SecurityDomainPath $SecurityDomainRestoredBlob -ImportRestoredBlob
306+
}
307+
308+
# Cannot test importing because it needs another HSM
309+
# Import-AzKeyVaultSecurityDomain -Name $hsmName -Keys $certsKeys -SecurityDomainPath $sd.FullName
310+
}
311+
281312
Describe 'Custom Role Definition' {
282313
$roleName = "my custom role"
283314
$roleDesc = "description for my role"

src/KeyVault/KeyVault/ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
- Additional information about change #1
1919
-->
2020
## Upcoming Release
21+
* Supported splitting `Import-AzKeyVaultSecurityDomain` process into three steps to allow keys to be hidden offline.
22+
- Added `DownloadExchangeKey`, `RestoreBlob` and `ImportRestoredBlob` in `Import-AzKeyVaultSecurityDomain`.
2123

2224
## Version 4.11.0
2325
* Fixed certificate policy bugs if DnsName is null. [#22642]

src/KeyVault/KeyVault/SecurityDomain/Cmdlets/BackupSecurityDomain.cs renamed to src/KeyVault/KeyVault/SecurityDomain/Cmdlets/ExportAzKeyVaultSecurityDomain.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Microsoft.Azure.Commands.Common.Authentication;
2+
using Microsoft.Azure.Commands.KeyVault.Models;
23
using Microsoft.Azure.Commands.KeyVault.Properties;
4+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
35
using System;
46
using System.Linq;
57
using System.Management.Automation;
@@ -9,8 +11,21 @@ namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets
911
{
1012
[Cmdlet(VerbsData.Export, ResourceManager.Common.AzureRMConstants.AzurePrefix + CmdletNoun.KeyVault + "SecurityDomain", SupportsShouldProcess = true, DefaultParameterSetName = ByName)]
1113
[OutputType(typeof(bool))]
12-
public class BackupSecurityDomain: SecurityDomainCmdlet
14+
public class ExportAzKeyVaultSecurityDomain: SecurityDomainCmdlet
1315
{
16+
protected const string ByName = "ByName";
17+
protected const string ByInputObject = "ByInputObject";
18+
// protected const string ByResourceId = "ByResourceID";
19+
20+
[Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = ByName)]
21+
[Alias("HsmName")]
22+
[ValidateNotNullOrEmpty]
23+
public string Name { get; set; }
24+
25+
[Parameter(HelpMessage = "Object representing a managed HSM.", Mandatory = true, ParameterSetName = ByInputObject, ValueFromPipeline = true)]
26+
[ValidateNotNull]
27+
public PSKeyVaultIdentityItem InputObject { get; set; }
28+
1429
[Parameter(HelpMessage = "Paths to the certificates that are used to encrypt the security domain data.", Mandatory = true)]
1530
[ValidateNotNullOrEmpty()]
1631
public string[] Certificates { get; set; }
@@ -31,6 +46,10 @@ public class BackupSecurityDomain: SecurityDomainCmdlet
3146

3247
public override void DoExecuteCmdlet()
3348
{
49+
if (this.IsParameterBound(c => c.InputObject))
50+
{
51+
Name = InputObject.VaultName;
52+
}
3453
ValidateParameters();
3554

3655
var certificates = Certificates.Select(path => new X509Certificate2(ResolveUserPath(path)));
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
using Microsoft.Azure.Commands.Common.Authentication;
2+
using Microsoft.Azure.Commands.KeyVault.Models;
3+
using Microsoft.Azure.Commands.KeyVault.Properties;
4+
using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Common;
5+
using Microsoft.Azure.Commands.KeyVault.SecurityDomain.Models;
6+
using Microsoft.WindowsAzure.Commands.Utilities.Common;
7+
using Newtonsoft.Json;
8+
using System;
9+
using System.IO;
10+
using System.Linq;
11+
using System.Management.Automation;
12+
using System.Security.Cryptography;
13+
using System.Security.Cryptography.X509Certificates;
14+
15+
namespace Microsoft.Azure.Commands.KeyVault.SecurityDomain.Cmdlets
16+
{
17+
[Cmdlet(VerbsData.Import, ResourceManager.Common.AzureRMConstants.AzurePrefix + CmdletNoun.KeyVault + "SecurityDomain", SupportsShouldProcess = true, DefaultParameterSetName = ByName)]
18+
[OutputType(typeof(bool))]
19+
public class ImportAzKeyVaultSecurityDomain : SecurityDomainCmdlet
20+
{
21+
protected const string DoRestoreBlob = "DoRestoreBlob";
22+
protected const string ByRestoredBlob = "ByRestoredBlob";
23+
protected const string GenerateExchangeKey = "GenerateExchangeKey";
24+
protected const string ByName = "ByName";
25+
protected const string ByInputObject = "ByInputObject";
26+
// protected const string ByResourceId = "ByResourceID";
27+
28+
[Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = ByName)]
29+
[Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = ByRestoredBlob)]
30+
[Parameter(HelpMessage = "Name of the managed HSM.", Mandatory = true, ParameterSetName = GenerateExchangeKey)]
31+
[Alias("HsmName")]
32+
[ValidateNotNullOrEmpty]
33+
public string Name { get; set; }
34+
35+
[Parameter(HelpMessage = "Object representing a managed HSM.", Mandatory = true, ParameterSetName = ByInputObject, ValueFromPipeline = true)]
36+
[ValidateNotNull]
37+
public PSKeyVaultIdentityItem InputObject { get; set; }
38+
39+
[Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data. See examples for how it is constructed.", Mandatory = true, ParameterSetName = ByName)]
40+
[Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data. See examples for how it is constructed.", Mandatory = true, ParameterSetName = ByInputObject)]
41+
[Parameter(HelpMessage = "Information about the keys that are used to decrypt the security domain data. See examples for how it is constructed.", Mandatory = true, ParameterSetName = DoRestoreBlob)]
42+
[ValidateNotNullOrEmpty]
43+
public KeyPath[] Keys { get; set; }
44+
45+
[Parameter(HelpMessage = "Specify the path to the encrypted security domain data.", Mandatory = true, ParameterSetName = ByName)]
46+
[Parameter(HelpMessage = "Specify the path to the encrypted security domain data.", Mandatory = true, ParameterSetName = ByInputObject)]
47+
[Parameter(HelpMessage = "Specify the path to the prepared security domain data.", Mandatory = true, ParameterSetName = ByRestoredBlob)]
48+
[Parameter(HelpMessage = "Specify the path to the encrypted security domain data.", Mandatory = true, ParameterSetName = DoRestoreBlob)]
49+
[Alias("Path")]
50+
[ValidateNotNullOrEmpty]
51+
public string SecurityDomainPath { get; set; }
52+
53+
[Parameter(HelpMessage = "Local file path to store the security domain encrypted with the exchange key.", Mandatory = true, ParameterSetName = DoRestoreBlob)]
54+
[Parameter(HelpMessage = "Local file path to store the exported key.", Mandatory = true, ParameterSetName = GenerateExchangeKey)]
55+
[ValidateNotNullOrEmpty]
56+
public string OutFile { get; set; }
57+
58+
[Parameter(HelpMessage = "Local path of exchange key used to encrypt the security domain data. Generated by running Import-AzKeyVaultSecurityDomain with -DownloadExchangeKey.", Mandatory = true, ParameterSetName=DoRestoreBlob)]
59+
[ValidateNotNullOrEmpty]
60+
public string ExchangeKeyPath { get; set; }
61+
62+
63+
[Parameter(HelpMessage = "Specify whether to overwrite existing file.", ParameterSetName = GenerateExchangeKey)]
64+
[Parameter(HelpMessage = "Specify whether to overwrite existing file.", ParameterSetName = DoRestoreBlob)]
65+
public SwitchParameter Force { get; set; }
66+
67+
[Parameter(HelpMessage = "When specified, an exchange key will be downloaded to specified path.", Mandatory = true, ParameterSetName = GenerateExchangeKey)]
68+
public SwitchParameter DownloadExchangeKey { get; set; }
69+
70+
[Parameter(HelpMessage = "When specified, the security domain data will be decrypted and encrypted using generated ExchangeKey locally.", Mandatory = true, ParameterSetName = DoRestoreBlob)]
71+
public SwitchParameter RestoreBlob { get; set; }
72+
73+
[Parameter(HelpMessage = "When specified, SecurityDomainPath should be encrypted security domain data generated by Restore-AzKeyVaultSecurityDomainBlob.", Mandatory = true, ParameterSetName = ByRestoredBlob)]
74+
public SwitchParameter ImportRestoredBlob { get; set; }
75+
76+
[Parameter(HelpMessage = "When specified, a boolean will be returned when cmdlet succeeds.")]
77+
public SwitchParameter PassThru { get; set; }
78+
79+
public override void DoExecuteCmdlet()
80+
{
81+
if (this.IsParameterBound(c => c.InputObject))
82+
{
83+
Name = InputObject.VaultName;
84+
}
85+
if (ParameterSetName == GenerateExchangeKey)
86+
{
87+
if (ShouldProcess($"managed HSM {Name}", $"download exported key to '{OutFile}'"))
88+
{
89+
var exchangeKey = Client.DownloadSecurityDomainExchangeKeyAsPem(Name, CancellationToken);
90+
OutFile = ResolveUserPath(OutFile);
91+
92+
if (!AzureSession.Instance.DataStore.FileExists(OutFile) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutFile), Resources.FileOverwriteCaption))
93+
{
94+
AzureSession.Instance.DataStore.WriteFile(OutFile, exchangeKey);
95+
WriteDebug($"Security domain data of managed HSM '{Name}' downloaded to '{OutFile}'.");
96+
}
97+
}
98+
}
99+
else if (ParameterSetName == DoRestoreBlob)
100+
{
101+
ValidateParameters();
102+
if (ShouldProcess($"Generating file {OutFile}", $"restore security domain data from file \"{SecurityDomainPath}\""))
103+
{
104+
Keys = Keys.Select(key => new KeyPath()
105+
{
106+
PublicKey = ResolveUserPath(key.PublicKey),
107+
PrivateKey = ResolveUserPath(key.PrivateKey)
108+
}).ToArray();
109+
ExchangeKeyPath = ResolveUserPath(ExchangeKeyPath);
110+
OutFile = ResolveUserPath(OutFile);
111+
112+
// Decrypt using Private Keys
113+
var securityDomain = LoadSdFromFile(ResolveUserPath(SecurityDomainPath));
114+
var rawSecurityDomain = Client.DecryptSecurityDomain(securityDomain, Keys);
115+
// Encript using Exchange Keys Generated by Initialize-AzKeyVaultSecurityDomainRecovery
116+
var exchangeKey = new X509Certificate2(ExchangeKeyPath);
117+
var encryptedSecurityDomain = Client.EncryptForRestore(rawSecurityDomain, exchangeKey);
118+
string securityDomainBlob = JsonConvert.SerializeObject(encryptedSecurityDomain);
119+
120+
if (!AzureSession.Instance.DataStore.FileExists(OutFile) || Force || ShouldContinue(string.Format(Resources.FileOverwriteMessage, OutFile), Resources.FileOverwriteCaption))
121+
{
122+
AzureSession.Instance.DataStore.WriteFile(OutFile, securityDomainBlob);
123+
WriteDebug($"Security domain data of exported managed HSM '{SecurityDomainPath}' restored to '{OutFile}'.");
124+
}
125+
}
126+
}
127+
else
128+
{
129+
if (ShouldProcess($"managed HSM {Name}", $"restore security domain data from file \"{SecurityDomainPath}\""))
130+
{
131+
var securityDomainRestore = JsonConvert.DeserializeObject<SecurityDomainRestoreData>(Utils.FileToString(SecurityDomainPath));
132+
133+
if (!ImportRestoredBlob)
134+
{
135+
ValidateParameters();
136+
var securityDomain = LoadSdFromFile(ResolveUserPath(SecurityDomainPath));
137+
Keys = Keys.Select(key => new KeyPath()
138+
{
139+
PublicKey = this.ResolveUserPath(key.PublicKey),
140+
PrivateKey = this.ResolveUserPath(key.PrivateKey)
141+
}).ToArray();
142+
143+
var rawSecurityDomain = Client.DecryptSecurityDomain(securityDomain, Keys);
144+
var exchangeKey = Client.DownloadSecurityDomainExchangeKey(Name, CancellationToken);
145+
securityDomainRestore = Client.EncryptForRestore(rawSecurityDomain, exchangeKey);
146+
}
147+
148+
Client.RestoreSecurityDomain(Name, securityDomainRestore, CancellationToken);
149+
}
150+
}
151+
if (PassThru)
152+
{
153+
WriteObject(true);
154+
}
155+
156+
}
157+
158+
private void ValidateParameters()
159+
{
160+
if (Keys.Length < 2)
161+
{
162+
throw new ArgumentException(string.Format(Resources.RestoreSecurityDomainNotEnoughKey, Common.Constants.MinQuorum));
163+
}
164+
if (Keys.Any(key => string.IsNullOrEmpty(key.PublicKey) || string.IsNullOrEmpty(key.PrivateKey)))
165+
{
166+
throw new ArgumentException(Resources.RestoreSecurityDomainBadKey);
167+
}
168+
}
169+
170+
private SecurityDomainData LoadSdFromFile(string path)
171+
{
172+
try
173+
{
174+
string content = Utils.FileToString(path);
175+
if (string.IsNullOrWhiteSpace(content))
176+
{
177+
throw new ArgumentException(nameof(SecurityDomainPath));
178+
}
179+
return JsonConvert.DeserializeObject<SecurityDomainData>(content);
180+
}
181+
catch (Exception ex)
182+
{
183+
throw new Exception(
184+
string.Format(Resources.LoadSecurityDomainFileFailed, path), ex);
185+
}
186+
}
187+
}
188+
}

src/KeyVault/KeyVault/SecurityDomain/Cmdlets/RestoreSecurityDomain.cs

Lines changed: 0 additions & 79 deletions
This file was deleted.

0 commit comments

Comments
 (0)