CertificateValidation/Microsoft.AzureStack.CertificateValidation.psm1

#Requires -RunAsAdministrator
#Requires -Version 5.0

<#############################################################
 # #
 # Copyright (C) Microsoft Corporation. All rights reserved. #
 # #
 #############################################################>


<#
 
.SYNOPSIS
This module is intended to be given to the customer along with their deploymentdata.json
in order to validate the certificates are suitable before Azure Stack deployment.
 
.DESCRIPTION
The validation checks the following:
    Read PFX. Checks for valid PFX file, correct password and warns if public information is not protected by the password.
    Signature Algorithm. Checks the Signature Algorithm is not SHA1
    Private Key. Checks the private key is present and is exported with the Local Machine attribute.
    Cert Chain. Checks certificate chain is in tact including for self-signed certificates.
    DNS Names. Checks the SAN contains relevant DNS names for each endpoint or if a supporting wildcard is present.
    Key Usage. Checks Key Usage contains Digital Signature and Key Encipherment and Enhanced Key Usage contains Server Authentication and Client Authentication.
    Key Size. Checks Key Size is 2048 or larger
    Chain Order. Checks the order of the other certificates making the chain is correct.
    Other Certificates. Ensure no other certificates have been packaged in PFX other than the relevant leaf certificate and its chain.
    No Profile. Checks a new user can load the PFX data without a user profile loaded, mimicking the behavior of gMSA accounts during certificate servicing.
 
 
.EXAMPLE
To Check certificates are ready for install run the following:
Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1
$password = Read-Host -Prompt "Enter PFX Password" -AsSecureString
Start-CertChecker -CertificatePath .\Certificates\ -pfxPassword $password -deploymentDataJSONPath .\DeploymentData.json
 
To import/export certificates as indicated by the output of the above command
Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1
$password = Read-Host -Prompt "Enter PFX Password" -AsSecureString
Start-CertChecker -pfxPassword $password -pfxPath \certificates\acs\old_ssl.pfx -ExportToPath \certificates\acs\new_ssl.pfx [<CommonParameters>]
 
.NOTES
The script expects the certificates in the same directory structure as the install: i.e.
\Certificates\
    \ACS
        \ssl.pfx
    \ADFS
        \ssl.pfx
The script requires the deploymentdata.json that will be used for deployment.
 
.LINK
 
#>


