Public/Export-VcCertificate.ps1

function Export-VcCertificate {
    <#
    .SYNOPSIS
    Export certificate data from TLSPC

    .DESCRIPTION
    Export certificate data in PEM format. You can retrieve the certificate, chain, and key.
    You can also save the certificate and private key in PEM or PKCS12 format.

    .PARAMETER ID
    Certificate ID, also known as uuid. Use Find-VcCertificate or Get-VcCertificate to determine the ID.
    You can pipe those functions as well.

    .PARAMETER PrivateKeyPassword
    Password required to include the private key.
    You can either provide a String, SecureString, or PSCredential.
    Requires PowerShell v7.0+.

    .PARAMETER IncludeChain
    Include the certificate chain with the exported or saved PEM certificate data.

    .PARAMETER OutPath
    Folder path to save the certificate to. The name of the file will be determined automatically.
    For each certificate a directory will be created in this folder with the format Name-ID.
    In the case of PKCS12, the file will be saved to the root of the folder.

    .PARAMETER PKCS12
    Export the certificate and private key in PKCS12 format.
    Requires PowerShell v7.1+.

    .PARAMETER ThrottleLimit
    Limit the number of threads when running in parallel; the default is 100. Applicable to PS v7+ only.

    .PARAMETER VenafiSession
    Authentication for the function.
    The value defaults to the script session object $VenafiSession created by New-VenafiSession.
    A TLSPC key can also provided.

    .INPUTS
    ID

    .OUTPUTS
    PSCustomObject

    .EXAMPLE
    $certId | Export-VcCertificate

    Export certificate data

    .EXAMPLE
    $certId | Export-VcCertificate -PrivateKeyPassword 'myPassw0rd!'

    Export certificate and private key data

    .EXAMPLE
    $certId | Export-VcCertificate -PrivateKeyPassword 'myPassw0rd!' -PKCS12 -OutPath '~/temp'

    Export certificate and private key in PKCS12 format

    .EXAMPLE
    $cert | Export-VcCertificate -OutPath '~/temp'

    Get certificate data and save to a file

    .EXAMPLE
    $cert | Export-VcCertificate -IncludeChain

    Get certificate data with the certificate chain included.

    .NOTES
    This function requires the use of sodium encryption.
    PS v7.1+ is required.
    On Windows, the latest Visual C++ redist must be installed. See https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist.
    #>


    [CmdletBinding(DefaultParameterSetName = 'PEM')]

    param (

        [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [Alias('certificateId')]
        [string] $ID,

        [Parameter(ParameterSetName = 'PEM')]
        [Parameter(ParameterSetName = 'PKCS12', Mandatory)]
        [ValidateScript(
            {
                if ($PSVersionTable.PSVersion -lt [version]'7.0') {
                    throw 'Exporting private keys is only supported on PowerShell v7.0+'
                }

                if ( $_ -is [string] -or $_ -is [securestring] -or $_ -is [pscredential] ) {
                    $true
                }
                else {
                    throw 'Unsupported type. Provide either a String, SecureString, or PSCredential.'
                }
            }
        )]
        [psobject] $PrivateKeyPassword,

        [Parameter(ParameterSetName = 'PEM')]
        [switch] $IncludeChain,

        [Parameter(ParameterSetName = 'PEM')]
        [Parameter(ParameterSetName = 'PKCS12', Mandatory)]
        [ValidateNotNullOrEmpty()]
        [ValidateScript( {
                if (Test-Path $_ -PathType Container) {
                    $true
                }
                else {
                    Throw "Output path '$_' does not exist"
                }
            })]
        [String] $OutPath,

        [Parameter(ParameterSetName = 'PKCS12', Mandatory)]
        [ValidateScript(
            {
                if ($PSVersionTable.PSVersion -lt [version]'7.1') {
                    throw 'Exporting in PKCS#12 foramt is only supported on PowerShell v7.1+'
                }
                $true
            }
        )]
        [switch] $PKCS12,

        [Parameter()]
        [int32] $ThrottleLimit = 100,

        [Parameter()]
        [psobject] $VenafiSession
    )

    begin {
        Test-VenafiSession -VenafiSession $VenafiSession -Platform 'VC'

        $allCerts = [System.Collections.Generic.List[hashtable]]::new()

        if ( $PrivateKeyPassword ) {

            $params = @{
                Method       = 'Post'
                Header       = @{ 'accept' = 'application/octet-stream' }
                UriRoot      = 'outagedetection/v1'
                Body         = @{
                    'exportFormat'                = 'PEM'
                    'encryptedKeystorePassphrase' = ''
                    'certificateLabel'            = ''
                }
                FullResponse = $true
            }

            $pkPassString = $PrivateKeyPassword | ConvertTo-PlaintextString
        }
        else {
            # no need to get the entire keystore if just getting cert/chain
            $params = @{
                UriRoot      = 'outagedetection/v1'
                Body         = @{
                    format = 'PEM'
                }
                FullResponse = $true
            }

            if ( $IncludeChain ) {
                $params.Body.chainOrder = 'EE_FIRST'
            }
            else {
                $params.Body.chainOrder = 'EE_ONLY'
            }
        }

        Initialize-PSSodium
    }

    process {
        if ( $PrivateKeyPassword ) {
            $params.UriLeaf = "certificates/$id/keystore"
            $allCerts.Add(
                @{
                    InvokeParams       = $params
                    ID                 = $ID
                    PrivateKeyPassword = $pkPassString
                }
            )
        }
        else {
            $params.UriLeaf = "certificates/$id/contents"
            $allCerts.Add(
                @{
                    InvokeParams = $params
                    ID           = $ID
                }
            )

        }
    }

    end {
        if ( $PrivateKeyPassword ) {
            $currDir = $PSScriptRoot
            $sb = {

                $out = [pscustomobject] @{
                    certificateId = $PSItem.ID
                    error         = ''
                }

                $thisCert = Get-VcCertificate -id $PSItem.ID

                if ( -not $thisCert.dekHash ) {
                    $out.error = 'Private key not found'
                    return $out
                }

                Import-Module (Join-Path -Path (Split-Path $using:currDir -Parent) -ChildPath 'import/PSSodium/PSSodium.psd1') -Force

                $params = $PSItem.InvokeParams

                $publicKey = Invoke-VenafiRestMethod -UriLeaf "edgeencryptionkeys/$($thisCert.dekHash)" | Select-Object -ExpandProperty key

                $privateKeyPasswordEnc = ConvertTo-SodiumEncryptedString -Text $PSItem.PrivateKeyPassword -PublicKey $publicKey

                $params.Body.encryptedPrivateKeyPassphrase = $privateKeyPasswordEnc

                $innerResponse = Invoke-VenafiRestMethod @params

                if ($innerResponse.StatusCode -notin 200, 201, 202) {
                    $out.error = $innerResponse.StatusDescription
                    return $out
                }

                if ( -not $innerResponse.Content ) {
                    $out.error = 'No certificate data received'
                    return $out
                }

                $zipFile = '{0}.zip' -f (New-TemporaryFile)
                $unzipPath = Join-Path -Path (Split-Path -Path $zipFile -Parent) -ChildPath $PSItem.ID

                try {
                    # always save the zip file then decide to copy to the final destination or return contents
                    [IO.File]::WriteAllBytes($zipFile, $innerResponse.Content)

                    Write-Verbose ('Expanding {0} to {1}' -f $zipFile, $unzipPath)

                    Expand-Archive -Path $zipFile -DestinationPath $unzipPath
                    $unzipFiles = Get-ChildItem -Path $unzipPath

                    if ( $using:OutPath ) {

                        if ( $using:PKCS12 ) {
                            $keyFile = Get-ChildItem -Path $unzipPath -Filter '*.key'

                            if ( $keyFile.Count -ne 1 ) {
                                $out.error = 'Private key not found'
                                return $out
                            }

                            $keyPath = $keyFile.FullName
                            $crtPath = $keyPath.Replace('.key', '.crt')
                            $cert = [System.Security.Cryptography.X509Certificates.x509Certificate2]::CreateFromEncryptedPemFile($crtPath, $PSItem.PrivateKeyPassword, $keyPath)
                            # export content type of 3 is for pfx
                            $cert.Export(3, $PSItem.PrivateKeyPassword) | Set-Content -Path (Join-Path -Path $using:OutPath -ChildPath ('{0}.pfx' -f $keyFile.BaseName)) -AsByteStream
                        }
                        else {
                            # copy files to final desination
                            $dest = Join-Path -Path (Resolve-Path -Path $using:OutPath) -ChildPath ('{0}-{1}' -f $thisCert.certificateName, $thisCert.certificateId)
                            $null = New-Item -Path $dest -ItemType Directory -Force
                            $unzipFiles | Copy-Item -Destination $dest -Force
                            $out | Add-Member @{'outPath' = $dest }
                        }
                    }
                    else {
                        # pull in the contents so we can provide them
                        switch ($unzipFiles) {
                            { $_.Name.EndsWith('.key') } {
                                $out | Add-Member @{'KeyPem' = Get-Content -Path $_.FullName -Raw }
                            }
                            { $_.Name.EndsWith('root-last.pem') } {

                                $certPem = @()

                                $pemLines = Get-Content -Path $_.FullName
                                for ($i = 0; $i -lt $pemLines.Count; $i++) {
                                    if ($pemLines[$i].Contains('-----BEGIN')) {
                                        $iStart = $i
                                        continue
                                    }
                                    if ($pemLines[$i].Contains('-----END')) {
                                        $thisPemLines = $pemLines[$iStart..$i]
                                        $certPem += $thisPemLines -join "`n"
                                        continue
                                    }
                                }

                                $out | Add-Member @{'CertPem' = $certPem[0] }
                                if ( $using:IncludeChain ) {
                                    $out | Add-Member @{'ChainPem' = $certPem[1..($certPem.Count - 1)] }
                                }
                            }
                        }
                    }
                    $out
                }
                finally {
                    Remove-Item -Path $unzipPath -Recurse -Force -ErrorAction SilentlyContinue
                    Remove-Item -Path $zipFile -Force -ErrorAction SilentlyContinue
                }
            }
        }
        else {
            # cert/chain only, no private key. different api call, better performance.
            $sb = {

                $thisCert = Get-VcCertificate -id $PSItem.ID

                $params = $PSItem.InvokeParams
                $innerResponse = Invoke-VenafiRestMethod @params
                $certificateData = $innerResponse.Content

                $out = [pscustomobject] @{
                    certificateId = $PSItem.ID
                    error         = if ($innerResponse.StatusCode -notin 200, 201, 202) { $innerResponse.StatusDescription }
                }

                if ( $certificateData ) {
                    if ( $using:OutPath ) {
                        $dest = Join-Path -Path (Resolve-Path -Path $using:OutPath) -ChildPath ('{0}-{1}' -f $thisCert.certificateName, $thisCert.certificateId)
                        $null = New-Item -Path $dest -ItemType Directory -Force
                        $outFile = Join-Path -Path $dest -ChildPath ('{0}.{1}' -f $PSItem.ID, $PSItem.InvokeParams.Body.format)
                        try {
                            $sw = [IO.StreamWriter]::new($outFile, $false, [Text.Encoding]::ASCII)
                            $sw.WriteLine($certificateData)
                            Write-Verbose "Saved $outFile"
                        }
                        finally {
                            if ($null -ne $sw) { $sw.Close() }
                        }

                        $out | Add-Member @{'outPath' = $dest }
                    }
                    else {
                        $out | Add-Member @{'certificateData' = $certificateData }
                    }
                }
                $out
            }
        }

        $invokeParams = @{
            InputObject   = $allCerts
            ScriptBlock   = $sb
            ThrottleLimit = $ThrottleLimit
            ProgressTitle = 'Exporting certificates'
        }
        Invoke-VenafiParallel @invokeParams
    }
}

# SIG # Begin signature block
# MIIhigYJKoZIhvcNAQcCoIIhezCCIXcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC8X5ARswitHGzp
# L/In+21YZbJ0VMLx3wPEp5MINLgxoKCCGokwggd8MIIFZKADAgECAhAEskBM6tH3
# agmQID1jirpbMA0GCSqGSIb3DQEBCwUAMGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQK
# Ew5EaWdpQ2VydCwgSW5jLjFBMD8GA1UEAxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBD
# b2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEzODQgMjAyMSBDQTEwHhcNMjMwOTEzMDAw
# MDAwWhcNMjQwOTEyMjM1OTU5WjCBgzELMAkGA1UEBhMCVVMxDTALBgNVBAgTBFV0
# YWgxFzAVBgNVBAcTDlNhbHQgTGFrZSBDaXR5MRUwEwYDVQQKEwxWZW5hZmksIElu
# Yy4xHjAcBgNVBAsTFVByb2Zlc3Npb25hbCBTZXJ2aWNlczEVMBMGA1UEAxMMVmVu
# YWZpLCBJbmMuMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAz2ga2w0N
# HzoqK1Npwmce0q2VZkosMIa4Mw4eFhDZiSlaWWwXbWKBEQVEEnd/mPlmOMv2jwBE
# PaBdTzX4bp5A4gr2Nwpw2Hjr9nsfBuuMNVkCCimXdjqbLhiyU0obIYk+5EMH0Lnw
# n1AupTbjtj63kqs7ZDfLRVq6jUtGJVdfDKBrIAjymePXi58G1991J6i8og3vKhhO
# 97sWciGXLblirUFNMpZpK32UrHr2QklIqhSo1ucvTT7x8EFW5P33z2eniQCDvssE
# UsV7vDdc4zll2io+B1j7vVOicLG+P8Jxhjy13seKsmAXSwfID51tWO3V2SfEZE2x
# fuxRN9bLOdXyB9808ifIAyxLmz36Kq7kaX/LQ6eGeVDwbnvdAUoUcCKYGK7FPYQh
# J0ZnxtXJRKfQU4rLaZItVtnJbPfXGJX1aXJY10fKZSvnEfYRrcb6pMVFxCyAMoZE
# U3XSg9bS0oc9fg+FTjknczyXFjMD97PZW8GcLAXWSukbstyzSHvh0Nh3tyGyXPyy
# +yGxMqAw6elop3FcG1sq6Ri9gSNA+oCzD2VfwoKpPJnomLDGrYuCYM/U1WG2hi/z
# gnhn/Lu/e8FKTkI8ZRhVB1Yfv4VgrxGSx0WBI+4WB6Bwi6LjVmSuasJZ0Oobl7ik
# 59nkseYc885U5bjgWZrUbXhfw34lUrVkfMkCAwEAAaOCAgMwggH/MB8GA1UdIwQY
# MBaAFGg34Ou2O/hfEYb7/mF7CIhl9E5CMB0GA1UdDgQWBBSoGeI5UP36z1PFpV0W
# 4oYJNTGVKDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRw
# Oi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v
# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex
# LmNybDCBlAYIKwYBBQUHAQEEgYcwgYQwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3Nw
# LmRpZ2ljZXJ0LmNvbTBcBggrBgEFBQcwAoZQaHR0cDovL2NhY2VydHMuZGlnaWNl
# cnQuY29tL0RpZ2lDZXJ0VHJ1c3RlZEc0Q29kZVNpZ25pbmdSU0E0MDk2U0hBMzg0
# MjAyMUNBMS5jcnQwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAgEADWd6cY3c
# UuXXxFhO4O+VPRPxNituYopOy3rgvLio6YncYfbbfZKRmKBYb79Ae6c/Nsz6K3bP
# lhs9UuXs6UVlVwRhHpf8w1ko1I9lZLjZM8gbgvXethyIB3bvDDrLXyESUX4iAL/U
# DNyuDjsQBOTe+7WvyXPrZhqlJL0kwO6kaMFffm+V+zaTBrSazco7GLlXVtp6+jWY
# EHSdzyaeNgY5N4j3nKlsdVo4LhynuyqC9aTyWfxC9KPKpRNq9tGxkTHyjeCB61Y/
# yA6C63GpDmfoZtD0x46nzr1r7AG5c//Td+g9sKA4raai2RxcmLXwoIEG/5W/60cK
# TAU44EnUW4ep/rmPBBLpinY3cg+k2b5UjBIUbYebanRVHiZmgCtLKQYLHdH8yu9L
# Zc96I6dGmm08C8zsZPTyiYg9JadKPlAdkI3sB1d8263Ufsa6zvHEvSK3QnutLxHf
# dOd/7XRwqSWx/oXrk8jggvAo3IAGEX/S+cRBjFYtmKZuhZUPQSh8LbiUfsRLsG/d
# omoKJw1JVZubeFORgByyscqIDAIoAptjyZeoKJal+MF1DhkGnBehUNdZe+q4h43c
# r573CZl4XZwY5w3y3ekc4Ahls9kE/VvMqkxGfHoTswmaSVM3EJuZ51FCg054zoka
# BEgxZ4/59gvjUKfRNuUYC8FfD5Ldj0oI21QwggWNMIIEdaADAgECAhAOmxiO+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
# twGpn1eqXijiuZQwggauMIIElqADAgECAhAHNje3JFR82Ees/ShmKl5bMA0GCSqG
# SIb3DQEBCwUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRy
# dXN0ZWQgUm9vdCBHNDAeFw0yMjAzMjMwMDAwMDBaFw0zNzAzMjIyMzU5NTlaMGMx
# CzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMy
# RGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcg
# Q0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDGhjUGSbPBPXJJUVXH
# JQPE8pE3qZdRodbSg9GeTKJtoLDMg/la9hGhRBVCX6SI82j6ffOciQt/nR+eDzMf
# UBMLJnOWbfhXqAJ9/UO0hNoR8XOxs+4rgISKIhjf69o9xBd/qxkrPkLcZ47qUT3w
# 1lbU5ygt69OxtXXnHwZljZQp09nsad/ZkIdGAHvbREGJ3HxqV3rwN3mfXazL6IRk
# tFLydkf3YYMZ3V+0VAshaG43IbtArF+y3kp9zvU5EmfvDqVjbOSmxR3NNg1c1eYb
# qMFkdECnwHLFuk4fsbVYTXn+149zk6wsOeKlSNbwsDETqVcplicu9Yemj052FVUm
# cJgmf6AaRyBD40NjgHt1biclkJg6OBGz9vae5jtb7IHeIhTZgirHkr+g3uM+onP6
# 5x9abJTyUpURK1h0QCirc0PO30qhHGs4xSnzyqqWc0Jon7ZGs506o9UD4L/wojzK
# QtwYSH8UNM/STKvvmz3+DrhkKvp1KCRB7UK/BZxmSVJQ9FHzNklNiyDSLFc1eSuo
# 80VgvCONWPfcYd6T/jnA+bIwpUzX6ZhKWD7TA4j+s4/TXkt2ElGTyYwMO1uKIqjB
# Jgj5FBASA31fI7tk42PgpuE+9sJ0sj8eCXbsq11GdeJgo1gJASgADoRU7s7pXche
# MBK9Rp6103a50g5rmQzSM7TNsQIDAQABo4IBXTCCAVkwEgYDVR0TAQH/BAgwBgEB
# /wIBADAdBgNVHQ4EFgQUuhbZbU2FL3MpdpovdYxqII+eyG8wHwYDVR0jBBgwFoAU
# 7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYYaHR0cDovL29j
# c3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2FjZXJ0cy5kaWdp
# Y2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNVHR8EPDA6MDig
# NqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9v
# dEc0LmNybDAgBgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwDQYJKoZI
# hvcNAQELBQADggIBAH1ZjsCTtm+YqUQiAX5m1tghQuGwGC4QTRPPMFPOvxj7x1Bd
# 4ksp+3CKDaopafxpwc8dB+k+YMjYC+VcW9dth/qEICU0MWfNthKWb8RQTGIdDAiC
# qBa9qVbPFXONASIlzpVpP0d3+3J0FNf/q0+KLHqrhc1DX+1gtqpPkWaeLJ7giqzl
# /Yy8ZCaHbJK9nXzQcAp876i8dU+6WvepELJd6f8oVInw1YpxdmXazPByoyP6wCeC
# RK6ZJxurJB4mwbfeKuv2nrF5mYGjVoarCkXJ38SNoOeY+/umnXKvxMfBwWpx2cYT
# gAnEtp/Nh4cku0+jSbl3ZpHxcpzpSwJSpzd+k1OsOx0ISQ+UzTl63f8lY5knLD0/
# a6fxZsNBzU+2QJshIUDQtxMkzdwdeDrknq3lNHGS1yZr5Dhzq6YBT70/O3itTK37
# xJV77QpfMzmHQXh6OOmc4d0j/R0o08f56PGYX/sr2H7yRp11LB4nLCbbbxV7HhmL
# NriT1ObyF5lZynDwN7+YAN8gFk8n+2BnFqFmut1VwDophrCYoCvtlUG3OtUVmDG0
# YgkPCr2B2RP+v6TR81fZvAT6gt4y3wSJ8ADNXcL50CN/AAvkdgIm2fBldkKmKYcJ
# RyvmfxqkhQ/8mJb2VVQrH4D6wPIOK+XW+6kvRBVK5xMOHds3OBqhK/bt1nz8MIIG
# wjCCBKqgAwIBAgIQBUSv85SdCDmmv9s/X+VhFjANBgkqhkiG9w0BAQsFADBjMQsw
# CQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIEluYy4xOzA5BgNVBAMTMkRp
# Z2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5NiBTSEEyNTYgVGltZVN0YW1waW5nIENB
# MB4XDTIzMDcxNDAwMDAwMFoXDTM0MTAxMzIzNTk1OVowSDELMAkGA1UEBhMCVVMx
# FzAVBgNVBAoTDkRpZ2lDZXJ0LCBJbmMuMSAwHgYDVQQDExdEaWdpQ2VydCBUaW1l
# c3RhbXAgMjAyMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKNTRYcd
# g45brD5UsyPgz5/X5dLnXaEOCdwvSKOXejsqnGfcYhVYwamTEafNqrJq3RApih5i
# Y2nTWJw1cb86l+uUUI8cIOrHmjsvlmbjaedp/lvD1isgHMGXlLSlUIHyz8sHpjBo
# yoNC2vx/CSSUpIIa2mq62DvKXd4ZGIX7ReoNYWyd/nFexAaaPPDFLnkPG2ZS48jW
# Pl/aQ9OE9dDH9kgtXkV1lnX+3RChG4PBuOZSlbVH13gpOWvgeFmX40QrStWVzu8I
# F+qCZE3/I+PKhu60pCFkcOvV5aDaY7Mu6QXuqvYk9R28mxyyt1/f8O52fTGZZUdV
# nUokL6wrl76f5P17cz4y7lI0+9S769SgLDSb495uZBkHNwGRDxy1Uc2qTGaDiGhi
# u7xBG3gZbeTZD+BYQfvYsSzhUa+0rRUGFOpiCBPTaR58ZE2dD9/O0V6MqqtQFcmz
# yrzXxDtoRKOlO0L9c33u3Qr/eTQQfqZcClhMAD6FaXXHg2TWdc2PEnZWpST618Rr
# IbroHzSYLzrqawGw9/sqhux7UjipmAmhcbJsca8+uG+W1eEQE/5hRwqM/vC2x9XH
# 3mwk8L9CgsqgcT2ckpMEtGlwJw1Pt7U20clfCKRwo+wK8REuZODLIivK8SgTIUlR
# fgZm0zu++uuRONhRB8qUt+JQofM604qDy0B7AgMBAAGjggGLMIIBhzAOBgNVHQ8B
# Af8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAg
# BgNVHSAEGTAXMAgGBmeBDAEEAjALBglghkgBhv1sBwEwHwYDVR0jBBgwFoAUuhbZ
# bU2FL3MpdpovdYxqII+eyG8wHQYDVR0OBBYEFKW27xPn783QZKHVVqllMaPe1eNJ
# MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
# Q2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcmwwgZAG
# CCsGAQUFBwEBBIGDMIGAMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2Vy
# dC5jb20wWAYIKwYBBQUHMAKGTGh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9E
# aWdpQ2VydFRydXN0ZWRHNFJTQTQwOTZTSEEyNTZUaW1lU3RhbXBpbmdDQS5jcnQw
# DQYJKoZIhvcNAQELBQADggIBAIEa1t6gqbWYF7xwjU+KPGic2CX/yyzkzepdIpLs
# jCICqbjPgKjZ5+PF7SaCinEvGN1Ott5s1+FgnCvt7T1IjrhrunxdvcJhN2hJd6Pr
# kKoS1yeF844ektrCQDifXcigLiV4JZ0qBXqEKZi2V3mP2yZWK7Dzp703DNiYdk9W
# uVLCtp04qYHnbUFcjGnRuSvExnvPnPp44pMadqJpddNQ5EQSviANnqlE0PjlSXcI
# WiHFtM+YlRpUurm8wWkZus8W8oM3NG6wQSbd3lqXTzON1I13fXVFoaVYJmoDRd7Z
# ULVQjK9WvUzF4UbFKNOt50MAcN7MmJ4ZiQPq1JE3701S88lgIcRWR+3aEUuMMsOI
# 5ljitts++V+wQtaP4xeR0arAVeOGv6wnLEHQmjNKqDbUuXKWfpd5OEhfysLcPTLf
# ddY2Z1qJ+Panx+VPNTwAvb6cKmx5AdzaROY63jg7B145WPR8czFVoIARyxQMfq68
# /qTreWWqaNYiyjvrmoI1VygWy2nyMpqy0tg6uLFGhmu6F/3Ed2wVbK6rr3M66ElG
# t9V/zLY4wNjsHPW2obhDLN9OTH0eaHDAdwrUAuBcYLso/zjlUlrWrBciI0707NMX
# +1Br/wd3H3GXREHJuEbTbDJ8WC9nR2XlG3O2mflrLAZG70Ee8PBf4NvZrZCARK+A
# EEGKMYIGVzCCBlMCAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNl
# cnQsIEluYy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWdu
# aW5nIFJTQTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAEskBM6tH3agmQID1jirpbMA0G
# CWCGSAFlAwQCAQUAoIGIMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCSqG
# SIb3DQEJBTEPFw0yNDA1MTUxNDU5NTRaMBwGCisGAQQBgjcCAQsxDjAMBgorBgEE
# AYI3AgEVMC8GCSqGSIb3DQEJBDEiBCC1woGc9mryLBQVM+0jStJheHAa1bVryzbc
# XhyfV2+GKDANBgkqhkiG9w0BAQEFAASCAgBPzwFE7ij7m9b10aTslWrCCBHWCRBM
# U9DnjHQhKV/23LBxaztAeIqLav7BVyCAG3F50d1Mg2TNNVEAb8tmjaegYD5dN1JK
# U+zECI7vEYLtlVhA5C9u+Tbt29kKaWRDrHi2s9e8LeqG3vxM6BIben9wOOZeYZYp
# DQ46E2qRckDLBccSG+Ug/tNsnCNZg396crDD3KjvY138iazTuzgnEIbH527l6rSx
# eoIjIO2TN0Hyb+5DCLr0DknPLObOFo0zSajOy8d011GvzZTmLTHKtlOyKNrKnX0N
# jNhqIL8iwHGkME7nwGAr8+96RMJJhi+5WiHYqcnAnSFcvv+UWOCO08ilC/qinu8N
# VzFiYusHSLiOMEm4eALLzVnxdbQ/raHb34q73qm9z8VGtdoOl4+PJ6xVjKIFNbve
# nSwGFQ290h8/PCIZZX3DTqlKMKtDFa0U6PqV6EA4rCXKx+D5hCmvqXaRTCVvXGs1
# T07PPJFttFWKJaileoRjJH011su0cHv9zhdUfGVZeXf0FpFmdh6IXTCe273DSxxz
# t8/wIHdchXM81vW3q330cIjSPwb98k6O3q9YB2B20IVowirCFZ+7v0QnJDjQodag
# 4fX/FBGgRJBZSAuStw88r61mvLRVWbQRoxC5Gx+ENLQzQP3NnEPlyDSq5AiUL+Ph
# 9lCOAYwTtMGZaqGCAyAwggMcBgkqhkiG9w0BCQYxggMNMIIDCQIBATB3MGMxCzAJ
# BgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjE7MDkGA1UEAxMyRGln
# aUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBUaW1lU3RhbXBpbmcgQ0EC
# EAVEr/OUnQg5pr/bP1/lYRYwDQYJYIZIAWUDBAIBBQCgaTAYBgkqhkiG9w0BCQMx
# CwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0yNDA1MTUxNDU5NTVaMC8GCSqG
# SIb3DQEJBDEiBCAKByU3oCorExhQDXgbOGcIyYfHpMLuaZ7ZDQADKH1m6jANBgkq
# hkiG9w0BAQEFAASCAgA4JIgF5/rCM/MalMRsG5BU6l3g65A1ePMTI0s2a61aAZq8
# HaFg8FoB/gwI5p5jl8L3T3n5uMy1sb1EK4wC+lWexTH1h/+VNBJqAv3D6CNv3mwX
# R80dfwRHbxdBC42jXcLeeIeaXdIlvlKEabS8zw/q/zZ+0nJNyfIVm9XRP8cpnZCo
# ERBC7SzFkAdQKnXg7bK/oxwYgS/wNYjESBS/T+/MR+INXBuPqe/0TM5CIQ+2IkK3
# TYh54Q9qU/pj8fURh6MEQEUe8dYhfXjgcnaPcUfFInC0uul3WUoQgxubJm6YxcVw
# owLUbo+UK/rijCrtuZXAYp1cI9XO16g7pWGuSccelkOGWE9xzkMa/NQkFDRJta7T
# +sI8q3ZFStz+FVrqJTnZg6spPXCquQPl0pjdYjImZOgD2fC5Ck80SeQUnGi00/LO
# Nlc71oABTpD59qhvRXpFGhn4ZW3nW1U1wT1NQfu9+LhmXH91q6HFO5eXcLNfAbkL
# fA5r0hrMK7Acm1/3ie0tOoPqOQa5Hbg030Yt5qu1HkrD7CUF2Zhf2AqzulC6JLLP
# rQnnlO8YQTPHbDqfbGlEtk046KioUsjyyJPxXILC8LfOB2Gc7Rt0aU9j7PFNT6nV
# gwYdluP2C+b5hdMlOl/Xl0vYwNfBA+WcUXQ7PUdt7+LnTdBgEcyghKyiWFo1eA==
# SIG # End signature block