KeeperBiometrics.ps1
|
$KeeperBiometricAvailable = $false if ($IsWindows -or ($PSVersionTable.Platform -eq 'Win32NT') -or ($env:OS -like '*Windows*')) { try { $null = [KeeperBiometric.PasskeyManager] $KeeperBiometricAvailable = $true } catch { Write-Warning "KeeperBiometric assembly not available: $($_.Exception.Message)" $KeeperBiometricAvailable = $false } } else { $KeeperBiometricAvailable = $false } function Test-AssemblyAvailable { <# .SYNOPSIS Tests if the KeeperBiometric assembly is available .PARAMETER Quiet Suppress warning messages if assembly is not available .OUTPUTS [bool] True if assembly is available, false otherwise #> [CmdletBinding()] param( [Parameter()] [switch]$Quiet ) if ($KeeperBiometricAvailable) { return $true } if (-not $Quiet -and ($IsWindows -or ($PSVersionTable.Platform -eq 'Win32NT') -or ($env:OS -like '*Windows*'))) { Write-Warning "KeeperBiometric assembly not available. Please build the project first." } return $false } function Test-WindowsHelloCapabilities { <# .SYNOPSIS Tests Windows Hello capabilities with comprehensive information .DESCRIPTION This function checks if Windows Hello is available and returns detailed capability information. .EXAMPLE Test-WindowsHelloCapabilities .EXAMPLE $caps = Test-WindowsHelloCapabilities -PassThru if ($caps.IsAvailable) { Write-Host "Windows Hello is ready for use" } #> [CmdletBinding()] param( [Parameter()] [switch]$PassThru, [Parameter()] [switch]$Quiet ) if (-not (Test-AssemblyAvailable -Quiet:$Quiet)) { return $false } try { if ($PassThru) { return [KeeperBiometric.PasskeyManager]::GetCapabilities() } else { return [KeeperBiometric.PasskeyManager]::IsAvailable() } } catch { if (-not $Quiet) { Write-Debug "Failed to check Windows Hello capabilities: $($_.Exception.Message)" } return $false } } function Assert-KeeperBiometricCredential { <# .SYNOPSIS Performs Windows Hello authentication using native WebAuthn APIs .DESCRIPTION This function performs the complete Windows Hello authentication flow using PasskeyManager. .PARAMETER Username The username to authenticate (optional - will use current auth username if not provided) .PARAMETER Purpose The purpose of authentication: 'login' (default) or 'vault' (re-authentication) .PARAMETER AuthSyncObject Keeper AuthSync instance (optional - will use global auth if not provided) .PARAMETER Vault Keeper Vault instance (optional - will use global vault if not provided) .PARAMETER PassThru Return the authentication result object. If not specified, function returns nothing. .EXAMPLE Assert-KeeperBiometricCredential .EXAMPLE $result = Assert-KeeperBiometricCredential -PassThru if ($result.Success) { Write-Host "Authentication successful!" } .EXAMPLE $result = Assert-KeeperBiometricCredential -Purpose "vault" -PassThru #> [CmdletBinding()] param( [Parameter()] [string]$Username, [Parameter()] [ValidateSet('login', 'vault')] [string]$Purpose = 'login', [Parameter()] [object]$AuthSyncObject, [Parameter()] [object]$Vault, [Parameter()] [switch]$PassThru ) if (-not (Test-AssemblyAvailable)) { if ($PassThru) { return @{ Success = $false IsValid = $false EncryptedLoginToken = $null ErrorMessage = "KeeperBiometric assembly not available" ErrorType = "AssemblyNotFound" } } return } try { $auth = $null if ($Vault) { $auth = $Vault.Auth } elseif ($AuthSyncObject) { $auth = $AuthSyncObject } else { if (Get-Command getVault -ErrorAction SilentlyContinue) { $vault = getVault $auth = $vault.Auth } elseif ($Script:Context.Vault) { $vault = $Script:Context.Vault $auth = $vault.Auth } elseif (Get-Command getAuthSync -ErrorAction SilentlyContinue) { $auth = getAuthSync } elseif ($Script:Context.AuthSync) { $auth = $Script:Context.AuthSync } else { throw "No Vault or AuthSync instance available. Please connect to Keeper first." } } if ([string]::IsNullOrEmpty($Username)) { $Username = $auth.Username } $task = [KeeperBiometric.PasskeyManager]::AuthenticatePasskeyAsync($auth, $Username, $Purpose) $result = $task.GetAwaiter().GetResult() if ($result.Success) { Write-Host "Verification completed successfully!" -ForegroundColor Green } else { if ($result.ErrorMessage -match "cancelled|cancel" -or $result.ErrorType -eq "OperationCanceledException") { Write-Host "Windows Hello authentication was cancelled." -ForegroundColor Yellow } else { Write-Warning "Verification failed: $($result.ErrorMessage)" } } if ($PassThru) { return $result } } catch { Write-Error "Windows Hello authentication flow failed: $($_.Exception.Message)" if ($PassThru) { return @{ Success = $false IsValid = $false EncryptedLoginToken = $null ErrorMessage = $_.Exception.Message ErrorType = $_.Exception.GetType().Name } } } } function Get-KeeperAvailableBiometricCredentials { <# .SYNOPSIS Get list of available biometric credentials from Keeper .DESCRIPTION This function retrieves a list of all registered biometric credentials (passkeys) associated with the current Keeper account. .PARAMETER Vault Keeper vault instance (optional - will use global vault if not provided) .PARAMETER IncludeDisabled Include disabled credentials in the results (default: false) .EXAMPLE $credentials = Get-KeeperAvailableBiometricCredentials $credentials | Format-Table .EXAMPLE $allCredentials = Get-KeeperAvailableBiometricCredentials -IncludeDisabled #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] [object]$Vault, [Parameter(Mandatory=$false)] [switch]$IncludeDisabled ) if (-not (Test-AssemblyAvailable)) { throw "KeeperBiometric assembly not available. Please build the project first." } try { if (-not $Vault) { if (Get-Command getVault -ErrorAction SilentlyContinue) { $vault = getVault } elseif ($Script:Context.Vault) { $vault = $Script:Context.Vault } else { throw "No vault instance available. Please connect to Keeper first or provide a vault parameter." } } else { $vault = $Vault } $auth = $vault.Auth $task = [KeeperBiometric.PasskeyManager]::ListPasskeysAsync($auth, $IncludeDisabled.IsPresent) $passkeyList = $task.GetAwaiter().GetResult() $credentials = @() foreach ($passkey in $passkeyList) { $credential = [PSCustomObject]@{ Id = $passkey.UserId Name = $passkey.FriendlyName Created = $passkey.CreatedAt LastUsed = $passkey.LastUsedAt CredentialId = $passkey.CredentialId AAGUID = $passkey.AAGUID Disabled = $passkey.IsDisabled } $credentials += $credential } return $credentials } catch { Write-Error "Failed to get available biometric credentials: $($_.Exception.Message)" throw "Error getting available biometric credentials: $($_.Exception.Message)" } } $script:AAGUID_PROVIDER_MAPPING = @{ 'ea9b8d664d011d213ce4b6b48cb575d4' = 'Google Password Manager' 'adce000235bcc60a648b0b25f1f05503' = 'Chrome on Mac' 'fbfc3007154e4ecc8c0b6e020557d7bd' = 'iCloud Keychain' 'dd4ec289e01d41c9bb8970fa845d4bf2' = 'iCloud Keychain (Managed)' '08987058cadc4b81b6e130de50dcbe96' = 'Windows Hello' '9ddd1817af5a4672a2b93e3dd95000a9' = 'Windows Hello' '6028b017b1d44c02b4b3afcdafc96bb2' = 'Windows Hello' '00000000000000000000000000000000' = 'Platform Authenticator' } function Get-ProviderNameFromAAGUID { <# .SYNOPSIS Get friendly provider name from AAGUID .DESCRIPTION Maps an AAGUID to a friendly provider name using the community-sourced mapping. .PARAMETER AAGUID The AAGUID to look up .EXAMPLE Get-ProviderNameFromAAGUID -AAGUID "9ddd1817-af5a-4672-a2b9-3e3dd95000a9" #> [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$AAGUID ) if ($script:AAGUID_PROVIDER_MAPPING.ContainsKey($AAGUID)) { return $script:AAGUID_PROVIDER_MAPPING[$AAGUID] } else { return "Unknown Provider ($AAGUID)" } } function Unregister-KeeperBiometricCredential { <# .SYNOPSIS Remove/unregister biometric credentials from Keeper .DESCRIPTION This function removes biometric credentials (passkeys) from the Keeper account. .PARAMETER CredentialId Specific credential ID to remove (deprecated - not used, function removes for current username) .PARAMETER Username Username to unregister biometric auth for (optional - uses current user if not provided) .PARAMETER Confirm Skip confirmation prompt (default: false) .PARAMETER Vault Keeper vault instance (optional - will use global vault if not provided) .PARAMETER PassThru Return the result object. If not specified, function returns nothing. .EXAMPLE Unregister-KeeperBiometricCredential .EXAMPLE Unregister-KeeperBiometricCredential -Username "user@company.com" .EXAMPLE $result = Unregister-KeeperBiometricCredential -PassThru if ($result.Success) { Write-Host "Unregistration successful!" } #> [CmdletBinding(SupportsShouldProcess)] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidUsingPlainTextForPassword', 'CredentialId', Justification='Credential IDs are public identifiers, not sensitive data')] param( [Parameter(Mandatory=$false)] [string]$CredentialId, [Parameter(Mandatory=$false)] [string]$Username, [Parameter(Mandatory=$false)] [object]$Vault, [Parameter(Mandatory=$false)] [switch]$PassThru ) if (-not (Test-AssemblyAvailable)) { if ($PassThru) { return @{ Success = $false ErrorMessage = "KeeperBiometric assembly not available" ErrorType = "AssemblyNotFound" } } return } try { if (-not $Vault) { if (Get-Command getVault -ErrorAction SilentlyContinue) { $vault = getVault } elseif ($Script:Context.Vault) { $vault = $Script:Context.Vault } else { throw "No vault instance available. Please connect to Keeper first or provide a vault parameter." } } else { $vault = $Vault } $auth = $vault.Auth if (-not $Username) { $Username = $auth.Username } $credentialId = [KeeperBiometric.CredentialStorage]::GetCredentialId($Username) if (-not $credentialId) { $result = @{ Success = $true Message = "Biometric authentication is not registered for user: $Username" } if ($PassThru) { return $result } Write-Host $result.Message -ForegroundColor Yellow return } if (-not $PSCmdlet.ShouldProcess($Username, "Remove biometric authentication")) { $result = @{ Success = $false Message = "Operation cancelled by user" } if ($PassThru) { return $result } return } $task = [KeeperBiometric.PasskeyManager]::RemovePasskeyAsync($auth, $Username) $success = $task.GetAwaiter().GetResult() $result = if ($success) { Write-Host "Successfully unregistered biometric credentials for user: $Username" -ForegroundColor Green @{ Success = $true Message = "Biometric credentials unregistered successfully" Username = $Username } } else { @{ Success = $false Message = "Failed to unregister biometric credentials" Username = $Username } } if ($PassThru) { return $result } } catch { Write-Error "Failed to unregister biometric credentials: $($_.Exception.Message)" $errorResult = @{ Success = $false ErrorMessage = $_.Exception.Message ErrorType = $_.Exception.GetType().Name } if ($PassThru) { return $errorResult } } } function Show-KeeperBiometricCredentials { <# .SYNOPSIS Display biometric credentials in a formatted table .DESCRIPTION This function retrieves and displays all registered biometric credentials (passkeys) It shows credential name, creation date, and last used date. .PARAMETER Vault Keeper vault instance (optional - will use global vault if not provided) .PARAMETER IncludeDisabled Include disabled credentials in the results (default: false) .EXAMPLE Show-KeeperBiometricCredentials .EXAMPLE Show-KeeperBiometricCredentials -IncludeDisabled #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] [object]$Vault, [Parameter(Mandatory=$false)] [switch]$IncludeDisabled ) try { $credentials = Get-KeeperAvailableBiometricCredentials -Vault $Vault -IncludeDisabled:$IncludeDisabled if (-not $credentials -or $credentials.Count -eq 0) { Write-Host "No biometric authentication methods found." -ForegroundColor Yellow return } foreach ($credential in $credentials) { $createdDate = if ($credential.Created) { $credential.Created.ToString("yyyy-MM-dd HH:mm:ss") } else { "Never" } $lastUsedDate = if ($credential.LastUsed) { $credential.LastUsed.ToString("yyyy-MM-dd HH:mm:ss") } else { "Never" } $displayName = $credential.Name if ([string]::IsNullOrWhiteSpace($displayName)) { $aaguid = $credential.AAGUID if ($aaguid) { $displayName = Get-ProviderNameFromAAGUID -AAGUID $aaguid } else { $displayName = "Unknown Provider" } } # Determine status $status = if ($credential.Disabled) { "DISABLED" } else { "ACTIVE" } $statusColor = if ($credential.Disabled) { "Red" } else { "Green" } $credentialIdDisplay = $credential.CredentialId Write-Host "Id: $credentialIdDisplay" -ForegroundColor Cyan Write-Host "Name: $displayName" -ForegroundColor White Write-Host "Status: $status" -ForegroundColor $statusColor Write-Host "Created: $createdDate" -ForegroundColor Cyan Write-Host "Last Used: $lastUsedDate" -ForegroundColor Cyan Write-Host ("-" * 70) -ForegroundColor Gray } } catch { Write-Error "Failed to display biometric credentials: $($_.Exception.Message)" throw "Error displaying biometric credentials: $($_.Exception.Message)" } } function Register-KeeperBiometricCredential { <# .SYNOPSIS Complete Windows Hello credential creation flow for Keeper .DESCRIPTION This function performs the complete Windows Hello credential creation flow using PasskeyManager. .PARAMETER Vault Keeper vault instance (optional) .PARAMETER Force Force creation of new credential even if existing credentials are found .PARAMETER FriendlyName Friendly name for the credential (optional) .PARAMETER PassThru Return the registration result object. If not specified, function returns nothing on success or false on failure. .EXAMPLE Register-KeeperBiometricCredential .EXAMPLE Register-KeeperBiometricCredential -Force .EXAMPLE Register-KeeperBiometricCredential -FriendlyName "My Work Laptop" .EXAMPLE $result = Register-KeeperBiometricCredential -PassThru if ($result.Success) { Write-Host "Registration successful" } #> [CmdletBinding()] param( [Parameter(Mandatory=$false)] [object]$Vault, [Parameter(Mandatory=$false)] [switch]$Force, [Parameter(Mandatory=$false)] [string]$FriendlyName, [Parameter(Mandatory=$false)] [switch]$PassThru ) if (-not (Test-AssemblyAvailable)) { if ($PassThru) { return @{ Success = $false ErrorMessage = "KeeperBiometric assembly not available" ErrorType = "AssemblyNotFound" } } return $false } try { Write-Host "Biometric Credential Creation for Keeper" -ForegroundColor Yellow if (-not $Vault) { if (Get-Command getVault -ErrorAction SilentlyContinue) { $vault = getVault } elseif ($Script:Context.Vault) { $vault = $Script:Context.Vault } else { throw "No vault instance available. Please connect to Keeper first or provide a vault parameter." } } else { $vault = $Vault } $auth = $vault.Auth $task = [KeeperBiometric.PasskeyManager]::RegisterPasskeyAsync($auth, $FriendlyName, $Force.IsPresent) $result = $task.GetAwaiter().GetResult() if ($result.Success) { Write-Host "Credential created successfully" -ForegroundColor Green Write-Host "Success! Biometric authentication has been registered." -ForegroundColor Green Write-Host "Please register your device using the `"Set-KeeperDeviceSettings -Register`" command to set biometric authentication as your default login method." -ForegroundColor Yellow } else { Write-Warning "Registration failed: $($result.ErrorMessage)" } if ($PassThru) { return @{ Success = $result.Success ErrorMessage = $result.ErrorMessage Username = $result.Username CredentialId = $result.CredentialId } } return $result.Success } catch { Write-Error "Keeper credential creation failed: $($_.Exception.Message)" $errorResult = @{ Success = $false Error = $_.Exception.Message ErrorType = $_.Exception.GetType().Name Timestamp = [DateTime]::UtcNow } if ($PassThru) { return $errorResult } return $false } } function Get-WindowsHelloCredentialId { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Username ) try { return [KeeperBiometric.CredentialStorage]::GetCredentialId($Username) } catch { return $null } } function Test-WindowsHelloBiometricPreviouslyUsed { [CmdletBinding()] param( [Parameter(Mandatory = $true)] [string]$Username ) try { $credId = [KeeperBiometric.CredentialStorage]::GetCredentialId($Username) return (-not [string]::IsNullOrEmpty($credId)) } catch { return $false } } $exportFunctions = @( "Test-WindowsHelloCapabilities","Assert-KeeperBiometricCredential","Register-KeeperBiometricCredential","Show-KeeperBiometricCredentials","Unregister-KeeperBiometricCredential" ) Export-ModuleMember -Function $exportFunctions # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAMXZkOCSwLjG0P # LiLA+6pSi36GHRXrDMD9tjb5sbpCeKCCITswggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0GCSqG # SIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTlaMGkx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4 # RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQg # MjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C0Cit # eLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce2vnS # 1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0daE6ZM # swEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6TSXBC # Mo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoAFdE3 # /hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7OhD26j # q22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM1bL5 # OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z8ujo # 7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05huzU # tw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNYmtwm # KwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP/2NP # TLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATANBgkq # hkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95RysQDK # r2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HLIvda # qpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5BtfQ/g+ # lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnhOE7a # brs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIhdXNS # y0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV9zeK # iwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/jwVYb # KyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYHKi8Q # xAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmCXBVm # zGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l/aCn # HwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZWeE4w # gga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBH # NDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVT # MRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1 # c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphBcr48RsAcrHXbo0Zo # dLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi # 6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNg # xVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEdgkFiDNYiOTx4OtiF # cMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjUjsZvkgFkriK9tUKJ # m/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bRVFLeGkuAhHiGPMvS # GmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1 # ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9 # MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL6s36czwzsucuoKs7 # Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bG # RinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6 # X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAd # BgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJx # XWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUF # BwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGln # aWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJo # dHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNy # bDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEL # BQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxj # aaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQE7jU/kXjjytJgnn0 # hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9rEVKChHyfpzee5kH0 # F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y1IsA0QF8dTXqvcnT # mpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKf # ZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3ty9qIijanrUR3anzE # wlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbh # OhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEBYTptMSbhdhGQDpOX # gpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud/v4+7RWsWCiKi9EO # LLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wG # WqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZPubdcMIIG7TCCBNWg # AwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0 # IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0Ex # MB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzELMAkGA1UEBhMCVVMx # FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEy # NTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUgMTCCAiIwDQYJKoZI # hvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMrV7pvUf+GcAoB38o3 # zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8dE2/pPvOx/Vj8Tch # TySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7MrxVyfQO9sMx6ZAWj # FDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZZREr4h/GI6Dxb2Uo # yrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFOnHoRh6+86Ltc5zjP # KHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+nigNJFmt6LAHvH3KS # uNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeItK/DhKbPxTTuGoX7w # JNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1zBp+xUIZkpSFA8vW # doUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk8iyyizNDIXj//cOg # rY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsWeupWs7NpChUk555K # 096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAkprxMiXAJQ1XCmnCf # gPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTkO/zy # Me39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezL # TjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsG # AQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5j # b20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdp # Q2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNy # dDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGln # aUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5j # cmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEB # CwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWLpQq1b4URGnwWBdEZ # D9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgjg8K8elC4+oWCqnU/ # ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3QYIUP2S3HQvHG1FDu # +WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5bdrPbF6MRYs03h4o # bEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUGtMTaiLR9wjxUxu2h # ECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNEsuEB7O7/cuvTQasn # M9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6UArb+BOVAkg2oOvol # /DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG0LIhp6GvReQGgMgY # xQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWVFjF7mcr4C34Mj3oc # CVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5t2nGj/ULLi49xTcB # ZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjsarfNZzCCB0kwggUx # oAMCAQICEAe0P3SLJmcoVNrErUyxTt0wDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB # MTAeFw0yNTEyMzEwMDAwMDBaFw0yOTAxMDIyMzU5NTlaMIHRMRMwEQYLKwYBBAGC # NzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMR0wGwYDVQQPDBRQ # cml2YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQwNzk4NTELMAkGA1UEBhMC # VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK # ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5 # IEluYy4wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCUcNMoSVmxAi0a # vG+StFJMNFFTUIOo3HdBZ+0gqA1XpNgUx11vB1vCZrvFsD9m5oA58tdp4gZN3LmQ # aMvCl2ANUT7MilI02Hf1RWlygBzon6iE0GpU3lgRrwrk1dhtLpGsR6dbMKUUHprc # vKpXk90/VN+vhzY1uik1tCTxkDCPu/AYJg7m9+tR2KqvMuYMaMLhii66eWUAGsBC # h/uZxjkGoJF6qZ0DgFd7rW7VYljbfYSNPeZNGTDgB0J/wOsKl0mn612DTseIvAKt # 4vra/FLFukyEyStnfQ8lWYDcLLCMCjNVrzGipmT5E2iyx7Y1RZCIpNwVogp3Ixbk # Gbq5A/41YNOLLd4cFewyB2F037RevBCRsUODZEt1qBf7Jbu3DiYo1G+zTj9E0R1s # FzyijcfdsTm6X5ble+yCJeGkX5XgsyPnZpyz/FX9Fr0N9pMPGWwW2PKyHEnSytXm # 0Dxdq2P4mA4CBUxq7YoV26L2PF6QEh9BQdXTPcnLysUv7SI/a0ECAwEAAaOCAgIw # ggH+MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBRG # 4H6CH8pvNX632bsdnrda4MtJLDA9BgNVHSAENjA0MDIGBWeBDAEDMCkwJwYIKwYB # BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC # B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p # bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT # QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA # A4ICAQA1Wlq0WzJa3N6DgjgBU7nagIJBab1prPARXZreX1MOv9VjnS5o0CrfQLr6 # z3bmWHw7xT8dt6bcSwRixqvPJtv4q8Rvo80O3eUMvMxQzqmi7z1zf+HG+/3G4F+2 # IYegvPc8Ui151XCV9rjA8tvFWRLRMX0ZRxY1zfT027HMw0iYL20z44+Cky//FAnL # iRwoNDGiRkZiHbB9YOftPAYNMG3gm1z3zOW5RdfKPrqvMuijE+dfyLIAA6Immpzu # FMH+Wgn8NnSlot9b4YKycaqqdjd7wXDjPub/oQ7VShuCSBWj+UNOTVh0vcZGackc # H1DLVgwp2dcKlxJiQKtkHT/T6LloY6LTe6+8wkVkr8EAv1W+q/+M1a4Ao+ykFbIA # 2LBEmA9qdgoLtenAYIiEg+48SjMPgyBbVPE3bhL1vIqjEIxYCfdmi6wx33oYX7HB # +bJ7zitHw4GgtpfPV8y8QRZImKmeDOKyXjQPDmQM/Eglm/Ns0GzBkVXM8h6UI34b # WZrHz9sbLSE20m5Svmxftvw5zju+I3WsmS/stNfWlOkwU0niUgwPHaz21kjXEA5A # g+aqv26wodqZcnGOlChoWDvSJ8KKgdOFbeAYKAMp1NY7iWV315zpGH19RipCR1NH # 0ND8iIubk3WGNf2rzEfqlOi3h2ywqVkU6AKXHdO5JV4otSKKEDGCBdkwggXVAgEB # MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD # VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI # QTM4NCAyMDIxIENBMQIQB7Q/dIsmZyhU2sStTLFO3TANBglghkgBZQMEAgEFAKCB # hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ # BDEiBCAAmwzirLYUn/KqU7H0W9ae3zJcKaX4hCCRIbPyxk+xBzANBgkqhkiG9w0B # AQEFAASCAYAWtU6d0JA5DxZT59BTzgOYT9vrHeOCtzarfTsAShVoqHfXSLFSv87R # Fb6KwuUypDqDXfOZ7I3+jJ8xewxQfFPGMucSEgSqqb2yQk01JbjSI03eLVg5xap9 # hxDk9ho1sO2IsUQSgqdGbBiB4W/4Jd7XVbZHAOm/sZa0qy9ZTzuIUxCIkyd8M+/g # SfEFomvh3PaLRYJPXMek4E4gTA5KfLqtqVmT2w12Z2oAoFOwQCeWJ3T26TA36K1y # 3qb+1YE2cK6IOALaXro2GBHpd9rlwRXNp7twToCEVdPkvHZtMYVNFsHYKxLYFpfH # l+Kts1Nkkxh95oBo4VmLV7FRDYcMJm10Ek17dsj1QozY/dYG96N70o7YXxk1eTtx # 4GdTjy7wHv/O1amZ8EJcADs9EwvC3Eb3348ZKVFhSsT4AZUHix4iubcBZufH423G # yj8O9NDEz8yREMK6FYP1Dv2O/EtGuM4vgkmI7LdQkCyQxOzC53ZaaeC312pjS/8K # FPkYB+cYqRChggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg # Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwMTIzMjMzOTI0WjAv # BgkqhkiG9w0BCQQxIgQgg/qYJezzeej/QXfhhYWvfTZUsKXST3VpounQBtVQSLYw # DQYJKoZIhvcNAQEBBQAEggIAq8hOo78hPkbY9MOMUFzwe6v8ikpBRNz+htEkEuPP # 89McjAuRN8Dn/EZuv6qtnqSL4ZfbgmTadVfS9uq8PqNW2PlDNhC1DAGBhevwh6TM # ZuGd2WsHYZ00hlH3xC4JhUGh211/6201zghfVYgLJHGk47+pHxTvgm7HwUnH1a+a # 0aVvPO83E4qYK/nRnUqrgw2C7lLEwl6vDqV/7MeTH8XUntvFBOZLvynWsTdnNeAf # qNF3ffnf0jEAL1003Q6lvcYYRZOvqTs4amzv56SDNOfVVsCEFkCmB0gFwH51smXW # htattH2L0C1jOExIIeo38NJkceiTWJ7/uJvZSm8Q2vOA9/DolfbzOtLuFqWVaf6X # 1rSZNO2V3OJ3w4kg3ZCL1B/H8GeZvRZmC94ZBbhZGdYIj+p3yzO3ZWJCChVKMAcK # IBW+LEFwJYyDYuKacdEaaVZmTNLC7BpVFQJOgVojo/SD9nNkIbFSeTNdPdq9bofQ # Vmd1mC79fUNo/KhN9oBmNw037y4tEcSw4MPW2tGzOfwFiYWFCk9chu60xnbjusR4 # 99WV5LUs27fapMSS41a5KdgNgrnsR9NNraTOo/17J+n2dYl6xHdsBqNhCPtio3Sd # dV7FfrFrJBG118M6qDukcx5KBE+LhzoBv10f+UQDV1KeG101xgqFw0A5WdbUhgLF # AO8= # SIG # End signature block |