MISP.Tools.psm1
## Running A Build Will Compile This To A Single PSM1 File Containing All Module Code ## ## If Importing Module Source Directly, This Will Dynamically Build Root Module ## # Get list of private functions and public functions to import, in order. $PrivateFunctions = @(Get-ChildItem -Path $PSScriptRoot\private -Recurse -Filter "*.ps1") | Sort-Object Name $PublicFunctions = @(Get-ChildItem -Path $PSScriptRoot\public -Recurse -Filter "*.ps1") | Sort-Object Name $ModuleName = 'MISP.Tools' $DefaultMispPreferencePath = Join-Path -Path ([System.Environment]::GetFolderPath('LocalApplicationData')) -ChildPath $ModuleName $DefaultMispPreferenceFilename = 'MISPContext.xml' Write-Verbose "Default Preference Path: $($DefaultMispPreferencePath); Default Preference Filename: $($DefaultMispPreferenceFilename)" # Dot source the private function files. foreach ($ImportItem in $PrivateFunctions) { try { . $ImportItem.FullName Write-Verbose -Message ("Imported private function {0}" -f $ImportItem.FullName) } catch { Write-Error -Message ("Failed to import private function {0}: {1}" -f $ImportItem.FullName, $_) } } # Dot source the public function files. foreach ($ImportItem in $PublicFunctions) { try { . $ImportItem.FullName Write-Verbose -Message ("Imported public function {0}" -f $ImportItem.FullName) } catch { Write-Error -Message ("Failed to import public function {0}: {1}" -f $ImportItem.FullName, $_) } } # Export the public functions. Export-ModuleMember -Function $PublicFunctions.BaseName Export-ModuleMember -Variable DefaultMispPreferenceFilename, DefaultMispPreferencePath # Trust all certificates Function Enable-TrustAllCertsPolicy { <# .SYNOPSIS Trust all SSL certificates even if self-signed, and set protocol to Tls 1.2. #> [CmdletBinding()] Param( [Parameter(Mandatory=$False)] [switch]$NoValidateSsl ) $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($me): Setting TLS Preferences with Certificate Validation: $(!$NoValidateSsl)" # Establish Certification Policy Exception $PSDesktopException = @" using System.Net; using System.Security.Cryptography.X509Certificates; public class TrustAllCertsPolicy : ICertificatePolicy { public bool CheckValidationResult( ServicePoint srvPoint, X509Certificate certificate, WebRequest request, int certificateProblem) { return true; } } "@ # Set PowerShell to TLS1.2 [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 if ($NoValidateSsl) { if ($PSEdition -ne 'Core'){ if (-Not ("TrustAllCertsPolicy" -as [type])) { Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: Cert Policy is not enabled. Enabling." Add-Type $PSDesktopException try { [System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy } catch { throw [Exception] ` "$($Me): [Enable-TrustAllCertsPolicy]: Failed to update System.Net.ServicePointManager::CertificatePolicy to new TrustAllCertsPolicy" } } } else { # Set session default for Invoke-RestMethod and Invoke-WebRequest to SkipCertificateCheck if (-Not $PSDefaultParameterValues.ContainsKey('Invoke-RestMethod:SkipCertificateCheck')) { Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: PowerShell Core - Invoke-RestMethod SkipCertificateCheck set to true" Try { $PSDefaultParameterValues.Add("Invoke-RestMethod:SkipCertificateCheck",$true) } Catch { throw [Exception] ` "$($Me): [Enable-TrustAllCertsPolicy]: Failed to update PSDefaultParameterValues Invoke-RestMethod:SkipCertificateCheck to value true" } } } } else { Write-Verbose "$($Me): [Enable-TrustAllCertsPolicy]: Cert Policy set as Not Required." } } # Private Function Example - Replace With Your Function function Invoke-MispRestMethod { <# .SYNOPSIS Invoke a call to the MISP REST API .DESCRIPTION Invoke a call to the MISP REST API This function is intended to abstract the call to Invoke-RestMethod and supply the required details for a MISP call .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Method Base API URL for the API Calls to MISP .PARAMETER NoValidateSsl Don't validate the SSL Certificate .INPUTS [PSCredential] -> Credential [System.Uri.Builder] -> BaseUri [Switch] -> NoValidateSsl [string] -> Method (Post, Get, Put, Delete) .OUTPUTS [PSCustomObject] -> MISP Context .EXAMPLE PS> $MispContext = New-MispContext -Credential (Get-Credential -Username 'MISP Api Key') -BaseUri 'https://misp.domain.com' .LINK https://github.com/IPSecMSSP/misp.tools #> [CmdletBinding()] param( [Parameter(Mandatory=$true, HelpMessage = 'MISP Context to use. A MISP Context includes both the BaseUri as well as the API Key')] [PSCustomObject] $Context, [Parameter(Mandatory=$true, HelpMessage = 'Full URI to requested REST Resource as String or UriBuilder object')] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder]$Uri, [Parameter(Mandatory=$false, HelpMessage = 'Method to use when making the request. Defaults to GET')] [ValidateSet("Post","Get","Put","Delete")] [string] $Method = "GET", [Parameter(Mandatory=$false, HelpMessage = 'PsCustomObject containing data that will be sent as the Json Body')] [PsCustomObject] $Body ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Invoking MISP REST API with URI $($Uri)" $Headers = @{ "Content-Type" = "application/json" "Accept" = "application/json" "Authorization" = $Context.Credential.GetNetworkCredential().Password } } Process { # Set SSL Preferences/Certificate Trust Policy Enable-TrustAllCertsPolicy -NoValidateSsl:$Context.NoValidateSsl # Build list of parameters to pass to Invoke-RestMethod $Request = @{ Method = $Method Uri = $Uri.ToString() Headers = $Headers } # Add body if supplied if ($MyInvocation.BoundParameters.ContainsKey("Body")) { $Request.Add('Body', ($Body | ConvertTo-Json -Depth 10)) # $Request.Add('Body', $Body) } # There's some altered handling of "Content-Type" Header in some PowerShell 7.2 releases where ContentType Parameter has to be passed instead or in addition if (($PSVersionTable.PSVersion.Major -eq 7 -and $PSVersionTable.PSVersion.Minor -ge 2) -or ($PSVersionTable.PSVersion.Major -ge 8)) { $Request.Add("ContentType", "application/json") } $Response = Invoke-RestMethod @Request Write-Output $Response } End { } } function Add-MispAttributeToEvent { <# .SYNOPSIS Add one or more MISP Attributes to an Event .DESCRIPTION Add one or more MISP Attributes to an Event Take the provided details and use these to Add additional Attributes to a MISP event .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of MISP event to add attributes to .PARAMETER Attribute Array of Attributes to attach ot the event. Each attribute consist of a Value and Type at minimum, and may include IPs, hostnames, file hashes, etc. You can create a suitable attribute using New-MispAttribute. .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [int]$Id, [Parameter(Mandatory = $false)] [array] $Attribute ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Add Attributes to MISP Event" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "attributes/add", $Id) } Process { if ($PSCmdlet.ShouldProcess(("EventID: {0}" -f $EventId), "Add Attribute")) { foreach ($Entry in $Attribute) { Write-Verbose ('{0}: Adding attribute Value {1}, Type {2}' -f $Me, $Entry.value, $Entry.type) Write-Debug ($Entry | ConvertTo-json) # Add each Attribute to the Event in turn $AttrResp = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method POST -Body $Entry Write-Verbose ('{0}: Added attribute ID {1}' -f $Me, $AttrResp.Id) } } } End { } } # Set MISP API Key/URI Context function Get-MispAttribute { <# .SYNOPSIS Get MISP Attribute(s) .DESCRIPTION Get MISP Attribute(s) If no Attribute ID is supplied, all attributes are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Attributes .EXAMPLE PS> $Attributes = Get-MispAttribute -Context $MispContext Return all Attributes .EXAMPLE PS> $Event = Get-MispAttribute -Context $MispContext -Id 1234 Return details for attribute 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $false)] [Int]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('{0}: Get MISP Attribute(s)' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "attributes") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response.Attribute } else { # Return all fo the events Write-Output $Response } } End { } } function Get-MispAttributeType { <# .SYNOPSIS Retrieve available Attribute Type information from MISP Instance .DESCRIPTION Get the available Attribute Types, Categories, Sane Defaults and Category to Type mappings .PARAMETER Context PSCustomObject with containing the MISP Context .INPUTS [PsCustomObject] -> Context .OUTPUTS [PSCustomObject] - types - categories - category_type_mappings - sane_defaults #> [CmdLetBinding()] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('{0}: Fetch list of Available Attribute Types' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "attributes/describeTypes") } Process { $Response = Invoke-MispRestMethod -Context $Context -Method GET -Uri $Uri.Uri.ToString() Write-Output $Response.result } } # Set MISP API Key/URI Context function New-MispAttribute { <# .SYNOPSIS Construct a new MISP Attribute(s) .DESCRIPTION Construct a new MISP Attribute(s) The Attribute will be able to be added to an new Event, or added to an existing Event .PARAMETER Category The category that the Attribute belogs to. One of: - Internal reference - Targeting data - Antivirus detection - Payload delivery - Artifacts dropped - Payload installation - Persistence mechanism - Network activity - Payload type - Attribution - External analysis - Financial fraud - Support Tool - Social network - Person - Other .PARAMETER Type The type of Attribute. There are many types to choose from. Common types include: - md5 - sha1 - sha256 - ip-src - ip-dst - hostname - domain - url - user-agent - port - comment - other .INPUTS [PsCustomObject] -> Context [string] -> Category [string] -> Type .OUTPUTS [Array] -> Array of Attributes .EXAMPLE PS> $Attributes = Get-MispAttribute -Context $MispContext Return all Attributes .EXAMPLE PS> $Event = Get-MispAttribute -Context $MispContext -Id 1234 Return details for attribute 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding(SupportsShouldProcess, DefaultParameterSetName = 'NoSaneDefaults')] param ( [Parameter(Mandatory = $true, ParameterSetName = 'UseSaneDefaults')] [PsCustomObject]$Context, [Parameter(Mandatory = $false, ParameterSetName = 'NoSaneDefaults')] [ValidateSet("Internal reference", "Targeting data", "Antivirus detection", "Payload delivery", "Artifacts dropped", "Payload installation", "Persistence mechanism", "Network activity", "Payload type", "Attribution", "External analysis", "Financial fraud", "Support Tool", "Social network", "Person", "Other")] [string]$Category = "Other", [Parameter(Mandatory = $true)] [ValidateSet("md5", "sha1", "sha256", "filename", "pdb", "filename|md5", "filename|sha1", "filename|sha256", "ip-src", "ip-dst", "hostname", "domain", "domain|ip", "email", "email-src", "eppn", "email-dst", "email-subject", "email-attachment", "email-body", "float", "git-commit-id", "url", "http-method", "user-agent", "ja3-fingerprint-md5", "jarm-fingerprint", "favicon-mmh3", "hassh-md5", "hasshserver-md5", "regkey", "regkey|value", "AS", "snort", "bro", "zeek", "community-id", "pattern-in-file", "pattern-in-traffic", "pattern-in-memory", "filename-pattern", "pgp-public-key", "pgp-private-key", "ssh-fingerprint", "yara", "stix2-pattern", "sigma", "gene", "kusto-query", "mime-type", "identity-card-number", "cookie", "vulnerability", "cpe", "weakness", "attachment", "malware-sample", "link", "comment", "text", "hex", "other", "named pipe", "mutex", "process-state", "target-user", "target-email", "target-machine", "target-org", "target-location", "target-external", "btc", "dash", "xmr", "iban", "bic", "bank-account-nr", "aba-rtn", "bin", "cc-number", "prtn", "phone-number", "threat-actor", "campaign-name", "campaign-id", "malware-type", "uri", "authentihash", "vhash", "ssdeep", "imphash", "telfhash", "pehash", "impfuzzy", "sha224", "sha384", "sha512", "sha512/224", "sha512/256", "sha3-224", "sha3-256", "sha3-384", "sha3-512", "tlsh", "cdhash", "filename|authentihash", "filename|vhash", "filename|ssdeep", "filename|imphash", "filename|impfuzzy", "filename|pehash", "filename|sha224", "filename|sha384", "filename|sha512", "filename|sha512/224", "filename|sha512/256", "filename|sha3-224", "filename|sha3-256", "filename|sha3-384", "filename|sha3-512", "filename|tlsh", "windows-scheduled-task", "windows-service-name", "windows-service-displayname", "whois-registrant-email", "whois-registrant-phone", "whois-registrant-name", "whois-registrant-org", "whois-registrar", "whois-creation-date", "x509-fingerprint-sha1", "x509-fingerprint-md5", "x509-fingerprint-sha256", "dns-soa-email", "size-in-bytes", "counter", "integer", "datetime", "port", "ip-dst|port", "ip-src|port", "hostname|port", "mac-address", "mac-eui-64", "email-dst-display-name", "email-src-display-name", "email-header", "email-reply-to", "email-x-mailer", "email-mime-boundary", "email-thread-index", "email-message-id", "github-username", "github-repository", "github-organisation", "jabber-id", "twitter-id", "dkim", "dkim-signature", "first-name", "middle-name", "last-name", "full-name", "date-of-birth", "place-of-birth", "gender", "passport-number", "passport-country", "passport-expiration", "redress-number", "nationality", "visa-number", "issue-date-of-the-visa", "primary-residence", "country-of-residence", "special-service-request", "frequent-flyer-number", "travel-details", "payment-details", "place-port-of-original-embarkation", "place-port-of-clearance", "place-port-of-onward-foreign-destination", "passenger-name-record-locator-number", "mobile-application-id", "azure-application-id", "chrome-extension-id", "cortex", "boolean", "anonymised")] [string] $Type, [Parameter(Mandatory = $false)] [ValidateSet("Organisation", "Community", "Connected", "All", "Group", "Inherit")] [string]$Distribution = "Organisation", [Parameter(Mandatory = $false)] [string] $Comment, [Parameter(Mandatory = $true)] [string] $Value, [Parameter(Mandatory = $false, ParameterSetName = 'NoSaneDefaults')] [boolean] $toIds = $false, [Parameter(Mandatory = $false)] [datetime] $FirstSeen, [Parameter(Mandatory = $false)] [datetime] $LastSeen, [Parameter(Mandatory = $false)] [System.Boolean]$Correlate = $true ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('{0}: Build New MISP Attribute(s)' -f $Me) $DistributionMap = @{ Organisation = 0 # Your organisation only Community = 1 # This community only Connected = 2 # Connected Communities All = 3 # All communities Group = 4 # Sharing Group Inherit = 5 # Inherit Event } } Process { If ($PSCmdlet.ParameterSetName -eq 'UseSaneDefaults') { $TypeMapping = Get-MispAttributeType -Context $Context $Category = $TypeMapping.sane_defaults.$Type.default_category $toIds = $TypeMapping.sane_defaults.$Type.to_ids } if ($PSCmdlet.ShouldProcess("Construct New MISP Attribute")) { # Build the Attribute Body $Attribute = @{ type = $Type category = $Category distribution = $DistributionMap.($Distribution) to_ids = $toIds comment = $Comment value = $Value correlate = $Correlate } if ($PSBoundParameters.ContainsKey('FirstSeen')) { $Attribute.first_seen = $FirstSeen.ToUniversalTime().ToString("o") } if ($PSBoundParameters.ContainsKey('LastSeen')) { $Attribute.last_seen = $LastSeen.ToUniversalTime().ToString("o") } # Return all for the events Write-Output $Attribute Write-Debug ($Attribute | ConvertTo-Json) } } End { } } # Set MISP API Key/URI Context function Search-MispAttribute { <# .SYNOPSIS Search for MISP Event(s) .DESCRIPTION Search for MISP Event(s) If no Event ID is supplied, the first 100 events are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Search JSON containing search criteria As an example, if we want to export all the IP Addresses that have a TLP marking and not marked as TLP:red, the following filter will achieve this $Search = @{ returnFormat = "json" type = @{ OR = @("ip-src", "ip-dst") } tags = @{ NOT = @("tlp:red") OR = @("tlp:%") } } Refer to MISP Automation -> Search for further details on available search criteria .INPUTS [PsCustomObject] -> Context [hastable] -> Search .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Search = @{ returnFormat = "json" value = 8.8.8.8 } PS> $Attributes = Search-MispAttribute -Context $MispContext -Search $Search Return matching attributes .LINK https://www.circl.lu/doc/misp/automation/#search https://github.com/IPSecMSSP/misp.tools #> [CmdletBinding( SupportsShouldProcess, ConfirmImpact = "Low" )] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [hashtable]$Search ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Search for MISP Attributes(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "attributes/restSearch") } Process { if ($PSCmdlet.ShouldProcess("Search MISP Attributes")) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Body $Search -Method 'POST' # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function Get-MispEvent { <# .SYNOPSIS Get MISP Event(s) .DESCRIPTION Get MISP Event(s) If no Event ID is supplied, all events are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = Get-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = Get-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$false)] [Int]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Event(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, $Id) } } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response.Event } else { # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function New-MispEvent { <# .SYNOPSIS Create a new MISP Event .DESCRIPTION Create a new MISP Event Take the provided details and use these to create a new Event in MISP .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Info Name/Description of the Event .PARAMETER ThreatLevel Threat Level of the event. One of [High, Medium, Low, Undefined] .PARAMETER Analysis Analysis State for the Event. One of [Initial, Ongoing, Complete]. Defaults to 'Initial' .PARAMETER Distribution Distribution Level for the Event. One of [Organisation, Community, Connected, All, Group, Inherit]. Defaults to 'Organisation' .PARAMETER Published Boolean value as to whether the event should be published. Defaults to false. .PARAMETER Attribute Array of Attributes to attach ot the event. Each attribute consist of a Value and Type at minimum, and may include IPs, hostnames, file hashes, etc. .PARAMETER Organisation Name or Id of organisation to associate the event to. This will be uses as the "Creator Organisation". If not specified, will use the Organisation of the User creating the Event. The Owner Organisation is always the Organisation of the user creating the event. .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [string]$Info, [Parameter(Mandatory = $false)] [ValidateSet("High", "Medium", "Low", "Undefined")] [string]$ThreatLevel = "Undefined", [Parameter(Mandatory = $false)] [ValidateSet("Initial", "Ongoing", "Complete")] [string]$Analysis = "Initial", [Parameter(Mandatory = $false)] [ValidateSet("Organisation", "Community", "Connected", "All", "Group", "Inherit")] [string]$Distribution = "Organisation", [Parameter(Mandatory = $false)] [System.Boolean]$Published = $false, [Parameter(Mandatory = $false)] [array] $Attribute, [Parameter(Mandatory = $false)] [string]$Organisation ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Create new MISP Event(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events") $ThreatLevelMap = @{ High = 1 Medium = 2 Low = 3 Undefined = 4 } $AnalysisMap = @{ Initial = 0 Ongoing = 1 Complete = 2 } $DistributionMap = @{ Organisation = 0 # Your organisation only Community = 1 # This community only Connected = 2 # Connected Communities All = 3 # All communities Group = 4 # Sharing Group Inherit = 5 # Inherit Event } } Process { # Build up the Event Body $EventBody = [pscustomobject]@{ date = (Get-Date).ToString("yyyy-MM-dd") threat_level_id = $ThreatLevelMap.($ThreatLevel) info = $Info published = $Published analysis = $AnalysisMap.($Analysis) distribution = $DistributionMap.($Distribution) } if ($PSBoundParameters.ContainsKey('Organisation')) { # Check if the organisation is an Integer or something else $OrganisationId = 0 if ([int]::TryParse($Organisation, [ref]$OrganisationId)) { # Integer ID, check for existence $Org = Get-MispOrganisation -Context $Context -Id $OrganisationId if (-not $Org) { throw ('Organisation does not exist in MISP: {0}' -f $Organisation) } } elseif ($Organisation -match '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') { # Id is a UUID $Org = Get-MispOrganisation -Context $Context -Id $Organisation if (-not $Org) { throw ('Organisation does not exist in MISP: {0}' -f $Organisation) } } else { # Not an integer, try a name match $Org = Get-MispOrganisation -Context $Context -Name $Organisation if (-not $Org) { Write-Verbose ('Organisation Name not found in MISP: {0}' -f $Organisation) $Org = New-MispOrganisation -Context $Context -Name $Organisation } } # When setting the Creator Org for an event, the back-end checks the value of orgc.uuid, so pass the whole Org object $EventBody | Add-Member -MemberType NoteProperty -Name 'orgc' -Value $Org } # If attributes were supplied, add these too if ($MyInvocation.BoundParameters.ContainsKey("Attribute")) { $EventBody | Add-Member -MemberType NoteProperty -Name 'Attribute' -Value $Attribute } Write-Debug "Event Body:`n$($EventBody | ConvertTo-Json -Depth 10)" If ($PSCmdlet.ShouldProcess("Create new MISP Event")) { # Call the API #Write-Output $EventBody | ConvertTo-Json -depth 10 $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" -Body $EventBody } Write-Output $Response.Event } End { } } # Set MISP API Key/URI Context function Publish-MispEvent { <# .SYNOPSIS Publish an existing MISP Event .DESCRIPTION Publish an existing MISP Event Mark the Event identified by the id (numeric or UUID) as published .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$true)] [string]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('{0}: Publish MISP Event(s)' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, 'events/publish') } Process { $Uri.Path = [io.path]::combine($Uri.Path, $Id) If ($PSCmdlet.ShouldProcess("Publish MISP Event")) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" } Write-Output $Response } End { } } # Set MISP API Key/URI Context function Search-MispEvent { <# .SYNOPSIS Search for MISP Event(s) .DESCRIPTION Search for MISP Event(s) If no Event ID is supplied, the first 100 events are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Search JSON containing search criteria As an example, if we want to export all the IP Addresses that have a TLP marking and not marked as TLP:red, the following filter will achieve this $Search = @{ returnFormat = "json" type = @{ OR = @("ip-src", "ip-dst") } tags = @{ NOT = @("tlp:red") OR = @("tlp:%") } } Refer to MISP Automation -> Search for further details on available search criteria .INPUTS [PsCustomObject] -> Context [hastable] -> Search .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = Search-MispEvent -Context $MispContext -Search $Search Return matching events .LINK https://www.circl.lu/doc/misp/automation/#search https://github.com/IPSecMSSP/misp.tools #> [CmdletBinding( SupportsShouldProcess, ConfirmImpact="Low" )] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$true)] [hashtable]$Search ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Search for MISP Event(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events/restSearch") } Process { if ($PSCmdlet.ShouldProcess("Search MISP Events")) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Body $Search -Method POST # Return all fo the events Write-Output $Response } } End { } } # Set MISP API Key/URI Context function Unpublish-MispEvent { <# .SYNOPSIS Unpublish an existing MISP Event .DESCRIPTION Unpublish an existing MISP Event Unmark the Event identified by the id (numeric or UUID) as published .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific event .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter(Mandatory=$true)] [string]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('{0}: Unpublish MISP Event(s)' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, 'events/unpublish') } Process { $Uri.Path = [io.path]::combine($Uri.Path, $Id) If ($PSCmdlet.ShouldProcess("Unpublish MISP Event")) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" } Write-Output $Response } End { } } # Set MISP API Key/URI Context function Update-MispEvent { <# .SYNOPSIS Create a new MISP Event .DESCRIPTION Create a new MISP Event Take the provided details and use these to create a new Event in MISP .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of MISP event to update .PARAMETER Info Name/Description of the Event .PARAMETER ThreatLevel Threat Level of the event. One of [High, Medium, Low, Undefined] .PARAMETER Analysis Analysis State for the Event. One of [Initial, Ongoing, Complete]. Defaults to 'Initial' .PARAMETER Distribution Distribution Level for the Event. One of [Organisation, Community, Connected, All, Group, Inherit]. Defaults to 'Organisation' .PARAMETER Published Boolean value as to whether the event should be published. Defaults to false. .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [int]$Id, [Parameter(Mandatory = $false)] [string]$Info, [Parameter(Mandatory = $false)] [ValidateSet("High", "Medium", "Low", "Undefined")] [string]$ThreatLevel, [Parameter(Mandatory = $false)] [ValidateSet("Initial", "Ongoing", "Complete")] [string]$Analysis = "Initial", [Parameter(Mandatory = $false)] [ValidateSet("Organisation", "Community", "Connected", "All", "Group", "Inherit")] [string]$Distribution = "Organisation", [Parameter(Mandatory = $false)] [System.Boolean]$Published = $false ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): UPdate MISP Event" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "events/edit") $ThreatLevelMap = @{ High = 1 Medium = 2 Low = 3 Undefined = 4 } $AnalysisMap = @{ Initial = 0 Ongoing = 1 Complete = 2 } $DistributionMap = @{ Organisation = 0 # Your organisation only Community = 1 # This community only Connected = 2 # Connected Communities All = 3 # All communities Group = 4 # Sharing Group Inherit = 5 # Inherit Event } } Process { # Create a new UriBuilder object for each event $EventUri = [System.UriBuilder]$Uri.Uri.ToString() $EventUri.Path = [io.path]::combine($EventUri.Path, $Id) # Build up the Event Body $EventBody = [pscustomobject]@{} if ($PSBoundParameters.ContainsKey('Info')) { $EventBody | Add-Member -MemberType NoteProperty -Name 'info' -Value $Info } if ($PSBoundParameters.ContainsKey('ThreatLevel')) { $EventBody | Add-Member -MemberType NoteProperty -Name 'threat_level_id' -Value $ThreatLevelMap.($ThreatLevel) } if ($PSBoundParameters.ContainsKey('Analysis')) { $EventBody | Add-Member -MemberType NoteProperty -Name 'analysis' -Value $AnalysisMap.($Analysis) } if ($PSBoundParameters.ContainsKey('Distribution')) { $EventBody | Add-Member -MemberType NoteProperty -Name 'distribution' -Value $DistributionMap.($Distribution) } if ($PSBoundParameters.ContainsKey('Published')) { $EventBody | Add-Member -MemberType NoteProperty -Name 'published' -Value $Published } Write-Debug "Event Body:`n$($EventBody | ConvertTo-Json -Depth 10)" If ($PSCmdlet.ShouldProcess("Update MISP Event")) { # Call the API #Write-Output $EventBody | ConvertTo-Json -depth 10 $Response = Invoke-MispRestMethod -Context $Context -Uri $EventUri -Method "PUT" -Body $EventBody } Write-Output $Response.Event } End { } } function Add-MispGalaxyClusterToObject { <# .SYNOPSIS Add one or more MISP Galaxy Clusters to an Object .DESCRIPTION Add one or more MISP Galaxy Clusters to an Object Add the Galaxy Clusters to the object specified by Object Type and Id. .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER ObjectType Type of MISP object to add attributes to. One of 'event', 'attribute', 'tag_collection' .PARAMETER ObjectId Id of MISP object to add attributes to .PARAMETER Id Array of Galaxy Cluster Ids to attach to the specified object/type You will need to ascertain appropriate galaxy cluster Ids using Get-MispGalaxyCluster. .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#events-management #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [ValidateSet('event','attribute','tag_collection')] [string]$ObjectType, [Parameter(Mandatory = $true)] [int]$ObjectId, [Parameter(Mandatory = $false)] [array] $Id, [Parameter(Mandatory = $false)] [bool] $Local = $false ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Add Galaxy Cluster to MISP Object" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "galaxies/attachCluster", $ObjectId, ("local:{0}" -f $local)) } Process { if ($PSCmdlet.ShouldProcess(("Object Type: {0}, ObjectId: {1}" -f $ObjectType, $ObjectId), ("Add Galaxy Cluster {0}" -f $Id))) { foreach ($Entry in $Id) { Write-Verbose ('{0}: Adding Galaxy Cluster {1} to ObjectId {2}, Type {3}' -f $Me, $Entry, $ObjectId, $ObjectType) $Body = @{ Galaxy = @{ target_id = $Entry } } Write-Debug ($Body) # Add each Galaxy Cluster to the Object in turn $Resp = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method POST -Body $Body if ($Resp.saved) { Write-Verbose ('{0}: Added Galaxy Cluster {1}' -f $Me, $Resp.success) } else { Write-Verbose ('{0}: Failed to add Galaxy Cluster: {1}' -f $Me, $Resp.message) } } } } End { } } # Set MISP API Key/URI Context function Get-MispGalaxy { <# .SYNOPSIS Get MISP Galax(y/ies) .DESCRIPTION Get MISP Galax(y/ies) If no Galaxy ID is supplied, all galaxies are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific galaxy .PARAMETER Criteria Search criteria to match. eg. "banana" .INPUTS [PsCustomObject] -> Context [Int] -> Id [string] -> Criteria .OUTPUTS [Array] -> Array of tags .EXAMPLE PS> $Tags = Get-MispGalaxy -Context $MispContext Return all Tags .EXAMPLE PS> $Event = Get-MispGalaxy -Context $MispContext -Id 1234 Return details for galaxy 1234 .EXAMPLE PS> $Event = Get-MispGalaxy -Context $MispContext -Criteria 'banana' Return all galaxies containing the text 'banana' .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter( Mandatory = $false, ParameterSetName = 'ById' )] [Int]$Id, [Parameter( Mandatory = $false, ParameterSetName = 'ByCriteria' )] [string]$Criteria ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Galax(y/ies)" # Default Method $Method = 'GET' # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "galaxies") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { $Method = 'POST' $SearchBody = @{ value = $Criteria } } } Process { # Call the API if ($MyInvocation.BoundParameters.ContainsKey('ByCriteria')) { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method $Method -Body $SearchBody } else { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method $Method } if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single galaxy was requested Write-Output $Response } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { Write-Output $Response.Galaxy } else { # Return all fo the galaxies Write-Output $Response.Galaxy } } End { } } # Set MISP API Key/URI Context function Get-MispGalaxyCluster { <# .SYNOPSIS Get MISP Galaxy clusters .DESCRIPTION Get MISP Galaxy Clusters If no Galaxy Cluster ID is supplied, all galaxy clusters are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER GalaxyId Id of a specific galaxy .PARAMETER Id Id of a specific galaxy cluster .PARAMETER Criteria Search criteria in SQL Match format. eg. "%banana%" .INPUTS [PsCustomObject] -> Context [Int] -> Id [string] -> Criteria .OUTPUTS [Array] -> Array of tags .EXAMPLE PS> $Tags = Get-MispGalaxy -Context $MispContext Return all Tags .EXAMPLE PS> $Event = Get-MispGalaxy -Context $MispContext -Id 1234 Return details for galaxy 1234 .EXAMPLE PS> $Event = Get-MispGalaxy -Context $MispContext -Criteria '%banana%' Return all galaxies containing the text 'banana' .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding(DefaultParameterSetName = 'ListAll')] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [Int]$GalaxyId, [Parameter( Mandatory = $false, ParameterSetName = 'ById' )] [Int]$Id, [Parameter( Mandatory = $false, ParameterSetName = 'ByCriteria' )] [string]$Criteria, [Parameter(Mandatory = $false)] [ValidateSet('all', 'default', 'org', 'deleted')] [string]$SearchContext = 'default' ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Galaxy Cluster(s)" # Default Method $Method = 'GET' # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "galaxy_clusters") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view", $Id) } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { $Uri.Path = [io.path]::combine($Uri.Path, "index", $GalaxyId) $Method = 'POST' $SearchBody = @{ context = $SearchContext searchall = $Criteria } } else { $Uri.Path = [io.path]::combine($Uri.Path, "index", $GalaxyId) } } Process { # Call the API if ($MyInvocation.BoundParameters.ContainsKey('ByCriteria')) { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method $Method -Body $SearchBody } else { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method $Method } if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single galaxy cluster was requested Write-Output $Response } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { Write-Output $Response.GalaxyCluster } else { # Return all fo the galaxy clusters Write-Output $Response.GalaxyCluster } } End { } } # Set MISP API Key/URI Context function New-MispContext { <# .SYNOPSIS Create a PSObject containing Credentials and Base URI for interaction with MISP .DESCRIPTION Create a PSObject containing Credentials and Base URI for interaction with MISP Credentials supplied in the form of a PSCredential Object and Base URI as either a string or Uri::Builder object .PARAMETER Credential PSCredential Object with ApiKey as Password .PARAMETER BaseUri Base API URL for the API Calls to MISP .PARAMETER NoValidateSsl Don't validate the SSL Certificate .INPUTS [PSCredential] -> Credential [System.Uri.Builder] -> BaseUri [Switch] -> NoValidateSsl .OUTPUTS [PSCustomObject] -> MISP Context .EXAMPLE PS> $MispContext = New-MispContext -Credential (Get-Credential -Username 'MISP Api Key') -BaseUri 'https://misp.domain.com' .LINK https://github.com/IPSecMSSP/misp.tools #> [CmdletBinding(SupportsShouldProcess)] param ( [Parameter(Mandatory=$true)] [pscredential]$Credential, [Parameter(Mandatory=$true)] [ValidateScript({ $TypeName = $_ | Get-Member | Select-Object -ExpandProperty TypeName -Unique if ($TypeName -eq 'System.String' -or $TypeName -eq 'System.UriBuilder') { [System.UriBuilder]$_ } })] [System.UriBuilder]$BaseUri, [Parameter(Mandatory=$false)] [switch]$NoValidateSsl ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Setting credentials for MISP with Base URI: $($BaseUri). Cerificiate Validation: $(!$NoValidateSsl)" $MispContext = New-Object pscustomobject -Property @{ Credential = $Credential BaseUri = $BaseUri NoValidateSsl = [boolean]$NoValidateSsl } } Process { # Set SSL Preferences/Certificate Trust Policy Enable-TrustAllCertsPolicy if ($PSCmdlet.ShouldProcess($MispContext.BaseUri), "Create new MISP Context") { Write-Output $MispContext } } End { } } # Read MISP API Key/URI from configuration file function Read-MispContext { <# .SYNOPSIS Read the MISP Context from a saved preferences file .DESCRIPTION Read the MISP Context from a saved preferences file Check to see that the contents of the file matches our expectations after loading .PARAMETER Path Path containing the preferences file, defaults to users AppDataLocal Path .PARAMETER FileName Name of file to use for MISP Context, defaults to MISPContext.xml .INPUTS [String] -> Path [String] -> Filename .OUTPUTS [PSCustomObject] -> MISP Context into $MispContext .EXAMPLE PS > $MispContext = Read-MispContext .EXAMPLE PS > $MispContext = Read-MispContext -Filename 'MyMISP.xml' .LINK https://github.com/IPSecMSSP/misp.tools #> [CmdletBinding()] Param ( [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 0 )] [string] $Path = $DefaultMispPreferencePath, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 1 )] [string] $Filename = $DefaultMispPreferenceFilename ) Begin { $Me = $MyInvocation.MyCommand.Name $ContextFilename = Join-Path -Path $Path -ChildPath $Filename Write-Verbose "$($Me): Loading MISP Context from $($ContextFilename)" } Process { # Check directory exists if (!(Test-Path -Path $Path -PathType Container)) { throw "Path not found: $($Path)" } # Check file exists if (!(Test-Path -Path $ContextFilename -PathType Leaf)) { throw "Context File not found: $($ContextFilename)" } # Load file contents try { $LoadContext = Import-Clixml -Path $ContextFilename } catch { throw "Error loading context from file $($ContextFilename)" } $ContextProperties = $LoadContext | Get-Member -MemberType NoteProperty # Check if a Credential Property Exists If (!($ContextProperties.Name -contains 'Credential')){ throw "Context File did not contain a Credential Property" } # Ensure Credential Property is of the correct type If (!($LoadContext.Credential.GetType().Name -eq 'PSCredential')) { throw "Context File Credential Property is not of type [PSCredential]" } # Check if a Credential Property Exists If (!($ContextProperties.Name -contains 'BaseUri')){ throw "Context File did not contain a BaseUri Property" } # Ensure BaseUri Property is of the correct type If (!($LoadContext.BaseUri.GetType().Name -eq 'String')) { throw "Context File BaseUri Property is not of type [String]" } # Check if a NoValidateSsl Property Exists If (!($ContextProperties.Name -contains 'NoValidateSsl')){ throw "Context File did not contain a NoValidateSsl Property" } # Ensure NoValidateSsl Property is of the correct type If (!($LoadContext.NoValidateSsl.GetType().Name -eq 'boolean')) { throw "Context File NoValidateSsl Property is not of type [boolean]" } $OutputContext = [PSCustomObject]@{ Credential = [PSCredential]$LoadContext.Credential BaseUri = [System.UriBuilder]$LoadContext.BaseUri NoValidateSsl = $LoadContext.NoValidateSsl } Write-Output $OutputContext } End { } } # Save MISP API Key/URI to Configuration File function Save-MispContext { <# .SYNOPSIS Write MISP Context containing Api Key/URL out to a file for future reference .DESCRIPTION Write MISP Context containing Api Key/Url out to a file for future reference Default location is user's profile path, but can be overridden with -DestinationPath Default Filename is MISPContext.xml, but can be overridden with -Filename .PARAMETER DestinationPath Path to save MispContext to, defaults to users AppDataLocal Path on your operating system .PARAMETER FileName Name of file to use for MISP Context, defaults to MISPContext.xml .INPUTS [PSCustomObject] -> MISP Context [String] -> DestinationPath [String] -> Filename .OUTPUTS No output is expected if this succeeds .EXAMPLE Save to default location and filename PS > $MispContext | Save-MispContext .EXAMPLE Save to default location with alternate filename PS > $MispContext | Save-MispContext -Filename 'MyMISP.xml' .LINK https://github.com/IPSecMSSP/misp.tools #> [CmdletBinding(SupportsShouldProcess)] Param ( [Parameter( Mandatory = $true, ValueFromPipeline = $true, Position = 0 )] [pscustomobject] $InputObject, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 1 )] [string] $DestinationPath = $DefaultMispPreferencePath, [Parameter( Mandatory = $false, ValueFromPipeline = $false, Position = 2 )] [string] $Filename = $DefaultMispPreferenceFilename ) Begin { $Me = $MyInvocation.MyCommand.Name $ContextFilename = Join-Path -Path $DestinationPath -ChildPath $Filename Write-Verbose "$($Me): Saving MISP Context to $($ContextFilename)" } Process { # Create the folder if it does not exist if (!(Test-Path $DestinationPath)) { if ($PSCmdlet.ShouldProcess($DestinationPath, "Create Configuration Folder")) { Write-Verbose "$($me): Configration Folder $($DestinationPath) does not exist, Creating" $Output = New-Item -ItemType Directory -Path $DestinationPath Write-Debug $Output } } # Save the file if ($PSCmdlet.ShouldProcess($ContextFilename, "Save MISP Context")) { Write-Verbose "$($Me): Saving MISP Context to $($ContextFilename)" # Ensure output is appropriately formatted $OutputObject = [PSCustomObject]@{ Credential = $InputObject.Credential BaseUri = $InputObject.BaseUri.ToString() NoValidateSsl = [bool]$InputObject.NoValidateSsl } $OutputObject | Export-Clixml $ContextFilename } } End { } } function Get-MispOrganisation { <# .SYNOPSIS Get MISP Organisation Details .DESCRIPTION Get MISP Organisation Details, either by Id, Name, or all Take the provided details and use these to create a new Organisation in MISP .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Name Name of the organisation to return detais for. .PARAMETER Id Id of Organisation to return details for. .INPUTS [PsCustomObject] -> Context [Int] -> Id [string] -> Name .OUTPUTS [Array] -> Array of Organisations .EXAMPLE PS> $Org = Get-MispOrganisation -Context $MispContext -Name 'My Organisation' Returns definition of 'My Organisation', or $Null if it does not exist .EXAMPLE PS> $Org = Get-MispOrganisation -Context $MispContext -Id 7 Returns definition of the organisation with Id 7, or $Null if it does not exist .LINK https://github.com/IPSecMSSP/misp.tools https://www.misp-project.org/openapi/#tag/Organisations #> [cmdletbinding(DefaultParameterSetName = 'All')] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true,ParameterSetName = 'ByName')] [string]$Name, [Parameter(Mandatory = $true, ParameterSetName = 'ById')] [string]$Id ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('Entering: {0}' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, 'organisations') } Process { switch ($PsCmdLet.ParameterSetName) { 'ById' { # Append the Org ID to the URI Path $Uri.Path = [io.path]::combine($Uri.Path, 'view', $Id) $Organisation = (Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "GET").Organisation } 'ByName' { # Get all orgs $Orgs = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "GET" # Filter to the matching Org $Organisation = $Orgs.Organisation | Where-Object -Property name -eq $Name } 'All' { # Get all orgs $Organisation = (Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "GET").Organisation } } # Output Organisation(s) Write-Output $Organisation } End { } } function New-MispOrganisation { <# .SYNOPSIS Create a new MISP Organisation .DESCRIPTION Create a new MISP Organisation Take the provided details and use these to create a new Organisation in MISP .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Name Name of the organisation. .PARAMETER Uuid Specific UUID to assign to Organisation. If not specified, a new UUID will be generated automatically. .PARAMETER Description More detailed description of the Organisation. .PARAMETER Nationality Nationality of the Organisation, if applicable/known. .PARAMETER Sector Industry Sector of the Organisation. Sectors defined as per https://github.com/MISP/misp-galaxy/blob/main/clusters/sector.json .PARAMETER LocalOrg Boolean value indicating whether this is a local organisation. Defaults to true. .PARAMETER RestrictDomains Array of domains to restrict users to. Users that are part of the listed domains will be associated with this org. .PARAMETER LandingPage URL to Landing Page for Org. .PARAMETER Type Organisation Type. Freeform text. .INPUTS [PsCustomObject] -> Context [String] -> Name [string] -> Uuid [string] -> Description [string] -> Nationality [string] -> Sector [boolean] -> LocalOrg [array][string] -> RestrictDomains [string] -> LandingPage [string] -> Type .OUTPUTS [PsCustomObject] -> Properties of New Organisation .EXAMPLE PS> $Org = New-MispOrganisation -Context $MispContext -Name 'My Organisation' Returns definition of new organisation .EXAMPLE PS> $Org = New-MispOrganisation -Context $MispContext -Name 'My Organisation' -UUID '63f6c8c0-2563-4b3b-bc46-64b3016f9948' -Local $False Create a new remote organisation with the specified UUID (to match a definition elsewhere) Returns definition of the new organisation .LINK https://github.com/IPSecMSSP/misp.tools https://www.misp-project.org/openapi/#tag/Organisations #> [cmdletbinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [string]$Name, [Parameter(Mandatory = $false)] [string]$Uuid, [Parameter(Mandatory = $false)] [string]$Description, [Parameter(Mandatory = $false)] [string]$Type, [Parameter(Mandatory = $false)] [string]$Nationality, [Parameter(Mandatory = $false)] [string]$Sector, [Parameter(Mandatory = $false)] [boolean]$LocalOrg = $true, [Parameter(Mandatory = $false)] [array]$RestrictDomains, [Parameter(Mandatory = $false)] [string]$LandingPage ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('Entering: {0}' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, 'admin','organisations', 'add') } Process { $Organisation = @{ name = $Name description = $Description type = $Type sector = $Sector uuid = $Uuid nationality = $Nationality restricted_to_domain = $RestrictDomains landingpage = $LandingPage local = $LocalOrg } if ($PSCmdlet.ShouldProcess($Uri.Uri.ToString(), 'Create new MISP Organisation')) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "POST" -Body $Organisation } Write-Output $Response.Organisation } End { } } function Update-MispOrganisation { <# .SYNOPSIS Create a new MISP Event .DESCRIPTION Create a new MISP Event Take the provided details and use these to create a new Event in MISP .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of Organisation to update. .PARAMETER Name New Name of the organisation. .PARAMETER Uuid New UUID to assign to Organisation. If not specified, a new UUID will be generated automatically. .PARAMETER Description More detailed description of the Organisation. .PARAMETER Nationality Nationality of the Organisation, if applicable/known. .PARAMETER Sector Industry Sector of the Organisation. Sectors defined as per https://github.com/MISP/misp-galaxy/blob/main/clusters/sector.json .PARAMETER LocalOrg Boolean value indicating whether this is a local organisation. Defaults to true. .PARAMETER RestrictDomains Array of domains to restrict users to. Users that are part of the listed domains will be associated with this org. .PARAMETER LandingPage URL to Landing Page for Org. .PARAMETER Type Organisation Type. Freeform text. .INPUTS [PsCustomObject] -> Context [Int] -> Id .OUTPUTS [Array] -> Array of Events .EXAMPLE PS> $Events = New-MispEvent -Context $MispContext Return first all Events .EXAMPLE PS> $Event = New-MispEvent -Context $MispContext -Id 1234 Return details for event 1234 .LINK https://github.com/IPSecMSSP/misp.tools https://www.misp-project.org/openapi/#tag/Organisations #> [cmdletbinding(SupportsShouldProcess)] param ( [Parameter(Mandatory = $true)] [PsCustomObject]$Context, [Parameter(Mandatory = $true)] [string]$Id, [Parameter(Mandatory = $false)] [string]$Name, [Parameter(Mandatory = $false)] [string]$Description, [Parameter(Mandatory = $false)] [string]$Type, [Parameter(Mandatory = $false)] [string]$Nationality, [Parameter(Mandatory = $false)] [string]$Sector, [Parameter(Mandatory = $false)] [boolean]$LocalOrg = $true, [Parameter(Mandatory = $false)] [array]$RestrictDomains, [Parameter(Mandatory = $false)] [string]$LandingPage ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose ('Entering: {0}' -f $Me) # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, 'admin', 'organisations', 'edit') } Process { $Uri.Path = [io.path]::combine($Uri.Path, $Id) $Organisation = @{} if ($PSBoundParameters.ContainsKey('Name')) { Write-Verbose ('{0}: Adding parameter [name]: {1}' -f $Me, $Name) $Organisation.Add('name', $Name) } if ($PSBoundParameters.ContainsKey('Description')) { $Organisation.Add('descriptiopn',$Description) } if ($PSBoundParameters.ContainsKey('Uuid')) { $Organisation.Add('uuid', $Uuid) } if ($PSBoundParameters.ContainsKey('Type')) { $Organisation.Add('type', $Type) } if ($PSBoundParameters.ContainsKey('Sector')) { $Organisation.Add('sector', $Sector) } if ($PSBoundParameters.ContainsKey('Nationality')) { $Organisation.Add('nationality', $Nationality) } if ($PSBoundParameters.ContainsKey('RestrictDomains')) { $Organisation.Add('restricted_to_domain', $RestrictDomains) } if ($PSBoundParameters.ContainsKey('LocalOrg')) { $Organisation.Add('local', $LocalOrg) } Write-Debug ('{0}: Payload: {1}' -f $Me, $EventBody) if ($PSCmdlet.ShouldProcess($Uri.Uri.ToString(), 'Update MISP Organisation')) { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method "PUT" -Body $Organisation } Write-Output $Response } End { } } # Set MISP API Key/URI Context function Add-MispAttributeTag { <# .SYNOPSIS Add a tag to a MISP Attribute .DESCRIPTION Add a tag to a MISP Attribute An attribute may have one or more tags associated. This can assist with the filtering and grouping of attributes .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER TagId Id of tag to add to Attribute .PARAMETER EventId Id of Attribute to which tag should be added .PARAMETER Local Whether the tag should be attached locally or not to the event .INPUTS [PsCustomObject] -> Context [Int] -> TagId [Int] -> AttributeId [switch] -> Local .OUTPUTS None if successful .EXAMPLE PS> $Tags = Add-MispAttributeTag -Context $MispContext -AttributeId 1234 -TagId 69 -Local Add Tag with Id 69 to attribute Id 1234, locally .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$true )] [Int]$AttributeId, [Parameter( Mandatory=$true )] [Int]$TagId, [Parameter( Mandatory=$false )] [switch] $Local ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Add Tag to MISP Attribute" if($Local) { $TagLocal = 1 } else { $TagLocal = 0 } # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, ("attributes/addTag/{0}/{1}/local:{2}" -f $AttributeId, $TagId, $TagLocal)) } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' Write-Debug $Response | ConvertTo-Json -Depth 10 if (!$Response.saved) { Write-Warning "Unable to add tag to Attribute" } } End { } } # Set MISP API Key/URI Context function Add-MispEventTag { <# .SYNOPSIS Add a tag to a MISP Event .DESCRIPTION Add a tag to a MISP Event An event may have one or more tags associated. This can assist with the filtering and grouping of events .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER TagId Id of tag to add to Event .PARAMETER EventId Id of event to which tag should be added .PARAMETER Local Whether the tag should be attached locally or not to the event .INPUTS [PsCustomObject] -> Context [Int] -> TagId [Int] -> EventId [switch] -> Local .OUTPUTS None if successful .EXAMPLE PS> $Tags = Add-MispEventTag -Context $MispContext -EventId 1234 -TagId 69 -Local Add Tag with Id 69 to event Id 1234, locally .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding()] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$true )] [Int]$EventId, [Parameter( Mandatory=$true )] [Int]$TagId, [Parameter( Mandatory=$false )] [switch] $Local ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Add Tag to MISP Event" if($Local) { $TagLocal = 1 } else { $TagLocal = 0 } # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, ("events/addTag/{0}/{1}/local:{2}" -f $EventId, $TagId, $TagLocal)) } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' Write-Debug $Response | ConvertTo-Json -Depth 10 if (!$Response.saved) { Write-Warning "Unable to add tag to Event" } } End { } } # Set MISP API Key/URI Context function Get-MispTag { <# .SYNOPSIS Get MISP Tag(s) .DESCRIPTION Get MISP Tag(s) If no Tag ID is supplied, all tags are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific tag .PARAMETER Criteria Search criteria in SQL Match format. eg. "%banana%" .INPUTS [PsCustomObject] -> Context [Int] -> Id [string] -> Criteria .OUTPUTS [Array] -> Array of tags .EXAMPLE PS> $Tags = Get-MispTag -Context $MispContext Return all Tags .EXAMPLE PS> $Event = Get-MispTag -Context $MispContext -Id 1234 Return details for tag 1234 .EXAMPLE PS> $Event = Get-MispTag -Context $MispContext -Criteria '%banana%' Return all tags containing the text 'banana' .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#attribute-management #> [CmdletBinding(DefaultParameterSetName='ListAll')] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$false, ParameterSetName='ById' )] [Int]$Id, [Parameter( Mandatory=$false, ParameterSetName='ByCriteria' )] [string]$Criteria ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Attribute(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "tags") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { $Uri.Path = [io.path]::Combine($Uri.Path, "search/$($Criteria)") } } Process { # Call the API $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response } elseif ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { Write-Output $Response.Tag } else { # Return all fo the events Write-Output $Response.Tag } } End { } } # Set MISP API Key/URI Context function Get-MispWarningList { <# .SYNOPSIS Get MISP Warning List(s) .DESCRIPTION Get MISP Warning List(s) If no Warning List ID is supplied, all warning lists are returned .PARAMETER Context PSCustomObject with containing the MISP Context .PARAMETER Id Id of a specific warning list .PARAMETER Criteria Criteria to limit the set of returned Warning Lists Available Criteria are: - enabled Any criteria other than 'enabled' are ignored by the API .INPUTS [PsCustomObject] -> Context [Int] -> Id [hashtable] -> Criteria .OUTPUTS [Array] -> Array of Warning Lists .EXAMPLE PS> $WarningLists = Get-MispWarningList -Context $MispContext Return first all Warning Lists .EXAMPLE PS> $WarningLists = Get-MispWarningList -Context $MispContext -Id 1234 Return details for warning list 1234 .EXAMPLE PS> $Criteria = @{ enabled = $true } PS> $WarningLists = Get-MispWarningList -Context $MispContext -Criteria $Criteria Return Enabled Warning Lists .LINK https://github.com/IPSecMSSP/misp.tools https://www.circl.lu/doc/misp/automation/#warninglists-api #> [CmdletBinding(DefaultParameterSetName='ListAll')] param ( [Parameter(Mandatory=$true)] [PsCustomObject]$Context, [Parameter( Mandatory=$false, ParameterSetName='ById' )] [Int]$Id, [Parameter( Mandatory=$false, ParameterSetName='ByCriteria' )] [hashtable]$Criteria ) Begin { $Me = $MyInvocation.MyCommand.Name Write-Verbose "$($Me): Get MISP Warning List(s)" # If we don't "Clone" the UriBuilder object from the Context, the Context's instance of the BaseUri gets updated. We do not want that. $Uri = [System.UriBuilder]$Context.BaseUri.ToString() $Uri.Path = [io.path]::combine($Uri.Path, "warninglists") # Append the Event Id if requested if ($MyInvocation.BoundParameters.ContainsKey("Id")) { $Uri.Path = [io.path]::combine($Uri.Path, "view/$($Id)") } } Process { Write-Verbose "$($Me): Invoking with ParameterSetName: $($PSCmdlet.ParameterSetName)" # Call the API if ($PSCmdlet.ParameterSetName -eq 'ByCriteria') { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri -Method 'POST' -Body ($Criteria | ConvertTo-Json) } else { $Response = Invoke-MispRestMethod -Context $Context -Uri $Uri } if ($MyInvocation.BoundParameters.ContainsKey("Id")) { # Only a single event was requested Write-Output $Response.warningList } else { # Return all fo the events Write-Output $Response.warninglists.warninglist } } End { } } Export-ModuleMember -Function Add-MispAttributeToEvent, Get-MispAttribute, Get-MispAttributeType, New-MispAttribute, Search-MispAttribute, Get-MispEvent, New-MispEvent, Publish-MispEvent, Search-MispEvent, Unpublish-MispEvent, Update-MispEvent, Add-MispGalaxyClusterToObject, Get-MispGalaxy, Get-MispGalaxyCluster, New-MispContext, Read-MispContext, Save-MispContext, Get-MispOrganisation, New-MispOrganisation, Update-MispOrganisation, Add-MispAttributeTag, Add-MispEventTag, Get-MispTag, Get-MispWarningList # SIG # Begin signature block # MIIt6QYJKoZIhvcNAQcCoIIt2jCCLdYCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC1m6k9AnKN9SbC # 9Qkf8bBu6y1lUjzGZIdzVRZwfE+7eaCCEyswggWQMIIDeKADAgECAhAFmxtXno4h # MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV # BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z # ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB # AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z # G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ # anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s # Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL # 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb # BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3 # JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c # AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx # YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0 # viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL # T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud # EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf # Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk # aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS # PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK # 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB # cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp # 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg # dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri # RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7 # 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5 # nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3 # i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H # EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G # CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ # bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0 # IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C # 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce # 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da # E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T # SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA # FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh # D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM # 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z # 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05 # huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY # mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP # /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T # AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD # VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG # A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV # HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU # cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN # BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry # sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL # IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf # Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh # OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh # dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV # 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j # wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH # Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC # XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l # /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW # eE4wggbfMIIEx6ADAgECAhABYmy7Lnkq4ZrwtGPYmCG8MA0GCSqGSIb3DQEBCwUA # MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE # AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz # ODQgMjAyMSBDQTEwHhcNMjQwMTE1MDAwMDAwWhcNMjUwMTE0MjM1OTU5WjBnMQsw # CQYDVQQGEwJBVTERMA8GA1UECBMIVmljdG9yaWExFTATBgNVBAcTDE5vdHRpbmcg # SGlsbDEWMBQGA1UEChMNSVBTZWMgUHR5IEx0ZDEWMBQGA1UEAxMNSVBTZWMgUHR5 # IEx0ZDCCAaIwDQYJKoZIhvcNAQEBBQADggGPADCCAYoCggGBALbFLv+X/DB/f3wO # 2nvk53ZIkPhN7tzZCRKUDqY+u41Eb+QPgM/KhDP7Kl8g7uoapmrpyIPNoydJghST # C0rv23fAeOIVnA1IBFPCx4r/cgcEKKLD4QFxPhLS6pFWCEbHHWB6IXjt5uTM3/5f # c7qLunpnHKB4Nfh0845UzA30sz9mnT6CVpVKVl9owUNJusOXOtZCNrrG7nXXOphi # YtinoVCFQmq8LJO2wPQUTMY91W9IN7lPW4SARDkDidAovZu6BCveL4+K3K7UsIBT # pPopOLt6JXcitfedRsDUxz8v+Gak66dgCu2ie0jKC043FLr8ADZkHLcDKbHJnpQO # OYrOls50NU3ATuNKABUjkrtYy63WdMbDGYeCNbKSESUIkkhzG02dswRnBk0CN65V # +Jsyts6D3Ag6x10qeambGDEH8nlIZY2wdTvqT5UpGg6GvrOwu2cSt8eWYn69LCtB # XJCP6Aackgpbm8LbXHs2EHfg0yMMsdKWHTTfEZYs650U3xQUBwIDAQABo4ICAzCC # Af8wHwYDVR0jBBgwFoAUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHQYDVR0OBBYEFIEj # lb9LKiYKWjk5Yyx4RPBn1Z6rMD4GA1UdIAQ3MDUwMwYGZ4EMAQQBMCkwJwYIKwYB # BQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzAOBgNVHQ8BAf8EBAMC # B4AwEwYDVR0lBAwwCgYIKwYBBQUHAwMwgbUGA1UdHwSBrTCBqjBToFGgT4ZNaHR0 # cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25p # bmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwU6BRoE+GTWh0dHA6Ly9jcmw0LmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI # QTM4NDIwMjFDQTEuY3JsMIGUBggrBgEFBQcBAQSBhzCBhDAkBggrBgEFBQcwAYYY # aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFwGCCsGAQUFBzAChlBodHRwOi8vY2Fj # ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JT # QTQwOTZTSEEzODQyMDIxQ0ExLmNydDAJBgNVHRMEAjAAMA0GCSqGSIb3DQEBCwUA # A4ICAQB6zn4EhgUEk3+8Oa2clKNbDWIOooLawaw7gO3lJp3PNCN/Ov4BrQ5NqnyO # GfEK7/eJ/7xGa6+jk4cexYEhFfzAyIUwo+Hm8f/Ui3sClnLBoX0kUBYVKT/+Npj4 # kc+4VncXdDC6q8NY0mTpRX0CdbZZgCwd04d83YX7YwqVsjkMtPCynFaGGYufG5Yo # V6gyeQy8tnm8maKRlP2yBPIH83gUT2rbfTnUaJ1lpKQP044HWNx75PpwtRK+nq9U # loYir5A6lAuyVcrTWKrpMxs5bXYxCSphqR+LKCbAa7Gg3P4X5/bTIyrPc7Tv1PsG # T8yo8wTzvAaZLpl7ncmCrlaiXusbKoVxVTJ8FKUUMLCAelOKKgkdHeUvd3EIuFqM # cP6sg3cR6wu+hexS7ZOZtkfzguHtyp4A+1a6HhEbTubzY9N9Bom0WNWyUe3Yzd5d # FaqtIkntwju48TIHdvP06Kh2166c7mIX+Krq/TR8AeND2FhhsNO/yTP+aU6CebBF # M94Ds14Q9aNPYD0ml0lPn2nFIcGPZetfnXCxQC/eudLrGd8jGaQjeeKQIvkhiCqL # POXcTQXu2PhwU14tJYehWtYZITLScPzLklMIOuGwO3LqsS/vvVXK5+N7CqxHgzAV # JAjIbgLvBdteW/zoUiJLifJNOdSAVc4DvDdw4elMWpIR7gCzfDGCGhQwghoQAgEB # MH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYD # VQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IENvZGUgU2lnbmluZyBSU0E0MDk2IFNI # QTM4NCAyMDIxIENBMQIQAWJsuy55KuGa8LRj2JghvDANBglghkgBZQMEAgEFAKCB # hDAYBgorBgEEAYI3AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEE # AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJ # BDEiBCBqvVu8WbztPupeBQeKdFN/xaeEzlYTnkRs17Tk3WVHJjANBgkqhkiG9w0B # AQEFAASCAYBXEeyioaX+LI8EpWx5okds5gAuzCeR+0xUI0bXIpuWqnvXz8ssqE92 # N0dXAtqtB45wdY/F7lIYnm67t/pi2zZUKQyifnuSHXf36h0IGkrdguULF0TLWVJ9 # tsEHkfZ7STr0eLdnlZZw/LxTh/MJIBIijNT4ntGF9F2kBnClhG3t24vlhGSLdAc0 # Rju1wKhoc2zHpgW/cB40YKbUHgJzozsnH/vpCsMwITubQ++fDYQKZLJQuVfYLJB6 # QyIhCpTwWX4crQsZr5772JU+IJQPA3g0/Gr8aqXS6mA7r73MmbPUYINfZG76BgiP # l0BbDOGSmtKalubAYc9VxjMsReSZ51CIxb3gdZA1gq2YnuMo+85FKp6nyK3esrSe # Xpk16SU/hWeD2z8FC0dSCA5W1agYl+Xxcf5e6g0tRyjmX93O/Lgir8V2vhTHMvUK # IzD3bujpEx70FCEUfwBLZw7PJnXVgvBpDgptuCfg43dB7jxI5Fn3iRJ60IiW6ko7 # TOh6ydm9GRqhghdhMIIXXQYKKwYBBAGCNwMDATGCF00wghdJBgkqhkiG9w0BBwKg # ghc6MIIXNgIBAzEPMA0GCWCGSAFlAwQCAgUAMIGIBgsqhkiG9w0BCRABBKB5BHcw # dQIBAQYJYIZIAYb9bAcBMEEwDQYJYIZIAWUDBAICBQAEMBIEsHsa9etHh9sFZmrJ # gALBwaHAQrolfsxEDOJPl6wGJNPUmakFb9+v7RkGHOHXNwIRAL++zhCOfwDwsaQx # GUZGHzUYDzIwMjQwODIwMDcwNjM5WqCCEwkwggbCMIIEqqADAgECAhAFRK/zlJ0I # Oaa/2z9f5WEWMA0GCSqGSIb3DQEBCwUAMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQK # Ew5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBS # U0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwHhcNMjMwNzE0MDAwMDAwWhcN # MzQxMDEzMjM1OTU5WjBIMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs # IEluYy4xIDAeBgNVBAMTF0RpZ2lDZXJ0IFRpbWVzdGFtcCAyMDIzMIICIjANBgkq # hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAo1NFhx2DjlusPlSzI+DPn9fl0uddoQ4J # 3C9Io5d6OyqcZ9xiFVjBqZMRp82qsmrdECmKHmJjadNYnDVxvzqX65RQjxwg6sea # Oy+WZuNp52n+W8PWKyAcwZeUtKVQgfLPywemMGjKg0La/H8JJJSkghraarrYO8pd # 3hkYhftF6g1hbJ3+cV7EBpo88MUueQ8bZlLjyNY+X9pD04T10Mf2SC1eRXWWdf7d # EKEbg8G45lKVtUfXeCk5a+B4WZfjRCtK1ZXO7wgX6oJkTf8j48qG7rSkIWRw69Xl # oNpjsy7pBe6q9iT1HbybHLK3X9/w7nZ9MZllR1WdSiQvrCuXvp/k/XtzPjLuUjT7 # 1Lvr1KAsNJvj3m5kGQc3AZEPHLVRzapMZoOIaGK7vEEbeBlt5NkP4FhB+9ixLOFR # r7StFQYU6mIIE9NpHnxkTZ0P387RXoyqq1AVybPKvNfEO2hEo6U7Qv1zfe7dCv95 # NBB+plwKWEwAPoVpdceDZNZ1zY8SdlalJPrXxGshuugfNJgvOuprAbD3+yqG7HtS # OKmYCaFxsmxxrz64b5bV4RAT/mFHCoz+8LbH1cfebCTwv0KCyqBxPZySkwS0aXAn # DU+3tTbRyV8IpHCj7ArxES5k4MsiK8rxKBMhSVF+BmbTO77665E42FEHypS34lCh # 8zrTioPLQHsCAwEAAaOCAYswggGHMA4GA1UdDwEB/wQEAwIHgDAMBgNVHRMBAf8E # AjAAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMCAGA1UdIAQZMBcwCAYGZ4EMAQQC # MAsGCWCGSAGG/WwHATAfBgNVHSMEGDAWgBS6FtltTYUvcyl2mi91jGogj57IbzAd # BgNVHQ4EFgQUpbbvE+fvzdBkodVWqWUxo97V40kwWgYDVR0fBFMwUTBPoE2gS4ZJ # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNBNDA5 # NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNybDCBkAYIKwYBBQUHAQEEgYMwgYAwJAYI # KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBYBggrBgEFBQcwAoZM # aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0UlNB # NDA5NlNIQTI1NlRpbWVTdGFtcGluZ0NBLmNydDANBgkqhkiG9w0BAQsFAAOCAgEA # gRrW3qCptZgXvHCNT4o8aJzYJf/LLOTN6l0ikuyMIgKpuM+AqNnn48XtJoKKcS8Y # 3U623mzX4WCcK+3tPUiOuGu6fF29wmE3aEl3o+uQqhLXJ4Xzjh6S2sJAOJ9dyKAu # JXglnSoFeoQpmLZXeY/bJlYrsPOnvTcM2Jh2T1a5UsK2nTipgedtQVyMadG5K8TG # e8+c+njikxp2oml101DkRBK+IA2eqUTQ+OVJdwhaIcW0z5iVGlS6ubzBaRm6zxby # gzc0brBBJt3eWpdPM43UjXd9dUWhpVgmagNF3tlQtVCMr1a9TMXhRsUo063nQwBw # 3syYnhmJA+rUkTfvTVLzyWAhxFZH7doRS4wyw4jmWOK22z75X7BC1o/jF5HRqsBV # 44a/rCcsQdCaM0qoNtS5cpZ+l3k4SF/Kwtw9Mt911jZnWon49qfH5U81PAC9vpwq # bHkB3NpE5jreODsHXjlY9HxzMVWggBHLFAx+rrz+pOt5Zapo1iLKO+uagjVXKBbL # afIymrLS2Dq4sUaGa7oX/cR3bBVsrquvczroSUa31X/MtjjA2Owc9bahuEMs305M # fR5ocMB3CtQC4Fxguyj/OOVSWtasFyIjTvTs0xf7UGv/B3cfcZdEQcm4RtNsMnxY # L2dHZeUbc7aZ+WssBkbvQR7w8F/g29mtkIBEr4AQQYowggauMIIElqADAgECAhAH # Nje3JFR82Ees/ShmKl5bMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUw # EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x # ITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAw # MDBaFw0zNzAzMjIyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdp # Q2VydCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2 # IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw # ggIKAoICAQDGhjUGSbPBPXJJUVXHJQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGh # RBVCX6SI82j6ffOciQt/nR+eDzMfUBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISK # Ihjf69o9xBd/qxkrPkLcZ47qUT3w1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdG # AHvbREGJ3HxqV3rwN3mfXazL6IRktFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9 # zvU5EmfvDqVjbOSmxR3NNg1c1eYbqMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKl # SNbwsDETqVcplicu9Yemj052FVUmcJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae # 5jtb7IHeIhTZgirHkr+g3uM+onP65x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnz # yqqWc0Jon7ZGs506o9UD4L/wojzKQtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/ # BZxmSVJQ9FHzNklNiyDSLFc1eSuo80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7T # A4j+s4/TXkt2ElGTyYwMO1uKIqjBJgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbs # q11GdeJgo1gJASgADoRU7s7pXcheMBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IB # XTCCAVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuhbZbU2FL3Mpdpov # dYxqII+eyG8wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0P # AQH/BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAk # BggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAC # hjVodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v # dEc0LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5j # b20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEE # AjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m # 1tghQuGwGC4QTRPPMFPOvxj7x1Bd4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dt # h/qEICU0MWfNthKWb8RQTGIdDAiCqBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+K # LHqrhc1DX+1gtqpPkWaeLJ7giqzl/Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd # 6f8oVInw1YpxdmXazPByoyP6wCeCRK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ # 38SNoOeY+/umnXKvxMfBwWpx2cYTgAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+ # k1OsOx0ISQ+UzTl63f8lY5knLD0/a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3l # NHGS1yZr5Dhzq6YBT70/O3itTK37xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGY # X/sr2H7yRp11LB4nLCbbbxV7HhmLNriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFm # ut1VwDophrCYoCvtlUG3OtUVmDG0YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADN # XcL50CN/AAvkdgIm2fBldkKmKYcJRyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kv # RBVK5xMOHds3OBqhK/bt1nz8MIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAY # WjANBgkqhkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNl # cnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdp # Q2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5 # MjM1OTU5WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkw # FwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVz # dGVkIFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBz # aN675F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbr # VsaXbR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTR # EEQQLt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJ # z82sNEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyO # j4DatpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6R # AXwhTNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k # 98FpiHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJ # tppEGSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUa # dmJ+9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZB # dd56rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVf # nSD8oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0T # AQH/BAUwAwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0j # BBgwFoAUReuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsG # AQUFBwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29t # MEMGCCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNl # cnRBc3N1cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9j # cmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYD # VR0gBAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3Qb # PbYW1/e/Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5 # +KH38nLeJLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+n # BgMTdydE1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc # /RzY9HdaXFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVr # zyerbHbObyMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o # 4rmUMYIDhjCCA4ICAQEwdzBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl # cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBT # SEEyNTYgVGltZVN0YW1waW5nIENBAhAFRK/zlJ0IOaa/2z9f5WEWMA0GCWCGSAFl # AwQCAgUAoIHhMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0B # CQUxDxcNMjQwODIwMDcwNjM5WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRm8Csy # wsLJD4JdzqqKycZPGZzPQDA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCDS9uRt7XQi # zNHUQFdoQTZvgoraVZquMxavTRqa1Ax4KDA/BgkqhkiG9w0BCQQxMgQwOK9ncSs2 # BCIs9Wa6pDwBNP74gqGZmiF6u6i6XHRol5RU1y017PBwBveZfUbrIp42MA0GCSqG # SIb3DQEBAQUABIICAJJzJ216gcLCPVWCvOBUAvVH/FJq3t6qUyeklEWZbkf4IX+J # vzj0rFo0WjLB6tmB4S9uoVMvM9YSCdAyrgu5mQwqiju6QivIbXj+P+P8vwCrsR/6 # j1hKhzA8cX7GO7lu1fBLyCandKXHZTjkzrvJbHdttllYUYOMIQth+A7PW8wtwodk # yaa/wqWlL8QTiJjhhLnxgBYJXRmBkAzNasPK8/BN+jHAx1comfeUPIhnGm5XVfWw # QeEVo0rGV5VMHBqXBjCJVN789tJf+fHSyIqfo0pw0c7N5B9o0zNLr9L1+93oUFkk # XHn44MG9VPXFRARWYNY51IQ06cPy4aEA5Ff9s3BjFOkRo/+IdPHD5J9/sTPUJK9U # yOo5VWPL1M94uzLLkpzUGu5LezdeX3pzvo0vE9847at2NhQzbv04A0Ga5DOBBEPn # 82R/3qE0kWcewe19v5gl3a8/xcvR7xbK/L7JiGjL5KDuzePHTgZiiFbzUu5JeFNp # SMwBWP0VNMxeyzsiYZ/xdamEWN+FJ3IPLZsF5ia9hgniTBNU2DAmmR4o3hOqltNr # 9XMwsN0qvbjH1j31ZWVVC3mIdWegIlaDzqLPneCF+Rnv3T0FxFYME+Q3vtmdf/PY # 2BcAdL9noO5ht7Ey5k+ZwH1Y/GLATTDwPdtP1RMl1Q8mL1zKIz099tRH7UU4 # SIG # End signature block |