ConfigureWVDSSO.ps1

<#PSScriptInfo
 
.VERSION 1.0.0.5
 
.GUID f093f861-273c-4436-a189-efe0ccd5bc2b
 
.AUTHOR Microsoft Corporation
 
.COMPANYNAME Microsoft Corporation
 
.COPYRIGHT (c) 2021 Microsoft Corporation. All rights reserved.
 
.TAGS
 
.LICENSEURI http://www.microsoftvolumelicensing.com/DocumentSearch.aspx?Mode=3&DocumentTypeId=31
 
.PROJECTURI https://docs.microsoft.com/azure/virtual-desktop/configure-adfs-sso
 
.ICONURI
 
.EXTERNALMODULEDEPENDENCIES ADFS
 
.REQUIREDSCRIPTS
 
.EXTERNALSCRIPTDEPENDENCIES
 
.RELEASENOTES
 
 
#>


<#
.SYNOPSIS
Configures ADFS for a SSO with WVD deployment
 
.DESCRIPTION
This script creates creates a Relying Party and two ADFS clients. The Relying Party is used for issueing claims that can be used for
cerificate logon.
The first client is created with ADFSClientId parameter as it's client Id which allows the
RD client to get a token directly from ADFS. Later this token is exchanged using the second ADFS client with
ResourceBaseAddress parameter as its ClientId. The second ADFS client is also confidential client, the secret generated
for that client is used by the Rds deploymenent to sign a user certificate in exchange to the token acqiured from the first client.
 
.PARAMETER WvdClientAppApplicationID
Required
Guid of the Resource's ClientId
 
.PARAMETER WvdWebAppAppIDUri
Required
The URL for which the RDS deployment is configured. This equals IdentityAuthority setting in the globalSettings table
 
.PARAMETER RelyingPartyClientName
Optional
Name of the Relying Part
 
.PARAMETER ADFSAuthority
Required
The URL where ADFS can be found. Usually it has the form of "https://<FQDN of ADFS>/adfs"
 
.PARAMETER RdWebURL
Required
The URL that points to RD web service. This Url is used to add the appropriate RedirectUri to the clients
 
.EXAMPLE
 
.\ConfigureWVDSSO.ps1 -WvdWebAppAppIDUri "https://www.wvd.microsoft.com" -WvdClientAppApplicationID "a85cf173-4192-42f8-81fa-777a763e6e2c" -RelyingPartyClientName "Windows Virtual Desktop ADFS Logon SSO" -ADFSAuthority "https://sts.contoso.com/adfs" -RdWebURL "https://rdweb.wvd.microsoft.com"
#>


#Requires -Version 4.0

Param(
    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $WvdWebAppAppIDUri="https://www.wvd.microsoft.com",

    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $WvdClientAppApplicationID="a85cf173-4192-42f8-81fa-777a763e6e2c",

    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $RelyingPartyClientName="Windows Virtual Desktop ADFS Logon SSO",

    [Parameter(Mandatory=$True)]
    [ValidateNotNullOrEmpty()]
    [string] $ADFSAuthority,

    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $RdWebURL="https://rdweb.wvd.microsoft.com",

    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $RdWebAliasURL,    

    [Parameter ()]
    [switch] $UseCert,
    
    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $CertPath,

    [Parameter(Mandatory=$False)]
    [ValidateNotNullOrEmpty()]
    [string] $CertPassword
)

# Windows (Desktop) Microsoft Remote Desktop client, with the Client App ID
$OOBRedirectUri = "ms-appx-web://Microsoft.AAD.BrokerPlugin/$($WvdClientAppApplicationID)"

# Microsoft Remote Desktop Preview application in Microsoft Store
$URDCPreviewRedirectUri = "ms-appx-web://Microsoft.AAD.BrokerPlugin/S-1-15-2-1675141024-1003855329-915403128-1652244152-3721706001-1815751561-448073730"

# Microsoft Remote Desktop application in Microsoft Store
$URDCReleaseRedirectUri = "ms-appx-web://Microsoft.AAD.BrokerPlugIn/S-1-15-2-1351064824-1747509879-3343020337-936898558-3022384538-2153969374-721259951"

# Calculate ring rdweb URLs
$RdWebURLR0 = $RdWebURL.Replace("rdweb", "rdweb-r0")
$RdWebURLR1 = $RdWebURL.Replace("rdweb", "rdweb-r1")

