ShieldedVMDeployment/ShieldedVMDeployment.psm1
# Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. function GetPdk($Path) { $pdk = CimCmdlets\Invoke-CimMethod -ClassName Msps_ProvisioningFileProcessor -Namespace root\msps -MethodName PopulateFromFile -Arguments @{ FilePath = $ShieldingDataFilePath } -Verbose:$false -ErrorAction SilentlyContinue if (-not $pdk) { throw "Could not read PDK file." } return $pdk.ProvisioningFile } function GetSecuritySettingDataString($vm) { try { $cimvm = CimCmdlets\Get-CimInstance -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "Name = '$($vm.VMId)'" -Verbose:$false -ErrorAction Stop $vsd = CimCmdlets\Get-CimAssociatedInstance -InputObject $cimvm -ResultClassName "Msvm_VirtualSystemSettingData" -Verbose:$false -ErrorAction Stop $ssd = CimCmdlets\Get-CimAssociatedInstance -InputObject $vsd -ResultClassName "Msvm_SecuritySettingData" -Verbose:$false -ErrorAction Stop $cimSerializer = [Microsoft.Management.Infrastructure.Serialization.CimSerializer]::Create() $ssdString = [System.Text.Encoding]::Unicode.GetString($cimSerializer.Serialize($ssd, [Microsoft.Management.Infrastructure.Serialization.InstanceSerializationOptions]::None)) } catch { throw "Could not read security setting data string" } return $ssdString } function GetSecurityService($vm) { try { $cimvm = CimCmdlets\Get-CimInstance -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "Name = '$($vm.VMId)'" -Verbose:$false -ErrorAction Stop $ss = CimCmdlets\Get-CimAssociatedInstance -InputObject $cimvm -ResultClassName "Msvm_SecurityService" -Verbose:$false -ErrorAction Stop } catch { throw "Could not get security service for VM" } return $ss } function SetSecurityPolicy($vm, $pdk) { try { $ss = GetSecurityService -vm $vm $ssdString = GetSecuritySettingDataString -vm $vm $null = CimCmdlets\Invoke-CimMethod -InputObject $ss -MethodName SetSecurityPolicy -Arguments @{ "SecuritySettingData" = $ssdString; "SecurityPolicy" = $pdk.PolicyData } -Verbose:$false -ErrorAction Stop } catch { throw "Could not apply security policy to VM" } } function IsPdkForExistingVM($pdk) { return (-not $pdk.VolumeIDFilters) } function New-ShieldedVM { <# .SYNOPSIS Creates a new shielded virtual machine. .DESCRIPTION Creates a new shielded virtual machine using a prepared template disk and shielding data file. The virtual machine will go through shielded VM provisioning, which re-encrypts the OS volume and may take several minutes to complete. Use the -Wait parameter to observe the progress. .PARAMETER Name Name of the new virtual machine. .PARAMETER TemplateDiskPath Location of the template disk that has been prepared for use with shielded virtual machines. .PARAMETER ShieldingDataFilePath Location of the shielding data file used to configure the shielded VM. .PARAMETER SwitchName Name of network switch to which the VM should be connected. If no switch name is provided, and there is only one switch configured in Hyper-V, the VM will be connected to that switch. .PARAMETER Linux Indicates that the VM will run a Linux-based operating system. .PARAMETER MemoryStartupBytes Amount of memory to allocate to the VM (defaults to 2GB). .PARAMETER CpuCount Number of virtual processors to allocate to the VM (defaults to 2). .PARAMETER VMPath Location to store the resulting VM. If omitted, the default VM path configured in Hyper-V will be used for VM storage. .PARAMETER SpecializationValues Key-value pairs to replace in the shielding data answer file. @ComputerName@ is automatically set using the value of the -Name parameter, but can be overridden if desired. .PARAMETER Wait Shows the progress of the provisioning job and waits to return control until the VM is provisioned. .EXAMPLE New-ShieldedVM -Name 'CorpDC01' -TemplateDiskPath '.\WS2016-Template.vhdx' -ShieldingDataFilePath '.\DC.pdk' -SwitchName 'corpnet' Creates a new Windows shielded VM called "CorpDC01" using the specified templtae disk and shielding data file. .EXAMPLE New-ShieldedVM -Name 'ExampleVM' -TemplateDiskPath '.\template.vhdx' -ShieldingDataFilePath '.\myvm.pdk' -SpecializationValues @{ '@ComputerName@' = 'myVM01' } Creates a new Windows shielded VM with a custom replacement for the @ComputerName@ property in the shielding data answer file. #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] $Name, [Parameter(Mandatory=$true)] [string] $TemplateDiskPath, [Parameter(Mandatory=$true)] [string] $ShieldingDataFilePath, [string] $SwitchName, [switch] $Linux, [ValidateRange(1GB, 1TB)] [Int64] $MemoryStartupBytes = 2GB, [ValidateRange(1,240)] [int] $CpuCount = 2, [string] $VMPath, [System.Collections.IDictionary] $SpecializationValues, [switch] $Wait ) ## Parameter validation # Check for invalid names (relating to file paths assumed later) if ($Name -match '"|<|>|\||\\|/|\*|\?') { throw [System.ArgumentException] "Name cannot contain the following characters: `", ?, *, |, \, /, <, >" } # Ensure template disk exists $TemplateDiskPath = Microsoft.PowerShell.Management\Resolve-Path $TemplateDiskPath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path if (-not (Microsoft.PowerShell.Management\Test-Path $TemplateDiskPath -PathType Leaf) -or $TemplateDiskPath -notlike "*.vhdx") { throw [System.IO.FileNotFoundException] "The template disk path is invalid." } # Ensure shielding data file exists $ShieldingDataFilePath = Microsoft.PowerShell.Management\Resolve-Path $ShieldingDataFilePath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path if (-not (Microsoft.PowerShell.Management\Test-Path $ShieldingDataFilePath -PathType Leaf) -or $ShieldingDataFilePath -notlike "*.pdk") { throw [System.IO.FileNotFoundException] "The shielding data file path is invalid." } # Ensure the switch name is valid if ($SwitchName -and -not (Hyper-V\Get-VMSwitch -Name $SwitchName -ErrorAction SilentlyContinue)) { throw [System.ArgumentException] ("A networking switch with the name '{0}' could not be found on the host." -f $SwitchName) } elseif (-not $SwitchName) { $switches = Hyper-V\Get-VMSwitch if ($switches.Count -eq 1) { Microsoft.PowerShell.Utility\Write-Verbose ("No switch name was provided. The VM will be connected to the '{0}' switch." -f $switches.Name) $SwitchName = $switches.Name } else { throw [System.ArgumentException] ("More than one VM switch was found. Re-run the command and specify one of the following switch names to the -SwitchName parameter: {0}" -f ` [string]::Join(', ', $switches.Name)) } } # Ensure the VM path is valid if ($VMPath) { $VMPath = Microsoft.PowerShell.Management\Resolve-Path $VMPath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path if (-not (Microsoft.PowerShell.Management\Test-Path $VMPath -PathType Container)) { throw [System.IO.DirectoryNotFoundException] "The VM path is not a valid directory." } $VhdDirectory = Microsoft.PowerShell.Management\Join-Path $VMPath "Virtual Hard Disks" if (-not (Microsoft.PowerShell.Management\Test-Path $VhdDirectory)) { Microsoft.PowerShell.Utility\Write-Verbose ("Creating directory for VHD at '{0}'" -f $VhdDirectory) $null = Microsoft.PowerShell.Management\New-Item $VhdDirectory -ItemType Directory } } else { $vmroot = (Hyper-V\Get-VMHost).VirtualMachinePath $VMPath = Microsoft.PowerShell.Management\Join-Path $vmroot $Name if ((Microsoft.PowerShell.Management\Test-Path $VMPath)) { foreach ($i in 1..999) { $samplePath = "{0}-{1:D3}" -f $Name, $i $VMPath = Microsoft.PowerShell.Management\Join-Path $vmroot $samplePath if (-not (Microsoft.PowerShell.Management\Test-Path $VMPath)) { break } } } Microsoft.PowerShell.Utility\Write-Verbose ("Creating a VM directory at '{0}'" -f $VMPath) $null = Microsoft.PowerShell.Management\New-Item -Path $VMPath -ItemType Directory -ErrorAction Stop $VhdDirectory = Microsoft.PowerShell.Management\Join-Path $VMPath "Virtual Hard Disks" $null = Microsoft.PowerShell.Management\New-Item -Path $VhdDirectory -ItemType Directory -ErrorAction Stop } # Ensure specialization values are not null if ($SpecializationValues) { foreach ($key in $SpecializationValues.Keys) { if ($key -isnot [string] -or $key -notlike "@*@") { throw [System.ArgumentException] ("Specialization key '{0}' is invalid. All specialization keys must be in the form '@KeyName@'." -f $key) } $value = $SpecializationValues.$key if ($value -isnot [string] -or [string]::IsNullOrEmpty($value)) { throw [System.ArgumentException] ("The value for specialization key '{0}' is invalid. Values must be non-empty strings." -f $key) } } } ## Create the VM $VHDPath = Microsoft.PowerShell.Management\Join-Path $VhdDirectory "$Name-OS.vhdx" Microsoft.PowerShell.Utility\Write-Verbose ("Copying the template disk to '{0}'" -f $VHDPath) Microsoft.PowerShell.Management\Copy-Item -Path $TemplateDiskPath -Destination $VHDPath -ErrorAction Stop Microsoft.PowerShell.Utility\Write-Verbose "Creating the new VM" $vm = Hyper-V\New-VM -Name $Name -Generation 2 -Path $VMPath -VhdPath $VHDPath -SwitchName $SwitchName -MemoryStartupBytes $MemoryStartupBytes -ErrorAction Stop Hyper-V\Set-VMProcessor -VM $vm -Count $CpuCount if ($Linux) { Hyper-V\Set-VMFirmware -VM $vm -SecureBootTemplate OpenSourceShieldedVM -ErrorAction Stop } # Attach the key protector $kp = Get-KeyProtectorFromShieldingDataFile -ShieldingDataFilePath $ShieldingDataFilePath Hyper-V\Set-VMKeyProtector -VM $vm -KeyProtector $kp -ErrorAction Stop # Get the security data from the PDK file try { $pdk = CimCmdlets\Invoke-CimMethod -ClassName Msps_ProvisioningFileProcessor -Namespace root\msps -MethodName PopulateFromFile -Arguments @{ FilePath = $ShieldingDataFilePath } -Verbose:$false -ErrorAction Stop $cimvm = CimCmdlets\Get-CimInstance -Namespace root\virtualization\v2 -Class Msvm_ComputerSystem -Filter "Name = '$($vm.VMId)'" -Verbose:$false -ErrorAction Stop $vsd = CimCmdlets\Get-CimAssociatedInstance -InputObject $cimvm -ResultClassName "Msvm_VirtualSystemSettingData" -Verbose:$false -ErrorAction Stop $ssd = CimCmdlets\Get-CimAssociatedInstance -InputObject $vsd -ResultClassName "Msvm_SecuritySettingData" -Verbose:$false -ErrorAction Stop $ss = CimCmdlets\Get-CimAssociatedInstance -InputObject $cimvm -ResultClassName "Msvm_SecurityService" -Verbose:$false -ErrorAction Stop $cimSerializer = [Microsoft.Management.Infrastructure.Serialization.CimSerializer]::Create() $ssdString = [System.Text.Encoding]::Unicode.GetString($cimSerializer.Serialize($ssd, [Microsoft.Management.Infrastructure.Serialization.InstanceSerializationOptions]::None)) } catch { throw "A security policy could not be created from the shielding data file.`n`n$($_.Message)" } # Apply the VM security policy and enable the VM TPM Microsoft.PowerShell.Utility\Write-Verbose "Enabling VM TPM" $null = CimCmdlets\Invoke-CimMethod -InputObject $ss -MethodName SetSecurityPolicy -Arguments @{ "SecuritySettingData" = $ssdString; "SecurityPolicy" = $pdk.ProvisioningFile.PolicyData } -Verbose:$false -ErrorAction Stop Hyper-V\Enable-VMTPM -VM $vm -ErrorAction Stop # Create the fabric specialization keyfile Microsoft.PowerShell.Utility\Write-Verbose "Creating specialization data file" $fskPath = Microsoft.PowerShell.Management\Join-Path $VMPath "SpecializationData.fsk" $simpleComputerName = $Name -replace '[^\w-]', '' $fskParams = @{ '@ComputerName@' = $simpleComputerName } if ($SpecializationValues) { foreach ($key in $SpecializationValues.Keys) { $fskParams.$key = $SpecializationValues.$key } } ShieldedVMProvisioning\New-ShieldedVMSpecializationDataFile -ShieldedVMSpecializationDataFilePath $fskPath -SpecializationDataPairs $fskParams -ErrorAction Stop # Provision the VM Microsoft.PowerShell.Utility\Write-Verbose "Initiating shielded VM provisioning process" $provisioningJob = ShieldedVMProvisioning\Initialize-ShieldedVM -VM $vm -ShieldingDataFilePath $ShieldingDataFilePath -ShieldedVMSpecializationDataFilePath $fskPath -ErrorAction Stop if ($Wait) { do { $status = ShieldedVMProvisioning\Get-ShieldedVMProvisioningStatus -VM $vm Write-Progress -Activity ("Provisioning shielded VM '{0}'" -f $Name) -PercentComplete $status.PercentComplete -Status ("{0}% complete" -f $Status.PercentComplete) Microsoft.PowerShell.Utility\Start-Sleep -Milliseconds 1500 } while ($status -and $status.Status -ne 'Error' -and $status.PercentComplete -lt 100) if ($status) { Microsoft.PowerShell.Utility\Write-Output $status.JobStatus if ($status.ErrorDescription) { Microsoft.PowerShell.Utility\Write-Error $status.ErrorDescription } } else { Microsoft.Powershell.Utility\Write-Output "Unable to check the status of the shielded VM provisioning process." } } else { return $provisioningJob } } function ConvertTo-ShieldedVM { <# .SYNOPSIS Converts an existing virtual machine to a shielded virtual machine. .DESCRIPTION Converts an existing virtual machine to a shielded virtual machine by applying a virtual TPM and security policy to the VM. The VM will not automatically be encrypted by this command, but encryption can be enabled inside the guest OS after the virtual TPM is available. See https://aka.ms/AA3trat for information about automating the encryption of existing VMs. .PARAMETER VM Specifies the virtual machine to convert to a shielded virtual machine. .PARAMETER VMName Specifies the name of the virtual machine to convert to a shielded virtual machine. .PARAMETER ShieldingDataFilePath Path to a shielding data file used to shield the VM. The security policy and key protector from the shielding data file are applied to the specified VM. Answer files and additional files included in the shielding data file are not copied into the VM. .EXAMPLE $vm = Get-VM "SQL01" ConvertTo-ShieldedVM -VM $vm -ShieldingDataFilePath 'D:\ShieldingData\existingvm.pdk' Converts the "SQL01" virtual machine to a shielded virtual machine using the specified shielding data file. .EXAMPLE ConvertTo-ShieldedVM -VMName "CORPDC01" -ShieldingDataFilePath 'D:\ShieldingData\existingvm.pdk' Converts the "CORPDC01" virtual machine to a shielded virtual machine using the specified shielding data file. #> [CmdletBinding(DefaultParameterSetName="ByName", SupportsShouldProcess=$true, ConfirmImpact="Medium")] param( [Parameter(Mandatory=$true, Position=0, ParameterSetName="ByVM")] [ValidateNotNullOrEmpty()] [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, [Parameter(Mandatory=$true, Position=0, ParameterSetName="ByName")] [ValidateNotNullOrEmpty()] [string] $VMName, [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $ShieldingDataFilePath ) ## Parameter validation # Check VM name if ($PSCmdlet.ParameterSetName -eq "ByName") { $VM = Get-VM -Name $VMName -ErrorAction Stop } # Check if VM can be shielded if ($VM.Generation -ne 2) { throw "The specified virtual machine is a generation 1 VM. Only generation 2 VMs can be shielded." } $kp = Get-VMKeyProtector -VM $VM if ($kp.Length -gt 4) { throw "The specified virtual machine has already been configured with a key protector. To change the key protector, delete the VM configuration and create a new one. This will also delete the virtual TPM associated with this VM and could result in data loss if data encryption is not suspended first." } $firmware = Get-VMFirmware -VM $VM if ($firmware.SecureBoot -ne "On") { throw "Secure Boot must be enabled on the virtual machine before it can be shielded." } if ($vm.State -ne 'Off') { throw "The virtual machine must be turned off before it can be shielded." } $vmVersion = [System.Version] $vm.Version $minVersion = [System.Version] "8.0" if ($vmVersion -lt $minVersion) { $latestVersion = (Get-VMHostSupportedVersion -Default).Version.ToString() throw "The virtual machine configuration version is currently $($vm.Version) but must be upgraded before the virtual machine can be shielded. Run `"Update-VMVersion '$($vm.Name)'`" to upgrade the VM to version $latestVersion." } # Ensure shielding data file exists $ShieldingDataFilePath = Microsoft.PowerShell.Management\Resolve-Path $ShieldingDataFilePath -ErrorAction Stop | Microsoft.PowerShell.Management\Convert-Path if (-not (Microsoft.PowerShell.Management\Test-Path $ShieldingDataFilePath -PathType Leaf) -or $ShieldingDataFilePath -notlike "*.pdk") { throw [System.IO.FileNotFoundException] "The shielding data file path is invalid." } # Try opening the shielding data file try { $pdk = GetPDK -Path $ShieldingDataFilePath if (-not $pdk -or -not $pdk.PolicyData) { throw "Could not parse the shielding data file." } } catch { throw "Could not open the shielding data file." } # Warn if differencing disks are in use $VHDs = Get-VMHardDiskDrive -VM $VM foreach ($vhd in $VHDs) { $vhdDetails = Get-VHD -Path $vhd.Path if ($vhdDetails.VhdType -eq "Differencing") { Write-Warning "The specified virtual machine uses one or more differencing disks. To ensure all data is protected when you encrypt data on this virtual machine, delete all checkpoints and merge any differencing disks before enabling full volume encryption. Existing backups, checkpoints, and parent VHDs will not be encrypted if the guest OS enables full volume encryption." break } } ## Convert the VM if ($PSCmdlet.ShouldProcess("Converting '$($VM.Name)' to a shielded virtual machine", "Are you sure you want to convert '$($VM.Name)' to a shielded virtual machine?", "Shield existing VM")) { # Upgrade the VM version if necessary if ($vmVersion -lt $minVersion) { Write-Verbose "Upgrading VM version" Update-VMVersion -VM $VM -Force } # Attach the key protector Write-Verbose "Setting key protector on VM" $kp = Get-KeyProtectorFromShieldingDataFile -ShieldingDataFilePath $ShieldingDataFilePath Set-VMKeyProtector -VM $vm -KeyProtector $kp -ErrorAction Stop # Set the security policy Write-Verbose "Applying security policy to the VM" SetSecurityPolicy -vm $VM -pdk $pdk # Enable the VM TPM Write-Verbose "Enabling the VM TPM" Enable-VMTPM -VM $VM -ErrorAction Stop Write-Output "The virtual TPM has been enabled on the VM. To encrypt the VM data, turn on the VM and enable full volume encryption (leveraging the TPM to protect the encryption key) using the instructions for your operating system." } } # SIG # Begin signature block # MIIjtgYJKoZIhvcNAQcCoIIjpzCCI6MCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAg/3mkUXBOQ1M8 # 95I8s5glIqqsIs83hU/9kOfRzHXQ36CCDYEwggX/MIID56ADAgECAhMzAAABUZ6N # j0Bxow5BAAAAAAFRMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTkwNTAyMjEzNzQ2WhcNMjAwNTAyMjEzNzQ2WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQCVWsaGaUcdNB7xVcNmdfZiVBhYFGcn8KMqxgNIvOZWNH9JYQLuhHhmJ5RWISy1 # oey3zTuxqLbkHAdmbeU8NFMo49Pv71MgIS9IG/EtqwOH7upan+lIq6NOcw5fO6Os # +12R0Q28MzGn+3y7F2mKDnopVu0sEufy453gxz16M8bAw4+QXuv7+fR9WzRJ2CpU # 62wQKYiFQMfew6Vh5fuPoXloN3k6+Qlz7zgcT4YRmxzx7jMVpP/uvK6sZcBxQ3Wg # B/WkyXHgxaY19IAzLq2QiPiX2YryiR5EsYBq35BP7U15DlZtpSs2wIYTkkDBxhPJ # IDJgowZu5GyhHdqrst3OjkSRAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUV4Iarkq57esagu6FUBb270Zijc8w # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU0MTM1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAWg+A # rS4Anq7KrogslIQnoMHSXUPr/RqOIhJX+32ObuY3MFvdlRElbSsSJxrRy/OCCZdS # se+f2AqQ+F/2aYwBDmUQbeMB8n0pYLZnOPifqe78RBH2fVZsvXxyfizbHubWWoUf # NW/FJlZlLXwJmF3BoL8E2p09K3hagwz/otcKtQ1+Q4+DaOYXWleqJrJUsnHs9UiL # crVF0leL/Q1V5bshob2OTlZq0qzSdrMDLWdhyrUOxnZ+ojZ7UdTY4VnCuogbZ9Zs # 9syJbg7ZUS9SVgYkowRsWv5jV4lbqTD+tG4FzhOwcRQwdb6A8zp2Nnd+s7VdCuYF # sGgI41ucD8oxVfcAMjF9YX5N2s4mltkqnUe3/htVrnxKKDAwSYliaux2L7gKw+bD # 1kEZ/5ozLRnJ3jjDkomTrPctokY/KaZ1qub0NUnmOKH+3xUK/plWJK8BOQYuU7gK # YH7Yy9WSKNlP7pKj6i417+3Na/frInjnBkKRCJ/eYTvBH+s5guezpfQWtU4bNo/j # 8Qw2vpTQ9w7flhH78Rmwd319+YTmhv7TcxDbWlyteaj4RK2wk3pY1oSz2JPE5PNu # Nmd9Gmf6oePZgy7Ii9JLLq8SnULV7b+IP0UXRY9q+GdRjM2AEX6msZvvPCIoG0aY # HQu9wZsKEK2jqvWi8/xdeeeSI9FN6K1w4oVQM4Mwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVizCCFYcCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAVGejY9AcaMOQQAAAAABUTAN # BglghkgBZQMEAgEFAKCB3jAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgcKQa89X/ # d2XeMlkqOJ3D5qSMBResPlmcWwoN8uZ8pi8wcgYKKwYBBAGCNwIBDDFkMGKgQoBA # AEcAdQBhAHIAZABlAGQAIABGAGEAYgByAGkAYwAgAFQAbwBvAGwAcwAgAHYAZQBy # AHMAaQBvAG4AIAAxADEAMaEcgBpodHRwczovL3d3dy5taWNyb3NvZnQuY29tLzAN # BgkqhkiG9w0BAQEFAASCAQAZl2f++dpOfq07lIctnIOtcAz0m74kT8TVZ6JEA8vr # 8NxEf4868iHih1eTGC/HESc0SdALcip+LBRQpF9ZMT7WRu+veXNPuimxzJzdFlC7 # CwmX99vSjiNEWvQ+iTCXEQ7nWH3Kc7kN66GXOw+92o1xJTObDuWFygGUO3+3Vjfr # OQlkDE3iKzEF6eDXcKKq5Y4TKAWsGhz69Y0jq9FsB+ED+9qaBL+7BN1LdtwEsSHP # oS1Ya2A5na3u/c/AMFaKGxhTzghqlcFoQFsHp/7CLrr49WIjptlU9QCsWnA1/vpk # lRCIdpvD3FYCRXHE/dL4ZMzQ+z4sxhQWHE0I6nb/t5aloYIS5TCCEuEGCisGAQQB # gjcDAwExghLRMIISzQYJKoZIhvcNAQcCoIISvjCCEroCAQMxDzANBglghkgBZQME # AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIIb7qQ34D2uUFz9n4BPjFWZcILY9G1CKZkByyu8u # siZEAgZdb8hBMtoYEzIwMTkwOTIwMjMyODQ1LjgzMVowBIACAfSggdCkgc0wgcox # CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJXQTEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQg # SXJlbGFuZCBPcGVyYXRpb25zIExpbWl0ZWQxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg # RVNOOkUwNDEtNEJFRS1GQTdFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt # cCBzZXJ2aWNloIIOPDCCBPEwggPZoAMCAQICEzMAAADWnmWBjg0YozsAAAAAANYw # DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 # b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh # dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN # MTgwODIzMjAyNjQ5WhcNMTkxMTIzMjAyNjQ5WjCByjELMAkGA1UEBhMCVVMxCzAJ # BgNVBAgTAldBMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlv # bnMgTGltaXRlZDEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RTA0MS00QkVFLUZB # N0UxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIHNlcnZpY2UwggEiMA0G # CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDVz8BehZsCB+9oaa8v+wngEIhQqe02 # mIA57mmFSeE0+4I8xANhOj4MBzV9y0lpOp2zgQEeeIFP3lGbp9rV6OzcCw+TvFRs # GSPGYLU8AzuDdQRo8sNbwJl+a/HwDRS+Q5QG5aSq+qMWtVrBRf+EZuYlILaWOOym # uLMmaBJ+NadMeR0CP7KGJV5hwB6E04yynVHpy2mFEPAT4p+P2wBqsDePEdU6Mbtb # FC8gFcOBhJ7NBQS1fzRT8ecxMuL60PdbQIC99fvaM5+lCn19SDwmHxaIbOWXTaw2 # GJyYzeBXgriQ8JpM1WNnKHyRzKLvrk593cgklSDc0PGg8gYAONY6NNuJAgMBAAGj # ggEbMIIBFzAdBgNVHQ4EFgQUZ1Qwk+BJMRp+9k4XHKcRODePiPgwHwYDVR0jBBgw # FoAU1WM6XIoxkPNDe3xGG8UzaFqFbVUwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDov # L2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljVGltU3RhUENB # XzIwMTAtMDctMDEuY3JsMFoGCCsGAQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0 # cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNUaW1TdGFQQ0FfMjAx # MC0wNy0wMS5jcnQwDAYDVR0TAQH/BAIwADATBgNVHSUEDDAKBggrBgEFBQcDCDAN # BgkqhkiG9w0BAQsFAAOCAQEAT9KFgImb5aXD4JG1xS3+Zrf6ufSuWIQ9cbZwHDd9 # oxmNolN+zdL1Y/DyNWxiCBUG717EYmKBaKZbFj/0NH6nbW0H5fAbyJNB+LAAJYqI # saMO7gN/CFfni//IIBVOwCL5vEWiVuK9HTrt/PQ0fC5kC5up23hrcrb4CGfxywAm # YXOQX5zy0tOAoW6K3TOmcEghlzn3U06grq9hKf4F7Su2AmGN4V6JkHaGkegK8O3r # Z7JcsmzokiV4o1cRwaE2OkyVPpsbn6vZVe/HQSxxNNoBVjCgTovzdUCA0BoSH+T7 # XO6bwhtU3J6zkjf+3hY9RYXHxHoyN+L7a5Yp0IZAj7wmvzCCBnEwggRZoAMCAQIC # CmEJgSoAAAAAAAIwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRp # ZmljYXRlIEF1dGhvcml0eSAyMDEwMB4XDTEwMDcwMTIxMzY1NVoXDTI1MDcwMTIx # NDY1NVowfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCpHQ28dxGKOiDs/BOX9fp/aZRrdFQQ1aUKAIKF # ++18aEssX8XD5WHCdrc+Zitb8BVTJwQxH0EbGpUdzgkTjnxhMFmxMEQP8WCIhFRD # DNdNuDgIs0Ldk6zWczBXJoKjRQ3Q6vVHgc2/JGAyWGBG8lhHhjKEHnRhZ5FfgVSx # z5NMksHEpl3RYRNuKMYa+YaAu99h/EbBJx0kZxJyGiGKr0tkiVBisV39dx898Fd1 # rL2KQk1AUdEPnAY+Z3/1ZsADlkR+79BL/W7lmsqxqPJ6Kgox8NpOBpG2iAg16Hgc # sOmZzTznL0S6p/TcZL2kAcEgCZN4zfy8wMlEXV4WnAEFTyJNAgMBAAGjggHmMIIB # 4jAQBgkrBgEEAYI3FQEEAwIBADAdBgNVHQ4EFgQU1WM6XIoxkPNDe3xGG8UzaFqF # bVUwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGGMA8GA1Ud # EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186aGMQwVgYD # VR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwv # cHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsGAQUFBwEB # BE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraS9j # ZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwgaAGA1UdIAEB/wSBlTCB # kjCBjwYJKwYBBAGCNy4DMIGBMD0GCCsGAQUFBwIBFjFodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vUEtJL2RvY3MvQ1BTL2RlZmF1bHQuaHRtMEAGCCsGAQUFBwICMDQe # MiAdAEwAZQBnAGEAbABfAFAAbwBsAGkAYwB5AF8AUwB0AGEAdABlAG0AZQBuAHQA # LiAdMA0GCSqGSIb3DQEBCwUAA4ICAQAH5ohRDeLG4Jg/gXEDPZ2joSFvs+umzPUx # vs8F4qn++ldtGTCzwsVmyWrf9efweL3HqJ4l4/m87WtUVwgrUYJEEvu5U4zM9GAS # inbMQEBBm9xcF/9c+V4XNZgkVkt070IQyK+/f8Z/8jd9Wj8c8pl5SpFSAK84Dxf1 # L3mBZdmptWvkx872ynoAb0swRCQiPM/tA6WWj1kpvLb9BOFwnzJKJ/1Vry/+tuWO # M7tiX5rbV0Dp8c6ZZpCM/2pif93FSguRJuI57BlKcWOdeyFtw5yjojz6f32WapB4 # pm3S4Zz5Hfw42JT0xqUKloakvZ4argRCg7i1gJsiOCC1JeVk7Pf0v35jWSUPei45 # V3aicaoGig+JFrphpxHLmtgOR5qAxdDNp9DvfYPw4TtxCd9ddJgiCGHasFAeb73x # 4QDf5zEHpJM692VHeOj4qEir995yfmFrb3epgcunCaw5u+zGy9iCtHLNHfS4hQEe # gPsbiSpUObJb2sgNVZl6h3M7COaYLeqN4DMuEin1wC9UJyH3yKxO2ii4sanblrKn # QqLJzxlBTeCG+SqaoxFmMNO7dDJL32N79ZmKLxvHIa9Zta7cRDyXUHHXodLFVeNp # 3lfB0d4wwP3M5k37Db9dT+mdHhk4L7zPWAUu7w2gUDXa7wknHNWzfjUeCLraNtvT # X4/edIhJEqGCAs4wggI3AgEBMIH4oYHQpIHNMIHKMQswCQYDVQQGEwJVUzELMAkG # A1UECBMCV0ExEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD # b3Jwb3JhdGlvbjEtMCsGA1UECxMkTWljcm9zb2Z0IElyZWxhbmQgT3BlcmF0aW9u # cyBMaW1pdGVkMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpFMDQxLTRCRUUtRkE3 # RTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgc2VydmljZaIjCgEBMAcG # BSsOAwIaAxUAD1RfSr8v62EwlfSujM+3vZAgFdGggYMwgYCkfjB8MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg # VGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIFAOEvXnEwIhgPMjAx # OTA5MjAyMjE5MjlaGA8yMDE5MDkyMTIyMTkyOVowdzA9BgorBgEEAYRZCgQBMS8w # LTAKAgUA4S9ecQIBADAKAgEAAgIOOQIB/zAHAgEAAgIRkTAKAgUA4TCv8QIBADA2 # BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIB # AAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAB0zJBZ9xECOEjFPFPy52omKzZPUZp0V # EgDOBqC+5QW5BsdCJN4aT3jjkwqFTCNwaRzgiOxETBPQti6hQnyieCeINPawoXgA # JXt7ljgP6iAx3hHLWKGlv/25/Hrv/mUs9msmMpw9TPcWJGSSjfoactMrCqwxUNjY # OsoheAuV6CghMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgT # Cldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m # dCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENB # IDIwMTACEzMAAADWnmWBjg0YozsAAAAAANYwDQYJYIZIAWUDBAIBBQCgggFKMBoG # CSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgYDQvrHBW # zpOiKc3aJmATPl2MS/Xo9oSMfJ/eP2FlxKMwgfoGCyqGSIb3DQEJEAIvMYHqMIHn # MIHkMIG9BCAMpxcbLzk+qbGa3mRFNyw6oaNx47F25Vv+0ZgnLpRIzjCBmDCBgKR+ # MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT # HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAA1p5lgY4NGKM7AAAA # AADWMCIEIGVSBCZLkdpRzaB+pbu29xpqOSFu0n94CBvhi2r8qXqhMA0GCSqGSIb3 # DQEBCwUABIIBAID2BZEOo0GtzXuWo0ONk5YctdVZZ5UPOyJZJV4HYtEgKVmSPC5F # BaXouZLedMzAfPlVfEwmc4lX7avHE8BAQuHF4Xurrj4xU/98qgqGjjDVM7mKgig2 # 0Qnu+EG1rh/ZE+tXQNZRLghTG2ZhSnAn8HGKgBvcujYlK3HfeNlQTkMElDQWcRBX # 6bP5UgPtaqCNUwnbmf1d2Sh/gGPYYiUvFb6S/BQ4m4PTD7Gec03YLmCwt1RMW4QC # Elr0F2o7NBesbAqQ12NbmEPR4YlkKSM44sI58KW31S5tvGkUiVvcvthgpJfGnNz/ # xttmbguCpQLNX8Dkq/zyGthTaBeZVFR6paE= # SIG # End signature block |