scan.ps1

# Scan APIs


function Get-ICScan {
    [cmdletbinding()]
    param(
        [parameter(ValueFromPipelineByPropertyName)]
        [alias('scanId')]
        [String]$Id,

        [parameter(ValueFromPipelineByPropertyName)]
        [alias('targetId')]
        [String]$TargetGroupId,

        [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")]
        [HashTable]$where=@{},
        
        [Switch]$NoLimit,
        [Switch]$CountOnly
    )

    PROCESS {
        $Endpoint = "scans"

        if ($Id -AND (-NOT $_.targetId)) {
            Write-Verbose "Getting Scan with Id $Id"
            $CountOnly = $false
            $Endpoint += "/$Id"
        }
        elseif ($TargetGroupId) {
            $tg = Get-ICTargetGroup -Id $TargetGroupId
            if ($tg) {
                Write-Verbose "Getting Scans against Target Group $TargetGroup [$TargetGroupId]"
                $where += @{ targetId = TargetGroupId }
            } else {
                Write-Error "TargetGroup with Id $TargetGroupId does not exist."
                return
            }
        }

        Get-ICAPI -Endpoint $Endpoint -where $where -NoLimit:$NoLimit -CountOnly:$CountOnly
    }
}

function Invoke-ICScan {
    [cmdletbinding(DefaultParameterSetName = 'extensionIds')]
    param(
        [parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [Alias("targetId")]
        [String]$TargetGroupId,

        [Switch]$FindHosts,

        [parameter(
            Mandatory=$false, 
            ParameterSetName = 'options')]
        [PSCustomObject]$ScanOptions,

        [parameter(
            Mandatory=$true, 
            ParameterSetName = 'extensions',
            HelpMessage = "Array of @{ id = "", args = {}}")]
        [Object[]]$Extensions = @(),

        [parameter(
            Mandatory = $false, 
            ParameterSetName = 'extensions')]
        [Switch]$ExtensionsOnly,

        [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")]
        [HashTable]$where
    )

    $TargetGroup = Get-ICTargetGroup -Id $TargetGroupId
    if (-NOT $TargetGroup) {
        Throw "TargetGroup with id $TargetGroupId does not exist!"
    }

    if ($FindHosts) {
        Write-Progress -Activity "Performing discovery on $($TargetGroup.name)"
        $stillactive = $true
        $UserTask = Invoke-ICFindHosts -TargetGroupId $TargetGroupId -where $where
        While ($stillactive) {
            Start-Sleep 20
            # -where @{ createdOn = @{ gt = (Get-Date).AddHours(-10) }; name = @{ regexp = $TargetGroup.name } }
            $taskstatus = Get-ICUserTask -Id $UserTask.userTaskId
            if ($taskstatus.status -eq "Active") {
                Write-Progress -Activity "Performing discovery on $($TargetGroup.name)" -PercentComplete $($taskstatus.progress)
            } elseif ($taskstatus.status -eq "Completed") {
                Write-Progress -Activity "Performing discovery on $($TargetGroup.name)" -Completed
                $stillactive = $false
            } else {
                Throw "Something went wrong in enumeration. Last Status: $($taskstatus.status)"
            }
        }

    }
    $Endpoint = "targets/$TargetGroupId/scan"
    Write-Verbose "Starting Scan of TargetGroup $($TargetGroup.name)"

    if ($ScanOptions) {
        $body = @{ options = $ScanOptions }
    } 
    elseif ($Extensions -AND $Extensions.count -gt 0) {
        $ScanOptions = New-ICScanOptions -Empty:$ExtensionsOnly -Extensions $Extensions
        $body = @{ options = $ScanOptions }
    }
    if ($where) {
        $body += @{ where = $where }
    }
    Invoke-ICAPI -Endpoint $Endpoint -body $body -method POST
}

function Invoke-ICFindHosts {
    [cmdletbinding()]
    param(
        [parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [alias('targetId')]
        [String]$TargetGroupId,

        [parameter()]
        [String[]]$queryId,

        [parameter(HelpMessage="This will convert a hashtable into a JSON-encoded Loopback Where-filter: https://loopback.io/doc/en/lb2/Where-filter ")]
        [HashTable]$where
    )
    $TargetGroup = Get-ICTargetGroup -Id $TargetGroupId
    $Queries = Get-ICQuery -TargetGroupId $TargetGroupId
    if (-NOT $Queries) {
        Throw "Target Group not found or does not have any Queries associated with it"
    }
    $Endpoint = "targets/$TargetGroupId/Enumerate"
    $body = @{
        queries = @()
    }
    if ($QueryId) {
        $QueryId | ForEach-Object {
            $body['queries'] += $_
        }
    } else {
        $Queries | ForEach-Object {
            $body['queries'] += $Queries.Id
        }
        Write-Verbose "Starting Enumeration of $($TargetGroup.name) with all associated queries.`n$($body['queries'] | convertto-json)"
    }
    if ($where) {
        $body['where'] += $where
    }
    Invoke-ICAPI -Endpoint $Endpoint -body $body -method POST
}

function Invoke-ICScanTarget {
    [cmdletbinding(DefaultParameterSetName = 'extensions')]
    param(
        [parameter(
            Mandatory=$true, 
            ValueFromPipeline)]
        [ValidateNotNullOrEmpty()]
        [String]$target,

        [String]$TargetGroupId,
        [String]$TargetGroupName = "OnDemand",

        [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })]
        [String]$CredentialId,
        [String]$CredentialName,

        [ValidateScript({ if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid."} })]
        [String]$sshCredentialId,
        [String]$sshCredentialName,

        [parameter(
            Mandatory=$false, 
            ParameterSetName = 'options')]
        [Object]$ScanOptions,

        [parameter(
            Mandatory=$false, 
            ParameterSetName = 'extensions')]
        [Object[]]$Extensions,

        [Switch]$Wait
    )
    PROCESS {
        if (-NOT $target -AND $_) {
            Write-Debug "Taking input from raw pipeline (`$_): $_."
            $target = $_
        }
        $body = @{
            target = $target
        }

        # Select Target targetgroup
        if ($TargetGroupId) {
            $tg = Get-ICTargetGroup -Id $TargetGroupId
            if (-NOT $tg) {
                Throw "TargetGroup with id $TargetGroupid does not exist!"
            } 
        } else {
            Write-Verbose "Using Target Group Name [$TargetGroupName] -- will be created if it does not exist."
            $tg = Get-ICTargetGroup -where @{ name = $TargetGroupName}
            if (-NOT $tg) {
                $tg = New-ICTargetGroup -Name $TargetGroupName -Force
            }
            $targetGroupId = $tg.id
        }
        $body['targetGroup'] = @{ id = $targetGroupId }

        # Select Credential
        if ($CredentialId) {
            $Credential = Get-ICCredential -Id $credentialId
            if (-NOT $Credential) {
                Throw "Credential with id $credentialId does not exist!"
            }
            $body['credential'] = @{ id = $credentialId }
        } elseif ($CredentialName) {
            # Use Credentialname
            $Credential =  Get-ICCredential -where @{ name = $CredentialName } | Select-Object -First 1
            if (-NOT $Credential) {
                Throw "Credential with name [$CredentialName] does not exist! Please create it or specify a different credential (referenced by id or name)"
              }
              $credentialId = $Credential.id
              $body['credential'] = @{ id = $credentialId }
        }
        

        # Select ssh Credential
        if ($sshCredentialId) {
            $body['sshcredential'] = @{ id = $credentialId }
        } elseif ($sshCredentialName) {
            $body['sshcredential'] = @{ name = $sshCredentialName }
        }

        if ($Extensions -AND $Extensions.count -gt 0) {
            $ScanOptions = New-ICScanOptions -Extensions $Extensions
        }
        $body['options'] = $ScanOptions

        # Check for active agent
        if (-NOT $body['credential']) {
            $agent = Get-ICAgent -where @{ authorized = $true; or = @(  @{ hostname = $target }, @{ ipstring = $target }) }
            if (-NOT $agent.active) {
                Write-Verbose "No active agent found, looking for existing address entry in target group $tg"
                #Check TG Address Table
                $Addr = Get-ICAddress -targetId $targetGroupId -where @{ accessible = $true; or = @(  @{ hostname = $target }, @{ ipstring = $target }) } | Sort-Object lastAccessedOn -Descending | Select-Object -Last 1
                if (-NOT $addr) {
                    Write-Verbose "No accessible address entry in target group $tg, looking at all target groups"
                    # Check FULL address table
                    $Addr2 = Get-ICAddress -where @{ accessible = $true; or = @(  @{ hostname = $target }, @{ ipstring = $target }) } | Sort-Object lastAccessedOn -Descending | Select-Object -Last 1
                    if (-NOT $Addr2) {
                        Write-Verbose "No accessible address entry found for $target"
                    }
                }

                if ($Addr) {
                    $where = @{ id = $Addr.id }
                    $scan = Invoke-ICScan -TargetGroupId $TargetGroupId -ScanOptions $ScanOptions -where $where 
                    $task = Get-ICTask -id $scan.userTaskId
                    if ($Wait) {
                        $timer = [system.diagnostics.stopwatch]::StartNew()
                        while ($task.status -eq "Active"){
                            $t = $timer.Elapsed
                            Write-Progress -Activity "Scanning $target" -Status "[$($t.TotalSeconds.ToString("#.#"))] Status=$($task.message)"
                            Start-Sleep 5
                            $task = Get-ICTask -id $scan.userTaskId
                        }
                        $timer.Stop()
                        $TotalTime = $timer.Elapsed
                        Write-Progress -Activity "Scanning $target" -Status "[$($TotalTime.TotalSeconds.ToString("#.#"))] Status=$($task.message)" -Completed
                        Write-Verbose "Task $($task.status). Completed in $($TotalTime.TotalSeconds.ToString("#.#")) Seconds."
                    }
                    return $scan
                }
            }
        }

        # Initiating Scan
        $Endpoint = "targets/scan"
        Write-Verbose "Starting Scan of target $($target)"
        try {
            $scan = Invoke-ICAPI -Endpoint $Endpoint -body $body -method POST
        } catch {
            $StatusCode = "$($_.Exception.Response.StatusCode)($($_.Exception.Response.StatusCode.value__))"
            Switch -regex ($statuscode) {
                "5\d\d" {
                    Write-Warning "Error[$StatusCode]: $($_.Exception.Message). No active agent or accessible address entry may have been found for '$target'"
                    return
                }
                default {
                    Write-Host -ForegroundColor Red "ERROR[$StatusCode]: $url`n$($body|convertto-json -compress)`n$($_.Exception.Message)"
                    return
                }
            }
        }
        $scan = [PSCustomObject]@{ userTaskId = $scan.scanTaskId } 
        $scan
        if ($Wait) {
            $timer = [system.diagnostics.stopwatch]::StartNew()
            $task = Get-ICTask -id $scan.userTaskId
            while ($task.status -eq "Active"){
                $t = $timer.Elapsed
                Write-Progress -Activity "Scanning $target" -Status "[$($t.TotalSeconds.ToString("#.#"))] Status=$($task.message)"
                Start-Sleep 5
                $task = Get-ICTask -id $scan.userTaskId
            }
            $timer.Stop()
            $TotalTime = $timer.Elapsed
            Write-Progress -Activity "Scanning $target" -Status "[$($TotalTime.TotalSeconds.ToString("#.#"))] Status=$($task.message)" -Completed
            Write-Verbose "Task $($task.status). Completed in $($TotalTime.TotalSeconds.ToString("#.#")) Seconds."
        } 
    }
}

function Invoke-ICResponse {
    [cmdletbinding(DefaultParameterSetName = 'ByName')]
    param(
        [parameter(
            Mandatory, 
            ValueFromPipeline,
            ValueFromPipelineByPropertyName)]
        [ValidateNotNullOrEmpty()]
        [alias("ip")]
        [alias("hostname")]
        [String]$Target,

        [parameter()]
        [ValidateScript( { if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid." } })]
        [String]$TargetGroupId,

        [parameter()]
        [String]$TargetGroupName = "OnDemand",

        [parameter(
            Mandatory = $true, 
            ParameterSetName = 'ById')]
        [ValidateScript( { if ($_ -match $GUID_REGEX) { $true } else { throw "Incorrect input: $_. Should be a guid." } })]
        [String]$ExtensionId,

        [parameter(
            Mandatory = $true, 
            ParameterSetName = 'ByName')]
        [ValidateNotNullOrEmpty()]
        [String]$ExtensionName,

        [Object]$Arguments,

        [Switch]$Wait
    )

    BEGIN {
        if ($ExtensionId) {
            $Ext = Get-ICExtension -Id $ExtensionId
            if (-NOT $Ext) {
                Throw "Extension with id $ExtensionId does not exist!"
            }
            $ExtensionName = $Ext.name
        }
        else {
            $Ext = Get-ICExtension -where @{ name = $ExtensionName } | Select-Object -Last 1
            if (-NOT $Ext) {
                Throw "Extension with name $ExtensionName does not exist!"
            }
            $ExtensionId = $Ext.Id
        }
        $Extensions = @{
            id   = $ExtensionId
            args = {}
        }
        if ($Arguments) {
            $Extensions['args'] = $Arguments
        }
        $ScanOptions = New-ICScanOptions -Empty -Extensions $Extensions
    }

    PROCESS {
        Invoke-ICScanTarget -target $target -TargetGroupId $TargetGroupId -TargetGroupName $TargetGroupName -ScanOptions $ScanOptions -Wait:$Wait
    }
}

enum ScanOptions {
    process     
    module       
    driver       
    memory       
    account  
    artifact
    autostart
    application
    installed
    hook
    network
    events
}

function New-ICScanOptions {
    [cmdletbinding(DefaultParameterSetName = 'Options')]
    param(
        [parameter(Mandatory=$false)]
        [Object[]]$Extensions,

        [Parameter(ParameterSetName="Empty")]
        [Switch]$Empty,

        [parameter(ParameterSetName="Options")]
        [ScanOptions[]]$Options

    )

    END {
        Write-Verbose 'ScanOption object properties should be set ($True or $False) and then passed into Invoke-ICScan or Add-ICScanSchedule'
        if ($Empty -OR $Options) {
            $default = $false
        }
        else {
            $default = $true
        }
        $opts = @{
            process      = $default
            module       = $default
            driver       = $default
            memory       = $default
            account      = $default
            artifact     = $default
            autostart    = $default
            application  = $default
            installed    = $false
            hook         = $false
            network      = $default
            events       = $default
            extensions = @()
        }

        if ($Extensions) {
            $n = 0
            $Extensions | % {
                if ($_.keys -notcontains "id" -OR $_.keys -notcontains "args") {
                    # extensions form: @{ id = "", args = {}}
                    throw "Incorrect form for extensions: @{ id = '', args = {}}"
                }
                $_['order'] = $n
                $opts['extensions'] += $_
                $n++
            }
        }

        if ($Options) {
            $Options | ForEach-Object {
                Write-Verbose "Changing $_ to True"
                $opts["$_"] = $true
            }
        }
    
        return [PSCustomObject]$opts
    }
}

function Import-ICSurvey {
    [cmdletbinding(DefaultParameterSetName = 'Path')]
    param(
        [parameter(
            Mandatory,
            ParameterSetName  = 'Path',
            Position = 0,
            ValueFromPipeline,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [SupportsWildcards()]
        [string[]]$Path, # <paths of the survey results (.bz2) files to upload>

        [parameter(
            Mandatory,
            ParameterSetName = 'LiteralPath',
            Position = 0,
            ValueFromPipelineByPropertyName
        )]
        [ValidateNotNullOrEmpty()]
        [Alias('PSPath')]
        [string[]]$LiteralPath,

        [String]$ScanId,
        [String]$TargetGroupId,
        [alias('TargetGroupName')]
          [String]$DefaultTargetGroupName = "OfflineScans"
    )

    BEGIN {
      # INITIALIZE
      $survey = "HostSurvey.json.gz"
      $surveyext = "*.json.gz"

      function Send-ICSurveys ([String]$FilePath, [String]$ScanId){
          Write-Verbose "Uploading Surveys"
            $headers = @{
            Authorization = $Global:ICToken
                scanid = $ScanId
            }
          try {
              Invoke-RestMethod $HuntServerAddress/api/survey -Headers $headers -Method POST -InFile $FilePath -ContentType "application/octet-stream"
          } catch {
              throw "$($_.Exception.Message)"
          }
      }

    if ($ScanId) {
        # Check for existing ScanId and use it
        $scan = Get-ICScan -Id $ScanId
        if ($scan) {
            $ScanName = $scan.name
            $TargetGroupName = $Scan.targetList
        } else {
            Write-Warning "No scan exists with ScanId $ScanId. Generating one."
        }
    }

    if ($TargetGroupId) {
        # Check TargetGroupId and create new ScanId for that group
        Write-Verbose "Checking for existance of target group with TargetGroupId: '$TargetGroupId' and generating new ScanId"
        $TargetGroup = Get-ICTargetGroup -id $TargetGroupId
        if ($TargetGroup) {
            $TargetGroupName = ($TargetGroups | Where-Object { $_.id -eq $TargetGroupId }).name
        } else {
            Throw "No Target Group exists with TargetGroupId $TargetGroupId. Specify an existing TargetGroupId to add this survey to or use other parameters to generate one."
        }
    }
    else {
        Write-Verbose "No ScanId or TargetGroupId specified. Checking for existance of target group: '$TargetGroupName'"
          $TargetGroups = Get-ICTargetGroup
          if ($TargetGroups.name -contains $TargetGroupName) {
              Write-Verbose "$TargetGroupName Exists."
            $TargetGroupId = ($targetGroups | Where-Object { $_.name -eq $TargetGroupName}).id
          } else {
            Write-Warning "$TargetGroupName does not exist. Creating new Target Group '$TargetGroupName'"
            $g = Get-ICControllerGroup
            if ($g.id.count -eq 1) {
                $ControllerGroupId = $g.id
            } else {
                $ControllerGroupId = $g[0].id
            }
            $TargetGroupId = (New-ICTargetGroup -Name $TargetGroupName -ControllerGroupId $ControllerGroupId).id
          }
    }

    # Creating ScanId
    if (-NOT $ScanName) {
        $ScanName = "Offline-" + (get-date).toString("yyyyMMdd-HHmm")
        Write-Verbose "Creating scan named $ScanName [$TargetGroupName-$ScanName]..."
        $StartTime = _Get-ICTimeStampUTC
        $body = @{
            name = $scanName;
            targetId = $TargetGroupId;
            startedOn = $StartTime
        }
        $newscan = Invoke-ICAPI -Endpoint $Endpoint -body $body -Method 'POST'
        $ScanId = $newscan.id
    }

    Write-Verbose "Importing Survey Results into $TargetGroupName-$ScanName [ScanId: $ScanId] [TargetGroupId: $TargetGroupId]"
    }

    PROCESS {
        # Resolve path(s)
        if ($PSCmdlet.ParameterSetName -eq 'Path') {
            $resolvedPaths = Resolve-Path -Path $Path | Select-Object -ExpandProperty Path
        } elseif ($PSCmdlet.ParameterSetName -eq 'LiteralPath') {
            $resolvedPaths = Resolve-Path -LiteralPath $LiteralPath | Select-Object -ExpandProperty Path
        }
        # Process each item in resolved paths
        foreach ($file in $resolvedPaths) {
             Write-Verbose "Uploading survey [$file]..."
             if ((Test-Path $file -type Leaf) -AND ($file -like $surveyext)) {
                 Send-ICSurveys -FilePath $file -ScanId $ScanId
               } else {
                   Write-Warning "$file does not exist or is not a $surveyext file"
               }
           }
    }

    END {
        # TODO: detect when scan is no longer processing submissions, then mark as completed
        #Write-Verbose "Closing scan..."
        #Invoke-RestMethod -Headers @{ Authorization = $token } -Uri "$HuntServerAddress/api/scans/$scanId/complete" -Method Post
    }

}
# SIG # Begin signature block
# MIINFwYJKoZIhvcNAQcCoIINCDCCDQQCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUlqvuIB69V7wJsm8VVggVmGAh
# JCCgggpZMIIFITCCBAmgAwIBAgIQD1SHruUyzkN01AFx5d7oATANBgkqhkiG9w0B
# AQsFADByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdpQ2VydCBTSEEyIEFz
# c3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMB4XDTIwMTExNzAwMDAwMFoXDTIyMTEy
# OTIzNTk1OVowXjELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQH
# EwZBdXN0aW4xFjAUBgNVBAoTDUluZm9jeXRlLCBJbmMxFjAUBgNVBAMTDUluZm9j
# eXRlLCBJbmMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNUhaEiZbu
# H7Q6oqA0uq0klkstXkdJU3eJukMrrLpxKdtFqtJwFXcSYp5G/WFlwDRJ8v8fisfp
# SiPS1WajFUHe3EWLh2oXjf44eQYVWQ8SqAn2J8dDLNJ5bWY0w7MD2GrSiTwN0Vi9
# X9pJKJDdm7mJo7bSlZ9p7XvNoraSAx/hkODalPSMvCIVAEOZutlzeWyJ4p0DbTDA
# kjQPF4EZ7JqxYXFeItoi0uYZQNEHbBxr+5SG45ziC8vuwyljIbo+mKD/PwT48OQl
# 9cdnI651Hz+r5kL3t48WvxYrAUJ7g8EJyw2uYnVnSroIC3TmUQHeXS6FuqeObuX7
# MqapBKRcTHvxAgMBAAGjggHFMIIBwTAfBgNVHSMEGDAWgBRaxLl7KgqjpepxA8Bg
# +S32ZXUOWDAdBgNVHQ4EFgQUxKDwshqav/aGaGVOFv67CuwSjcYwDgYDVR0PAQH/
# BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4wNaAzoDGGL2h0
# dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEuY3JsMDWg
# M6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVkLWNzLWcx
# LmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRw
# czovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBhAYIKwYBBQUHAQEE
# eDB2MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTgYIKwYB
# BQUHMAKGQmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJB
# c3N1cmVkSURDb2RlU2lnbmluZ0NBLmNydDAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3
# DQEBCwUAA4IBAQByJW5tIcmouIY7tdngPLdEOM4FYqLGf9IjKPMS0s+NeTaP/0hp
# dmNeGFEvMozfgDA/gPFCUaRVJwy4rKsGnCznCE1YDA6UFDGZq3VLUbzC6GDP4aY8
# EbfDMbF54TVuOKRue9a6KnVE67gOj+g862qAR6fm/GdeO/KrdvCT1A7xbyg02cCq
# +QgdkYoxI3bsiUwgZ33I2rn2T2zSp8C+RX2bZ8rgtXHxgYLCJdayqMptRsPbxOlQ
# Z7dRhkQXg5D/PyUnpWASF+sLQQ0IMvx8ZKy/P01IhKU0pTJ8OFSYKwPLQnYm1Zp0
# JT/IXZ/tzmtY/StdhaCs3LlOkuHxl2iERxdtMIIFMDCCBBigAwIBAgIQBAkYG1/V
# u2Z1U0O1b5VQCDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UE
# ChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYD
# VQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIyMTIwMDAw
# WhcNMjgxMDIyMTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl
# cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQDEyhEaWdp
# Q2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjANBgkqhkiG
# 9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZsTrbRPV/
# 5aid2zLXcep2nQUut4/6kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn08b7KSfH
# 03sjlOSRI5aQd4L5oYQjZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mILCCVrhxK
# hwjfDPXiTWAYvqrEsq5wMWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7SrXpdOYr
# /mzLfnQ5Ng2Q7+S1TqSp6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S9MWOD8Gi
# 6CxR93O8vYWxYoNzQYIH5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQABo4IBzTCC
# AckwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww
# CgYIKwYBBQUHAwMweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8v
# b2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEGA1UdHwR6
# MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
# ZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4BgpghkgBhv1s
# AAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw
# CgYIYIZIAYb9bAMwHQYDVR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5YMB8GA1Ud
# IwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUAA4IBAQA+
# 7A1aJLPzItEVyCx8JSl2qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8gVTew4fbR
# knUPUbRupY5a4l4kgU4QpO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5CxGwcOkRX7
# uq+1UcKNJK4kxscnKqEpKBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc2XGxDI+7
# qPjFEmifz0DLQESlE/DmZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRka7LrZkPa
# s7CM1ekN3fYBIM6ZMWM9CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oSlBiFLpKR
# 6mhsRDKyZqHnGKSaZFHvMYICKDCCAiQCAQEwgYYwcjELMAkGA1UEBhMCVVMxFTAT
# BgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEx
# MC8GA1UEAxMoRGlnaUNlcnQgU0hBMiBBc3N1cmVkIElEIENvZGUgU2lnbmluZyBD
# QQIQD1SHruUyzkN01AFx5d7oATAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEK
# MAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3
# AgELMQ4wDAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQU1ctmkAEh4HImQqcO
# /2LXNvqo7powDQYJKoZIhvcNAQEBBQAEggEAQ2oJ/EY1/So7HGpEzYrFcj/Xlbou
# SzuxxrtoSaiYyX4nqr0KUq2YUl+At6XKe+I+3upHYGn9ClBTG//ZQIW5IpeupJDs
# 4wAAxjpp/+rpodaPx6fTy16IcKfnp1xPokBkaJx4xnRJ9RrG3o/9nmeo27fv4iPL
# PnHEwlHmTxTiAEHM0ApaHrl/jCfsbxO7cdlYhpqr8IW59tQVeCBhXb1yHm1YqDss
# /Uv1OUoPsh+0f82Xk10SIzzDGEp0NZhXlilEY0zaN/0MU0+0n5vu/4iQ/rvaPy1T
# qmppUPiYS0lssBqumnIDdIoBGufpDKZEnFE8EsReBP4gwLkkIy5MDadSkQ==
# SIG # End signature block