function Test-AzsCertificates{
    [CmdletBinding()]
    Param(
            [Parameter(Mandatory=$false,HelpMessage="Enter Path to Certificates Directory")]
            [string] 
            $CertificatePath = "$PSScriptRoot\Certificates",
            
            [Parameter(Mandatory=$true,HelpMessage="Enter Password for PFX Certificates as a secure string")]
            [securestring]
            $pfxPassword,
    
            [Parameter(Mandatory=$false,ParameterSetName="ValidateJSON")]
            [string]
            $deploymentDataJSONPath = "$PSScriptRoot\DeploymentData.json",

            [Parameter(Mandatory=$true,ParameterSetName="Validate")]
            [string]
            $RegionName,

            [Parameter(Mandatory=$true,ParameterSetName="Validate")]
            [string]
            $externalFQDN,

            [Parameter(Mandatory=$true,ParameterSetName="Validate")]
            [switch]
            $UseADFS
        )
    
    $thisFunction = $MyInvocation.MyCommand.Name
    
    if ($PSCmdlet.ParameterSetName -contains 'ValidateJSON')
    {
        Try
        {
            $deploymentDataJSON = Get-Content $deploymentDataJSONPath | ConvertFrom-Json
        }
        Catch
        { 
            if ($_.exception -like '*Invalid JSON primitive*') 
            {
                Write-Log -Message "Invalid JSON file provided $deploymentDataJSONPath" -type Error -function $thisFunction
            }
            break
        }

        # detect deployment type and get region name and fqdn.
        if ($deploymentDataJSON.DeploymentData.UseAdfs)
        {
            $UseADFS = $true
        }
        elseif ($deploymentDataJSON.DeploymentData.InfraAzureEnvironment)
        {
            $UseADFS = $false
        }
        else 
        {
            Write-Error -message "Failed to parse identity store in DeploymentJSON"
        }

        $RegionName = $deploymentDataJSON.DeploymentData.RegionName
        $externalFQDN = $deploymentDataJSON.DeploymentData.ExternalDomainFQDN
    }
    
    Test-AzsCertificateFolderStructure -UseADFS:$UseADFS -certificatePath $CertificatePath

    Try 
    {
        # Tests external certs
        #ADFS or not
        if ($UseADFS)
        {
            $dirName = 'ADFS'
        }
        else
        {
            $dirname = 'AAD'
        }

        #make the path and copy the certs into a temp cache per the identity system as in deployment
        $testPath = new-item $ENV:TEMP\Certchecker\certificates\$dirname -ItemType Directory -Force
        Get-ChildItem $CertificatePath -Recurse -Directory | Copy-Item -Destination $testPath -Recurse
        $allCertResults = Test-AzureStackCerts -ExpectedDomainFQDN "$RegionName.$externalFQDN" `
                                    -CertificatePassword $pfxPassword `
                                    -UseADFS $UseADFS `
                                    -PfxFilesPath $testPath.Parent.FullName
    }
    Catch 
    {
        $certResult = $_
    }
    Finally
    {
        if ($certResult)
        {
            Write-Host "Failed" -ForegroundColor Red
            Write-Host "Detail: $certResult"
            Write-Host "Please ensure the certificates meet all the requirements as per the companion guide."
        }

        #Write Summary to log
        Write-Log -Message "Validation Summary Results:" -Type Info -Function $thisfunction
        $allCertResults | Out-File $PSScriptRoot\CertChecker.log -Append -Force
        #Clean up temp cache
        Remove-Item $ENV:TEMP\Certchecker -Force -Recurse
        $allCertResults | Select-Object Result, FailureDetail, Test, Path
    }
}

function Test-AzsCertificateFolderStructure
{
    param ([switch]$UseADFS,$certificatePath)
    # set expected folder names
    if ($UseADFS)
    {
        $validContainers = 'ADFS','Graph','ACSBlob','ACSQueue','ACSTable','Admin Portal','ARM Admin','ARM Public','KeyVault','KeyVaultInternal','Public Portal'
    }
    else
    {
        $validContainers = 'ACSBlob','ACSQueue','ACSTable','Admin Portal','ARM Admin','ARM Public','KeyVault','KeyVaultInternal','Public Portal'
    }

    # Check directory structure is valid
    $actualContainers = (Get-ChildItem -Path $certificatePath -Directory).Name
    $compareContainers = Compare-Object -ReferenceObject $validContainers -DifferenceObject $actualContainers
    if ($compareContainers)
    {
        $invalidContainers = $compareContainers | Where-Object SideIndicator -eq '=>' | Select-Object -ExpandProperty InputObject
        $missingContainers = $compareContainers | Where-Object SideIndicator -eq '<=' | Select-Object -ExpandProperty InputObject
        if (-not $invalidContainers){$invalidContainers = '[none]'}
        if (-not $missingContainers){$missingContainers = '[none]'}
        Write-Error ("Invalid folder structure found in certificate folder {0}, ensure only required folders are present. `nRequired folders: {1} `nInvalid folders: {2} `nMissing folders: {3}" -f `
                    $CertificatePath, `
                    ($validContainers -join ', '), `
                    ($invalidContainers -join ', '), `
                    ($missingContainers -join ', ')
                    )
    }

    # Check there is only a single cert in each folder
    foreach ($folder in (Get-ChildItem -Path $certificatePath -Directory))
    {
        $pfxfiles = Get-ChildItem $folder.FullName -Recurse -Filter *.pfx -ErrorAction SilentlyContinue
        if ($pfxfiles.count -ne 1)
        {
            Write-Error ("The certificate Path '{0}' should only contain 1 certificate. `nEnsure all certificate folders only contain a single certificate." -f $folder.FullName) 
        }
    }
}
<#
    .SYNOPSIS
    Invoke-Microsoft.AzureStack.CertificateValidation is a standalone wrapper for "certchecker"
     
    .DESCRIPTION
    Either invokes certificate validation for Azure Stack public certificates or
    import/export routine to correct Azure Stack public certificates
     
    .PARAMETER CertificatePath
    Path to directory structure for Azure Stack Public Certificates.
    The path given for this parameter is expected to contain one of the following sets of sub-directories, each with a single pfx certificate:
    Identity System AAD:
        ACSBlob, ACSQueue, ACSTable, Admin Portal, ARM Admin, ARM Public, KeyVault, KeyVaultInternal, Public Portal
    Identity System ADFS:
        ADFS, Graph, ACSBlob, ACSQueue, ACSTable, Admin Portal, ARM Admin, ARM Public, KeyVault, KeyVaultInternal, Public Portal
 
    .PARAMETER pfxPassword
    A securestring containing a single password that is common across all certificates
     
    .PARAMETER deploymentDataJSONPath
    Path to deploymentdatajsonPath relevant for the Azure Stack Deployment.
     
    .PARAMETER pfxPath
    Path to pfx file that needs to be imported and exported for remediation purposes.
     
    .PARAMETER ExportToPath
    Path to place exported pfx file after import/export routine.
     
    .EXAMPLE
    To Check certificates are ready for install run the following:
        Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1
        $password = Read-Host -Prompt "Enter PFX Password" -AsSecureString
        .\Start-CertChecker -CertificatePath .\Certificates\ -pfxPassword $password -deploymentDataJSONPath .\DeploymentData.json
 
    To import/export certificates as indicated by the output of the above command
        Import-Module .\Microsoft.AzureStack.CertificateValidation.psm1
        $password = Read-Host -Prompt "Enter PFX Password" -AsSecureString
        .\Start-CertChecker -pfxPassword $password -pfxPath \certificates\acs\old_ssl.pfx -ExportToPath \certificates\acs\new_ssl.pfx [<CommonParameters>]
     
    .NOTES
     
