Public/Test/Test-MailHeaderAuth.ps1
|
function Test-MailHeaderAuth { <# .SYNOPSIS Analyzes email headers to determine authentication status and phishing risk. .DESCRIPTION Tests mail headers for SPF, DKIM, DMARC, and ARC authentication results. Provides verdict on message legitimacy based on authentication alignment. .PARAMETER HeadersText Raw email headers as a string. .PARAMETER Path Path to a file containing email headers. .PARAMETER FromClipboard Read headers from clipboard (Windows only). .PARAMETER Format Output format: 'Summary' (default), 'Markdown', 'Object', or 'Json'. .PARAMETER AsObject Return PowerShell object instead of formatted output. .OUTPUTS PSCustomObject or formatted string depending on Format parameter. #> [CmdletBinding()] param( [Parameter(ParameterSetName = 'Text', Mandatory)][string]$HeadersText, [Parameter(ParameterSetName = 'File', Mandatory)][string]$Path, [Parameter(ParameterSetName = 'Clipboard')][switch]$FromClipboard, [ValidateSet('Summary', 'Markdown', 'Object', 'Json')] [string]$Format = 'Summary', [switch]$AsObject ) $ErrorActionPreference = 'Stop' Initialize-TechToolboxRuntime Write-Log -Level Info -Message "Starting header analysis..." try { if ($PSCmdlet.ParameterSetName -eq 'File') { if (-not (Test-Path $Path)) { throw "File not found: $Path" } $HeadersText = Get-Content -LiteralPath $Path -Raw Write-Log -Level Warn -Message "Loaded headers from file: $Path" } elseif ($PSCmdlet.ParameterSetName -eq 'Clipboard') { if ($IsWindows) { $HeadersText = Get-Clipboard -Raw if (-not $HeadersText) { throw "Clipboard is empty." } Write-Log -Level Warn -Message "Loaded headers from clipboard." } else { throw "-FromClipboard is only supported on Windows." } } $block = Get-HeaderBlock -HeadersText $HeadersText $lines = $block.Lines # Extract key headers $kv = @{ From = ($lines | Where-Object { $_ -match '^(?i)From:' } | Select-Object -First 1) Sender = ($lines | Where-Object { $_ -match '^(?i)Sender:' } | Select-Object -First 1) ReturnPath = ($lines | Where-Object { $_ -match '^(?i)Return-Path:' } | Select-Object -First 1) Subject = ($lines | Where-Object { $_ -match '^(?i)Subject:' } | Select-Object -First 1) MessageID = ($lines | Where-Object { $_ -match '^(?i)Message-Id:' } | Select-Object -First 1) } $fromAddr = $kv.From -replace '^(?i)From:\s*', '' $senderAddr = $kv.Sender -replace '^(?i)Sender:\s*', '' $returnPath = $kv.ReturnPath -replace '^(?i)Return-Path:\s*', '' $fromDomain = if ($fromAddr) { Get-Domain $fromAddr } $senderDomain = if ($senderAddr) { Get-Domain $senderAddr } $rpDomain = if ($returnPath) { Get-Domain $returnPath } # Collect Authentication-Results variants $ar_edge = $lines | Where-Object { $_ -match '^(?i)Authentication-Results:' } $ar_origin1 = $lines | Where-Object { $_ -match '^(?i)Authentication-Results-Original:' } $ar_origin2 = $lines | Where-Object { $_ -match '^(?i)X-Original-Authentication-Results:' } $edgeAuth = if ($ar_edge) { Format-AuthResults -Lines $ar_edge -Label 'edge' } else { $null } $originAuth = if ($ar_origin1 -or $ar_origin2) { Format-AuthResults -Lines ($ar_origin1 + $ar_origin2) -Label 'origin' } else { $null } # Received chain $receivedLines = $lines | Where-Object { $_ -match '^(?i)Received:' } $firstPublicIP = if ($receivedLines) { Get-FirstPublicIP -ReceivedLines $receivedLines } else { $null } # Also look for helper hints $xLastPublicIp = ($lines | Where-Object { $_ -match '^(?i)X-Fe-Last-Public-Client-Ip:' } | Select-Object -First 1) -replace '^(?i)X-Fe-Last-Public-Client-Ip:\s*', '' if (-not $firstPublicIP -and $xLastPublicIp) { $firstPublicIP = $xLastPublicIp } # Find envelope MailFrom (smtp.mailfrom) $mailFrom = $null if ($edgeAuth -and $edgeAuth.SPF_MailFrom) { $mailFrom = $edgeAuth.SPF_MailFrom } elseif ($originAuth -and $originAuth.SPF_MailFrom) { $mailFrom = $originAuth.SPF_MailFrom } elseif ($returnPath) { $mailFrom = $returnPath } $mailFromDomain = if ($mailFrom) { Get-Domain $mailFrom } else { $null } # Determine alignment & verdict $signals = [ordered]@{ FromDomain = $fromDomain MailFromDomain = $mailFromDomain DKIM_Domains = @() # always an array SPF_Status_Edge = $edgeAuth.SPF SPF_Status_Orig = $originAuth.SPF DKIM_Status_Edge = $edgeAuth.DKIM DKIM_Status_Orig = $originAuth.DKIM DMARC_Status_Edge = $edgeAuth.DMARC DMARC_Status_Orig = $originAuth.DMARC ARC_Status_Edge = $edgeAuth.ARC ARC_Status_Orig = $originAuth.ARC CompAuth = $edgeAuth.CompAuth } # Collect $dkimCollected = @() if ($edgeAuth -and ($edgeAuth.PSObject.Properties.Name -contains 'DKIM_Domains') -and $edgeAuth.DKIM_Domains) { $dkimCollected += @($edgeAuth.DKIM_Domains) } if ($originAuth -and ($originAuth.PSObject.Properties.Name -contains 'DKIM_Domains') -and $originAuth.DKIM_Domains) { $dkimCollected += @($originAuth.DKIM_Domains) } # De-dup & normalize $dkimDomains = @($dkimCollected | Where-Object { $_ } | Select-Object -Unique) # Prepare DKIM display vars from the finalized list (no $obj yet) $dkimList = @($dkimDomains) # already unique & normalized $dkimCount = $dkimList.Count # (Optional) compute alignment now that we have final lists $dkimAligned = $false foreach ($d in $dkimDomains) { if ($fromDomain -and $d) { if ($d -eq $fromDomain -or $d -like "*.$fromDomain" -or $fromDomain -like "*.$d") { $dkimAligned = $true; break } } } $spfAligned = $false if ($fromDomain -and $mailFromDomain) { if ($mailFromDomain -eq $fromDomain -or $mailFromDomain -like "*.$fromDomain" -or $fromDomain -like "*.$mailFromDomain") { $spfAligned = $true } } # Build reasons list (for human-readable explanations in summary/markdown, and also include in object for structured output) $reasons = New-Object System.Collections.Generic.List[string] # Determine best-available statuses (prefer edge if present; else origin) $spf = $signals.SPF_Status_Edge ?? $signals.SPF_Status_Orig $dkim = $signals.DKIM_Status_Edge ?? $signals.DKIM_Status_Orig $dmarc = $signals.DMARC_Status_Edge ?? $signals.DMARC_Status_Orig $arc = $signals.ARC_Status_Edge ?? $signals.ARC_Status_Orig if ($dmarc -eq 'pass') { $verdict = 'Low chance of phishing, etc. - Likely Legitimate' $reasons.Add('DMARC passed (aligned).') } elseif ($dkim -eq 'pass' -and $dkimAligned) { $verdict = 'Low chance of phishing, etc. - Likely Legitimate' $reasons.Add('DKIM passed and aligned with From domain.') } elseif ($spf -eq 'pass' -and $spfAligned) { $verdict = 'Low chance of phishing, etc. - Likely Legitimate' $reasons.Add('SPF passed and aligned with From domain.') } elseif ($dkim -eq 'pass' -and -not $dkimAligned) { $verdict = 'Medium - Possibly Legitimate - Check DKIM domains.' $reasons.Add('DKIM passed but appears misaligned with From domain (possible mailing list/forwarder).') } elseif ($arc -eq 'pass' -and $dmarc -ne 'pass') { $verdict = 'Medium - Possibly Legitimate - Check ARC/DMARC.' $reasons.Add('ARC passed but DMARC did not; forwarded/mediated message likely.') } else { # If we get here, likely spoof or badly configured sender $verdict = 'High - Likely Malicious - Failing DMARC/SPF/DKIM is a strong signal of spoofing/phishing.' if ($dmarc -eq 'fail') { $reasons.Add('DMARC failed.') } if ($dkim -in @('fail', 'none', $null)) { $reasons.Add('DKIM did not validate.') } if ($spf -in @('fail', 'softfail', 'none', $null)) { $reasons.Add('SPF did not validate.') } if (-not $fromDomain) { $reasons.Add('Unable to parse From domain.') } } # Build object $obj = [pscustomobject]@{ Verdict = $verdict Confidence = if ($verdict -like 'Low*') { 'High' } elseif ($verdict -like 'Medium*') { 'Medium' } else { 'Variable' } Reasons = $reasons # always a list/array VisibleFrom = $fromAddr Sender = $senderAddr ReturnPath = $returnPath Domains = [pscustomobject]@{ From = $fromDomain MailFrom = $mailFromDomain DKIM = $dkimDomains # normalized array } AuthSummary = [pscustomobject]@{ Edge = $edgeAuth Origin = $originAuth Best = [pscustomobject]@{ SPF = ($signals.SPF_Status_Edge ?? $signals.SPF_Status_Orig) DKIM = ($signals.DKIM_Status_Edge ?? $signals.DKIM_Status_Orig) DMARC = ($signals.DMARC_Status_Edge ?? $signals.DMARC_Status_Orig) ARC = ($signals.ARC_Status_Edge ?? $signals.ARC_Status_Orig) } Alignment = [pscustomobject]@{ DKIM_Aligned = $dkimAligned SPF_Aligned = $spfAligned } CompAuth = $signals.CompAuth } Path = [pscustomobject]@{ FirstPublicIP = $firstPublicIP ReceivedHops = $receivedLines } KeyHeaders = [pscustomobject]@{ Subject = ($kv.Subject -replace '^(?i)Subject:\s*', '') MessageId = ($kv.MessageID -replace '^(?i)Message-Id:\s*', '') } } if ($Format -eq 'Object' -or $AsObject) { return $obj } elseif ($Format -eq 'Json') { return ($obj | ConvertTo-Json -Depth 6) } elseif ($Format -eq 'Markdown') { $md = @() $md += "# Mail Header Authentication Analysis" $md += "" $md += "**Verdict:** $($obj.Verdict) " $md += "**Confidence:** $($obj.Confidence)" $md += "" if ($obj.Reasons -and $obj.Reasons.Count) { $md += "### Reasons" foreach ($r in $obj.Reasons) { $md += "- $r" } $md += "" } $md += "### Sender" $md += "- From: ``$($obj.VisibleFrom)``" if ($obj.Sender) { $md += "- Sender: ``$($obj.Sender)``" } if ($obj.ReturnPath) { $md += "- Return-Path: ``$($obj.ReturnPath)``" } $md += "" $md += "### Domains" $md += "- header.from: **$($obj.Domains.From)**" $md += "- smtp.mailfrom: **$($obj.Domains.MailFrom)**" $md += "- DKIM d=: **$([string]::Join(', ',$obj.Domains.DKIM))**" $md += "" $md += "### Authentication" $md += "- SPF: **$($obj.AuthSummary.Best.SPF)** (aligned: $($obj.AuthSummary.Alignment.SPF_Aligned))" $md += "- DKIM: **$($obj.AuthSummary.Best.DKIM)** (aligned: $($obj.AuthSummary.Alignment.DKIM_Aligned))" $md += "- DMARC: **$($obj.AuthSummary.Best.DMARC)**" if ($obj.AuthSummary.Best.ARC) { $md += "- ARC: **$($obj.AuthSummary.Best.ARC)**" } if ($obj.AuthSummary.CompAuth) { $md += "- CompAuth: **$($obj.AuthSummary.CompAuth)**" } $md += "" $md += "### Path" if ($obj.Path.FirstPublicIP) { $md += "- First public IP: **$($obj.Path.FirstPublicIP)**" } # Use a fenced code block for the Received chain # Created reusable helper for future use: Add-FencedBlock -Lines $obj.Path.ReceivedHops -Language 'txt' $md += "<details><summary>Received chain</summary>" $md += "" $md += Add-FencedBlock -Lines $obj.Path.ReceivedHops # or -Language 'txt' $md += "</details>" $md += "" $md += "### Key Headers" if ($obj.KeyHeaders.Subject) { $md += "- Subject: $($obj.KeyHeaders.Subject)" } if ($obj.KeyHeaders.MessageId) { $md += "- Message-Id: $($obj.KeyHeaders.MessageId)" } $md += "" return ($md -join "`n") } else { # Summary (colorized handled inside Write-Log) Write-Log -level Info -Message "" Write-Log -level Info -Message "" Write-Log -Level OK -Message "Mail Header Authentication Analysis" Write-Log -Level OK -Message "-----------------------------------" Write-Log -level Info -Message "" Write-Log -Level Warn -Message ("Verdict: {0}" -f $obj.Verdict) Write-Log -Level Info -Message ("Confidence: {0}" -f $obj.Confidence) if ($obj.Reasons.Count) { Write-Log -Level OK -Message "Reasons:" $obj.Reasons | ForEach-Object { Write-Log -Level Info -Message (" - {0}" -f $_) } } Write-Log -Level Info -Message "" Write-Log -Level OK -Message "Sender" Write-Log -Level Info -Message (" From: {0}" -f $obj.VisibleFrom) if ($obj.Sender) { Write-Log -Level Info -Message (" Sender: {0}" -f $obj.Sender) } if ($obj.ReturnPath) { Write-Log -Level Info -Message (" Return-Path: {0}" -f $obj.ReturnPath) } Write-Log -Level Info -Message "" Write-Log -Level OK -Message "Domains" Write-Log -Level Info -Message (" header.from: {0}" -f $obj.Domains.From) Write-Log -Level Info -Message (" smtp.mailfrom: {0}" -f $obj.Domains.MailFrom) # DKIM domains line Write-Log -Level Info -Message (" DKIM domains count: {0}" -f $dkimCount) if ($dkimCount -gt 0) { Write-Log -Level Info -Message (" dkim d=: {0}" -f ($dkimList -join ', ')) } Write-Log -Level Info -Message "" Write-Log -Level OK -Message "Authentication" Write-Log -Level Info -Message (" SPF: {0} (aligned: {1})" -f $obj.AuthSummary.Best.SPF, $obj.AuthSummary.Alignment.SPF_Aligned) Write-Log -Level Info -Message (" DKIM: {0} (aligned: {1})" -f $obj.AuthSummary.Best.DKIM, $obj.AuthSummary.Alignment.DKIM_Aligned) Write-Log -Level Info -Message (" DMARC: {0}" -f $obj.AuthSummary.Best.DMARC) if ($obj.AuthSummary.Best.ARC) { Write-Log -Level Info -Message (" ARC: {0}" -f $obj.AuthSummary.Best.ARC) } if ($obj.AuthSummary.CompAuth) { Write-Log -Level Info -Message (" CompAuth: {0}" -f $obj.AuthSummary.CompAuth) } Write-Log -Level Info -Message "" Write-Log -Level OK -Message "Path" if ($obj.Path.FirstPublicIP) { Write-Log -Level Info -Message (" First public IP: {0}" -f $obj.Path.FirstPublicIP) } Write-Log -Level Info -Message (" Received hops: {0}" -f $obj.Path.ReceivedHops.Count) return $obj | Out-Null } } catch { Write-Log -Level Error -Message ("Error analyzing mail headers: {0}" -f $_.Exception.Message) return $null } } # SIG # Begin signature block # MIIfAgYJKoZIhvcNAQcCoIIe8zCCHu8CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAP26rKobTg7rBz # UKOrlEXXBTP6seigo4IFanSrL/IeAqCCGEowggUMMIIC9KADAgECAhAR+U4xG7FH # qkyqS9NIt7l5MA0GCSqGSIb3DQEBCwUAMB4xHDAaBgNVBAMME1ZBRFRFSyBDb2Rl # IFNpZ25pbmcwHhcNMjUxMjE5MTk1NDIxWhcNMjYxMjE5MjAwNDIxWjAeMRwwGgYD # VQQDDBNWQURURUsgQ29kZSBTaWduaW5nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8A # MIICCgKCAgEA3pzzZIUEY92GDldMWuzvbLeivHOuMupgpwbezoG5v90KeuN03S5d # nM/eom/PcIz08+fGZF04ueuCS6b48q1qFnylwg/C/TkcVRo0WFcKoFGT8yGxdfXi # caHtapZfbSRh73r7qR7w0CioVveNBVgfMsTgE0WKcuwxemvIe/ptmkfzwAiw/IAC # Ib0E0BjiX4PySbwWy/QKy/qMXYY19xpRItVTKNBtXzADUtzPzUcFqJU83vM2gZFs # Or0MhPvM7xEVkOWZFBAWAubbMCJ3rmwyVv9keVDJChhCeLSz2XR11VGDOEA2OO90 # Y30WfY9aOI2sCfQcKMeJ9ypkHl0xORdhUwZ3Wz48d3yJDXGkduPm2vl05RvnA4T6 # 29HVZTmMdvP2475/8nLxCte9IB7TobAOGl6P1NuwplAMKM8qyZh62Br23vcx1fXZ # TJlKCxBFx1nTa6VlIJk+UbM4ZPm954peB/fIqEacm8LkZ0cPwmLE5ckW7hfK4Trs # o+RaudU1sKeA+FvpOWgsPccVRWcEYyGkwbyTB3xrIBXA+YckbANZ0XL7fv7x29hn # gXbZipGu3DnTISiFB43V4MhNDKZYfbWdxze0SwLe8KzIaKnwlwRgvXDMwXgk99Mi # EbYa3DvA/5ZWikLW9PxBFD7Vdr8ZiG/tRC9I2Y6fnb+PVoZKc/2xsW0CAwEAAaNG # MEQwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMB0GA1UdDgQW # BBRfYLVE8caSc990rnrIHUjoB7X/KjANBgkqhkiG9w0BAQsFAAOCAgEAiGB2Wmk3 # QBtd1LcynmxHzmu+X4Y5DIpMMNC2ahsqZtPUVcGqmb5IFbVuAdQphL6PSrDjaAR8 # 1S8uTfUnMa119LmIb7di7TlH2F5K3530h5x8JMj5EErl0xmZyJtSg7BTiBA/UrMz # 6WCf8wWIG2/4NbV6aAyFwIojfAcKoO8ng44Dal/oLGzLO3FDE5AWhcda/FbqVjSJ # 1zMfiW8odd4LgbmoyEI024KkwOkkPyJQ2Ugn6HMqlFLazAmBBpyS7wxdaAGrl18n # 6bS7QuAwCd9hitdMMitG8YyWL6tKeRSbuTP5E+ASbu0Ga8/fxRO5ZSQhO6/5ro1j # PGe1/Kr49Uyuf9VSCZdNIZAyjjeVAoxmV0IfxQLKz6VOG0kGDYkFGskvllIpQbQg # WLuPLJxoskJsoJllk7MjZJwrpr08+3FQnLkRuisjDOc3l4VxFUsUe4fnJhMUONXT # Sk7vdspgxirNbLmXU4yYWdsizz3nMUR0zebUW29A+HYme16hzrMPOeyoQjy4I5XX # 3wXAFdworfPEr/ozDFrdXKgbLwZopymKbBwv6wtT7+1zVhJXr+jGVQ1TWr6R+8ea # tIOFnY7HqGaxe5XB7HzOwJKdj+bpHAfXft1vUoiKr16VajLigcYCG8MdwC3sngO3 # JDyv2V+YMfsYBmItMGBwvizlQ6557NbK95EwggWNMIIEdaADAgECAhAOmxiO+dAt # 5+/bUOIIQBhaMA0GCSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQK # EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNV # BAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBa # Fw0zMTExMDkyMzU5NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2Vy # dCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lD # ZXJ0IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC # ggIBAL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3E # MB/zG6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKy # unWZanMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsF # xl7sWxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU1 # 5zHL2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJB # MtfbBHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObUR # WBf3JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6 # nj3cAORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxB # YKqxYxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5S # UUd0viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+x # q4aLT8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIB # NjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwP # TzAfBgNVHSMEGDAWgBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMC # AYYweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp # Y2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0 # aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENB # LmNybDARBgNVHSAECjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0Nc # Vec4X6CjdBs9thbX979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnov # Lbc47/T/gLn4offyct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65Zy # oUi0mcudT6cGAxN3J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFW # juyk1T3osdz9HNj0d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPF # mCLBsln1VWvPJ6tsds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9z # twGpn1eqXijiuZQwgga0MIIEnKADAgECAhANx6xXBf8hmS5AQyIMOkmGMA0GCSqG # SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx # GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy # dXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAxMTQyMzU5NTlaMGkx # CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4 # RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYg # MjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC0eDHTCphB # cr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcybEKq+RuwOnPhof6p # vF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzTqpyFQ/4Bt0mAxAHe # HYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftBdsOECS1UkxBvMgEd # gkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3mmdglTcaarps0wjU # jsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6zMUjZa/zbCclF83bR # VFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS5xLrfxnGpTXiUOeS # LsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBBBnGy3tW/AMOMCZIV # NSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqLXvJWnY0v5ydPpOjL # 6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7psNOdgJMoiwOrUG2Zd # SoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeEWvjJSjNm2qA+sdFU # eEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCCAVkwEgYDVR0TAQH/ # BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv1Ensy04wHwYDVR0j # BBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1Ud # JQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0 # cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0 # cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8E # PDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz # dGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEw # DQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvItTSmf83Qh8WIGjB/ # T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/mS83afsl3YTj+IQhQ # E7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgXf9r5nWMQwr8Myb9r # EVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liyrukZ2iA/wdG2th9y # 1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+Cdmu4y81hjajV/gx # dEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2ykgcGV00TYr2Lr3t # y9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipDoq7+qNNjqFzeGxcy # tL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6AxnJll38F0cuJG7uEB # YTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAlZ66RzIg9sC+NJpud # /v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1MIDpMPx0LckTetiS # uEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZsq8WhbaM2tszWkPZP # ubdcMIIG7TCCBNWgAwIBAgIQCoDvGEuN8QWC0cR2p5V0aDANBgkqhkiG9w0BAQsF # ADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xQTA/BgNV # BAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJTQTQwOTYgU0hB # MjU2IDIwMjUgQ0ExMB4XDTI1MDYwNDAwMDAwMFoXDTM2MDkwMzIzNTk1OVowYzEL # MAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMTswOQYDVQQDEzJE # aWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3RhbXAgUmVzcG9uZGVyIDIwMjUg # MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANBGrC0Sxp7Q6q5gVrMr # V7pvUf+GcAoB38o3zBlCMGMyqJnfFNZx+wvA69HFTBdwbHwBSOeLpvPnZ8ZN+vo8 # dE2/pPvOx/Vj8TchTySA2R4QKpVD7dvNZh6wW2R6kSu9RJt/4QhguSssp3qome7M # rxVyfQO9sMx6ZAWjFDYOzDi8SOhPUWlLnh00Cll8pjrUcCV3K3E0zz09ldQ//nBZ # ZREr4h/GI6Dxb2UoyrN0ijtUDVHRXdmncOOMA3CoB/iUSROUINDT98oksouTMYFO # nHoRh6+86Ltc5zjPKHW5KqCvpSduSwhwUmotuQhcg9tw2YD3w6ySSSu+3qU8DD+n # igNJFmt6LAHvH3KSuNLoZLc1Hf2JNMVL4Q1OpbybpMe46YceNA0LfNsnqcnpJeIt # K/DhKbPxTTuGoX7wJNdoRORVbPR1VVnDuSeHVZlc4seAO+6d2sC26/PQPdP51ho1 # zBp+xUIZkpSFA8vWdoUoHLWnqWU3dCCyFG1roSrgHjSHlq8xymLnjCbSLZ49kPmk # 8iyyizNDIXj//cOgrY7rlRyTlaCCfw7aSUROwnu7zER6EaJ+AliL7ojTdS5PWPsW # eupWs7NpChUk555K096V1hE0yZIXe+giAwW00aHzrDchIc2bQhpp0IoKRR7YufAk # prxMiXAJQ1XCmnCfgPf8+3mnAgMBAAGjggGVMIIBkTAMBgNVHRMBAf8EAjAAMB0G # A1UdDgQWBBTkO/zyMe39/dfzkXFjGVBDz2GM6DAfBgNVHSMEGDAWgBTvb1NK6eQG # fHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYB # BQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsGAQUFBzABhhhodHRwOi8vb2Nz # cC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0dHA6Ly9jYWNlcnRzLmRpZ2lj # ZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEy # NTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQhk5odHRwOi8vY3JsMy5kaWdp # Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1lU3RhbXBpbmdSU0E0MDk2U0hB # MjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZIAYb9bAcB # MA0GCSqGSIb3DQEBCwUAA4ICAQBlKq3xHCcEua5gQezRCESeY0ByIfjk9iJP2zWL # pQq1b4URGnwWBdEZD9gBq9fNaNmFj6Eh8/YmRDfxT7C0k8FUFqNh+tshgb4O6Lgj # g8K8elC4+oWCqnU/ML9lFfim8/9yJmZSe2F8AQ/UdKFOtj7YMTmqPO9mzskgiC3Q # YIUP2S3HQvHG1FDu+WUqW4daIqToXFE/JQ/EABgfZXLWU0ziTN6R3ygQBHMUBaB5 # bdrPbF6MRYs03h4obEMnxYOX8VBRKe1uNnzQVTeLni2nHkX/QqvXnNb+YkDFkxUG # tMTaiLR9wjxUxu2hECZpqyU1d0IbX6Wq8/gVutDojBIFeRlqAcuEVT0cKsb+zJNE # suEB7O7/cuvTQasnM9AWcIQfVjnzrvwiCZ85EE8LUkqRhoS3Y50OHgaY7T/lwd6U # Arb+BOVAkg2oOvol/DJgddJ35XTxfUlQ+8Hggt8l2Yv7roancJIFcbojBcxlRcGG # 0LIhp6GvReQGgMgYxQbV1S3CrWqZzBt1R9xJgKf47CdxVRd/ndUlQ05oxYy2zRWV # FjF7mcr4C34Mj3ocCVccAvlKV9jEnstrniLvUxxVZE/rptb7IRE2lskKPIJgbaP5 # t2nGj/ULLi49xTcBZU8atufk+EMF/cWuiC7POGT75qaL6vdCvHlshtjdNXOCIUjs # arfNZzGCBg4wggYKAgEBMDIwHjEcMBoGA1UEAwwTVkFEVEVLIENvZGUgU2lnbmlu # ZwIQEflOMRuxR6pMqkvTSLe5eTANBglghkgBZQMEAgEFAKCBhDAYBgorBgEEAYI3 # AgEMMQowCKACgAChAoAAMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisG # AQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCCskJHs1zdx # RMcjCjFr+yThSu1t89hg8c1cudDPoWMk8DANBgkqhkiG9w0BAQEFAASCAgCK/qfa # sncxannbLXeF8e3CsYllOxfN2Uhea71euwKmJGnLkldnNh8FSyP7a7Ogm7V8gsky # HzOSytL1S80pd3GnRb11Av7YhRCG3xAuPFdAyqwJ1W+QGoxCNMJsB/KFiKpw8qCE # /Hv3cGe+tc9UDMZaIcWsYppzO282ZsXaLnd+/zj3CjIwTWPsLteYaTViLTkkMaok # r6an7GJ43f9H3q36bi4um1ymRRMsxT7eyFrKBo7P2qZFNS4mWGkA20oFa6HF8tSc # 4DBu0/B3PyIk+VBbChl0F38xEvCTYIPAraqE5+Y9TikYQVDhoK5LeblMdGIqlohH # ELOMoIVLxLp6qKRnpWPaEsNByO71hSDY4871r+r5X0G/egTJBoQjN7sRUjrf8M14 # 0G1cm5ra0nBUuPs1IkqjRLKp83PolPvDWjzmZWTHesIcOEHIL7ABtIpkt/dbAYdz # 03Qh9MiC5/34AITfqT7U/JPZ1qMyqoSOzAReDw23MgX6niLvLCXSkChrdejt3rpz # x5li68lsQ1eSuVQ+LSVpfdkG7rSdDiXo8manSAUTXiPcjJAEKhwOxRzUbcB/SwII # NkHa2oEi7P/G+JzplE+ydA+p2OUUXMoNUPCEbo/vYzaLWmNJY07D7D4BA3ce5F3U # u7KR5PjI3q3hcoLWPirOeVxs50hPXziMbjV+76GCAyYwggMiBgkqhkiG9w0BCQYx # ggMTMIIDDwIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg # SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg # UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAqA7xhLjfEFgtHEdqeVdGgwDQYJYIZI # AWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJ # BTEPFw0yNjAyMTIyMjQyNDRaMC8GCSqGSIb3DQEJBDEiBCAObI6sPsMh0tbLDcs4 # GCOJJW3qCH+InUe9BjfhQ0nA5DANBgkqhkiG9w0BAQEFAASCAgCJceeIPMReCcHO # 1nrr+lB7auWzfZj5RKGOpDFOZdqQa54jedmddkYblxWd1d7KueTQJNEdGeHr38uq # yytoUBVsMZuOB9yH5Ks8DciijWFuGXKIjsT3HD4VznoHVCTPqAWCE2hPS/kXUhyL # W9J5n//a8zINrfv4XlYJlQY9UBlNNGwmIb1i5yXDjgQL2WjR7InmSisiivFNyEub # UZzShu7j7qWE/FqanmncufqLTx1gKLxTZtSup3ERXTnZh4m/cYaVr5CBZb9Rptgk # 5bVsPv9H9JCYRfhrkB9spfcb5qEf+JNiyRInZ6ULHtlVkNwsO/pZsA0qDouI+Oae # ooOd/dM29Y78rRzPE2cIqyc9VPLVffWGzNKJRSxs6xKLdELIBc3hShIO5svcckUW # 8aClGX8fvmveiwmWTzun/0lsuOgVW3fDtKO6ImohOOf/EnnB5gtGkKqfyEqjG0Bk # PgE9cJziSfoUEZH4bnPg6/UVDNYVAZXamMru/aPZ5RKm0kXrgOTR+zgDKvx/yM6+ # 9IJwDaJ0DsYCA36Q7jkRb+XdzaCrov40zxp6nqJlvz3/ZvylVSLJGHJaiuTc5GYQ # 6HNtaRx7xvYoHHaPvabfsipSkIl3HttJtAWRLyJaPG+TbTDXtB5z1p42l0kjqET3 # EPUP21v8DUlGfqDMnfusWF6tm9nTIQ== # SIG # End signature block |