# Calculate the rdweb alias URI
if ($RdWebAliasURL.Length -ne 0)
{
    $RdWebWWWUri = $RdWebAliasURL
}
else
{
    $RdWebWWWUri = $RdWebURL.Replace("rdweb", "www")
}

# Calculate afd webclient URI. Example: client.wvd.microsoft.com
$RdWebClientUri = $RdWebURL.Replace("rdweb", "client")

# Windows Virtual Desktop web client
$webClientRedirectUri = $RdWebURL + "/webclient/sso.html"
$webClientRedirectUriR0 = $RdWebURLR0 + "/webclient/sso.html"
$webClientRedirectUriR1 = $RdWebURLR1 + "/webclient/sso.html"
$webClientWWWRedirectUri = $RdWebWWWUri + "/webclient/sso.html"
$webClientARMRedirectUri =  $RdWebURL + "/arm/webclient/sso.html"
$webClientARMRedirectUriR0 = $RdWebURLR0 + "/arm/webclient/sso.html"
$webClientARMRedirectUriR1 = $RdWebURLR1 + "/arm/webclient/sso.html"
$webClientARMWWWRedirectUri = $RdWebWWWUri + "/arm/webclient/sso.html"
$webClientAFDRedirectUri = $RdWebClientUri + "/webclient/sso.html"
$webClientARMAFDRedirectUri = $RdWebClientUri + "/arm/webclient/sso.html"

$ADFSRedirectUri = @($WvdWebAppAppIDUri, $OOBRedirectUri, $URDCPreviewRedirectUri, $URDCReleaseRedirectUri, $webClientRedirectUri, $webClientRedirectUriR0, $webClientRedirectUriR1, $webClientWWWRedirectUri, $webClientARMRedirectUri, $webClientARMRedirectUriR0, $webClientARMRedirectUriR1, $webClientARMWWWRedirectUri, $webClientAFDRedirectUri, $webClientARMAFDRedirectUri)
$SSORedirectUri = @(($RdWebURL + "/sso"), ($RdWebURLR0 + "/sso"), ($RdWebURLR1 + "/sso"))

if($UseCert.IsPresent)
{
    $flags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
    try{
        $cer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertPath, $CertPassword, $flags)
    }
    catch {
        Write-Error "Error importing certificate: $_"
        Write-Error "Exiting script!"
        $error_object = New-Object -TypeName PSObject
        return $error_object
    }
}
# first add Implicit UPN claim if none exists
$impupnclaim = Get-AdfsClaimDescription -ClaimType http://schemas.xmlsoap.org/ws/2005/05/identity/claims/implicitupn
if ($impupnclaim -eq $null)
{
    Write-Host "Adding Implicit UPN claim"
    Add-AdfsClaimDescription -Name "Implicit UPN" -ClaimType "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/implicitupn" -ShortName "Implicit UPN" -IsAccepted $true -IsOffered $true
    Write-Host "Implicit UPN claim added"
} else {
    Write-Host "Implicit UPN claim already exists"
}

