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
# MIIvWwYJKoZIhvcNAQcCoIIvTDCCL0gCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# 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+QHYIlidzJl16tYD3DlF5xefxyQcdEsxghqYMIIalAIBATBs
# 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+V4oYIXdjCCF3IGCisGAQQBgjcDAwExghdiMIIX
# XgYJKoZIhvcNAQcCoIIXTzCCF0sCAQMxDzANBglghkgBZQMEAgEFADB3BgsqhkiG
# 9w0BCRABBKBoBGYwZAIBAQYJYIZIAYb9bAcBMDEwDQYJYIZIAWUDBAIBBQAEIEL6
# 9QI8/p7RgdI8REoySp4zAMmwJPPdQYYdn45DYAd8AhADXqER1yxmKUJ1hr/bZMBR
# GA8yMDI2MTAwODE5MzQyOFqgghM6MIIG7TCCBNWgAwIBAgIQCE/cM09+RU7bww+P
# +ZIYNTANBgkqhkiG9w0BAQsFADBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
# aUNlcnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0
# YW1waW5nIFJTQTQwOTYgU0hBMjU2IDIwMjUgQ0ExMB4XDTI2MDgwNTAwMDAwMFoX
# DTM3MTEwNDIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0
# LCBJbmMuMTswOQYDVQQDEzJEaWdpQ2VydCBTSEEyNTYgUlNBNDA5NiBUaW1lc3Rh
# bXAgUmVzcG9uZGVyIDIwMjYgMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBALZ7pvLJ/s1K+NSbTGWz/TjGMPh8CQ6RucZCLv5anHzWJjF/NWJrFIhy24fc
# pKXlgRiky4WAawDfU3YP0BMxt9l3Dm5oCG5Z69AqEN1kgHg2epx+l+lZBcmJCcN0
# ASURML5uFIS80sZsDwO3BSkUxDjLJhBI+qiZP3aixAC/qEGLjsBNlLol9VZ7pfGE
# XiMlneJIC5/YKuizVzNFKZZEeoy/0B8Zm+nzKBgSWG52lCO1w+nCg6XpCtklTJXe
# Ig283hw7TmmsZXR+SMbjbrEOvZ3fP2VxIgeR28Y90ZStd3F9VuA5RVynb/whITPA
# o9b75Zr4Ta6Mj3URm26QZYMn/FnbuTegcoRcFEZ9FOqM5T6MTdtr/n74lIT/ug0e
# eOzmZ6QTFg33otX+bFRsIolvykE1jive4PuESaT8zzVeFWDAMDtozNgLctkGD1Zj
# kEyZtJrLl5ya0m5doH/ScpaZCZVl6pNUOCybMc/kxC6EAmSJY24L0yYKD1Nkddsn
# b/ItVKi/2nXpQNMu1PT5prW83vV8d67WowuUs0HdY4H8AMLGvdL/WHEj3ZnqMqAQ
# QP9u3Ai9t+5eQ02GDwy0ODjdzi0xlp70W+ow63/0++YDEX1M0iwgUHwbrJvfpklk
# ZQvw3+kv3vUPItdwroczk9icflf55W1zOEKAcJVAIXpcMCU9AgMBAAGjggGVMIIB
# kTAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQUyWOKMC7USvtulPPm40B+9ezN4jAf
# BgNVHSMEGDAWgBTvb1NK6eQGfHrK4pBW9i/USezLTjAOBgNVHQ8BAf8EBAMCB4Aw
# FgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwgZUGCCsGAQUFBwEBBIGIMIGFMCQGCCsG
# AQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXQYIKwYBBQUHMAKGUWh0
# dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRpbWVT
# dGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNydDBfBgNVHR8EWDBWMFSgUqBQ
# hk5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRUaW1l
# U3RhbXBpbmdSU0E0MDk2U0hBMjU2MjAyNUNBMS5jcmwwIAYDVR0gBBkwFzAIBgZn
# gQwBBAIwCwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQCNxTphHp1SCt+Z
# rAmAfn0oQLFr0mLywSLaDXQIENoyKqxrFbJblzCVP/pkXmwXOdrOpWygLzlT12os
# 5ipDCy35RBCg2UMeApEtrfGhz45F4Wt4WGdNdIbRWt3YTYJmpR+b7lr4d7Uwn+H6
# 00u4D7RnOGf8Wj4UNgAdZkfHhHv1mx9EVh71SJelcEN/oORSjXzdjfw1iZH9d8Nh
# /thn6hH23d+VsPAr6GAYyzSA02nXD1nYLI7Ijmiv+xLCiYC41DSFYL3GhTiy0Pxp
# awPtGRyaBVGzq+UiTfM8pD7KVyF5aQyWP4KhVGUUTnmm/RlYJoW3TiXA/+t0YcT2
# oRVBm3JETjajHug2AL+v5jhtKVnd3D0rbHXEu27o+Q8p4sEWPMqKDB+qbceb6T/6
# WcwTwXmQ9lOCLLYcsQeSWmvKqzpAec9etE14jOQAzLKWdE3w/TCaKtLRaRT7LCkR
# YVnhA2D73FLje1O5b3HR5eHs0NzU/+xX7NbEdcofy0W3Wdwd1XOqtlpg/JgwtKfZ
# M5dqO94lbUveOiJBI+xZEbGRsMNbXmMREUTgu+Oca7Y73MPWcslIx2VhkSKSXjDb
# D6rgg39H5Mh7QfieAIjWagkJNt68Yfim6cjEzVSiLSeZfdkr5dtFPTW6jATlWJdY
# eeDRGCyatf8R1hSjzSvdN8yWQPT9gzCCBrQwggScoAMCAQICEA3HrFcF/yGZLkBD
# Igw6SYYwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERp
# Z2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMY
# RGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTI1MDUwNzAwMDAwMFoXDTM4MDEx
# NDIzNTk1OVowaTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMu
# MUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGluZyBSU0E0
# MDk2IFNIQTI1NiAyMDI1IENBMTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC
# ggIBALR4MdMKmEFyvjxGwBysddujRmh0tFEXnU2tjQ2UtZmWgyxU7UNqEY81FzJs
# Qqr5G7A6c+Gh/qm8Xi4aPCOo2N8S9SLrC6Kbltqn7SWCWgzbNfiR+2fkHUiljNOq
# nIVD/gG3SYDEAd4dg2dDGpeZGKe+42DFUF0mR/vtLa4+gKPsYfwEu7EEbkC9+0F2
# w4QJLVSTEG8yAR2CQWIM1iI5PHg62IVwxKSpO0XaF9DPfNBKS7Zazch8NF5vp7ea
# Z2CVNxpqumzTCNSOxm+SAWSuIr21Qomb+zzQWKhxKTVVgtmUPAW35xUUFREmDrMx
# SNlr/NsJyUXzdtFUUt4aS4CEeIY8y9IaaGBpPNXKFifinT7zL2gdFpBP9qh8SdLn
# Eut/GcalNeJQ55IuwnKCgs+nrpuQNfVmUB5KlCX3ZA4x5HHKS+rqBvKWxdCyQEEG
# cbLe1b8Aw4wJkhU1JrPsFfxW1gaou30yZ46t4Y9F20HHfIY4/6vHespYMQmUiote
# 8ladjS/nJ0+k6MvqzfpzPDOy5y6gqztiT96Fv/9bH7mQyogxG9QEPHrPV6/7umw0
# 52AkyiLA6tQbZl1KhBtTasySkuJDpsZGKdlsjg4u70EwgWbVRSX1Wd4+zoFpp4Ra
# +MlKM2baoD6x0VR4RjSpWM8o5a6D8bpfm4CLKczsG7ZrIGNTAgMBAAGjggFdMIIB
# WTASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTvb1NK6eQGfHrK4pBW9i/U
# SezLTjAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8E
# BAMCAYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsG
# AQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0
# dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQu
# Y3J0MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsG
# CWCGSAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAF877FoAc/gc9EXZxML2+C8i1
# NKZ/zdCHxYgaMH9Pw5tcBnPw6O6FTGNpoV2V4wzSUGvI9NAzaoQk97frPBtIj+ZL
# zdp+yXdhOP4hCFATuNT+ReOPK0mCefSG+tXqGpYZ3essBS3q8nL2UwM+NMvEuBd/
# 2vmdYxDCvwzJv2sRUoKEfJ+nN57mQfQXwcAEGCvRR2qKtntujB71WPYAgwPyWLKu
# 6RnaID/B0ba2H3LUiwDRAXx1Neq9ydOal95CHfmTnM4I+ZI2rVQfjXQA1WSjjf4J
# 2a7jLzWGNqNX+DF0SQzHU0pTi4dBwp9nEC8EAqoxW6q17r0z0noDjs6+BFo+z7bK
# SBwZXTRNivYuve3L2oiKNqetRHdqfMTCW/NmKLJ9M+MtucVGyOxiDf06VXxyKkOi
# rv6o02OoXN4bFzK0vlNMsvhlqgF2puE6FndlENSmE+9JGYxOGLS/D284NHNboDGc
# mWXfwXRy4kbu4QFhOm0xJuF2EZAOk5eCkhSxZON3rGlHqhpB/8MluDezooIs8CVn
# rpHMiD2wL40mm53+/j7tFaxYKIqL0Q4ssd8xHZnIn/7GELH3IdvG2XlM9q7WP/Uw
# gOkw/HQtyRN62JK4S1C8uw3PdBunvAZapsiI5YKdvlarEvf8EA+8hcpSM9LHJmyr
# xaFtoza2zNaQ9k+5t1wwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0G
# CSqGSIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0
# IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5
# NTlaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNV
# BAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQg
# Um9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvk
# XUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdt
# HauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu
# 34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0
# QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2
# kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM
# 1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmI
# dph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZ
# K37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72
# gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqs
# X40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyh
# HsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8E
# BTADAQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAW
# gBRF66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUH
# AQEEbTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYI
# KwYBBQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFz
# c3VyZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMu
# ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAE
# CjAIMAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX
# 979XB72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offy
# ct4kvFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3
# J0TU53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0
# d1pcVIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6ts
# ds5vIy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQx
# ggN8MIIDeAIBATB9MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwg
# SW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1lU3RhbXBpbmcg
# UlNBNDA5NiBTSEEyNTYgMjAyNSBDQTECEAhP3DNPfkVO28MPj/mSGDUwDQYJYIZI
# AWUDBAIBBQCggdEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3
# DQEJBTEPFw0yNjEwMDgxOTM0MjhaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFFHZ
# q9oDSXPYT0JmrKSCSOazacQ5MC8GCSqGSIb3DQEJBDEiBCCxUJU62CkJhXonAEI8
# 2PiOuVC5wrjLioJzyURkH9dXrzA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCAtoJ2n
# 9BMfn+cttsXm6cllZ1WvBD8ep0LMDSEg4UHr/DANBgkqhkiG9w0BAQEFAASCAgAN
# fsiMfS/UBqxQoOIX+NIqATdC+Wujtgx//f0470qOt+DSQFyDKtPYAFbGQJYTtgdM
# cwDZJUHOIAnLcmqst3rBUQLVGc8zUpzbCUudsZHno5eOEUneOOxa2ObkggZusMVV
# rLfGuR57Iwbaxb7NPyBvTxKLoL/ZXG0Q35uMpGLAS2Kv/SjrFe30hxMQIe+gsiw2
# 1ltDX7kQ9OnrIBGZoTB+ycBFRJ2avLp4MiSUiTWHGiV5TmRzclck1ANW54D2ku0a
# P88xTmYT2mYYnRgSHa6QnCoXqD79wewCKaKLF8MfcGTymJ2qQ3hPr9EkmvwWlOQM
# Grq77rc0Sz1hiw7IxPc8xMY75RFjjn5jEKMGGm5buRGwf77G+zPBOSAfHVq6YW5Y
# Hmbh1oabcjYZRVI2ZA78OrGVkCZnxB4XKDAYKac0bz9yNCw8Vn9PCl6pHmwy2BNG
# oBBktTj3VjR0/b4ypyo8kW+v7F+OYGz4N/JZG0d5OCdNpOP/OZu6HYQeSjddxd+9
# WTfaIYvppXp82dwEwWto+JbXRt35idgvQB9JNpnmam7cSn4MeBKx/bEI0SxRAyfo
# Jnnq1tEQqzAO+mw9BI6CTEtIejDaCNc8i5JwSYCTcSV+g1NLOvOnaVzmLyBJ2qMS
# 1SBr8pML8YpbVshl/7KavMxwu3Ku/FYwrFmnP6z3Dg==
# SIG # End signature block