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 # MIInSQYJKoZIhvcNAQcCoIInOjCCJzYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # 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 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghnlMIIZ4QIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK # KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIAyyasJV # 6kxV89Z/We0nK4GgJcGQTpbJGcHtjpZQtAxUMEIGCisGAQQBgjcCAQwxNDAyoBSA # EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w # DQYJKoZIhvcNAQEBBQAEggEAt4CeH15104VWcVaLY2mp5NXu7MtrjYNknUN2ZJgx # IjwSLijCtCy44s0cyaOyCqlEFaPBJFgXDJJ/tv149/rtyjt0W55QCFqAWa0+Bsyb # lWBeLx7q+hRuSETI/syP/IoyqW2IXs2NuP6hbOm0AiwhVxdo5agbgwwXr75/8oce # ck2nykUd66dfFsObjvfBFeC/YJXDI1hVBmGzcfFQRzSEgxDFjG/rfi7Ly8VFcQVS # BkqaulxtH3HXAFd5P50/V7BJnFe5oGu4ZzTScqwbc0sytaNaY5Oy4xEaKAAVGYRw # oVEmTJ1nI3rRtw2VKS9OEXlcNoJObzYKN43UTDDjNazvZKGCF5cwgheTBgorBgEE # AYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUD # BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD # ATAxMA0GCWCGSAFlAwQCAQUABCBbXDQ789brKooSZ3SxsaRGTkKLsXtdd4rdAlld # 39Qn+wIGajFN3sznGBMyMDI2MDcxNTE4MjU1OS4zNzlaMASAAgH0oIHRpIHOMIHL # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN # aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT # UyBFU046QTkzNS0wM0UwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAifVwIPDsS5XLQABAAAC # JzANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe # Fw0yNjAyMTkxOTQwMDRaFw0yNzA1MTcxOTQwMDRaMIHLMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046QTkzNS0wM0Uw # LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDixWy1fDOSL4qj3A1pady+elID # LwnF3UuLzJIOWwGHcEgrxxwtnyviUIDmmxylTUl1u+2rBPp2zT4BwwQhvGaJpExq # vPLlDFlbfmSflKI86eFqofiZ7j8NTRO4l7wGg9Njm+muNauTcFW2qdfIjKE950Ok # rm9MnMOGYy+fibNYdxTPRPq1T4MLZK3s3vdMyMEOldcOQkSKpxD6/1Gk6gOmCu2K # gI8f0ex6vYxnKDl9W0OLSEa/6y82oIbsm+1QBifOQ47xWKTG1CmvtGr85LzA75/M # AcUmRw5/of/qET0UFV1WulMcJrI6DASAsNCNB+6WLrotuBZAj+VMlqbn5RMZ6Q4I # Y7JwaAiIXh7VjxrnwUOYZG8WEGhfrA98di+7LEn9AqvvEOyG+UQcjVhCCbMGXigJ # XSApeyeWupCsD0jgQMNCxfB5BLBDWxgdY3dJBEPgxfkgTDQLBggtVv2d5CYxHKgI # ItB4bI5eSb5jkIG2WotnFetT0legpw/Eozwf39ao6tENY21eVWIzRw/GsmvwjYQF # 6vVrxOD0pGVsfqGF8s3VPeY7hI2TxHFMqNA0IB/a2NLY7JTxYAKAP/11EJZt7xbq # DLMgD1YDdGEzGpQijm3nAPCL2CebP/jmu90abJ2W425yglGHTI/nCBrwSpfRCgwz # rfFelJaCKM6+35aFfwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFNLW58N4MGSG6ud7 # jWqgT92orfReMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud # HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr # BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw # DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQAqncud4PSC1teb2H6n # Ruy7sDiKK13FXJirVB4Tfwjdo2Mb+QL4j7wZ/k4G9P0CANHZFrDQcK0VFDTysrYu # 8Z0Aha14acDZPsyIoPvAGRRhaHEuf7NckRjkfa/ylo1KyII8jbL9N9sJAqBPL8V4 # FNBjljv+1GHDOw127rZz5ZSTPoAPb2SA0v5yDgcpUMfxglPyp6cnPPoQpTtD9OGx # 8Dwm2P+o1TPxBIy6I0T9RauulogVCvKwflfeLTcKAvnSG1rCjerSXmU1DNXOsAD/ # bsrSjgbX5mAbD7XTRMF/vawAWESFcn/BjjizxeWZb00aYSlkJA2rVtFlMM481aVW # XdAbXPP5RzUiWTlgyHf/G7lCxHYWGIZuB13T3aI6Y8mEgn/ou40aiFJo8r0+i0P5 # GdNneWtxiR0CMKUfko+5s/73cwe1Wfp8BKXa270cicVQasFf5sRV7pFm+V7fNRXw # Cu7anTOmga76zO7/2t+zOlibvphT+Q6Zd+B2qYsSn4xBaY+YzHpnycLW5cvJyhPx # BCcb1oRYfhRzCADb2utI2EtGCjc2P2ii4LyR4QMb/n8cOweL9IqVTKKzzVk+zZJx # V3vrp4LyuQXw0O30la6BcHdNAAAB9UC83zs3G9d+AlIfZLM97tMUNKWjbBpIirFx # 6LTDFXVtZQd7hqzLYByjbjH0ujCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA # AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX # 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q # UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d # q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN # pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k # rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d # Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS # Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8 # QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm # gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF # ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID # AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU # KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1 # GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0 # bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA # QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL # j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p # Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz # LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU # tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN # 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU # 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5 # KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy # qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6 # 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE # AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp # AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd # FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb # atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd # VTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOkE5MzUtMDNFMC1E # OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw # BwYFKw4DAhoDFQAjHzqthPwO0GDckDMA6x54lIiMKqCBgzCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7gIDvzAiGA8y # MDI2MDcxNTEzMDM1OVoYDzIwMjYwNzE2MTMwMzU5WjB3MD0GCisGAQQBhFkKBAEx # LzAtMAoCBQDuAgO/AgEAMAoCAQACAj41AgH/MAcCAQACAhhfMAoCBQDuA1U/AgEA # MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI # AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAF7mJUZNOaZxvhltEhs5AaJUWFwa # Z6rWAy6U4KnhY18kNHWjCdfolSLnkHG/PMWeczXofGppuyS2Pej9PdW3gMnAxSot # hqfd4awFoeQ9kagUBYqrJ7cWWu12uTKAO4BExGtRelUC7KZJNNGpgM+pZF7sS5vM # ZWb/tF2sCYVGPrnsQNiWhRa7Tx9G9QCdApzbF949aet3Sg4IZSgOITM9efP3mGtI # DmanJ1PysY5JlwElHWt+3JqwFb2jYbBQ9QmCpc2fNTY9vwuzQTBc2yOYFy+uYf/S # GImg752RIE4ziSpmlb/+unkvTyUjJ5jubfYjyuSCr1/nKUtrNEwhf7fBkh4xggQN # MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAifV # wIPDsS5XLQABAAACJzANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCBA6z0biRQPVgvS0Yt1cxol3cdf # HZ42fPZr8fMCHUiluDCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIOXnARo1 # oVIcOLJKDqlE0adq/jZ9TXdlnXWRcXGThBFyMIGYMIGApH4wfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAIn1cCDw7EuVy0AAQAAAicwIgQgS+ztoLOi # U9Qt8MX8s1Shg6Pyp/Ni40xmRNcMGvqdZaIwDQYJKoZIhvcNAQELBQAEggIAD2B0 # kJbLByWL3mjW3+2FzjIsYEa1Mon7b4GW71lEVuQ3vqn966rxPi0P8bA1A5Vc1H0q # 8suxkJaEV37TITMf6p+xWF+iNw40iaixrCanjXh39xjfDKQlLPp85m+uTzJyAgZ2 # qLTncPrlEgpya9vSiU55IZWiRChxLSuqZRjh0bB+npWGxNWjUD4CmkonLY2+wBDa # m0dXyuv5A94ilRzL9N8Q0xVTIElz/sukdB/lAODGBStr3rB5g1IbWIdoocEm8O6o # G5qCkuYE1TNgQDV0++pcxr4uitWciphvTTwKMOqCqBLzDhvvgvnctHp0k6wkymcl # OGP1rnwC4z28eM0Y6q4p8v1ejoj0oyR51KDnz6lRzEJ1rZtAkBhm2DkCJebyA2k/ # Lgk7J4Aej4njNY16pR9wIXQVktXeb64jPZIDgpP9i5/rOVvhfUfO8S86ZSBAgHr/ # nXfxYC+SLPY4W/EAIrpas7DPGSkCJaPZ4ogXTrA6UCAq/fWx1hPiOYJ5eiF3oqWf # rE7N1D4Z8bizq3uHBAA5SmHkh7hAvTf+Y+SSnwBk0Ztl74HunW+r/hU6sVJMzpWL # DSKymtoNz6b+QZUbKKfY7yqAf+a+Yqdzg2YvZUFyQAUGQN16mXyM3fU+1Go7HTmK # L3yiXPnSxnAZ0a7jIuEu/xh5BZ3O52qal3AZMHE= # SIG # End signature block |