Private/AuthenticationOperations.ps1
|
<#
SAMPLE CODE NOTICE THIS SAMPLE CODE IS MADE AVAILABLE AS IS. MICROSOFT MAKES NO WARRANTIES, WHETHER EXPRESS OR IMPLIED, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OR CONDITIONS OF MERCHANTABILITY. THE ENTIRE RISK OF THE USE OR THE RESULTS FROM THE USE OF THIS SAMPLE CODE REMAINS WITH THE USER. NO TECHNICAL SUPPORT IS PROVIDED. YOU MAY NOT DISTRIBUTE THIS CODE UNLESS YOU HAVE A LICENSE AGREEMENT WITH MICROSOFT THAT ALLOWS YOU TO DO SO. #> function Connect-Azure { param( [Parameter(Mandatory, ParameterSetName = 'ByEndpoint')] [ValidateNotNullOrEmpty()] [PPEndpoint]$Endpoint, [Parameter(Mandatory, ParameterSetName = 'ByEnvironment')] [ValidateNotNullOrEmpty()] [AzureEnvironment] $AzureEnvironment, [Parameter(Mandatory=$false, ParameterSetName = 'ByEndpoint')] [Parameter(Mandatory=$false, ParameterSetName = 'ByEnvironment')] [string]$TenantId = $null, [Parameter(Mandatory=$false, ParameterSetName = 'ByEndpoint')] [Parameter(Mandatory=$false, ParameterSetName = 'ByEnvironment')] [string]$AuthScope = $null, [Parameter(Mandatory=$false, ParameterSetName = 'ByEndpoint')] [Parameter(Mandatory=$false, ParameterSetName = 'ByEnvironment')] [switch]$Force ) if($PSCmdlet.ParameterSetName -eq 'ByEndpoint') { $AzureEnvironment = switch ($Endpoint) { ([PPEndpoint]::china) { "AzureChinaCloud" } ([PPEndpoint]::dod) { "AzureUSGovernment" } ([PPEndpoint]::usgovhigh) { "AzureUSGovernment" } Default { "AzureCloud" } } } # When re-authentication is not forced, honor a cached service principal auth # configuration and prioritize that flow over the default interactive login. if(-not($Force)) { $servicePrincipalConfig = Get-CachedServicePrincipalAuth if($null -ne $servicePrincipalConfig) { return Connect-AzureWithServicePrincipal -Config $servicePrincipalConfig -AzureEnvironment $AzureEnvironment -TenantId $TenantId } } $context = Get-AzContext -ListAvailable $foundContext = $false if(-not($Force) -and [string]::IsNullOrWhiteSpace($AuthScope) -and $null -ne $context) { if([string]::IsNullOrWhiteSpace($TenantId)) { $matchedContext = $context | Where-Object { $_.Environment.Name -eq $AzureEnvironment } | Select-Object -First 1 if($matchedContext) { Set-AzContext -Context $matchedContext Write-Host "Already connected to Azure environment: $AzureEnvironment with account $($matchedContext.Account.Id) with tenants [$($matchedContext.Account.Tenants -join ",")]" -ForegroundColor Yellow $foundContext = $true } } else { # Prioritize the home tenant if it exists $homeTenantContext = $context | Where-Object { $_.Environment.Name -eq $AzureEnvironment -and $_.Tenant.TenantCategory -eq "Home" -and $_.Tenant.Id -eq $TenantId } | Select-Object -First 1 if($homeTenantContext) { Set-AzContext -Context $homeTenantContext Write-Host "Already connected to Azure environment: $AzureEnvironment with account $($homeTenantContext.Account.Id) with home tenant Id $TenantId" -ForegroundColor Yellow $foundContext = $true } else { $tenantContext = $context | Where-Object { $_.Environment.Name -eq $AzureEnvironment -and $_.Account.Tenants -contains $TenantId } | Select-Object -First 1 if ($tenantContext) { Set-AzContext -Context $tenantContext Write-Host "Already connected to Azure environment: $AzureEnvironment with account $($tenantContext.Account.Id) with tenant Id $TenantId" -ForegroundColor Yellow $foundContext = $true } } } } if ($foundContext) { return $true } Write-Host "Logging In..." -ForegroundColor Green $connectParameters = @{ Environment = $AzureEnvironment } if(-not([string]::IsNullOrWhiteSpace($TenantId))) { $connectParameters['Tenant'] = $TenantId } if(-not([string]::IsNullOrWhiteSpace($AuthScope))) { $connectParameters['AuthScope'] = $AuthScope } $connect = Connect-AzAccount @connectParameters if ($null -eq $connect) { Write-Host "Error connecting to Azure Account" -ForegroundColor Red return $false } Write-Host "Logged In..." -ForegroundColor Green return $true } function Resolve-CertificateThumbprint { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$SubjectName ) $stores = @("Cert:\CurrentUser\My", "Cert:\LocalMachine\My") foreach ($store in $stores) { $cert = Get-ChildItem -Path $store -ErrorAction SilentlyContinue | Where-Object { $_.Subject -eq $SubjectName -or $_.Subject -like "*CN=$SubjectName*" } | Sort-Object -Property NotAfter -Descending | Select-Object -First 1 if ($null -ne $cert) { return $cert.Thumbprint } } throw "Could not find a certificate with subject name '$SubjectName' in the CurrentUser or LocalMachine 'My' store." } function Connect-AzureWithServicePrincipal { param( [Parameter(Mandatory)] [object]$Config, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$AzureEnvironment, [Parameter(Mandatory=$false)] [string]$TenantId = $null ) $method = $Config.Method if ([string]::IsNullOrWhiteSpace($method)) { throw "Service principal auth configuration is missing the 'Method' property. Expected 'ManagedIdentity' or 'Certificate'." } $clientId = $Config.ClientId $effectiveTenantId = if (-not [string]::IsNullOrWhiteSpace($TenantId)) { $TenantId } else { $Config.TenantId } # Reuse an existing matching context when one is already available. $existingContexts = Get-AzContext -ListAvailable | Where-Object { $_.Environment.Name -eq $AzureEnvironment -and $_.Account.Id -eq $clientId } if (-not [string]::IsNullOrWhiteSpace($effectiveTenantId)) { $existingContexts = $existingContexts | Where-Object { $_.Tenant.Id -eq $effectiveTenantId -or $_.Account.Tenants -contains $effectiveTenantId } } $existingContext = $existingContexts | Select-Object -First 1 if ($existingContext) { Set-AzContext -Context $existingContext Write-Host "Already connected to Azure environment: $AzureEnvironment with service principal $($existingContext.Account.Id)" -ForegroundColor Yellow return $true } $connectParameters = @{ Environment = $AzureEnvironment } switch ($method) { "ManagedIdentity" { $connectParameters['Identity'] = $true if (-not [string]::IsNullOrWhiteSpace($clientId)) { $connectParameters['AccountId'] = $clientId } if (-not [string]::IsNullOrWhiteSpace($effectiveTenantId)) { $connectParameters['Tenant'] = $effectiveTenantId } Write-Host "Logging in with managed identity..." -ForegroundColor Green } "Certificate" { if ([string]::IsNullOrWhiteSpace($clientId)) { throw "Service principal auth configuration for 'Certificate' requires a 'ClientId'." } if ([string]::IsNullOrWhiteSpace($effectiveTenantId)) { throw "Service principal auth configuration for 'Certificate' requires a 'TenantId'." } $thumbprint = $Config.CertificateThumbprint if ([string]::IsNullOrWhiteSpace($thumbprint) -and -not [string]::IsNullOrWhiteSpace($Config.CertificateSubjectName)) { $thumbprint = Resolve-CertificateThumbprint -SubjectName $Config.CertificateSubjectName } if ([string]::IsNullOrWhiteSpace($thumbprint)) { throw "Service principal auth configuration for 'Certificate' requires either 'CertificateThumbprint' or 'CertificateSubjectName'." } $connectParameters['ServicePrincipal'] = $true $connectParameters['ApplicationId'] = $clientId $connectParameters['Tenant'] = $effectiveTenantId $connectParameters['CertificateThumbprint'] = $thumbprint Write-Host "Logging in with certificate-based service principal..." -ForegroundColor Green } default { throw "Unknown service principal auth method '$method'. Expected 'ManagedIdentity' or 'Certificate'." } } $connect = Connect-AzAccount @connectParameters if ($null -eq $connect) { Write-Host "Error connecting to Azure with service principal" -ForegroundColor Red return $false } Write-Host "Logged In..." -ForegroundColor Green return $true } function Get-PPAccessToken { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PPEndpoint]$Endpoint, [Parameter(Mandatory=$false)] [string]$TenantId = $null ) $resourceUrl = Get-PPResourceUrl -Endpoint $Endpoint return Get-AccessToken -Endpoint $Endpoint -ResourceUrl $resourceUrl -TenantId $TenantId } function Get-PPAPIAccessToken { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PPEndpoint]$Endpoint, [Parameter(Mandatory=$false)] [string]$TenantId = $null ) $resourceUrl = Get-APIResourceUrl -Endpoint $Endpoint return Get-AccessToken -Endpoint $Endpoint -ResourceUrl $resourceUrl -TenantId $TenantId } function Get-AccessToken { param( [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [PPEndpoint]$Endpoint, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$ResourceUrl, [Parameter(Mandatory=$false)] [string]$TenantId = $null ) $token = Get-AzAccessToken -ResourceUrl $resourceUrl -AsSecureString -ErrorAction SilentlyContinue if ($null -eq $token) { $tokenError = $global:Error[0] if($tokenError.Exception.AuthenticationErrorCode -eq "failed_to_acquire_token_silently_from_broker") { Write-Host "Failed to acquire token silently. Please log in interactively." -ForegroundColor Red Connect-Azure -AuthScope $resourceUrl -Endpoint $Endpoint -TenantId $TenantId } elseif($null -ne $tokenError.Exception.AuthenticationErrorCode) { Write-Host "Failed to acquire access token: $($tokenError.Exception.AuthenticationErrorCode)" -ForegroundColor Red Connect-Azure -AuthScope $resourceUrl -Endpoint $Endpoint -TenantId $TenantId -Force } else { Write-Host "Failed to acquire access token: $($tokenError.Exception.Message)" -ForegroundColor Red Connect-Azure -AuthScope $resourceUrl -Endpoint $Endpoint -TenantId $TenantId -Force } $token = Get-AzAccessToken -ResourceUrl $resourceUrl -AsSecureString if($null -eq $token) { throw "Failed to acquire access token. Please check your Azure login and try again." } } return $token.Token } function ConvertFrom-SecureStringInternal { param ( [Parameter(Mandatory)] [System.Security.SecureString]$SecureString ) try{ $ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString) $plainText = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr) return $plainText } catch { throw "Failed to convert SecureString to plain text: $_" } finally { if ($ptr) { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr) } } } # Module-scoped cache for Authorization Service MSAL applications # Key: "Endpoint|TenantId|ClientId", Value: @{ App = <MSAL app>; Account = <cached account> } $script:AuthorizationServiceCache = @{} $script:AuthorizationServiceCurrentKey = $null function New-AuthorizationServiceMsalClient { param( [Parameter(Mandatory = $false)] [string]$ClientId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string]$TenantId, [Parameter(Mandatory = $false)] [PPEndpoint]$Endpoint = [PPEndpoint]::Prod, [Parameter(Mandatory = $false)] [switch]$Force ) # Resolve ClientId from cache if not provided; cache it if provided if ([string]::IsNullOrWhiteSpace($ClientId)) { $ClientId = Get-CachedAuthorizationServiceClientId if ([string]::IsNullOrWhiteSpace($ClientId)) { throw "ClientId was not provided and no cached ClientId was found. Run New-PPAuthorizationApplication or specify -ClientId." } } else { Set-CachedAuthorizationServiceClientId -AuthorizationServiceClientId $ClientId } $cacheKey = "$Endpoint|$TenantId|$ClientId" # Check if we have a cached client for this configuration if (-not $Force -and $script:AuthorizationServiceCache.ContainsKey($cacheKey)) { Write-Verbose "Reusing cached Authorization Service MSAL client for: $cacheKey" $script:AuthorizationServiceCurrentKey = $cacheKey return $true } # Determine the authority based on endpoint $authority = switch ($Endpoint) { ([PPEndpoint]::china) { "https://login.chinacloudapi.cn/$TenantId" } { $_ -in [PPEndpoint]::dod, [PPEndpoint]::usgovhigh } { "https://login.microsoftonline.us/$TenantId" } default { "https://login.microsoftonline.com/$TenantId" } } Write-Verbose "Creating Authorization Service MSAL client with authority: $authority" # Build the public client application with localhost redirect for interactive auth $builder = Get-PublicClientApplicationBuilder -ClientId $ClientId $app = $builder.WithAuthority($authority).WithRedirectUri("http://localhost").Build() if ($null -eq $app) { throw "Failed to create Authorization Service application." } # Store in cache $script:AuthorizationServiceCache[$cacheKey] = @{ App = $app Account = $null } $script:AuthorizationServiceCurrentKey = $cacheKey Write-Verbose "Successfully created Authorization Service MSAL client application." return $true } function Get-AuthorizationServiceToken { [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "", Justification="Converting AAD token into secure string")] param( [Parameter(Mandatory=$false)] [PPEndpoint]$Endpoint = [PPEndpoint]::Prod, [Parameter(Mandatory=$false)] [int]$TimeoutSeconds = 60 ) # Returns the access token as a SecureString so callers can use it directly without converting. if ($null -eq $script:AuthorizationServiceCurrentKey -or -not $script:AuthorizationServiceCache.ContainsKey($script:AuthorizationServiceCurrentKey)) { throw "Authorization Service MSAL client application not created. Call New-AuthorizationServiceMsalClient first." } $resourceUrl = (Get-APIResourceUrl -Endpoint $Endpoint).TrimEnd('/') [string[]]$Scopes = @("$resourceUrl/.default") $cached = $script:AuthorizationServiceCache[$script:AuthorizationServiceCurrentKey] $app = $cached.App $account = $cached.Account # Create cancellation token with timeout $cts = [System.Threading.CancellationTokenSource]::new([TimeSpan]::FromSeconds($TimeoutSeconds)) try { # Try to acquire token silently first if we have a cached account if ($null -ne $account) { Write-Verbose "Attempting silent token acquisition for scopes: $($Scopes -join ', ')" try { $result = $app.AcquireTokenSilent($Scopes, $account).ExecuteAsync($cts.Token).GetAwaiter().GetResult() if ($null -ne $result -and -not [string]::IsNullOrEmpty($result.AccessToken)) { Write-Verbose "Successfully acquired token silently for account: $($result.Account.Username)" return (ConvertTo-SecureString -String $result.AccessToken -AsPlainText -Force) } } catch [System.OperationCanceledException] { throw "Token acquisition timed out after $TimeoutSeconds seconds." } catch { Write-Verbose "Silent token acquisition failed: $($_.Exception.Message). Falling back to interactive." } } # Fall back to interactive authentication Write-Verbose "Acquiring token interactively for scopes: $($Scopes -join ', ')" try { $result = $app.AcquireTokenInteractive($Scopes).ExecuteAsync($cts.Token).GetAwaiter().GetResult() } catch [System.OperationCanceledException] { throw "Interactive authentication timed out after $TimeoutSeconds seconds. Please try again." } if ($null -eq $result -or [string]::IsNullOrEmpty($result.AccessToken)) { throw "Failed to acquire access token interactively." } # Cache the account for future silent acquisitions $script:AuthorizationServiceCache[$script:AuthorizationServiceCurrentKey].Account = $result.Account Write-Verbose "Successfully acquired token for account: $($result.Account.Username)" return (ConvertTo-SecureString -String $result.AccessToken -AsPlainText -Force) } finally { $cts.Dispose() } } # SIG # Begin signature block # MIInSQYJKoZIhvcNAQcCoIInOjCCJzYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDvIyx/uMnldvn6 # Q5DGNpEsiSe0DUB3eTgUFqKNRzxC8qCCDLowggX1MIID3aADAgECAhMzAAACHU0Z # yE7XD1dIAAAAAAIdMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNVBAYTAlVTMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBD # b2RlIFNpZ25pbmcgUENBIDIwMjQwHhcNMjYwNDE2MTg1OTQzWhcNMjcwNDE1MTg1 # OTQzWjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE # BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYD # VQQDExVNaWNyb3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IB # DwAwggEKAoIBAQDQvewXxx9gZZFC6Ys1WBay8BJ8kGA4JQnH5CMafqOASlTpK9H8 # o5ZXTXt0caVQTNMUPt445wXYD+dFtaKWTwDn1I52oUSrC9vJin1Gsqt+zyKJL5Dg # 3eQXbQNR61DmMy20GLTIO3SFed9Rfi/ophgCLGFLDR3r0KvHjwMb/jYWS0celV/4 # Lz27LfAekm8v9E5IXaeiXbAUYZKK090n4CVl3JBtbN+9DtI9SNu/yjvozW52/u7R # X/Ttpa/KDlpuokZ+Zcbvmtd9ur9gFLvZzh41o9MsE/clQtdaFWGvuo6Jua/ntpgk # ey3E5/vBFe+MJPG6phdnuo6r57ZudCudiI1bAgMBAAGjggGbMIIBlzAOBgNVHQ8B # Af8EBAMCB4AwHwYDVR0lBBgwFgYKKwYBBAGCN0wIAQYIKwYBBQUHAwMwHQYDVR0O # BBYEFH6QuMwqcPG0hQlQ6c5jCtTTLrVeMEUGA1UdEQQ+MDykOjA4MR4wHAYDVQQL # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xFjAUBgNVBAUTDTIzMDAxMis1MDc1NTkw # HwYDVR0jBBgwFoAUf1k/VCHarU/vBeXmo9ctBpQSCDEwYAYDVR0fBFkwVzBVoFOg # UYZPaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jcmwvTWljcm9zb2Z0 # JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNybDBtBggrBgEFBQcBAQRh # MF8wXQYIKwYBBQUHMAKGUWh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMv # Y2VydHMvTWljcm9zb2Z0JTIwQ29kZSUyMFNpZ25pbmclMjBQQ0ElMjAyMDI0LmNy # dDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4ICAQBKTbYOjzwTG/DXGaz9 # s6+fQeaTtDcFmMY+5UyVFCyj7Pv+5i37qfX8lSL/tBIfYQfWsMuBQlfZurJD6r4H # VJ2CeH+1fgiq8dcHdVKoZ3Sa2qXoX3cq9iS8cVb06B7+5/XJ7I0OxHH9fDsvJ3T3 # w5V/ZtAIFmLrl+P0CtG+92uzRsn0nTbdFjOkLMLWPLAU3THohKRlSEMgFJpPkm5n # 5UAZ35xX6FWCrDLsSKb555bTifwa8mJBwdlof0bmfYidH+dxZ1FdDxvLnNl9zeKs # A4kejaaIqqIPguhwAti5Ql7BlTNoJNwxCvBmqW2MQLnCkYN/VVUsR3V2x/rcTNzo # Bf/Z/SpROvdaA2ZOOd1uioXJt3tdLQ7vHpqpib0KfWr/FWXW10q38VxfCnRQBqzb # SuztR7nEMuzX7Ck+B/XaPDXd1qh72+QYyB0Z2VzWmO9zsnb9Uq/dwu8LGeQqnyu6 # 7SDGACvnXii2fb9+US492VTnXSnFKyqwgzUyFMtZK1/sHYTv6bG4TtQUygQxTN+Z # V+aJIlKO2MqZ7bKrAnOzS9m6NgoTdWOq11bTOZwKlIEV/EhV9SWkDmdpR/hPPT2v # 6TEj4F8PT/zHjRezIU5c/DGlt/VhY/pK0XkJtEyMmmS1BMtjU/rqBZVMIm3dnxQs # /TBByr+Cf8Z1r7aifQVQ+WSqzjCCBr0wggSloAMCAQICEzMAAAA5O7Y3Gb8GHWcA # AAAAADkwDQYJKoZIhvcNAQEMBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDExMB4XDTI0MDgwODIwNTQxOFoXDTM2MDMyMjIyMTMwNFow # VzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo # MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAyNDCCAiIwDQYJ # KoZIhvcNAQEBBQADggIPADCCAgoCggIBANgBnB7jOMeqlRYHNa265v4IY9fH8TKh # emHfPINe1gpLaV3dhg324WwH06LcHbpnsBukCDNitryo0dtS/EW6I/yEL/bLSY8h # KpbfQuWusBPr9qazYcDxCW/qnjb5JsI1s8bNOg3bVATvQVL4tcf03aTycsz8QeCd # M0l/yHRObJ9QqazM1r6VPEOJ7LL+uEEb73w6QCuhs89a1uv1zerOYMnsneRRwCbp # yW11IcggU0cRKDDq1pjVJzIbIF6+oiXXbReOsgeI8zu1FyQfK0fVkaya8SmVHQ/t # Of23mZ4W9k0Ri22QW9p3UgSC5OUDktKxxcCmGL6tXLfOGSWHIIV4YrTJTT6PNty5 # REojHJuZHArkF9VnHTERWoTjAzfI3kP+5b4alUdhgAZ7ttOu1bVnXfHaqPYl2rPs # 20ji03LOVWsh/radgE17es5hL+t6lV0eVHrVhsssROWJuz2MXMCt7iw7lFPG9LXK # Gjsmonn2gotGdHIuEg5JnJMJVmixd5LRlkmgYRZKzhxSCwyoGIq0PhaA7Y+VPct5 # pCHkijcIIDm0nlkK+0KyepolcqGm0T/GYQRMhHJlGOOmVQop36wUVUYklUy++vDW # eEgEo4s7hxN6mIbf2MSIQ/iIfMZgJxC69oukMUXCrOC3SkE/xIkgpfl22MM1itkZ # 35nNXkMolU1lAgMBAAGjggFOMIIBSjAOBgNVHQ8BAf8EBAMCAYYwEAYJKwYBBAGC # NxUBBAMCAQAwHQYDVR0OBBYEFH9ZP1Qh2q1P7wXl5qPXLQaUEggxMBkGCSsGAQQB # gjcUAgQMHgoAUwB1AGIAQwBBMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAU # ci06AjGQQ7kUBU7h6qfHMdEjiTQwWgYDVR0fBFMwUTBPoE2gS4ZJaHR0cDovL2Ny # bC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNybDBeBggrBgEFBQcBAQRSMFAwTgYIKwYBBQUHMAKGQmh0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0MjAx # MV8yMDExXzAzXzIyLmNydDANBgkqhkiG9w0BAQwFAAOCAgEAFJQfOChP7onn6fLI # MKrSlN1WYKwDFgAddymOUO3FrM8d7B/W/iQ6DxXsDn7D5W4wMwYeLystcEqfkjz4 # NURRgazyMu5yRzQh4LqjA4tStTcJh1opExo7nn5PuPBYnbu0+THSuVHTe0VTTPVh # ily/piFrDo3axQ9P4C+Ol5yet+2gTfekICS5xS+cYfSIvgn0JksVBVMYVI5QFu/q # hnLhsEFEUzG8fvv0hjgkO+lkpV9ty6GkN4vdnd7ya6Q6aR9y34aiM1qmxaxBi6OU # nyNl6fkuun/diTFnYDLTppOkr/mg5WSfCiDVMNCxtj4wPKC5OmHm1DQIt/MNokbb # H3UGsFP1QbzsLocuSqLCvH09Io3fDPTmscR9Y75G4qX7RTX8AdBPo0I6OEojf39z # uFZt0qOHm65YWQE69cZM2ueE1MB05dNNgHK9gTE7zKvK/fg8B2qjW88MT/WF5V5u # vZGtqa9FSL2RazArA+rDPuf6JGYz4HpgMZHB4S6szWSKYBv0VisCzfxgeU+dquXW # 9bd0auYlOB58DPcOYKdc3Se94g+xL4pcEhbB54JOgAkwYTu/9dLeH2pDqeJZAABV # DWRQCaXfO5LgyKwKCLYXpigrZYCjUSBcr+Ve8PFWMhVTQl0v4q8J/AUmQN5W4n10 # 1cY2L4A7GTQG1h32HHAvfQESWP0xghnlMIIZ4QIBATBuMFcxCzAJBgNVBAYTAlVT # MR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jv # c29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMjQCEzMAAAIdTRnITtcPV0gAAAAAAh0w # DQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYK # KwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIHr//1uH # hOtyW75eF3FvyJ2uIZVjaLJcRzqORpxMnpXDMEIGCisGAQQBgjcCAQwxNDAyoBSA # EgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20w # DQYJKoZIhvcNAQEBBQAEggEAiqUm2FNo/d4HvWl4rPuqMHozK2SI3GS5TwupUnYm # uZP9NapjnCy9K0smZ5KXGjHw4xuRKyae+FFHneURlDSRni4o+EO0Pc2hWjJRL7in # 8rtoHGM7jx8KutHSFUFzb29ydQILT3JAeKcZzFca5p/RRUk9nN4bzoC5l548dVnJ # SlLoMkB1N7WTDqZTBwx7m4nNuKEZYj/Exrb0nrjSrc0jsgJg1ye3/KQCk1tbEzsn # dho86X3l9D2qWqXRQspFUgvcH7rrUugAwMiuwbAAKD2/haIFnMPqg1g8SqSqXc10 # +cWpdKsOq0r1njqhDxenz+OGZQDtZb8jxH4k14WfsX8qyaGCF5cwgheTBgorBgEE # AYI3AwMBMYIXgzCCF38GCSqGSIb3DQEHAqCCF3AwghdsAgEDMQ8wDQYJYIZIAWUD # BAIBBQAwggFSBgsqhkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoD # ATAxMA0GCWCGSAFlAwQCAQUABCCkk9P5aTCoJE67/dp2iHQ8WPyRVC1i4wKu+X35 # WtIK2QIGalosYtu5GBMyMDI2MDgwNzE2MTEzMy4yODlaMASAAgH0oIHRpIHOMIHL # MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVk # bW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxN # aWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRT # UyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0 # YW1wIFNlcnZpY2WgghHtMIIHIDCCBQigAwIBAgITMwAAAiJB0vaq/8i1/wABAAAC # IjANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu # Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv # cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAe # Fw0yNjAyMTkxOTM5NTZaFw0yNzA1MTcxOTM5NTZaMIHLMQswCQYDVQQGEwJVUzET # MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV # TWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmlj # YSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUw # LUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIi # MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1ueKJukIuUsAAJo/AY5DZRqH7 # bhgv7CWGNlEdbRGoITrdE6Wsn57NaNu1BTdjBbFcv7Rfixte0x+HRvXSqsD+WeSX # /6/y9wE0Mz+xRPTGIY20K7aQDa68OyzVyUeUCypyZC/gW/3ytO/ZOnU9H2ri77kJ # P8ABrqyy1UxX/OseEgvHsj8yikWT0ARtrjWbXMHFzSOo5hQcfUmMXKqWWz6+N0+U # ynhGy1n+doW4WZgpH8Y5W7hpSokWj1M/Lu4wi3o6Dz9vVWukcgUFGjLAl4YZpOha # h7HuiC/alXImMQf8C3A8q/6/1hFoeIZB4UGkywxB/OSTOSsL6+39pDqzM7CgOpf4 # V799kN94yM9uXJI5T/SiA5MdIZIhEW0+bh85RqDh5YW3/oav54RPxw5OPlH64QV6 # KJkl0FIElMVoLNo8UWRQcMD179x7WASjC6LsaNZ7yK0qcESIsL1wiQmdfQBxcqrF # CpIQfnmQFkOp9IyXUWqza8tmpz8E6aXg9b1eiAT3PVTgrOlPi/hYZCfPxX/6jGty # Pjy1CiwOmJamohmSU//COAenfRT2G2HMRUpCX1zs+AmDmdQM1XRab4YSALLAlDzG # CsgI77nnuJjoXAliJmv7NfrvWAcA5KqCUOWQ6kSPt5r28MfKXWJJpSXtFeS/MkDz # Jy/iJRVyHcFy/B+MtwIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFFkHwGoDJ5ZbEEiu # 8KstiusqaozQMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1Ud # HwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3Js # L01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggr # BgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNv # bS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIw # MTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgw # DgYDVR0PAQH/BAQDAgeAMA0GCSqGSIb3DQEBCwUAA4ICAQBiAM+nqrpwG29txSXv # 42o+CsTe2C4boaRfFju9JaWkLTHwq7pknNONL3n+UG3x/B083EKXiFYrAmul7BTH # CGXU63/xRsZ2wj3ZmR0A4d9nf9saCJVm4juPVFBai/oktOOYH2j+1+zM70woN5on # gB/pvy7X8AfY6JB4XPvb80Qz7fY5eddbnwjzg1sZhUPFbbcweWeACINrzqFK62mM # eXKmhtufMraoogJeJXfWY3x4/pbubgENT3+pXT65203CPF9kfdKE7GKAIRYy3xkB # TDvFd8dufjOpCn38nK6qMlVtnBjDhWQG0PM3E/oxBs5UBrI6pBYkmIHtbjifDquH # T+ThaVV7xHc6InoSc3aNzX49JHUgQmuvDdMjLkbYXeA0/1q5IxSg2U+ycZBOvAi3 # udZPKhA5VzODjf/ucu/vFtXrYcRkmGKN3jujaK3/yMZi2Ju5NEL3ISWorwp7RjeZ # g+JMIK0fosuVj+YCm5r64LH/D9QJDAj+XfZaNeFdv90K5A0QRRGP/poB9yTIVjEX # j/uJzp8L4Dd44sAquqDOiHdkLgxfK8nPqpCSWPZ9G+RCPm85o9cAfxENtrSuOwcp # yKzxsRCYCL+PK4+98orit9EVJ/LLoCeG+jLlj0KaD4Qy6sZe4rWMr1brQLosTBZN # wFnXxNjInCWBd0i7is1yTS/4qTCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkA # AAAAABUwDQYJKoZIhvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX # YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg # Q29ycG9yYXRpb24xMjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRl # IEF1dGhvcml0eSAyMDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVow # fDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl # ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMd # TWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUA # A4ICDwAwggIKAoICAQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX # 9gF/bErg4r25PhdgM/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1q # UoNEt6aORmsHFPPFdvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8d # q6z2Nr41JmTamDu6GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byN # pOORj7I5LFGc6XBpDco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2k # rnopN6zL64NF50ZuyjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4d # Pf0gz3N9QZpGdc3EXzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgS # Uei/BQOj0XOmTTd0lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8 # QmguEOqEUUbi0b1qGFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6Cm # gyFdXzB0kZSU2LlQ+QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzF # ER1y7435UsSFF5PAPBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQID # AQABo4IB3TCCAdkwEgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQU # KqdS/mTEmr6CkTxGNSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1 # GelyMFwGA1UdIARVMFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0 # dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0 # bTATBgNVHSUEDDAKBggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMA # QTALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbL # j+iiXGJo0T2UkFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1p # Y3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0w # Ni0yMy5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIz # LmNydDANBgkqhkiG9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwU # tj5OR2R4sQaTlz0xM7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN # 3Zi6th542DYunKmCVgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU # 5HhTdSRXud2f8449xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5 # KYnDvBewVIVCs/wMnosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGy # qVvfSaN0DLzskYDSPeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB6 # 2FD+CljdQDzHVG2dY3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltE # AY5aGZFrDZ+kKNxnGSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFp # AUR+fKFhbHP+CrvsQWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcd # FYmNcP7ntdAoGokLjzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRb # atGePu1+oDEzfbzL6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQd # VTNYs6FwZvKhggNQMIICOAIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzAR # BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p # Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg # T3BlcmF0aW9uczEnMCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVFMC1E # OTQ3MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEw # BwYFKw4DAhoDFQC7ycXVZx3bsDpJkr7VucgpksozuKCBgzCBgKR+MHwxCzAJBgNV # BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w # HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29m # dCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA7iBXZDAiGA8y # MDI2MDgwNzEzMDg1MloYDzIwMjYwODA4MTMwODUyWjB3MD0GCisGAQQBhFkKBAEx # LzAtMAoCBQDuIFdkAgEAMAoCAQACAidMAgH/MAcCAQACAhPMMAoCBQDuIajkAgEA # MDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAI # AgEAAgMBhqAwDQYJKoZIhvcNAQELBQADggEBAE0YLUwgd3Lmnzb1W9x6PDvRTf8W # bZ2ygnhT7HLXVxbVmbRispqYxImxtlaHkxUxlbLft8HR6OLnrm3PMq3tTtH5du/9 # j/xZgZX4zT5weZa4E1pbenpTOTEKIuDHvKVWz1ifmLsdVADz2FiL4JHq29pNMq2h # kASS5l81avGTiAtRj0K7YRTXtuX9hflPKQ0o/Aa8znduqPxpDc2hPnWL2T0dY0bT # zXLKaWdnOnFiBc1p9/v50+LndSi2k/6QMKZdyTfkHHR+rcZZl1mjK+0PM4UYhbzm # U9TKsEt1lnne5W13+lHY+XXTzSQfM5JAQq4RYm1z6EG7Zwt47l00AY5NlbwxggQN # MIIECQIBATCBkzB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ # MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u # MSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAiJB # 0vaq/8i1/wABAAACIjANBglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0G # CyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEiBCCJpHW/rl6Rh2zkW2sCWgpMr9aF # RoPyqkW9WRqHe0nIsjCB+gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EIAVgXQEK # BOfGgjNskmDOmbcEIOnHGNwA+QcRufDR5AkTMIGYMIGApH4wfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAIiQdL2qv/Itf8AAQAAAiIwIgQgXXVYrg11 # +ihBfBd0HzWLCpffRtecI2MpA2Rgw76rHsswDQYJKoZIhvcNAQELBQAEggIAc2jr # k5JBGR/8Gol13IfuQZW4wtwTwYOflpA9+izJK5mkxc9Bk8qH6TIU5LyulOsr8orX # wKc065k1bTQHEH/sTFH5e+d/TJw38pIKgOlXPcae+TXsA/u9LJ81KLjgRXpdvj1m # q/6+6ImMpn33VBKOE9JC6oS0ZP1afL5qz1+8OVfaK8YKKHz6XPVPT25+1wSQ5aKu # fOpDN+e9MLfq91mgEPEHp5adHtIcxa4iEFFICM1hXViDqOYxI/7D9HFicn926pZw # lxC3qv6l2UG74h3GeLI5+32e8kRELuFcAuIuEzlNP1SUFlBzTaNm2r3gG756nNLy # nHPyYhuYepS5EsJdXobn0UDFmxL+F+YalRFIM/bOFyVKmm29wmVeAYWMZPq02/nu # ZzV9gR3ZYhQFbhy50p4EzGa2XXF8uduIzjG+qMycYU358iVlmaONi8eBQo0nzT3h # fzHF81vE3Yp3pRVQ9kEcxAQgPPzCtk1jFTFkBfP3VW7UCBLxlnj/VIFttGBie2FB # axDUfc3+AbYTEPW8IgvNq3kT/rqJPfNZm5mb7uLI+QNeGb5mHQZsEW1hvfbIOF7g # rOLCCLSvwlO/m+KclpZts4lTI/KplfiCe9gD/aQFM/Vo9Gss2h9zjAN5GRjS8etG # cUS2dcAPddmdczIWHb7hM2j6ycid8tWN5OS4OLM= # SIG # End signature block |