#>

function Invoke-AzureStackCertificateValidation
{
    [CmdletBinding()]
    [Alias("Start-CertChecker","Start-AzsCertChecker","Start-AzureStackCertChecker","CertChecker")]
    Param(
            [Parameter(HelpMessage="Enter Path to Certificates Directory")]
            [Parameter(Mandatory=$false,HelpMessage="Enter Path to Certificates Directory",ParameterSetName="ValidateJSON")]
            [Parameter(Mandatory=$false,HelpMessage="Enter Path to Certificates Directory",ParameterSetName="Validate")]
            [string] 
            $CertificatePath = "$PSScriptRoot\Certificates",
            
            [Parameter(Mandatory=$false,HelpMessage="Enter Password for PFX Certificates as a secure string")]
            [securestring]
            $pfxPassword,
    
            [Parameter(Mandatory=$true,HelpMessage="Enter Path to DeploymentData.json",ParameterSetName="ValidateJSON")]
            [Parameter(Mandatory=$false,ParameterSetName="ValidatePaaS")]
            [ValidateScript({Test-Path $_ -Include *.json})] 
            [string]
            $deploymentDataJSONPath,

            [Parameter(Mandatory=$true,ParameterSetName="Validate")]
            [Parameter(Mandatory=$false,ParameterSetName="ValidatePaaS")]
            [string]
            $RegionName,

            [Parameter(Mandatory=$true,ParameterSetName="Validate")]
            [Parameter(Mandatory=$false,ParameterSetName="ValidatePaaS")]
            [string]
            $externalFQDN,

            [Parameter(Mandatory=$true,ParameterSetName="Validate")]
            [switch]
            $UseADFS,

            [Parameter(Mandatory=$true,HelpMessage="Path to PFX on disk",ParameterSetName="ImportExport")]
            [ValidateScript({Test-Path $_ -Include *.pfx -PathType Leaf})]
            [string]
            $pfxPath,

            [Parameter(Mandatory=$true,HelpMessage="Destination Path for PFX export",ParameterSetName="ImportExport")]
            [ValidateScript({Test-Path -Path (split-path -Path $_ -Parent)})]
            [string]
            $ExportPFXPath,

            [Parameter(Mandatory=$true,ParameterSetName="ValidatePaaS")]
            [hashtable]
            $PaaSCertificates
        )

    $ErrorActionPreference = 'Stop'

    #setting global variable to true
    $GLOBAL:standalone = $true
    $thisFunction = $MyInvocation.MyCommand.Name
    $mod = Import-Module $PSScriptRoot\PublicCertHelper.psd1 -Force -PassThru
    if ($PSCmdlet.ParameterSetName -eq 'ValidateJSON')
    {
        Write-Host ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version)
        Write-Log -Message ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) -Type Info -Function $thisfunction
        $TestAzsCertificatesResults = Test-AzsCertificates -pfxPassword $pfxPassword -CertificatePath $CertificatePath -deploymentDataJSONPath $deploymentDataJSONPath
        Write-Log -Message "Ending Certificate Validation" -Type Info -Function $thisfunction
        $TestAzsCertificatesResults
    }
    if ($PSCmdlet.ParameterSetName -eq 'Validate')
    {
        Write-Host ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version)
        Write-Log -Message ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version) -Type Info -Function $thisfunction
        $TestAzsCertificatesResults = Test-AzsCertificates -pfxPassword $pfxPassword -CertificatePath $CertificatePath -RegionName $RegionName -externalFQDN $externalFQDN -UseADFS:$UseADFS
        Write-Log -Message "Ending Certificate Validation" -Type Info -Function $thisfunction
        $TestAzsCertificatesResults
    }
    if ($PSCmdlet.ParameterSetName -eq 'ValidatePaaS')
    {
        Write-Host ("Starting Azure Stack Certificate Validation {0} " -f $mod.Version)
        Write-Log -Message ("Starting Azure Stack PaaS Certificate Validation {0} " -f $mod.Version) -Type Info -Function $thisfunction
        if ($deploymentDataJSONPath)
        {
            $deploymentDataJSON = Get-Content $deploymentDataJSONPath | ConvertFrom-Json
            $RegionName = $deploymentDataJSON.DeploymentData.RegionName
            $externalFQDN = $deploymentDataJSON.DeploymentData.ExternalDomainFQDN
        }
        $TestAzsPaaSCertificatesResults = foreach ($key in $PaaSCertificates.keys)
        {
            $PaaSCertificate = $PaaSCertificates[$key]
            Switch ($key)
            {
                'PaaSDBCert' { $ExpectedPrefix = '*.dbadapter' }
                'PaaSDefaultCert' { $ExpectedPrefix = '*.appservice','*.scm.appservice','*.sso.appservice' }
                'PaaSAPICert' { $ExpectedPrefix = 'api.appservice' }
                'PaaSFTPCert' { $ExpectedPrefix = 'ftp.appservice' }
                'PaaSSSOCert' { $ExpectedPrefix = 'sso.appservice' }
            }
            Test-AzSCertificate -PfxFile $PaaSCertificate.pfxPath -CertificatePassword $PaaSCertificate.pfxPassword -expectedPrefix $ExpectedPrefix -ExpectedDomainFQDN "$RegionName.$externalFQDN"
        }

        Write-Log -Message "Ending PaaS Certificate Validation" -Type Info -Function $thisfunction
        $TestAzsPaaSCertificatesResults
    }
    if ($PSCmdlet.ParameterSetName -eq 'ImportExport')
    {
        Write-Host ("Starting Azure Stack Certificate Import/Export {0} " -f $mod.Version)
        Write-Host "Importing PFX $pfxPath into Local Machine Store"
        $certificate = Import-AzsCertificate -pfxPath $pfxPath -pfxPassword $pfxPassword
        Write-Host "Exporting $($certificate.pspath) to $ExportPFXPath"
        Export-AzsCertificate -filePath $ExportPFXPath -certPath $certificate -pfxPassword $pfxPassword
        Write-Host "Export complete: $ExportPFXPath"
    }
}


