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 $TargetGroup) {
                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
# MIINOAYJKoZIhvcNAQcCoIINKTCCDSUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU9AFKwtQMtRoOD+I2tM9U6kig
# N7mgggp2MIIFHzCCBAegAwIBAgIQA7ShIT20JORyIag/jWOSyzANBgkqhkiG9w0B
# AQsFADB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD
# VQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdpQ2VydCBTSEEyIEhp
# Z2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQTAeFw0xODA5MTIwMDAwMDBaFw0y
# MDExMTgxMjAwMDBaMF4xCzAJBgNVBAYTAlVTMQ4wDAYDVQQIEwVUZXhhczEPMA0G
# A1UEBxMGQXVzdGluMRYwFAYDVQQKEw1JbmZvY3l0ZSwgSW5jMRYwFAYDVQQDEw1J
# bmZvY3l0ZSwgSW5jMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApzcR
# GuszlupWLdmtti4glKsr6SS2sp370yioc2XyIwPzU/etsBQa41x6VO7HXjtSXSry
# p3SYaoLPQmBiAzKjDP6dzu0l7cQFbwPMky3SGqrC3Wr+Kw/qoMgn3wKBxzPJ53Gj
# s1oxNwyz2N7FwN977vErW9C/FgM/XuE7Zde/HGl3oxTJNtY++BG2Ri3rwi5hNbzV
# 5+avrJFW1DzHVBXYxbrE9vNy4V6s7dlZT2xZoJ3AtHoBCUMgHRKii3wHgFRaxiuz
# 6XzlvHzmnh02KUfoV6cX++bP4bRtsJjmvrfJV+Mhlh/MhUidhhQQx0spLIfxv+vZ
# OACP5jLm0g2fj4G4VQIDAQABo4IBvzCCAbswHwYDVR0jBBgwFoAUZ50PIAkMzIo6
# 5YJGcmL88cyQ5UAwHQYDVR0OBBYEFBqi6MjBKip4kQYxVCjC7yOrUHWFMA4GA1Ud
# DwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzBtBgNVHR8EZjBkMDCgLqAs
# hipodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vc2hhMi1oYS1jcy1nMS5jcmwwMKAu
# oCyGKmh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9zaGEyLWhhLWNzLWcxLmNybDBM
# BgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3
# dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBiAYIKwYBBQUHAQEEfDB6MCQG
# CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wUgYIKwYBBQUHMAKG
# Rmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFNIQTJIaWdoQXNz
# dXJhbmNlQ29kZVNpZ25pbmdDQS5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0B
# AQsFAAOCAQEABnQP0mJYXNzfz4pMCc4FbQ9qe8NjloGuIlUgXaxlFAoMKZWMueXq
# mciPWULaE+Wd5ChTuNsrWKG7bWYYWmmo7C1RWhdZhQT/C+k3lFcsSr6gdXAXiOmv
# 3xv3d3oqfNe19G6jt6akQ6LjEauRw4xKinoK/S61Pw9c1KtEAGT8djgX74h433fy
# FPiQd//ePnihKN+GXRCeLvSaDGuVrhHuI6UUhe3MK2/Nb8MzFddwkOOdpky1HBn4
# 8oFEAOzbrTVTTv4BWLNRvAiY8UO3D2Kt322UuAdXIKNxWB94UaFt2jg2QsRkTHGQ
# MmbQ8OgMIWWNcE9RcVKuobYbzUAGPoMimTCCBU8wggQ3oAMCAQICEAt+EJA8OEkP
# +i9nmoehp7kwDQYJKoZIhvcNAQELBQAwbDELMAkGA1UEBhMCVVMxFTATBgNVBAoT
# DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTErMCkGA1UE
# AxMiRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2UgRVYgUm9vdCBDQTAeFw0xMzEwMjIx
# MjAwMDBaFw0yODEwMjIxMjAwMDBaMHYxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE
# aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xNTAzBgNVBAMT
# LERpZ2lDZXJ0IFNIQTIgSGlnaCBBc3N1cmFuY2UgQ29kZSBTaWduaW5nIENBMIIB
# IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtEpefQcPQd7E9XYWNr1x/88/
# T3NLnNEN/krLV1hehRbdAhVUmfCPPC9NAngQaMjYNUs/wfdnzpgcrjO5LR2kClST
# xIWi3zWx9fE8p7M0+11IyUbJYkS8SJnrKElTwz2PwA7eNZjpYlHfPWtAYe4EQdrP
# p1xWltH5TLdEhIeYaeWCuRPmVb/IknCSCjFvf4syq89rWp9ixD7uvu1ZpFN/C/FS
# iIp7Cmcky5DN7NJNNEyw4bWfnMb2byzN5spTdAGfZzXeOEktzu05RIIZeU4asrX7
# u3jwSWanz/pclnWSixpy2f9QklPMPsJDMgkahhNpPPuBMjMyZHVzKCYdCDA7BwID
# AQABo4IB4TCCAd0wEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYw
# EwYDVR0lBAwwCgYIKwYBBQUHAwMwfwYIKwYBBQUHAQEEczBxMCQGCCsGAQUFBzAB
# hhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wSQYIKwYBBQUHMAKGPWh0dHA6Ly9j
# YWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEhpZ2hBc3N1cmFuY2VFVlJvb3RD
# QS5jcnQwgY8GA1UdHwSBhzCBhDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu
# Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0
# cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9v
# dENBLmNybDBPBgNVHSAESDBGMDgGCmCGSAGG/WwAAgQwKjAoBggrBgEFBQcCARYc
# aHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAKBghghkgBhv1sAzAdBgNVHQ4E
# FgQUZ50PIAkMzIo65YJGcmL88cyQ5UAwHwYDVR0jBBgwFoAUsT7DaQP4v0cB1Jgm
# GggC72NkK8MwDQYJKoZIhvcNAQELBQADggEBAGoO/34TfAalS8AujPlTZAniuliR
# MFDszJ/h06gvSEY2GCnQeChfmFZADx66vbE7h1zcW9ggDe0aFk3VESQhS/EnaZAT
# 6xGhAdr9tU55WXW9OCpqw/aOQSuKoovXLFFR2ZygyONOumyoR9JO0WgfjAJXO7Mp
# ao5qICq58gBiZLrI6QD5zKTUupo12K8sZWwWfFgh3kow0PrrJF0GyZ0Wt61KRdMl
# 4gzwQKpcTax+zQaCuXZGaQjYMraC/uOpWDRDG45nZ5c/aDEWNjiVPof3x8OvnXp3
# Gdnek7X9biv8lPk9t0wSNSwwvuiNngVwmkgT9IzW5x6sOOeo860Mt3rsZ+0xggIs
# MIICKAIBATCBijB2MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5j
# MRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTUwMwYDVQQDEyxEaWdpQ2VydCBT
# SEEyIEhpZ2ggQXNzdXJhbmNlIENvZGUgU2lnbmluZyBDQQIQA7ShIT20JORyIag/
# jWOSyzAJBgUrDgMCGgUAoHgwGAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkq
# hkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGC
# NwIBFTAjBgkqhkiG9w0BCQQxFgQU0EqMACcqCLbTWUQXkOC/+Lwmk0IwDQYJKoZI
# hvcNAQEBBQAEggEAU+PoYNaR7yZ3tQe8yGCLnk+UhA0MlGL2rghyUhPGANiLnfNF
# A1n/WDg8G9Br+nJMi1nS+QyjZWMjk0WzDjrXBEcpgiGrIG3Z6Db1wso0NQqhnq4y
# L23u7wS8ZSnyfsuu2/VOvJiF6zB4WgYs4HM3DPHDDntmvcPnOZevXJvVtB5eIlt1
# uOR8vPbMGsdAe8mKb/PcrPdOouDiFz0iffl2y4cwvFAx+OqIN3Y8WyataO/N2Wms
# BcC979Xx+Ob4Kk/4bbZ2zEljfS7cVrQMW/MQO0BJ7kyfICcjm9sEh0G8ufT6qSHT
# qVNBBjgsq1PRAkgOgIudhLfrWhPnfZMWiQX6jg==
# SIG # End signature block