packages/AzStackHci.DiagnosticSettings.0.6.8/Private/AzStackHci.Network.Helpers.ps1
|
# //////////////////////////////////////////////////////////////////////////// # Strict Mode v1 (PS 5.1 safe) - surfaces reads of uninitialised variables at runtime. Set-StrictMode -Version 1.0 # Test if proxy is enabled and return boolean function Get-Proxy { <# .SYNOPSIS Check if proxy is enabled and return boolean and proxy server uri #> begin { # Write-Debug "Get-Proxy: Beginning proxy detection process" } process { $line1, $line2, $line3, $JsonLines = netsh winhttp show advproxy $ProxyInformation = $JsonLines | ConvertFrom-Json -ErrorAction SilentlyContinue # Return True/False and the Proxy server uri as a PSObject $ProxyReturnVariable = New-Object PsObject -Property @{ # True/False Enabled = [bool]$ProxyInformation.Proxy # Proxy server URI Server = $ProxyInformation.Proxy # Arc Gateway scenario can carry distinct HTTP and HTTPS proxies in a single # 'http=customerproxy:8080;https=localhost:40343' string. These are parsed out # below (null when a single, un-prefixed proxy is configured — Server holds it). HttpProxy = $null HttpsProxy = $null # Proxy bypass configuration (v0.6.8). Two independent sources are captured so the # RFC1918 / Private-Link classification can VERIFY whether a private-endpoint FQDN is # correctly excluded from the forward proxy (rather than merely advising it): # ProxyBypass - the WinHTTP 'Bypass List' (comma-separated, '*' wildcard). Read from # the TEXT `netsh winhttp show proxy` output below; the advproxy JSON # parsed above does not expose the bypass entries. # NoProxyEnv - the machine NO_PROXY environment variable (comma-separated; entries # may be CIDR ranges, '.'-prefixed domain suffixes, hostnames or IPs). # Since Azure Local 2506 this is configured automatically at Arc # registration, so it is the authoritative bypass fallback. ProxyBypass = @() NoProxyEnv = @() } # Initialised here so the later `if($ProxyStrings)` read is safe under Set-StrictMode v1.0 # even when the proxy server string contains no ';' (no semicolon -> never assigned below). $ProxyStrings = $null # ------------------------------------------------------------------------ # Capture the WinHTTP 'Bypass List' (v0.6.8) from the human-readable # `netsh winhttp show proxy` output. It prints a 'Bypass List : a,b,c' line # when a static WinHTTP proxy is configured. Best-effort: never throws. try { $winHttpProxyText = netsh winhttp show proxy 2>$null if ($winHttpProxyText) { $bypassLine = $winHttpProxyText | Where-Object { $_ -match '(?i)Bypass\s*List' } | Select-Object -First 1 if ($bypassLine) { $bypassRaw = ($bypassLine -split ':', 2)[1] if (-not [string]::IsNullOrWhiteSpace($bypassRaw)) { $ProxyReturnVariable.ProxyBypass = @( $bypassRaw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ -and $_ -ne '(none)' } ) } } } } catch { Write-Debug "Get-Proxy: failed to read WinHTTP bypass list: $($_.Exception.Message)" } # Capture the NO_PROXY environment variable (machine scope first, then the # current process scope as a fallback). Best-effort: never throws. try { $noProxyRaw = [Environment]::GetEnvironmentVariable('NO_PROXY', 'Machine') if ([string]::IsNullOrWhiteSpace($noProxyRaw)) { $noProxyRaw = $env:NO_PROXY } if (-not [string]::IsNullOrWhiteSpace($noProxyRaw)) { $ProxyReturnVariable.NoProxyEnv = @( $noProxyRaw -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ } ) } } catch { Write-Debug "Get-Proxy: failed to read NO_PROXY environment variable: $($_.Exception.Message)" } # Check if the Proxy is enabled and if the proxy server is set if($ProxyReturnVariable.Enabled) { if(-not($ProxyReturnVariable.Server)){ # In case the proxy server is not set from netsh winhttp show advproxy output $proxyUri = [Uri]$null $ProxyTest = [System.Net.WebRequest]::GetSystemWebProxy() $ProxyTest.Credentials = [System.Net.CredentialCache]::DefaultCredentials # use a known URL to test the proxy server, but http, in case of Arc Gateway, to prevent "localhost" from being returned # This is a known URL that should be accessible from any network $TestUrl = [System.Uri]::new("http://oneocsp.microsoft.com") # Test the proxy server using a known URL $proxyUri = $ProxyTest.GetProxy($TestUrl) if($proxyUri -eq $TestUrl) { # Proxy is not required, so do not use it $ProxyReturnVariable.Enabled = $false Write-HostAzS "`t`nNo proxy server detected, using direct connection...`n" -ForegroundColor Green } else { # Proxy is required, so use it $ProxyReturnVariable.Server = $proxyUri } } # Check if the proxy server string has a semi-colon which can be used for Arc Gateway if(-not [string]::IsNullOrWhiteSpace($ProxyReturnVariable.Server) -and $ProxyReturnVariable.Server -match ";"){ # In case the proxy server is set from netsh winhttp show advproxy output [array]$ProxyStrings = $ProxyReturnVariable.Server.ToString().Split(";") } if($ProxyStrings){ # Check if the proxy server string has a semi-colon which can be used for Arc Gateway # Multiple proxy servers detected Write-HostAzS "`n`tArc Gateway scenario detected (possible), as multiple proxy servers detected ('netsh winhttp show advproxy')..." ForEach($ProxyServer in $ProxyStrings){ # Trim the proxy server string $ProxyServer = $ProxyServer.Trim() if($ProxyServer -like "http=*"){ # HTTP proxy server (should be customer proxy server) # Check if the proxy server is set from netsh winhttp show advproxy output $ProxyReturnVariable.HttpProxy = $ProxyServer.Substring(5) Write-HostAzS "`tHTTP Proxy server detected, using proxy: $($ProxyServer)" -ForegroundColor Green } elseif($ProxyServer -like "https=*"){ # HTTPS proxy server (might be Arc Gateway Agent, "localhost") # Check if the proxy server is set from netsh winhttp show advproxy output $ProxyReturnVariable.HttpsProxy = $ProxyServer.Substring(6) Write-HostAzS "`tHTTPS Proxy server detected, using proxy: $($ProxyServer)" -ForegroundColor Green } else { # Other proxy server detected # Check if the proxy server is set from netsh winhttp show advproxy output Write-HostAzS "`tProxy server detected, using proxy: $($ProxyServer)" -ForegroundColor Green } } # End ForEach Write-HostAzS "" } else { # Single proxy detected # A single proxy can still carry an 'http=' / 'https=' prefix (no semicolon); # parse it so HttpProxy / HttpsProxy are populated for consumers in that case too. $singleProxy = $ProxyReturnVariable.Server.ToString().Trim() if($singleProxy -like "http=*"){ $ProxyReturnVariable.HttpProxy = $singleProxy.Substring(5) } elseif($singleProxy -like "https=*"){ $ProxyReturnVariable.HttpsProxy = $singleProxy.Substring(6) } Write-HostAzS "`t`nProxy server detected, using proxy: $($ProxyReturnVariable.Server)`n" -ForegroundColor Green } } else { # Proxy is NOT enabled # No proxy server detected, so use direct connection Write-HostAzS "`t`nNo proxy server detected, using direct connection...`n" -ForegroundColor Green } } # End of process block end { # Write-Debug "Get-Proxy: Proxy detection process completed" # Return the ProxyReturnVariable object Return $ProxyReturnVariable } } # End Function Get-Proxy # //////////////////////////////////////////////////////////////////////////// # Determine whether an IPv4 address falls inside a CIDR range (v0.6.8). # IPv4-only — the Azure Local NO_PROXY bypass CIDRs (10.0.0.0/8, 172.16.0.0/12, # 192.168.0.0/16, infra subnets) are all IPv4. Best-effort: never throws. function Test-IpInCidr { <# .SYNOPSIS Return $true if the given IPv4 address is contained within the given CIDR range. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$IpAddress, [Parameter(Mandatory = $true)][string]$Cidr ) try { $parts = $Cidr -split '/' if ($parts.Count -ne 2) { return $false } $network = [System.Net.IPAddress]::Parse($parts[0].Trim()) $prefix = [int]$parts[1].Trim() if ($prefix -lt 0 -or $prefix -gt 32) { return $false } $ip = [System.Net.IPAddress]::Parse($IpAddress.Trim()) $ipBytes = $ip.GetAddressBytes() $netBytes = $network.GetAddressBytes() # IPv4 only (4 bytes). Reverse for little-endian BitConverter on both. if ($ipBytes.Length -ne 4 -or $netBytes.Length -ne 4) { return $false } [array]::Reverse($ipBytes) [array]::Reverse($netBytes) $ipInt = [uint64][System.BitConverter]::ToUInt32($ipBytes, 0) $netInt = [uint64][System.BitConverter]::ToUInt32($netBytes, 0) # /0 matches everything. Otherwise compare only the top $prefix (network) bits by # right-shifting away the (32 - prefix) host bits on both operands. Using -shr avoids # the unreliable uint64 -shl mask construction in Windows PowerShell. if ($prefix -eq 0) { return $true } $hostBits = 32 - $prefix return (($ipInt -shr $hostBits) -eq ($netInt -shr $hostBits)) } catch { return $false } } # End Function Test-IpInCidr # //////////////////////////////////////////////////////////////////////////// # Verify whether an endpoint (FQDN + its resolved IP) is excluded from the forward # proxy via either the WinHTTP bypass list or the NO_PROXY environment variable (v0.6.8). # # Private-endpoint traffic (RFC1918 addresses) MUST bypass the proxy and route over # ExpressRoute / Site-to-Site VPN. This helper turns the module's previous *advisory* # ("ensure this FQDN is on the bypass list") into a *verified* check, honouring the two # different bypass syntaxes documented for Azure Local: # * WinHTTP / WinInet : '*' wildcard (e.g. '*.blob.core.windows.net', '192.168.1.*') # * NO_PROXY env var : '.'-prefix / bare-suffix domains and CIDR ranges # (e.g. '.contoso.com', '10.0.0.0/8') # Matching is case-insensitive. Returns a result object; never throws. function Test-EndpointBypassed { <# .SYNOPSIS Return a result object indicating whether an FQDN/IP is on a proxy bypass source. .OUTPUTS [pscustomobject] @{ Bypassed = [bool]; Source = 'WinHTTP'|'NO_PROXY'|''; MatchedRule = [string] } #> [CmdletBinding()] param( [Parameter(Mandatory = $true)][string]$Fqdn, [Parameter()][string]$IpAddress, [Parameter()][string[]]$WinHttpBypass = @(), [Parameter()][string[]]$NoProxyEnv = @() ) $result = [pscustomobject]@{ Bypassed = $false; Source = ''; MatchedRule = '' } if ([string]::IsNullOrWhiteSpace($Fqdn)) { return $result } $normalizedFqdn = $Fqdn.Trim().ToLower().TrimEnd('.') # --- WinHTTP / WinInet bypass entries ('*' wildcard) --- foreach ($entry in $WinHttpBypass) { if ([string]::IsNullOrWhiteSpace($entry)) { continue } $e = $entry.Trim().ToLower() # Escape everything, then turn the escaped '*' back into '.*' for a full-string match. $pattern = '^' + ([regex]::Escape($e) -replace '\\\*', '.*') + '$' if ($normalizedFqdn -match $pattern) { $result.Bypassed = $true; $result.Source = 'WinHTTP'; $result.MatchedRule = $entry return $result } if ($IpAddress -and ($IpAddress -match $pattern)) { $result.Bypassed = $true; $result.Source = 'WinHTTP'; $result.MatchedRule = $entry return $result } } # --- NO_PROXY env entries ('.'-prefix / bare suffix + CIDR) --- foreach ($entry in $NoProxyEnv) { if ([string]::IsNullOrWhiteSpace($entry)) { continue } $e = $entry.Trim().ToLower() if ($e -match '^\d{1,3}(\.\d{1,3}){3}/\d{1,2}$') { # CIDR range — match against the resolved IP (NO_PROXY uses CIDR, not wildcards). if ($IpAddress -and (Test-IpInCidr -IpAddress $IpAddress -Cidr $e)) { $result.Bypassed = $true; $result.Source = 'NO_PROXY'; $result.MatchedRule = $entry return $result } continue } # A leading '.' means "this domain and any subdomain"; a bare domain is treated the # same way (suffix match) plus an exact-host match. $suffix = $e.TrimStart('.') if ($normalizedFqdn -eq $suffix -or $normalizedFqdn.EndsWith('.' + $suffix)) { $result.Bypassed = $true; $result.Source = 'NO_PROXY'; $result.MatchedRule = $entry return $result } if ($IpAddress -and ($IpAddress -eq $e)) { $result.Bypassed = $true; $result.Source = 'NO_PROXY'; $result.MatchedRule = $entry return $result } } return $result } # End Function Test-EndpointBypassed # //////////////////////////////////////////////////////////////////////////// # This function retrieves the SSL certificate chain from a remote HTTPS endpoint # It uses the System.Net.Http.HttpClient class to make the request and capture the certificate chain. # # Renamed from Get-SslCertificateChain in v0.6.6 to avoid a name collision with the # AzStackHci.EnvironmentChecker module (10.2509+), which exports a function with the # same name but a different parameter set. Because Get-AzStackHciEnvironmentCheckerModule # Import-Module's that module from inside our module's session state, EnvironmentChecker's # exported Get-SslCertificateChain shadowed our private helper, breaking certificate-detail # capture for every HTTPS endpoint with: "A parameter cannot be found that matches # parameter name 'AllowAutoRedirect'". Renaming the private helper resolves the conflict # permanently. function Get-AzSHciSslCertificateChain { <# .SYNOPSIS Retrieve remote ssl certificate & chain from https endpoint for Desktop and Core .NOTES Credit: https://github.com/markekraus #> [CmdletBinding()] param ( [system.uri] $url, [Parameter()] [bool] $AllowAutoRedirect, [Parameter()] [string] $Proxy ) begin { # Write-Debug "Get-AzSHciSslCertificateChain: Beginning SSL certificate chain retrieval for '$url'" } process { try { $cs = @' using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Security; using System.Security.Cryptography.X509Certificates; namespace CertificateCapture { public class Utility { public static Func<HttpRequestMessage,X509Certificate2,X509Chain,SslPolicyErrors,Boolean> ValidationCallback = (message, cert, chain, errors) => { CapturedCertificates.Clear(); var newCert = new X509Certificate2(cert); var newChain = new X509Chain(); newChain.Build(newCert); CapturedCertificates.Add(new CapturedCertificate(){ Certificate = newCert, CertificateChain = newChain, PolicyErrors = errors, URI = message.RequestUri }); return true; }; public static List<CapturedCertificate> CapturedCertificates = new List<CapturedCertificate>(); } public class CapturedCertificate { public X509Certificate2 Certificate { get; set; } public X509Chain CertificateChain { get; set; } public SslPolicyErrors PolicyErrors { get; set; } public Uri URI { get; set; } } } '@ try { if (-not ('CertificateCapture.Utility' -as [type])) { if ($PSEdition -ne 'Core') { Add-Type -AssemblyName System.Net.Http Add-Type $cs -ReferencedAssemblies System.Net.Http } else { Add-Type $cs } } } catch { if ($_.Exception.Message -notmatch 'Definition of new types is not supported in this language mode') { throw "Language mode does not allow this test Error: $_" } } # Reset variables, in case cached. Remove-Variable Certs, Handler, Client, Request -ErrorAction SilentlyContinue # Create a new list to hold captured certificates. $Certs = [CertificateCapture.Utility]::CapturedCertificates # Clear any previously captured certificates. $Certs.Clear() # Create the HttpClientHandler. $Handler = [System.Net.Http.HttpClientHandler]::new() # This is important to capture the certificate chain of the first endpoint, not redirected endpoints. if($AllowAutoRedirect -eq $false) { # Set the handler to not allow auto redirects # https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclienthandler.allowautoredirect $Handler.AllowAutoRedirect = $false } if ($Proxy) { $Handler.Proxy = New-Object System.Net.WebProxy($proxy) } # Set the ServerCertificateCustomValidationCallback to our custom callback. $Handler.ServerCertificateCustomValidationCallback = [CertificateCapture.Utility]::ValidationCallback # Create the HttpClient with the handler $Client = [System.Net.Http.HttpClient]::new($Handler) # Set a timeout to 15 seconds (reduced from 60s to limit impact of unresponsive backend pool servers): $Client.Timeout = [timespan]::FromTicks($script:CERT_CAPTURE_TIMEOUT_TICKS) try { # Setup the request to the URL. $Request = $Client.GetAsync($url) # Wait for the request to complete calling the Result method. $Null = $Request.Result # Check if the request is completed and that certificates were captured. if(($Request.IsCompleted) -and $Certs){ # Return the captured certificate chain Write-Debug "Successfully obtained SSL certificate chain from endpoint: '$url'" return $Certs.CertificateChain } else { # Return null if no certificates were captured. Write-Debug "Failed to obtain SSL certificate chain from endpoint: '$url'" return $null } } finally { # Dispose IDisposable resources to prevent socket/handle leaks if ($Request) { $Request.Dispose() } if ($Client) { $Client.Dispose() } if ($Handler) { $Handler.Dispose() } } } catch { throw $_ } } # End of process block end { # Write-Debug "Get-AzSHciSslCertificateChain: SSL certificate chain retrieval completed" } } # End Function Get-AzSHciSslCertificateChain # SIG # Begin signature block # MIInRAYJKoZIhvcNAQcCoIInNTCCJzECAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBZAf4hRPmnk2Oj # kwhhXjBzQI+t2EoqkwoIcxVuYWzNWqCCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghngMIIZ3AIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCggZAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwLwYJ # KoZIhvcNAQkEMSIEIAyyasJV6kxV89Z/We0nK4GgJcGQTpbJGcHtjpZQtAxUMEIG # CisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEBBQAEggEAEBnbDm5+gYdctldN # dozbsnm0Vkqiz4SGwnmjl1p/gV8260uM+UWNBlg8ihS6WQARBo9CcEIISxLqbBNm # 03YYMqm9kJGFqtYyBiBkYLSTlgb235a84dx1Nw7snaymJ/WgBBpKwNO15APMpHBQ # acIGyF8yOHjcWIslqT3JzVir147vAfz3v3KUP1Famu6WNiG89fsVGoA3GExErGlS # D3k1GWx7RrBOsZrGsuVWHK62tDTEIivq/5EqJla2TtcPuc40tuMUrqWFxAjZFSaS # 56phxpAdbnzynSMaZbTdddNu0v89c+rP8Vr9mcstNWHO/sUwWrzEjaOIrzQmea/j # a9FSmaGCF7AwghesBgorBgEEAYI3AwMBMYIXnDCCF5gGCSqGSIb3DQEHAqCCF4kw # gheFAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFaBgsqhkiG9w0BCRABBKCCAUkEggFF # MIIBQQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFlAwQCAQUABCAEVI1xom5KE/We # WSf/FFpfg/v02K++wylvL6HhvHWzHAIGamO384MYGBMyMDI2MDcyOTEzMTYxNC40 # MjhaMASAAgH0oIHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaCCEf4wggcoMIIF # EKADAgECAhMzAAACF3H7LqWvAR3qAAEAAAIXMA0GCSqGSIb3DQEBCwUAMHwxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jv # c29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTI1MDgxNDE4NDgyM1oXDTI2MTEx # MzE4NDgyM1owgdMxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw # DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x # LTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJhdGlvbnMgTGltaXRlZDEn # MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjUyMUEtMDVFMC1EOTQ3MSUwIwYDVQQD # ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIICIjANBgkqhkiG9w0BAQEF # AAOCAg8AMIICCgKCAgEAwM82sEw+39vYR7iGCIFDnYNhRM+BzF2AYiq5dUpZpJFP # RjCcipQ6RUbI+RAYNRApExx5ygrXbaWtuwvqsqAVSWbU/W6fecujjILkPqn9pngt # WRkfQgbYgvaXALl6PY2yOH9f72MD+6AyxQenSpAMdUzY/Qk/jtjsHdFXVBe+tshl # IkSJ3GZw8VVKqTg3GZElztwbJWNtrhBEvhf6anxMegQMJP7tO8/BJ7ITs4/AV3D2 # bv8eHk81Y+fOmQ8mQ61WLq2wItvlzIT5bzelK9LvEycf5x1lXxAwEw5a7dpS+CKT # anhtv+Q2mwebAybjf9io4k48stTaq1rtcrOiDwddqVm1S9e8h1TszXFzjLLvE9Em # jnNfIewsY+RChUaHnY4FFwwJEnEv/JS76oHT0oGdy7+J60fGOl7A1UoUyAkhpb2B # ja+SwSIiHbQ4FDyJiLlZ6drZZ84MoJ852JSxM0hBjGO6FZlPO8iuNyk680Di8Vnb # SNpIdJN+DhlepeTUMBDHqCmd0mVWRWZPm1pvgty93asNt/Ng6o4m2dnooWOdM3yK # sJaWjyHqic9gfTrZBM+PCXqeTaO1oEiaQ+h4w0nHVdV+XSvI2m1yN4iibqjm5HPa # AO3OJ+OmNLftNVmr4Z6U2T6pIcLBysoKcDUvCqycXj4C/+n1KFBpDGdDMw9gmu8C # AwEAAaOCAUkwggFFMB0GA1UdDgQWBBRQrN9jlwNOoeE5ZQqnF5x8S1bJQzAfBgNV # HSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNVHR8EWDBWMFSgUqBQhk5o # dHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNyb3NvZnQlMjBU # aW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYIKwYBBQUHAQEEYDBeMFwG # CCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRz # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNydDAMBgNV # HRMBAf8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMA4GA1UdDwEB/wQEAwIH # gDANBgkqhkiG9w0BAQsFAAOCAgEARmgFdhB7xIAIHEEg5I/5S+gx67aR6RiW8ZAw # tE3mz8o0dyn+pIP+lidNR1IKQQ0r+RjYgI9cZ6mbvAyvh3e2q/BV8rjHE3ud9PyY # yq32euFgdZ3vX4b5QXePWlpBAYrdziR27rHz6WwpH5dZsSypbXDBbQkWkNl6g82y # Ty3AbBbKDXBdzxZsEauaOplatK7Er4dhglKBex8JQ2dMSkSZweCNDXqd9r/9W2Vd # RZsDJKP/Xc4UyQlVsboBotKtYESXFkjwR1HVsH+Q0C69/N5CP/Tq3YgI1ub4b9+3 # MJFKWhJXCcJGFZkcLwUmYwoFg1XLo7DLJdGjrIH1jsI2NFXJFQHef6AdRe1ERvYQ # eqtyrBvxIvR+P/83FNYyzx04inUT9TF2AwTOuqCC6Z67oNwR4pEEJyAIEREvkdhj # jfWcgsk/nGTlfahvNY/SOHrNRKo49KDlccNzRCJQyQ+D59r7/qebNSyQPTfwI9++ # jEY0Q/UWKVNLhio55GYBseJ99s7NzkdxOr9Uftp597HEovbA69qGlZ3OpUE3H1RB # GDVp/FvM2uXTum8LrMkPXx5Ap/kbPASsC9ju9oMCe2IEXO2SeD1aD3IqvAOdHFKH # g1vpbPUQSWb6g2xfBV30wFcqaPYgzcbxPWPyZqK+S8l7zw64aO5hmJ7eQwoMfTu0 # Vay6r48wggdxMIIFWaADAgECAhMzAAAAFcXna54Cm0mZAAAAAAAVMA0GCSqGSIb3 # DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIw # MAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAx # MDAeFw0yMTA5MzAxODIyMjVaFw0zMDA5MzAxODMyMjVaMHwxCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1l # LVN0YW1wIFBDQSAyMDEwMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA # 5OGmTOe0ciELeaLL1yR5vQ7VgtP97pwHB9KpbE51yMo1V/YBf2xK4OK9uT4XYDP/ # XE/HZveVU3Fa4n5KWv64NmeFRiMMtY0Tz3cywBAY6GB9alKDRLemjkZrBxTzxXb1 # hlDcwUTIcVxRMTegCjhuje3XD9gmU3w5YQJ6xKr9cmmvHaus9ja+NSZk2pg7uhp7 # M62AW36MEBydUv626GIl3GoPz130/o5Tz9bshVZN7928jaTjkY+yOSxRnOlwaQ3K # Ni1wjjHINSi947SHJMPgyY9+tVSP3PoFVZhtaDuaRr3tpK56KTesy+uDRedGbsoy # 1cCGMFxPLOJiss254o2I5JasAUq7vnGpF1tnYN74kpEeHT39IM9zfUGaRnXNxF80 # 3RKJ1v2lIH1+/NmeRd+2ci/bfV+AutuqfjbsNkz2K26oElHovwUDo9Fzpk03dJQc # NIIP8BDyt0cY7afomXw/TNuvXsLz1dhzPUNOwTM5TI4CvEJoLhDqhFFG4tG9ahha # YQFzymeiXtcodgLiMxhy16cg8ML6EgrXY28MyTZki1ugpoMhXV8wdJGUlNi5UPkL # iWHzNgY1GIRH29wb0f2y1BzFa/ZcUlFdEtsluq9QBXpsxREdcu+N+VLEhReTwDwV # 2xo3xwgVGD94q0W29R6HXtqPnhZyacaue7e3PmriLq0CAwEAAaOCAd0wggHZMBIG # CSsGAQQBgjcVAQQFAgMBAAEwIwYJKwYBBAGCNxUCBBYEFCqnUv5kxJq+gpE8RjUp # zxD/LwTuMB0GA1UdDgQWBBSfpxVdAF5iXYP05dJlpxtTNRnpcjBcBgNVHSAEVTBT # MFEGDCsGAQQBgjdMg30BATBBMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jv # c29mdC5jb20vcGtpb3BzL0RvY3MvUmVwb3NpdG9yeS5odG0wEwYDVR0lBAwwCgYI # KwYBBQUHAwgwGQYJKwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQDAgGG # MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU1fZWy4/oolxiaNE9lJBb186a # GMQwVgYDVR0fBE8wTTBLoEmgR4ZFaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3Br # aS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3JsMFoGCCsG # AQUFBwEBBE4wTDBKBggrBgEFBQcwAoY+aHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcnQwDQYJKoZIhvcN # AQELBQADggIBAJ1VffwqreEsH2cBMSRb4Z5yS/ypb+pcFLY+TkdkeLEGk5c9MTO1 # OdfCcTY/2mRsfNB1OW27DzHkwo/7bNGhlBgi7ulmZzpTTd2YurYeeNg2LpypglYA # A7AFvonoaeC6Ce5732pvvinLbtg/SHUB2RjebYIM9W0jVOR4U3UkV7ndn/OOPcbz # aN9l9qRWqveVtihVJ9AkvUCgvxm2EhIRXT0n4ECWOKz3+SmJw7wXsFSFQrP8DJ6L # GYnn8AtqgcKBGUIZUnWKNsIdw2FzLixre24/LAl4FOmRsqlb30mjdAy87JGA0j3m # Sj5mO0+7hvoyGtmW9I/2kQH2zsZ0/fZMcm8Qq3UwxTSwethQ/gpY3UA8x1RtnWN0 # SCyxTkctwRQEcb9k+SS+c23Kjgm9swFXSVRk2XPXfx5bRAGOWhmRaw2fpCjcZxko # JLo4S5pu+yFUa2pFEUep8beuyOiJXk+d0tBMdrVXVAmxaQFEfnyhYWxz/gq77EFm # PWn9y8FBSX5+k77L+DvktxW/tM4+pTFRhLy/AsGConsXHRWJjXD+57XQKBqJC482 # 2rpM+Zv/Cuk0+CQ1ZyvgDbjmjJnW4SLq8CdCPSWU5nR0W2rRnj7tfqAxM328y+l7 # vzhwRNGQ8cirOoo6CGJ/2XBjU02N7oJtpQUQwXEGahC0HVUzWLOhcGbyoYIDWTCC # AkECAQEwggEBoYHZpIHWMIHTMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMS0wKwYDVQQLEyRNaWNyb3NvZnQgSXJlbGFuZCBPcGVyYXRpb25zIExp # bWl0ZWQxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjo1MjFBLTA1RTAtRDk0NzEl # MCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsO # AwIaAxUAabKAFaKt2haUdqkHfFYzAzfgSMuggYMwgYCkfjB8MQswCQYDVQQGEwJV # UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE # ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt # ZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQsFAAIFAO4UJNIwIhgPMjAyNjA3 # MjkwNzA1NTRaGA8yMDI2MDczMDA3MDU1NFowdzA9BgorBgEEAYRZCgQBMS8wLTAK # AgUA7hQk0gIBADAKAgEAAgIyEQIB/zAHAgEAAgIUgjAKAgUA7hV2UgIBADA2Bgor # BgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAID # AYagMA0GCSqGSIb3DQEBCwUAA4IBAQAYx9eOcnQhch83C94JG28p9UvU3b0cK0f1 # V84zaiy4p+6VpINQMbxBcmrW1z39nx3OJ7+0D9/L246BILlhKYzCtZZlCGlL79+e # 6D/olJxiuFPAfMPpOOPRqAbimbzfvTqtd5PKz3IbxTML1TlgN1Iq+26P1xZ4LCYw # kMpF/8NdgkeGNldetH9hmXHp3uK9byK0DA88pacAFlmpjB+IWbXDYdcc76bg7+Wo # eNhKxyJCJVz/baCSE7g4cY3LK1kvVqrXsbx7uz1Dpc9eOHEHRTO5M4NoEJ/wuzEP # IOAo9Unm/Dghr6zJXT+yOBBM5z1/gKQ9NIzEK6NzpUKZwRluIkkOMYIEDTCCBAkC # AQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQG # A1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAIXcfsupa8B # HeoAAQAAAhcwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG # 9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgNkN9uifA1ddAbTnIjWssuBfXZgSlTa6K # XsnSSzxufJkwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCDQ8lBgPl23yZ0S # zUSt5phOIegHPywrkNwevxe2k+RaWzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFBDQSAyMDEwAhMzAAACF3H7LqWvAR3qAAEAAAIXMCIEIFfql52jZ1oRFu/3 # mx/yZpmBwSl52nrbx+h4jtf7vbzhMA0GCSqGSIb3DQEBCwUABIICACJ3jLzY+1Zq # iaM/fGmQjgVHC7D+jogRlQi35pBOTJiityBlIh0C1fZLxSCeBhJujqAQDak6CTD4 # FCrl3hQ50A4RNzAWCtMsHawjTAnEYhC4V57URLUNMvVke/Sv6zM5ECWB2JknJ81H # LaZm58cF4SH4qPOHrtdtadpzoG7Y7dwRL+89quRnovoW7hgrlm8eHBRMywJS5NJC # SpU9wG9lJ53Pwu54bWcGpwAhpC96+WXZPv07wzfaSsYPatNb6t0HpR4Wndz8eCyx # nljLLt+tH7dCwj6weB1C3E23/E3Q7kYxSPWVoyfMvZpl5hkWsey4HcQ6qCu8xhuM # RPjmu/fT2zI7bwjBczrBgMM7YLsVbrU2ZZDFgeij7FrCQ9NwMNyiRDMYMG/JKQ5Q # hmrrTJaIkOBJFToKzGuKG82vSTWVwkhg0vIF3pNW0v6z4eCccvaygsvvHHR8A9Hq # CzuDCt45w/Xvg41OR2LJIABx0A/OmxwoXZeJQguG0R4273U1lb4WilLV6ME+xs+L # HWjaijXZ3ng0iMVGSCboRLzbuAUTxHsLn/QiaQ6tf7lQINnY7A9s7+1qalkvQaMl # U1naQXtUhLGLE1rGxl8gmNb2yhaMrt8B8Jh5afQLZPXO7RvbFd1435y3Z0gAF6Kn # GuJS8UMxWQMUZj50ezdUDXmdstDR7n3x # SIG # End signature block |