EPM/Policy.ps1
|
class EpmPolicyDataInfo { [string]$Name [string]$Type [System.Collections.Generic.List[string]]$Controls [string]$Users [string]$Machines [string]$Applications [string]$Collections EpmPolicyDataInfo() { $this.Name = '' $this.Type = '' $this.Controls = [System.Collections.Generic.List[string]]::new() $this.Users = '' $this.Machines = '' $this.Applications = '' $this.Collections = '' } } class EpmDateRange { [long]$StartDate [long]$EndDate EpmDateRange([long]$start, [long]$end) { $this.StartDate = $start $this.EndDate = $end } } class EpmTimeRange { [string]$StartTime [string]$EndTime EpmTimeRange([string]$start, [string]$end) { $this.StartTime = $start $this.EndTime = $end } } class EpmPolicyFilterResult { [int[]]$DayCheck [EpmDateRange[]]$DateCheck [EpmTimeRange[]]$TimeCheck EpmPolicyFilterResult() { $this.DayCheck = $null $this.DateCheck = $null $this.TimeCheck = $null } } class EpmPolicyRule { [string]$RuleName [string]$ErrorMessage [string]$RuleExpressionType [string]$Expression EpmPolicyRule([string]$name, [string]$error, [string]$exprType, [string]$expr) { $this.RuleName = $name $this.ErrorMessage = $error $this.RuleExpressionType = $exprType $this.Expression = $expr } } class EpmPolicyListRow { [string]$PolicyUid [string]$PolicyName [string]$PolicyType [string]$Status [string]$Controls [string]$Users [string]$Machines [string]$Applications [string]$Collections } class EpmPolicyAgentRow { [string]$Key [string]$UID [string]$Name [string]$Status } function script:Get-KeeperEpmPolicyListStatus { Param ($Policy) if ($Policy.Disabled) { return [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Off } try { if ($null -ne $Policy.Data) { $d = $Policy.Data if ($null -ne $d.Status -and $d.Status -ne '') { return [string]$d.Status } } elseif ($Policy.PolicyData -and $Policy.PolicyData.Length -gt 0) { $jsonText = [System.Text.Encoding]::UTF8.GetString($Policy.PolicyData) if ([string]::IsNullOrWhiteSpace($jsonText)) { Write-Warning "Get-KeeperEpmPolicyListStatus: PolicyData decoded to empty string for policy '$($Policy.PolicyUid)'." } else { $jo = $jsonText | ConvertFrom-Json -ErrorAction Stop if ($null -ne $jo -and $jo.PSObject.Properties['Status']) { return [string]$jo.Status } } } } catch { Write-Warning "Get-KeeperEpmPolicyListStatus: Failed to parse policy data for '$($Policy.PolicyUid)': $($_.Exception.Message)" } return [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Enforce } function script:Get-KeeperEpmPolicyDataInfo { Param ( [Parameter(Mandatory = $true)] $Policy, [Parameter(Mandatory = $true)] $Plugin ) $info = [EpmPolicyDataInfo]::new() $data = $Policy.Data if ($null -eq $data) { return $info } if ($null -ne $data.PolicyName) { $info.Name = [string]$data.PolicyName } if ($null -ne $data.PolicyType) { $info.Type = [string]$data.PolicyType } if ($null -ne $data.Actions -and $null -ne $data.Actions.OnSuccess -and $data.Actions.OnSuccess.Controls) { foreach ($control in $data.Actions.OnSuccess.Controls) { if ($null -eq $control) { continue } $controlStr = [string]$control if ([string]::IsNullOrEmpty($controlStr)) { continue } $upper = $controlStr.ToUpperInvariant() if ($upper -eq [KeeperSecurity.Plugins.EPM.EpmPolicyControl]::Approval) { [void]$info.Controls.Add([KeeperSecurity.Plugins.EPM.EpmPolicyControl]::Approval) } elseif ($upper -eq [KeeperSecurity.Plugins.EPM.EpmPolicyControl]::Justify) { [void]$info.Controls.Add([KeeperSecurity.Plugins.EPM.EpmPolicyControl]::Justify) } elseif ($upper -eq [KeeperSecurity.Plugins.EPM.EpmPolicyControl]::Mfa) { [void]$info.Controls.Add([KeeperSecurity.Plugins.EPM.EpmPolicyControl]::Mfa) } else { [void]$info.Controls.Add($upper) } } } if ($data.UserCheck -and $data.UserCheck.Count -gt 0) { $info.Users = $data.UserCheck -join ', ' } if ($data.MachineCheck -and $data.MachineCheck.Count -gt 0) { $info.Machines = $data.MachineCheck -join ', ' } if ($data.ApplicationCheck -and $data.ApplicationCheck.Count -gt 0) { $info.Applications = $data.ApplicationCheck -join ', ' } try { $allAgentsUid = $Plugin.AllAgentsCollectionUid $policyLinks = @($Plugin.GetCollectionLinksForObject($Policy.PolicyUid)) $collectionUids = [System.Collections.Generic.List[string]]::new() foreach ($link in $policyLinks) { $collUid = $link.Item1 if (-not [string]::IsNullOrEmpty($collUid)) { if ($null -ne $allAgentsUid -and $collUid -eq $allAgentsUid) { [void]$collectionUids.Add('*') } else { [void]$collectionUids.Add($collUid) } } } $collectionUids.Sort() $info.Collections = $collectionUids -join ', ' } catch { Write-Debug "GetCollectionLinksForObject: $($_.Exception.Message)" } return $info } function script:Resolve-KeeperEpmPolicy { Param ( [Parameter(Mandatory = $true)] [string] $Identifier, [Parameter(Mandatory = $true)] $Plugin ) if ([string]::IsNullOrEmpty($Identifier)) { throw "Identifier cannot be null or empty" } $id = $Identifier.Trim() if ([string]::IsNullOrEmpty($id)) { throw "Identifier cannot be whitespace-only" } $policy = $Plugin.Policies.GetEntity($id) if ($null -ne $policy) { return @($policy) } $matched = [System.Collections.Generic.List[object]]::new() foreach ($p in $Plugin.Policies.GetAll()) { $pInfo = Get-KeeperEpmPolicyDataInfo -Policy $p -Plugin $Plugin if (-not [string]::IsNullOrEmpty($pInfo.Name) -and $pInfo.Name.Equals($id, [System.StringComparison]::OrdinalIgnoreCase)) { [void]$matched.Add($p) } } return @($matched) } function script:Resolve-KeeperEpmSinglePolicy { Param ( [Parameter(Mandatory = $true)] [string] $Identifier, [Parameter(Mandatory = $true)] [object] $Plugin ) $policies = @(Resolve-KeeperEpmPolicy -Identifier $Identifier -Plugin $Plugin) if ($policies.Count -eq 0) { Write-Error -Message "Policy '$Identifier' not found." -ErrorAction Stop } if ($policies.Count -gt 1) { Write-Warning "Multiple policies match name `"$Identifier`":" foreach ($p in $policies) { $info = Get-KeeperEpmPolicyDataInfo -Policy $p -Plugin $Plugin Write-Warning " UID: $($p.PolicyUid) Name: $($info.Name)" } Write-Error -Message "Policy name `"$Identifier`" is not unique. Use Policy UID." -ErrorAction Stop } return $policies[0] } function script:Confirm-EpmPolicyFilterParams { Param ( [string[]] $DayFilter, [string[]] $DateFilter, [string[]] $TimeFilter ) $validDays = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) 'sunday','monday','tuesday','wednesday','thursday','friday','saturday' | ForEach-Object { [void]$validDays.Add($_) } $dayMap = @{ sunday = 0; monday = 1; tuesday = 2; wednesday = 3; thursday = 4; friday = 5; saturday = 6 } $result = [EpmPolicyFilterResult]::new() if ($DayFilter) { foreach ($day in $DayFilter) { if (-not $validDays.Contains($day.Trim())) { throw "Invalid day '$day'. Allowed values: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday." } } $result.DayCheck = @($DayFilter | ForEach-Object { $dayMap[$_.Trim().ToLowerInvariant()] }) } if ($DateFilter) { $dateRanges = [System.Collections.Generic.List[EpmDateRange]]::new() $dateFmt = 'yyyy-MM-dd' $culture = [System.Globalization.CultureInfo]::InvariantCulture foreach ($df in $DateFilter) { $parts = $df -split ':' if ($parts.Count -ne 2) { Write-Error -Message "Invalid date filter format '$df'. Use YYYY-MM-DD:YYYY-MM-DD." -ErrorAction Stop } try { $startDate = [DateTimeOffset]::ParseExact($parts[0].Trim(), $dateFmt, $culture).ToUnixTimeMilliseconds() } catch { Write-Error -Message "Invalid start date '$($parts[0])'. Use format YYYY-MM-DD." -ErrorAction Stop } try { $endDate = [DateTimeOffset]::ParseExact($parts[1].Trim(), $dateFmt, $culture).ToUnixTimeMilliseconds() } catch { Write-Error -Message "Invalid end date '$($parts[1])'. Use format YYYY-MM-DD." -ErrorAction Stop } if ($endDate -lt $startDate) { Write-Error -Message "Date range end '$($parts[1])' is before start '$($parts[0])'." -ErrorAction Stop } [void]$dateRanges.Add([EpmDateRange]::new($startDate, $endDate)) } $result.DateCheck = @($dateRanges) } if ($TimeFilter) { $timeRanges = [System.Collections.Generic.List[EpmTimeRange]]::new() foreach ($tf in $TimeFilter) { $parts = $tf -split '-' if ($parts.Count -ne 2) { Write-Error -Message "Invalid time filter format '$tf'. Use HH-HH (e.g. 09-17)." -ErrorAction Stop } $startHour = 0; $endHour = 0 if (-not [int]::TryParse($parts[0], [ref]$startHour) -or -not [int]::TryParse($parts[1], [ref]$endHour)) { Write-Error -Message "Invalid time filter '$tf'. Hours must be numeric." -ErrorAction Stop } if ($startHour -lt 0 -or $startHour -gt 23 -or $endHour -lt 0 -or $endHour -gt 23) { Write-Error -Message "Invalid time filter '$tf'. Hours must be between 0 and 23." -ErrorAction Stop } if ($startHour -eq $endHour) { Write-Error -Message "Invalid time filter '$tf'. Start and end hours cannot be equal (zero-width range)." -ErrorAction Stop } [void]$timeRanges.Add([EpmTimeRange]::new($parts[0], $parts[1])) } $result.TimeCheck = @($timeRanges) } return $result } function script:Assert-EpmPolicyRequiredParams { Param ( [string] $PolicyType, [bool] $HasMachine, [bool] $HasUser, [bool] $HasApp, [bool] $HasControl, [string] $ErrorSuffix ) $rules = @{ 'PrivilegeElevation' = @{ MachineFilter = $HasMachine; UserFilter = $HasUser; AppFilter = $HasApp; Control = $HasControl } 'FileAccess' = @{ MachineFilter = $HasMachine; UserFilter = $HasUser; AppFilter = $HasApp; Control = $HasControl } 'CommandLine' = @{ MachineFilter = $HasMachine; UserFilter = $HasUser; Control = $HasControl } 'LeastPrivilege' = @{ MachineFilter = $HasMachine } } $required = $rules[$PolicyType] if (-not $required) { return } $missing = @($required.GetEnumerator() | Where-Object { -not $_.Value } | ForEach-Object { "-$($_.Key)" }) if ($missing.Count -gt 0) { $msg = "Policy type '$PolicyType' requires the following parameters: $($missing -join ', ')" if ($ErrorSuffix) { $msg += ". $ErrorSuffix" } Write-Error -Message $msg -ErrorAction Stop } } function script:GetPedmPolicyAgentsResponse { Param ( [Parameter(Mandatory = $true)] [KeeperSecurity.Authentication.IAuthentication] $Auth, [Parameter(Mandatory = $true)] [string[]] $PolicyUids ) $rq = New-Object PEDM.PolicyAgentRequest $rq.SummaryOnly = $false foreach ($uid in $PolicyUids) { try { $b = [KeeperSecurity.Utils.CryptoUtils]::Base64UrlDecode($uid) } catch { Write-Error -Message "Invalid policy UID '$uid': $($_.Exception.Message)" -ErrorAction Stop } [void]$rq.PolicyUid.Add([Google.Protobuf.ByteString]::CopyFrom($b)) } $executeRouterMethod = [KeeperSecurity.Authentication.AuthExtensions].GetMethods() | Where-Object { $_.Name -eq 'ExecuteRouter' -and $_.GetGenericArguments().Count -eq 1 } | Select-Object -First 1 $genericMethod = $executeRouterMethod.MakeGenericMethod([PEDM.PolicyAgentResponse]) $task = $genericMethod.Invoke($null, @($Auth, 'pedm/get_policy_agents', [Google.Protobuf.IMessage]$rq)) return $task.GetAwaiter().GetResult() } function Get-KeeperEpmPolicyList { <# .Synopsis List EPM/PEDM policies. .Description Takes no parameters; returns a table of policies with status, controls, and scope fields. #> [CmdletBinding()] Param () $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $policies = @($plugin.Policies.GetAll()) if ($policies.Count -eq 0) { Write-Output 'No policies found.' return } $rows = [System.Collections.Generic.List[EpmPolicyListRow]]::new() foreach ($pol in ($policies | Sort-Object -Property PolicyUid)) { $policyInfo = Get-KeeperEpmPolicyDataInfo -Policy $pol -Plugin $plugin $status = Get-KeeperEpmPolicyListStatus -Policy $pol $row = [EpmPolicyListRow]::new() $row.PolicyUid = $pol.PolicyUid $row.PolicyName = $policyInfo.Name $row.PolicyType = $policyInfo.Type $row.Status = $status $row.Controls = ($policyInfo.Controls -join "`n").Trim() $row.Users = $policyInfo.Users $row.Machines = $policyInfo.Machines $row.Applications = $policyInfo.Applications $row.Collections = $policyInfo.Collections [void]$rows.Add($row) } $rows | Format-Table -AutoSize -Wrap } function Get-KeeperEpmPolicy { <# .Synopsis View an EPM policy by UID or name. .Parameter PolicyUidOrName Policy UID or policy display name (case-insensitive match on name). #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0)] [string] $PolicyUidOrName ) $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $policy = Resolve-KeeperEpmSinglePolicy -Identifier $PolicyUidOrName -Plugin $plugin $policyInfo = Get-KeeperEpmPolicyDataInfo -Policy $policy -Plugin $plugin Write-Output "Policy: $($policyInfo.Name)" Write-Output " UID: $($policy.PolicyUid)" Write-Output " Type: $($policyInfo.Type)" $d = $policy.Data $displayStatus = Get-KeeperEpmPolicyListStatus -Policy $policy Write-Output " Status: $displayStatus" if ($null -eq $d) { Write-Output " (Policy data could not be decrypted)" } else { if (-not [string]::IsNullOrEmpty($d.PolicyId)) { Write-Output " Policy ID: $($d.PolicyId)" } if (-not [string]::IsNullOrEmpty($d.NotificationMessage)) { Write-Output " Notification Message: $($d.NotificationMessage)" } if ($d.NotificationRequiresAcknowledge) { Write-Output " Notification Requires Acknowledge: $($d.NotificationRequiresAcknowledge)" } if ($d.RiskLevel -gt 0) { Write-Output " Risk Level: $($d.RiskLevel)" } if (-not [string]::IsNullOrEmpty($d.Operator)) { Write-Output " Operator: $($d.Operator)" } if ($d.Rules -and $d.Rules.Count -gt 0) { Write-Output " Rules ($($d.Rules.Count)):" foreach ($rule in $d.Rules) { Write-Output " - $($rule.RuleName): $($rule.Expression) ($($rule.RuleExpressionType))" if (-not [string]::IsNullOrEmpty($rule.ErrorMessage)) { Write-Output " Error: $($rule.ErrorMessage)" } } } if ($null -ne $d.Actions) { if ($d.Actions.OnSuccess -and $d.Actions.OnSuccess.Controls -and $d.Actions.OnSuccess.Controls.Count -gt 0) { Write-Output " On Success Controls: $($d.Actions.OnSuccess.Controls -join ', ')" } if ($d.Actions.OnFailure -and -not [string]::IsNullOrEmpty($d.Actions.OnFailure.Command)) { Write-Output " On Failure Command: $($d.Actions.OnFailure.Command)" } } } if ($policyInfo.Controls.Count -gt 0) { Write-Output " Controls: $($policyInfo.Controls -join ', ')" } if (-not [string]::IsNullOrEmpty($policyInfo.Users)) { Write-Output " Users: $($policyInfo.Users)" } if (-not [string]::IsNullOrEmpty($policyInfo.Machines)) { Write-Output " Machines: $($policyInfo.Machines)" } if (-not [string]::IsNullOrEmpty($policyInfo.Applications)) { Write-Output " Applications: $($policyInfo.Applications)" } if (-not [string]::IsNullOrEmpty($policyInfo.Collections)) { Write-Output " Collections: $($policyInfo.Collections)" } if ($null -ne $d) { if ($d.DayCheck -and $d.DayCheck.Count -gt 0) { $dayNames = @('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday') $days = foreach ($x in $d.DayCheck) { $dayNames[$x % 7] } Write-Output " Allowed Days: $($days -join ', ')" } if ($d.DateCheck -and $d.DateCheck.Count -gt 0) { Write-Output " Date Ranges ($($d.DateCheck.Count)):" foreach ($dateRange in $d.DateCheck) { $start = [DateTimeOffset]::FromUnixTimeMilliseconds($dateRange.StartDate).ToString('yyyy-MM-dd') $end = [DateTimeOffset]::FromUnixTimeMilliseconds($dateRange.EndDate).ToString('yyyy-MM-dd') Write-Output " - $start to $end" } } if ($d.TimeCheck -and $d.TimeCheck.Count -gt 0) { Write-Output " Time Ranges ($($d.TimeCheck.Count)):" foreach ($timeRange in $d.TimeCheck) { Write-Output " - $($timeRange.StartTime) to $($timeRange.EndTime)" } } if ($d.CertificationCheck -and $d.CertificationCheck.Count -gt 0) { Write-Output " Certification Checks: $($d.CertificationCheck -join ', ')" } if ($d.Extension -and $d.Extension.Count -gt 0) { Write-Output " Extensions ($($d.Extension.Count) custom fields)" } } Write-Output " Created: $([DateTimeOffset]::FromUnixTimeMilliseconds($policy.Created).ToString('yyyy-MM-dd HH:mm:ss'))" Write-Output " Updated: $([DateTimeOffset]::FromUnixTimeMilliseconds($policy.Updated).ToString('yyyy-MM-dd HH:mm:ss'))" } function Add-KeeperEpmPolicy { <# .Synopsis Add an EPM policy. .Parameter PolicyName Name for the policy (required). .Parameter PolicyType Policy type: PrivilegeElevation, FileAccess, CommandLine, LeastPrivilege. .Parameter Status Policy status: enforce, monitor, monitor_and_notify, off. .Parameter Control Policy controls (can specify multiple): APPROVAL, JUSTIFY, MFA. .Parameter UserFilter User collection UID(s) or '*' for all users. .Parameter MachineFilter Machine collection UID(s). .Parameter AppFilter Application collection UID(s). .Parameter RiskLevel Risk level (0-100). .Parameter NotificationMessage Notification message displayed to users. .Parameter NotificationRequiresAcknowledge Whether notification requires user acknowledgement. .Parameter DayFilter Allowed days of the week (can specify multiple): Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday. .Parameter DateFilter Date range(s) in format YYYY-MM-DD:YYYY-MM-DD (can specify multiple). .Parameter TimeFilter Time range(s) in 24-hour format HH-HH, e.g. "09-17" (can specify multiple). #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0)] [string] $PolicyName, [Parameter(Mandatory = $true)] [ValidateSet('PrivilegeElevation', 'FileAccess', 'CommandLine', 'LeastPrivilege')] [string] $PolicyType, [Parameter()] [ValidateSet('enforce', 'monitor', 'monitor_and_notify', 'off')] [string] $Status = [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Enforce, [Parameter()] [ValidateSet('APPROVAL', 'JUSTIFY', 'MFA')] [string[]] $Control, [Parameter()] [string[]] $UserFilter, [Parameter()] [string[]] $MachineFilter, [Parameter()] [string[]] $AppFilter, [Parameter()] [ValidateRange(0, 100)] [int] $RiskLevel = 50, [Parameter()] [string] $NotificationMessage, [Parameter()] [bool] $NotificationRequiresAcknowledge = $false, [Parameter()] [string[]] $DayFilter, [Parameter()] [string[]] $DateFilter, [Parameter()] [string[]] $TimeFilter ) $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } if ($Status -ne [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Off) { Assert-EpmPolicyRequiredParams -PolicyType $PolicyType ` -HasMachine ([bool]$MachineFilter) -HasUser ([bool]$UserFilter) ` -HasApp ([bool]$AppFilter) -HasControl ([bool]$Control) } $policyUid = [KeeperSecurity.Utils.CryptoUtils]::GenerateUid() $controls = @() if ($Control) { $controls = @($Control | ForEach-Object { $_.ToUpperInvariant() }) } $rules = @( [EpmPolicyRule]::new('UserCheck', 'This user is not included in this policy', 'BuiltInAction', 'CheckUser()') [EpmPolicyRule]::new('MachineCheck', 'This Machine is not included in this policy', 'BuiltInAction', 'CheckMachine()') [EpmPolicyRule]::new('ApplicationCheck', 'This application is not included in this policy', 'BuiltInAction', 'CheckFile(false)') [EpmPolicyRule]::new('DateCheck', 'Current date is not covered by this policy', 'BuiltInAction', 'CheckDate()') [EpmPolicyRule]::new('TimeCheck', 'Current time is not covered by this policy', 'BuiltInAction', 'CheckTime()') [EpmPolicyRule]::new('DayCheck', 'Today is not included in this policy', 'BuiltInAction', 'CheckDay()') ) $policyData = [ordered]@{ PolicyName = $PolicyName PolicyType = $PolicyType PolicyId = $policyUid Status = $Status Actions = @{ OnSuccess = @{ Controls = $controls } OnFailure = @{ Command = '' } } NotificationMessage = if ($NotificationMessage) { $NotificationMessage } else { '' } NotificationRequiresAcknowledge = $NotificationRequiresAcknowledge RiskLevel = $RiskLevel Operator = 'And' Rules = $rules } $validated = Confirm-EpmPolicyFilterParams -DayFilter $DayFilter -DateFilter $DateFilter -TimeFilter $TimeFilter if ($UserFilter) { $policyData['UserCheck'] = @($UserFilter) } if ($MachineFilter) { $policyData['MachineCheck'] = @($MachineFilter) } if ($AppFilter) { $policyData['ApplicationCheck'] = @($AppFilter) } if ($null -ne $validated.DayCheck) { $policyData['DayCheck'] = $validated.DayCheck } if ($null -ne $validated.DateCheck) { $policyData['DateCheck'] = $validated.DateCheck } if ($null -ne $validated.TimeCheck) { $policyData['TimeCheck'] = $validated.TimeCheck } $policyJson = $policyData | ConvertTo-Json -Depth 10 -Compress $plainData = [ordered]@{ PolicyName = $PolicyName PolicyType = $PolicyType Status = $Status } $plainJson = $plainData | ConvertTo-Json -Depth 5 -Compress $pi = New-Object KeeperSecurity.Plugins.EPM.EpmPlugin+PolicyInput $pi.PolicyUid = $policyUid $pi.PlainDataJson = $plainJson $pi.PolicyDataJson = $policyJson if ($Status -eq [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Off) { $pi.Disabled = $true } try { $addStatus = $plugin.ModifyPolicies([KeeperSecurity.Plugins.EPM.EpmPlugin+PolicyInput[]]@($pi), $null, $null).GetAwaiter().GetResult() if ($addStatus.AddErrors -and $addStatus.AddErrors.Count -gt 0) { $err = $addStatus.AddErrors[0] Write-Error -Message "Failed to add policy `"$($err.EntityUid)`": $($err.Message)" -ErrorAction Stop } if ($addStatus.Add -and $addStatus.Add.Count -gt 0) { Write-Output "Policy added. UID: $policyUid" } else { Write-Warning "No policy was added. Check server response." } writeEpmModifyStatus -Status $addStatus $plugin.SyncDown($false).GetAwaiter().GetResult() | Out-Null } catch { Write-Error -Message "Error adding policy: $($_.Exception.Message)" -ErrorAction Stop } } function Update-KeeperEpmPolicy { <# .Synopsis Update an EPM policy. .Parameter PolicyUidOrName Policy UID or policy display name (case-insensitive match on name). .Parameter PolicyName New policy name. .Parameter Status Policy status: enforce, monitor, monitor_and_notify, off. .Parameter Control Policy controls (can specify multiple): APPROVAL, JUSTIFY, MFA. .Parameter UserFilter User collection UID(s) or '*' for all users. .Parameter MachineFilter Machine collection UID(s). .Parameter AppFilter Application collection UID(s). .Parameter RiskLevel Risk level (0-100). .Parameter NotificationMessage Notification message displayed to users. .Parameter NotificationRequiresAcknowledge Whether notification requires user acknowledgement. .Parameter DayFilter Allowed days of the week (can specify multiple): Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday. .Parameter DateFilter Date range(s) in format YYYY-MM-DD:YYYY-MM-DD (can specify multiple). .Parameter TimeFilter Time range(s) in 24-hour format HH-HH, e.g. "09-17" (can specify multiple). #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0)] [string] $PolicyUidOrName, [Parameter()] [string] $PolicyName, [Parameter()] [ValidateSet('enforce', 'monitor', 'monitor_and_notify', 'off')] [string] $Status, [Parameter()] [ValidateSet('APPROVAL', 'JUSTIFY', 'MFA')] [string[]] $Control, [Parameter()] [string[]] $UserFilter, [Parameter()] [string[]] $MachineFilter, [Parameter()] [string[]] $AppFilter, [Parameter()] [ValidateRange(0, 100)] [Nullable[int]] $RiskLevel, [Parameter()] [string] $NotificationMessage, [Parameter()] [Nullable[bool]] $NotificationRequiresAcknowledge, [Parameter()] [string[]] $DayFilter, [Parameter()] [string[]] $DateFilter, [Parameter()] [string[]] $TimeFilter ) $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $policy = Resolve-KeeperEpmSinglePolicy -Identifier $PolicyUidOrName -Plugin $plugin $hasChanges = $false $policyData = $null if ($null -ne $policy.Data) { # Deep-clone via JSON round-trip to avoid mutating the cached SDK object try { $existingJson = $policy.Data | ConvertTo-Json -Depth 10 $policyData = $existingJson | ConvertFrom-Json } catch { Write-Error -Message "Failed to clone existing policy data for '$($policy.PolicyUid)': $($_.Exception.Message)" -ErrorAction Stop } } if ($null -eq $policyData) { $policyData = [PSCustomObject]@{} } if (-not [string]::IsNullOrEmpty($PolicyName)) { $policyData | Add-Member -MemberType NoteProperty -Name 'PolicyName' -Value $PolicyName -Force $hasChanges = $true } if (-not [string]::IsNullOrEmpty($Status)) { $policyData | Add-Member -MemberType NoteProperty -Name 'Status' -Value $Status -Force $hasChanges = $true } if ($null -ne $Control) { $controls = @($Control | ForEach-Object { $_.ToUpperInvariant() }) $actions = if ($policyData.PSObject.Properties['Actions']) { $policyData.Actions } else { @{} } if ($null -eq $actions) { $actions = @{} } $onSuccess = if ($actions.PSObject -and $actions.PSObject.Properties['OnSuccess']) { $actions.OnSuccess } else { @{} } if ($null -eq $onSuccess) { $onSuccess = @{} } $onSuccess | Add-Member -MemberType NoteProperty -Name 'Controls' -Value $controls -Force $actions | Add-Member -MemberType NoteProperty -Name 'OnSuccess' -Value $onSuccess -Force $policyData | Add-Member -MemberType NoteProperty -Name 'Actions' -Value $actions -Force $hasChanges = $true } $validated = Confirm-EpmPolicyFilterParams -DayFilter $DayFilter -DateFilter $DateFilter -TimeFilter $TimeFilter if ($null -ne $UserFilter) { $policyData | Add-Member -MemberType NoteProperty -Name 'UserCheck' -Value @($UserFilter) -Force $hasChanges = $true } if ($null -ne $MachineFilter) { $policyData | Add-Member -MemberType NoteProperty -Name 'MachineCheck' -Value @($MachineFilter) -Force $hasChanges = $true } if ($null -ne $AppFilter) { $policyData | Add-Member -MemberType NoteProperty -Name 'ApplicationCheck' -Value @($AppFilter) -Force $hasChanges = $true } if ($null -ne $RiskLevel) { $policyData | Add-Member -MemberType NoteProperty -Name 'RiskLevel' -Value $RiskLevel.Value -Force $hasChanges = $true } if (-not [string]::IsNullOrEmpty($NotificationMessage)) { $policyData | Add-Member -MemberType NoteProperty -Name 'NotificationMessage' -Value $NotificationMessage -Force $hasChanges = $true } if ($null -ne $NotificationRequiresAcknowledge) { $policyData | Add-Member -MemberType NoteProperty -Name 'NotificationRequiresAcknowledge' -Value $NotificationRequiresAcknowledge -Force $hasChanges = $true } if ($null -ne $validated.DayCheck) { $policyData | Add-Member -MemberType NoteProperty -Name 'DayCheck' -Value $validated.DayCheck -Force $hasChanges = $true } if ($null -ne $validated.DateCheck) { $policyData | Add-Member -MemberType NoteProperty -Name 'DateCheck' -Value $validated.DateCheck -Force $hasChanges = $true } if ($null -ne $validated.TimeCheck) { $policyData | Add-Member -MemberType NoteProperty -Name 'TimeCheck' -Value $validated.TimeCheck -Force $hasChanges = $true } if (-not $hasChanges) { Write-Error -Message 'No changes specified. Provide at least one parameter to update.' -ErrorAction Stop } $effectiveStatus = if (-not [string]::IsNullOrEmpty($Status)) { $Status } elseif ($policyData.PSObject.Properties['Status'] -and $policyData.Status) { $policyData.Status } else { '' } $effectiveType = if ($policyData.PSObject.Properties['PolicyType']) { [string]$policyData.PolicyType } else { '' } if ($effectiveStatus -ne [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Off -and -not [string]::IsNullOrEmpty($effectiveType)) { $hasMachine = $policyData.PSObject.Properties['MachineCheck'] -and $policyData.MachineCheck -and $policyData.MachineCheck.Count -gt 0 $hasUser = $policyData.PSObject.Properties['UserCheck'] -and $policyData.UserCheck -and $policyData.UserCheck.Count -gt 0 $hasApp = $policyData.PSObject.Properties['ApplicationCheck'] -and $policyData.ApplicationCheck -and $policyData.ApplicationCheck.Count -gt 0 $hasControl = $false if ($policyData.PSObject.Properties['Actions'] -and $policyData.Actions) { $act = $policyData.Actions if ($act.PSObject.Properties['OnSuccess'] -and $act.OnSuccess -and $act.OnSuccess.PSObject.Properties['Controls'] -and $act.OnSuccess.Controls -and $act.OnSuccess.Controls.Count -gt 0) { $hasControl = $true } } Assert-EpmPolicyRequiredParams -PolicyType $effectiveType ` -HasMachine ([bool]$hasMachine) -HasUser ([bool]$hasUser) ` -HasApp ([bool]$hasApp) -HasControl ([bool]$hasControl) ` -ErrorSuffix 'Provide them or set -Status off' } $policyJson = $policyData | ConvertTo-Json -Depth 10 -Compress $plainData = [ordered]@{} $pName = if (-not [string]::IsNullOrEmpty($PolicyName)) { $PolicyName } elseif ($policyData.PSObject.Properties['PolicyName']) { $policyData.PolicyName } else { '' } $pType = if ($policyData.PSObject.Properties['PolicyType']) { $policyData.PolicyType } else { '' } $pStatus = if (-not [string]::IsNullOrEmpty($Status)) { $Status } elseif ($policyData.PSObject.Properties['Status']) { $policyData.Status } else { '' } if (-not [string]::IsNullOrEmpty($pName)) { $plainData['PolicyName'] = $pName } if (-not [string]::IsNullOrEmpty($pType)) { $plainData['PolicyType'] = $pType } if (-not [string]::IsNullOrEmpty($pStatus)) { $plainData['Status'] = $pStatus } $plainJson = $plainData | ConvertTo-Json -Depth 5 -Compress $pi = New-Object KeeperSecurity.Plugins.EPM.EpmPlugin+PolicyInput $pi.PolicyUid = $policy.PolicyUid $pi.PlainDataJson = $plainJson $pi.PolicyDataJson = $policyJson if (-not [string]::IsNullOrEmpty($Status)) { if ($Status -eq [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Off) { $pi.Disabled = $true } else { $pi.Disabled = $false } } try { $updateStatus = $plugin.ModifyPolicies($null, [KeeperSecurity.Plugins.EPM.EpmPlugin+PolicyInput[]]@($pi), $null).GetAwaiter().GetResult() if ($updateStatus.UpdateErrors -and $updateStatus.UpdateErrors.Count -gt 0) { $err = $updateStatus.UpdateErrors[0] Write-Error -Message "Failed to update policy `"$($err.EntityUid)`": $($err.Message)" -ErrorAction Stop } if ($updateStatus.Update -and $updateStatus.Update.Count -gt 0) { Write-Output "Policy '$($policy.PolicyUid)' updated." } else { Write-Warning "No policy was updated. Check server response." } writeEpmModifyStatus -Status $updateStatus $plugin.SyncDown($false).GetAwaiter().GetResult() | Out-Null } catch { Write-Error -Message "Error updating policy: $($_.Exception.Message)" -ErrorAction Stop } } function Remove-KeeperEpmPolicy { <# .Synopsis Remove an EPM policy by UID or name. .Parameter PolicyUidOrName Policy UID or policy display name (case-insensitive match on name). .Parameter Force If set, skip confirmation prompt before delete. #> [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'High')] Param ( [Parameter(Mandatory = $true, Position = 0)] [string] $PolicyUidOrName, [Parameter()] [switch] $Force ) $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $policy = Resolve-KeeperEpmSinglePolicy -Identifier $PolicyUidOrName -Plugin $plugin $policyInfo = Get-KeeperEpmPolicyDataInfo -Policy $policy -Plugin $plugin $label = if (-not [string]::IsNullOrEmpty($policyInfo.Name)) { $policyInfo.Name } else { $policy.PolicyUid } if (-not $Force -and -not $PSCmdlet.ShouldProcess("policy '$label'", "Remove")) { return } try { $removeStatus = $plugin.ModifyPolicies($null, $null, [string[]]@($policy.PolicyUid)).GetAwaiter().GetResult() if ($removeStatus.RemoveErrors -and $removeStatus.RemoveErrors.Count -gt 0) { $err = $removeStatus.RemoveErrors[0] Write-Error -Message "Failed to remove policy `"$($err.EntityUid)`": $($err.Message)" -ErrorAction Stop } if ($removeStatus.Remove -and $removeStatus.Remove.Count -gt 0) { Write-Output "Policy '$($policy.PolicyUid)' removed." } else { Write-Warning "No policy was removed. Check server response." } writeEpmModifyStatus -Status $removeStatus $plugin.SyncDown($false).GetAwaiter().GetResult() | Out-Null } catch { Write-Error -Message "Error removing policy: $($_.Exception.Message)" -ErrorAction Stop } } function Get-KeeperEpmPolicyAgent { <# .Synopsis List agents for one or more policies by policy UID or name. .Parameter PolicyUidOrNames One or more policy UIDs or policy names. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [string[]] $PolicyUidOrNames ) $ent = getEnterprise $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $auth = $ent.loader.Auth if ($null -eq $auth) { Write-Error -Message 'Authentication context is not available.' -ErrorAction Stop } $identifiers = @($PolicyUidOrNames | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrEmpty($_) }) if ($identifiers.Count -eq 0) { Write-Error -Message "Policy UID or name is required for 'agents'." -ErrorAction Stop } $policyUids = [System.Collections.Generic.List[string]]::new() foreach ($identifier in $identifiers) { $resolvedPolicies = @(Resolve-KeeperEpmPolicy -Identifier $identifier -Plugin $plugin) if ($resolvedPolicies.Count -eq 0) { Write-Warning "Policy '$identifier' not found." continue } if ($resolvedPolicies.Count -gt 1) { Write-Warning "Multiple policies match name '$identifier'. Use Policy UID." continue } [void]$policyUids.Add($resolvedPolicies[0].PolicyUid) } if ($policyUids.Count -eq 0) { return } try { $rs = GetPedmPolicyAgentsResponse -Auth $auth -PolicyUids ($policyUids.ToArray()) if ($null -eq $rs) { return } $activeAgentUids = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) foreach ($agentUidBytes in $rs.AgentUid) { $b = $agentUidBytes.ToByteArray() [void]$activeAgentUids.Add([KeeperSecurity.Utils.CryptoUtils]::Base64UrlEncode($b)) } $rows = [System.Collections.Generic.List[EpmPolicyAgentRow]]::new() foreach ($policyUid in $policyUids) { $policy = $plugin.Policies.GetEntity($policyUid) if ($null -ne $policy) { $policyInfo = Get-KeeperEpmPolicyDataInfo -Policy $policy -Plugin $plugin $status = Get-KeeperEpmPolicyListStatus -Policy $policy $row = [EpmPolicyAgentRow]::new() $row.Key = 'Policy' $row.UID = $policyUid $row.Name = $policyInfo.Name $row.Status = $status [void]$rows.Add($row) } } foreach ($agentUid in $activeAgentUids) { $agent = $plugin.Agents.GetEntity($agentUid) $machineName = '' $st = '' if ($null -ne $agent) { $machineName = if ($agent.MachineId) { $agent.MachineId } else { '' } $st = if ($agent.Disabled) { [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Off } else { [KeeperSecurity.Plugins.EPM.EpmPolicyStatus]::Enforce } } $row = [EpmPolicyAgentRow]::new() $row.Key = 'Agent' $row.UID = $agentUid $row.Name = $machineName $row.Status = $st [void]$rows.Add($row) } $rows | Format-Table -Property Key, UID, Name, Status -AutoSize } catch { Write-Error -Message "Error getting policy agents: $($_.Exception.Message)" -ErrorAction Stop } } function Add-KeeperEpmPolicyCollection { <# .Synopsis Assign one or more collections to one or more policies. .Parameter PolicyUidOrNames One or more policy UIDs or policy names. .Parameter CollectionUid One or more collection UIDs. Use '*' or 'all' for the all-agents collection. #> [CmdletBinding()] Param ( [Parameter(Mandatory = $true, Position = 0, ValueFromPipeline = $true)] [string[]] $PolicyUidOrNames, [Parameter(Mandatory = $true)] [string[]] $CollectionUid ) $plugin = ensureEpmPlugin if (-not $plugin) { Write-Error -Message 'EPM plugin is not available. Enterprise admin access is required.' -ErrorAction Stop } $identifiers = @($PolicyUidOrNames | ForEach-Object { $_.Trim() } | Where-Object { -not [string]::IsNullOrEmpty($_) }) if ($identifiers.Count -eq 0) { Write-Error -Message "Policy UID or name is required for 'assign'." -ErrorAction Stop } $policies = [System.Collections.Generic.List[object]]::new() foreach ($identifier in $identifiers) { $resolvedPolicies = @(Resolve-KeeperEpmPolicy -Identifier $identifier -Plugin $plugin) if ($resolvedPolicies.Count -eq 0) { Write-Warning "Policy '$identifier' not found." continue } if ($resolvedPolicies.Count -gt 1) { Write-Warning "Multiple policies match name '$identifier'. Use Policy UID." continue } [void]$policies.Add($resolvedPolicies[0]) } if ($policies.Count -eq 0) { return } $collectionUids = [System.Collections.Generic.List[byte[]]]::new() foreach ($collUid in $CollectionUid) { if ([string]::IsNullOrWhiteSpace($collUid)) { Write-Warning "Empty collection UID. Skipped." continue } $c = $collUid.Trim() if ($c -eq '*' -or $c -eq 'all') { $allAgentsUid = $plugin.AllAgentsCollectionUid if (-not [string]::IsNullOrEmpty($allAgentsUid)) { try { $allBytes = [KeeperSecurity.Utils.CryptoUtils]::Base64UrlDecode($allAgentsUid) if ($null -eq $allBytes -or $allBytes.Length -ne 16) { Write-Warning "Invalid all-agents collection UID (expected 16 bytes, got $($allBytes.Length)). Skipped." } else { [void]$collectionUids.Add($allBytes) } } catch { Write-Warning "Invalid all-agents collection UID: $($_.Exception.Message). Skipped." } } } else { if ($c -notmatch '^[A-Za-z0-9_\-]+=*$') { Write-Warning "Collection UID '$c' is not valid Base64Url. Skipped." continue } try { $collUidBytes = [KeeperSecurity.Utils.CryptoUtils]::Base64UrlDecode($c) if ($null -eq $collUidBytes -or $collUidBytes.Length -ne 16) { Write-Warning "Invalid collection UID: $c (expected 16 bytes, got $($collUidBytes.Length)). Skipped." continue } $existing = $plugin.Collections.GetEntity($c) if ($null -eq $existing) { Write-Warning "Collection '$c' not found. Skipped." continue } [void]$collectionUids.Add($collUidBytes) } catch { Write-Warning "Invalid collection UID: $c. Skipped." } } } if ($collectionUids.Count -eq 0) { Write-Error -Message 'No collections to assign.' -ErrorAction Stop } $setLinks = [System.Collections.Generic.List[KeeperSecurity.Plugins.EPM.CollectionLink]]::new() foreach ($policy in $policies) { foreach ($collUidBytes in $collectionUids) { $link = New-Object KeeperSecurity.Plugins.EPM.CollectionLink $link.CollectionUid = [KeeperSecurity.Utils.CryptoUtils]::Base64UrlEncode($collUidBytes) $link.LinkUid = $policy.PolicyUid $link.LinkType = [PEDM.CollectionLinkType]::CltPolicy [void]$setLinks.Add($link) } } try { $status = $plugin.SetCollectionLinks($setLinks, $null).GetAwaiter().GetResult() $hasErrors = $false if ($status.AddErrors -and $status.AddErrors.Count -gt 0) { foreach ($err in $status.AddErrors) { if (-not $err.Success) { Write-Error -Message "Failed to assign collection to policy `"$($err.EntityUid)`": $($err.Message)" -ErrorAction Continue $hasErrors = $true } } } if ($status.RemoveErrors -and $status.RemoveErrors.Count -gt 0) { foreach ($err in $status.RemoveErrors) { if (-not $err.Success) { Write-Error -Message "Failed to remove collection from policy `"$($err.EntityUid)`": $($err.Message)" -ErrorAction Continue $hasErrors = $true } } } if ($status.Add -and $status.Add.Count -gt 0) { Write-Output "$($status.Add.Count) collection(s) assigned to policy." } elseif (-not $hasErrors) { Write-Warning "No collections were assigned. Check server response." } writeEpmModifyStatus -Status $status $plugin.SyncDown($false).GetAwaiter().GetResult() | Out-Null } catch { Write-Error -Message "Error assigning collections to policy: $($_.Exception.Message)" -ErrorAction Stop } } New-Alias -Name kepm-policy-list -Value Get-KeeperEpmPolicyList -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-view -Value Get-KeeperEpmPolicy -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-add -Value Add-KeeperEpmPolicy -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-edit -Value Update-KeeperEpmPolicy -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-delete -Value Remove-KeeperEpmPolicy -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-remove -Value Remove-KeeperEpmPolicy -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-agents -Value Get-KeeperEpmPolicyAgent -ErrorAction SilentlyContinue New-Alias -Name kepm-policy-assign -Value Add-KeeperEpmPolicyCollection -ErrorAction SilentlyContinue # SIG # Begin signature block # MIInvgYJKoZIhvcNAQcCoIInrzCCJ6sCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCI9aNcZPdbY6U7 # lbgGKXX442w0QZRdC0ruzZcOe6HaIaCCITswggWNMIIEdaADAgECAhAOmxiO+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 # oAMCAQICEAHdzU+FVN9jCMv0HhHagNUwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UE # BhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2Vy # dCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNIQTM4NCAyMDIxIENB # MTAeFw0yNjA2MDUwMDAwMDBaFw0yNzA2MDQyMzU5NTlaMIHRMRMwEQYLKwYBBAGC # NzwCAQMTAlVTMRkwFwYLKwYBBAGCNzwCAQITCERlbGF3YXJlMR0wGwYDVQQPDBRQ # cml2YXRlIE9yZ2FuaXphdGlvbjEQMA4GA1UEBRMHMzQwNzk4NTELMAkGA1UEBhMC # VVMxETAPBgNVBAgTCElsbGlub2lzMRAwDgYDVQQHEwdDaGljYWdvMR0wGwYDVQQK # ExRLZWVwZXIgU2VjdXJpdHkgSW5jLjEdMBsGA1UEAxMUS2VlcGVyIFNlY3VyaXR5 # IEluYy4wggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQCb4DRTV0sNQsa1 # 0YRh+bliabmLOVYr6S0+BSVvRJAN3SHP6x52i1Dkpki5xVDIH06ZnnsToVrgvTv+ # QxGwsn9SAPHEZ/PIJRFxbMR4ShDaptYyL4f0u4k/3HwRzIleWE4mTUonYH8BdgLw # /F53B7wa7VTDHtxXltYTibEOwJxYCOi4Zr2FYQhjw14/CHcqS3FSMs6YYU2T56+g # w819hQM3K0YlwTNOFoIm1v7/ZZZiJGH8uGDsvy1makh1Xyyo/wN8EbQ1nbslmePT # roPm9w7WqiP/yiq+CZHiuTk9JK5bEgkWG3ns+v25cI251WidJx3SU7IZnX0OTd6/ # ZdKhprD5Gcfy5GBbJdcYw2WycQRW0PT5BEt55xRE0heufkpDaTUN6RdOuJdXbkl0 # hV91IZIuhueEMCk3h5mDTlU5gImxqj0R/TbAxjSSGTKCeuYFkQIRqytSabdrZZ48 # kW5hOIZMVDY1f4kpPJa8UeEvDZXT3vrtj36aSJrwez2uh4FMNlkCAwEAAaOCAgIw # ggH+MB8GA1UdIwQYMBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBT1 # SmCYU/7Yrz1fX66Ur5nSzlSYOzA9BgNVHSAENjA0MDIGBWeBDAEDMCkwJwYIKwYB # BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC # B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p # bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT # QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA # A4ICAQBcavcUHNFEg872HDRq2+hRlnvaghCXv7X/6h9HSzjAQP3rt95BZty3ASqi # 2MYyGQLGdDl4DToe/WhajtEOBOYa83agW6tBvrfcKRrDrwJOMPTbwNYvn+GuiL4T # CKzXaytWiJJbrc5odc7Ecat2ZvJylpPmNainr4Q0LzzH23Gea/Mm/hIJTN4IGgrH # hrXiTIIW/ZUzrY6g8b3RZB4BA497n43wNdSqP+C3ntFw6NiGB4Z25SW4YntIxYPv # Kf37OVhF0xqxLC1sK/XxgK0EGQ6iaj8Ncpr2C5vSNZqfW2MndxOA1W67pgDpg83k # UWG+/YJeGhqOTF82/0kIzQXeI/lIqbnL/IJAJqSm/ROSpsGUKVbzk03cpTD55ZQX # WjM0fLirypBqY05T8gnh1L0fSwxr/SwJZ8OddivgyK1YOMn02nnsEG5kxBt9cMX4 # JCYABhypmAVDRvyYifEVdoFWv2gAXXW+PPRvlNa6E4aMCZrVcoKHiyeMAXOi1IC9 # mHvC2+foTSMFueq3AdnYfeKnZnAiKXKRhXcdHbQYcR2A7AIzIcqahPYr4FNEgb/E # /y/kypAkf0rMHlYl1kNqLs2Nv1UnMEHYT5YmDVLO63+1Trcw4zTZ70zuqIqeID/d # nbOlgtyG6DSRCL7f0E7kP18f4RoX5i1PkfeO4VJHsAuCeNG1qjGCBdkwggXVAgEB # MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD # VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI # QTM4NCAyMDIxIENBMQIQAd3NT4VU32MIy/QeEdqA1TANBglghkgBZQMEAgEFAKCB # hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ # BDEiBCAnnb84NxvB6H624GPquX9ryJsVDS41CxKHbNiuPImnFjANBgkqhkiG9w0B # AQEFAASCAYBh97BBwkK65YpstZYrDeM5LEPyoJoy02yU7e4/sBrF/uhSFpLihR2a # xOVKvJyQ5ZnVis3Vu1E6fwbGiObaPgz8+MNAAOXR5KVaYzQa/RaP6cZGxrhQUkWZ # y/A3mf+IGAJPiQ2WEzhG8Zpo1qCvR//vRFH1yadJjvfwJ0pkWWAy72AmdVixJAe/ # aoEZRO75BwesN6IdA8GPcH1K1UrgR9plJLn6C2CAqA0uLr4vapnbOBKDotGtXKK1 # rH1Wnsz2etk2mYoob0Rsb7TmyIp+/pm5i+NT74zbJPAJ+KK2q9r05tomYhRMyVUF # Xjy/PhUUtxKd696uG35lT5+SDhn4berGrliWKU6SpZIMRI6WYv+5aEFlgzNon52Q # gPmdU64bPFiWPnvKKeoxmiKB1woVvVNIsKSscqfzHTKsag0H+4lcDxQQXodLzfkm # a6vtOYQqYyUmMCvlGmNYYZgywYFhSrzAT0qTVtRwXAtc7gLsH7qFeoMHn0XS2h93 # jW7TUK7AJ2mhggMmMIIDIgYJKoZIhvcNAQkGMYIDEzCCAw8CAQEwfTBpMQswCQYD # VQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lD # ZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUg # Q0ExAhAKgO8YS43xBYLRxHanlXRoMA0GCWCGSAFlAwQCAQUAoGkwGAYJKoZIhvcN # AQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjYwNjEzMDA1NjM0WjAv # BgkqhkiG9w0BCQQxIgQgiA4JH8+JP/R4DWJOtew4dBv9f9gxU6CUZDhlmlWrxKUw # DQYJKoZIhvcNAQEBBQAEggIAdl3GemLZ0JscaZRwvyEXtB3OVh5HcBSxt845/7Oa # AsbVJXWR9Z0X/0g7oQZgg7pF5bnYYTbgGrSnw50fQaTLskT9ucvEeY5+8tj6U65q # ozoyadSkzZk6sWjiSUB7Oi1CgHrIIYUafSeXWtxk+HXQTWLgmgUVhmRvehea8fSB # ZzafTzxvjFEGvUaAGYBSh+kJGN3iRXgx4ivgWzycZ1IZj5bkzgFyCab4AaANM6wV # ccNEyM5/deteuizvow5gvblmDKjccSaiYUqg8UltbmpaU1S+EOk69p0Z1mDOyS58 # V8EIQ4jgYID3L6ZwTjWYUyGNCqJbJNqWf6aUru0Rfu1uTX3X6rOLOy+ijHRTuKs+ # Y73+EyJhxgjSJnBmNrYvvvlUxhS9c1nbTPsog3oXeLyuTqRJasnk5+FynFUhjSIM # PoQ/4fvg1XETGu2LqZyQpOVG1JFcdCxA/T/35aVfzbs3sRQqrN4Jyqs/1CJAcDbg # OZxWmLyHNe1IKtzQwoZfr7r1WkXpQmE+6xz58SNgBIbwVLDLGxxvn0syWB9UNwgU # MWCdUp2Y0JTy8RAS3+JAWSlKm0Q2f/s8SfIHAyFlTEv22qRViMswc5MzOJpwiWup # aXBWwqYM/YWsLNMl7sNUnK5QXNI75bm3TF3xPt2cgM1S2dvMADMPgIGAOXOqKOBa # slY= # SIG # End signature block |