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)] [string] $AzureEnvironment ) if ($AzureEnvironment -eq "AzureChinaCloud") { return @{ GraphUri = "https://graph.chinacloudapi.cn/" LoginUri = "https://login.chinacloudapi.cn/" ManagementServiceUri = "https://management.core.chinacloudapi.cn/" ARMUri = "https://management.chinacloudapi.cn/" } } elseif ($AzureEnvironment -eq "AzureGermanCloud") { return @{ GraphUri = "https://graph.cloudapi.de/" LoginUri = "https://login.microsoftonline.de/" ManagementServiceUri = "https://management.core.cloudapi.de/" ARMUri = "https://management.microsoftazure.de/" } } else { return @{ GraphUri = "https://graph.windows.net/" LoginUri = "https://login.windows.net/" ManagementServiceUri = "https://management.core.windows.net/" ARMUri = "https://management.azure.com/" } } } 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 ) Write-Progress -Activity $LocalizedData.InfoSigningIn $azureURIs = Get-AzureURIs -AzureEnvironment $AzureEnvironment $TenantsInfo = Get-AADTenantIds -Credential $Credential -AzureURIs $azureURIs $TenantIds = [Array] $TenantsInfo.TenantIds $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 $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 -Verbose Write-Verbose -Message "using token_endpoint $($response.token_endpoint) to parse tenant id" -Verbose $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" -Verbose return $tenantId } } Export-ModuleMember -Function Get-AzureADTenantDetails Export-ModuleMember -Function Get-AzureURIs Export-ModuleMember -Function Get-TenantIdFromName # SIG # Begin signature block # MIIdqAYJKoZIhvcNAQcCoIIdmTCCHZUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUKd14Vo9TVPF68B5Cn/+imiMd # v2egghhUMIIEwjCCA6qgAwIBAgITMwAAALpqNt4arb08HwAAAAAAujANBgkqhkiG # 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G # A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw # HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ2 # WhcNMTgwOTA3MTc1ODQ2WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046 # RjZGRi0yREE3LUJCNzUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl # cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDbkc0j1DekYIbe # VMtJHd3kxvCvQnxVC0dNcc2Tvu9dDBtILt4pNaDkO8xhEFIzOgsVucV5UZaQHtmu # vcwvG3F1xrgKCTDGgRhdCb1/JGBs67K7emkIkF8dgbmZtITASGSjwzy2jhlK+eMn # cSBuuoatutTxSS86jJxeN5pml2HV+z648r+rkqVmJpTLR2EnI07QcXt0nZ/g9J/k # /A7mfSfFGeFHRHsVc/abypJdUNWSGv1RVYJ7FoicxUoXudbiYBdTDfsyvZPrOJ28 # 9S8KoZ7KUxGuSGGDkyfctQgZoZFI+XlD89KmKiNbahq9hG5m5weSGwVePfw/99JH # GTI+jC25AgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQUW+wPFw2KORr5sOGDFIF72bhI # HKAwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ # oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv # TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB # BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z # b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG # 9w0BAQUFAAOCAQEAB4FDLPBU1MrtqkPH4Y0KmjsyoVUgQhQN/HvuW46j0DrKoDVr # cloJb21SN2CQe9oKarFfjyxfmXZ3ChKmB42MBaHkETqo9LAG22q3cpxbYO8W0uke # 4RpYA/rV8V+S310vexazwXLDrhddWQzaRJQT3Brq/H6T2LwBqV3fk0mKUXTKoPf2 # RrQu4+tAmZJBv5QOxhmNRiR4EazqERp4QUmIJfiPw9vmyf0K3mn6inBhYFIFj4wz # hI1CfKpy/JwM1WozANowmalYYrBUcNa/lk2+9ZeywjI8TynnEo9HxtPhiugw2qGV # mOKyJZIFdcS2lPoGPPkuPiTikd4ipJgIFAc7SzCCBgEwggPpoAMCAQICEzMAAADE # 6Yn4eoFQ6f8AAAAAAMQwDQYJKoZIhvcNAQELBQAwfjELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2ln # bmluZyBQQ0EgMjAxMTAeFw0xNzA4MTEyMDIwMjRaFw0xODA4MTEyMDIwMjRaMHQx # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHjAcBgNVBAMTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC # ggEBAIiKuCTDB4+agHkV/CZg/HKILPr0o5eIlka3o8tfiS86My4ekXj6fKkfggG1 # essavAPKRuvFmff7BB3yhQr/Im6h8mc9xScY5Sgf9QSUQWPs47oVjO0TmjXeOHBU # bzvsrUUJMEnBvo8wmQzLdsn3c5UWd9GLu5THCIUg7R6oNfFxwuB0AEuK0tyR69Z4 # /o36rWCIPb25H65il7/FhLGQrtavK9NU+zXazXGS5h7/7HFry38IdnTgEFFI1PEA # yEhMowc15VkN/XycyOZa44X11poPH46m5IQXwdbKnx0Bx/1IpxOSM5chSDL4wiSi # ALK+U8qDbilbge84boDzu+wTC+sCAwEAAaOCAYAwggF8MB8GA1UdJQQYMBYGCisG # AQQBgjdMCAEGCCsGAQUFBwMDMB0GA1UdDgQWBBTL1mKEz2A56v9nwlzSyLurt8MT # mDBSBgNVHREESzBJpEcwRTENMAsGA1UECxMETU9QUjE0MDIGA1UEBRMrMjMwMDEy # K2M4MDRiNWVhLTQ5YjQtNDIzOC04MzYyLWQ4NTFmYTIyNTRmYzAfBgNVHSMEGDAW # gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v # d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw # MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx # XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB # AAYWH9tXwlDII0+iUXjX7fj9zb3VwPH5G1btU8hpRwXVxMvs4vyZW5VfETgowAVF # E+CaeYi8Zqvbu+sCVSO3PSN4QW2u+PEAWpSZihzMCZXQmhxEMKmlFse6R1v1KzSL # n49YN8NOHK8iyhDN2IIQqTXwriLIjySmgYvfJxzkZh2JPi7/VwNNwW6DoDLrtLMv # UFZdBrEVjMgdY7dzDOPWeiYPKpZFpzKDPpY+V0l3I4n+sRDHiuUIFVHFK1oxWzlq # lqikiGuWKG/xxK7qvUUXzGJOgbVUGkeOmKVtwG4nxvgnH8jtIKkLsfHOC5qU4mqd # aYOhNtdtIP6F1f/DuJc2Cf49FMGYFKnAhszvgsGrVSRDGLVIhXiG0PnSnT8Z2RSJ # 542faCSIaDupx4BOJucIIUxj/ZyTFU0ztVZgT9dKuTiO/y7dsV+kQ2vJeM+xu2uP # g2yHcqrqpfuf3RrWOfxkyW0+COV8g7GtvKO6e8+WVqR6WMsSR2LSIe/8PMQxC/cv # PmSlN29gUD+3RJBPoAuLvn5Y9sdnh2HbnpjEyIzLb0fhwC6U7bH2sDBt7GpJqOmW # dsi9CMT+O/WuczcGslbPGdS79ZTKhxzygGoBT7YbgXOz01siPzpYGN+I7mfESacv # 3CWLPV7Q7DREkR28kQx2gj7vxNgtoQQCjkj5790CzwOiMIIGBzCCA++gAwIBAgIK # YRZoNAAAAAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29t # MRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQg # Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEw # NDAzMTMwMzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3 # DQEBAQUAA4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4k # D+7Rp9FMrXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMk # h53y9GccLPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDl # KEYuJ6yGT1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gA # SkdmEScpZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1U # n68eeEExd8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIB # pzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWz # DzALBgNVHQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAU # DqyCYEBWJ5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20x # GTAXBgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBS # b290IENlcnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNV # HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w # cm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQG # CCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p # Y3Jvc29mdFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG # 9w0BAQUFAAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQS # ooxtYrhXAstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBT # Fd1Pq5Lk541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2Oawpylbih # OZxnLcVRDupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfr # Tot/xTUrXqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWG # zFFW6J1wlGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H21 # 46SodDW4TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4i # IdBD6Svpu/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2 # sWo9iaF2YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1 # sMpjtHhUBdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/J # mu5J4PcBZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0wggd6 # MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg # Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDla # Fw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n # dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y # YXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEw # ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS6 # 8rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15 # ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+er # CFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVc # eaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGM # XeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/ # U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwj # p6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwC # gl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1J # MKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3co # KPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfe # nk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAw # HQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoA # UwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQY # MBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6 # Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1 # dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAC # hkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1 # dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4D # MIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz # L2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBs # AF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcN # AQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjD # ctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw # /WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkF # DJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3z # Dq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEn # Gn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1F # p3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0Qax # dR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AAp # xbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//W # syNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqx # P/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIEvjCC # BLoCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO # BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAMTp # ifh6gVDp/wAAAAAAxDAJBgUrDgMCGgUAoIHSMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJ # BDEWBBQw2z8q+XehHDQPWpDsML/8XzZlRDByBgorBgEEAYI3AgEMMWQwYqBIgEYA # TQBpAGMAcgBvAHMAbwBmAHQAIABBAHoAdQByAGUAUwB0AGEAYwBrACAAUABhAHIA # dABuAGUAcgBUAG8AbwBsAGsAaQB0oRaAFGh0dHA6Ly9Db2RlU2lnbkluZm8gMA0G # CSqGSIb3DQEBAQUABIIBABpBpb4XddUsHCbB8Gg/FA6I1OWBSVLWtnR2yRxzShu5 # 89CpJsaFvrvO3pIBkvKSShuN5ai/ghV8aP8kIfhAnoPUDxh74bJtgtcKXs43U32i # NM9UQ3Xf08LjGcOEyimdVl4fEt9H8o8PHC5m4eMipJ188Bq+KgSyEriv3sXJzGDg # mnA0AntvXppgGSmI8KvwHqKKqHR6wEVKE1R4qFeRRQLIvMc2+cOr/qMK7Ypy4LKZ # FldFuh8yz34ypHiY5XV3S1nHFdRyStVs0hFvwZPJRd3L8Bkw7WZBodbn2uL2OYGa # kj3SlUzFXnI5A2g/EB2n9/u2s3R3oSreZOfvGzHMrF+hggIoMIICJAYJKoZIhvcN # AQkGMYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBAhMzAAAA # umo23hqtvTwfAAAAAAC6MAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZI # hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODA0MjcwOTI5MzBaMCMGCSqGSIb3DQEJ # BDEWBBShdJCWghtX2whwvFfGFwJ7UzZVJzANBgkqhkiG9w0BAQUFAASCAQAExpMU # UanA4WEL9ia95/ivBOU4yxSW7hWyOi3FI59msGXoRrrO08aTg1qLymU6w5Vr9rOG # /Lrx3hF4+zyOVs2xR+GzOezAW08L9T6CZKxa47km23J9XOVi05C17cDbw3xd+qkb # eitHx8mekvj5puZ3FYOGDsI5AWSxRScB46GbSQQHQn5olNF3PrgP4MC7rRmvKiP7 # HwA+ML3hYO/+UE4r7cmGCf7W4yLyLi+6LR9NVOI3+mV8PfQ8wUMF27QyfYulfEhi # 07CScdi2w6+nt2bN8PsgCSpx0Cc/MnglY5h+3ZKyjwHjpIG04ibg2pIv5vcYeubw # H0P+4Cho04OFBoXN # SIG # End signature block |