(C) Ondrej Sevecek, 2019 - www.sevecek.com, ondrej@sevecek.com
$libDir = Split-Path -parent $MyInvocation.MyCommand.Definition & "$libDir\lib-common.ps1" -defaultConfig -rootDir $libDir -outFile adfsLib & "$libDir\lib-modifyActions.ps1" & "$libDir\lib-buildup.ps1" $vmName = $args[0] DBG ('ADFS installation library') Redirect-TempToOutput Load-VMConfig Find-MarkedVolumes $appTag = 'adfs' $appConfig = $vmConfig.$appTag $firstAppHost = Get-FirstAppHostInInstance $appTag $appConfig.instance 'waitParams' $firstAppConfig = (Get-FirstAppHostInInstance $appTag $appConfig.instance 'vmConfig').$appTag $firstAppHostInInstance = Check-FirstAppHostInInstance $appTag $appConfig.instance $allAppConfigs = Get-AllAppHostsOfInstance $appTag $appConfig.instance 'svcConfig' if (Is-Null $appConfig) { DBG ('Invalid appCofnig, exiting'); exit } #==================== #==================== function global:Build-PrimaryADFS ( [string] $organizationDNS = 'gopas.cz', [string] $identifier = 'adfs.gopas.cz', [string] $deviceRegDomain = 'gopas.cz', [string] $thumbprint = '0bca5ac6e79a3b4fee22e4b44d593fbabb1c11c7', [string] $companyDisplay = 'GOPAS a.s.', [string] $gmsa = 'gps\svc-adfs$', [int] $extranetLockout = -1, [string] $extranetObservation = '00:17:00', [bool] $enableKmsi, [string] $extendedProtection, [pscredential] $svcCred # Note: obtain this with our own Get-PwdCredentials($samLogin = $true) ) { DBG ('{0}: Parameters: {1}' -f $MyInvocation.MyCommand.Name, (($PSBoundParameters.Keys | ? { $_ -ne 'object' } | % { '{0}={1}' -f $_, $PSBoundParameters[$_]}) -join $multivalueSeparator)) $adfsRes = $null if (Is-ValidString $gmsa) { DBG ('Install ADFS farm with GMSA: {0} | {1} | {2} | {3} | {4}' -f $organizationDNS, $identifier, $thumbprint, $companyDisplay, $gmsa) DBGSTART $adfsRes = Install-AdfsFarm -CertificateThumbprint $thumbprint -FederationServiceName $identifier -FederationServiceDisplayName $companyDisplay -GroupServiceAccountIdentifier $gmsa # Note: we could also make use of the following: # -DecryptionCertificateThumbprint # -SigningCertificateThumbprint DBGER $MyInvocation.MyCommand.Name $error DBGEND # Note: the GMSA account must be member of WAAG imperatively # but as it is not created during the DC setup, we must add it # to the group manually now # Note: the question is about which domain, but the answer is for # now just the machine domain, because the machine domain is # actually the domain which gets the GMSA created in [string] $waagPath = "WinNT://$global:thisComputerDomainNetBIOS/Windows Authorization Access Group" DBG ('Get the WAAG group: {0}' -f $waagPath) DBGSTART $waag = [ADSI] $waagPath DBGER $MyInvocation.MyCommand.Name $error DBGEND DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $waag.Guid } DBGIF $MyInvocation.MyCommand.Name { (New-Object System.Security.Principal.SecurityIdentifier $waag.objectSID.Value, 0).Value -ne 'S-1-5-32-560' } DBG ('Normalize GMSA account to be usable in WinNT:// ADSI path: {0}' -f $gmsa) DBGIF $MyInvocation.MyCommand.Name { $gmsa -notmatch '\A[^\\]+\\[^\\]+\Z' } [string] $gmsaPathId = $gmsa.Replace('\', '/') DBG ('Add the GMSA into WAAG group in the machine domain: {0}' -f $gmsaPathId) DBGSTART $waag.Add("WinNT://$gmsaPathId,user") DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ('Restart the ADFS service to update its group membership') DBGSTART Restart-Service adfssrv DBGER $MyInvocation.MyCommand.Name $error DBGEND } else { DBGIF ('Install ADFS farm with normal SVC account: {0} | {1} | {2} | {3} | {4}' -f $organizationDNS, $identifier, $thumbprint, $companyDisplay, $svcCred.UserName) { $true } DBGSTART $adfsRes = Install-AdfsFarm -CertificateThumbprint $thumbprint -FederationServiceName $identifier -FederationServiceDisplayName $companyDisplay -ServiceAccountCredential $svcCred DBGER $MyInvocation.MyCommand.Name $error DBGEND } DBG ('ADFS install status: {0} | {1} | {2}' -f $adfsRes.Status, $adfsRes.Message, $adfsRes.Context) DBGIF $MyInvocation.MyCommand.Name { $adfsRes.Status -ne 'Success' } $organizationDNS = $organizationDNS.ToLower() $identifier = $identifier.ToLower() DBG ('Verify the SSL certificates') DBGSTART $sslCerts = Get-AdfsSslCertificate DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ('ADFS TLS certificates: {0}' -f ($sslCerts | Out-String)) # Note: the EnterpriseRegistration automatically bound on Windows 2016 DBGIF ("Weird number of ADFS certificates: {0}" -f $sslCerts.Count) { (($global:thisOSVersionNumber -lt 10) -and ($sslCerts.Count -ne 3)) -or (($global:thisOSVersionNumber -ge 10) -and ($sslCerts.Count -ne 4)) } [string] $uniqueCertThumbprint = [string]::Empty foreach ($oneSslCert in $sslCerts) { DBGIF ('Weird ADFS TLS certificate: {0} | {1} | {2}' -f $oneSslCert.HostName, $oneSslCert.CertificateHash, $oneSslCert.PortNumber) { $oneSslCert.CertificateHash -ne $thumbprint } DBGIF ('Weird ADFS TLS certificate: {0} | {1} | {2}' -f $oneSslCert.HostName, $oneSslCert.CertificateHash, $oneSslCert.PortNumber) { ($oneSslCert.Hostname -ne 'localhost') -and ($oneSslCert.Hostname -ne ('EnterpriseRegistration.{0}' -f $deviceRegDomain)) -and ($oneSslCert.Hostname -ne $identifier) -and ($oneSslCert.Hostname -ne ('certauth.{0}' -f $identifier)) } DBGIF ('Weird ADFS TLS certificate: {0} | {1} | {2}' -f $oneSslCert.HostName, $oneSslCert.CertificateHash, $oneSslCert.PortNumber) { ($global:thisOSVersionNumber -lt 10) -and ($oneSslCert.Hostname -eq ('EnterpriseRegistration.{0}' -f $deviceRegDomain)) } DBGIF ('Weird ADFS TLS certificate: {0} | {1} | {2}' -f $oneSslCert.HostName, $oneSslCert.CertificateHash, $oneSslCert.PortNumber) { ($oneSslCert.PortNumber -ne 443) -and ($oneSslCert.PortNumber -ne 49443) } DBGIF ('Non-identical ADFS TLS certificate found: {0} | {1} | {2}' -f $oneSslCert.HostName, $oneSslCert.CertificateHash, $oneSslCert.PortNumber) { (Is-ValidString $uniqueCertThumbprint) -and ($uniqueCertThumbprint -ne $oneSslCert.CertificateHash) } $uniqueCertThumbprint = $oneSslCert.CertificateHash } DBG ('Get ADFS properties') DBGSTART $adfsProp = Get-AdfsProperties DBGER $MyInvocation.MyCommand.Name $error DBGEND DBGIF $MyInvocation.MyCommand.Name { Is-Null $adfsProp } # Note: verify some defaults DBGIF $MyInvocation.MyCommand.Name { $adfsProp.CertificateDuration -ne 365 } DBGIF $MyInvocation.MyCommand.Name { $adfsProp.KmsiEnabled } DBGIF $MyInvocation.MyCommand.Name { -not $adfsProp.PersistentSsoEnabled } $now = [DateTime]::Now $urn = 'urn:fdc:{0}:{1:D4}-{2:D2}:{3}' -f $organizationDNS, $now.Year, $now.Month, $identifier DBG ('Set ADFS identifier: {0}' -f $urn) DBGSTART Set-AdfsProperties -Identifier $urn DBGER $MyInvocation.MyCommand.Name $error DBGEND if ($enableKmsi) { DBG ('Enable KMSI') DBGSTART Set-AdfsProperties -EnableKmsi $true DBGER $MyInvocation.MyCommand.Name $error DBGEND } if ($extranetLockout -gt 0) { DBG ('Enable extranet lockout: {0} | {1}' -f $extranetLockout, $extranetObservation) DBGSTART Set-AdfsProperties -EnableExtranetLockout $true -ExtranetLockoutThreshold $extranetLockout -ExtranetObservationWindow $extranetObservation DBGER $MyInvocation.MyCommand.Name $error DBGEND } if (Is-ValidString $extendedProtection) { DBG ('Set extended protection for authentication: {0}' -f $extendedProtection) DBGSTART Set-AdfsProperties -ExtendedProtectionTokenCheck $extendedProtection DBGER $MyInvocation.MyCommand.Name $error DBGEND } DBG ('Verify that the anonymous web services are running') [void] (Download-WebPage "http://$identifier/adfs/probe") [System.Xml.XmlDocument] $fedMeta = [XML] (Download-WebPage "https://$identifier/FederationMetadata/2007-06/FederationMetadata.xml") DBGIF $MyInvocation.MyCommand.Name { Is-Null $fedMeta } [System.Xml.XmlDocument] $mexMeta = [XML] (Download-WebPage "https://$identifier/adfs/services/trust/mex") DBGIF $MyInvocation.MyCommand.Name { Is-Null $mexMeta } [System.Xml.XmlDocument] $fsMeta = [XML] (Download-WebPage "https://$identifier/adfs/fs/federationserverservice.asmx") DBGIF $MyInvocation.MyCommand.Name { Is-Null $fsMeta } [void] (Download-WebPage "https://$identifier/adfs/ls") DBGIF ('Weird entity ID: {0}' -f $fedMeta.EntityDescriptor.entityId) { $fedMeta.EntityDescriptor.entityId -ne $urn } DBGIF ('Weird passive endpoint: {0}' -f (($fedMeta.EntityDescriptor.RoleDescriptor | select -Expand PassiveRequestorEndpoint | select -Expand EndpointReference | select -Expand Address) -join ', ')) { Is-NonNull ($fedMeta.EntityDescriptor.RoleDescriptor | select -Expand PassiveRequestorEndpoint | select -Expand EndpointReference | select -Expand Address | ? { $_ -ne "https://$identifier/adfs/ls/" }) } } ################ # # Note: Main code # if ($global:thisOSVersionNumber -lt 6.3) { DBGIF $MyInvocation.MyCommand.Name { $global:thisOSVersionNumber -lt 6.3 } exit } DBG ('Verify that the instance name is DNS resolvable: {0}' -f $appConfig.instance) DBGSTART [IPAddress[]] $instanceIPs = [Net.Dns]::Resolve($appConfig.instance).AddressList DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ('Name resolved as: {0}' -f (($instanceIPs | Select -Expand IPAddressToString) -join ',')) DBGIF ('Cannot DNS resolve name: {0}' -f $appConfig.instance) { (Get-CountSafe $instanceIPs) -lt 1 } # Note: this will always be setting of each individual host node because of the permissions # on the template. Both ADFS and WAP servers will have to have such a certificate # There may be a problem in fact, because I suspect that the certificate must be exactly the same # as ADFS farm synchronizes its thumbprint among the farm members [System.Xml.XmlElement] $sslCaCert = $appConfig.SelectSingleNode('./cert[one[@appTag="tls"]]') DBGIF $MyInvocation.MyCommand.Name { Is-Null $sslCaCert } DBGIF $MyInvocation.MyCommand.Name { Is-Null $sslCaCert.one } DBGIF $MyInvocation.MyCommand.Name { $sslCaCert.one -isnot 'System.Xml.XmlElement' } #DBG ('Wait for the TLS certificate CA to finish') #Wait-Machine (Get-FirstAppHostInInstance 'ca' $sslCaCert.instance 'waitParams') [string] $tlsCert = Enroll-AppCertificate $sslCaCert.one DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $tlsCert } if ($appConfig.type -eq 'adfs') { # Note: yet before the ADFS install finishes, because the WAP installation waits for the first ADFS # server only. [System.Xml.XmlElement] $firstWAP = $null DBG ('We have overall instance members: {0} | wap members = {1}' -f (Get-CountSafe $allAppConfigs), (Get-CountSafe ($allAppConfigs | ? { $_.type -eq 'wap' }))) $firstWAP = $allAppConfigs | ? { $_.type -eq 'wap' } | Select -First 1 if (Is-NonNull $firstWAP) { DBG ('We have to add the possible WAP install account into local Administrators on all ADFS farm members: {0} | {1}' -f $firstWAP.app.iLogin, $firstWAP.app.iDomain) DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $firstWAP.app.iLogin } Add-MemberLocalGroup 'Administrators' $firstWAP.app.iLogin $firstWAP.app.iDomain } # # if ($firstAppHostInInstance) { DBG ('Check if signature and decryption certificates are requestes') [System.Xml.XmlElement] $sigCaCert = $appConfig.SelectSingleNode('./cert[one[@appTag="sig"]]') [System.Xml.XmlElement] $decryptCaCert = $appConfig.SelectSingleNode('./cert[one[@appTag="decrypt"]]') DBG ('Signature certificate requested: {0} | {1} | {2}' -f (Is-NonNull $sigCaCert), $sigCaCert.instance, $sigCaCert.template) DBG ('Decryption certificate requested: {0} | {1} | {2}' -f (Is-NonNull $decryptCaCert), $decryptCaCert.instance, $decryptCaCert.template) [string] $sigCert = $null if (Is-NonNull $sigCaCert) { DBG ('Enroll signature certificate') if ($sigCaCert.instance -ne $sslCaCert.instance) { DBG ('Wait for the SIGNATURE certificate CA to finish') Wait-Machine (Get-FirstAppHostInInstance 'ca' $sigCaCert.instance 'waitParams') } [string] $sigCert = Enroll-AppCertificate $sigCaCert.one DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $sigCert } } [string] $decryptCert = $null if (Is-NonNull $decryptCaCert) { DBG ('Enroll decryption certificate') if ($decryptCaCert.instance -ne $sslCaCert.instance) { DBG ('Wait for the DECRYPTION certificate CA to finish') Wait-Machine (Get-FirstAppHostInInstance 'ca' $decryptCaCert.instance 'waitParams') } [string] $decryptCert = Enroll-AppCertificate $decryptCaCert.one DBGIF $MyInvocation.MyCommand.Name { Is-EmptyString $decryptCert } } if (Is-ValidString $appConfig.gmsa.login) { DBG ('Going to build first ADFS with GMSA: {0} | {1}' -f $appConfig.gmsa.login, $appConfig.gmsa.domain) # Note: we cannot use the Get-SAMLogin because the account does not exist yet [string] $gmsaSAMLogin = $appConfig.gmsa.login if ($gmsaSAMLogin -notlike '?*$') { $gmsaSAMLogin = '{0}$' -f $gmsaSAMLogin } [string] $netbiosDomainName = $appConfig.gmsa.domain $gmsaSAMLogin = '{0}\{1}' -f $netbiosDomainName, $gmsaSAMLogin DBG ('GMSA SAM login determined: {0}' -f $gmsaSAMLogin) Build-PrimaryADFS -organizationDNS $appConfig.urnDNS -identifier $appConfig.instance -deviceRegDomain $appConfig.deviceRegDomain -thumbprint $tlsCert -companyDisplay $appConfig.display -gmsa $gmsaSAMLogin -extranetLockout (Parse-IntSafe $appConfig.lockout.threshold) -extranetObservation $appConfig.lockout.window -enableKmsi (Parse-BoolSafe $appConfig.kmsi.enable) -extendedProtection $appConfig.extendedProtection } else { DBG ('Going to build first ADFS with manual SVC account') [System.Xml.XmlElement] $manualSvc = $appConfig.SelectSingleNode('./svc[@appTag="manual"]') DBGIF $MyInvocation.MyCommand.Name { Is-Null $manualSvc } $svcCred = Get-PwdCredentials -user $manualSvc.login -domain $manualSvc.domain -password $manualSvc.pwd -samLogin $true Build-PrimaryADFS -organizationDNS $appConfig.urnDNS -identifier $appConfig.instance -deviceRegDomain $appConfig.deviceRegDomain -thumbprint $tlsCert -companyDisplay $appConfig.display -svcCred $svcCred -extranetLockout (Parse-IntSafe $appConfig.lockout.threshold) -extranetObservation $appConfig.lockout.window -enableKmsi (Parse-BoolSafe $appConfig.kmsi.enable) -extendedProtection $appConfig.extendedProtection } DBG ('Check if we need to create any claim description') DBGSTART $claimDescsRequested = $appConfig.SelectNodes('./claimDesc') DBGER $MyInvocation.MyCommand.Name $error DBGEND DBG ('Claim descriptions requested: {0}' -f (Get-CountSafe $claimDescsRequested)) if ((Get-CountSafe $claimDescsRequested) -gt 0) { foreach ($oneClaimDesc in $claimDescsRequested) { DBG ('Define one claim description: {0} | {1} | {2}' -f $oneClaimDesc.display, $oneClaimDesc.urn, $oneClaimDesc.oauth) DBGIF $MyInvocation.MyCommand.Name { (Is-EmptyString $oneClaimDesc.display) -or (Is-EmptyString $oneClaimDesc.urn) -or (Is-EmptyString $oneClaimDesc.oauth) } DBGSTART Add-AdfsClaimDescription -Name $oneClaimDesc.display -ClaimType $oneClaimDesc.urn -ShortName $oneClaimDesc.oauth DBGER $MyInvocation.MyCommand.Name $error DBGEND } } } else { DBG ('Wait for the first ADFS server to finish') Wait-Machine $firstAppHost } DBG ('Should we install the Office365 redirector: {0}' -f (Parse-BoolSafe $firstAppConfig.o365redir.install)) if (Parse-BoolSafe $firstAppConfig.o365redir.install) { Run-Process (Join-Path $global:rootDir 'IIS\SevecekOffice365Redirector\Debug\SevecekRedirector.bat') ($firstAppConfig.o365redir.replace) } } if ($appConfig.type -eq 'wap') { DBG ('Doing WAP installation: {0} | {1}' -f $tlsCert, $appConfig.instance) DBG ('Wait for the first APP host to finish') DBGIF $MyInvocation.MyCommand.Name { $firstAppHostInInstance } Wait-Machine $firstAppHost DBG ('ADFS machine finished but the service is in delayed load, so give it some more time to come online: {0}' -f $appConfig.instance) Wait-Periodically -maxTrialCount 15 -sleepSec 7 -sleepMsg 'Waiting until the ADFS server comes online' -scriptBlockWhichReturnsTrueToStop { [bool] $online = $false [System.Net.WebClient] $webClient = New-Object System.Net.WebClient [string] $fedMetaUri = 'https://{0}/FederationMetadata/2007-06/FederationMetadata.xml' -f $appConfig.instance DBG ('Downloading federation metadata: {0}' -f $fedMetaUri) DBGSTART [string] $xmlStr = $webClient.DownloadString($fedMetaUri) $online = (([int] $error.Length) -eq 0) -and ($xmlStr.Length -gt 200) DBGEND if ($online) { DBG ('Downloaded ADFS federation metadata: {0} | {1}' -f $xmlStr.Length, $xmlStr.SubString(0, 200)) } return $online } DBG ('Create credential for the current install account: {0} | {1}' -f $appConfig.app.iLogin, $appConfig.app.iDomain) $adfsAdminCred = Get-PwdCredentials $appConfig.app.iLogin $appConfig.app.iDomain $appConfig.app.iPwd DBG ('Do the installation as such: {0} | {1} | {2}' -f $adfsAdminCred.UserName, $tlsCert, $appConfig.instance) DBGSTART Install-WebApplicationProxy -FederationServiceTrustCredential $adfsAdminCred -CertificateThumbprint $tlsCert -FederationServiceName $appConfig.instance DBGER $MyInvocation.MyCommand.Name $error DBGEND } DBG ('Throw up any remaining errors') DBGSTART; DBGEND # SIG # Begin signature block # MIIc/QYJKoZIhvcNAQcCoIIc7jCCHOoCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCl6BJh3Izaj9SF # +4ojAHZ6sV1RWEJf1D/wi4qigIKzPqCCGAQwggTlMIIDzaADAgECAhA5vUKe0oFu # utW8yQO0umXnMA0GCSqGSIb3DQEBCwUAMHUxCzAJBgNVBAYTAklMMRYwFAYDVQQK # Ew1TdGFydENvbSBMdGQuMSkwJwYDVQQLEyBTdGFydENvbSBDZXJ0aWZpY2F0aW9u # IEF1dGhvcml0eTEjMCEGA1UEAxMaU3RhcnRDb20gQ2xhc3MgMiBPYmplY3QgQ0Ew # HhcNMTYxMjAxMTU1MTEzWhcNMTgxMjAxMTU1MTEzWjBRMQswCQYDVQQGEwJDWjEa # MBgGA1UECAwRSmlob21vcmF2c2t5IEtyYWoxDTALBgNVBAcMBEJybm8xFzAVBgNV # BAMMDk9uZHJlaiBTZXZlY2VrMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC # AQEAr9E9hNj06bash9JX97kpsqK9Z/ciOBC6trI4nvlW9CPwhKBTb5wArhxLYZBG # 9jWPWrdy1nL/cm5qMqBb/mogYwMwvEYWMvsIOOVn6HD9lVhNAovD6PHz0ziBBKIs # zXTjyUPQaoIlIELovz967m78HJdUZJGxqhluAsS9o9/fEzA7XXUhUuqRKsetuZV/ # Asfh5sOveeoRsbeW4daTWvtz3TJuULL0w43LNVYJkd6LL8cegvLPVZUe1N7skvid # EvntdlowQsJlqFdrH3SGKIPKA6ObcY8SZWkEQSbVBF8Kum1UT+jN0gm+84FwOg5W # qKx+VvTK2ljVWnPrCD0Zzu2oIQIDAQABo4IBkzCCAY8wDgYDVR0PAQH/BAQDAgeA # MBMGA1UdJQQMMAoGCCsGAQUFBwMDMAkGA1UdEwQCMAAwHQYDVR0OBBYEFG2vSo3N # hQWILeUs0oN9XzHTejcfMB8GA1UdIwQYMBaAFD5ik5rXxxnuPo9JEIVVFSDjlIQc # MG0GCCsGAQUFBwEBBGEwXzAkBggrBgEFBQcwAYYYaHR0cDovL29jc3Auc3RhcnRz # c2wuY29tMDcGCCsGAQUFBzAChitodHRwOi8vYWlhLnN0YXJ0c3NsLmNvbS9jZXJ0 # cy9zY2EuY29kZTIuY3J0MDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6Ly9jcmwuc3Rh # cnRzc2wuY29tL3NjYS1jb2RlMi5jcmwwIwYDVR0SBBwwGoYYaHR0cDovL3d3dy5z # dGFydHNzbC5jb20vMFEGA1UdIARKMEgwCAYGZ4EMAQQBMDwGCysGAQQBgbU3AQIF # MC0wKwYIKwYBBQUHAgEWH2h0dHBzOi8vd3d3LnN0YXJ0c3NsLmNvbS9wb2xpY3kw # DQYJKoZIhvcNAQELBQADggEBAJuRiEvHtIYSpsmMkPhTz4QOOShN3p5KWdf8vm71 # A33CR9fds10d8D2B2aE+vjmHJ69GY0bbfg5oZY2Lsq2euL7Da5/hS8+6T3MEtD4h # njfHV7mxmoSfFuy/KDipoV6uwhI+ksqchXYdUH+5uCQO0MOO8ITjAgzUQsnZ4UIB # HBGeP+e+3ljxSYSXWdPIrgxdR971P/HhWSVfKNlmBgEKMQM5Jy0aAd4jxSl/AzdY # t0+6pliFJ1peGhdFni2Fm8fu5oN68aTIrNtc5WY7Lzgf+sRTVeWORWS37+1zAD0m # jzd8gyfBLxRuaRSfjYxny0rLXelAwfiA3ze2DU2Bfg9/rfcwggXYMIIDwKADAgEC # AhBsO9J+3TyUnpWOKKmzx1egMA0GCSqGSIb3DQEBCwUAMH0xCzAJBgNVBAYTAklM # MRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSswKQYDVQQLEyJTZWN1cmUgRGlnaXRh # bCBDZXJ0aWZpY2F0ZSBTaWduaW5nMSkwJwYDVQQDEyBTdGFydENvbSBDZXJ0aWZp # Y2F0aW9uIEF1dGhvcml0eTAeFw0xNTEyMTYwMTAwMDVaFw0zMDEyMTYwMTAwMDVa # MHUxCzAJBgNVBAYTAklMMRYwFAYDVQQKEw1TdGFydENvbSBMdGQuMSkwJwYDVQQL # EyBTdGFydENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEjMCEGA1UEAxMaU3Rh # cnRDb20gQ2xhc3MgMiBPYmplY3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw # ggEKAoIBAQC5FARY97LFhiwIMmCtCCbAgXe5aBnZFSsdGGnk2hqWBZcuZHkaqT1R # M1rQd2r0ApNBw466cBur2Ht0b5jo17mpPmh2pImgIqwX1in4u7hhn9IH0GYOMEcg # K3ACHv5zCRxxNLXifqmsqKfxjjpABnaSyvd4bO9YBXN9f4NQ6aJVAuMArpanxsJk # e+P4WECVLk17v92CAN5JVaczI+baT/lgo5NVcTEkloCViSbIfU6ILeyhOSQZvpom # MYk8eJqI0nimOTJJfmXangNDsrX8np+3lXD0+6rCZisXRWIaeffyTMHZ31Qj1D50 # WYdRtX5yev4WgaXoKJQN3lkgXUcytvyHAgMBAAGjggFaMIIBVjAOBgNVHQ8BAf8E # BAMCAQYwEwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAy # BgNVHR8EKzApMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0c3NsLmNvbS9zZnNjYS5j # cmwwZgYIKwYBBQUHAQEEWjBYMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5zdGFy # dHNzbC5jb20wMAYIKwYBBQUHMAKGJGh0dHA6Ly9haWEuc3RhcnRzc2wuY29tL2Nl # cnRzL2NhLmNydDAdBgNVHQ4EFgQUPmKTmtfHGe4+j0kQhVUVIOOUhBwwHwYDVR0j # BBgwFoAUTgvvGqRAW6UXaYcwyjRoQ9BBrvIwPwYDVR0gBDgwNjA0BgRVHSAAMCww # KgYIKwYBBQUHAgEWHmh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3BvbGljeTANBgkq # hkiG9w0BAQsFAAOCAgEAY6U81bNtJyjY67pTrzAL6kpdEtX5mspw+kxjjNdNVH5G # 6lLnhaEkIxqdpvY/Wdw+UdNtExs+N8efKPSwh2m/BxXj2fSeLMwXcwHFookScEER # 8ez0quCNzioqNHac7LCXPEnQzbtG2FHlePKNDWh8eU6KxiAzNzIrIxPthinHGgLT # BOACHQM2YTlD8YoU5oN3dLmBOqtH0BDMZoLcjEIoEW1zC+TnVb3yU1G0xub6gnN7 # lP50vbAiHJYrnywQiXaloBV8B9YYfe6ZgvjqxwufwFcMVyE3UmCuDTsOpjqDEKpJ # 25s+FUdkie5VqCS1aaudLo31X+9UvP45pfgyRqzyfUnVEhH4ZXxlBWZMzj2Xov5+ # m/+H3kxYuFA5xdqdshj/Zx00S7PkCSF+8M1NCcvFgQwjIw61bZAjDBl3P3a8xNTX # sb2CjFdiNKbT3LD6IGeIf0b/EbPf0FXdvBrxm0ofMOhnngdPolPYCtoOGtZPAVe/ # xeu+/ZyKv6TSHlshaUO0iYfsmbXnZ51vvt/kkjwms9/qPFxSuE0fjEfF7aQazwRE # Df2hiVPR0pAhvShtM3oU4XreEFEUWEYHs25fYV4WMmxkUKSgmSmwRq45tvtGH4LT # b5+cd+iLqK8rBQL0E6xaUjjGfsYx7bueIvqTvCkrQvoxMbn/qDHCiypowDVq6TAw # ggZqMIIFUqADAgECAhADAZoCOv9YsWvW1ermF/BmMA0GCSqGSIb3DQEBBQUAMGIx # CzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3 # dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0Et # MTAeFw0xNDEwMjIwMDAwMDBaFw0yNDEwMjIwMDAwMDBaMEcxCzAJBgNVBAYTAlVT # MREwDwYDVQQKEwhEaWdpQ2VydDElMCMGA1UEAxMcRGlnaUNlcnQgVGltZXN0YW1w # IFJlc3BvbmRlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKNkXfx8 # s+CCNeDg9sYq5kl1O8xu4FOpnx9kWeZ8a39rjJ1V+JLjntVaY1sCSVDZg85vZu7d # y4XpX6X51Id0iEQ7Gcnl9ZGfxhQ5rCTqqEsskYnMXij0ZLZQt/USs3OWCmejvmGf # rvP9Enh1DqZbFP1FI46GRFV9GIYFjFWHeUhG98oOjafeTl/iqLYtWQJhiGFyGGi5 # uHzu5uc0LzF3gTAfuzYBje8n4/ea8EwxZI3j6/oZh6h+z+yMDDZbesF6uHjHyQYu # RhDIjegEYNu8c3T6Ttj+qkDxss5wRoPp2kChWTrZFQlXmVYwk/PJYczQCMxr7GJC # kawCwO+k8IkRj3cCAwEAAaOCAzUwggMxMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMB # Af8EAjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIIBvwYDVR0gBIIBtjCCAbIw # ggGhBglghkgBhv1sBwEwggGSMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdp # Y2VydC5jb20vQ1BTMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBBAG4AeQAgAHUAcwBl # ACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBu # AHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0 # AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABTACAAYQBuAGQAIAB0 # AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABBAGcAcgBlAGUAbQBl # AG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5 # ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBhAHQAZQBkACAAaABl # AHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAuMAsGCWCGSAGG/WwD # FTAfBgNVHSMEGDAWgBQVABIrE5iymQftHt+ivlcNK2cCzTAdBgNVHQ4EFgQUYVpN # JLZJMp1KKnkag0v0HonByn0wfQYDVR0fBHYwdDA4oDagNIYyaHR0cDovL2NybDMu # ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcmwwOKA2oDSGMmh0 # dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRENBLTEuY3Js # MHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl # cnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v # RGlnaUNlcnRBc3N1cmVkSURDQS0xLmNydDANBgkqhkiG9w0BAQUFAAOCAQEAnSV+ # GzNNsiaBXJuGziMgD4CH5Yj//7HUaiwx7ToXGXEXzakbvFoWOQCd42yE5FpA+94G # AYw3+puxnSR+/iCkV61bt5qwYCbqaVchXTQvH3Gwg5QZBWs1kBCge5fH9j/n4hFB # pr1i2fAnPTgdKG86Ugnw7HBi02JLsOBzppLA044x2C/jbRcTBu7kA7YUq/OPQ6dx # nSHdFMoVXZJB2vkPgdGZdA0mxA5/G7X1oPHGdwYoFenYk+VVFvC7Cqsc21xIJ2bI # o4sKHOWV2q7ELlmgYd3a822iYemKC23sEhi991VUQAOSK2vCUcIKSK+w1G7g9BQK # Ohvjjz3Kr2qNe9zYRDCCBs0wggW1oAMCAQICEAb9+QOWA63qAArrPye7uhswDQYJ # KoZIhvcNAQEFBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IElu # YzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNlcnQg # QXNzdXJlZCBJRCBSb290IENBMB4XDTA2MTExMDAwMDAwMFoXDTIxMTExMDAwMDAw # MFowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UE # CxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgQXNzdXJlZCBJ # RCBDQS0xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6IItmfnKwkKV # pYBzQHDSnlZUXKnE0kEGj8kz/E1FkVyBn+0snPgWWd+etSQVwpi5tHdJ3InECtqv # y15r7a2wcTHrzzpADEZNk+yLejYIA6sMNP4YSYL+x8cxSIB8HqIPkg5QycaH6zY/ # 2DDD/6b3+6LNb3Mj/qxWBZDwMiEWicZwiPkFl32jx0PdAug7Pe2xQaPtP77blUjE # 7h6z8rwMK5nQxl0SQoHhg26Ccz8mSxSQrllmCsSNvtLOBq6thG9IhJtPQLnxTPKv # mPv2zkBdXPao8S+v7Iki8msYZbHBc63X8djPHgp0XEK4aH631XcKJ1Z8D2KkPzIU # YJX9BwSiCQIDAQABo4IDejCCA3YwDgYDVR0PAQH/BAQDAgGGMDsGA1UdJQQ0MDIG # CCsGAQUFBwMBBggrBgEFBQcDAgYIKwYBBQUHAwMGCCsGAQUFBwMEBggrBgEFBQcD # CDCCAdIGA1UdIASCAckwggHFMIIBtAYKYIZIAYb9bAABBDCCAaQwOgYIKwYBBQUH # AgEWLmh0dHA6Ly93d3cuZGlnaWNlcnQuY29tL3NzbC1jcHMtcmVwb3NpdG9yeS5o # dG0wggFkBggrBgEFBQcCAjCCAVYeggFSAEEAbgB5ACAAdQBzAGUAIABvAGYAIAB0 # AGgAaQBzACAAQwBlAHIAdABpAGYAaQBjAGEAdABlACAAYwBvAG4AcwB0AGkAdAB1 # AHQAZQBzACAAYQBjAGMAZQBwAHQAYQBuAGMAZQAgAG8AZgAgAHQAaABlACAARABp # AGcAaQBDAGUAcgB0ACAAQwBQAC8AQwBQAFMAIABhAG4AZAAgAHQAaABlACAAUgBl # AGwAeQBpAG4AZwAgAFAAYQByAHQAeQAgAEEAZwByAGUAZQBtAGUAbgB0ACAAdwBo # AGkAYwBoACAAbABpAG0AaQB0ACAAbABpAGEAYgBpAGwAaQB0AHkAIABhAG4AZAAg # AGEAcgBlACAAaQBuAGMAbwByAHAAbwByAGEAdABlAGQAIABoAGUAcgBlAGkAbgAg # AGIAeQAgAHIAZQBmAGUAcgBlAG4AYwBlAC4wCwYJYIZIAYb9bAMVMBIGA1UdEwEB # /wQIMAYBAf8CAQAweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8v # b2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6 # MHgwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy # ZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9E # aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwHQYDVR0OBBYEFBUAEisTmLKZB+0e # 36K+Vw0rZwLNMB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqG # SIb3DQEBBQUAA4IBAQBGUD7Jtygkpzgdtlspr1LPUukxR6tWXHvVDQtBs+/sdR90 # OPKyXGGinJXDUOSCuSPRujqGcq04eKx1XRcXNHJHhZRW0eu7NoR3zCSl8wQZVann # 4+erYs37iy2QwsDStZS9Xk+xBdIOPRqpFFumhjFiqKgz5Js5p8T1zh14dpQlc+Qq # q8+cdkvtX8JLFuRLcEwAiR78xXm8TBJX/l/hHrwCXaj++wc4Tw3GXZG5D2dFzdaD # 7eeSDY2xaYxP+1ngIw/Sqq4AfO6cQg7PkdcntxbuD8O9fAqg7iwIVYUiuOsYGk38 # KiGtSTGDR5V3cdyxG0tLHBCcdxTBnU8vWpUIKRAmMYIETzCCBEsCAQEwgYkwdTEL # MAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKTAnBgNVBAsTIFN0 # YXJ0Q29tIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSMwIQYDVQQDExpTdGFydENv # bSBDbGFzcyAyIE9iamVjdCBDQQIQOb1CntKBbrrVvMkDtLpl5zANBglghkgBZQME # AgEFAKCBhDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEM # BgorBgEEAYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqG # SIb3DQEJBDEiBCC+NzKQMQqIM2t6RA3KN7xZsU7O0BKX13IWcjVqC7hHkzANBgkq # hkiG9w0BAQEFAASCAQBhrbdN1FAOWUrAgivAoofC6eOadsS6vnUhd+2NbYGi6z2d # y2BZ9bWZH3X5l1cubBzGhzhYj5tWaCyNGeqxYOvAmu6Xw5iNNpso4fmKhD7prd8r # 7HnHFPdBTeFYzI1m6FR+ZsnvQyGfTXe1O7SC2Xnp1mgARorYYisvMlYcUfuUYW+R # OjB8OToNNml0uostm8gkitdnDdHvjPPx3ARzsSL8wmLMbWyJU7x7lCK52HrQcM9h # pN57b0nq3fryV23JdUK25RWmkc5YGRQ01/VIsn/JvTVdnTdXRw40cYWu4m+g6kIs # B3K2vYc6ufyuUWpbsY75akHC3FoqdSk9bTPEvGc/oYICDzCCAgsGCSqGSIb3DQEJ # BjGCAfwwggH4AgEBMHYwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0 # IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNl # cnQgQXNzdXJlZCBJRCBDQS0xAhADAZoCOv9YsWvW1ermF/BmMAkGBSsOAwIaBQCg # XTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODAy # MDYwODM2MDhaMCMGCSqGSIb3DQEJBDEWBBTmQQNw0wI0SQa3BxFV5gGOnC12kDAN # BgkqhkiG9w0BAQEFAASCAQAabnxtb9sVx8V0pOcH+TEFe50kp9jQwQpjOTAR36R8 # OTUQUEW9oIlVXBvoU9/t5iZlVZ+daaZ6f5RRd/8zjxi8kTf7mPYpHvp3xyTlAP4d # ZCZuPX8z4jipIFD8F9w7rGc7aFwWHFduXX+pfCb8ue3jj6gZYqTyfHR4yIjzCJPy # ixWEpvmab3qv4XTC7lNviT92TFU3LWXK0ch32C4fwjwWWjD2+CQ+kJQgEFj5n1m6 # CwOEsDnh8QVev9WIvD2Y+F9TBwVHNw05jtDFlHF4BilTEvrXSBZj1/3JgzimUaun # vyFuyt8inoLbj6UZIqLAl0MAJE2gJw8I9HotqWCRyIrZ # SIG # End signature block