ozo-ad-windows-enable-bitlocker.ps1
#Requires -Version 5.1 -RunAsAdministrator <#PSScriptInfo .VERSION 1.0.0 .GUID c253d6ab-fb10-4d07-b03c-f2b630a39fa1 .AUTHOR Andy Lievertz <alievertz@onezeroone.dev> .COMPANYNAME One Zero One .COPYRIGHT This script is released under the terms of the GNU General Public License ("GPL") version 2.0. .TAGS .LICENSEURI https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker/blob/main/LICENSE .PROJECTURI https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker/blob/main/CHANGELOG.md #> <# .DESCRIPTION Enables BitLocker on all local fixed volumes after verifying that the endpoint meets the BitLocker prerequisites and has successfully recorded a recovery password in Active Directory. .PARAMETER GPOName The name of the group policy containing the BitLocker settings. The script will proceed only if this GPO is applied. .PARAMETER Restart Restarts the computer after enabling BitLocker to perform the hardware test and begin encrypting. .PARAMETER SkipSecureBootCheck By default, the script will proceed only if Secure Boot is enabled, however, Secure Boot is not a strict requirement for enabling BitLocker. Specifying this parameter will allow the script to continue even if Secure Boot if off. .EXAMPLE powershell.exe -ExecutionPolicy Bypass -WindowStyle Hidden -NonInteractive -NoProfile -File ozo-ad-windows-enable-bitlocker.ps1 -GPOName "OZO BitLocker Settings Policy" -SkipSecureBootCheck .EXAMPLE ozo-ad-windows-enable-bitlocker -GPOName "All Workstations Settings" -Restart .LINK https://github.com/onezeroone-dev/OZO-AD-Windows-Enable-BitLocker/blob/main/README.md .NOTES Messages are written to the Windows Event Viewer "One Zero One" provider. When this provider is not available, messages are written to the Microsoft-Windows-PowerShell provider with Event ID 4100. #> # PARAMETERS [CmdletBinding(SupportsShouldProcess = $true)] Param ( [Parameter(Mandatory=$true,HelpMessage="BitLocker settings GPO name")][String]$GPOName, [Parameter(Mandatory=$false,HelpMessage="Restarts the computer after enabling BitLocker")][Switch]$Restart, [Parameter(Mandatory=$false,HelpMessage="Disables checking if Secure Boot is on")][Switch]$SkipSecureBootCheck ) # CLASSES Class OZOMain { # PROPERTIES: Booleans, Strings [Boolean] $Restart = $false [Boolean] $skipSBCheck = $false [XML] $rsopXml = $null # PROPERTIES: Lists [System.Collections.Generic.List[PSCustomObject]] $blVolumes = @() # METHODS: Constructor method OZOMain($GPOName,$Restart,$SkipSecureBootCheck) { # Set properties $this.Restart = $Restart # Letting skipSBCheck = $true for now - once we work out how to relaunch in 64-bit with parameters passed, this can be uncommented $this.skipSBCheck = $SkipSecureBootCheck # Declare ourselves to the world Write-OZOProvider -Message "Starting process." -Level "Information" # Determine if the environment validates, the GPO is refreshed, the required GPO is adopted, [Secure Boot is enabled], and the TPM is good If (($this.ValidateEnvironment() -And $this.GenerateRSoP() -And $this.EvaluateGPOSettings($GPOName) -And $this.EvaluateSecureBoot() -And $this.EvaluateTPM()) -eq $true) { # The environment validates, the GPO is refreshed, the required GPO is adopted, [Secure Boot is enabled], and the TPM is good; iterate on the BitLocker volume mount points ForEach ($mountPoint in (Get-BitLockerVolume).MountPoint) { # Determine if mount point is a letter If (($mountPoint -Split(":"))[0] -CMatch "^[A-Z]") { # Create an OZOBLVolume object for this mount point $this.blVolumes.Add(([OZOBLVolume]::new($mountPoint))) } } # Call RestartComputer to determine if a restart is requested and (if yes) execute it $this.RestartComputer() } # Declare ourselves to the world Write-OZOProvider -Message "Process complete." -Level "Information" } # METHODS: Environment validation method Hidden [Boolean] ValidateEnvironment() { # Control variable [Boolean] $Return = $true # Try to quietly import the BitLocker module Try { Import-Module -Name "BitLocker" -ErrorAction Stop *> $null # Success } Catch { # Failure Write-OZOProvider -Message "Error importing the BitLocker module." -Level "Error" $Return = $false } # Try to quietly import the TrustedPlatformModule module Try { Import-Module -Name "TrustedPlatformModule" -ErrorAction Stop *> $null # Success } Catch { # Failure Write-OZOProvider -Message "Error importing the TrustedPlatformModule module." -Level "Error" $Return = $false } # Return return $Return } # METHODS: Generate RSoP method Hidden [Boolean] GenerateRSoP() { # Control variable [Boolean] $Return = $true # Local variables [String]$rsopXmlPath = (Join-Path -Path $Env:windir -ChildPath ("Temp\" + (New-Guid).Guid + "-RSoP.xml")) # Try to generate an RSoP Try { Start-Process -FilePath (Join-Path -Path $Env:windir -ChildPath "System32\gpresult.exe") -ArgumentList "/scope","computer","/X",$rsopXmlPath -NoNewWindow -Wait -ErrorAction Stop # Success; try to read in XML Try { $this.rsopXml = [Xml](Get-Content -Path $rsopXmlPath -ErrorAction Stop) # Success; try to remove the file Try { Remove-Item -Force -Path $rsopXmlPath -ErrorAction Stop # Success } Catch { Write-OZOProvider -Message ("Failed to remove temporary RSoP XML results file, " + $rsopXmlPath + ".") -Level "Warning" } } Catch { # Failure Write-OZOProvider -Message ("Failed to read RSoP XML results file, " + $rsopXmlPath + ".") -Level "Warning" } } Catch { # Failure Write-OZOProvider -Message ("Failed to generate RSoP XML results file. Error message is: " + $_) -Level "Warning" $Return = $false } # Return return $Return } # METHODS: Evaluate GPO settings method Hidden [Boolean] EvaluateGPOSettings($GPOName) { # Control variable [Boolean] $Return = $true # Determine if the GPO name appears in the RSoP XML If ($this.rsopXml.Rsop.ComputerResults.GPO.Name -Contains $GPOName) { # GPO Name appears in the XML; determine if it is access denied If (($this.rsopXml.Rsop.ComputerResults.GPO | Where-Object {$_.Name -eq $GPOName}).AccessDenied -eq $true) { # Access is denied Write-OZOProvider -Message ("Access is denied to " + $GPOName + ".") -Level "Error" $Return = $false } } Else { # GPO Name does not appear in the XML Write-OZOProvider -Message ($GPOName + " not found.") -Level "Error" $Return = $false } # Return return $Return } # METHODS: Evaluate SecureBoot method Hidden [Boolean] EvaluateSecureBoot() { # Control variable [Boolean] $Return = $true # Determine if we are not skipping Secure Boot check If ($this.skipSBCheck -eq $false) { # We are not skipping Secure Boot check; determine if SecureBoot is present and enabled Try { Confirm-SecureBootUEFI -ErrorAction Stop # Success } Catch { # Failure Write-OZOProvider -Message "UEFI Secure Boot is present and disabled or not present." -Level "Error" $Return = $false } } # Return return $return } # METHODS: Evaluate TPM method Hidden [Boolean] EvaluateTPM() { # Control variable [Boolean] $Return = $true # Local variable [PSCustomObject] $Tpm = $null # Try to get the TPM Try { $Tpm = (Get-Tpm -ErrorAction Stop) # Success; determine if TPM If (((Get-WmiObject -class Win32_Tpm -namespace root\CIMV2\Security\MicrosoftTpm).SpecVersion -Split(",")) -Contains "2.0") { # TPM 2.0; determine if it does not meet requirements If (($Tpm.TpmPresent -And $Tpm.TpmReady -And $Tpm.TpmEnabled -And $Tpm.TpmActivated) -eq $false) { # One or more requirements not met Write-OZOProvider -Message "TPM 2.0 is not present and ready and enabled and activated." -Level "Error" $Return = $false } } Else { # TPM 1.2; determine if it does not meet requirements If (($Tpm.TpmPresent -And $Tpm.TpmReady) -eq $false) { # One or more requirements not met Write-OZOProvider -Message "TPM 1.2 is not present and ready." -Level "Error" $Return = $false } } } Catch { # Failure Write-OZOProvider -Message "TPM not found." -Level "Error" $Return = $false } # Return return $Return } # METHODS: RestartComputer method Hidden [Void] RestartComputer() { # Determine if the operator requested restart If ($this.Restart -eq $true) { # Operator requested restart Write-OZOProvider -Message "Computer will restart in 30 seconds." -Level "Warning" # Sleep for 30 seconds Start-Sleep -Seconds 30 # Force restart Restart-Computer -Force } } } Class OZOBLVolume { # PROPERTIES: Booleans and Strings [Boolean] $Validates = $false [String] $mountPoint = $null # PROPERTIES: System Objects [System.Object] $blVolume = $null [System.Object] $phyVolume = $null # METHODS: Constructor Method OZOBLVolume([String]$MountPoint) { # Set properties $this.mountPoint = $MountPoint # Read the volume $this.ReadVolume() # Switch on DriveType + VolumeStatus + ProtectionStatus Switch -Wildcard ($this.phyVolume.DriveType + " " + $this.blVolume.VolumeStatus + " " + $this.blVolume.ProtectionStatus) { ("Removable*") { # Removable disk; do nothing Write-OZOProvider -Message ($this.blVolume.MountPoint + " is a removable disk; skipping") -Level "Warning" } ("Fixed FullyEncrypted On") { # Fixed disk with BitLocker enabled and Protectionstatus on; determine if protection is not TPM + RecoveryPassword If ($this.blVolume.KeyProtector.KeyProtectorType -NotContains "Tpm" -And $this.blVolume.KeyProtector.KeyProtectorType -NotContains "RecoveryPassword") { # Volume does not have the correct protectors; decrypt then encrypt with proper protectors Write-OZOProvider -Message ($this.blVolume.MountPoint + " is encrypted but does not have the correct protectors; decrypting then re-encrypting.") -Level "Warning" $this.DisableBitLocker() $this.EnableBitLocker() } Else { # Volume has correct protectors Write-OZOProvider -Message ($this.blVolume.MountPoint + " is encrypted and already has the correct protectors; skipping") -Level "Information" } } ("Fixed FullyEncrypted Off") { # Fixed disk with BitLocker enabled but no key protectors; decrypt and encrypt Write-OZOProvider -Message ($this.blVolume.MountPoint + " is encrypted but has no protectors; decrypting then re-encrypting.") -Level "Warning" $this.DisableBitLocker() $this.EnableBitLocker() } ("Fixed FullyDecrypted Off") { # Determine if there are NOT Tpm and RecoveryPassword protectors (Fixed FullyDecrypted Off but with Tpm and RecoveryPassword protectors indicates that BitLocker has been enabled but is pending reboot) If ($this.blVolume.KeyProtector.KeyProtectorType -NotContains "Tpm" -And $this.blVolume.KeyProtector.KeyProtectorType -NotContains "RecoveryPassword") { # Fixed disk with no encryption and not pending reboot; encrypt Write-OZOProvider -Message ($this.blVolume.MountPoint + " is not encrypted; encrypting.") -Level "Warning" $this.EnableBitLocker() } Else { # Fixed disk with no encryption and pending reboot; skip Write-OZOProvider -Message ($this.blVolume.MountPoint + " is pending reboot; skipping.") -Level "Warning" } } default { # Nothing to do Write-OZOProvider -Message ($this.blVolume.MountPoint + " meets no criteria (likely already encrypted with correct protectors)") -Level "Information" } } # Reread the volume $this.ReadVolume() } # METHODS: Read volume method Hidden [Void] ReadVolume() { # Store the BitLocker volume object $this.blVolume = (Get-BitLockerVolume -MountPoint $this.mountPoint) # Store the physical volume object $this.phyVolume = (Get-Volume -DriveLetter ($this.mountPoint -Split(":"))[0]) } # METHODS: [Void] EnableBitLocker() { # Call ManageTpmProtector, ManageRecoveryPasswordProtector, and BackupKeyProtector to determine if we can enable BitLocker If (($this.ManageTpmProtector() -And $this.ManageRecoveryPasswordProtector() -And $this.BackupKeyProtector()) -eq $true) { # TPM Protector is managed, RecoveryPassword protector is managed and keys are backed up to AD; enable BitLocker Try { Enable-BitLocker -MountPoint $this.blVolume.MountPoint -TpmProtector -ErrorAction Stop # Success Write-OZOProvider -Message ("Enabled Bitlocker on " + $this.blVolume.MountPoint + ".") -Level "Information" # Determine if this volume is *not* the OS volume If ($this.blVolume.VolumeType -ne "OperatingSystem") { # Volume is not the OS volume; try to enable automagic unlock Try { Enable-BitLockerAutoUnlock -MountPoint $this.blVolume.MountPoint -ErrorAction Stop # Success Write-OZOProvider -Message ("Set auto-unlock for " + $this.blVolume.MountPoint + ".") -Level "Information" } Catch { # Failure Write-OZOProvider -Message ("Error setting auto-unlock on " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error" } } } Catch { # Failure Write-OZOProvider -Message ("Error enabling BitLocker on " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error" } } # Re-read the volume $this.ReadVolume() } # METHODS: Disable BitLocker method Hidden [Boolean] DisableBitLocker() { # Control variable [Boolean] $Return = $true # Try to decrypt Try { Disable-BitLocker -MountPoint $this.blVolume.MountPoint -ErrorAction Stop # Success; wait until decryption is complete Do { Start-Sleep -Seconds 60 Write-OZOProvider -Message ("Waiting for decryption to complete on " + $this.blVolume.MountPoint + ".") -Level "Warning" } Until ((Get-BitLockerVolume -MountPoint $this.blVolume.MountPoint).VolumeStatus -eq "FullyDecrypted") # Decryption complete; log Write-OZOProvider -Message ("Decryption of " + $this.blVolume.MountPoint + " is complete.") -Level "Information" } Catch { # Failure Write-OZOProvider -Message ("Decryption of " + $this.blVolume.MountPoint + " failed.") -Level "Error" $Return = $false } # Re-read the volume $this.ReadVolume() # Return return $Return } # METHODS: Manage TPM Protector method Hidden [Boolean] ManageTpmProtector() { # Control variable $Return = $true # Determine if a TPM Protector is defined If (($this.blVolume.KeyProtector | Where-Object {$_.KeyprotectorType -eq "Tpm"}).Count -gt 0) { # Found existing TPM protector (dealbreaker) Write-OZOProvider -Message ("TPM protector for " + $this.blVolume.MountPoint + " already exists; attempting to remove.") -Level "Warning" # Try to remove ForEach ($keyProtectorID in ($this.blVolume.KeyProtector.KeyProtectorID)) { Try { Remove-BitLockerKeyProtector -MountPoint $this.blVolume.MountPoint -KeyProtectorId $keyProtectorID -ErrorAction Stop # Success Write-OZOProvider -Message ("Removed Key Protector ID " + $keyProtectorID + " from " + $this.blVolume.MountPoint + ".") -Level "Information" } Catch { # Failure Write-OZOProvider -Message ("Unable to remove Key Protector ID " + $keyProtectorID + " from " + $this.blVolume.MountPoint + "; aborting.") -Level "Error" $Return = $false } } } # Re-read the volume $this.ReadVolume() # Return return $Return } # METHODS: Manage RecoveryPassword Protector method Hidden [Boolean] ManageRecoveryPasswordProtector() { # Control variable $Return = $true # Determine if a RecoveryPassword protector exists If (($this.blVolume.KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"}).Count -eq 0) { # None found; log Write-OZOProvider -Message ("No Recovery Password protector found for " + $this.blVolume.MountPoint + "; attempting to add.") -Level "Information" # Try to add a RecoveryPassword protector Try { Add-BitLockerKeyProtector -MountPoint $this.blVolume.MountPoint -RecoveryPasswordProtector -ErrorAction Stop # Success Write-OZOProvider -Message ("Added Recovery Password protector for " + $this.blVolume.MountPoint + ".") -Level "Information" } Catch { # Failure Write-OZOProvider -Message ("Error adding Recovery Password protector for " + $this.blVolume.MountPoint + ". Error message is: " + $_) -Level "Error" $Return = $false } } # Re-read volume $this.ReadVolume() # Return return $Return } # METHODS: Backup Key Protector method Hidden [Boolean] BackupKeyProtector() { # Control variable [Boolean] $Return = $true # Iterate through all RecoveryPassword protectors and back them up to AD ForEach ($keyProtector in ($this.blVolume.KeyProtector | Where-Object {$_.KeyProtectorType -eq "RecoveryPassword"})) { # Try to back up the key protector Try { Backup-BitLockerKeyProtector -MountPoint $this.blVolume.MountPoint -KeyProtectorId $keyProtector.KeyProtectorID -ErrorAction Stop # Success Write-OZOProvider -Message ("Recovery Password protector for " + $this.blVolume.MountPoint + " backed up to AD.") -Level "Information" } Catch { # Failure Write-OZOProvider -Message ("Error backing up Recovery Password protector for " + $this.blVolume.MountPoint + " to AD. Error message is: " + $_) -Level "Error" $Return = $false } } # Re-read volume $this.ReadVolume() # Return return $Return } } # FUNCTIONS Function Get-OZOPSArgumentList { # Parameters Param ( [Parameter(Mandatory=$true)][System.Object]$InvocationInfo ) # Local variables [System.Collections.Generic.List[String]]$ArgumentList = @() # Add arguments to list $ArgumentList.Add("-ExecutionPolicy") $ArgumentList.Add("ByPass") $ArgumentList.Add("-WindowStyle") $ArgumentList.Add("Hidden") $ArgumentList.Add("-NonInteractive") $ArgumentList.Add("-File") $ArgumentList.Add(('"' + $InvocationInfo.MyCommand.Definition + '"')) # Iterate through the bound parameters and add to list ForEach ($BoundParameter in $InvocationInfo.BoundParameters.GetEnumerator()) { Switch ($BoundParameter.Value.GetType().Name) { "String" { $ArgumentList.Add("-" + $BoundParameter.Key) $ArgumentList.Add('"' + $BoundParameter.Value + '"') } "SwitchParameter" { If ($BoundParameter.Value -eq $true) { $ArgumentList.Add("-" + $BoundParameter.Key) } } default { $ArgumentList.Add("-" + $BoundParameter.Key) $ArgumentList.Add($BoundParameter.Value) } } } # Return return $ArgumentList } Function Write-OZOProvider { [CmdLetBinding()] Param( [Parameter(Mandatory=$true)] [String] $Message, [Parameter(Mandatory=$false)][String] $Level = "Information" ) # Initialize variables [String] $Source = $null [Int32] $Id = 1000 # Switch on level to set Id Switch($Level.ToLower()) { "information" { $Id = 1000 } "warning" { $Id = 1001 } "error" { $Id = 1002 } default { $Id = 1000 } } # Set the script name If ([String]::IsNullOrEmpty($MyInvocation.ScriptName)) { $Source = "Command-line input:" } Else { $Source = ((Split-Path -Path $MyInvocation.ScriptName -Leaf) + ":") } # Try to write to the One Zero One log Try { # Write to One Zero One and suppress output New-WinEvent -ProviderName "One Zero One" -Id $Id -Payload $Source,$Message -ErrorAction Stop # Success } Catch { # Failure; write to Microsoft-Windows-PowerShell and suppress output New-WinEvent -ProviderName "Microsoft-Windows-PowerShell" -Id 4100 -Payload $Source,"Script output.",$Message } } # MAIN # Determine if this is a 64 bit process If ([Environment]::Is64BitOperatingSystem -eq $true -And [Environment]::Is64BitProcess -eq $false) { # Process is not 64-bit; log Write-OZOProvider -Message ("Running in 32-bit PowerShell; attempting to re-launch " + $MyInvocation.InvocationName + " in 64-bit PowerShell") -Level "Warning" # Re-launch using 64-bit PowerShell Start-Process -FilePath (Join-Path -Path $Env:Windir -ChildPath "SysNative\WindowsPowerShell\v1.0\powershell.exe") -ArgumentList (Get-OZOPSArgumentList -InvocationInfo $MyInvocation) -Wait -NoNewWindow -ErrorAction Stop } Else { # OS is 64-bit and PowerShell is 64-bit; log Write-OZOProvider -Message "Running in 64-bit PowerShell." -Level "Information" # Create a OZOMain object [OZOMain]::new($GPOName,$Restart,$SkipSecureBootCheck) | Out-Null } # SIG # Begin signature block # MIIfcgYJKoZIhvcNAQcCoIIfYzCCH18CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyZsFEKI90k0VO # nOr/yTc0IzSOx+e7A1xINC4fZLYvaqCCDPgwggZyMIIEWqADAgECAghkM1HTxzif # CDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMx # EDAOBgNVBAcMB0hvdXN0b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8G # A1UEAwwoU1NMLmNvbSBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IFJTQTAe # Fw0xNjA2MjQyMDQ0MzBaFw0zMTA2MjQyMDQ0MzBaMHgxCzAJBgNVBAYTAlVTMQ4w # DAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENv # cnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWduaW5nIEludGVybWVkaWF0ZSBD # QSBSU0EgUjEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfgxNzqrDG # bSHL24t6h3TQcdyOl3Ka5LuINLTdgAPGL0WkdJq/Hg9Q6p5tePOf+lEmqT2d0bKU # Vz77OYkbkStW72fL5gvjDjmMxjX0jD3dJekBrBdCfVgWQNz51ShEHZVkMGE6ZPKX # 13NMfXsjAm3zdetVPW+qLcSvvnSsXf5qtvzqXHnpD0OctVIFD+8+sbGP0EmtpuNC # GVQ/8y8Ooct8/hP5IznaJRy4PgBKOm8yMDdkHseudQfYVdIYyQ6KvKNc8HwKp4WB # wg6vj5lc02AlvINaaRwlE81y9eucgJvcLGfE3ckJmNVz68Qho+Uyjj4vUpjGYDdk # jLJvSlRyGMwnh/rNdaJjIUy1PWT9K6abVa8mTGC0uVz+q0O9rdATZlAfC9KJpv/X # gAbxwxECMzNhF/dWH44vO2jnFfF3VkopngPawismYTJboFblSSmNNqf1x1KiVgMg # Lzh4gL32Bq5BNMuURb2bx4kYHwu6/6muakCZE93vUN8BuvIE1tAx3zQ4XldbyDge # VtSsSKbt//m4wTvtwiS+RGCnd83VPZhZtEPqqmB9zcLlL/Hr9dQg1Zc0bl0EawUR # 0tOSjAknRO1PNTFGfnQZBWLsiePqI3CY5NEv1IoTGEaTZeVYc9NMPSd6Ij/D+KNV # t/nmh4LsRR7Fbjp8sU65q2j3m2PVkUG8qQIDAQABo4H7MIH4MA8GA1UdEwEB/wQF # MAMBAf8wHwYDVR0jBBgwFoAU3QQJB6L1en1SUxKSle44gCUNplkwMAYIKwYBBQUH # AQEEJDAiMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTARBgNVHSAE # CjAIMAYGBFUdIAAwEwYDVR0lBAwwCgYIKwYBBQUHAwMwOwYDVR0fBDQwMjAwoC6g # LIYqaHR0cDovL2NybHMuc3NsLmNvbS9zc2wuY29tLXJzYS1Sb290Q0EuY3JsMB0G # A1UdDgQWBBRUwv4QlQCTzWr158DX2bJLuI8M4zAOBgNVHQ8BAf8EBAMCAYYwDQYJ # KoZIhvcNAQELBQADggIBAPUPJodwr5miyvXWyfCNZj05gtOII9iCv49UhCe204MH # 154niU2EjlTRIO5gQ9tXQjzHsJX2vszqoz2OTwbGK1mGf+tzG8rlQCbgPW/M9r1x # xs19DiBAOdYF0q+UCL9/wlG3K7V7gyHwY9rlnOFpLnUdTsthHvWlM98CnRXZ7WmT # V7pGRS6AvGW+5xI+3kf/kJwQrfZWsqTU+tb8LryXIbN2g9KR+gZQ0bGAKID+260P # Z+34fdzZcFt6umi1s0pmF4/n8OdX3Wn+vF7h1YyfE7uVmhX7eSuF1W0+Z0duGwdc # +1RFDxYRLhHDsLy1bhwzV5Qe/kI0Ro4xUE7bM1eV+jjk5hLbq1guRbfZIsr0WkdJ # LCjoT4xCPGRo6eZDrBmRqccTgl/8cQo3t51Qezxd96JSgjXktefTCm9r/o35pNfV # HUvnfWII+NnXrJlJ27WEQRQu9i5gl1NLmv7xiHp0up516eDap8nMLDt7TAp4z5T3 # NmC2gzyKVMtODWgqlBF1JhTqIDfM63kXdlV4cW3iSTgzN9vkbFnHI2LmvM4uVEv9 # XgMqyN0eS3FE0HU+MWJliymm7STheh2ENH+kF3y0rH0/NVjLw78a3Z9UVm1F5VPz # iIorMaPKPlDRADTsJwjDZ8Zc6Gi/zy4WZbg8Zv87spWrmo2dzJTw7XhQf+xkR6Od # MIIGfjCCBGagAwIBAgIQZ2iSsNbwOsjnLExSAX6F6DANBgkqhkiG9w0BAQsFADB4 # MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24x # ETAPBgNVBAoMCFNTTCBDb3JwMTQwMgYDVQQDDCtTU0wuY29tIENvZGUgU2lnbmlu # ZyBJbnRlcm1lZGlhdGUgQ0EgUlNBIFIxMB4XDTI0MTExNjEwMzUyOFoXDTI1MTEx # NjEwMzUyOFowZTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCENvbG9yYWRvMQ8wDQYD # VQQHDAZEZW52ZXIxGDAWBgNVBAoMD0FuZHJldyBMaWV2ZXJ0ejEYMBYGA1UEAwwP # QW5kcmV3IExpZXZlcnR6MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEA # vIBAQzK0aahepOrPmvCEqfd6dMZC4GvV7kflKwrn4QPJGfqhFmUtadP1e3ange8O # QZ3/w7UjOTAUNUHfhjbSgUBlKjbS6EWQKZuRFzI3SNkMJkcjTX4uS2P4QsnwM+SW # IE5me3CTssdjtgue+Iiy53TMgW8JpoxiULVxmm3bhCRUAgxWeT6tzjytR1UyGcMc # cm/YE6TOgsCHiZoo4X4HJD9iHDrNldArq04Jl6FsADxEswttKyfqpIRJLoAysVl1 # f8CEDBwhszJrEXBnAlWViJFfNY+dKP4jhf7lLqSvPCuADqP2jvM0Ym5I8qDGMz9j # XPSMLF58MFB4vM4viS7nLRFJ8S1Q98vQvB8W4kk0WPuiZbZTHsROzohE1VSbLnIY # ag5dDOWI8L6yutAsfdZFYFmSTKcMSiOj5VbK4LhAJUL2G8vPwpTGFgr+cEp0p62F # P0WXK+/cRfGqodI5S+bg+9rQTD9zf829DwraSRAt5P5zrQk4WPst3JW/vIKNx7cV # AgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB8GA1UdIwQYMBaAFFTC/hCVAJPN # avXnwNfZsku4jwzjMHoGCCsGAQUFBwEBBG4wbDBIBggrBgEFBQcwAoY8aHR0cDov # L2NlcnQuc3NsLmNvbS9TU0xjb20tU3ViQ0EtQ29kZVNpZ25pbmctUlNBLTQwOTYt # UjEuY2VyMCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3NsLmNvbTBRBgNVHSAE # SjBIMAgGBmeBDAEEATA8BgwrBgEEAYKpMAEDAwEwLDAqBggrBgEFBQcCARYeaHR0 # cHM6Ly93d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMD # ME0GA1UdHwRGMEQwQqBAoD6GPGh0dHA6Ly9jcmxzLnNzbC5jb20vU1NMY29tLVN1 # YkNBLUNvZGVTaWduaW5nLVJTQS00MDk2LVIxLmNybDAdBgNVHQ4EFgQUSj8HrSK7 # f/j+Dz31jJFhOF7rJUMwDgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4IC # AQBf4lcc6FUJ1W/opNz8yjS9qLUy9cQt0s35BhasB5QoTbDaW4jv9xnFGhQVg6n+ # jhL0i94Vsywd/MRBb8lYGpuBZnS/7LHuRZu7qUuud+IMDyRHIyBK6koN5bfyA5VY # c7bFbNpbe1s1hMWke8di4qgMLZKDfyG/RtA0swf5t4UgQLPP0h+koZ8X8V5+P0V0 # 1HsdXyXd+ojo38EoZyCKfQL2aAwMPwzZfCbmI5SRXNOc6K8oqXzQcendhlKSfVBo # Zgpi+1updqbD4jmJfYdK5AYPxJ3YH6td6ETtr8owL+bmX8lQjlXPOwVnC11rVlNB # VjqtaJRUClLtiNiYSTKVfjdmGVJ4+sNov0dWhHc0A9o5NX/05VVYTlImuJpnG5Og # o7w6kWRdsgE8gM58jWf7XfI6aQS0Np/z2B+ZBj0K93khEHBX7cvvORa92LCHiVeP # km+zEAMXgxIPs/e8cmcc/o3CORgzEwxlH9Z3UOWCuXSHD3P2RPNDAY+WPdjSHm9f # JFlGq+f9iKyedxYa/NNjNag/5EbZ+Z2NldtSMNeFdsejGJ/TJHF1PyJd4aXx9J1i # B/IZBOoJYyh9xpQ3ljZUKE/4otPi7INpuDFwgWiUHZZJVvrGTWwxH1Yhf8P+VpFf # aNqsBuvklUcUDs3RNE0f1qlgFfcnAepFF+RiBRqmsj29fjGCEdAwghHMAgEBMIGM # MHgxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwHSG91c3Rv # bjERMA8GA1UECgwIU1NMIENvcnAxNDAyBgNVBAMMK1NTTC5jb20gQ29kZSBTaWdu # aW5nIEludGVybWVkaWF0ZSBDQSBSU0EgUjECEGdokrDW8DrI5yxMUgF+hegwDQYJ # YIZIAWUDBAIBBQCgfDAQBgorBgEEAYI3AgEMMQIwADAZBgkqhkiG9w0BCQMxDAYK # KwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG # 9w0BCQQxIgQg3VXrUU18J3mwx1ZuB7AZcWxNQ1rfo8K/xs3gM0viVBwwDQYJKoZI # hvcNAQEBBQAEggGAVfqKrTzrlSOxAztx2eQEwyI93qP1cdJGuRSxYK67r4orKma9 # WwjIyifPLmnHDC0ByAChlwrK/2/mVk7M8A3Kh4aczOCicf4EpxdXBFB6/vq3YXjw # 5/Uqb8oLdX2vSE66HbCbLSyd9502mZl+QA4tOW416K1uKqnHu76tDsR90yVuvshB # kMexIcI71A0jbPX8EmfntH+5zO0LhRTEff7ZZ7BD43mtqDrJwanw+kvn3SRp+ftH # Yuz18Nz2hS3qW8ZIaccp+TM6egCfPNrgRi6TnvMsTb020RD4lKKYnvLsrzF63253 # 6w3fhIJ+VwvueiUIYaDWLU2kq1MzqDgPH4jAsA0ywg3dQzZfW40sk/z0BfAbwKyR # YRIXb5NZifvOuvDm60cN2TfmIgdeFe4sz78H+w5s0AkB4lLg/n0aN7fqLewj5H7h # IwkKmsjw51gWW8uqvB9EgImU/+pfJyGxR7uWTF5QkPs/wdhIzV1pWC32Y1xbXLg0 # W980l5ILRT3vNbQloYIPFjCCDxIGCisGAQQBgjcDAwExgg8CMIIO/gYJKoZIhvcN # AQcCoIIO7zCCDusCAQMxDTALBglghkgBZQMEAgEwdwYLKoZIhvcNAQkQAQSgaARm # MGQCAQEGDCsGAQQBgqkwAQMGATAxMA0GCWCGSAFlAwQCAQUABCBCC/mFPXl5vNYm # sK/BS2wE2lZkVHTW8SfPjW/A42IeyQIIL4qRkGvLzOQYDzIwMjUwNzA0MTcwOTI4 # WjADAgEBoIIMADCCBPwwggLkoAMCAQICEFparOgaNW60YoaNV33gPccwDQYJKoZI # hvcNAQELBQAwczELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQH # DAdIb3VzdG9uMREwDwYDVQQKDAhTU0wgQ29ycDEvMC0GA1UEAwwmU1NMLmNvbSBU # aW1lc3RhbXBpbmcgSXNzdWluZyBSU0EgQ0EgUjEwHhcNMjQwMjE5MTYxODE5WhcN # MzQwMjE2MTYxODE4WjBuMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAO # BgNVBAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMSowKAYDVQQDDCFTU0wu # Y29tIFRpbWVzdGFtcGluZyBVbml0IDIwMjQgRTEwWTATBgcqhkjOPQIBBggqhkjO # PQMBBwNCAASnYXL1MOl6xIMUlgVC49zonduUbdkyb0piy2i8t3JlQEwA74cjK8g9 # mRC8GH1cAAVMIr8M2HdZpVgkV1LXBLB8o4IBWjCCAVYwHwYDVR0jBBgwFoAUDJ0Q # JY6apxuZh0PPCH7hvYGQ9M8wUQYIKwYBBQUHAQEERTBDMEEGCCsGAQUFBzAChjVo # dHRwOi8vY2VydC5zc2wuY29tL1NTTC5jb20tdGltZVN0YW1waW5nLUktUlNBLVIx # LmNlcjBRBgNVHSAESjBIMDwGDCsGAQQBgqkwAQMGATAsMCoGCCsGAQUFBwIBFh5o # dHRwczovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkwCAYGZ4EMAQQCMBYGA1UdJQEB # /wQMMAoGCCsGAQUFBwMIMEYGA1UdHwQ/MD0wO6A5oDeGNWh0dHA6Ly9jcmxzLnNz # bC5jb20vU1NMLmNvbS10aW1lU3RhbXBpbmctSS1SU0EtUjEuY3JsMB0GA1UdDgQW # BBRQTySs77U+YxMjCZIm7Lo6luRdIjAOBgNVHQ8BAf8EBAMCB4AwDQYJKoZIhvcN # AQELBQADggIBAJigjwMAkbyrxGRBf0Ih4r+rbCB57lTuwViC6nH2fZSciMogpqSz # rSeVZ2eIb5vhj9rT7jqWXZn02Fncs4YTrA1QyxJW36yjC4jl5/bsFCaWuXzGXt2Y # 6Ifp//A3Z0sNTMWTTBobmceM3sqnovdX9ToRFP+29r5yQnPcgRTI2PvrVSqLxY9E # yk9/0cviM3W29YBl080ENblRcu3Y8RsfzRtVT/2snuDocRxvRYmd0TPaMgIj2xII # 651QnPp1hiq9xU0AyovLzbsi5wlR5Ip4i/i8+x+HwYJNety5cYtdWJ7uQP6YaZtW # /jNoHp76qNftq/IlSx6xEYBRjFBxHSq2fzhUQ5oBawk2OsZ2j0wOf7q7AqjCt6t/ # +fbmWjrAWYWZGj/RLjltqdFPBpIKqdhjVIxaGgzVhaE/xHKBg4k4DfFZkBYJ9BWu # P93Tm+paWBDwXI7Fg3alGsboErWPWlvwMAmpeJUjeKLZY26JPLt9ZWceTVWuIyuj # erqb5IMmeqLJm5iFq/Qy4YPGyPiolw5w1k9OeO4ErmS2FKvk1ejvw4SWR+S1VyWn # ktY442WaoStxBCCVWZdMWFeB+EpL8uoQNq1MhSt/sIUjUudkyZLIbMVQjj7b6gPX # nD6mS8FgWiCAhuM1a/hgA+6o1sJWizHdmcpYDhyNzorf9KVRE6iR7rcmMIIG/DCC # BOSgAwIBAgIQbVIYcIfoI02FYADQgI+TVjANBgkqhkiG9w0BAQsFADB8MQswCQYD # VQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0b24xGDAWBgNV # BAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBSb290IENlcnRp # ZmljYXRpb24gQXV0aG9yaXR5IFJTQTAeFw0xOTExMTMxODUwMDVaFw0zNDExMTIx # ODUwMDVaMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEQMA4GA1UEBwwH # SG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAtBgNVBAMMJlNTTC5jb20gVGlt # ZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxMIICIjANBgkqhkiG9w0BAQEFAAOC # Ag8AMIICCgKCAgEArlEQE9L5PCCgIIXeyVAcZMnh/cXpNP8KfzFI6HJaxV6oYf3x # h/dRXPu35tDBwhOwPsJjoqgY/Tg6yQGBqt65t94wpx0rAgTVgEGMqGri6vCI6rEt # SZVy9vagzTDHcGfFDc0Eu71mTAyeNCUhjaYTBkyANqp9m6IRrYEXOKdd/eREsqVD # mhryd7dBTS9wbipm+mHLTHEFBdrKqKDM3fPYdBOro3bwQ6OmcDZ1qMY+2Jn1o0l4 # N9wORrmPcpuEGTOThFYKPHm8/wfoMocgizTYYeDG/+MbwkwjFZjWKwb4hoHT2WK8 # pvGW/OE0Apkrl9CZSy2ulitWjuqpcCEm2/W1RofOunpCm5Qv10T9tIALtQo73GHI # lIDU6xhYPH/ACYEDzgnNfwgnWiUmMISaUnYXijp0IBEoDZmGT4RTguiCmjAFF5OV # NbY03BQoBb7wK17SuGswFlDjtWN33ZXSAS+i45My1AmCTZBV6obAVXDzLgdJ1A1r # yyXz4prLYyfJReEuhAsVp5VouzhJVcE57dRrUanmPcnb7xi57VPhXnCuw26hw1Hd # +ulK3jJEgbc3rwHPWqqGT541TI7xaldaWDo85k4lR2bQHPNGwHxXuSy3yczyOg57 # TcqqG6cE3r0KR6jwzfaqjTvN695GsPAPY/h2YksNgF+XBnUD9JBtL4c34AcCAwEA # AaOCAYEwggF9MBIGA1UdEwEB/wQIMAYBAf8CAQAwHwYDVR0jBBgwFoAU3QQJB6L1 # en1SUxKSle44gCUNplkwgYMGCCsGAQUFBwEBBHcwdTBRBggrBgEFBQcwAoZFaHR0 # cDovL3d3dy5zc2wuY29tL3JlcG9zaXRvcnkvU1NMY29tUm9vdENlcnRpZmljYXRp # b25BdXRob3JpdHlSU0EuY3J0MCAGCCsGAQUFBzABhhRodHRwOi8vb2NzcHMuc3Ns # LmNvbTA/BgNVHSAEODA2MDQGBFUdIAAwLDAqBggrBgEFBQcCARYeaHR0cHM6Ly93 # d3cuc3NsLmNvbS9yZXBvc2l0b3J5MBMGA1UdJQQMMAoGCCsGAQUFBwMIMDsGA1Ud # HwQ0MDIwMKAuoCyGKmh0dHA6Ly9jcmxzLnNzbC5jb20vc3NsLmNvbS1yc2EtUm9v # dENBLmNybDAdBgNVHQ4EFgQUDJ0QJY6apxuZh0PPCH7hvYGQ9M8wDgYDVR0PAQH/ # BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4ICAQCSGXUNplpCzxkH2fL8lPrAm/AV6USW # Wi9xM91Q5RN7mZN3D8T7cm1Xy7qmnItFukgdtiUzLbQokDJyFTrF1pyLgGw/2hU3 # FJEywSN8crPsBGo812lyWFgAg0uOwUYw7WJQ1teICycX/Fug0KB94xwxhsvJBiRT # pQyhu/2Kyu1Bnx7QQBA1XupcmfhbQrK5O3Q/yIi//kN0OkhQEiS0NlyPPYoRboHW # C++wogzV6yNjBbKUBrMFxABqR7mkA0x1Kfy3Ud08qyLC5Z86C7JFBrMBfyhfPpKV # lIiiTQuKz1rTa8ZW12ERoHRHcfEjI1EwwpZXXK5J5RcW6h7FZq/cZE9kLRZhvnRK # tb+X7CCtLx2h61ozDJmifYvuKhiUg9LLWH0Or9D3XU+xKRsRnfOuwHWuhWch8G7k # EmnTG9CtD9Dgtq+68KgVHtAWjKk2ui1s1iLYAYxnDm13jMZm0KpRM9mLQHBK5Gb4 # dFgAQwxOFPBslf99hXWgLyYE33vTIi9p0gYqGHv4OZh1ElgGsvyKdUUJkAr5hfbD # X6pYScJI8v9VNYm1JEyFAV9x4MpskL6kE2Sy8rOqS9rQnVnIyPWLi8N9K4GZvPit # /Oy+8nFL6q5kN2SZbox5d69YYFe+rN1sDD4CpNWwBBTI/q0V4pkgvhL99IV2Xasj # HZf4peSrHdL4RjGCAlgwggJUAgEBMIGHMHMxCzAJBgNVBAYTAlVTMQ4wDAYDVQQI # DAVUZXhhczEQMA4GA1UEBwwHSG91c3RvbjERMA8GA1UECgwIU1NMIENvcnAxLzAt # BgNVBAMMJlNTTC5jb20gVGltZXN0YW1waW5nIElzc3VpbmcgUlNBIENBIFIxAhBa # WqzoGjVutGKGjVd94D3HMAsGCWCGSAFlAwQCAaCCAWEwGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0yNTA3MDQxNzA5MjhaMCgGCSqG # SIb3DQEJNDEbMBkwCwYJYIZIAWUDBAIBoQoGCCqGSM49BAMCMC8GCSqGSIb3DQEJ # BDEiBCB+iPo7CkidsB4as0n23b0ODRVZyvzUzDjkij3WJaX9iTCByQYLKoZIhvcN # AQkQAi8xgbkwgbYwgbMwgbAEIJ1xf43CN2Wqzl5KsOH1ddeaF9Qc7tj9r+8D/T29 # iUfnMIGLMHekdTBzMQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNV # BAcMB0hvdXN0b24xETAPBgNVBAoMCFNTTCBDb3JwMS8wLQYDVQQDDCZTU0wuY29t # IFRpbWVzdGFtcGluZyBJc3N1aW5nIFJTQSBDQSBSMQIQWlqs6Bo1brRiho1XfeA9 # xzAKBggqhkjOPQQDAgRHMEUCIQCMQjBCqDtVZnIQjJneq4q5Pcp/DIWBxFNhsi7k # 3U2tMQIgW1I+1LafJxTkqYkK8purvjDu+UJfmHxCSP0D5KYrQs8= # SIG # End signature block |