AuthCommands.ps1
#requires -Version 5.1 $expires = @( [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin, [KeeperSecurity.Authentication.TwoFactorDuration]::Every30Days, [KeeperSecurity.Authentication.TwoFactorDuration]::Forever) function twoFactorChannelToText ([KeeperSecurity.Authentication.TwoFactorChannel] $channel) { if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::Authenticator) { return 'authenticator' } if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::TextMessage) { return 'sms' } if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::DuoSecurity) { return 'duo' } if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::RSASecurID) { return 'rsa' } if ($channel -eq [KeeperSecurity.Authentication.TwoFactorChannel]::KeeperDNA) { return 'dna' } return '' } function deviceApprovalChannelToText ([KeeperSecurity.Authentication.DeviceApprovalChannel]$channel) { if ($channel -eq [KeeperSecurity.Authentication.DeviceApprovalChannel]::Email) { return 'email' } if ($channel -eq [KeeperSecurity.Authentication.DeviceApprovalChannel]::KeeperPush) { return 'keeper' } if ($channel -eq [KeeperSecurity.Authentication.DeviceApprovalChannel]::TwoFactorAuth) { return '2fa' } return '' } function twoFactorDurationToExpire ([KeeperSecurity.Authentication.TwoFactorDuration] $duration) { if ($duration -eq [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin) { return 'now' } if ($duration -eq [KeeperSecurity.Authentication.TwoFactorDuration]::Forever) { return 'never' } return "$([int]$duration)_days" } function getStepPrompt ([KeeperSecurity.Authentication.IAuthentication] $auth) { $prompt = "`nUnsupported ($($auth.step.State.ToString()))" if ($auth.step -is [KeeperSecurity.Authentication.Sync.DeviceApprovalStep]) { $prompt = "`nDevice Approval ($(deviceApprovalChannelToText $auth.step.DefaultChannel))" } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.TwoFactorStep]) { $channelText = twoFactorChannelToText $auth.step.DefaultChannel $prompt = "`n2FA channel($($channelText)) expire[$(twoFactorDurationToExpire $auth.step.Duration)]" } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) { $prompt = "`nMaster Password" } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoTokenStep]) { $prompt = "`nSSO Token" } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoDataKeyStep]) { $prompt = "`nSSO Login Approval" } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.ReadyToLoginStep]) { $prompt = "`nLogin" } return $prompt } function printStepHelp ([KeeperSecurity.Authentication.IAuthentication] $auth) { $commands = @() if ($auth.step -is [KeeperSecurity.Authentication.Sync.DeviceApprovalStep]) { $channels = @() foreach ($ch in $auth.step.Channels) { $channels += deviceApprovalChannelToText $ch } if ($channels) { $commands += "channel=<$($channels -join ' | ')> to change channel." } $commands += "`"push`" to send a push to the channel" $commands += '<code> to send a code to the channel' } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.TwoFactorStep]) { $channels = @() foreach ($ch in $auth.step.Channels) { $channelText = twoFactorChannelToText $ch if ($channelText) { $channels += $channelText } } if ($channels) { $commands += "channel=<$($channels -join ' | ')> to change channel." } $channels = @() foreach ($ch in $auth.step.Channels) { $pushes = $auth.step.GetChannelPushActions($ch) if ($null -ne $pushes) { foreach ($push in $pushes) { $channels += [KeeperSecurity.Authentication.AuthUIExtensions]::GetPushActionText($push) } } } if ($channels) { $commands += "`"$($channels -join ' | ')`" to send a push/code" } $channels = @() foreach ($exp in $expires) { $channels += twoFactorDurationToExpire $exp } $commands += "expire=<$($channels -join ' | ')> to set 2fa expiration." $commands += '<code> to send a 2fa code.' } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) { $commands += '<password> to send a master password.' } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoTokenStep]) { $commands += $auth.step.SsoLoginUrl $commands += '' if (-not $auth.step.LoginAsProvider) { $commands += '"password" to login using master password.' } $commands += '<sso token> paste SSO login token.' } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoDataKeyStep]) { $channels = @() foreach ($ch in $auth.step.Channels) { $channels += [KeeperSecurity.Authentication.AuthUIExtensions]::SsoDataKeyShareChannelText($ch) } if ($channels) { $commands += "`"$($channels -join ' | ')`" to request login approval" } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.ReadyToLoginStep]) { $commands += '"login <Keeper Email>" login to Keeper as user' $commands += '"login_sso <Enterprise Domain>" login to Enterprise Domain' } if ($commands) { Write-Output "`nAvailable Commands`n" foreach ($command in $commands) { Write-Output $command } Write-Output '<Enter> to resume' } } function executeStepAction ([KeeperSecurity.Authentication.IAuthentication] $auth, [string] $action) { function tryExpireToTwoFactorDuration ([string] $expire, [ref] [KeeperSecurity.Authentication.TwoFactorDuration] $duration) { $result = $true if ($expire -eq 'now') { $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin } elseif ($expire -eq 'never') { $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::Forever } elseif ($expire -eq '30_days') { $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::Every30Days } else { $duration.Value = [KeeperSecurity.Authentication.TwoFactorDuration]::EveryLogin } return $result } function tryTextToDeviceApprovalChannel ([string] $text, [ref] [KeeperSecurity.Authentication.DeviceApprovalChannel] $channel) { $result = $true if ($text -eq 'email') { $channel.Value = [KeeperSecurity.Authentication.DeviceApprovalChannel]::Email } elseif ($text -eq 'keeper') { $channel.Value = [KeeperSecurity.Authentication.DeviceApprovalChannel]::KeeperPush } elseif ($text -eq '2fa') { $channel.Value = [KeeperSecurity.Authentication.DeviceApprovalChannel]::TwoFactorAuth } else { Write-Output 'Unsupported device approval channel:', $text $result = $false } return $result } function tryTextToTwoFactorChannel ([string] $text, [ref] [KeeperSecurity.Authentication.TwoFactorChannel] $channel) { $result = $true if ($text -eq 'authenticator') { $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::Authenticator } elseif ($text -eq 'sms') { $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::TextMessage } elseif ($text -eq 'duo') { $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::DuoSecurity } elseif ($text -eq 'rsa') { $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::RSASecurID } elseif ($text -eq 'dna') { $channel.Value = [KeeperSecurity.Authentication.TwoFactorChannel]::KeeperDNA } else { Write-Output 'Unsupported 2FA channel:', $text $result = $false } return $result } if ($auth.step -is [KeeperSecurity.Authentication.Sync.DeviceApprovalStep]) { if ($action -eq 'push') { $auth.step.SendPush($auth.step.DefaultChannel).GetAwaiter().GetResult() | Out-Null } elseif ($action -match 'channel\s*=\s*(.*)') { $ch = $Matches.1 [KeeperSecurity.Authentication.DeviceApprovalChannel]$cha = $auth.step.DefaultChannel if (tryTextToDeviceApprovalChannel ($ch) ([ref]$cha)) { $auth.step.DefaultChannel = $cha } } else { Try { $auth.step.SendCode($auth.step.DefaultChannel, $action).GetAwaiter().GetResult() | Out-Null } Catch [KeeperSecurity.Authentication.KeeperApiException] { Write-Warning $_ } Catch { Write-Error $_ } } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.TwoFactorStep]) { if ($action -match 'channel\s*=\s*(.*)') { $ch = $Matches.1 [KeeperSecurity.Authentication.TwoFactorChannel]$cha = $auth.step.DefaultChannel if (tryTextToTwoFactorChannel($ch) ([ref]$cha)) { $auth.step.DefaultChannel = $cha } } elseif ($action -match 'expire\s*=\s*(.*)') { $exp = $Matches.1 [KeeperSecurity.Authentication.TwoFactorDuration]$dur = $auth.step.Duration if (tryExpireToTwoFactorDuration($exp) ([ref]$dur)) { $auth.step.Duration = $dur } } else { foreach ($cha in $auth.step.Channels) { $pushes = $auth.step.GetChannelPushActions($cha) if ($null -ne $pushes) { foreach ($push in $pushes) { if ($action -eq [KeeperSecurity.Authentication.AuthUIExtensions]::GetPushActionText($push)) { $auth.step.SendPush($push).GetAwaiter().GetResult() | Out-Null return } } } Try { $auth.step.SendCode($auth.step.DefaultChannel, $action).GetAwaiter().GetResult() | Out-Null } Catch { Write-Error $_ } } } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) { Try { $auth.step.VerifyPassword($action).GetAwaiter().GetResult() | Out-Null } Catch [KeeperSecurity.Authentication.KeeperAuthFailed] { Write-Warning 'Invalid password' } Catch { Write-Error $_ } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoTokenStep]) { if ($action -eq 'password') { $auth.step.LoginWithPassword().GetAwaiter().GetResult() | Out-Null } else { $auth.step.SetSsoToken($action).GetAwaiter().GetResult() | Out-Null } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.SsoDataKeyStep]) { [KeeperSecurity.Authentication.DataKeyShareChannel]$channel = [KeeperSecurity.Authentication.DataKeyShareChannel]::KeeperPush if ([KeeperSecurity.Authentication.AuthUIExtensions]::TryParseDataKeyShareChannel($action, [ref]$channel)) { $auth.step.RequestDataKey($channel).GetAwaiter().GetResult() | Out-Null } } elseif ($auth.step -is [KeeperSecurity.Authentication.Sync.ReadyToLoginStep]) { if ($action -match '^login\s+(.*)$') { $username = $Matches.1 $auth.Login($username).GetAwaiter().GetResult() | Out-Null } elseif ($action -match '^login_sso\s+(.*)$') { $providerName = $Matches.1 $auth.LoginSso($providerName).GetAwaiter().GetResult() | Out-Null } } } function Connect-Keeper { <# .Synopsis Login to Keeper .Parameter Username User email .Parameter NewLogin Do not use Last Login information .Parameter SsoPassword Use Master Password for SSO account .Parameter Server Change default keeper server #> [CmdletBinding(DefaultParameterSetName = 'regular')] Param( [Parameter(Position = 0)][string] $Username, [Parameter()] [SecureString]$Password, [Parameter()][switch] $NewLogin, [Parameter(ParameterSetName = 'sso_password')][switch] $SsoPassword, [Parameter(ParameterSetName = 'sso_provider')][switch] $SsoProvider, [Parameter()][string] $Server ) Disconnect-Keeper -Resume | Out-Null $storage = New-Object KeeperSecurity.Configuration.JsonConfigurationStorage if (-not $Server) { $Server = $storage.LastServer if ($Server) { Write-Information -MessageData "`nUsing Keeper Server: $Server`n" } else { Write-Information -MessageData "`nUsing Default Keeper Server: $([KeeperSecurity.Authentication.KeeperEndpoint]::DefaultKeeperServer)`n" } } $endpoint = New-Object KeeperSecurity.Authentication.KeeperEndpoint($Server, $storage.Servers) $endpoint.DeviceName = 'PowerShell Commander' $endpoint.ClientVersion = 'c16.1.0' $authFlow = New-Object KeeperSecurity.Authentication.Sync.AuthSync($storage, $endpoint) $authFlow.ResumeSession = $true $authFlow.AlternatePassword = $SsoPassword.IsPresent if (-not $NewLogin.IsPresent -and -not $SsoProvider.IsPresent) { if (-not $Username) { $Username = $storage.LastLogin } } $namePrompt = 'Keeper Username' if ($SsoProvider.IsPresent) { $namePrompt = 'Enterprise Domain' } if ($Username) { Write-Output "$(($namePrompt + ': ').PadLeft(21, ' ')) $Username" } else { while (-not $Username) { $Username = Read-Host -Prompt $namePrompt.PadLeft(20, ' ') } } if ($SsoProvider.IsPresent) { $authFlow.LoginSso($Username).GetAwaiter().GetResult() | Out-Null } else { $passwords = @() if ($Password) { if ($Password -is [SecureString]) { $passwords += [Net.NetworkCredential]::new('', $Password).Password } elseif ($Password -is [String]) { $passwords += $Password } } $authFlow.Login($Username, $passwords).GetAwaiter().GetResult() | Out-Null } Write-Output "" while (-not $authFlow.IsCompleted) { if ($lastStep -ne $authFlow.Step.State) { printStepHelp $authFlow $lastStep = $authFlow.Step.State } $prompt = getStepPrompt $authFlow if ($authFlow.Step -is [KeeperSecurity.Authentication.Sync.PasswordStep]) { $securedPassword = Read-Host -Prompt $prompt -AsSecureString if ($securedPassword.Length -gt 0) { $action = [Net.NetworkCredential]::new('', $securedPassword).Password } else { $action = '' } } else { $action = Read-Host -Prompt $prompt } if ($action) { if ($action -eq '?') { } else { executeStepAction $authFlow $action } } } if ($authFlow.Step.State -ne [KeeperSecurity.Authentication.Sync.AuthState]::Connected) { if ($authFlow.Step -is [KeeperSecurity.Authentication.Sync.ErrorStep]) { Write-Warning $authFlow.Step.Message } return } $auth = $authFlow if ([KeeperSecurity.Authentication.AuthExtensions]::IsAuthenticated($auth)) { Write-Debug -Message "Connected to Keeper as $Username" $vault = New-Object KeeperSecurity.Vault.VaultOnline($auth) $task = $vault.SyncDown() Write-Information -MessageData 'Syncing ...' $task.GetAwaiter().GetResult() | Out-Null $vault.AutoSync = $true $Script:Context.Auth = $auth $Script:Context.Vault = $vault [KeeperSecurity.Vault.VaultData]$vaultData = $vault Write-Information -MessageData "Decrypted $($vaultData.RecordCount) record(s)" Set-KeeperLocation -Path '\' | Out-Null } } $Keeper_ConfigServerCompleter = { param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters) $prefixes = @('', 'dev.', 'qa.') $suffixes = $('.com', '.eu') $prefixes | ForEach-Object { $p = $_; $suffixes | ForEach-Object { $s = $_; "${p}keepersecurity${s}" } } | Where-Object { $_.StartsWith($wordToComplete) } } Register-ArgumentCompleter -Command Connect-Keeper -ParameterName Server -ScriptBlock $Keeper_ConfigServerCompleter New-Alias -Name kc -Value Connect-Keeper function Disconnect-Keeper { <# .Synopsis Logout from Keeper #> [CmdletBinding()] Param( [Parameter()][switch] $Resume ) $Script:Context.AvailableTeams = $null $Script:Context.AvailableUsers = $null $Script:Context.ManagedCompanyId = 0 $Script:Context.Enterprise = $null $vault = $Script:Context.Vault if ($vault) { $vault.Dispose() | Out-Null } $Script:Context.Vault = $null [KeeperSecurity.Authentication.IAuthentication] $auth = $Script:Context.Auth if ($auth) { if (-not $Resume.IsPresent) { $auth.Logout().GetAwaiter().GetResult() | Out-Null } $auth.Dispose() | Out-Null } $Script:Context.Auth = $null } New-Alias -Name kq -Value Disconnect-Keeper function Sync-Keeper { <# .Synopsis Sync down with Keeper #> [CmdletBinding()] [KeeperSecurity.Vault.VaultOnline]$vault = $Script:Context.Vault if ($vault) { $task = $vault.SyncDown() $task.GetAwaiter().GetResult() | Out-Null } else { Write-Error -Message "Not connected" -ErrorAction Stop } } New-Alias -Name ks -Value Sync-Keeper function Get-KeeperInformation { <# .Synopsis Prints account license information #> $vault = getVault [KeeperSecurity.Authentication.IAuthentication]$auth = $vault.Auth [KeeperSecurity.Authentication.AccountLicense]$license = $auth.AuthContext.License switch ($license.AccountType) { 0 { $accountType = $license.ProductTypeName } 1 { $accountType = 'Family Plan'} 2 { $accountType = 'Enterprise' } Default { $accountType = $license.ProductTypeName } } $accountType = 'Enterprise' [PSCustomObject]@{ PSTypeName = "KeeperSecurity.License.Info" User = $auth.Username Server = $auth.Endpoint.Server Admin = $auth.AuthContext.IsEnterpriseAdmin AccountType = $accountType RenewalDate = $license.ExpirationDate StorageCapacity = [int] [Math]::Truncate($license.BytesTotal / (1024 * 1024 * 1024)) StorageUsage = [int] [Math]::Truncate($license.BytesUsed * 100 / $license.BytesTotal) StorageExpires = $license.StorageExpirationDate } if ($license.AccountType -eq 2) { $enterprise = getEnterprise if ($enterprise) { $enterpriseLicense = $enterprise.enterpriseData.EnterpriseLicense $productTypeId = $enterpriseLicense.ProductTypeId if ($productTypeId -in @(2, 5)) { $tier = $enterpriseLicense.Tier if ($tier -eq 1) { $plan = 'Enterprise' } else { $plan = 'Business' } } elseif ($productTypeId -in @(9, 10)) { $distributor = $enterpriseLicense.Distributor if ($distributor -eq $true) { $plan = 'Distributor' } else { $plan = 'Managed MSP' } } elseif ($productTypeId -in @(11, 12)) { $plan = 'Keeper MSP' } elseif ($productTypeId -eq 8) { $tier = $enterpriseLicense.Tier if ($tier -eq 1) { $plan = 'Enterprise' } else { $plan = 'Business' } $plan = "MC $plan" } else { $plan = 'Unknown' } if ($productTypeId -in @(5, 10, 12)) { $plan = "$plan Trial" } $enterpriseInfo = [PSCustomObject]@{ PSTypeName = "KeeperSecurity.License.EnterpriseInfo" LicenseType = 'Enterprise' EnterpriseName = $enterprise.loader.EnterpriseName BasePlan = $plan } if ($enterpriseLicense.Paid) { $expiration = $enterpriseLicense.Expiration if ($expiration -gt 0) { $exp = [KeeperSecurity.Utils.DateTimeOffsetExtensions]::FromUnixTimeMilliseconds($expiration) $expDate = $exp.ToString('d') Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'Expires' -Value $expDate } switch ($enterpriseLicense.filePlanTypeId) { -1 { $filePlan = 'No Storage' } 0 { $filePlan = 'Trial' } 1 { $filePlan = '1GB' } 2 { $filePlan = '10GB' } 3 { $filePlan = '50GB' } 4 { $filePlan = '100GB' } 5 { $filePlan = '250GB' } 6 { $filePlan = '500GB' } 7 { $filePlan = '1TB' } 8 { $filePlan = '10TB' } Default { $filePlan = '???' } } Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'StorageCapacity' -Value $filePlan $numberOfSeats = $enterpriseLicense.NumberOfSeats if ($numberOfSeats -gt 0) { Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'TotalUsers' -Value $numberOfSeats } $seatsAllocated = $enterpriseLicense.SeatsAllocated if ($seatsAllocated -gt 0) { Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'ActiveUsers' -Value $seatsAllocated } $seatsPending = $enterpriseLicense.SeatsPending if ($seatsAllocated -gt 0) { Add-Member -InputObject $enterpriseInfo -MemberType NoteProperty -Name 'InvitedUsers' -Value $SeatsPending } } $enterpriseInfo } } } New-Alias -Name kwhoami -Value Get-KeeperInformation # SIG # Begin signature block # MIIR1wYJKoZIhvcNAQcCoIIRyDCCEcQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB # gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR # AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU5596SoFS3rI1BiU+ehsFWSxa # C0Gggg4jMIIGsDCCBJigAwIBAgIQCK1AsmDSnEyfXs2pvZOu2TANBgkqhkiG9w0B # AQwFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk # IFJvb3QgRzQwHhcNMjEwNDI5MDAwMDAwWhcNMzYwNDI4MjM1OTU5WjBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg # Q0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA1bQvQtAorXi3XdU5 # WRuxiEL1M4zrPYGXcMW7xIUmMJ+kjmjYXPXrNCQH4UtP03hD9BfXHtr50tVnGlJP # DqFX/IiZwZHMgQM+TXAkZLON4gh9NH1MgFcSa0OamfLFOx/y78tHWhOmTLMBICXz # ENOLsvsI8IrgnQnAZaf6mIBJNYc9URnokCF4RS6hnyzhGMIazMXuk0lwQjKP+8bq # HPNlaJGiTUyCEUhSaN4QvRRXXegYE2XFf7JPhSxIpFaENdb5LpyqABXRN/4aBpTC # fMjqGzLmysL0p6MDDnSlrzm2q2AS4+jWufcx4dyt5Big2MEjR0ezoQ9uo6ttmAaD # G7dqZy3SvUQakhCBj7A7CdfHmzJawv9qYFSLScGT7eG0XOBv6yb5jNWy+TgQ5urO # kfW+0/tvk2E0XLyTRSiDNipmKF+wc86LJiUGsoPUXPYVGUztYuBeM/Lo6OwKp7AD # K5GyNnm+960IHnWmZcy740hQ83eRGv7bUKJGyGFYmPV8AhY8gyitOYbs1LcNU9D4 # R+Z1MI3sMJN2FKZbS110YU0/EpF23r9Yy3IQKUHw1cVtJnZoEUETWJrcJisB9IlN # Wdt4z4FKPkBHX8mBUHOFECMhWWCKZFTBzCEa6DgZfGYczXg4RTCZT/9jT0y7qg0I # U0F8WD1Hs/q27IwyCQLMbDwMVhECAwEAAaOCAVkwggFVMBIGA1UdEwEB/wQIMAYB # Af8CAQAwHQYDVR0OBBYEFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB8GA1UdIwQYMBaA # FOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB/wQEAwIBhjATBgNVHSUEDDAK # BggrBgEFBQcDAzB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUHMAGGGGh0dHA6Ly9v # Y3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDovL2NhY2VydHMuZGln # aWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcnQwQwYDVR0fBDwwOjA4 # oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJv # b3RHNC5jcmwwHAYDVR0gBBUwEzAHBgVngQwBAzAIBgZngQwBBAEwDQYJKoZIhvcN # AQEMBQADggIBADojRD2NCHbuj7w6mdNW4AIapfhINPMstuZ0ZveUcrEAyq9sMCcT # Ep6QRJ9L/Z6jfCbVN7w6XUhtldU/SfQnuxaBRVD9nL22heB2fjdxyyL3WqqQz/WT # auPrINHVUHmImoqKwba9oUgYftzYgBoRGRjNYZmBVvbJ43bnxOQbX0P4PpT/djk9 # ntSZz0rdKOtfJqGVWEjVGv7XJz/9kNF2ht0csGBc8w2o7uCJob054ThO2m67Np37 # 5SFTWsPK6Wrxoj7bQ7gzyE84FJKZ9d3OVG3ZXQIUH0AzfAPilbLCIXVzUstG2MQ0 # HKKlS43Nb3Y3LIU/Gs4m6Ri+kAewQ3+ViCCCcPDMyu/9KTVcH4k4Vfc3iosJocsL # 6TEa/y4ZXDlx4b6cpwoG1iZnt5LmTl/eeqxJzy6kdJKt2zyknIYf48FWGysj/4+1 # 6oh7cGvmoLr9Oj9FpsToFpFSi0HASIRLlk2rREDjjfAVKM7t8RhWByovEMQMCGQ8 # M4+uKIw8y4+ICw2/O/TOHnuO77Xry7fwdxPm5yg/rBKupS8ibEH5glwVZsxsDsrF # hsP2JjMMB0ug0wcCampAMEhLNKhRILutG4UI4lkNbcoFUCvqShyepf2gpx8GdOfy # 1lKQ/a+FSCH5Vzu0nAPthkX0tGFuv2jiJmCG6sivqf6UHedjGzqGVnhOMIIHazCC # BVOgAwIBAgIQAnNTGQOIer82vZ1cJyDJDjANBgkqhkiG9w0BAQsFADBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEg # Q0ExMB4XDTIyMDIwMjAwMDAwMFoXDTI1MDIwMTIzNTk1OVowcDELMAkGA1UEBhMC # VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK # ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5 # IEluYy4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDNgTqmksdjUyKF # 5zWkDyghf0PLWJWdzG0TX2j8B4J55xwt+B17zd4Xc3n0dvmSVAyPQANeN+mP1chf # 4LTRn9h4jWb8Jsfn+JzyRhj/gYINYvBnpRpqoM0z7QC9Ebwj5T61Cogm9EKGcrG+ # Ujh+Z7pTqfSUrHD8NMXhDL/UpVn+w0Pb4qg7o7AH2o94n7u/qTlMGZCs+VCAvhNr # wPABxvFY07YGb9t5/IZlPE8vG3p1vw2SbgREgFWSEQFj6X2CIhSrbiFCW/766/Mq # EX6qm+RyF71fD4d3yShg39guaE9o+TBl1MqVCje4bK/wGoNxCho0I6Z1fBBKloyp # vlx3gPpU7tJJ+KpuIiel9R9dGQuscqKzehPtbRc9Abr9ThN/HrLg1sFFVMdn2oMR # 63QCUdz+B1NuS7Ap8Ti7XvAPJHzEuQDcdMcRbkIfllJVqrb9UXEFwOPzvRU2KrcQ # 42Jlnn4T+WenPx5Nr3o/o08WLhLTicEK1OacEowyRLBmih4Gxpdk3fUAVCEkdvmq # TSydQpl1Bk8V88dxCkB1wMZyFYLNcddBL4kUbwjso/z6f2TtfAVYs/iIRWqs7Xqt # 4F2BBqobOGMymwg6VgVjjzDIgJCZSbjpq2IoVTci5vli6vxgSoZ01fccSaKa4Izm # B7DbobIkIjLgPqpnCkqlHuJj5hQ9twIDAQABo4ICBjCCAgIwHwYDVR0jBBgwFoAU # aDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFCZd3/KEdT2t5WTIFb3TUaM4 # sTikMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0f # BIGtMIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGg # T4ZNaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29k # ZVNpZ25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwPgYDVR0gBDcwNTAzBgZn # gQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BT # MIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln # aWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIx # Q0ExLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQAGyDM3Cbxq # Auhr8O2xwOoCSVKmFkXqicwlrugwLW44Y4WX+imvTrGfjj2S99k/4D5H8DgtW/u8 # tOxcCoehTOCIEwP5TLrieHppsqAR4jaJRcdAHOWiJ1bmwQBv/cBU9vaelL0oXxxf # TwD9oDaQNuyq6p+nIJMqbKv33b8AWGe3zq4JwblaFjRDL5lUDNhPx3g/pm7JhnbX # 7QTKydAJvpbuP5cqUH1GEeVMjc5vEELtGNy/fy7Ekm4dndX4IZcFXW5L0Lx8cReB # hIZwA+pzdzTWQYvfxgRMb/j2uY+Tkb6Wz2x9BBS1UXiP2qrs3rhQv8DZRkUSqnko # YD4uJP8gk8BXcIXIThgEF2YCq2hBiwna5Ijbwkmjn1lWwGv15SznTOTnrVApJqB1 # tB2s2ovUNV4CyKDPVr+9/CS6IQJfEZeHYcYLsIga2q5NZCrqZAasBfCwALVkALos # DIWhs33vYLfETMSuk5Hd5JC+hLjVM3ZJwslvnc/wec2r0GNAiZ3a1aweC7NYuzRz # 29Mi/eR/4ylmCltyZqYJ1JcC/g6eY2Q0xkdWc8P0yHfQ/3fe7+AKXXKNjfv858GW # lg1Ck2lvwPdLqJWqj1FwJPiGRCB+WulPe0csTyWnf+ed45TXx69tZ6BZr0Xr2jXu # ybBdJtg0NN0a62xxWrmX42CgsrzHzRm7OzGCAx4wggMaAgEBMH0waTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB # MQIQAnNTGQOIer82vZ1cJyDJDjAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEK # MAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3 # AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUr6+wO9XX7Mhf9MzB # n7zQAU9HH7swDQYJKoZIhvcNAQEBBQAEggIAWwTiFJK3ZrBTk3h/sm1MRYiaa6UK # usYzUnxNXzc+YCnZbB3D8Y7MaqQnzzkx71JwfPei3v4RMGcXEsqQHi7OJu205cEy # oRc9VQeCBoiIyUMsKT2EXwESO1K2GICKFOH/rfM+xALN+gFVRsxHVbF/CAyxwKMk # AO6eBTOKuoenicJ8ws266MJaXFLoDZcalc3la6con2siFrXPq1oW7kkuAOXsuRoW # PR9QxSEEygkACJUmDtyptrlRyoeYyA5wQNui5WZtawXlLMJCerc//Sn5IfdkHwJ8 # T151uQsN8sugzlVA+t1L0h0iAWobaUWObO+WEoQFOoXynxpuj1oixhNX2bSdQAUi # OXCED4P5tN5NXW1dd5zhPzxwt1naaClUvsxCWlvSbAyxs2QMjMMmnkfZhW15ari/ # Y6Q9ZjYXEPcpprD+OCT6gEtkynOm4/s/MWuC4LtM9327c3fhh+WpF0occwfQEieY # +FHj46dRvx79oxwIO+gtOFPI4m4bMcoMlL2a7G19L+Kr2OuV9jccAvxy0rhlLrEU # ms1sQjDsdq8Q03jKVR2HJudenlJa5UGOOl3opiLeg/dGpPEpNmJ5RfQgeUoZ3oFn # Q4/BLKod+GzL36azSa26o9YPW1fkON3SLtLMWv0uGAqps/1qvbmGpASvmV8hc4CA # HtMX2gN7UoaYlsc= # SIG # End signature block |