# SIG # Begin signature block
# MIIdqAYJKoZIhvcNAQcCoIIdmTCCHZUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQU34JttV0r+q3qDBRWHAlxcb8a
# jp2gghhUMIIEwjCCA6qgAwIBAgITMwAAAL+RbPt8GiTgIgAAAAAAvzANBgkqhkiG
# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw
# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ5
# WhcNMTgwOTA3MTc1ODQ5WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046
# NTdDOC0yRDE1LTFDOEIxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCt7X+GwPaidVcV
# TRT2yohV/L1dpTMCvf4DHlCY0GUmhEzD4Yn22q/qnqZTHDd8IlI/OHvKhWC9ksKE
# F+BgBHtUQPSg7s6+ZXy69qX64r6m7X/NYizeK31DsScLsDHnqsbnwJaNZ2C2u5hh
# cKsHvc8BaSsv/nKlr6+eg2iX2y9ai1uB1ySNeunEtdfchAr1U6Qb7AJHrXMTdKl8
# ptLov67aFU0rRRMwQJOWHR+o/gQa9v4z/f43RY2PnMRoF7Dztn6ditoQ9CgTiMdS
# MtsqFWMAQNMt5bZ8oY1hmgkSDN6FwTjVyUEE6t3KJtgX2hMHjOVqtHXQlud0GR3Z
# LtAOMbS7AgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQU5GwaORrHk1i0RjZlB8QAt3kX
# nBEwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ
# oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB
# BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z
# b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAQEAjt62jcZ+2YBqm7RKit827DRU9OKioi6HEERT0X0bL+JjUTu3
# 7k4piPcK3J/0cfktWuPjrYSuySa/NbkmlvAhQV4VpoWxipx3cZplF9HK9IH4t8AD
# YDxUI5u1xb2r24aExGIzWY+1uH92bzTKbAjuwNzTMQ1z10Kca4XXPI4HFZalXxgL
# fbjCkV3IKNspU1TILV0Dzk0tdKAwx/MoeZN1HFcB9WjzbpFnCVH+Oy/NyeJOyiNE
# 4uT/6iyHz1+XCqf2nIrV/DXXsJYKwifVlOvSJ4ZrV40MYucq3lWQuKERfXivLFXl
# dKyXQrS4eeToRPSevRisc0GBYuZczpkdeN5faDCCBgEwggPpoAMCAQICEzMAAADE
# 6Yn4eoFQ6f8AAAAAAMQwDQYJKoZIhvcNAQELBQAwfjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2ln
# bmluZyBQQ0EgMjAxMTAeFw0xNzA4MTEyMDIwMjRaFw0xODA4MTEyMDIwMjRaMHQx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHjAcBgNVBAMTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
# ggEBAIiKuCTDB4+agHkV/CZg/HKILPr0o5eIlka3o8tfiS86My4ekXj6fKkfggG1
# essavAPKRuvFmff7BB3yhQr/Im6h8mc9xScY5Sgf9QSUQWPs47oVjO0TmjXeOHBU
# bzvsrUUJMEnBvo8wmQzLdsn3c5UWd9GLu5THCIUg7R6oNfFxwuB0AEuK0tyR69Z4
# /o36rWCIPb25H65il7/FhLGQrtavK9NU+zXazXGS5h7/7HFry38IdnTgEFFI1PEA
# yEhMowc15VkN/XycyOZa44X11poPH46m5IQXwdbKnx0Bx/1IpxOSM5chSDL4wiSi
# ALK+U8qDbilbge84boDzu+wTC+sCAwEAAaOCAYAwggF8MB8GA1UdJQQYMBYGCisG
# AQQBgjdMCAEGCCsGAQUFBwMDMB0GA1UdDgQWBBTL1mKEz2A56v9nwlzSyLurt8MT
# mDBSBgNVHREESzBJpEcwRTENMAsGA1UECxMETU9QUjE0MDIGA1UEBRMrMjMwMDEy
# K2M4MDRiNWVhLTQ5YjQtNDIzOC04MzYyLWQ4NTFmYTIyNTRmYzAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AAYWH9tXwlDII0+iUXjX7fj9zb3VwPH5G1btU8hpRwXVxMvs4vyZW5VfETgowAVF
# E+CaeYi8Zqvbu+sCVSO3PSN4QW2u+PEAWpSZihzMCZXQmhxEMKmlFse6R1v1KzSL
# n49YN8NOHK8iyhDN2IIQqTXwriLIjySmgYvfJxzkZh2JPi7/VwNNwW6DoDLrtLMv
# UFZdBrEVjMgdY7dzDOPWeiYPKpZFpzKDPpY+V0l3I4n+sRDHiuUIFVHFK1oxWzlq
# lqikiGuWKG/xxK7qvUUXzGJOgbVUGkeOmKVtwG4nxvgnH8jtIKkLsfHOC5qU4mqd
# aYOhNtdtIP6F1f/DuJc2Cf49FMGYFKnAhszvgsGrVSRDGLVIhXiG0PnSnT8Z2RSJ
# 542faCSIaDupx4BOJucIIUxj/ZyTFU0ztVZgT9dKuTiO/y7dsV+kQ2vJeM+xu2uP
# g2yHcqrqpfuf3RrWOfxkyW0+COV8g7GtvKO6e8+WVqR6WMsSR2LSIe/8PMQxC/cv
# PmSlN29gUD+3RJBPoAuLvn5Y9sdnh2HbnpjEyIzLb0fhwC6U7bH2sDBt7GpJqOmW
# dsi9CMT+O/WuczcGslbPGdS79ZTKhxzygGoBT7YbgXOz01siPzpYGN+I7mfESacv
# 3CWLPV7Q7DREkR28kQx2gj7vxNgtoQQCjkj5790CzwOiMIIGBzCCA++gAwIBAgIK
# YRZoNAAAAAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29t
# MRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEw
# NDAzMTMwMzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4k
# D+7Rp9FMrXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMk
# h53y9GccLPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDl
# KEYuJ6yGT1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gA
# SkdmEScpZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1U
# n68eeEExd8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIB
# pzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWz
# DzALBgNVHQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAU
# DqyCYEBWJ5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20x
# GTAXBgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNV
# HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w
# cm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQG
# CCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y3Jvc29mdFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQS
# ooxtYrhXAstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBT
# Fd1Pq5Lk541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2Oawpylbih
# OZxnLcVRDupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfr
# Tot/xTUrXqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWG
# zFFW6J1wlGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H21
# 46SodDW4TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4i
# IdBD6Svpu/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2
# sWo9iaF2YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1
# sMpjtHhUBdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/J
# mu5J4PcBZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0wggd6
# MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDla
# Fw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS6
# 8rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15
# ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+er
# CFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVc
# eaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGM
# XeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/
# U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwj
# p6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwC
# gl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1J
# MKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3co
# KPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfe
# nk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAw
# HQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoA
# UwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQY
# MBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1
# dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAC
# hkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1
# dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4D
# MIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBs
# AF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcN
# AQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjD
# ctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw
# /WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkF
# DJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3z
# Dq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEn
# Gn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1F
# p3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0Qax
# dR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AAp
# xbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//W
# syNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqx
# P/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIEvjCC
# BLoCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAMTp
# ifh6gVDp/wAAAAAAxDAJBgUrDgMCGgUAoIHSMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJ
# BDEWBBRqJOO+VJU5jZF38XcQwfXkjRzxTzByBgorBgEEAYI3AgEMMWQwYqBIgEYA
# TQBpAGMAcgBvAHMAbwBmAHQAIABBAHoAdQByAGUAUwB0AGEAYwBrACAAUABhAHIA
# dABuAGUAcgBUAG8AbwBsAGsAaQB0oRaAFGh0dHA6Ly9Db2RlU2lnbkluZm8gMA0G
# CSqGSIb3DQEBAQUABIIBAHyyFh5iK+q5dd/jnWUwT2//RAHPksiZ8xIMaUeO1Vht
# sxWvfe3PHQjK39asWUMMfXJ+QgF/awqJiivoUzdCVUpOvg7gjUwS/PkVhlmiezhP
# DLcS/LvIs3Gkh0J4RT7kvIvAPH2hiDgL4WlHUhiKWlRuH3kPjvZn1SmmZnbx8lzd
# ldPBEZCLOz/Np2N2xfWOqZzpaz3TwYWweduz+9Dlu5kLVrpcO57jmlLnB8tfsmZM
# 6MIytZaxJZJmJoWOIbxO9Rh/m7swhzTtFzV+Yn5VrxU3BjDJPz7KZnhfnTmUBLGw
# OC1zf3shvpyw0POZe99/cyWd4dVU4L+jNmkQyMi7t0ChggIoMIICJAYJKoZIhvcN
# AQkGMYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBAhMzAAAA
# v5Fs+3waJOAiAAAAAAC/MAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZI
# hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODA0MjcwOTI5MzVaMCMGCSqGSIb3DQEJ
# BDEWBBT7sVk30aDKAcMO1A9Si5d5c7lu1DANBgkqhkiG9w0BAQUFAASCAQBIXZ7/
# urFfuIwabblAajIy/GMwOBHkum0p3ndSaME3e1erinudiQvjlu8RdApVXRpwI1u8
# D94Jtf6W4oGODcjjoMJTG3ar8NhlPPvlP97bfhKLCInkClCHQEl92tQXs1hOdXfJ
# HaSphQXJ29iopTQ1+xXvLeh+ZbL4Bh0QTDvFK/3yRJbI1l97cmL7WZ2OabpZbpvB
# KtDXtaAZcO8JgfFHuteZa0awc9GQHL6xXTgBeycy3xMT1D1SQ7hXmf65si1XZgkp
# 7PzuaPi4QGRt06C3X2uNkAvA9kFrhnETuG+RlzvPv07b611Kk3DuIy5AGn1yCez7
# LCvUj7f1/2BLBTEX
# SIG # End signature block