PKIStats.psm1
# Déclare la table une seule fois pour le module entier $script:TplByOid = @{} #region function_parse function Update-Top5 { [CmdletBinding()] param ( [Parameter()] [array]$GlobalTop5 = @(), [Parameter(Mandatory = $true)] [hashtable]$LocalStats ) # Combine current global top 5 and local stats into a single array $combined = @() foreach ($item in $GlobalTop5) { $combined += [pscustomobject]@{ Name = $item.Name Count = $item.Count } } foreach ($entry in $LocalStats.GetEnumerator()) { $combined += [pscustomobject]@{ Name = $entry.Key Count = $entry.Value } } # Merge duplicates by name and sum their counts $merged = @{} foreach ($obj in $combined) { if ($merged.ContainsKey($obj.Name)) { $merged[$obj.Name] += $obj.Count } else { $merged[$obj.Name] = $obj.Count } } # Return a new top 5 list sorted by count descending $result = $merged.GetEnumerator() | ForEach-Object { [pscustomobject]@{ Name = $_.Key Count = $_.Value } } | Sort-Object Count -Descending | Select-Object -First 5 return $result } function Get-Top5 { param( [Parameter(Mandatory)][object[]]$Data ) $firstProp = $Data[0].PSObject.Properties.Name | Where-Object { $_ -ne 'Count' } | Select-Object -First 1 if (-not $firstProp) { return @() } $Data | Group-Object $firstProp | ForEach-Object { [pscustomobject]@{ $firstProp = $_.Name; Count = ($_.Group | Measure-Object Count -Sum).Sum } } | Sort-Object Count -Descending | Select-Object -First 5 } function Get-Top5FromIssued { [CmdletBinding()] param( [Parameter(Mandatory)] [array]$InputObject, [Parameter(Mandatory)] [string]$FieldName, # ex : "Request.RequesterName" [Parameter(Mandatory)] [string]$Label # ex : "Requester" ou "Target" ) $top = $InputObject | Group-Object { $_.Properties[$FieldName] } | Where-Object { $_.Count -ge 2 } | Sort-Object Count -Descending | Select-Object -First 5 | ForEach-Object { $name = if ([string]::IsNullOrWhiteSpace($_.Name)) { "Unknown" } else { $_.Name } [pscustomobject]@{ "$Label" = $name Count = $_.Count } } return $top } function Resolve-TemplateName { param($row) if (-not $script:TplByOid -or $script:TplByOid.Count -eq 0) { try { Get-CertificateTemplate | ForEach-Object { if ($_.Oid.Value) { $script:TplByOid[$_.Oid.Value] = $_.Name } } } catch { Write-Warning "Failed to load certificate templates: $_" } } $raw = if ($row.CertificateTemplate) { $row.CertificateTemplate } else { $row.CertificateTemplateOid } if ($raw -match '^\d+(\.\d+)+') { if ($script:TplByOid.ContainsKey($raw)) { return $script:TplByOid[$raw] } } return $raw } function Format-Cert { param( [Parameter(Mandatory)]$Row, [Parameter(Mandatory)][datetime]$Now ) $dn = $Row.Properties["Request.DistinguishedName"] $subjectCN = if ($dn -match "CN=([^,]+)") { $matches[1] } else { $dn } $days = [int]($Row.NotAfter - $Now).TotalDays [pscustomobject]@{ RequestID = $Row.RequestID Template = Resolve-TemplateName $Row Name = $subjectCN Request = $Row.Properties["Request.RequesterName"] Subject = $Row.CommonName Created = $Row.NotBefore ExpiresOn = $Row.NotAfter DaysLeft = $days ConfigString = $Row.ConfigString Table = $Row.Table } } function Get-CSRSummary { param ( $RawRequest ) $result = [ordered]@{ ComboKey = $null ProcessName = $null } try { $bytes = [Convert]::FromBase64String($RawRequest) $req = New-Object SysadminsLV.PKI.Cryptography.X509Certificates.X509CertificateRequest -ArgumentList (, $bytes) # Signature + public key $sigAlgo = $req.SignatureAlgorithm.FriendlyName $keyAlgo = $req.PublicKey.Oid.FriendlyName $keyLen = $req.PublicKey.Key.KeySize $result.ComboKey = "$sigAlgo - $keyAlgo - ${keyLen}bits" # Extraire ProcessName à partir du texte formaté if ($req.Attributes) { $req.Attributes | ForEach-Object { if($_.Oid.Value -eq "1.3.6.1.4.1.311.21.20") { $asn = [SysadminsLV.Asn1Parser.Asn1Reader]::new($_.RawData) if (($asn.Tag -eq 48) -and $asn.MoveNext() -and ($asn.Tag -eq 2) -and $asn.MoveNext() -and ($asn.Tag -eq 12)) { $bytes = $asn.GetPayload() $encoding = [System.Text.UnicodeEncoding]::ASCII if ($bytes -cmatch '[^\x20-\x7F]') { $encoding = [System.Text.UnicodeEncoding]::Unicode } #$result.MachineName = $encoding.GetString($asn.GetPayload()) $null = $asn.MoveNext() #$result.UserName = $encoding.GetString($asn.GetPayload()) $null = $asn.MoveNext() $result.ProcessName = $encoding.GetString($asn.GetPayload()) #$result.RequestType = $CertRequest.RequestType #$result.ReqID = $ReqID } } } } } catch { # No result } return [pscustomobject]$result } function ConvertFrom-CSRExtensionMeta { param( $RawData ) $result = @{ ProcessName = $null } $RawRequestBytes = [Convert]::FromBase64String($RawData) Try { $CertRequest = New-Object SysadminsLV.PKI.Cryptography.X509Certificates.X509CertificateRequest (,$RawRequestBytes) } catch { } if ($CertRequest.Attributes) { $CertRequest.Attributes | ForEach-Object { if($_.Oid.Value -eq "1.3.6.1.4.1.311.21.20") { $asn = [SysadminsLV.Asn1Parser.Asn1Reader]::new($_.RawData) if (($asn.Tag -eq 48) -and $asn.MoveNext() -and ($asn.Tag -eq 2) -and $asn.MoveNext() -and ($asn.Tag -eq 12)) { $bytes = $asn.GetPayload() $encoding = [System.Text.UnicodeEncoding]::ASCII if ($bytes -cmatch '[^\x20-\x7F]') { $encoding = [System.Text.UnicodeEncoding]::Unicode } $null = $asn.MoveNext() $null = $asn.MoveNext() $result.ProcessName = $encoding.GetString($asn.GetPayload()) } } } } if ($result) { return [pscustomobject]$result } } function Get-CertSignatureKeyCombo { param ( $RawCertificate ) try { $bytes = [Convert]::FromBase64String($RawCertificate) $certif = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes) $sigAlgo = $certif.SignatureAlgorithm.FriendlyName $keyAlgo = $certif.PublicKey.Oid.FriendlyName $keyLen = $certif.PublicKey.Key.KeySize $certif.Dispose() return "$sigAlgo - $keyAlgo - ${keyLen}bits" } catch { Write-Warning "Raw certificate error: $($_.Exception.Message)" return "Unknown" } } function Update-StatCounter { [CmdletBinding()] param( [hashtable]$Bucket, [string]$Key ) if ([string]::IsNullOrEmpty($Key)) { return } if ($Bucket.ContainsKey($Key)) { $Bucket[$Key]++ } else { $Bucket[$Key] = 1 } } function Get-RandomColor { return "#{0:X6}" -f (Get-Random -Minimum 0 -Maximum 0xFFFFFF) } function Get-RevokerFromMessage { param([string]$Message) if ([string]::IsNullOrWhiteSpace($Message)) { return $null } return ($Message -split '\s+')[-1].Trim() } function Resolve-StatusCode { param ( [Parameter(Mandatory)] [int]$Code ) $statusMap = @{ 0x80094800 = 'CERTSRV_E_INVALID_REQUEST' 0x80094801 = 'CERTSRV_E_INVALID_CA_CERTIFICATE' 0x80094802 = 'CERTSRV_E_UNSUPPORTED_CERT_TYPE' 0x80094803 = 'CERTSRV_E_NO_CERT_TYPE' 0x80094812 = 'CERTSRV_E_SUBJECT_ALT_NAME_REQUIRED' 0x80094814 = 'CERTSRV_E_INVALID_EK' 0x80094001 = 'CERTSRV_E_BAD_REQUESTSUBJECT' 0x80094002 = 'CERTSRV_E_NO_REQUEST' 0x80094003 = 'CERTSRV_E_BAD_REQUESTSTATUS' 0x80094004 = 'CERTSRV_E_PROPERTY_EMPTY' 0x80094005 = 'CERTSRV_E_INVALID_PROPERTY' 0x80094006 = 'CERTSRV_E_INVALID_EXTENSION' 0x80092004 = 'CRYPT_E_NOT_FOUND' 0x80092009 = 'CRYPT_E_BAD_MSG' 0x80092010 = 'CRYPT_E_BAD_EXTENSIONS' 0x80092013 = 'CRYPT_E_REVOCATION_OFFLINE' 0x80092022 = 'CRYPT_E_ISSUER_SERIALNUMBER' 0x80070005 = 'E_ACCESSDENIED' 0x80070057 = 'E_INVALIDARG' } $hexCode = '0x{0:X8}' -f ($Code -band 0xFFFFFFFF) if ($statusMap.ContainsKey([int]$hexCode)) { return "$hexCode : $($statusMap[[int]$hexCode])" } else { return $hexCode } } function Format-FailedRequest { param ( [Parameter(Mandatory)] $cert ) return [pscustomobject]@{ RequestID = $cert.RequestID StatusCode = ('0x{0:X8}' -f ($cert.'Request.StatusCode')) Requester = $cert.'Request.RequesterName' CommonName = $cert.'Request.CommonName' Template = Resolve-TemplateName -row $cert Submitted = $cert.'Request.SubmittedWhen' Resolved = $cert.'Request.ResolvedWhen' Config = $cert.ConfigString Message = $cert.'Request.DispositionMessage' } } #endregion function_parse #region Displa_Functions function Show-ConsoleStatus { param ( [string]$Label = "Issued", [int]$Page, [int]$Count, [TimeSpan]$Elapsed ) $line = "[$Label] > Page $Page processed ($Count items) | Elapsed: $($Elapsed.ToString("hh\:mm\:ss"))" try { $oldColor = [Console]::ForegroundColor [Console]::ForegroundColor = 'Green' [Console]::SetCursorPosition(0, [Console]::CursorTop) [Console]::Write($line.PadRight([Console]::WindowWidth)) [Console]::ForegroundColor = $oldColor } catch { Write-Host $line -ForegroundColor Orange } } function Get-CAState { param([datetime]$ExpiryDate) if (-not $ExpiryDate) { return 'Unknown' } if ($ExpiryDate -gt (Get-Date).AddDays(90)) { return 'Active' } elseif ($ExpiryDate -gt (Get-Date)) { return 'Warning' } else { return 'Expired' } } function Get-CRLStatusFromAD { [CmdletBinding()] param( [Parameter(Mandatory)] [array]$CertificationAuthority ) # list CRL in AD without RSAT AD $configNC = ([ADSI]"LDAP://RootDSE").configurationNamingContext $searchBase = "CN=CDP,CN=Public Key Services,CN=Services,$configNC" $CRLCombined = @() $crlObjects = @() $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = "LDAP://$searchBase" $searcher.Filter = "(objectClass=cRLDistributionPoint)" $searcher.PageSize = 1000 $searcher.PropertiesToLoad.Add("whenCreated") | Out-Null $searcher.PropertiesToLoad.Add("whenChanged") | Out-Null $searcher.PropertiesToLoad.Add("name") | Out-Null $results = $searcher.FindAll() foreach ($result in $results) { $entry = $result.Properties $crlObjects += [PSCustomObject]@{ DistinguishedName = $result.Path -replace "^LDAP://", "" Name = $entry["name"] | Select-Object -First 1 Created = $entry["whencreated"] | Select-Object -First 1 Changed = $entry["whenchanged"] | Select-Object -First 1 } } foreach ($ca in Get-CRLValidityPeriod -CertificationAuthority $CertificationAuthority) { $caName = $ca.DisplayName $crlAD = $crlObjects | Where-Object { $_.Name -eq $caName } $CRLCombined += [pscustomobject]@{ Name = $ca.Name DisplayName = $ca.DisplayName ComputerName = $ca.ComputerName BaseCRL = $ca.BaseCRL DeltaCRL = $ca.DeltaCRL Modified = $ca.IsModified Created = if ($crlAD) { $crlAD.Created.ToString("yyyy-MM-dd") } else { $null } LastUpdate = if ($crlAD) { $crlAD.Changed.ToString("yyyy-MM-dd") } else { $null } ADStatus = if ($crlAD) { "Published" } else { "Not Published" } } } return $CRLCombined } function Get-PKIEnrollmentServices { param ( [string]$SearchBase = ([ADSI]"LDAP://RootDSE").configurationNamingContext ) $searcher = New-Object System.DirectoryServices.DirectorySearcher $searcher.SearchRoot = "LDAP://$SearchBase" $searcher.Filter = "(objectClass=pKIEnrollmentService)" $searcher.PageSize = 100 $searcher.PropertiesToLoad.Add("dNSHostName") | Out-Null $searcher.PropertiesToLoad.Add("displayName") | Out-Null $searcher.PropertiesToLoad.Add("whenCreated") | Out-Null $searcher.PropertiesToLoad.Add("Name") | Out-Null $results = $searcher.FindAll() foreach ($result in $results) { $entry = $result.Properties [PSCustomObject]@{ DistinguishedName = $result.Path -replace "^LDAP://", "" dNSHostName = $entry["dnshostname"] | Select-Object -First 1 DisplayName = $entry["displayname"] | Select-Object -First 1 Created = $entry["whencreated"] | Select-Object -First 1 Name = $entry["Name"] | Select-Object -First 1 } } } #endregion Displa_Functions # SIG # Begin signature block # MIImkwYJKoZIhvcNAQcCoIImhDCCJoACAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCiFq+EZlGEH6jP # 9ObLwl3yGZ/NR1TqVey7FNCdE+yPFKCCICMwggWNMIIEdaADAgECAhAOmxiO+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 # twGpn1eqXijiuZQwggYoMIIEEKADAgECAhBrxlWg9go45bxtH9Zi+WCgMA0GCSqG # SIb3DQEBCwUAMFYxCzAJBgNVBAYTAlBMMSEwHwYDVQQKExhBc3NlY28gRGF0YSBT # eXN0ZW1zIFMuQS4xJDAiBgNVBAMTG0NlcnR1bSBDb2RlIFNpZ25pbmcgMjAyMSBD # QTAeFw0yNDExMDYwOTQ0MjlaFw0yNTExMDYwOTQ0MjhaME4xCzAJBgNVBAYTAkZS # MQ8wDQYDVQQHDAZUb3Vsb24xFjAUBgNVBAoMDU1laGRpIERha2hhbWExFjAUBgNV # BAMMDU1laGRpIERha2hhbWEwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIB # gQCsFc3e5PwEJuycVRR54Qp8hFEckVwj7u1hMc7fejXKC/oR+uixlujLAHA9NcGX # jcQIXNP3GmezLF3Tj6Jvcs/kNT/a5zqjI5HEfIap7EHwf03f5060+Rc21v1UDjzj # DZzi9xFFum8eeGLc4pTzUB3wP3+M+mY7d5QlTjIxZSNnMBisJE8ASqG9JtRcQmIz # HACI70xRCQVV8ZjJ8J+Shr6wkNdDy/IjR+Y9VkMRIJozWR+pqbKuQOIDBSxQYVHg # bT+gsLOfvHkBPJN0ZQe7eJdG7J78Z1nzNH9yXhZ0HHdPB80tUwM0HC1n4LO3kki/ # IBmg4Qq/UyMMQd826fJk3ylbAlf8w7N80INQcLLBGVECmWI21d9f3l5usvWDa+mJ # ma57c6GUDY05Jg5owLgNREZsyRt5rOlg68NLmz9tuEkJA1D4ntpKq0KZc3HJv04x # XTcfTEqbKYr7vZ//ENsell5UdUQxL6rGJzazhsK02ZcmasICiHNLfG/tBaolCbeM # 8ekCAwEAAaOCAXgwggF0MAwGA1UdEwEB/wQCMAAwPQYDVR0fBDYwNDAyoDCgLoYs # aHR0cDovL2Njc2NhMjAyMS5jcmwuY2VydHVtLnBsL2Njc2NhMjAyMS5jcmwwcwYI # KwYBBQUHAQEEZzBlMCwGCCsGAQUFBzABhiBodHRwOi8vY2NzY2EyMDIxLm9jc3At # Y2VydHVtLmNvbTA1BggrBgEFBQcwAoYpaHR0cDovL3JlcG9zaXRvcnkuY2VydHVt # LnBsL2Njc2NhMjAyMS5jZXIwHwYDVR0jBBgwFoAU3XRdTADbe5+gdMqxbvc8wDLA # cM0wHQYDVR0OBBYEFAG3sIcT8bRm7QyFu8699Gpkr5NmMEsGA1UdIAREMEIwCAYG # Z4EMAQQBMDYGCyqEaAGG9ncCBQEEMCcwJQYIKwYBBQUHAgEWGWh0dHBzOi8vd3d3 # LmNlcnR1bS5wbC9DUFMwEwYDVR0lBAwwCgYIKwYBBQUHAwMwDgYDVR0PAQH/BAQD # AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCJ58BnchFNGzLksJ9oHFEWTs643G+PKOHr # 9RmrKSB/4MtPriG5iez+MFsGqYwkYd5QzqOIYg24ctfbWXJWG8Xj+YMfp1r+hkYq # O0Abpv26sZ1ZjNGgGUbb3z7KqhY+IdVpZf2aG/Rycl5dE2LbhWqp9h24WfQCIS/e # XxH7HmM9SEBHYbfOqlEA+RF/gRGYCQOg0ui2j0ZzIOrQGj3Njn/5rzP9OmPmLt4h # DsixjFWgu598XmRKj5KW1MShFIjUuUzSmOWDgKA16lJi6LggdFAB/MImiDH48v8N # /9R9En24pUGGj2XOgBX5SZ4kj+VN1YaY1vYPFp3wLu85zpgRZgZQC+WurX8s1tRn # iCIj/+ajUB4G4TcbTz6k16X1Yz9ba1y7p/hJB92uDW7esMGgqzEv+Osd11bVoNmv # CE8Twsz0cuFJqBtVZIycCkgw/AVyJIsNS6RADi94PvbOf8rty8HV3bHmm6O4wJVc # 5ch50bL7JVyYTPN5OTzXSDx62wKi5ePZvEF7RX3cQlTQMYticde91khs2n2FZ06K # Uin5DtQgxy0Q1ufFIDZthsk5AaSWiZzFgAgJt8JaQGPyGAYl2Sr8a/gMLpcBsPwI # zdlDUOJwyHPxlR9ZiraUzF/1SSN7CgjqFSDAAZ+i4i8gZsPpU38GtBSLrw/CrnUB # /KGcFNMvszCCBrQwggScoAMCAQICEA3HrFcF/yGZLkBDIgw6SYYwDQYJKoZIhvcN # AQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG # A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3Rl # ZCBSb290IEc0MB4XDTI1MDUwNzAwMDAwMFoXDTM4MDExNDIzNTk1OVowaTELMAkG # A1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdp # Q2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1 # IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBALR4MdMKmEFyvjxG # wBysddujRmh0tFEXnU2tjQ2UtZmWgyxU7UNqEY81FzJsQqr5G7A6c+Gh/qm8Xi4a # PCOo2N8S9SLrC6Kbltqn7SWCWgzbNfiR+2fkHUiljNOqnIVD/gG3SYDEAd4dg2dD # GpeZGKe+42DFUF0mR/vtLa4+gKPsYfwEu7EEbkC9+0F2w4QJLVSTEG8yAR2CQWIM # 1iI5PHg62IVwxKSpO0XaF9DPfNBKS7Zazch8NF5vp7eaZ2CVNxpqumzTCNSOxm+S # AWSuIr21Qomb+zzQWKhxKTVVgtmUPAW35xUUFREmDrMxSNlr/NsJyUXzdtFUUt4a # S4CEeIY8y9IaaGBpPNXKFifinT7zL2gdFpBP9qh8SdLnEut/GcalNeJQ55IuwnKC # gs+nrpuQNfVmUB5KlCX3ZA4x5HHKS+rqBvKWxdCyQEEGcbLe1b8Aw4wJkhU1JrPs # FfxW1gaou30yZ46t4Y9F20HHfIY4/6vHespYMQmUiote8ladjS/nJ0+k6Mvqzfpz # PDOy5y6gqztiT96Fv/9bH7mQyogxG9QEPHrPV6/7umw052AkyiLA6tQbZl1KhBtT # asySkuJDpsZGKdlsjg4u70EwgWbVRSX1Wd4+zoFpp4Ra+MlKM2baoD6x0VR4RjSp # WM8o5a6D8bpfm4CLKczsG7ZrIGNTAgMBAAGjggFdMIIBWTASBgNVHRMBAf8ECDAG # AQH/AgEAMB0GA1UdDgQWBBTvb1NK6eQGfHrK4pBW9i/USezLTjAfBgNVHSMEGDAW # gBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAww # CgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUFBzABhhhodHRwOi8v # b2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6Ly9jYWNlcnRzLmRp # Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0MEMGA1UdHwQ8MDow # OKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRS # b290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCGSAGG/WwHATANBgkq # hkiG9w0BAQsFAAOCAgEAF877FoAc/gc9EXZxML2+C8i1NKZ/zdCHxYgaMH9Pw5tc # BnPw6O6FTGNpoV2V4wzSUGvI9NAzaoQk97frPBtIj+ZLzdp+yXdhOP4hCFATuNT+ # ReOPK0mCefSG+tXqGpYZ3essBS3q8nL2UwM+NMvEuBd/2vmdYxDCvwzJv2sRUoKE # fJ+nN57mQfQXwcAEGCvRR2qKtntujB71WPYAgwPyWLKu6RnaID/B0ba2H3LUiwDR # AXx1Neq9ydOal95CHfmTnM4I+ZI2rVQfjXQA1WSjjf4J2a7jLzWGNqNX+DF0SQzH # U0pTi4dBwp9nEC8EAqoxW6q17r0z0noDjs6+BFo+z7bKSBwZXTRNivYuve3L2oiK # NqetRHdqfMTCW/NmKLJ9M+MtucVGyOxiDf06VXxyKkOirv6o02OoXN4bFzK0vlNM # svhlqgF2puE6FndlENSmE+9JGYxOGLS/D284NHNboDGcmWXfwXRy4kbu4QFhOm0x # JuF2EZAOk5eCkhSxZON3rGlHqhpB/8MluDezooIs8CVnrpHMiD2wL40mm53+/j7t # FaxYKIqL0Q4ssd8xHZnIn/7GELH3IdvG2XlM9q7WP/UwgOkw/HQtyRN62JK4S1C8 # uw3PdBunvAZapsiI5YKdvlarEvf8EA+8hcpSM9LHJmyrxaFtoza2zNaQ9k+5t1ww # gga5MIIEoaADAgECAhEAmaOACiZVO2Wr3G6EprPqOTANBgkqhkiG9w0BAQwFADCB # gDELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu # QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIG # A1UEAxMbQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQSAyMB4XDTIxMDUxOTA1MzIx # OFoXDTM2MDUxODA1MzIxOFowVjELMAkGA1UEBhMCUEwxITAfBgNVBAoTGEFzc2Vj # byBEYXRhIFN5c3RlbXMgUy5BLjEkMCIGA1UEAxMbQ2VydHVtIENvZGUgU2lnbmlu # ZyAyMDIxIENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAnSPPBDAj # O8FGLOczcz5jXXp1ur5cTbq96y34vuTmflN4mSAfgLKTvggv24/rWiVGzGxT9YEA # SVMw1Aj8ewTS4IndU8s7VS5+djSoMcbvIKck6+hI1shsylP4JyLvmxwLHtSworV9 # wmjhNd627h27a8RdrT1PH9ud0IF+njvMk2xqbNTIPsnWtw3E7DmDoUmDQiYi/ucJ # 42fcHqBkbbxYDB7SYOouu9Tj1yHIohzuC8KNqfcYf7Z4/iZgkBJ+UFNDcc6zokZ2 # uJIxWgPWXMEmhu1gMXgv8aGUsRdaCtVD2bSlbfsq7BiqljjaCun+RJgTgFRCtsuA # Ew0pG9+FA+yQN9n/kZtMLK+Wo837Q4QOZgYqVWQ4x6cM7/G0yswg1ElLlJj6NYKL # w9EcBXE7TF3HybZtYvj9lDV2nT8mFSkcSkAExzd4prHwYjUXTeZIlVXqj+eaYqoM # TpMrfh5MCAOIG5knN4Q/JHuurfTI5XDYO962WZayx7ACFf5ydJpoEowSP07YaBiQ # 8nXpDkNrUA9g7qf/rCkKbWpQ5boufUnq1UiYPIAHlezf4muJqxqIns/kqld6JVX8 # cixbd6PzkDpwZo4SlADaCi2JSplKShBSND36E/ENVv8urPS0yOnpG4tIoBGxVCAR # PCg1BnyMJ4rBJAcOSnAWd18Jx5n858JSqPECAwEAAaOCAVUwggFRMA8GA1UdEwEB # /wQFMAMBAf8wHQYDVR0OBBYEFN10XUwA23ufoHTKsW73PMAywHDNMB8GA1UdIwQY # MBaAFLahVDkCw6A/joq8+tT4HKbROg79MA4GA1UdDwEB/wQEAwIBBjATBgNVHSUE # DDAKBggrBgEFBQcDAzAwBgNVHR8EKTAnMCWgI6Ahhh9odHRwOi8vY3JsLmNlcnR1 # bS5wbC9jdG5jYTIuY3JsMGwGCCsGAQUFBwEBBGAwXjAoBggrBgEFBQcwAYYcaHR0 # cDovL3N1YmNhLm9jc3AtY2VydHVtLmNvbTAyBggrBgEFBQcwAoYmaHR0cDovL3Jl # cG9zaXRvcnkuY2VydHVtLnBsL2N0bmNhMi5jZXIwOQYDVR0gBDIwMDAuBgRVHSAA # MCYwJAYIKwYBBQUHAgEWGGh0dHA6Ly93d3cuY2VydHVtLnBsL0NQUzANBgkqhkiG # 9w0BAQwFAAOCAgEAdYhYD+WPUCiaU58Q7EP89DttyZqGYn2XRDhJkL6P+/T0IPZy # xfxiXumYlARMgwRzLRUStJl490L94C9LGF3vjzzH8Jq3iR74BRlkO18J3zIdmCKQ # a5LyZ48IfICJTZVJeChDUyuQy6rGDxLUUAsO0eqeLNhLVsgw6/zOfImNlARKn1FP # 7o0fTbj8ipNGxHBIutiRsWrhWM2f8pXdd3x2mbJCKKtl2s42g9KUJHEIiLni9Byo # qIUul4GblLQigO0ugh7bWRLDm0CdY9rNLqyA3ahe8WlxVWkxyrQLjH8ItI17RdyS # aYayX3PhRSC4Am1/7mATwZWwSD+B7eMcZNhpn8zJ+6MTyE6YoEBSRVrs0zFFIHUR # 08Wk0ikSf+lIe5Iv6RY3/bFAEloMU+vUBfSouCReZwSLo8WdrDlPXtR0gicDnytO # 7eZ5827NS2x7gCBibESYkOh1/w1tVxTpV2Na3PR7nxYVlPu1JPoRZCbH86gc96UT # vuWiOruWmyOEMLOGGniR+x+zPF/2DaGgK2W1eEJfo2qyrBNPvF7wuAyQfiFXLwvW # HamoYtPZo0LHuH8X3n9C+xN4YaNjt2ywzOr+tKyEVAotnyU9vyEVOaIYMk3IeBrm # Fnn0gbKeTTyYeEEUz/Qwt4HOUBCrW602NCmvO1nm+/80nLy5r0AZvCQxaQ4wggbt # MIIE1aADAgECAhAKgO8YS43xBYLRxHanlXRoMA0GCSqGSIb3DQEBCwUAMGkxCzAJ # BgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGln # aUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAy # NSBDQTEwHhcNMjUwNjA0MDAwMDAwWhcNMzYwOTAzMjM1OTU5WjBjMQswCQYDVQQG # EwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0 # IFNIQTI1NiBSU0E0MDk2IFRpbWVzdGFtcCBSZXNwb25kZXIgMjAyNSAxMIICIjAN # BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA0EasLRLGntDqrmBWsytXum9R/4Zw # CgHfyjfMGUIwYzKomd8U1nH7C8Dr0cVMF3BsfAFI54um8+dnxk36+jx0Tb+k+87H # 9WPxNyFPJIDZHhAqlUPt281mHrBbZHqRK71Em3/hCGC5KyyneqiZ7syvFXJ9A72w # zHpkBaMUNg7MOLxI6E9RaUueHTQKWXymOtRwJXcrcTTPPT2V1D/+cFllESviH8Yj # oPFvZSjKs3SKO1QNUdFd2adw44wDcKgH+JRJE5Qg0NP3yiSyi5MxgU6cehGHr7zo # u1znOM8odbkqoK+lJ25LCHBSai25CFyD23DZgPfDrJJJK77epTwMP6eKA0kWa3os # Ae8fcpK40uhktzUd/Yk0xUvhDU6lvJukx7jphx40DQt82yepyekl4i0r8OEps/FN # O4ahfvAk12hE5FVs9HVVWcO5J4dVmVzix4A77p3awLbr89A90/nWGjXMGn7FQhmS # lIUDy9Z2hSgctaepZTd0ILIUbWuhKuAeNIeWrzHKYueMJtItnj2Q+aTyLLKLM0Mh # eP/9w6CtjuuVHJOVoIJ/DtpJRE7Ce7vMRHoRon4CWIvuiNN1Lk9Y+xZ66lazs2kK # FSTnnkrT3pXWETTJkhd76CIDBbTRofOsNyEhzZtCGmnQigpFHti58CSmvEyJcAlD # VcKacJ+A9/z7eacCAwEAAaOCAZUwggGRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYE # FOQ7/PIx7f391/ORcWMZUEPPYYzoMB8GA1UdIwQYMBaAFO9vU0rp5AZ8esrikFb2 # L9RJ7MtOMA4GA1UdDwEB/wQEAwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCB # lQYIKwYBBQUHAQEEgYgwgYUwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2lj # ZXJ0LmNvbTBdBggrBgEFBQcwAoZRaHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29t # L0RpZ2lDZXJ0VHJ1c3RlZEc0VGltZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIwMjVD # QTEuY3J0MF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNv # bS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1 # Q0ExLmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI # hvcNAQELBQADggIBAGUqrfEcJwS5rmBB7NEIRJ5jQHIh+OT2Ik/bNYulCrVvhREa # fBYF0RkP2AGr181o2YWPoSHz9iZEN/FPsLSTwVQWo2H62yGBvg7ouCODwrx6ULj6 # hYKqdT8wv2UV+Kbz/3ImZlJ7YXwBD9R0oU62PtgxOao872bOySCILdBghQ/ZLcdC # 8cbUUO75ZSpbh1oipOhcUT8lD8QAGB9lctZTTOJM3pHfKBAEcxQFoHlt2s9sXoxF # izTeHihsQyfFg5fxUFEp7W42fNBVN4ueLaceRf9Cq9ec1v5iQMWTFQa0xNqItH3C # PFTG7aEQJmmrJTV3Qhtfparz+BW60OiMEgV5GWoBy4RVPRwqxv7Mk0Sy4QHs7v9y # 69NBqycz0BZwhB9WOfOu/CIJnzkQTwtSSpGGhLdjnQ4eBpjtP+XB3pQCtv4E5UCS # Dag6+iX8MmB10nfldPF9SVD7weCC3yXZi/uuhqdwkgVxuiMFzGVFwYbQsiGnoa9F # 5AaAyBjFBtXVLcKtapnMG3VH3EmAp/jsJ3FVF3+d1SVDTmjFjLbNFZUWMXuZyvgL # fgyPehwJVxwC+UpX2MSey2ueIu9THFVkT+um1vshETaWyQo8gmBto/m3acaP9Qsu # Lj3FNwFlTxq25+T4QwX9xa6ILs84ZPvmpovq90K8eWyG2N01c4IhSOxqt81nMYIF # xjCCBcICAQEwajBWMQswCQYDVQQGEwJQTDEhMB8GA1UEChMYQXNzZWNvIERhdGEg # U3lzdGVtcyBTLkEuMSQwIgYDVQQDExtDZXJ0dW0gQ29kZSBTaWduaW5nIDIwMjEg # Q0ECEGvGVaD2CjjlvG0f1mL5YKAwDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGC # NwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgHKuFZReR # GZog2PTmBitWDhAFu0QD+LDlwOm1eEmcQgUwDQYJKoZIhvcNAQEBBQAEggGAj9yl # yNJG7tceWvu41Y5QbPsG7tOUhVO2PBV1JX6aVsv9B6Mj2dLSZdJMLItKA5N5PlGE # 5Ypz3pSPG6axfb/sBWfhcibc4QRTGvcmi4dFBiDk4G4m5b0HC2HI2Z2VxKuh3b4T # FJ+if5B+VWI/cUDxy/rjhFgeuoPlc3JJwep6jlDvWHqulElLTq1h3AiF7L6q6UcB # DslfWaE5Gy9Jutkt5hgGNhw3zNlmLeXYkVAk3ipqvw38K4zH8INjy6hrsBYpoe2k # oYYKoDrxeeInaIuwhwPGIMIUmJecqjCgM+YkJcw5mbM0S3mSFxk26A9vn+CtF6Dm # TX8XH7M2dACwbM3E4r9de9x5TluCjoIDPE7aCZ2tK9Zy+RG1VibEwGHRiCSyEn+C # wPNgfCpiI/wt3FSlqrJfkCgHBvUTjyWTOhF/1zcdvBGioIlsz2m8rRLIXXKEc0Gv # fNZc15fuzA7QUL6bOBWGT3Mpm8yMfz5cuM/fSVZEsnrDbW5VwJJFyYeHcD3ZoYID # JjCCAyIGCSqGSIb3DQEJBjGCAxMwggMPAgEBMH0waTELMAkGA1UEBhMCVVMxFzAV # BgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVk # IEc0IFRpbWVTdGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMQIQCoDvGEuN # 8QWC0cR2p5V0aDANBglghkgBZQMEAgEFAKBpMBgGCSqGSIb3DQEJAzELBgkqhkiG # 9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTI1MTAwMzIwMjkxMFowLwYJKoZIhvcNAQkE # MSIEIMRNa0clslPQfnIqTzJJVybQ92ULiSn6GGGMSAVe1S73MA0GCSqGSIb3DQEB # AQUABIICAG0nbsF9SY0nYIYOI/UC1/LappzRoahyR0Mr2i+kMxPfW/loX/xxepiR # ICZG1cZevisEialgYcBfVWP261vga3n4A+fsxF4y32mCzaMedjhJARX+g4pAz8Cq # dqB+afpD3phqQDnxWwG+qRC9YDmgyLDGqBG8cNZaslnIxArXDAu8buh77loFYt+z # H0zf0VIKea/6CgLY+coshHcHQP9xctNlCWbAUNqnhi5FPXWm1qALlcswdNo4vtkM # rH97yguqEtCLoDox/SQCgMyVxFO7vNSI4ChxEa0spmuZrFGF/fwabSS65ESpk9zn # 2dvNnZiWhICyB45bu3AX3bTeDYSJLNRiLRnbqvK4POx7PUvitiQ9+1kGA3U/9AcS # /fOOlA9QaTdXSLs5exD227vZomRqMcU7izJGQz9qVgycblysxpf6HGiI9KbiJ15v # db1wj0G6sEgn1ivJiuHtilWrC1a8qQrmNyhV/cgjDk3j260uxZbhXJnSJ/jWSf0R # 95/xtCtXKeoXxCJqjUDGQGPBBTpWDznXZE3YMA3yS0ZVPkvwBmIs9s1b1lM9UHuc # Wh3Z/igCYqvGQpl2cY5sKakgmfIdkbIS86s97FbXOlN9HCJN4w0TN9O8gmtPTdOO # y0xqaPbcYo0mhTywCZBNq3Khq9RWqggBvsol0+lSnM59tSa0mQ2e # SIG # End signature block |