Oauth2Toolkit.psm1
function ConvertFrom-Timestamp { param( [Parameter(Mandatory = $true)] [int]$Timestamp ) $utc = (Get-Date 01.01.1970) + ([System.TimeSpan]::fromseconds($Timestamp)) $datetime = [datetime]::SpecifyKind($utc, 'Utc').ToLocalTime() $datetime } function New-AccessToken { param( [string]$Tenant, [Parameter(ParameterSetName='ClientCredential')] [pscredential]$Client, [Parameter(ParameterSetName='ClientExplicit')] [string]$ClientId, [Parameter(ParameterSetName='ClientExplicit')] [string]$ClientSecret, [string]$RefreshToken ) $authUrl = "https://login.microsoftonline.com/{0}/oauth2/token" -f $Tenant $parameters = @{ grant_type = "refresh_token" client_secret= $ClientSecret refresh_token = $RefreshToken client_id = $ClientId } $response = Invoke-RestMethod -Uri $authUrl -Method Post -Body $parameters $expires = ConvertFrom-Timestamp -Timestamp $response.expires_on $result = [PSCustomObject]@{ Expires = $expires AccessToken = $response.access_token } $result } function Invoke-OnBehalfOfFlow { # https://docs.microsoft.com/en-us/azure/active-directory/develop/v1-oauth2-on-behalf-of-flow param( [Parameter(Mandatory = $true)] [string]$Tenant, [Parameter(Mandatory = $true)] [string]$ClientId, [Parameter(Mandatory = $true)] [string]$clientSecret, [Parameter(Mandatory = $true)] [string]$AccessToken, [Parameter()] [string]$Resource = "https://graph.microsoft.com" ) $payload = @{ grant_type = "urn:ietf:params:oauth:grant-type:jwt-bearer" requested_token_use = "on_behalf_of" scope = "openid" assertion = $AccessToken resource = $Resource client_id = $ClientId client_secret = $clientSecret } $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$Tenant/oauth2/token" -Body $payload $response } function ConvertTo-AuthorizationHeaders { param( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline)] $response ) $headers = @{ 'Content-Type' = 'application/json' 'Authorization' = "Bearer " + $response.access_token 'ExpiresOn' = (ConvertFrom-Timestamp -Timestamp $response.expires_on) } $headers } function New-OnBehalfOfAccessToken { param( [Parameter(Mandatory = $true)] [string]$Tenant, [Parameter(Mandatory = $true)] [string]$ClientId, [Parameter(Mandatory = $true)] [string]$clientSecret, [Parameter(Mandatory = $true)] [string]$AccessToken, [Parameter()] [string]$ResourcePrincial = "https://graph.microsoft.com" ) #Import-AdalLibrary $authority = "https://login.microsoftonline.com/$Tenant" $authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority $clientCredentials = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential" -ArgumentList ($ClientId, $ClientSecret) $userAssertion = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserAssertion" -ArgumentList ($AccessToken) $authResult = $authContext.AcquireTokenAsync($ResourcePrincial, $clientCredentials, $userAssertion) if ($authResult.Result.AccessToken) { # Creating header for Authorization token $authHeader = @{ 'Content-Type' = 'application/json' 'Authorization' = "Bearer " + $authResult.Result.AccessToken 'ExpiresOn' = $authResult.Result.ExpiresOn } $authHeader } elseif ($authResult.Exception) { throw "An error occured getting access token: $($authResult.Exception.InnerException)" } } # Oauth password flow function Add-Win32HelperType { $nativeHelperTypeDefinition = @" using System; using System.Runtime.InteropServices; public static class WinApi { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetForegroundWindow(IntPtr hWnd); public static bool SetForeground(IntPtr windowHandle) { return SetForegroundWindow(windowHandle); } [DllImport("user32.dll", SetLastError=true)] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); public static void KillProcess(IntPtr windowHandle) { uint pid; GetWindowThreadProcessId(windowHandle, out pid); System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById((int)pid); if(p != null) p.Kill(); } } "@ if(-not ([System.Management.Automation.PSTypeName] "WinApi").Type) { Add-Type -TypeDefinition $nativeHelperTypeDefinition } } function Invoke-BrowserLogin { param ( [Parameter(HelpMessage='Authorization URL', Mandatory = $true)] [ValidateNotNull()] [string]$AuthorizationUrl, [Parameter(HelpMessage='Redirect URI', Mandatory = $true)] [ValidateNotNull()] $RedirectUrl, $ExpectedSuccessParameter = "code" ) Add-Type -AssemblyName System.Web Add-Win32HelperType # Create an Internet Explorer Window for the Login Experience $ie = New-Object -ComObject InternetExplorer.Application $ie.Width = 550 $ie.Height = 600 $ie.AddressBar = $false $ie.ToolBar = $false $ie.StatusBar = $false $ie.visible = $true $ie.navigate($authorizationUrl) $handle = $ie.HWND $winForeground = [WinApi]::SetForeground($handle) # Grab the window $wind = (New-Object -ComObject Shell.Application).Windows() | Where-Object { $_.HWND -eq $handle } $sleepCounter = 0 while ($ie.Busy) { Start-Sleep -Milliseconds 50 $sleepCounter++ if ($sleepCounter -eq 100) { throw "Unable to connect to $authorizationUrl, timed out waiting for page." } } try { while($true) { $urls = @() $urls += $wind.LocationUrl | Where-Object { $_ -and $_ -match "(^https?://.+)|(^ftp://)" } if (-not $urls) { # "No urls found, refreshing window" $wind = (New-Object -ComObject Shell.Application).Windows() | Where-Object { $_.HWND -eq $handle } if (-not $wind) { throw "Could not find IE window with handle: $handle" } } foreach ($url in $urls) { $urlPrefix = "{0}?{1}=" -f $RedirectUrl, $ExpectedSuccessParameter if (($url).StartsWith($urlPrefix)) { $code = $url -replace (".*$($ExpectedSuccessParameter)=") -replace ("&.*") # | Out-File $outputAuth return $code } elseif (($url).StartsWith($RedirectUrl + "?error=")) { $error = [System.Web.HttpUtility]::UrlDecode(($a.LocationUrl) -replace (".*error=")) throw $error } } } } finally { [WinApi]::KillProcess($handle) } } function Invoke-CodeGrantFlow { param( [Parameter(HelpMessage='Redirect Uri', Mandatory = $true)] [ValidateNotNull()] [string]$RedirectUrl, [Parameter(Mandatory = $true)] [string]$ClientId, [Parameter(Mandatory = $true)] [string]$ClientSecret, [Parameter(Mandatory = $true)] [string]$Tenant, [Parameter(Mandatory = $true)] [string]$Resource, [bool]$AlwaysPrompt = $false ) $authorizationUrl = ("https://login.microsoftonline.com/{0}/oauth2/authorize?response_type=code&client_id={1}&redirect_uri={2}&resource={3}" -f $Tenant, $ClientId, $RedirectUrl, $Resource) if($AlwaysPrompt) { $authorizationUrl += "&prompt=select_account" } $code = Invoke-BrowserLogin -AuthorizationUrl $authorizationUrl -RedirectUrl $RedirectUrl if(-not $code) { throw "Code Grant Flow failed" } $url = "https://login.microsoftonline.com/{0}/oauth2/token" -f $Tenant $fields = @{ grant_type = "authorization_code" client_id = $clientId code = $code redirect_uri = $RedirectUrl resource = $Resource client_secret = $clientSecret } $response = Invoke-RestMethod -Method Post -Uri $url -Body $fields $response } function Invoke-DeviceCodeFlow { param( [Parameter(Mandatory = $true)] [string]$ClientId, [Parameter(Mandatory = $true)] [string]$ClientSecret, [Parameter(Mandatory = $true)] [string]$Tenant, [Parameter(Mandatory = $true)] [string]$Resource ) $postParams = @{ resource = $Resource client_id = $ClientId } $url = "https://login.microsoftonline.com/{0}/oauth2/devicecode" -f $Tenant $response = Invoke-RestMethod -Method Post -Uri $url -Body $postParams if(-not $response.device_code) { throw "Device Code Flow failed" } $tokenResponse = $null $maxDate = (Get-Date).AddSeconds($response.expires_in) $url = "https://login.microsoftonline.com/{0}/oauth2/token" -f $Tenant $tokenParams = @{ grant_type = "device_code" resource = $Resource client_id = "$ClientId" code = $response.device_code } while (!$tokenResponse -and (Get-Date) -lt $maxDate) { try { $tokenResponse = Invoke-RestMethod -Method Post -Uri $url -Body $tokenParams } catch [System.Net.WebException], [Microsoft.PowerShell.Commands.HttpResponseException] { if ($_.Exception.Response -eq $null) { throw } $errBody = $null if($PSEdition -eq "Core") { $errBody = ConvertFrom-Json ($_.ErrorDetails | Select-Object -ExpandProperty Message) } else { $result = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($result) $reader.BaseStream.Position = 0 $errBody = ConvertFrom-Json $reader.ReadToEnd(); } if(-not $errBody -or $errBody.Error -ne "authorization_pending") { throw } Start-Sleep($response.interval) Write-Host -NoNewline "." } } $tokenResponse } function Invoke-ResourceOwnerPasswordGrantFlow { param( [Parameter(Mandatory = $true)] [string]$ClientId, [Parameter(Mandatory = $true)] [pscredential]$UserCredentials, [Parameter(Mandatory = $true)] [string]$Tenant, [Parameter(Mandatory = $true)] [string]$Resource ) $btsr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($UserCredentials.Password) $plainPassword = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($btsr) [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($btsr) $payload = @{ grant_type = "password" client_id = $ClientId resource = $Resource username = $UserCredentials.UserName password = $plainPassword } $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$Tenant/oauth2/token" -Body $payload $response } function Invoke-AdminConsentForApplication { param( [Parameter(Mandatory = $true)] [string]$ClientId, [Parameter(Mandatory = $true)] [string]$Tenant, [Parameter(Mandatory = $true)] [string]$RedirectUrl ) $consentUrl = "https://login.microsoftonline.com/{0}/adminconsent?client_id={1}&state=12345&redirect_uri={2}" -f $Tenant, $ClientId, $RedirectUrl $response = Invoke-BrowserLogin -AuthorizationUrl $consentUrl -RedirectUrl $RedirectUrl -ExpectedSuccessParameter "admin_consent" if($response -ne "True") { throw "Admin consent failed" } } # SIG # Begin signature block # MIIgQgYJKoZIhvcNAQcCoIIgMzCCIC8CAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUGRlUBGjo9KQnOegfgfYVAfzs # I9ygghtxMIIDtzCCAp+gAwIBAgIQDOfg5RfYRv6P5WD8G/AwOTANBgkqhkiG9w0B # AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMzExMTEwMDAwMDAwWjBlMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg # Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCtDhXO5EOAXLGH87dg # +XESpa7cJpSIqvTO9SA5KFhgDPiA2qkVlTJhPLWxKISKityfCgyDF3qPkKyK53lT # XDGEKvYPmDI2dsze3Tyoou9q+yHyUmHfnyDXH+Kx2f4YZNISW1/5WBg1vEfNoTb5 # a3/UsDg+wRvDjDPZ2C8Y/igPs6eD1sNuRMBhNZYW/lmci3Zt1/GiSw0r/wty2p5g # 0I6QNcZ4VYcgoc/lbQrISXwxmDNsIumH0DJaoroTghHtORedmTpyoeb6pNnVFzF1 # roV9Iq4/AUaG9ih5yLHa5FcXxH4cDrC0kqZWs72yl+2qp/C3xag/lRbQ/6GW6whf # GHdPAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G # A1UdDgQWBBRF66Kv9JLLgjEtUYunpyGd823IDzAfBgNVHSMEGDAWgBRF66Kv9JLL # gjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEAog683+Lt8ONyc3pklL/3 # cmbYMuRCdWKuh+vy1dneVrOfzM4UKLkNl2BcEkxY5NM9g0lFWJc1aRqoR+pWxnmr # EthngYTffwk8lOa4JiwgvT2zKIn3X/8i4peEH+ll74fg38FnSbNd67IJKusm7Xi+ # fT8r87cmNW1fiQG2SVufAQWbqz0lwcy2f8Lxb4bG+mRo64EtlOtCt/qMHt1i8b5Q # Z7dsvfPxH2sMNgcWfzd8qVttevESRmCD1ycEvkvOl77DZypoEd+A5wwzZr8TDRRu # 838fYxAe+o0bJW1sj6W3YQGx0qMmoRBxna3iw/nDmVG3KwcIzi7mULKn+gpFL6Lw # 8jCCBTAwggQYoAMCAQICEAQJGBtf1btmdVNDtW+VUAgwDQYJKoZIhvcNAQELBQAw # ZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQ # d3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQgQXNzdXJlZCBJRCBS # b290IENBMB4XDTEzMTAyMjEyMDAwMFoXDTI4MTAyMjEyMDAwMFowcjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUg # U2lnbmluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPjTsxx/ # DhGvZ3cH0wsxSRnP0PtFmbE620T1f+Wondsy13Hqdp0FLreP+pJDwKX5idQ3Gde2 # qvCchqXYJawOeSg6funRZ9PG+yknx9N7I5TkkSOWkHeC+aGEI2YSVDNQdLEoJrsk # acLCUvIUZ4qJRdQtoaPpiCwgla4cSocI3wz14k1gGL6qxLKucDFmM3E+rHCiq85/ # 6XzLkqHlOzEcz+ryCuRXu0q16XTmK/5sy350OTYNkO/ktU6kqepqCquE86xnTrXE # 94zRICUj6whkPlKWwfIPEvTFjg/BougsUfdzvL2FsWKDc0GCB+Q4i2pzINAPZHM8 # np+mM6n9Gd8lk9ECAwEAAaOCAc0wggHJMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYD # VR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHkGCCsGAQUFBwEBBG0w # azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF # BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk # SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsNC5kaWdp # Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw # Oi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js # ME8GA1UdIARIMEYwOAYKYIZIAYb9bAACBDAqMCgGCCsGAQUFBwIBFhxodHRwczov # L3d3dy5kaWdpY2VydC5jb20vQ1BTMAoGCGCGSAGG/WwDMB0GA1UdDgQWBBRaxLl7 # KgqjpepxA8Bg+S32ZXUOWDAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823I # DzANBgkqhkiG9w0BAQsFAAOCAQEAPuwNWiSz8yLRFcgsfCUpdqgdXRwtOhrE7zBh # 134LYP3DPQ/Er4v97yrfIFU3sOH20ZJ1D1G0bqWOWuJeJIFOEKTuP3GOYw4TS63X # X0R58zYUBor3nEZOXP+QsRsHDpEV+7qvtVHCjSSuJMbHJyqhKSgaOnEoAjwukaPA # JRHinBRHoXpoaK+bp1wgXNlxsQyPu6j4xRJon89Ay0BEpRPw5mQMJQhCMrI2iiQC # /i9yfhzXSUWW6Fkd6fp0ZGuy62ZD2rOwjNXpDd32ASDOmTFjPQgaGLOBm0/GkxAG # /AeB+ova+YJJ92JuoVP6EpQYhS6SkepobEQysmah5xikmmRR7zCCBT8wggQnoAMC # AQICEARqN2Coe6LGwkVyLzQpADwwDQYJKoZIhvcNAQELBQAwcjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTExMC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2ln # bmluZyBDQTAeFw0xOTA5MTYwMDAwMDBaFw0yMDEwMDcxMjAwMDBaMHwxCzAJBgNV # BAYTAkNaMQ8wDQYDVQQIEwZQcmFndWUxEjAQBgNVBAcTCVByYWd1ZSAxMDEjMCEG # A1UEChMaVUFNIEN6ZWNoIFJlcHVibGljLCBzLnIuby4xIzAhBgNVBAMTGlVBTSBD # emVjaCBSZXB1YmxpYywgcy5yLm8uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB # CgKCAQEAuq5cfBDFBQvMe+Xj1O1mxsCcE0T+JLQtqOZE/AsHCVzMpxCCDHtCSwct # HHsPVtrKMeFyaLDek3VQWrCMgiErkZtr6j2RIwo33+CwM9aLcjvKaVx1Qjkdlfs8 # zXi+JUKCHNbN2irY5b8wK8DUmMG3kp8iqJWITTATsKdabJUC3qP7KJhuRs6rYoPK # cjDhn3PBNE9lcDs0vgdp0pRDqYcepk3NTEu2pHW8xG54+/rYNbyy0dbqakTvo+lU # 6B4Jrob1RFgefz6g6X6HoVOsujalPEacPdbhQmzWuAijzD39r6nVxrOSWaWUDv20 # obphl85MHz7Ezuzi+DqJaZ1ymfymXwIDAQABo4IBxTCCAcEwHwYDVR0jBBgwFoAU # WsS5eyoKo6XqcQPAYPkt9mV1DlgwHQYDVR0OBBYEFL+kJzPjqsjuCW4WG06ed+gr # Q0A7MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzB3BgNVHR8E # cDBuMDWgM6Axhi9odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk # LWNzLWcxLmNybDA1oDOgMYYvaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL3NoYTIt # YXNzdXJlZC1jcy1nMS5jcmwwTAYDVR0gBEUwQzA3BglghkgBhv1sAwEwKjAoBggr # BgEFBQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAIBgZngQwBBAEw # gYQGCCsGAQUFBwEBBHgwdjAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl # cnQuY29tME4GCCsGAQUFBzAChkJodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v # RGlnaUNlcnRTSEEyQXNzdXJlZElEQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/ # BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAqnAvqpnss3KmMh8DhGngUFOSCJI5kDFZ # 6kR+G5sQqbwhQKiLLKYT/pXopDm1yob9o48Ef9aAb0ILYayq2bSN+sBdf8iX9j44 # EyVOE5mjwQj570ETuGPXrrfE7s7zBYDvLGsuk4AqnWF2HyR9eIsc/8MNl/nXQHsg # fYh8xsX8HzOpNppP0iY72OUDsKzwwIpH7UtwPNxuDEEanU0KMPUcMStwWXl8V9SZ # Q/Y35n8HpRu8dzeu8N/jh5o/s2+CVMTuF6xat2p1pxtTWeeDJxYlPReq5IugY8e7 # iJCI7YAvDMsOt7f+OOR22VIXuEO/3tL0zWe9ChLXN1VmNhgcur4y7jCCBmowggVS # oAMCAQICEAMBmgI6/1ixa9bV6uYX8GYwDQYJKoZIhvcNAQEFBQAwYjELMAkGA1UE # BhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2lj # ZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xMB4XDTE0 # MTAyMjAwMDAwMFoXDTI0MTAyMjAwMDAwMFowRzELMAkGA1UEBhMCVVMxETAPBgNV # BAoTCERpZ2lDZXJ0MSUwIwYDVQQDExxEaWdpQ2VydCBUaW1lc3RhbXAgUmVzcG9u # ZGVyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo2Rd/Hyz4II14OD2 # xirmSXU7zG7gU6mfH2RZ5nxrf2uMnVX4kuOe1VpjWwJJUNmDzm9m7t3LhelfpfnU # h3SIRDsZyeX1kZ/GFDmsJOqoSyyRicxeKPRktlC39RKzc5YKZ6O+YZ+u8/0SeHUO # plsU/UUjjoZEVX0YhgWMVYd5SEb3yg6Np95OX+Koti1ZAmGIYXIYaLm4fO7m5zQv # MXeBMB+7NgGN7yfj95rwTDFkjePr+hmHqH7P7IwMNlt6wXq4eMfJBi5GEMiN6ARg # 27xzdPpO2P6qQPGyznBGg+naQKFZOtkVCVeZVjCT88lhzNAIzGvsYkKRrALA76Tw # iRGPdwIDAQABo4IDNTCCAzEwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAw # FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwggG/BgNVHSAEggG2MIIBsjCCAaEGCWCG # SAGG/WwHATCCAZIwKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNv # bS9DUFMwggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYA # IAB0AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkA # dAB1AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAA # RABpAGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAA # UgBlAGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAA # dwBoAGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4A # ZAAgAGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkA # bgAgAGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMB8GA1Ud # IwQYMBaAFBUAEisTmLKZB+0e36K+Vw0rZwLNMB0GA1UdDgQWBBRhWk0ktkkynUoq # eRqDS/QeicHKfTB9BgNVHR8EdjB0MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2Vy # dC5jb20vRGlnaUNlcnRBc3N1cmVkSURDQS0xLmNybDA4oDagNIYyaHR0cDovL2Ny # bDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwdwYIKwYB # BQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w # QQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy # dEFzc3VyZWRJRENBLTEuY3J0MA0GCSqGSIb3DQEBBQUAA4IBAQCdJX4bM02yJoFc # m4bOIyAPgIfliP//sdRqLDHtOhcZcRfNqRu8WhY5AJ3jbITkWkD73gYBjDf6m7Gd # JH7+IKRXrVu3mrBgJuppVyFdNC8fcbCDlBkFazWQEKB7l8f2P+fiEUGmvWLZ8Cc9 # OB0obzpSCfDscGLTYkuw4HOmksDTjjHYL+NtFxMG7uQDthSr849Dp3GdId0UyhVd # kkHa+Q+B0Zl0DSbEDn8btfWg8cZ3BigV6diT5VUW8LsKqxzbXEgnZsijiwoc5ZXa # rsQuWaBh3drzbaJh6YoLbewSGL33VVRAA5Ira8JRwgpIr7DUbuD0FAo6G+OPPcqv # ao173NhEMIIGzTCCBbWgAwIBAgIQBv35A5YDreoACus/J7u6GzANBgkqhkiG9w0B # AQUFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVk # IElEIFJvb3QgQ0EwHhcNMDYxMTEwMDAwMDAwWhcNMjExMTEwMDAwMDAwWjBiMQsw # CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu # ZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBBc3N1cmVkIElEIENBLTEw # ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDogi2Z+crCQpWlgHNAcNKe # VlRcqcTSQQaPyTP8TUWRXIGf7Syc+BZZ3561JBXCmLm0d0ncicQK2q/LXmvtrbBx # MevPOkAMRk2T7It6NggDqww0/hhJgv7HxzFIgHweog+SDlDJxofrNj/YMMP/pvf7 # os1vcyP+rFYFkPAyIRaJxnCI+QWXfaPHQ90C6Ds97bFBo+0/vtuVSMTuHrPyvAwr # mdDGXRJCgeGDboJzPyZLFJCuWWYKxI2+0s4Grq2Eb0iEm09AufFM8q+Y+/bOQF1c # 9qjxL6/siSLyaxhlscFzrdfx2M8eCnRcQrhofrfVdwonVnwPYqQ/MhRglf0HBKIJ # AgMBAAGjggN6MIIDdjAOBgNVHQ8BAf8EBAMCAYYwOwYDVR0lBDQwMgYIKwYBBQUH # AwEGCCsGAQUFBwMCBggrBgEFBQcDAwYIKwYBBQUHAwQGCCsGAQUFBwMIMIIB0gYD # VR0gBIIByTCCAcUwggG0BgpghkgBhv1sAAEEMIIBpDA6BggrBgEFBQcCARYuaHR0 # cDovL3d3dy5kaWdpY2VydC5jb20vc3NsLWNwcy1yZXBvc2l0b3J5Lmh0bTCCAWQG # CCsGAQUFBwICMIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMA # IABDAGUAcgB0AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMA # IABhAGMAYwBlAHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMA # ZQByAHQAIABDAFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkA # bgBnACAAUABhAHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgA # IABsAGkAbQBpAHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUA # IABpAG4AYwBvAHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAA # cgBlAGYAZQByAGUAbgBjAGUALjALBglghkgBhv1sAxUwEgYDVR0TAQH/BAgwBgEB # /wIBADB5BggrBgEFBQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp # Z2ljZXJ0LmNvbTBDBggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu # Y29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNydDCBgQYDVR0fBHoweDA6oDig # NoY0aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9v # dENBLmNybDA6oDigNoY0aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0 # QXNzdXJlZElEUm9vdENBLmNybDAdBgNVHQ4EFgQUFQASKxOYspkH7R7for5XDStn # As0wHwYDVR0jBBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDQYJKoZIhvcNAQEF # BQADggEBAEZQPsm3KCSnOB22WymvUs9S6TFHq1Zce9UNC0Gz7+x1H3Q48rJcYaKc # lcNQ5IK5I9G6OoZyrTh4rHVdFxc0ckeFlFbR67s2hHfMJKXzBBlVqefj56tizfuL # LZDCwNK1lL1eT7EF0g49GqkUW6aGMWKoqDPkmzmnxPXOHXh2lCVz5Cqrz5x2S+1f # wksW5EtwTACJHvzFebxMElf+X+EevAJdqP77BzhPDcZdkbkPZ0XN1oPt55INjbFp # jE/7WeAjD9KqrgB87pxCDs+R1ye3Fu4Pw718CqDuLAhVhSK46xgaTfwqIa1JMYNH # lXdx3LEbS0scEJx3FMGdTy9alQgpECYxggQ7MIIENwIBATCBhjByMQswCQYDVQQG # EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl # cnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBT # aWduaW5nIENBAhAEajdgqHuixsJFci80KQA8MAkGBSsOAwIaBQCgeDAYBgorBgEE # AYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwG # CisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJBDEWBBQ2deey # g6Ryc4meADhr9z5qMLWBZTANBgkqhkiG9w0BAQEFAASCAQAq9UXEHGqC2VYB/4LM # JEnWalVtI4+JWWrW83HGxd+DUImbohbZzfDmeA21yARGfYJD06nMTu5LH9sX3xkO # 1BHt5RGEJzP2AFQ6zCXuGjxMHFkeZzyIW1jYqFStCtDDv/30TTKlLikxIzFnjLV1 # 4w96I/H+Jf5izxeTmMDmLL/8qcpf3gcbajltLoJ0TPjuGq/WsAERhlabHreU1eFQ # /XCHZwgxO8G0O14v1YW6ZOpRkavqf7QlaicSe1Iutj+mHh3QrYN32CYurPlq0JLa # bM0AoZJ8QSmNaadfsNJeWTQJ3dTO77abm6UuZCXXBwOzO/ysEbmMNp4DWxOHBJ+K # DzX7oYICDzCCAgsGCSqGSIb3DQEJBjGCAfwwggH4AgEBMHYwYjELMAkGA1UEBhMC # VVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0 # LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJRCBDQS0xAhADAZoCOv9Y # sWvW1ermF/BmMAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcB # MBwGCSqGSIb3DQEJBTEPFw0xOTEwMDQyMDUyMDVaMCMGCSqGSIb3DQEJBDEWBBSz # I72IewhXE34oML7NtjqPRYHzLjANBgkqhkiG9w0BAQEFAASCAQBVj8fVucitg6MG # XM3gwJg6CP5EkiVRyr8zoftUjSm2QPwYeDhMaFIzH8lyMu1JssKHUSaCUXseFEaW # NEgGKO8KIZLJrYweji4ofUVKNqVhCtm1HIy5fsDKZlDBBVxf/rXpL08qN1Oa18wC # CiQPqM5NrPDL6bh245x/ozzh7UZLRFPuFbqiuA/HTya0OIyVk9YkHgDvH2O2DS1v # UExqKoCM3RBeldm1wMTNxDoo7VcpjbfbloS5wwJyyinqMwTCWsXCrFjlG2xp+I2w # +Nrz5w4kMuZAMoSe8nXVE9ZP4f3hVBYHhy0cvMgEXZZ8th2qLJKAvtJiqDcD19c/ # RcFfpB8C # SIG # End signature block |