Write-Host "Adding RP trust for SSO"
$rp = Get-AdfsRelyingPartyTrust -Identifier $WvdWebAppAppIDUri
if ($rp -eq $null)
{
    Write-Host "Adding Relying Party Trust"
    Add-AdfsRelyingPartyTrust -Name $RelyingPartyClientName -Identifier $WvdWebAppAppIDUri -IssuanceAuthorizationRules '=> issue(Type = "http://schemas.microsoft.com/authorization/claims/permit", Value = "true");' -IssuanceTransformRules '@RuleTemplate = "PassThroughClaims"
                                       @RuleName = "UPN"
                                       c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
                                        => issue(claim = c);
                                       @RuleTemplate = "PassThroughClaims"
                                       @RuleName = "Group"
                                       c:[Type == "http://schemas.xmlsoap.org/claims/Group"]
                                        => issue(claim = c);
                                       @RuleTemplate = "PassThroughClaims"
                                       @RuleName = "Primary SID"
                                       c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid"]
                                        => issue(claim = c);
                                       @RuleName = "Anchor Claim Type"
                                       EXISTS([Type ==
                                       "http://schemas.microsoft.com/ws/2014/01/identity/claims/anchorclaimtype"])
                                        => issue(Type =
                                       "http://schemas.microsoft.com/ws/2014/01/identity/claims/anchorclaimtype",
                                       Value = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn");
                                       @RuleTemplate = "PassThroughClaims"
                                       @RuleName = "Implicit UPN"
                                       c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/implicitupn"]
                                        => issue(claim = c);
                                       @RuleTemplate = "PassThroughClaims"
                                       @RuleName = "Windows Account Name"
                                       c:[Type == "http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname"]
                                       => issue(claim = c);
                                       '
 
    Write-Host "Relying Party Trust added"
} else {
    Write-Host "Relying Party Trust already exists"
    if (-not $rp.IssuanceTransformRules.Contains("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn") `
        -or -not $rp.IssuanceTransformRules.Contains("http://schemas.xmlsoap.org/claims/Group") `
        -or -not $rp.IssuanceTransformRules.Contains("http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid") `
        -or -not $rp.IssuanceTransformRules.Contains("http://schemas.microsoft.com/ws/2014/01/identity/claims/anchorclaimtype")`
        -or -not $rp.IssuanceTransformRules.Contains("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/implicitupn"))
    {
        Write-Error "Relying Party with Identifier $WvdWebAppAppIDUri has missing claims. Delete it and run this script again"
        $error_object = New-Object -TypeName PSObject
        return $error_object
    }
}

$adfsClient = Get-AdfsClient -ClientId $WvdClientAppApplicationID
if ($adfsClient -eq $null)
{
    Write-Host "Adding ADFS client with ClientId: $WvdClientAppApplicationID"
    Add-AdfsClient -ClientId $WvdClientAppApplicationID -Name $RelyingPartyClientName -RedirectUri $ADFSRedirectUri
    Write-Host "ADFS client added"
} else {
    Write-Host "ADFS client with ClientId: $WvdClientAppApplicationID already exists"
}

$perm = Get-AdfsApplicationPermission|where-object {$_.ClientRoleIdentifier -eq $WvdClientAppApplicationID -and $_.ServerRoleIdentifier -eq $WvdWebAppAppIDUri -and $_.ScopeNames[0] -eq "openid" -and $_.ScopeNames[1] -eq "logon_cert"}
if ($perm -eq $null)
{
    Write-Host "Granting logon_cert access to $WvdClientAppApplicationID"
    Grant-AdfsApplicationPermission -ServerRoleIdentifier $WvdWebAppAppIDUri -ClientRoleIdentifier $WvdClientAppApplicationID -ScopeNames "openid","logon_cert"
    Write-Host "Access granted"
} else {
    Write-Host "Access logon_cert already granted for $WvdClientAppApplicationID"
}

$adfsClient = Get-AdfsClient -ClientId $WvdWebAppAppIDUri
if ($adfsClient -eq $null)
{
    if ($UseCert.IsPresent)
    {
        Write-Host "Adding confidential ADFS client (with JWT signing certificate authentication) with ClientId: $WvdWebAppAppIDUri"
        $adfsClient = Add-AdfsClient -ClientId $WvdWebAppAppIDUri -Name ($RelyingPartyClientName + " SSO")  -RedirectUri $SSORedirectUri -ClientType Confidential -JWTSigningCertificate $cer -JWTSigningCertificateRevocationCheck CheckChainExcludeRoot
        Write-Host "ADFS client added"
    }
    else
    {
        Write-Host "Adding confidential ADFS client (with shared client secret authentication) with ClientId: $WvdWebAppAppIDUri"
        $adfsClient = Add-AdfsClient -ClientId $WvdWebAppAppIDUri -Name ($RelyingPartyClientName + " SSO")  -RedirectUri $SSORedirectUri -ClientType Confidential -GenerateClientSecret 
        Write-Host "ADFS client added"
    }
} 
else {
    Write-Host "Confidential ADFS client with ClientId: $WvdWebAppAppIDUri already exists"
}
$clientSecret = $null
$perm = Get-AdfsApplicationPermission|where-object {$_.ClientRoleIdentifier -eq $WvdWebAppAppIDUri -and $_.ServerRoleIdentifier -eq $WvdWebAppAppIDUri -and $_.ScopeNames[0] -eq "openid" -and $_.ScopeNames[1] -eq "logon_cert"}
if ($perm -eq $null)
{
    Write-Host "Granting logon_cert access to $WvdWebAppAppIDUri"
    Grant-AdfsApplicationPermission -ServerRoleIdentifier $WvdWebAppAppIDUri -ClientRoleIdentifier $WvdWebAppAppIDUri -ScopeNames "openid","logon_cert"
    
    if (!($UseCert.IsPresent))
    {
        $clientSecret = $adfsClient.ClientSecret
    }
    Write-Host "Access granted"
} else {
    Write-Host "Access logon_cert already granted for $WvdWebAppAppIDUri"
    if (!($UseCert.IsPresent))
    {
        Write-Error "Can't get secret for ADFS client. Need to recreate it using UnConfigureWVDSSO.ps1"
        $error_object = New-Object -TypeName PSObject
        return $error_object
    }
}

$return_object = New-Object -TypeName PSObject
if ($clientSecret -ne $null)
{
    Add-Member -InputObject $return_object -NotePropertyName SSOClientSecret -NotePropertyValue $clientSecret | Out-Null

    Write-Warning "Ready to execute: Update-AzWvdHostPool -Name <Host Pool Name> -ResourceGroupName <Host Pool Resource Group Name> -SsoadfsAuthority $ADFSAuthority -SsoClientId $WvdWebAppAppIDUri -SsoSecretType SharedKeyInKeyVault -SsoClientSecretKeyVaultPath <KeyVault secret URL>"
} else {
    Add-Member -InputObject $return_object -NotePropertyName SSOCertPath -NotePropertyValue $CertPath | Out-Null

    Write-Warning "Ready to execute: Update-AzWvdHostPool -Name <Host Pool Name> -ResourceGroupName <Host Pool Resource Group Name> -SsoadfsAuthority $ADFSAuthority -SsoClientId $WvdWebAppAppIDUri -SsoSecretType CertificateInKeyVault -SsoClientSecretKeyVaultPath <KeyVault secret URL>"
}
Write-Warning "Also remember to set tag on the secret in Azure Key Vault to the subscription id of the host pool"

Add-Member -InputObject $return_object -NotePropertyName SSOADFSAuthority -NotePropertyValue $ADFSAuthority  | Out-Null
Add-Member -InputObject $return_object -NotePropertyName SSOClientId -NotePropertyValue $WvdWebAppAppIDUri | Out-Null

return $return_object
# SIG # Begin signature block
# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCyV9BISOFgB9r+
# FniKyPxqCMYWus83jAqLPGsDr0mqpaCCDYEwggX/MIID56ADAgECAhMzAAAB32vw
# LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn
# s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw
# PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS
# yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG
# 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh
# EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH
# tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS
# 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp
# TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok
# t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4
# b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao
# mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD
# Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt
# VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G
# CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+
# Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82
# oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN
# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgCvGgUnKI
# wdQXXC1ajtzPITeBzgLNAArOQT5yvsnnedkwQgYKKwYBBAGCNwIBDDE0MDKgFIAS
# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN
# BgkqhkiG9w0BAQEFAASCAQB/Sz0UAqeqABqRzvb56puIUdUErCQaQ1r1snKWt4nk
# zipCyJ6j6kHVhoOJAd+/wcuOtJJ9hqzoNXFoKdhTITnpQEecE0LSQH/oouhg6OO6
# lJkNhiMqYYVPV+EtqvnFrsuMGiH+CqqG5BQM938Y2Oex4w5uP+vNH+MQbkdl3o8O
# QHVN/jEYEsf5EJIJwXZWQQgUhGMdN64ZXgY8P/o0ZSo+J1jfgpuqFtfvxXTW2fqS
# OLN319zmI/z2XBp0DjjmUaH1rXMYgIAjA8Vi74xkFqIcEoW1CYeTSTLlCg9Lw6Yz
# 2n3qyTsmwvKyO9DeaW+7+zpaPWEI5M7LhKE56aY0/ebYoYIS8TCCEu0GCisGAQQB
# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME
# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIGhX55KXqx6zM5GRzhrslaubjiUjTL57j264/kjR
# qEGcAgZgieYt8REYEzIwMjEwNTI2MTEwNzQ3LjU0N1owBIACAfSggdSkgdEwgc4x
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p
# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg
# VFNTIEVTTjo4OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt
# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABYAcg8JJI2r7rAAAA
# AAFgMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo
# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y
# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw
# MB4XDTIxMDExNDE5MDIyMFoXDTIyMDQxMTE5MDIyMFowgc4xCzAJBgNVBAYTAlVT
# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK
# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy
# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4OTdB
# LUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj
# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALQxgBzQ8sbM8VIzPgmN
# 9TWkWs2k8S1CZUyN58SLzLW3158rZZ/O4l68Gb4SkE/iBUSk4yVesBKOVonbLL3n
# AwIXmsYhn5BR0RXPI9XLizzVqslUgHPzTPRtMtZu+jtygCph5MfquBia/Sp0Hjj7
# lEDrNHR/C/1/xCaZyQpgIR7lARJIaRVh5y+WqbqmbUsFIoF5UHwwzJFJpOXxYtA0
# uWuraKpdCUzAzh6wkKZgMkRrXP3E/w5cs4U7cYiYjZRGlm3Gr+vJgkyTsKW3OBiT
# tkaW/ejtpW+S6pu/NezXFqAzwuSDjeIImOOeEavlA9O9VpCexo1z6VCpp0Eb3904
# uhMCAwEAAaOCARswggEXMB0GA1UdDgQWBBSnmx74t6gX4JrhdTIJdXzQ674fYzAf
# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH
# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU
# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF
# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0
# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG
# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAVNJgImzoFpolQdU8nYdcX7ySlKEP3
# 59qg5Q/SFp7e4j5dA1V+ksvmKdZB1jhjsBs2ImNOItyNY2u2Nwja4JNxFf+2y/KT
# cP8GE97H8ogr9c+Vb1uHF7gtne3F8vnv0Cb79K4mbhi6S0QIhqPcI8c36UqePwKM
# Lm32J+vO9wuKW2NK8v9bA5gQu92Aj6uf7AGAuNYHIh0iZok+bcuJpYPKKY1nvS2E
# a1gzmF8J/irPY4MW8v4/gaekJosfkc6gboma4w1gFDGKgKhv7tlT1+53uHGR2TS1
# qMYeNwGfGtjiTxQ1BlQEZibk7PFfCe+KRhTRTCRtzWnSg3NEE0y2qLnUMIIGcTCC
# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv
# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN
# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv
# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0
# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw
# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0
# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw
# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe
# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx
# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G
# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA
# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7
# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g
# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93
# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB
# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA
# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh
# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS
# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK
# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon
# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi
# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/
# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII
# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0
# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a
# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ
# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+
# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4
# OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZaIjCgEBMAcGBSsOAwIaAxUA+zKSdHWLRNzIlyqHVxXZgKnZ0KKggYMwgYCk
# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD
# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF
# AORYpS0wIhgPMjAyMTA1MjYxNDQ1MDFaGA8yMDIxMDUyNzE0NDUwMVowdzA9Bgor
# BgEEAYRZCgQBMS8wLTAKAgUA5FilLQIBADAKAgEAAgIMMgIB/zAHAgEAAgIRWjAK
# AgUA5Fn2rQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB
# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBADD0Rrbkheyx4Eji
# 6Sb2EJXwRZ/i3SgXLYvzNh9sN02Pmpi7FPSYlWvD0vfJSaK9HVVV7kN2Vcung1pI
# 7lKzV+mdjWhuMwYFR4X1M3FQLCpww62fd65YbsBXcT4c0A2+44vOYHbXhAHE3HRy
# pBezjiWHYxnzHTtxudv/Mb6jylwdMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTACEzMAAAFgByDwkkjavusAAAAAAWAwDQYJYIZIAWUD
# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B
# CQQxIgQgP0KxPRXHsa+9nY8eNjP00EaO+cg6UV84HRhbu2SQ7j0wgfoGCyqGSIb3
# DQEJEAIvMYHqMIHnMIHkMIG9BCACEqO9o3Mul1tFp68e6ivKfpXS/49cFwniXkj3
# qLBluTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u
# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp
# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB
# YAcg8JJI2r7rAAAAAAFgMCIEIKoe5iM14kqM6a4JI38l8NeVs1dKjHCqlbaJD1NC
# uHxsMA0GCSqGSIb3DQEBCwUABIIBAD9tdimfjyloLk11Qi+XmWYSC2cKfTjEbd7E
# /MJEKdHJxSIQgJYRMjphwYov4xYkXh6thZihid44qITggq9MFrTyBoeBNG5qIKxp
# S9lFR9FaSspMUyi21eAz1/7E8voxjjV/BbWg8Vbll9Q0vfh4LarGaifpC4DrfDIp
# NS/kbw+kZ5tTipzxqs/IQMYHdwdHrqOnQsS9l1IyUNCFr0jzocKME+CGjbKdUYUz
# Z/6vzNF/6rLExlRFy18BuqpSljZIrKVEofoRcPkG1SBe8k+HjFlWcFXIfE6/QMyT
# 7OBJizVTs845Ymf9CE5TuYC5gaPWU4B78ivzThaKUAZfu5vWEPE=
# SIG # End signature block