Client/Convert-PemToPfx.ps1

function Convert-PemToPfx {
<#
.ExternalHelp PSPKI.Help.xml
#>

[OutputType('[System.Security.Cryptography.X509Certificates.X509Certificate2]')]
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, Position = 0)]
        [Alias('CertificatePath', 'CertPath')]
        [string]$InputPath,
        [string]$KeyPath,
        [string]$OutputPath,
        [SysadminsLV.PKI.Cryptography.X509Certificates.X509KeySpecFlags]$KeySpec = "AT_KEYEXCHANGE",
        [Security.SecureString]$Password,
        [string]$ProviderName = "Microsoft Software Key Storage Provider",
        [Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = "CurrentUser",
        [switch]$Install
    )
    if ($PSBoundParameters["Verbose"]) {$VerbosePreference = "continue"}
    if ($PSBoundParameters["Debug"]) {$DebugPreference = "continue"}
    $ErrorActionPreference = "Stop"

    # PSCore needs extra assembly import
    if ($PsIsCore) {
        Add-Type -AssemblyName "System.Security.Cryptography.X509Certificates"
    }

    # global variables
    Write-Verbose "Determining key storage flags..."
    $KeyStorageFlags = [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable
    if ($Install) {
        if ($StoreLocation -eq "CurrentUser") {
            $KeyStorageFlags = $KeyStorageFlags -bor [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::UserKeySet
        } else {
            $KeyStorageFlags = $KeyStorageFlags -bor [Security.Cryptography.X509Certificates.X509KeyStorageFlags]::MachineKeySet
        }
    } else {
        $KeyStorageFlags = $KeyStorageFlags -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::EphemeralKeySet
    }
    Write-Verbose "Resulting storage flags: $KeyStorageFlags"

    $script:AlgFamily = ""
    [System.Collections.Generic.List[object]]$Disposables = @()

    # returns: X509Certificate2
    function __extractCert([string]$Text) {
        Write-Verbose "Reading certificate.."
        if ($Text -match "(?msx).*-{5}BEGIN\sCERTIFICATE-{5}(.+)-{5}END\sCERTIFICATE-{5}") {
            $CertRawData = [Convert]::FromBase64String($matches[1])
            $Cert = New-Object Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList (,$CertRawData)
            Write-Verbose "Public key algorithm: $($Cert.PublicKey.Oid.FriendlyName) ($($Cert.PublicKey.Oid.Value))"
            switch ($Cert.PublicKey.Oid.Value) {
                $([SysadminsLV.PKI.Cryptography.AlgorithmOid]::RSA) { $script:AlgFamily = "RSA" }
                $([SysadminsLV.PKI.Cryptography.AlgorithmOid]::ECC) { $script:AlgFamily = "ECC" }
            }

            $Cert
        } else {
            throw "Missing certificate file."
        }
    }
    # returns: AsymmetricKey (RSACng or ECDsaCng)
    function __extractPrivateKey([string]$Text) {
        Write-Verbose "Reading private key..."
        $bin = if ($Text -match "(?msx).*-{5}BEGIN\sPRIVATE\sKEY-{5}(.+)-{5}END\sPRIVATE\sKEY-{5}") {
            Write-Verbose "Found private key in PKCS#8 format."
            [convert]::FromBase64String($matches[1])
        } elseif ($Text -match "(?msx).*-{5}BEGIN\sRSA\sPRIVATE\sKEY-{5}(.+)-{5}END\sRSA\sPRIVATE\sKEY-{5}") {
            Write-Verbose "Found RSA private key in PKCS#1 format."
            [convert]::FromBase64String($matches[1])
        }  else {
            throw "The data is invalid."
        }
        if ($AlgFamily -eq "RSA") {
            Write-Verbose "Converting RSA PKCS#1 to PKCS#8..."
            # RSA can be in PKCS#1 format, which is not supported by CngKey.
            $rsa = New-Object SysadminsLV.PKI.Cryptography.RsaPrivateKey (,$bin)
            $bin = $rsa.Export("Pkcs8")
            $rsa.Dispose()
        }

        Write-Verbose "Using provider: $ProviderName"
        $prov = [System.Security.Cryptography.CngProvider]::new($ProviderName)
        $blobType = [System.Security.Cryptography.CngKeyBlobFormat]::Pkcs8PrivateBlob
        # make it exportable
        $cngProp = New-Object System.Security.Cryptography.CngProperty("Export Policy", [BitConverter]::GetBytes(3), "None")
        $cng = [System.Security.Cryptography.CngKey]::Import($bin, $blobType, $prov)
        $Disposables.Add($cng)
        $cng.SetProperty($cngProp)

        switch ($AlgFamily) {
            "RSA" {
                New-Object System.Security.Cryptography.RSACng $cng
            }
            "ECC" {
                New-Object System.Security.Cryptography.ECDsaCng $cng
                
            }
            default {
                throw "Specified algorithm is not supported"
            }
        }
    }
    # returns: X509Certificate2 with associated private key
    function __associatePrivateKey($Cert, $AsymmetricKey) {
        Write-Verbose "Merging public certificate with private key..."
        switch ($AlgFamily) {
            "RSA" {
                if ($csp.IsLegacy) {
                    $NewCert = New-Object Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList (,$Cert.RawData)
                    $cspParams = New-Object System.Security.Cryptography.CspParameters $csp.Type, $ProviderName, ("pspki-" + [guid]::NewGuid())
                    $cspParams.KeyNumber = [int]$KeySpec
                    if ($Install) {
                        if ($StoreLocation -eq "LocalMachine") {
                            $cspParams.Flags = 1
                        }
                    } else {
                        #$cspParams.Flags = 128
                    }
                    $legacyRsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider $cspParams
                    $legacyRsa.ImportParameters($AsymmetricKey.ExportParameters($true))
                    $NewCert.PrivateKey = $legacyRsa

                    $NewCert
                } else {
                    [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::CopyWithPrivateKey($Cert, $AsymmetricKey)
                }
            }
            "ECC" {[System.Security.Cryptography.X509Certificates.ECDsaCertificateExtensions]::CopyWithPrivateKey($Cert, $AsymmetricKey)}
        }
        Write-Verbose "Public certificate and private key are successfully merged."
    }

    function __duplicateCertWithKey($CertWithKey) {
        $PfxBytes = $CertWithKey.Export("pfx")
        New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $PfxBytes, "", $KeyStorageFlags
    }
    function __installCert($CertWithKey) {
        if (!$Install) {
            $CertWithKey
            return
        }

        Write-Verbose "Installing certificate to certificate store: $StoreLocation"
        # $CertWithKey cert has ephemeral key which cannot be installed into cert store as is.
        # so export it into PFX in memory and re-import back with storage flags
        $NewCert = __duplicateCertWithKey $CertWithKey
        # dispose ephemeral cert received from params, we have a persisted cert to return
        $CertWithKey.Dispose()

        $store = New-Object Security.Cryptography.X509Certificates.X509Store "my", $StoreLocation
        $store.Open("ReadWrite")
        $Disposables.Add($store)
        $store.Add($NewCert)
        $store.Close()
        Write-Verbose "Certificate is installed."
        # dispose this temporary cert
        $NewCert
    }
    function __exportPfx($CertWithKey) {
        if ([string]::IsNullOrWhiteSpace($OutputPath)) {
            return
        }
        Write-Verbose "Saving PFX to a file: $OutputPath"
        if (!$Password) {
            $Password = Read-Host -Prompt "Enter PFX password" -AsSecureString
        }
        $pfxBytes = $CertWithKey.Export("pfx", $Password)
        Export-Binary $OutputPath $pfxBytes
        Write-Verbose "PFX is saved."
    }

    # Determine CSP
    $csp = Get-CryptographicServiceProvider -Name $ProviderName
    if (!$csp) {
        throw "Specified provider name is invalid."
    }

    # parse content
    $Text = Get-Content -Path $InputPath -Raw -ErrorAction Stop
    Write-Debug "Extracting certificate information..."
    $Cert = __extractCert $Text
    if ($KeyPath) {
        $Text = Get-Content -Path $KeyPath -Raw -ErrorAction Stop
    }
    $PrivateKey = __extractPrivateKey $Text
    $Disposables.Add($PrivateKey)

    $PfxCert = __associatePrivateKey $Cert $PrivateKey
    __exportPfx $PfxCert
    $PfxCert = __installCert $PfxCert
    # release unmanaged resources
    $Disposables | %{$_.Dispose()}

    $PfxCert
}
# SIG # Begin signature block
# MIIvDQYJKoZIhvcNAQcCoIIu/jCCLvoCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCcuch2oPSDeNbh
# ASduY8N18YyXwEThMXZLjtuS4vn0EKCCE5kwggWQMIIDeKADAgECAhAFmxtXno4h
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
# eE4wggdNMIIFNaADAgECAhAObby6tbZ0sFYtkajicQ8aMA0GCSqGSIb3DQEBDAUA
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
# ODQgMjAyMSBDQTEwHhcNMjYwMjA2MDAwMDAwWhcNMjkwMjI3MjM1OTU5WjBVMQsw
# CQYDVQQGEwJMVjEOMAwGA1UEBwwFUsSrZ2ExGjAYBgNVBAoMEUlLICJTeXNhZG1p
# bnMgTFYiMRowGAYDVQQDDBFJSyAiU3lzYWRtaW5zIExWIjCCAiIwDQYJKoZIhvcN
# AQEBBQADggIPADCCAgoCggIBAKwGGasrfMfwdc37W1d3/vgvVZ5Wgfmk8Rd4sb3e
# f1vZ4D2kA0sLNU1AwbwfLjnnRT0brMbrI2y/MSt10oqOFSnR7nw9AkhW/1PVZS9l
# rxRYX/g/RjO+lG5TnxRTsaWUiw7clvhRipxBgWJuxky8MuUD/eTyMLu1rn6He7p/
# cp4kg6nu6ZsaPxsEY9EBTAdXzlyYS/U3NJMk8GjrLdVAlZpoONXnoB9yChIly409
# yh2xWczWIUv9ku2SFrZbB6l3YhY2inDAPKpAKyCd0xeQPKSeRoC+JXAnA1dSSRJ9
# 557LFFBfnR4XFh4E1OHuKRfTk18UjfwQqdF4cdmH5I4rR5Pqhy2shwBP8XrT26em
# OhpfUAWVfJWIWcoQPq9JV/3dr8tPZgCW97dZpmu0/q3TlHS/sPASNO279diN2o/f
# H0fsNPBzFSe85BCtUk3muyEhVHU3R//Lg1MSFB0BRFeAafhlsm/VvhIQ8tQvb7it
# a7UB7gacdB795+XhpY0MLc0fFsrSTENxXeH5N7sIlNa3zao4l3IiNmR6lwvm6IMf
# L8aRBPWg6PicLK2sXa10PDQLxee533dHuuvvpaVdpyvcEpaCHlaSKxeFhwur339D
# yuaWQ37rospo/pS8oaG05edihaF7O4LRiqeVDx1RL9Yd4uPH02J284seLMKTZ7wa
# Tle1AgMBAAGjggIDMIIB/zAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfRO
# QjAdBgNVHQ4EFgQU8Tdc7JEZ0WMWKjWL7tfz8hanUtgwPgYDVR0gBDcwNTAzBgZn
# gQwBBAEwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5kaWdpY2VydC5jb20vQ1BT
# MA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzCBtQYDVR0fBIGt
# MIGqMFOgUaBPhk1odHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVz
# dGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0ExLmNybDBToFGgT4ZN
# aHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNp
# Z25pbmdSU0E0MDk2U0hBMzg0MjAyMUNBMS5jcmwwgZQGCCsGAQUFBwEBBIGHMIGE
# MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUH
# MAKGUGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRH
# NENvZGVTaWduaW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAkGA1UdEwQCMAAw
# DQYJKoZIhvcNAQEMBQADggIBAIRcHe3iWZ4t2G3pttGtw01+okbWPPachOEYxiGL
# 2Gp7DBP3Pvfw1qEHnwULtPs0hBG/Rd8+TtKkTpyZO0E2EfIYfe/EVmXjr+6jrOnU
# beRiVDMahp7ym37FhrRPzzDEihFmyzz/Ec8YmE4oeEH+0ZTKT+7F7VThVmVGWQ6x
# YIGqSL3k6/Af1P1gdK+1MdQtB/E1mg+7aDqiOxCRRsfwXwLjB+LSi0GflBQZeeHf
# pvogtHPWM6yMEZVZkY6sxF9jo9ewhVc9DJB86KQSxJ14vkM0OAu7Q83L8N+exia2
# XuC3C+7nuVdHB7+HHu3z/Cz1aftWuHLnVRYrhJZCZ+46YvjRFQxf+GIjqyopBfDh
# DX6Oq4GlOSrJPnSnyRgNu4Mr4nOWJ2LuTzFmgJu+GmeHSLH+U2uudNdagjkjRnWQ
# TOZ4hdnqG0WlhMazHMf0RGx8s3DRIX9kjttDQ3RbYUJRqMq9KVf5K+CdNYKRyqET
# 4tn+ixidYWazVTgsaQi1ZxmdxmS784F5CQxLlg87xWIUlWyIA2fWWnqePxGaLZrB
# pDr0KkKT6gF3z2+Bs9q3o1sOAvjP7SvaBLJHuBmC5oeKnbZa+iuVEJFQShEdfl28
# Khk3jA3RU0cOK+CJMup5FXo4mdydO2ItLKZ62vC4f2m3IinXfyh7MfUyZQ0heZMi
# Who4MYIayjCCGsYCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl
# cnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWdu
# aW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAObby6tbZ0sFYtkajicQ8aMA0G
# CWCGSAFlAwQCAQUAoIGEMBgGCisGAQQBgjcCAQwxCjAIoAKAAKECgAAwGQYJKoZI
# hvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcC
# ARUwLwYJKoZIhvcNAQkEMSIEIOecuaTCX9Sv2/2C0UI8LDUXVLiQVN6UBg45nxei
# DIYaMA0GCSqGSIb3DQEBAQUABIICAFl4wQkQnU5sNVdW0GdmDaeakf0eSGc/PO9D
# hNjFZwW3YwdWNsD36QYQEYaYso6X+LaHLwUMI9sVBPhW01YLWvOoNiBBHk1BiEUz
# 7ocXT1qmdFUY0j9wzKpxq+dNOOAm07iBp5g/ruSaWZO+7CKHWciw2h/WTs+QxR8v
# MIg1nd1vxmMYUzRS1Eft733w8M8bAzq0DuusKc7hKxELGnYIVfCZHS/mgoipDdrb
# GJeOrZPVWZSCSZJwagEdVxwriHUHe/L0WP2fEd53EZyg2i5JT89Wt8tKPeVR0Plw
# o3NL/ix6tym8pVUV3TrxLgMEqgmAWUM0C33N0VRyC+PSRcV6J4JpegLHf1fxFAUH
# nhmHoOWmv4ncWRY6+95r8tYGuq/q8dPh1tKJdzmBm4NZfKOy+jNJVBYS6lUpIiyg
# Abiq3ST+MPMJVwEo3J5DMxObuDHyMNXboCtR5Nnf1cqYMiqO8t57KOC5k5VOsFSB
# 7dgD0dT0j7ICam2Y0CZ8mH3anNph2r1QlasfMc1IwBAOhlyGsscXj7oQvG/8H0TV
# +Cn8btGtnVAF6o5FXWQYjtklpORML1RpwXSOnIcTwza4ikuDG5eZOOKC2/PcIjnV
# X7i/zJgIR5/l29q4IS2vH8SxQcxmZLY3f+zrWmdXJyjwgRu76dHmOw5+hkxq/lyz
# XwsUR/PUoYIXlzCCF5MGCisGAQQBgjcDAwExgheDMIIXfwYJKoZIhvcNAQcCoIIX
# cDCCF2wCAQMxDzANBglghkgBZQMEAgIFADCBhwYLKoZIhvcNAQkQAQSgeAR2MHQC
# AQEGCWCGSAGG/WwHATBBMA0GCWCGSAFlAwQCAgUABDBnhQV+hjQnWnV+JrYnH5M3
# uaJq9pCfxztiB3+s9UCf4WQ9TwJJPQAOUEHQckZe6lICECLGtuUPxvw3ndZnwSGE
# Th0YDzIwMjYwMzMwMTExMTA5WqCCEzowggbtMIIE1aADAgECAhAMIENJ+dD3WfuY
# LeQIG4h7MA0GCSqGSIb3DQEBDAUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5E
# aWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBUaW1l
# U3RhbXBpbmcgUlNBNDA5NiBTSEEyNTYgMjAyNSBDQTEwHhcNMjUwNjA0MDAwMDAw
# WhcNMzYwOTAzMjM1OTU5WjBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl
# cnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFNIQTM4NCBSU0E0MDk2IFRpbWVz
# dGFtcCBSZXNwb25kZXIgMjAyNSAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEA2zlS+4t0t+XJDVHY+vNJxpv794sM3O4UQycmKRXmYLs+YRfztyl8QJ7n
# /UqxNTKWmjdFDWGv43+a2oiJ41yxOe0sLoFx8F1az2JRTZc7dhAxbne+byd5bf2S
# EZlCruGxxWSqbpUY6dAGRCCyBOaiFaoXhkn+L15efcomDSrTnA5Vgd9pvMO+7bM+
# tSW4JzAiIbO2mIPyCEdKYscmPl+YBuenSP7NJw9icL1tWpn61uM6WyUNv4RcyBAz
# +NvJbNf5kTM7F46cvBwp0lZYisZR985y5sYj4e4yUBbPBxyrT5aNMZ++5tis8GDm
# HCpqyVLQ4eLHwpim5iwR49TREfETtlEFORWTkJ2hOO1zzVAWs6jtdep12VtFZoQO
# hIwdUfPHSsAw39xFVevFEFf2u+DVr1sOV7JACY+xcG8hWIeqPGVUwkiyBRUTgA7H
# eAxJb0iQl4GDBC6ZBA4wGN/ahMxF4fuJsOs1zwkPBSnXmHkm18HwHgIPKk287dMI
# chZyjm7zGcCYZ4bisoUYWL9oTga9JCfFMTc9yl26XDB0zl9rdSwviOmaYSlaRanF
# 84oxAYnqgBy6Z89ykPgWnb7SRi31NyP359Whok+36fkyxTPjSrCWvMK7pzbRg8tf
# IRlUnxl7G5bIrkPqMbD9zJoB79MHFgLr5ljU7rrcLwy+cEfpzFMCAwEAAaOCAZUw
# ggGRMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFFWeuednyJEQSbQ2Uo15tyTFPy34
# MB8GA1UdIwQYMBaAFO9vU0rp5AZ8esrikFb2L9RJ7MtOMA4GA1UdDwEB/wQEAwIH
# gDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDCBlQYIKwYBBQUHAQEEgYgwgYUwJAYI
# KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBdBggrBgEFBQcwAoZR
# aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0VGlt
# ZVN0YW1waW5nUlNBNDA5NlNIQTI1NjIwMjVDQTEuY3J0MF8GA1UdHwRYMFYwVKBS
# oFCGTmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNFRp
# bWVTdGFtcGluZ1JTQTQwOTZTSEEyNTYyMDI1Q0ExLmNybDAgBgNVHSAEGTAXMAgG
# BmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZIhvcNAQEMBQADggIBABt+CySH2Alq
# xUHnUWnZJI7rpdAqo0PcikyV48Ltk5QWFgxpHP9WtjR3lskEAOk3TszmuNyMid7V
# uxHlQJl4KcdTr5cQ2YLy+l560peBgM7kA4HCJqGqdQdzjXyrlg3YCdfnjs9w/7BO
# 8xUmlAaq/D+PTZZO+Mnxa3/IoyYsF+L9gWX4VJxZLljVs5JKmpSonnysMYv7Caqk
# QpBDmJWU2F68mLLZXfU0wXbDy9QQTskgcHviyQDeB1l6jl/WwOQiSNTNafYQUR2Z
# sJ5rPJu1NPzO1htKwdiUjWenHwq5BRK1BR7+D+TwG97UHX4V0W+JvFZp8z3d3G5s
# A7Pt9qO5/6AWZ+0yf8nN58D+HAAShHmny25t6W7qF6VSRZCIpGr8hbAjfbBhO4MY
# 8G2U9zwVKp6SljuKknxd2buihO33dioCGsB6trX++xQKf4QlYSggFvD9ZWSG4ysJ
# PYOx+hbsBTEONFtr99x6OgJnnyVkDoudIn+gmV+Bq+a2G++BLU5AXOVclExpuoUQ
# XUZF5p3sUrd21QjF9Ra0x4RD02gS4XwgzN+tvuY+tjhPICwXmH3ERL+fPIoxZT0X
# gwVP+17UqUbi5Zpe4YdadG5WjCTBvtmlM4JVovGYRvyAyfmYJJx0/0T+qK05wRJp
# g4q81vOKuCQPaE9H99JCVvfCDBm4KjrEMIIGtDCCBJygAwIBAgIQDcesVwX/IZku
# QEMiDDpJhjANBgkqhkiG9w0BAQsFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM
# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQD
# ExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwHhcNMjUwNTA3MDAwMDAwWhcNMzgw
# MTE0MjM1OTU5WjBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu
# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgVGltZVN0YW1waW5nIFJT
# QTQwOTYgU0hBMjU2IDIwMjUgQ0ExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAtHgx0wqYQXK+PEbAHKx126NGaHS0URedTa2NDZS1mZaDLFTtQ2oRjzUX
# MmxCqvkbsDpz4aH+qbxeLho8I6jY3xL1IusLopuW2qftJYJaDNs1+JH7Z+QdSKWM
# 06qchUP+AbdJgMQB3h2DZ0Mal5kYp77jYMVQXSZH++0trj6Ao+xh/AS7sQRuQL37
# QXbDhAktVJMQbzIBHYJBYgzWIjk8eDrYhXDEpKk7RdoX0M980EpLtlrNyHw0Xm+n
# t5pnYJU3Gmq6bNMI1I7Gb5IBZK4ivbVCiZv7PNBYqHEpNVWC2ZQ8BbfnFRQVESYO
# szFI2Wv82wnJRfN20VRS3hpLgIR4hjzL0hpoYGk81coWJ+KdPvMvaB0WkE/2qHxJ
# 0ucS638ZxqU14lDnki7CcoKCz6eum5A19WZQHkqUJfdkDjHkccpL6uoG8pbF0LJA
# QQZxst7VvwDDjAmSFTUms+wV/FbWBqi7fTJnjq3hj0XbQcd8hjj/q8d6ylgxCZSK
# i17yVp2NL+cnT6Toy+rN+nM8M7LnLqCrO2JP3oW//1sfuZDKiDEb1AQ8es9Xr/u6
# bDTnYCTKIsDq1BtmXUqEG1NqzJKS4kOmxkYp2WyODi7vQTCBZtVFJfVZ3j7OgWmn
# hFr4yUozZtqgPrHRVHhGNKlYzyjlroPxul+bgIspzOwbtmsgY1MCAwEAAaOCAV0w
# ggFZMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFO9vU0rp5AZ8esrikFb2
# L9RJ7MtOMB8GA1UdIwQYMBaAFOzX44LScV1kTN8uZz/nupiuHA9PMA4GA1UdDwEB
# /wQEAwIBhjATBgNVHSUEDDAKBggrBgEFBQcDCDB3BggrBgEFBQcBAQRrMGkwJAYI
# KwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1
# aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZFJvb3RH
# NC5jcnQwQwYDVR0fBDwwOjA4oDagNIYyaHR0cDovL2NybDMuZGlnaWNlcnQuY29t
# L0RpZ2lDZXJ0VHJ1c3RlZFJvb3RHNC5jcmwwIAYDVR0gBBkwFzAIBgZngQwBBAIw
# CwYJYIZIAYb9bAcBMA0GCSqGSIb3DQEBCwUAA4ICAQAXzvsWgBz+Bz0RdnEwvb4L
# yLU0pn/N0IfFiBowf0/Dm1wGc/Do7oVMY2mhXZXjDNJQa8j00DNqhCT3t+s8G0iP
# 5kvN2n7Jd2E4/iEIUBO41P5F448rSYJ59Ib61eoalhnd6ywFLerycvZTAz40y8S4
# F3/a+Z1jEMK/DMm/axFSgoR8n6c3nuZB9BfBwAQYK9FHaoq2e26MHvVY9gCDA/JY
# sq7pGdogP8HRtrYfctSLANEBfHU16r3J05qX3kId+ZOczgj5kjatVB+NdADVZKON
# /gnZruMvNYY2o1f4MXRJDMdTSlOLh0HCn2cQLwQCqjFbqrXuvTPSegOOzr4EWj7P
# tspIHBldNE2K9i697cvaiIo2p61Ed2p8xMJb82Yosn0z4y25xUbI7GIN/TpVfHIq
# Q6Ku/qjTY6hc3hsXMrS+U0yy+GWqAXam4ToWd2UQ1KYT70kZjE4YtL8Pbzg0c1ug
# MZyZZd/BdHLiRu7hAWE6bTEm4XYRkA6Tl4KSFLFk43esaUeqGkH/wyW4N7Oigizw
# JWeukcyIPbAvjSabnf7+Pu0VrFgoiovRDiyx3zEdmcif/sYQsfch28bZeUz2rtY/
# 9TCA6TD8dC3JE3rYkrhLULy7Dc90G6e8BlqmyIjlgp2+VqsS9/wQD7yFylIz0scm
# bKvFoW2jNrbM1pD2T7m3XDCCBY0wggR1oAMCAQICEA6bGI750C3n79tQ4ghAGFow
# DQYJKoZIhvcNAQEMBQAwZTELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0
# IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEkMCIGA1UEAxMbRGlnaUNl
# cnQgQXNzdXJlZCBJRCBSb290IENBMB4XDTIyMDgwMTAwMDAwMFoXDTMxMTEwOTIz
# NTk1OVowYjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcG
# A1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UEAxMYRGlnaUNlcnQgVHJ1c3Rl
# ZCBSb290IEc0MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv+aQc2je
# u+RdSjwwIjBpM+zCpyUuySE98orYWcLhKac9WKt2ms2uexuEDcQwH/MbpDgW61bG
# l20dq7J58soR0uRf1gU8Ug9SH8aeFaV+vp+pVxZZVXKvaJNwwrK6dZlqczKU0RBE
# EC7fgvMHhOZ0O21x4i0MG+4g1ckgHWMpLc7sXk7Ik/ghYZs06wXGXuxbGrzryc/N
# rDRAX7F6Zu53yEioZldXn1RYjgwrt0+nMNlW7sp7XeOtyU9e5TXnMcvak17cjo+A
# 2raRmECQecN4x7axxLVqGDgDEI3Y1DekLgV9iPWCPhCRcKtVgkEy19sEcypukQF8
# IUzUvK4bA3VdeGbZOjFEmjNAvwjXWkmkwuapoGfdpCe8oU85tRFYF/ckXEaPZPfB
# aYh2mHY9WV1CdoeJl2l6SPDgohIbZpp0yt5LHucOY67m1O+SkjqePdwA5EUlibaa
# RBkrfsCUtNJhbesz2cXfSwQAzH0clcOP9yGyshG3u3/y1YxwLEFgqrFjGESVGnZi
# fvaAsPvoZKYz0YkH4b235kOkGLimdwHhD5QMIR2yVCkliWzlDlJRR3S+Jqy2QXXe
# eqxfjT/JvNNBERJb5RBQ6zHFynIWIgnffEx1P2PsIV/EIFFrb7GrhotPwtZFX50g
# /KEexcCPorF+CiaZ9eRpL5gdLfXZqbId5RsCAwEAAaOCATowggE2MA8GA1UdEwEB
# /wQFMAMBAf8wHQYDVR0OBBYEFOzX44LScV1kTN8uZz/nupiuHA9PMB8GA1UdIwQY
# MBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA4GA1UdDwEB/wQEAwIBhjB5BggrBgEF
# BQcBAQRtMGswJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBD
# BggrBgEFBQcwAoY3aHR0cDovL2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0
# QXNzdXJlZElEUm9vdENBLmNydDBFBgNVHR8EPjA8MDqgOKA2hjRodHRwOi8vY3Js
# My5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMBEGA1Ud
# IAQKMAgwBgYEVR0gADANBgkqhkiG9w0BAQwFAAOCAQEAcKC/Q1xV5zhfoKN0Gz22
# Ftf3v1cHvZqsoYcs7IVeqRq7IviHGmlUIu2kiHdtvRoU9BNKei8ttzjv9P+Aufih
# 9/Jy3iS8UgPITtAq3votVs/59PesMHqai7Je1M/RQ0SbQyHrlnKhSLSZy51PpwYD
# E3cnRNTnf+hZqPC/Lwum6fI0POz3A8eHqNJMQBk1RmppVLC4oVaO7KTVPeix3P0c
# 2PR3WlxUjG/voVA9/HYJaISfb8rbII01YBwCA8sgsKxYoA5AY8WYIsGyWfVVa88n
# q2x2zm8jLfR+cWojayL/ErhULSd+2DrZ8LaHlv1b0VysGMNNn3O3AamfV6peKOK5
# lDGCA4wwggOIAgEBMH0waTELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0
# LCBJbmMuMUEwPwYDVQQDEzhEaWdpQ2VydCBUcnVzdGVkIEc0IFRpbWVTdGFtcGlu
# ZyBSU0E0MDk2IFNIQTI1NiAyMDI1IENBMQIQDCBDSfnQ91n7mC3kCBuIezANBglg
# hkgBZQMEAgIFAKCB4TAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZI
# hvcNAQkFMQ8XDTI2MDMzMDExMTEwOVowKwYLKoZIhvcNAQkQAgwxHDAaMBgwFgQU
# crz9oBB/STSwBxxhD+bXllAAmHcwNwYLKoZIhvcNAQkQAi8xKDAmMCQwIgQgMvPj
# sb2i17JtTx0bjN29j4uEdqF4ntYSzTyqep7/NcIwPwYJKoZIhvcNAQkEMTIEMH0B
# rMdOk6wXTEwcs3B9cHjtjBobDaIYA4Q+G3J4zflxwfIyXltbCuFkbpHlF/Cu1zAN
# BgkqhkiG9w0BAQEFAASCAgCUGah+JMg9bZ6cuS6wYZpFSRa1LV8NGe+Ij3REo5CC
# TtzG5/p4UX2MZ20wlNMp+9wOZzmaKlBr3AVR5d4bR51rbtTsv5ug5dbxI9RjVWI7
# UR+tZR0XrroMRltWIffjkLBc72upH3Y/OUvvWPO9cd4crKx4MEPD2OQpVybC/23m
# 0kRUioc0l5YE3bZBmBg7Rn3a8mwsgGDiQZ2XLUDevjKyBuD1SguOiid3IjJJpbvI
# vxlRHYwwultJn1hVZHmNfUJ885jDilETqqMkU16mGyzht3q2NcDeN3BjW+2/IRUl
# PvPg72nX8eKE4oTCWw3tIemW0t3WD38gKuFlqyVSr5ikC0yzMNfn8l48BYkdS2RO
# SK9GSo8cMX1Kf/8Zbf5/YQkQDAQpFJCx0ODMvJUdq2RcJlN7PCAlghL89yMZtcuG
# QRSMPpJWMpla3mc3gVULJ8i05yHWQeFilfzrG98bsVf7Y08l6Y3Vpm+aRBNr7iRz
# Prm+x4FfljRJ7xbJ4Z6YIBf0ax1T00T9PfG4CvvfvOJwgfWTXcQgo2A1vsDuxye/
# 1fCzvKNvsKezD/Aml7SmpF76z8RveKpzMgpt9XUzZO53JqRdRCvIEsPzHVulMJWP
# b3zE1dqFtyO9M5Rcy94tjx0VE7JgTLLance1+bMoaikxQf/GFvsyqBj6omEZP2se
# 1Q==
# SIG # End signature block