Private/CertificateHelper.ps1
|
function ConvertTo-RsaPrivateKey { [OutputType('System.Security.Cryptography.RSA')] param( [Parameter(Mandatory=$true,Position=0)] [string] $Pem ) if ($PSEdition -eq 'Core') { $Rsa = [System.Security.Cryptography.RSA]::Create() $Rsa.ImportFromPem($Pem) $Rsa } else { throw "ConvertTo-RsaPrivateKey unsupported in Windows PowerShell" } } function ConvertFrom-RsaPrivateKey { [OutputType('System.String')] param( [Parameter(Mandatory=$true)] [System.Security.Cryptography.RSA] $Rsa ) if ($PSEdition -eq 'Core') { $bytes = $Rsa.ExportRSAPrivateKey() ConvertTo-PemEncoding -Label "PRIVATE KEY" -RawData $bytes } else { throw "ConvertFrom-RsaPrivateKey unsupported in Windows PowerShell" } } function New-RsaKeyPair { param( [int] $KeySize = 2048 ) $bits = [System.UIntPtr]::op_Explicit($KeySize) $Rsa = [Devolutions.Picky.PrivateKey]::GenerateRsa($bits) $PublicKey = $Rsa.ToPublicKey().ToPem().ToRepr() $PrivateKey = $Rsa.ToPem().ToRepr() return [PSCustomObject]@{ PublicKey = $PublicKey PrivateKey = $PrivateKey } } function ConvertTo-PemEncoding { [OutputType('System.String')] param( [Parameter(Mandatory=$true)] [string] $Label, [Parameter(Mandatory=$true)] [byte[]] $RawData ) $base64 = [Convert]::ToBase64String($RawData) $offset = 0 $line_length = 64 $sb = [System.Text.StringBuilder]::new() $sb.AppendLine("-----BEGIN $label-----") | Out-Null while ($offset -lt $base64.Length) { $line_end = [Math]::Min($offset + $line_length, $base64.Length) $sb.AppendLine($base64.Substring($offset, $line_end - $offset)) | Out-Null $offset = $line_end } $sb.AppendLine("-----END $label-----") | Out-Null return $sb.ToString().Trim() } function ConvertFrom-PemEncoding { [OutputType('byte[]')] param( [Parameter(Mandatory=$true)] [string] $Label, [Parameter(Mandatory=$true)] [string] $PemData ) $base64 = $PemData ` -Replace "`n","" -Replace "`r","" ` -Replace "-----BEGIN $Label-----", "" ` -Replace "-----END $Label-----", "" return [Convert]::FromBase64String($base64) } function Split-PemChain { [OutputType('System.String[]')] param( [Parameter(Mandatory=$true)] [string] $Label, [Parameter(Mandatory=$true)] [string] $PemData ) [string[]] $PemChain = @() $PemData | Select-String -Pattern "(?smi)^-{2,}BEGIN $Label-{2,}.*?-{2,}END $Label-{2,}" ` -Allmatches | ForEach-Object { $_.Matches } | ForEach-Object { $PemChain += $_.Value } return $PemChain } function Get-IsPemCertificateAuthority { [OutputType('bool')] param( [Parameter(Mandatory=$true)] [string] $PemData ) $der = ConvertFrom-PemEncoding -Label 'CERTIFICATE' -PemData:$PemData $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]] $der) foreach ($extension in $cert.Extensions) { if ($extension.Oid.Value -eq "2.5.29.19") { $extension = [System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension] $extension return $extension.CertificateAuthority } } return $false } function Get-CertificateIssuerName { [OutputType('string')] param( [Parameter(Mandatory=$true)] [string] $PemData ) $der = ConvertFrom-PemEncoding -Label 'CERTIFICATE' -PemData:$PemData $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]] $der) return $cert.IssuerName.Name } function Get-CertificateSubjectName { [OutputType('string')] param( [Parameter(Mandatory=$true)] [string] $PemData ) $der = ConvertFrom-PemEncoding -Label 'CERTIFICATE' -PemData:$PemData $cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([byte[]] $der) return $cert.SubjectName.Name } function Expand-PfxCertificate { param ( [Parameter(Mandatory = $true)] [Devolutions.Picky.Pfx] $Pfx ) $Certificates = New-Object 'System.Collections.Generic.List[Devolutions.Picky.Cert]' $safeBagIterator = $Pfx.SafeBags() $safeBag = $safeBagIterator.Next() while ($null -ne $safeBag) { switch ($safeBag.Kind) { 'Certificate' { $Certificates.Add($safeBag.Certificate) } 'PrivateKey' { $PrivateKey = $safeBag.PrivateKey } default { Write-Host "SafeBag of ${safeBag.Kind} kind found" } } $safeBag.Dispose() $safeBag = $safeBagIterator.Next() } return [PSCustomObject]@{ Certificates = $Certificates PrivateKey = $PrivateKey } } function Get-PemCertificate { param( [string] $CertificateFile, [string] $PrivateKeyFile, [string] $Password ) [string[]] $PemChain = @() $PrivateKey = $null if (($CertificateFile -match ".pfx") -or ($CertificateFile -match ".p12")) { $AsByteStream = if ($PSEdition -eq 'Core') { @{AsByteStream = $true} } else { @{'Encoding' = 'Byte'} } $PfxBytes = Get-Content -Path $CertificateFile -Raw @AsByteStream $parsingParams = [Devolutions.Picky.Pkcs12ParsingParams]::New.Invoke(@()) $parsingParams.SkipDecryptionErrors = $true $parsingParams.SkipMacValidation = $true $pkcs12CryptoContext = if ($null -eq $password) { [Devolutions.Picky.Pkcs12CryptoContext]::NoPassword() } else { [Devolutions.Picky.Pkcs12CryptoContext]::WithPassword($password) } $pfx = [Devolutions.Picky.Pfx]::FromDer($PfxBytes, $pkcs12CryptoContext, $parsingParams) $expanded = Expand-PfxCertificate -Pfx $pfx $sb = New-Object System.Text.StringBuilder foreach ($cert in $expanded.Certificates) { $pem = $cert.ToPem() [void]$sb.AppendLine($pem.ToRepr()) } $PemData = $sb.ToString() if ($expanded.PrivateKey -Ne $null) { $PrivateKey = $expanded.PrivateKey.ToPem().ToRepr() } else { Write-Host "No private key was found in PFX file" } } else { try { $PemData = [System.IO.File]::ReadAllBytes($CertificateFile) # Try to parse the file as if it was a DER binary file. $Cert = [Devolutions.Picky.Cert]::FromDer($PemData) $PemData = $Cert.ToPem().ToRepr() } catch { # Assume we have a PEM. $PemData = Get-Content -Path $CertificateFile -Encoding UTF8 -Raw } try { $PrivateKey = [System.IO.File]::ReadAllBytes($PrivateKeyFile) # Try to parse the file as if it was a DER binary file. $PrivateKey = [Devolutions.Picky.PrivateKey]::FromPkcs8($PrivateKey) $PrivateKey = $PrivateKey.ToPem().ToRepr() } catch { # Assume we have a PEM. $PrivateKey = Get-Content -Path $PrivateKeyFile -Encoding UTF8 -Raw } } $PemChain = Split-PemChain -Label 'CERTIFICATE' -PemData $PemData if ($PemChain.Count -eq 0) { throw "Empty certificate chain!" } $PemChain | ForEach-Object -Begin { $Certs = @{} } -Process { $Key = Get-CertificateSubjectName -PemData $_ $Certs.Add($Key, $_) } -End { $Certs } $LeafCert = $PemChain | Where-Object { -Not ( Get-IsPemCertificateAuthority -PemData $_ )} [string[]] $SortedPemChain = @() if ($null -eq $LeafCert) { # Do not apply any transformation to the provided chain if no leaf certificate is found $SortedPemChain = $PemChain } else { # Otherwise, sort the chain: start by the leaf and then issued to issuer in order $SortedPemChain += $LeafCert $IssuerName = Get-CertificateIssuerName -PemData $LeafCert $SubjectName = Get-CertificateSubjectName -PemData $LeafCert While ($Certs.ContainsKey($IssuerName) -And ($IssuerName -Ne $SubjectName)) { $NextCert = $Certs[$IssuerName] $SortedPemChain += $NextCert $IssuerName = Get-CertificateIssuerName -PemData $NextCert $SubjectName = Get-CertificateSubjectName -PemData $NextCert } } $Certificate = $SortedPemChain -Join "`n" return [PSCustomObject]@{ Certificate = $Certificate PrivateKey = $PrivateKey } } # SIG # Begin signature block # MIIvXAYJKoZIhvcNAQcCoIIvTTCCL0kCAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAx5BRoOLYcCjaQ # 4NMFG3/zIlBmmP+giVd7OtDD4V9ytqCCFBkwggVyMIIDWqADAgECAhB2U/6sdUZI # k/Xl10pIOk74MA0GCSqGSIb3DQEBDAUAMFMxCzAJBgNVBAYTAkJFMRkwFwYDVQQK # ExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENvZGUgU2ln # bmluZyBSb290IFI0NTAeFw0yMDAzMTgwMDAwMDBaFw00NTAzMTgwMDAwMDBaMFMx # CzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQD # EyBHbG9iYWxTaWduIENvZGUgU2lnbmluZyBSb290IFI0NTCCAiIwDQYJKoZIhvcN # AQEBBQADggIPADCCAgoCggIBALYtxTDdeuirkD0DcrA6S5kWYbLl/6VnHTcc5X7s # k4OqhPWjQ5uYRYq4Y1ddmwCIBCXp+GiSS4LYS8lKA/Oof2qPimEnvaFE0P31PyLC # o0+RjbMFsiiCkV37WYgFC5cGwpj4LKczJO5QOkHM8KCwex1N0qhYOJbp3/kbkbuL # ECzSx0Mdogl0oYCve+YzCgxZa4689Ktal3t/rlX7hPCA/oRM1+K6vcR1oW+9YRB0 # RLKYB+J0q/9o3GwmPukf5eAEh60w0wyNA3xVuBZwXCR4ICXrZ2eIq7pONJhrcBHe # OMrUvqHAnOHfHgIB2DvhZ0OEts/8dLcvhKO/ugk3PWdssUVcGWGrQYP1rB3rdw1G # R3POv72Vle2dK4gQ/vpY6KdX4bPPqFrpByWbEsSegHI9k9yMlN87ROYmgPzSwwPw # jAzSRdYu54+YnuYE7kJuZ35CFnFi5wT5YMZkobacgSFOK8ZtaJSGxpl0c2cxepHy # 1Ix5bnymu35Gb03FhRIrz5oiRAiohTfOB2FXBhcSJMDEMXOhmDVXR34QOkXZLaRR # kJipoAc3xGUaqhxrFnf3p5fsPxkwmW8x++pAsufSxPrJ0PBQdnRZ+o1tFzK++Ol+ # A/Tnh3Wa1EqRLIUDEwIrQoDyiWo2z8hMoM6e+MuNrRan097VmxinxpI68YJj8S4O # JGTfAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB0G # A1UdDgQWBBQfAL9GgAr8eDm3pbRD2VZQu86WOzANBgkqhkiG9w0BAQwFAAOCAgEA # Xiu6dJc0RF92SChAhJPuAW7pobPWgCXme+S8CZE9D/x2rdfUMCC7j2DQkdYc8pzv # eBorlDICwSSWUlIC0PPR/PKbOW6Z4R+OQ0F9mh5byV2ahPwm5ofzdHImraQb2T07 # alKgPAkeLx57szO0Rcf3rLGvk2Ctdq64shV464Nq6//bRqsk5e4C+pAfWcAvXda3 # XaRcELdyU/hBTsz6eBolSsr+hWJDYcO0N6qB0vTWOg+9jVl+MEfeK2vnIVAzX9Rn # m9S4Z588J5kD/4VDjnMSyiDN6GHVsWbcF9Y5bQ/bzyM3oYKJThxrP9agzaoHnT5C # JqrXDO76R78aUn7RdYHTyYpiF21PiKAhoCY+r23ZYjAf6Zgorm6N1Y5McmaTgI0q # 41XHYGeQQlZcIlEPs9xOOe5N3dkdeBBUO27Ql28DtR6yI3PGErKaZND8lYUkqP/f # obDckUCu3wkzq7ndkrfxzJF0O2nrZ5cbkL/nx6BvcbtXv7ePWu16QGoWzYCELS/h # AtQklEOzFfwMKxv9cW/8y7x1Fzpeg9LJsy8b1ZyNf1T+fn7kVqOHp53hWVKUQY9t # W76GlZr/GnbdQNJRSnC0HzNjI3c/7CceWeQIh+00gkoPP/6gHcH1Z3NFhnj0qinp # J4fGGdvGExTDOUmHTaCX4GUT9Z13Vunas1jHOvLAzYIwggboMIIE0KADAgECAhB3 # vQ4Ft1kLth1HYVMeP3XtMA0GCSqGSIb3DQEBCwUAMFMxCzAJBgNVBAYTAkJFMRkw # FwYDVQQKExBHbG9iYWxTaWduIG52LXNhMSkwJwYDVQQDEyBHbG9iYWxTaWduIENv # ZGUgU2lnbmluZyBSb290IFI0NTAeFw0yMDA3MjgwMDAwMDBaFw0zMDA3MjgwMDAw # MDBaMFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIw # MAYDVQQDEylHbG9iYWxTaWduIEdDQyBSNDUgRVYgQ29kZVNpZ25pbmcgQ0EgMjAy # MDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMsg75ceuQEyQ6BbqYoj # /SBerjgSi8os1P9B2BpV1BlTt/2jF+d6OVzA984Ro/ml7QH6tbqT76+T3PjisxlM # g7BKRFAEeIQQaqTWlpCOgfh8qy+1o1cz0lh7lA5tD6WRJiqzg09ysYp7ZJLQ8LRV # X5YLEeWatSyyEc8lG31RK5gfSaNf+BOeNbgDAtqkEy+FSu/EL3AOwdTMMxLsvUCV # 0xHK5s2zBZzIU+tS13hMUQGSgt4T8weOdLqEgJ/SpBUO6K/r94n233Hw0b6nskEz # IHXMsdXtHQcZxOsmd/KrbReTSam35sOQnMa47MzJe5pexcUkk2NvfhCLYc+YVaMk # oog28vmfvpMusgafJsAMAVYS4bKKnw4e3JiLLs/a4ok0ph8moKiueG3soYgVPMLq # 7rfYrWGlr3A2onmO3A1zwPHkLKuU7FgGOTZI1jta6CLOdA6vLPEV2tG0leis1Ult # 5a/dm2tjIF2OfjuyQ9hiOpTlzbSYszcZJBJyc6sEsAnchebUIgTvQCodLm3HadNu # twFsDeCXpxbmJouI9wNEhl9iZ0y1pzeoVdwDNoxuz202JvEOj7A9ccDhMqeC5LYy # AjIwfLWTyCH9PIjmaWP47nXJi8Kr77o6/elev7YR8b7wPcoyPm593g9+m5XEEofn # GrhO7izB36Fl6CSDySrC/blTAgMBAAGjggGtMIIBqTAOBgNVHQ8BAf8EBAMCAYYw # EwYDVR0lBAwwCgYIKwYBBQUHAwMwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4E # FgQUJZ3Q/FkJhmPF7POxEztXHAOSNhEwHwYDVR0jBBgwFoAUHwC/RoAK/Hg5t6W0 # Q9lWULvOljswgZMGCCsGAQUFBwEBBIGGMIGDMDkGCCsGAQUFBzABhi1odHRwOi8v # b2NzcC5nbG9iYWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUwRgYIKwYBBQUH # MAKGOmh0dHA6Ly9zZWN1cmUuZ2xvYmFsc2lnbi5jb20vY2FjZXJ0L2NvZGVzaWdu # aW5ncm9vdHI0NS5jcnQwQQYDVR0fBDowODA2oDSgMoYwaHR0cDovL2NybC5nbG9i # YWxzaWduLmNvbS9jb2Rlc2lnbmluZ3Jvb3RyNDUuY3JsMFUGA1UdIAROMEwwQQYJ # KwYBBAGgMgECMDQwMgYIKwYBBQUHAgEWJmh0dHBzOi8vd3d3Lmdsb2JhbHNpZ24u # Y29tL3JlcG9zaXRvcnkvMAcGBWeBDAEDMA0GCSqGSIb3DQEBCwUAA4ICAQAldaAJ # yTm6t6E5iS8Yn6vW6x1L6JR8DQdomxyd73G2F2prAk+zP4ZFh8xlm0zjWAYCImbV # YQLFY4/UovG2XiULd5bpzXFAM4gp7O7zom28TbU+BkvJczPKCBQtPUzosLp1pnQt # pFg6bBNJ+KUVChSWhbFqaDQlQq+WVvQQ+iR98StywRbha+vmqZjHPlr00Bid/XSX # hndGKj0jfShziq7vKxuav2xTpxSePIdxwF6OyPvTKpIz6ldNXgdeysEYrIEtGiH6 # bs+XYXvfcXo6ymP31TBENzL+u0OF3Lr8psozGSt3bdvLBfB+X3Uuora/Nao2Y8nO # ZNm9/Lws80lWAMgSK8YnuzevV+/Ezx4pxPTiLc4qYc9X7fUKQOL1GNYe6ZAvytOH # X5OKSBoRHeU3hZ8uZmKaXoFOlaxVV0PcU4slfjxhD4oLuvU/pteO9wRWXiG7n9dq # cYC/lt5yA9jYIivzJxZPOOhRQAyuku++PX33gMZMNleElaeEFUgwDlInCI2Oor0i # xxnJpsoOqHo222q6YV8RJJWk4o5o7hmpSZle0LQ0vdb5QMcQlzFSOTUpEYck08T7 # qWPLd0jV+mL8JOAEek7Q5G7ezp44UCb0IXFl1wkl1MkHAHq4x/N36MXU4lXQ0x72 # f1LiSY25EXIMiEQmM2YBRN/kMw4h3mKJSAfa9TCCB7MwggWboAMCAQICDEwWQjHr # NX4fj8JFKDANBgkqhkiG9w0BAQsFADBcMQswCQYDVQQGEwJCRTEZMBcGA1UEChMQ # R2xvYmFsU2lnbiBudi1zYTEyMDAGA1UEAxMpR2xvYmFsU2lnbiBHQ0MgUjQ1IEVW # IENvZGVTaWduaW5nIENBIDIwMjAwHhcNMjUxMjA0MjEwMjI0WhcNMjgxMjA0MjEw # MjI0WjCB8zEdMBsGA1UEDwwUUHJpdmF0ZSBPcmdhbml6YXRpb24xEzARBgNVBAUT # CjExNjI1NDQ2ODkxEzARBgsrBgEEAYI3PAIBAxMCQ0ExFzAVBgsrBgEEAYI3PAIB # AhMGUXVlYmVjMQswCQYDVQQGEwJDQTEPMA0GA1UECBMGUXVlYmVjMRIwEAYDVQQH # EwlMYXZhbHRyaWUxGTAXBgNVBAoTEERFVk9MVVRJT05TIElOQy4xGTAXBgNVBAMT # EERFVk9MVVRJT05TIElOQy4xJzAlBgkqhkiG9w0BCQEWGHNlY3VyaXR5QGRldm9s # dXRpb25zLm5ldDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvGSmps # CDxiV4fapsFPCVhNMpfdlgrzFhCiXnX0Gy7Zu2e7cDMl2RNo0H8hpKNHhSODkguW # YNLZfmM+bpvE2FbLN0oYxUmlEHiBZWN8+0dq10ybkfQZp+O+95S3mX3ecdKTBhPY # B7I5HYNWBPR1Au86Y68x+LBaTtM3AY/zvwB2gMbefZsKAa9L9np1l7UjN6qfZskQ # G+zRxCj+88Cg6srkCYyNu/LMnFtWInbbxuqkh1ddTTAmVVSUOXXn+vKtUE2hTw/2 # 5x3psbHLLgcLbq0E4ZwzmRPB2ps972aouEaMkPhYXO4KTtTZFj6GoYy2sAUaGxgP # gADEeZY/guhqSBANnn2Wa6jaocgMvvNhSJd7aQfldvps0ZQyerVra7zgoqT0JkyL # NHm9AKiNV+mJQHqirXxL+Y70vO4uFJVukyvceNO+C+y0Re77pb8BntRfuSOdcySj # 1v/NQ7eHAV6/zY21xlYswIz7uysoHQeZEMWlEX8bjJNjDvkT1BUF9j655hVeyxpZ # zvaSgLdpdpZVSOQzSkdiH0KOhBiQ9R5Ppa2/W9qhdLNi+sKjr1/xEKSOd1fFG56R # iN4ems9YdEQR9KrnGrVpQQ0QKSutqFiWUlUqCgWuDwTPe2V+UvHa2xAlZlbYAzGf # X8c6OIniFLjWcxXKV21R7VP8UfQxrf38PapJAgMBAAGjggHbMIIB1zAOBgNVHQ8B # Af8EBAMCB4AwgZ8GCCsGAQUFBwEBBIGSMIGPMEwGCCsGAQUFBzAChkBodHRwOi8v # c2VjdXJlLmdsb2JhbHNpZ24uY29tL2NhY2VydC9nc2djY3I0NWV2Y29kZXNpZ25j # YTIwMjAuY3J0MD8GCCsGAQUFBzABhjNodHRwOi8vb2NzcC5nbG9iYWxzaWduLmNv # bS9nc2djY3I0NWV2Y29kZXNpZ25jYTIwMjAwVQYDVR0gBE4wTDBBBgkrBgEEAaAy # AQIwNDAyBggrBgEFBQcCARYmaHR0cHM6Ly93d3cuZ2xvYmFsc2lnbi5jb20vcmVw # b3NpdG9yeS8wBwYFZ4EMAQMwCQYDVR0TBAIwADBHBgNVHR8EQDA+MDygOqA4hjZo # dHRwOi8vY3JsLmdsb2JhbHNpZ24uY29tL2dzZ2NjcjQ1ZXZjb2Rlc2lnbmNhMjAy # MC5jcmwwIwYDVR0RBBwwGoEYc2VjdXJpdHlAZGV2b2x1dGlvbnMubmV0MBMGA1Ud # JQQMMAoGCCsGAQUFBwMDMB8GA1UdIwQYMBaAFCWd0PxZCYZjxezzsRM7VxwDkjYR # MB0GA1UdDgQWBBQc9AV01XqvHzkkeRDj0FeokGPnwDANBgkqhkiG9w0BAQsFAAOC # AgEAuuy64I5XgYWoKznXOKzBMUz4NTGnv1cN8ovX9pJlVUfeIhipffE6WZH9weHZ # +3OheToB4v8JWEobOBfZbz/8FI20WiXgtPvqU4T/ju8ZnVUv+XidlTdTRn6AzXjy # kpyJaLXMJA4BMvl5ISxUVYgnScOpP6hAqWsPDyp5CVOLmYWzZNKWvfBGMiEIwC+h # M+mkqKBnDu1V0tU9QDbqXgBqwBp1BKxWg5bxMYCNkC15WwqP8VVXRk/354x1Ua87 # LHGOSHBIobnySRbK+onnGESqKeVCzqL5DvTVBGZyjRARcYU0mY51raBnJ5/Yad3y # 7R+eSIuUjh35JNXwYAHYlWXYsVzfaH4OgFSaSkJcL0ieH4MT2xT8kITOb+9KElvc # YyFlxWhgIPW/vvY6vqU5aMlMCTcIWTZBbzp9dwCNsAYhhq4Okn5J1e5j+baIT58N # gOhsZjsME6VO1sYj1zUqKJnDza8vViZO7kKaHXtg6w2CgvfPNKO+tZncWEX3YIEa # dpt/t0UFlJ5mAWtlIzxf+79A/a8L7JWxcwNcZ1zUvy/mj+b/0G1S3rPCtsh7BaUs # sMVcHRzuanz+zMMBcQcdwxwyM1U5fnyFjBMbvsAhRJB6c68m1IbB8rM9QNrjZX4Y # 6YTxz1UOCMk2DusI+QHYIlidzJl16tYD3DlF5xefxyQcdEsxghqZMIIalQIBATBs # MFwxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTIwMAYD # VQQDEylHbG9iYWxTaWduIEdDQyBSNDUgRVYgQ29kZVNpZ25pbmcgQ0EgMjAyMAIM # TBZCMes1fh+PwkUoMA0GCWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAI # oAKAAKECgAAwGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIB # CzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIIoOdj6NsOj8CzEmXwuF # 5hy9kunacrANOHR7PRkafNmDMA0GCSqGSIb3DQEBAQUABIICAIrbsjrLr2bozj8A # qZVfphEBFIHmlWJklxmEMsv1ovLLFUCodZbi0yifVNMjYTmA4AZeK9TazShXN03Q # uxP98dWxAvPoWaXV74ENvteIrfSnwUpiidzZL0cCx+xzAxxHWPW3bzs0s96dzJgn # stZQ7zm5FA8YSEpFkQYwjrmnBCH6zIMIYr7DZY8VK9IV2UVbhO0/6Niy4qncdbb7 # 6XxmatfYeiKjV5uxp3Ko6qqv93fbTYbJwi2ydyKRWl1f3PlA6rz/T2+kUfF3CMIa # grptOvEEPB5/ny1pxshltHzNau5bl+czf81g8Ek+Sxxq5tQJ1gcY7xHc0UWj4K9U # u7QWCXfMoZrTyWG9lDnH0nCBc/ZTMopaTPgh36279zOyCQZ4M6loNI+OOrZ2sV2e # mX1ltdutbGBA8gHo/q82gR1VDiUUUzh9ijuer9PdC95bjN5qlWf7tQxrFgcmmlqE # r0dcPjnTqFqmUqwUy0v2AtsL6nMNR2scEy+kBdj74TQuqLeFMo2krnWMABgjQ05+ # ShGRkFbHyzsYMbWrXz7ZZOgh4fqw6mb0GJFSeeAUYHrJGKhEHkDx3VpekdNiijU5 # xrsGYa+gGg28IjPf04grmyP963QHaDAcjHYqhGiNUcMrCrOfcKcL8ofvb54E7/Mx # JOr03ewrwdDstcadn/XG2xcFZ+V4oYIXdzCCF3MGCisGAQQBgjcDAwExghdjMIIX # XwYJKoZIhvcNAQcCoIIXUDCCF0wCAQMxDzANBglghkgBZQMEAgEFADB4BgsqhkiG # 9w0BCRABBKBpBGcwZQIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIEL6 # 9QI8/p7RgdI8REoySp4zAMmwJPPdQYYdn45DYAd8AhEAgFcd+Y0zZvOueXqevX8t # YBgPMjAyNjEwMTUwNzA3NTdaoIITOjCCBu0wggTVoAMCAQICEAhP3DNPfkVO28MP # j/mSGDUwDQYJKoZIhvcNAQELBQAwaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRp # Z2lDZXJ0LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVT # dGFtcGluZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMTAeFw0yNjA4MDUwMDAwMDBa # Fw0zNzExMDQyMzU5NTlaMGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2Vy # dCwgSW5jLjE7MDkGA1UEAxMyRGlnaUNlcnQgU0hBMjU2IFJTQTQwOTYgVGltZXN0 # YW1wIFJlc3BvbmRlciAyMDI2IDEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK # AoICAQC2e6byyf7NSvjUm0xls/04xjD4fAkOkbnGQi7+Wpx81iYxfzViaxSIctuH # 3KSl5YEYpMuFgGsA31N2D9ATMbfZdw5uaAhuWevQKhDdZIB4NnqcfpfpWQXJiQnD # dAElETC+bhSEvNLGbA8DtwUpFMQ4yyYQSPqomT92osQAv6hBi47ATZS6JfVWe6Xx # hF4jJZ3iSAuf2Cros1czRSmWRHqMv9AfGZvp8ygYElhudpQjtcPpwoOl6QrZJUyV # 3iINvN4cO05prGV0fkjG426xDr2d3z9lcSIHkdvGPdGUrXdxfVbgOUVcp2/8ISEz # wKPW++Wa+E2ujI91EZtukGWDJ/xZ27k3oHKEXBRGfRTqjOU+jE3ba/5++JSE/7oN # Hnjs5mekExYN96LV/mxUbCKJb8pBNY4r3uD7hEmk/M81XhVgwDA7aMzYC3LZBg9W # Y5BMmbSay5ecmtJuXaB/0nKWmQmVZeqTVDgsmzHP5MQuhAJkiWNuC9MmCg9TZHXb # J2/yLVSov9p16UDTLtT0+aa1vN71fHeu1qMLlLNB3WOB/ADCxr3S/1hxI92Z6jKg # EED/btwIvbfuXkNNhg8MtDg43c4tMZae9FvqMOt/9PvmAxF9TNIsIFB8G6yb36ZJ # ZGUL8N/pL971DyLXcK6HM5PYnH5X+eVtczhCgHCVQCF6XDAlPQIDAQABo4IBlTCC # AZEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUFMljijAu1Er7bpTz5uNAfvXszeIw # HwYDVR0jBBgwFoAU729TSunkBnx6yuKQVvYv1Ensy04wDgYDVR0PAQH/BAQDAgeA # MBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMIMIGVBggrBgEFBQcBAQSBiDCBhTAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMF0GCCsGAQUFBzAChlFo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1l # U3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcnQwXwYDVR0fBFgwVjBUoFKg # UIZOaHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0VGlt # ZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIwMjVDQTEuY3JsMCAGA1UdIAQZMBcwCAYG # Z4EMAQQCMAsGCWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAjcU6YR6dUgrf # mawJgH59KECxa9Ji8sEi2g10CBDaMiqsaxWyW5cwlT/6ZF5sFznazqVsoC85U9dq # LOYqQwst+UQQoNlDHgKRLa3xoc+OReFreFhnTXSG0Vrd2E2CZqUfm+5a+He1MJ/h # +tNLuA+0Zzhn/Fo+FDYAHWZHx4R79ZsfRFYe9UiXpXBDf6DkUo183Y38NYmR/XfD # Yf7YZ+oR9t3flbDwK+hgGMs0gNNp1w9Z2CyOyI5or/sSwomAuNQ0hWC9xoU4stD8 # aWsD7RkcmgVRs6vlIk3zPKQ+ylcheWkMlj+CoVRlFE55pv0ZWCaFt04lwP/rdGHE # 9qEVQZtyRE42ox7oNgC/r+Y4bSlZ3dw9K2x1xLtu6PkPKeLBFjzKigwfqm3Hm+k/ # +lnME8F5kPZTgiy2HLEHklpryqs6QHnPXrRNeIzkAMyylnRN8P0wmirS0WkU+ywp # EWFZ4QNg+9xS43tTuW9x0eXh7NDc1P/sV+zWxHXKH8tFt1ncHdVzqrZaYPyYMLSn # 2TOXajveJW1L3joiQSPsWRGxkbDDW15jERFE4LvjnGu2O9zD1nLJSMdlYZEikl4w # 2w+q4IN/R+TIe0H4ngCI1moJCTbevGH4punIxM1Uoi0nmX3ZK+XbRT01uowE5ViX # WHng0RgsmrX/EdYUo80r3TfMlkD0/YMwgga0MIIEnKADAgECAhANx6xXBf8hmS5A # QyIMOkmGMA0GCSqGSIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxE # aWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMT # GERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0yNTA1MDcwMDAwMDBaFw0zODAx # MTQyMzU5NTlaMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j # LjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcgUlNB # NDA5NiBTSEEyNTYgMjAyNSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK # AoICAQC0eDHTCphBcr48RsAcrHXbo0ZodLRRF51NrY0NlLWZloMsVO1DahGPNRcy # bEKq+RuwOnPhof6pvF4uGjwjqNjfEvUi6wuim5bap+0lgloM2zX4kftn5B1IpYzT # qpyFQ/4Bt0mAxAHeHYNnQxqXmRinvuNgxVBdJkf77S2uPoCj7GH8BLuxBG5AvftB # dsOECS1UkxBvMgEdgkFiDNYiOTx4OtiFcMSkqTtF2hfQz3zQSku2Ws3IfDReb6e3 # mmdglTcaarps0wjUjsZvkgFkriK9tUKJm/s80FiocSk1VYLZlDwFt+cVFBURJg6z # MUjZa/zbCclF83bRVFLeGkuAhHiGPMvSGmhgaTzVyhYn4p0+8y9oHRaQT/aofEnS # 5xLrfxnGpTXiUOeSLsJygoLPp66bkDX1ZlAeSpQl92QOMeRxykvq6gbylsXQskBB # BnGy3tW/AMOMCZIVNSaz7BX8VtYGqLt9MmeOreGPRdtBx3yGOP+rx3rKWDEJlIqL # XvJWnY0v5ydPpOjL6s36czwzsucuoKs7Yk/ehb//Wx+5kMqIMRvUBDx6z1ev+7ps # NOdgJMoiwOrUG2ZdSoQbU2rMkpLiQ6bGRinZbI4OLu9BMIFm1UUl9VnePs6BaaeE # WvjJSjNm2qA+sdFUeEY0qVjPKOWug/G6X5uAiynM7Bu2ayBjUwIDAQABo4IBXTCC # AVkwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU729TSunkBnx6yuKQVvYv # 1Ensy04wHwYDVR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/ # BAQDAgGGMBMGA1UdJQQMMAoGCCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggr # BgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVo # dHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0 # LmNydDBDBgNVHR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20v # RGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjAL # BglghkgBhv1sBwEwDQYJKoZIhvcNAQELBQADggIBABfO+xaAHP4HPRF2cTC9vgvI # tTSmf83Qh8WIGjB/T8ObXAZz8OjuhUxjaaFdleMM0lBryPTQM2qEJPe36zwbSI/m # S83afsl3YTj+IQhQE7jU/kXjjytJgnn0hvrV6hqWGd3rLAUt6vJy9lMDPjTLxLgX # f9r5nWMQwr8Myb9rEVKChHyfpzee5kH0F8HABBgr0UdqirZ7bowe9Vj2AIMD8liy # rukZ2iA/wdG2th9y1IsA0QF8dTXqvcnTmpfeQh35k5zOCPmSNq1UH410ANVko43+ # Cdmu4y81hjajV/gxdEkMx1NKU4uHQcKfZxAvBAKqMVuqte69M9J6A47OvgRaPs+2 # ykgcGV00TYr2Lr3ty9qIijanrUR3anzEwlvzZiiyfTPjLbnFRsjsYg39OlV8cipD # oq7+qNNjqFzeGxcytL5TTLL4ZaoBdqbhOhZ3ZRDUphPvSRmMThi0vw9vODRzW6Ax # nJll38F0cuJG7uEBYTptMSbhdhGQDpOXgpIUsWTjd6xpR6oaQf/DJbg3s6KCLPAl # Z66RzIg9sC+NJpud/v4+7RWsWCiKi9EOLLHfMR2ZyJ/+xhCx9yHbxtl5TPau1j/1 # MIDpMPx0LckTetiSuEtQvLsNz3Qbp7wGWqbIiOWCnb5WqxL3/BAPvIXKUjPSxyZs # q8WhbaM2tszWkPZPubdcMIIFjTCCBHWgAwIBAgIQDpsYjvnQLefv21DiCEAYWjAN # BgkqhkiG9w0BAQwFADBlMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQg # SW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2Vy # dCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMjIwODAxMDAwMDAwWhcNMzExMTA5MjM1 # OTU5WjBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYD # VQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVk # IFJvb3QgRzQwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN67 # 5F1KPDAiMGkz7MKnJS7JIT3yithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaX # bR2rsnnyyhHS5F/WBTxSD1Ifxp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQ # Lt+C8weE5nQ7bXHiLQwb7iDVySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82s # NEBfsXpm7nfISKhmV1efVFiODCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4Da # tpGYQJB5w3jHtrHEtWoYOAMQjdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwh # TNS8rhsDdV14Ztk6MUSaM0C/CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98Fp # iHaYdj1ZXUJ2h4mXaXpI8OCiEhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppE # GSt+wJS00mFt6zPZxd9LBADMfRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+ # 9oCw++hkpjPRiQfhvbfmQ6QYuKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56 # rF+NP8m800ERElvlEFDrMcXKchYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8 # oR7FwI+isX4KJpn15GkvmB0t9dmpsh3lGwIDAQABo4IBOjCCATYwDwYDVR0TAQH/ # BAUwAwEB/zAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wHwYDVR0jBBgw # FoAUReuir/SSy4IxLVGLp6chnfNtyA8wDgYDVR0PAQH/BAQDAgGGMHkGCCsGAQUF # BwEBBG0wazAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMG # CCsGAQUFBzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRB # c3N1cmVkSURSb290Q0EuY3J0MEUGA1UdHwQ+MDwwOqA4oDaGNGh0dHA6Ly9jcmwz # LmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwEQYDVR0g # BAowCDAGBgRVHSAAMA0GCSqGSIb3DQEBDAUAA4IBAQBwoL9DXFXnOF+go3QbPbYW # 1/e/Vwe9mqyhhyzshV6pGrsi+IcaaVQi7aSId229GhT0E0p6Ly23OO/0/4C5+KH3 # 8nLeJLxSA8hO0Cre+i1Wz/n096wwepqLsl7Uz9FDRJtDIeuWcqFItJnLnU+nBgMT # dydE1Od/6Fmo8L8vC6bp8jQ87PcDx4eo0kxAGTVGamlUsLihVo7spNU96LHc/RzY # 9HdaXFSMb++hUD38dglohJ9vytsgjTVgHAIDyyCwrFigDkBjxZgiwbJZ9VVrzyer # bHbObyMt9H5xaiNrIv8SuFQtJ37YOtnwtoeW/VvRXKwYw02fc7cBqZ9Xql4o4rmU # MYIDfDCCA3gCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQs # IEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5n # IFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExAhAIT9wzT35FTtvDD4/5khg1MA0GCWCG # SAFlAwQCAQUAoIHRMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG # 9w0BCQUxDxcNMjYxMDE1MDcwNzU3WjArBgsqhkiG9w0BCRACDDEcMBowGDAWBBRR # 2avaA0lz2E9CZqykgkjms2nEOTAvBgkqhkiG9w0BCQQxIgQgED8IaNkKh69Nlueg # JQKvnNQJlmeSutOKvI/HnrZ9f1YwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgLaCd # p/QTH5/nLbbF5unJZWdVrwQ/HqdCzA0hIOFB6/wwDQYJKoZIhvcNAQEBBQAEggIA # fAhUp+cVmDydkST+QX3PHiWFuOHEqv+iPiPVpbGufK8BHQ0dpSFCq6rCYP0ra6fn # Bv3sGyyYkL6K0UEUQKqAZj3d9AkjzQ+gQwi8b4pjzxiJAXAWjh8HWXNKBbDTzLxp # GTTi+5Gul6qtGcroEHRQk4ai/Q7yWIU+HM4LNm3clVlp4+yJp/auqBc03Zgyf9Cn # BjspZs/CpIS+EIFj14NkBK2ihTPsJimRTKqcgAK7x0VWtjS5n7DbL7KlfcCoYqub # PVAx8QH/H3OkMicU+ZHTpAyLzuXB1MmRjX8Qkm56eNmoiCezGVaJqoYMthJmEnYp # 9g1RsakX1Cm5YnbhnjRg1GZ24odu+gkxdZbreQd/gFvSJG2EmXT8HMbrN3rMP9zf # mmH8RnyMYpBEPduLPlMbxkDDln4ROkjV+00gDCiLgryFUzPOatwRx5de9TeO27GY # HRUwGW8UPyEM2kwqHBHeEdyKWX90cJmskN7YNDqR7yDkAFk4d03R+B3tpZp5y5Rs # 6Jt6XCmPPCI7/Fd1JfWfwr0VUFXGlg1sqEbtUb67KXA4Gwv9hieFr0cMrXpQufcq # 1+6vg2lSPDsjGUmLwRTQrzLoRfhsxgyrA26a2aSMOgSnkILO78NAJ/seDY5ansaM # TbttPQJGdiFmOoun67aW0RHty+0Gw2semTF/WqFIG+4= # SIG # End signature block |