private/GenerateTorusOAuthToken.ps1
<#
.Synopsis Script to generate OAuth token using torus identity. .Description This script generates torus oauth tokens. It requests two lockbox requests (CustomerDataAccess, AppElevatedAccess). This scripts waits until both lockbox requests are approved by owners. .Parameter Tenant Name of the tenant for which you want to generate token. E.g microsoft.onmicrosoft.com .parameter Upn UserPrincipalName you want to use in the token. .parameter AppId Client app id registered in AAD. .parameter ElevationRequestMessage This message is added in each lockbox elevation request. .parameter OAuthPermissions Permissions to be stamped in OAuth token. .parameter ExecutingServer Which server to contact to generate OAuth token. .parameter AudienceUri Audience uri in OAuth token. .parameter GcsCmdletName Gcs cmdlet name for which the token needs to be generated. .Example GenerateTorusOAuthToken.ps1 -Tenant ba12dbb2-be5f-45a6-802f-3c269ed9d97e -Upn nisgup@microsoft.com -AppId "56c1da01-2129-48f7-9355-af6d59d42766" -ElevationRequestMessage "Testing" -OAuthPermission "ExternalItem.ReadWrite" -ExecutingServer CY1PR00MB0170.namprd00.prod.outlook.com -AudienceUri "https://outlook.office.com" -GcsCmdletName Remove-DataSet #> function IsOAuthTokenValid { if (!$global:GcsApiTorusAuth_OAuthToken) { return $false } $now = (New-TimeSpan -Start (Get-Date "01/01/1970") -End ([System.DateTime]::UtcNow)).TotalSeconds if ($global:GcsApiTorusAuth_OAuthTokenExpiry) { return $global:GcsApiTorusAuth_OAuthTokenExpiry -gt $now } Write-Verbose "Checking OAuth token validity $($global:GcsApiTorusAuth_OAuthToken)" $exp = Get-TokenExpiry $global:GcsApiTorusAuth_OAuthToken # Cache expiry. $global:GcsApiTorusAuth_OAuthTokenExpiry = $exp return $exp -gt $now } function Get-TokenExpiry { param([Parameter(Mandatory=$true)][string]$token) #Payload $tokenPayload = $token.Split(".")[1] while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" } Write-Host "Base64 encoded (padded) payoad:" Write-Host $tokenPayload #Convert to Byte array $tokenByteArray = [System.Convert]::FromBase64String($tokenPayload) #Convert to string array $tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray) Write-Host "Decoded array in JSON format:" Write-Host $tokenArray #Convert from JSON to PSObject $tokobj = $tokenArray | ConvertFrom-Json Write-Host "Decoded Payload:" return $tokobj.exp } # # Initiate Torus OAuth token generation workflow. # function InitiateTokenGenerationWorkflow { # Step#1 Request first elevation. Write-Host "Step#1 Requesting ElevatedAccess to read customer data. for Upn $($Upn)" $global:GcsApiTorusAuth_elevationLockboxRequestId = RequestElevatedAccess -Role AccessToCustomerData ` -Reason "Request to get OAuth token with torus creds. Scenario=$ElevationRequestMessage" ` -RequestNumber "0" ` -Tenant $Tenant # Step#2 Wait until this elevation request is approved. Write-Host "Step#2 Waiting for elevated access request lockbox approval. $($global:GcsApiTorusAuth_elevationLockboxRequestId)" WaitUntilLockBoxRequestApproved $global:GcsApiTorusAuth_elevationLockboxRequestId "DoneProcessing" "Approved" # Step#3 Elevated access reuquest is approved. Write-Host "Step#3 elevated access request is approved." # Step#4 Request app token access. Write-Host "Step#4 Requesting app elevated access request." $global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId = RequestAppElevatedAccess ` -ATCDRequestId $global:GcsApiTorusAuth_elevationLockboxRequestId ` -Tenant $Tenant ` -AppId $AppId ` -AudienceUri $AudienceUri ` -Permissions $OAuthPermissions ` -ResourceScope "UPN:$Upn" ` -Reason "App elevated access for torus creds. Scenario=$ElevationRequestMessage" # Step#5 Wait until this lock box request is approved. Write-Host "Step#5 Waiting for app elevated access request to be approved. $($global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId)" WaitUntilLockBoxRequestApproved $global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId "DoneProcessing" "Approved" # Adding 90 seconds delay for provisioning of lockbox requests Start-Sleep -Seconds 90 # Step#6 Write-Host "Step#6 Generating OAuth token" return GenerateTorusOAuthToken } # ApprovalStatus Approved # Status DoneProcessing # # Wait until given lockbox request is moved to given status. # function WaitUntilLockBoxRequestApproved { Param( [parameter(Mandatory=$true)] [string] $LockboxRequestId, [parameter(Mandatory=$true)] [string] $ExpectedStatus, [parameter(Mandatory=$true)] [string] $ExpectedApprovalStatus ) Write-Host "WaitUntilLockBoxRequestApproved: LockboxRequestId= $($LockboxRequestId) ExpectedStatus= $($ExpectedStatus)" While ($true) { Write-Host "Reading lockbox request status $($LockboxRequestId)" if (IsLockboxRequestInValidState $LockboxRequestId $ExpectedStatus $ExpectedApprovalStatus) { return } Write-Host "Sleeping for 60 seconds" Start-Sleep -Seconds 60 } } # # Returns true if both lock box requests are valid for generating OAuth token. # function AreLockboxRequestsValid { # If lockbox request variables are not defined, return false. if (!$global:GcsApiTorusAuth_elevationLockboxRequestId -or !$global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId) { return $false } Write-Host "Checking cached lockbox requests are valid or not" return (IsLockboxRequestInValidState $global:GcsApiTorusAuth_elevationLockboxRequestId "DoneProcessing" "Approved") -and ` (IsLockboxRequestInValidState $global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId "DoneProcessing" "Approved") } ########################################## Mock following functions for testing. ############# # # Cuts or remove sections from the input object line based on input delimiter and field arguments (Similar to Unix) # function cut { param( [Parameter(ValueFromPipeline=$True)] [string]$inputobject, [string]$delimiter='\s+', [string[]]$field ) process { if ($field -eq $null) { $inputobject -split $delimiter } else { ($inputobject -split $delimiter)[$field] } } } # # Get ResourceForest of given Tenant. # function GetResourceForest { Write-Host "Getting Resource Forest" # Below command first greps(Select-String) the ResourceForest from Get-GlobalLocatorServiceTenant output like "ResourceForest : namprd00.prod.outlook.com" # and then further cuts out forest value like - "namprd00.prod.outlook.com" $t = Get-GlobalLocatorServiceTenant $Tenant | out-string -stream | Select-String ResourceForest | cut -f 1 -d : | cut -f 1 -d " " return $t } # # Request elevation access. Returns corresponding lockbox request id. # function RequestElevatedAccess { Param( [parameter(Mandatory=$true)] [String]$Role, [parameter(Mandatory=$true)] [String]$Reason, [parameter(Mandatory=$true)] [String]$RequestNumber, [parameter(Mandatory=$true)] [String]$Tenant ) $Forest = GetResourceForest Write-Host "Forest = $($Forest)" $request = Request-ElevatedAccess_V2.ps1 -Role AccessToCustomerData ` -Reason $Reason ` -RequestNumber $RequestNumber ` -Forest $Forest ` -Tenant $Tenant return $request.LockboxRequestId } # # Request app elevated access. Returns corresponding lockbox request id. # function RequestAppElevatedAccess { Param( [parameter(Mandatory=$true)] [String]$ATCDRequestId, [parameter(Mandatory=$true)] [String]$Tenant, [parameter(Mandatory=$true)] [String]$AppId, [parameter(Mandatory=$true)] [String]$AudienceUri, [parameter(Mandatory=$true)] [String]$Permissions, [parameter(Mandatory=$true)] [String]$ResourceScope, [parameter(Mandatory=$true)] [String]$Reason ) $request = Request-AppElevatedAccess ` -ATCDRequestId $ATCDRequestId ` -Tenant $Tenant ` -AppId $AppId ` -AudienceUri $AudienceUri ` -Permissions $Permissions ` -ResourceScope $ResourceScope ` -Reason $Reason return $request.LockboxRequestId } # # Returns true if given lockbox request is in expected status. # function IsLockboxRequestInValidState { Param( [parameter(Mandatory=$true)] [String] $LockboxRequestId, [parameter(Mandatory=$true)] [String] $ExpectedStatus, [parameter(Mandatory=$true)] [String] $ExpectedApprovalStatus ) try { $requestStatus = Get-LockboxRequest $LockboxRequestId } catch { Write-Host "Error while getting lockbox request status." Write-Host $_.Exception # Since failed, consider this as request not approved. return $false } Write-Host "Checking LockBoxRequestStatus RequestId=$($LockboxRequestId) | Status=$($requestStatus.Status) ExpectedStatus=$($ExpectedStatus) | ApprovalStatus=$($requestStatus.ApprovalStatus) ExpectedApprovalStatus=$($ExpectedApprovalStatus) | ApprovalTime=$($requestStatus.ApprovalTime)" $elevationIsActive = CheckExpirationTime($requestStatus.ApprovalTime) return $requestStatus.Status -eq $ExpectedStatus -and $requestStatus.ApprovalStatus -eq $ExpectedApprovalStatus -and $elevationIsActive -eq $true } # # Check that if request is approved at given approval time, does the elevation active now? # This method returns true if elevation is action now. false otherwise. # function CheckExpirationTime { Param( [Parameter(Mandatory=$true)] [DateTime] $approvalTime ) # If approval time is not specified means, request is not approved. if (!$approvalTime) { return $false } $expirationTime = $approvalTime.ToUniversalTime().AddHours(4) $now = (Get-Date).ToUniversalTime() return $expirationTime -gt $now } # # Generate OAuth token by using two elevation lockbox requests. # function GenerateTorusOAuthToken { # Pass -ExecutingServer only if it is specified at cmdline. if ($ExecutingServer) { $cmdletResult = Get-SubstrateTokenOnBehalfOfApp ` -ATCDRequestId $global:GcsApiTorusAuth_elevationLockboxRequestId ` -appREARequestId $global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId ` -ExecutingServer $ExecutingServer ` -Verbose } else { $cmdletResult = Get-SubstrateTokenOnBehalfOfApp ` -ATCDRequestId $global:GcsApiTorusAuth_elevationLockboxRequestId ` -appREARequestId $global:GcsApiTorusAuth_appElevatedAccessLockBoxRequestId ` -Verbose } # cmdlet result could be array or string. if ($cmdletResult.GetType().FullName -eq "System.Object[]") { $global:GcsApiTorusAuth_OAuthToken = $cmdletResult[1].ToString() } else { # If not array, get to string version as token. $global:GcsApiTorusAuth_OAuthToken = $cmdletResult.ToString() } # Update cached token expiry value. $global:GcsApiTorusAuth_OAuthTokenExpiry = Get-TokenExpiry $global:GcsApiTorusAuth_OAuthToken return $global:GcsApiTorusAuth_OAuthToken } # # This function checks whether tenant-id and Gcs cmdlet name matches their corresponding cached session values if present. # If both tenant-id or cmdlet name matches their cached values, then valid cached token or cached lockbox approvals can be used. # Else, new token has to be generated through InitiateTokenGenerationWorkflow # function CanUseCachedTokenOrLockBoxApprovals { Write-Host "comandlet name is $($GcsCmdletName)" if ($global:GcsApiTorusAuth_tenantId -eq $Tenant -and $global:GcsApiTorusAuth_cmdletName -eq $GcsCmdletName) { Write-Host "Success : Tenant and cmdlet name matched." return $true } Write-Host "Failed : Tenant and cmdlet name didn't match." $global:GcsApiTorusAuth_tenantId = $Tenant $global:GcsApiTorusAuth_cmdletName = $GcsCmdletName return $false } # # Main logic to get OAuth token. # -> If cached token or lockbox approvals can be used then check for token/LC validity # --> If token is valid, return. # --> If LC requests are valid, generate token. # -> Else Use Initiate workflow for new token generation. # function Get-TorusOAuthToken { param( # Tenant Id for which token will be generated [parameter(Mandatory=$true)] [String]$Tenant, # GcsCmdletName which will be sent as message for approval [parameter(Mandatory=$true)] [String]$GcsCmdletName, [parameter(Mandatory=$false)] [string]$ExecutingServer ) Write-Host "Inside torus" $global:Tenant = $Tenant $global:GcsCmdletName = $GcsCmdletName $global:ExecutingServer = $ExecutingServer $global:Upn = "tenant:testcontoso1234.onmicrosoft.com" $global:AppId = "56c1da01-2129-48f7-9355-af6d59d42766" $global:ElevationRequestMessage = "Executing On call operation : $($GcsCmdletName). Please approve." $global:OAuthPermissions = "ExternalItem.ReadWrite" $global:AudienceUri = "https://outlook.office.com" if (CanUseCachedTokenOrLockBoxApprovals) { if(IsOAuthTokenValid) { Write-Host "Cached token is valid. Using the same" return [pscustomobject]@{AccessToken=$global:GcsApiTorusAuth_OAuthToken; TokenExpiry=$global:GcsApiTorusAuth_OAuthTokenExpiry} } if (AreLockboxRequestsValid) { Write-Host "Cached token is expired, but lock box requests are still valid. Generating new token" return GenerateTorusOAuthToken } } Write-Host "Tenant id is $($Tenant)" Write-Host "Initiating lockbox request workflow to generate token." $oauthToken = InitiateTokenGenerationWorkflow return [pscustomobject]@{AccessToken=$oauthToken; TokenExpiry=$global:GcsApiTorusAuth_OAuthTokenExpiry} } # SIG # Begin signature block # MIIoZQYJKoZIhvcNAQcCoIIoVjCCKFICAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBjBc/M8ErIwx8u # kBXX1z6mt4PdW3qy6NoCi8ozbQnCZ6CCDXYwggX0MIID3KADAgECAhMzAAADrzBA # DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA # hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG # 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN # xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL # go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB # tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw # RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW # MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci # tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG # CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu # Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0 # MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd # mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ # 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY # 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp # XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn # TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT # e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG # OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O # PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk # ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx # HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt # CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq # hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x # EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv # bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 # IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG # EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG # A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg # Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC # CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03 # a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr # rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg # OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy # 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9 # sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh # dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k # A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB # w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn # Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90 # lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w # ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o # ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD # VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa # BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny # bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG # AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t # L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV # HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3 # dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG # AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl # AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb # C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l # hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6 # I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0 # wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560 # STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam # ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa # J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah # XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA # 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt # Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr # /Xmfwb1tbWrJUnMTDXpQzTGCGkUwghpBAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw # EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN # aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp # Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB # BQCggeYwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO # MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMJxFQ+gmEEgxp9W/uWKGFH2 # KUP6zWSo0IlUaijNxHeDMHoGCisGAQQBgjcCAQwxbDBqoEyASgBNAGkAYwByAG8A # cwBvAGYAdAAuAEcAcgBhAHAAaAAuAEMAbwBuAG4AZQBjAHQAbwByAHMALgBDAG0A # ZABsAGUAdAAuAGQAbABsoRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTANBgkq # hkiG9w0BAQEFAASCAQAGudnYnM80daBAec085QqErfKcRm0y/YoDQtxFDNEegGyV # V7wWnjN+mZHyHQJASYimQ4pFWJyjcybFJxnzsVan0smcKVNaLKkzqwmwV+z1A7ZE # LddFkiS/sT9aKbYzTpw/q99gNGC9qGT48a4Ek7yMbzWPNzyNldYciD/zksUaENUS # XigKIZyYXriX1nIbPtjXGNyuyxN+PxvpHjxgBNd2sW6Mm0hcovPJoWOoBl3ZLzRF # TPVZvNS8sab6UIqDzwqCQ9ar2Bo5BcvJNSJK8b2Y92mJUgYimFaAs3S2XgswM68G # Va/cCho3x6Qgagh4iI58UlIIUkZX24Tj7kI76D4LoYIXlzCCF5MGCisGAQQBgjcD # AwExgheDMIIXfwYJKoZIhvcNAQcCoIIXcDCCF2wCAQMxDzANBglghkgBZQMEAgEF # ADCCAVIGCyqGSIb3DQEJEAEEoIIBQQSCAT0wggE5AgEBBgorBgEEAYRZCgMBMDEw # DQYJYIZIAWUDBAIBBQAEIBwG93B/JrMvpk+MJEnvh1jEt/TwzBIAy8+nJU3tTNRc # AgZl/GoVsWAYEzIwMjQwNDAyMDc0NDIwLjIzMlowBIACAfSggdGkgc4wgcsxCzAJ # BgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25k # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jv # c29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVT # TjozNzAzLTA1RTAtRDk0NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg # U2VydmljZaCCEe0wggcgMIIFCKADAgECAhMzAAAB6pokctVZP2FjAAEAAAHqMA0G # CSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMB4XDTIz # MTIwNjE4NDUzMFoXDTI1MDMwNTE4NDUzMFowgcsxCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVyaWNhIE9w # ZXJhdGlvbnMxJzAlBgNVBAsTHm5TaGllbGQgVFNTIEVTTjozNzAzLTA1RTAtRDk0 # NzElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2VydmljZTCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBALULX/FIPyAH1fsu52ijatZvaSypoXrl # C0mRtCmaxzobhuDkw6/pY/+4nhc4m8pf9zW3R6PihYGp0YPpVuNdfhPQp/KVO6Wv # Mq2DGfFmHurW4PQPL/DkbQMkM9vqjFCvPq8xXZnfL1nGN9moGcN+oaif/hUMedmF # 1qzbay9ILkYfLCxDYn3Qwzsvh5xjxOcsjzmRddNURJvT23Eva0cxisH4ocLLTx2z # fpqfshw4Z9GaEdsWg9rmib1galUpLzF5PsQDBbtZtcv+Wjmn0pFEiMCWwEEcPVN0 # YG5ysYLdNBdJOn2zsOOS+80W5RrQEqzPpSIIvEkZBJmF3aI4lMR8nV/FiTadjpII # qxX5Wa1XlqI/Nj+xagVjnjb7POsA+vh6Wu+v24HpyL8pyL/8Q4RFkRRME9cwT+Jr # 63yOtPbLe6DXkxIJW6E6w2ua5kXBpEKtEQPTLPhX3CUxMYcglbnmI0zcc9UknX28 # 5K+sI/2WwRwTBZkhDUULI86eQzV+zvzzR1qEBrlSY+oyTlYQrHMM9WnTzVflFDoc # ZVTPpl2BDSNxPn0Qb4IoM9EPqbHyi/MilL+v/AQc8q3mQ6FiuPJAddz0ocpNZ9ek # BWPVLKq3lfiev4yl65u/438+NAQ+vSJgkONLMmuoguEGzmnK1vq/JHwdRUyn6YAD # iteM7Dja+Qd9AgMBAAGjggFJMIIBRTAdBgNVHQ4EFgQUK4FFJaJR5ukXQFTUxMhy # iwVuWV4wHwYDVR0jBBgwFoAUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXwYDVR0fBFgw # VjBUoFKgUIZOaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWlj # cm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3JsMGwGCCsGAQUF # BwEBBGAwXjBcBggrBgEFBQcwAoZQaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3Br # aW9wcy9jZXJ0cy9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgx # KS5jcnQwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAOBgNV # HQ8BAf8EBAMCB4AwDQYJKoZIhvcNAQELBQADggIBACiDrVZeP37+fFVtfcbfsqC/ # Kg0Ce67bDcehZmPcfRgJ5Ddv0pJlOFVOFbiIVwesqeEUwFtclfi5AjneQ5ZJpYJp # XfELOelG3dzj+BKfd287/UY/cwmSkl+CjnoKBL3Ms6I/fWR+alR0+p6RlviK8xHo # ug9vkc2WrRZsGnMVu2xOM2tPJ+qpyoDBzqv30N/ZRBOoNrS/PCkDwLGICDYqVs/I # zAE49yv2ElPywalf9mEsOHXV1lxtQDNcejVEmitJJ+1Vr2EtafPEbMQZp89TAuag # ROKE4YuohCUKm+v3geJqTQarTBjqV25RCOT+XFngTMDD9wYx6TwndB2I1Ly726Ni # HUHs0uvq3ciCV9JwNXdt1VZ63WK1NSgpVEsiK9EPABPt1EfXcKrfaPYkbkFi79eK # 1ETxx3NomYNUHNiGU+X1Be8L7qpHwjo0g3/33XhtOr9LiDoUXh/V2LFTETiqV9Q8 # yLEavQW3j9LQ/h/CaGz5YdGfrY8HiPfMIeLEokKxGf0hHcTEFApB0yLlq6KoHrFA # EANR/4XuFIpl9sDywVIWt4tKqG+P6pRAXzg1zG5rGlslZWmw7XwgvhBu3jkLP9Ax # rsSYwY2ftrwwze5NA6VDLS7pz+OrXXWLUmoyNrJNx5Bk0wEwzkQxzkOvmbdPhsOP # 1ZM0uA/xIV7cSpNpZUw5MIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAA # FTANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp # bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw # b3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0 # aG9yaXR5IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy # b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIP # ADCCAgoCggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9s # SuDivbk+F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3 # po5GawcU88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2 # vjUmZNqYO7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GP # sjksUZzpcGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3 # rMvrg0XnRm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDP # c31BmkZ1zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8F # A6PRc6ZNN3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q # 6oRRRuLRvWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1f # MHSRlJTYuVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLv # jflSxIUXk8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGj # ggHdMIIB2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ # ZMSavoKRPEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIw # XAYDVR0gBFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDov # L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMG # A1UdJQQMMAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsG # A1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJc # YmjRPZSQW9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9z # b2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz # LmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWlj # cm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0 # MA0GCSqGSIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5H # ZHixBpOXPTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2 # HnjYNi6cqYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1 # JFe53Z/zjj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8 # F7BUhUKz/AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99J # o3QMvOyRgNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4K # WN1APMdUbZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZ # kWsNn6Qo3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58 # oWFsc/4Ku+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w # /ue10CgaiQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+ # 7X6gMTN9vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1iz # oXBm8qGCA1AwggI4AgEBMIH5oYHRpIHOMIHLMQswCQYDVQQGEwJVUzETMBEGA1UE # CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z # b2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVy # YXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046MzcwMy0wNUUwLUQ5NDcx # JTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUr # DgMCGgMVAInbHtxB+OlGyQnxQYhy04KSYSSPoIGDMIGApH4wfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQELBQACBQDpthCiMCIYDzIwMjQw # NDAyMDUwNTM4WhgPMjAyNDA0MDMwNTA1MzhaMHcwPQYKKwYBBAGEWQoEATEvMC0w # CgIFAOm2EKICAQAwCgIBAAICLWACAf8wBwIBAAICEqYwCgIFAOm3YiICAQAwNgYK # KwYBBAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQAC # AwGGoDANBgkqhkiG9w0BAQsFAAOCAQEAa8rmD7HZlFm8QCe1iGEnK/XivanmW+do # YRUJS1II+TIhNnKF1aSltGcKwIel/WkDaQmz6cpD9LPt/WGttuvs0yNdXqHs/z79 # Y5GFS8Wues/6aV0paX6lH63saPzmR+whyr33n5ouWjUi8k2M+t01PfTOF146XtwL # 9zlpravgJwBSXrdfGONDb6rXmHu1ggdbLiA2S02GKOj7jcGOSeXTQon9VIGiyMoi # XTbRB8xjqp40C3KLPXjOIZ+izP6ujPp602SEoD0cI+zRvN5hwu0Bcly31e4AL1ud # ovEQmtv40DxdFJBtQT+LiEBPC9NcaZGj4LjwEBgdpSjJwT/mdvriiTGCBA0wggQJ # AgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD # VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk # BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB6pokctVZ # P2FjAAEAAAHqMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZI # hvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIB5DMCn34JHTIelYiREKghoCgMnxgXTg # 3h4VfStOV3ufMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgKY+h1eNkNHiL # CDSW0sA1cGHkbW4qooi+ryyMp6S4ZngwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T # dGFtcCBQQ0EgMjAxMAITMwAAAeqaJHLVWT9hYwABAAAB6jAiBCA5+67zo/KbtRmx # 6y7ElZBXLqMpLvZdaMeHr6kMP60XjzANBgkqhkiG9w0BAQsFAASCAgBIjkW1fKdy # XA4FEbdSeAzWzeEQs1Abgv1Y/05kfjnw54nZIbru8kn3QeVHH5OspitRCxB+EATB # NmU/hSE3FjHkkevlQTi6F+nhSAk2UUtM1Bhg784sJCByK+dDwDQr9r6zqZgKVBsG # 8stURckGfN6YadWb7n/aelm1vsTrKLH6gebpBFIqyLtsnaJWswWPHib9IZYt6Ww0 # X9ydIymUoFb0DUYCc5aQ6x8w+120BAAK4dFHdMxey8Nf3rticrlR18KXjUhD5OVV # GaXkAn3w7xjKldHLkszMp2bLE87Tf1ceUDVGwpuqDFRp34poOOWkUAijlpSPbMFr # jWxmkxOb2lgpv8v0GvdU7IpafGc2rQaxvY0nZa4HaOV4xn+ddK2qAMJBP6tRbQP9 # EeA2l+M81uPKKiWwxcbvmRfQCaniS2Ai/C7pEGh9RT+tjrL8sJaABJztbIwZ3iqg # C4Lcl8lFf8ZhY+J2LwzKsDT7R2XbktRBErkKFkU0dSi7z/Rh8VOuJuM3gVGgX3M9 # qdufAQ5OuKAa+KxtITzrmfwBCaY4pPnpeKh9v3HH/GsqJD7n+XIH9tV1t+tvP0U2 # PTmakZMYJbCI/3m+zoLGXPtP0MHGnCupqiBwDqDnK9MpLggxFRKt55deAS/902yi # Om+wZDeqhZvpmOjGbVTIxvNnf1bUONbW8w== # SIG # End signature block |