AzureValidation/AzureADConfiguration.psm1
<#############################################################
# # # Copyright (C) Microsoft Corporation. All rights reserved. # # # #############################################################> $ErrorActionPreference = 'Stop' Import-LocalizedData LocalizedData -BaseDirectory $PSScriptRoot -Filename AzureADConfiguration.Strings.psd1 $AzurePowerShellClientID = "1950a258-227b-4e31-a9cf-717495945fc2" $AzurePowerShellReturnUri = "urn:ietf:wg:oauth:2.0:oob" function Get-AzureURIs { <# .SYNOPSIS Resolve Azure URIs for a given Azure Service .DESCRIPTION Resolve Azure URIs for a given Azure Service .EXAMPLE Get-AzureURIs -AzureEnvironment AzureCloud .INPUTS AzureEnvironment - string - should be AzureCloud, AzureChinaCloud, AzureGermanCloud .OUTPUTS None - logging only .NOTES General notes #> [OutputType([Hashtable])] [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateSet('AzureCloud', 'AzureChinaCloud', 'AzureUSGovernment', 'AzureGermanCloud')] [string] $AzureEnvironment ) $data = switch ($AzureEnvironment) { 'AzureCloud' { @{ GraphUri = "https://graph.windows.net/" LoginUri = "https://login.microsoftonline.com/" ManagementServiceUri = "https://management.core.windows.net/" ARMUri = "https://management.azure.com/" } } 'AzureChinaCloud' { @{ GraphUri = "https://graph.chinacloudapi.cn/" LoginUri = "https://login.chinacloudapi.cn/" ManagementServiceUri = "https://management.core.chinacloudapi.cn/" ARMUri = "https://management.chinacloudapi.cn/" } } 'AzureUSGovernment' { @{ GraphUri = "https://graph.windows.net/" LoginUri = "https://login-us.microsoftonline.com/" ManagementServiceUri = "https://management.core.usgovcloudapi.net/" ARMUri = "https://management.usgovcloudapi.net/" } } 'AzureGermanCloud' { @{ GraphUri = "https://graph.cloudapi.de/" LoginUri = "https://login.microsoftonline.de/" ManagementServiceUri = "https://management.core.cloudapi.de/" ARMUri = "https://management.microsoftazure.de/" } } default { throw New-Object NotImplementedException("Unknown environment type '$AzureEnvironment'") } } return $data } function Get-AADToken { [CmdletBinding()] param( [string] $ResourceUri, [string] $TenantId, [PSCredential] $Credential, [string] $UserId, [Hashtable] $AzureURIs, [Microsoft.IdentityModel.Clients.ActiveDirectory.PromptBehavior] $PromptBehavior ) $context = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext "$($AzureURIs.LoginUri)$TenantId" if ($Credential -ne $null) { $aadCredential = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserCredential $Credential.UserName,$Credential.Password return $context.AcquireToken($ResourceUri, $AzurePowerShellClientID, $aadCredential) } else { if ([string]::IsNullOrEmpty($UserId)) { $userIdentifier = [Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier]::AnyUser } else { $userIdentifier = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier $UserId, OptionalDisplayableId } return $context.AcquireToken( $ResourceUri, $AzurePowerShellClientID, $AzurePowerShellReturnUri, $PromptBehavior, $userIdentifier, "site_id=501358&display=popup") } } function Set-CookiesOptions { [CmdletBinding(SupportsShouldProcess=$True,ConfirmImpact="Medium")] $signature = @' [DllImport("wininet.dll", SetLastError = true)] public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int lpdwBufferLength); [DllImport("wininet.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern int PrivacySetZonePreference(int dwZone, int dwType, int dwTemplate, string pszPreference); '@ $type = Add-Type -MemberDefinition $signature -Name 'NativeMethods' -Namespace 'AzureStack.SetCookies' -PassThru -ErrorAction Ignore $INTERNET_OPTION_END_BROWSER_SESSION = 42 $URLZONE_INTERNET = 3 $PRIVACY_TYPE_FIRST_PARTY = 0 $PRIVACY_TEMPLATE_ADVANCED = 101 $preference = "IE6-P3PV1/settings: always=a session=a" $type::InternetSetOption([IntPtr]::Zero, $INTERNET_OPTION_END_BROWSER_SESSION, [IntPtr]::Zero, 0) $type::PrivacySetZonePreference($URLZONE_INTERNET, $PRIVACY_TYPE_FIRST_PARTY, $PRIVACY_TEMPLATE_ADVANCED, $preference) } function Get-AADTenantIds { [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [Hashtable] $AzureURIs, [Parameter(Mandatory=$false)] [PSCredential] $Credential ) Set-CookiesOptions $token = Get-AADToken -ResourceUri $AzureURIs.ManagementServiceUri -TenantId "Common" -Credential $Credential -PromptBehavior Always -AzureURIs $AzureURIs $UserInfo = @{ FirstName = $token.UserInfo.GivenName LastName = $token.UserInfo.FamilyName Account = $token.UserInfo.DisplayableId } $tenantsResponse = Invoke-RestMethod -Method Get -Uri "$($AzureURIs.ARMUri)tenants?api-version=2016-02-01" -Headers @{Authorization = "Bearer $($token.AccessToken)"} $tenantIds = [Array]$tenantsResponse.Value.tenantId return @{ UserInfo = $UserInfo TenantIds = $tenantIds } } function Get-AADTenantDetail { [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string] $TenantId, [Parameter(Mandatory=$true)] [string] $UserId, [Parameter(Mandatory=$true)] [Hashtable] $AzureURIs, [Parameter(Mandatory=$false)] [PSCredential] $Credential ) try { $token = Get-AADToken -ResourceUri $AzureURIs.GraphUri -TenantId $TenantId -Credential $Credential -PromptBehavior never -UserId $UserId -AzureURIs $AzureURIs } catch [Microsoft.IdentityModel.Clients.ActiveDirectory.AdalException] { if ($_.Exception.ErrorCode -eq "user_interaction_required") { $token = Get-AADToken -ResourceUri $AzureURIs.GraphUri -TenantId $TenantId -Credential $Credential -PromptBehavior auto -UserId $UserId -AzureURIs $AzureURIs } else { throw } } $graphUri = "$($AzureURIs.GraphUri)myOrganization/oauth2PermissionGrants?`$top=1&api-version=1.5" try { Invoke-RestMethod -Method Get -Uri $graphUri -Headers @{Authorization = "Bearer $($token.AccessToken)"} | Out-Null } catch { if ($_.Exception.Response.StatusCode -eq 'Internal Server Error') { throw ($LocalizedData.GraphEndpointError -f $graphUri) } if ($_.Exception.Response.StatusCode -eq 'Forbidden') { return $null } else { throw $_ } } $tenantResponse = Invoke-RestMethod -Method Get -Uri "$($AzureURIs.GraphUri)myOrganization/tenantDetails?api-version=1.5" -Headers @{Authorization = "Bearer $($token.AccessToken)"} return @{ Id = $tenantResponse.Value.objectId DisplayName = $tenantResponse.Value.DisplayName DomainName = ($tenantResponse.Value.VerifiedDomains | Where-Object {$_.default}).name Token = $token.AccessToken RefreshToken = $token.RefreshToken } } # Dlls packaged with AzureRM $AzureRMBase = (Get-Module AzureRM.Profile -ListAvailable | Sort-Object version -Descending | Select-Object -first 1).ModuleBase [System.Reflection.Assembly]::LoadFile("$AzureRMBase\Microsoft.IdentityModel.Clients.ActiveDirectory.dll") | Out-Null [System.Reflection.Assembly]::LoadFile("$AzureRMBase\Microsoft.IdentityModel.Clients.ActiveDirectory.WindowsForms.dll") | Out-Null function Get-AADTenantDetails { <# .SYNOPSIS Resolve Azure Tenant Details .DESCRIPTION Resolve Azure Tenant Details .EXAMPLE Get-AADTenantDetails -AzureEnvironment AzureCloud -Credential $tenantCredential .INPUTS AzureEnvironment - string - should be AzureCloud, AzureChinaCloud, AzureGermanCloud Credetential - PSCredential .OUTPUTS Hashtable of tenant details .NOTES General notes #> [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $AzureEnvironment, [Parameter(Mandatory=$false)] [PSCredential] $Credential, [Parameter(Mandatory=$false)] [string] $AADDirectoryTenantName ) Write-Progress -Activity $LocalizedData.InfoSigningIn $azureURIs = Get-AzureURIs -AzureEnvironment $AzureEnvironment $TenantsInfo = Get-AADTenantIds -Credential $Credential -AzureURIs $azureURIs $TenantIds = [Array] $TenantsInfo.TenantIds # Workaround to add the target tenant Id if specified to the list of "resolved" tenant memberships if ($AADDirectoryTenantName) { $targetTenantId = Get-TenantIdFromName -azureEnvironment $AzureEnvironment -tenantName $AADDirectoryTenantName if ($TenantIds -inotcontains $targetTenantId) { $TenantIds = ([Array] $TenantsInfo.TenantIds) + $targetTenantId } } $tenantDetails = @() for ($i = 1; $i -le $TenantIds.Count; $i++) { Write-Progress -Activity $LocalizedData.InfoSigningIn -PercentComplete ($i * 100 / ($TenantIds.Count + 1)) $tenantDetail = Get-AADTenantDetail -Credential $Credential -TenantId $TenantIds[$i-1] -UserId $TenantsInfo.UserInfo.Account -AzureURIs $azureURIs if ($tenantDetail) { $tenantDetails += $tenantDetail } } Write-Progress -Activity $LocalizedData.InfoSigningIn -Completed return @{ UserInfo = $TenantsInfo.UserInfo TenantDetails = $tenantDetails } } function Get-AADUserObjectId { [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $AzureEnvironment, [string] $TenantId, [PSCredential] $Credential ) $azureURIs = Get-AzureURIs -AzureEnvironment $AzureEnvironment return (Get-AADToken -ResourceUri $azureURIs.GraphUri -TenantId $TenantId -Credential $Credential -AzureURIs $azureURIs).UserInfo.UniqueId } <# .Synopsis Decodes a base64-encoded string. #> function ConvertFrom-Base64String { [CmdletBinding()] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [ValidateNotNull()] [string] $Base64String ) process { $data = $Base64String.Replace('-','+').Replace('_','/') switch ($data.Length % 4) { 0 { break } 2 { $data += '==' } 3 { $data += '=' } default { Write-Error "Invalid data: '$data'" } } $bytes = [System.Convert]::FromBase64String($data) Write-Output $bytes } } <# .Synopsis Converts a Jwt Token into an object. #> function ConvertFrom-JwtToken { [CmdletBinding()] Param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true)] [string] $JwtToken ) function ConvertFrom-RawData([String]$Data) { [System.Text.Encoding]::UTF8.GetString((ConvertFrom-Base64String $Data)) | ConvertFrom-Json | Write-Output } $parts = $JwtToken.Split('.') [pscustomobject]@{ Headers = ConvertFrom-RawData $parts[0] Claims = ConvertFrom-RawData $parts[1] Signature = $parts[2] } | Write-Output } function Get-AzureADTenantDetails { <# .SYNOPSIS Resolve Azure AD Tenant Details .DESCRIPTION Resolve Azure AD Tenant Details .EXAMPLE Get-AzureADTenantDetails -AzureEnvironment AzureCloud -AADAdminCredential $AADAdminCredential $AADDirectoryTenantName $AADDirectoryTenantName .INPUTS AzureEnvironment - string - should be AzureCloud, AzureChinaCloud, AzureGermanCloud AADAdminCredential - PSCredential - AAD Admin Credential AADDirectoryTenantName - string - AAD tenant Name .OUTPUTS Hashtable of Azure AD tenant details .NOTES General notes #> [OutputType([Hashtable])] [CmdletBinding()] param( [Parameter(Mandatory=$true)] [ValidateNotNullOrEmpty()] [string] $AzureEnvironment, [Parameter(Mandatory=$false)] [PSCredential] $AADAdminCredential, [Parameter(Mandatory=$false)] [string] $AADDirectoryTenantName ) $claimsProvider = $AzureEnvironment $azureResult = Get-AADTenantDetails -AzureEnvironment $AzureEnvironment -Credential $AADAdminCredential -AADDirectoryTenantName $AADDirectoryTenantName $azureToken = $null $azureRefreshToken = $null $tenantId = $null $tenantName = $null if ($null -eq $azureResult.TenantDetails -or @($azureResult.TenantDetails).Count -eq 0) { Write-Error ($LocalizedData.AADAccountNotAdmin -f $($azureResult.UserInfo.Account)) } elseif (@($azureResult.TenantDetails).Count -gt 1 -and -not $AADDirectoryTenantName) { Write-Error ($LocalizedData.MoreThanOneTenant -f @($($azureResult.UserInfo.Account), $($azureResult.TenantDetails.DomainName -join ', '))) } elseif (@($azureResult.TenantDetails).Count -ige 1 -and $AADDirectoryTenantName) { $tenantDetail = $azureResult.TenantDetails | Where-Object DomainName -eq $AADDirectoryTenantName if ($tenantDetail) { $azureToken = $tenantDetail.Token $azureRefreshToken = $tenantDetail.RefreshToken $tenantName = $tenantDetail.DomainName $tenantID = $tenantDetail.ID } else { Write-Error ($LocalizedData.NotAdminOfTenant -f @($($azureResult.UserInfo.Account), $AADDirectoryTenantName, $($azureResult.TenantDetails.DomainName -join ', '))) } } else { $azureToken = $azureResult.TenantDetails.Token $azureRefreshToken = $azureResult.TenantDetails.RefreshToken $tenantName = $azureResult.TenantDetails.DomainName $tenantID = $azureResult.TenantDetails.ID } $adminSubscriptionOwner = (ConvertFrom-JwtToken $azureToken).Claims.unique_name return @{ UserName = $azureResult.UserInfo.Account Token = $azureToken RefreshToken = $azureRefreshToken AdminSubscriptionOwner = $adminSubscriptionOwner TenantDirectoryName = $tenantName TenantDirectoryID = $tenantID ClaimsProvider = $claimsProvider } } <# .SYNOPSIS Returns Azure AD directory tenant ID given the login endpoint and the directory tenant name .DESCRIPTION Makes an unauthenticated REST call to the given Azure environment's login endpoint to retrieve directory tenant id .EXAMPLE $tenantId = Get-TenantIdFromName -azureEnvironment "Public Azure" -tenantName "msazurestack.onmicrosoft.com" #> function Get-TenantIdFromName { [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $azureEnvironment, [Parameter(Mandatory=$true)] [ValidateNotNull()] [string] $tenantName ) $azureURIs = Get-AzureURIs -AzureEnvironment $AzureEnvironment $uri = "{0}/{1}/.well-known/openid-configuration" -f ($azureURIs.LoginUri).TrimEnd('/'), $tenantName $response = Invoke-RestMethod -Uri $uri -Method Get Write-Verbose -Message "using token_endpoint $($response.token_endpoint) to parse tenant id" $tenantId = $response.token_endpoint.Split('/')[3] $tenantIdGuid = [guid]::NewGuid() $result = [guid]::TryParse($tenantId, [ref] $tenantIdGuid) if(-not $result) { Write-Error "Error obtaining tenant id from tenant name" } else { Write-Verbose -Message "Tenant Name: $tenantName Tenant id: $tenantId" return $tenantId } } Export-ModuleMember -Function Get-AzureADTenantDetails Export-ModuleMember -Function Get-AzureURIs Export-ModuleMember -Function Get-TenantIdFromName Export-ModuleMember -Function Get-AADToken # SIG # Begin signature block # MIIkhwYJKoZIhvcNAQcCoIIkeDCCJHQCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDNXMW3a0uE9Y3A # R7JCGq7en00CE5+Zgs+v8UvTQyq6EKCCDYEwggX/MIID56ADAgECAhMzAAABA14l # HJkfox64AAAAAAEDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMTgwNzEyMjAwODQ4WhcNMTkwNzI2MjAwODQ4WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDRlHY25oarNv5p+UZ8i4hQy5Bwf7BVqSQdfjnnBZ8PrHuXss5zCvvUmyRcFrU5 # 3Rt+M2wR/Dsm85iqXVNrqsPsE7jS789Xf8xly69NLjKxVitONAeJ/mkhvT5E+94S # nYW/fHaGfXKxdpth5opkTEbOttU6jHeTd2chnLZaBl5HhvU80QnKDT3NsumhUHjR # hIjiATwi/K+WCMxdmcDt66VamJL1yEBOanOv3uN0etNfRpe84mcod5mswQ4xFo8A # DwH+S15UD8rEZT8K46NG2/YsAzoZvmgFFpzmfzS/p4eNZTkmyWPU78XdvSX+/Sj0 # NIZ5rCrVXzCRO+QUauuxygQjAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUR77Ay+GmP/1l1jjyA123r3f3QP8w # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDM3OTY1MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn/XJ # Uw0/DSbsokTYDdGfY5YGSz8eXMUzo6TDbK8fwAG662XsnjMQD6esW9S9kGEX5zHn # wya0rPUn00iThoj+EjWRZCLRay07qCwVlCnSN5bmNf8MzsgGFhaeJLHiOfluDnjY # DBu2KWAndjQkm925l3XLATutghIWIoCJFYS7mFAgsBcmhkmvzn1FFUM0ls+BXBgs # 1JPyZ6vic8g9o838Mh5gHOmwGzD7LLsHLpaEk0UoVFzNlv2g24HYtjDKQ7HzSMCy # RhxdXnYqWJ/U7vL0+khMtWGLsIxB6aq4nZD0/2pCD7k+6Q7slPyNgLt44yOneFuy # bR/5WcF9ttE5yXnggxxgCto9sNHtNr9FB+kbNm7lPTsFA6fUpyUSj+Z2oxOzRVpD # MYLa2ISuubAfdfX2HX1RETcn6LU1hHH3V6qu+olxyZjSnlpkdr6Mw30VapHxFPTy # 2TUxuNty+rR1yIibar+YRcdmstf/zpKQdeTr5obSyBvbJ8BblW9Jb1hdaSreU0v4 # 6Mp79mwV+QMZDxGFqk+av6pX3WDG9XEg9FGomsrp0es0Rz11+iLsVT9qGTlrEOla # P470I3gwsvKmOMs1jaqYWSRAuDpnpAdfoP7YO0kT+wzh7Qttg1DO8H8+4NkI6Iwh # SkHC3uuOW+4Dwx1ubuZUNWZncnwa6lL2IsRyP64wggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIWXDCCFlgCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAQNeJRyZH6MeuAAAAAABAzAN # BglghkgBZQMEAgEFAKCB3jAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgJZqGOBUy # E/vQZQroo5lNJtt/MdJR2fPH8yd82IE21BgwcgYKKwYBBAGCNwIBDDFkMGKgSIBG # AE0AaQBjAHIAbwBzAG8AZgB0ACAAQQB6AHUAcgBlAFMAdABhAGMAawAgAFAAYQBy # AHQAbgBlAHIAVABvAG8AbABrAGkAdKEWgBRodHRwOi8vQ29kZVNpZ25JbmZvIDAN # BgkqhkiG9w0BAQEFAASCAQCUYx/BxK7i/k0XFWXwYeK2u4u18y4Sq8aZNzwMaWF0 # 45TWgyRfYEifhjA9zxNoBQZXIsvz8bRK+2J8Bd5Li5skjtVn+89T8TKAXgPEIMSJ # zwOXDh1GzkOyCnxW+7ivhjlsUWqTTjt6y+zN8OYxskFyhX2yjIoqujCSI/39xiSX # WRv/Iw3RkjPofd/+OqUyrDgKnL1LM0Hz6gYFvxn8+JqLyIO4qvt0qvSLdAjyALHS # tplLPnzLa/uP245IrNq6jTh2SIwuMvXVu3U60MLA3zVlxUCV2HHuaTyNUxs5DyLy # jDjLB8h9BVn5MF7wmbzZGEljl2HhCqEuJjBABLV+xiYXoYITtjCCE7IGCisGAQQB # gjcDAwExghOiMIITngYJKoZIhvcNAQcCoIITjzCCE4sCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIML/FS1ZpYnlql6NAGquSoZbjE6b+BhxCwQGDoxj # yjDfAgZb2cjKK30YEzIwMTgxMTAxMjE1MzQyLjA0NVowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjoxNDhDLUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDyEwggT1MIID3aADAgECAhMzAAAA1acj5XiVagn/AAAA # AADVMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTE4MDgyMzIwMjY0NVoXDTE5MTEyMzIwMjY0NVowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjoxNDhD # LUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMIUCSuuF/E0gjL87onw # du50M1BwUxQwtrZWEQYCmLMhGhNR/3eQxBD9lqY4FYxhRZ1bxfsIjs7SKvcF0m1Y # fhgK5j9BdZvzueedkEY8MVUu41zB18dn+Nmaelz5vFz9BLq5hJPAEdZg3ZR+4dP7 # 19tVXJP/iVkslLTTKH/YqT8VciemXOUaCsK/Re7xdBe8qBShsOwbgD7xVYwgmevz # +bSZ3UsWClccBB9kMhNHhniIKkuMo4BNJ5te+yp8KBVyzontKmtOhS1XKNSIywEW # AzRGKZtlP36DVGOy9SGajweV0RjHkqZMUD1tEiIaDoBpiZXacHPne1FQGwSjK4NM # lEsCAwEAAaOCARswggEXMB0GA1UdDgQWBBQpaKMxlHZD2V9dDZuf07KE9mI3hDAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB52cvC6cTRwKstDtImv9pnoAv99sC2 # wQLy4rmBzWL2kI/sleBNXo74cqdAWoYfLRCQo+DuW2m5/0mMe4DpvPDLYCQSSJkA # tu/hUL80wik5ZqaNvIfp7h1/CJ/KMuIgd8WbhNZxLyG5S4W7As667QNifweCrjFq # ZMV9XtBO2wjz9Qti1RjdOImhX7V4kdCYKStdQg2xJhUU9ZtPqGprthaPqto9Fheq # s37Cw2vO3tAjYqMXTZCvybz+4bUHxIWvqvJIXLpuPPz1zCvCotLfBzzBiV9miIEB # Xsnv2qIIkdDnISqlrIZ1nXdRHdXl4ZhBf3wAZR/EwILvKaksKFYzrk4LMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIDrzCCApcCAQEwgf6hgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjox # NDhDLUM0QjktMjA2NjElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIlCgEBMAkGBSsOAwIaBQADFQCtwyS80dl18F3Q3UUfOTGebtaesaCB3jCB # 26SB2DCB1TELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNV # BAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcG # A1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJzAlBgNVBAsT # Hm5DaXBoZXIgTlRTIEVTTjo1N0Y2LUMxRTAtNTU0QzErMCkGA1UEAxMiTWljcm9z # b2Z0IFRpbWUgU291cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIFAN+F # rwYwIhgPMjAxODExMDIwMDU3NDJaGA8yMDE4MTEwMzAwNTc0MlowdjA8BgorBgEE # AYRZCgQBMS4wLDAKAgUA34WvBgIBADAJAgEAAgEhAgH/MAcCAQACAhj2MAoCBQDf # hwCGAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwGgCjAIAgEAAgMH # oSChCjAIAgEAAgMHoSAwDQYJKoZIhvcNAQEFBQADggEBALwozOEZ9M6iJsdNsfFg # d5wm4k2VjObqEv39lkVwILtJ+pK7tvbCY1uR36qK74C02PD1n6jICtppnJQczj8o # 3hQVVbQJ6+ESOfjlcCAjy+4zc5B57rkxplenv4MwhraxU6bZOUB8ScPtSB6i1n+j # biSHadBZT+e7eloeNjyEloz2J3tOGTmU7rvpv/WqSYL8zxJFZJsIJJflKzzsA/7a # ziGqfa6ONKirq9Qc2vH+5yMEhCo8xVXtOMhsHwGZtHEQn9214Q8c8JytC5L+gGeg # lE+gXRbCvXfnMLbMk8a/l1BuQrFIHzMDSWas/7rlk3zt/DL1J6Lp0iLG/g0V5uXO # vqUxggL1MIIC8QIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAIT # MwAAANWnI+V4lWoJ/wAAAAAA1TANBglghkgBZQMEAgEFAKCCATIwGgYJKoZIhvcN # AQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCB4MwMSHgLCaW5RAxFa # JM11svQxlpSvAwtFo+9rGizh2TCB4gYLKoZIhvcNAQkQAgwxgdIwgc8wgcwwgbEE # FK3DJLzR2XXwXdDdRR85MZ5u1p6xMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh # bXAgUENBIDIwMTACEzMAAADVpyPleJVqCf8AAAAAANUwFgQUDcD7Gv0/x9wC/Ohl # 7maZLF2INvAwDQYJKoZIhvcNAQELBQAEggEAZqf+C2NME6jEjy1rjrKzlhvDJIqn # A/3czTxCEX9qxuaTXPyRgB4/V5BVElrFsFLOOqw/d9mtYr2djDO/rlu3fhE140Rx # 1gvqWxywCZ0CHnxmVVX1pG4nhqUj7jpySrBsFCmQbe4VaTxw19qQmR43HFc8cwYB # mMy5V4nEugd71nzmjNm9NF3IfM4hU9cvBA4tVZGmOl6nw5GE2ZYP1ii1DY0i85Q8 # sZSxVzXbWT6LNTVluEo9DP6wl91V7tVns9VURo0H0zwdQkcmQ6oLJNMWHlFqVNyg # dTYJ6FpVOLccPcgFdXbTCn8yj2UajeUrlTZ7JKPKvNuv5nBPhF5HATLwMg== # SIG # End signature block |