CertificateValidation/PublicCertHelper.psm1

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


$ErrorActionPreference = 'Stop'

# Explicitly importing it as ERCS VM does not have it by default
Import-Module PKI
Import-LocalizedData LocalizedData -Filename PublicCertHelper.Strings.psd1 -ErrorAction SilentlyContinue

# workaround for https://github.com/PowerShell/JEA/issues/42
# can't use Import-PowerShellDataFile because PS5.1
Import-LocalizedData -FileName Microsoft.AzureStack.CertificateConfig.psd1 -BaseDirectory $PSScriptRoot -BindingVariable CertificateData
$certificateDefaults = $CertificateData.CertificateDefaults

$myCertPath = 'cert:\localmachine\my\'
if ($standalone)
{
    $publicCertRoot = $PSScriptRoot
    $WarnOnSelfSigned = $true
}
else
{
    $publicCertRoot = 'C:\CloudDeployment\Setup\Certificates'
}

$armPublicCertInfo = @{Path="$publicCertRoot\AAD\ARM Public";RecordPrefix=@("management")}
$armAdminCertInfo = @{Path="$publicCertRoot\AAD\ARM Admin";RecordPrefix=@("adminmanagement")}
$publicPortalCertInfo = @{Path="$publicCertRoot\AAD\Public Portal";RecordPrefix=@("portal")}
$adminPortalCertInfo = @{Path="$publicCertRoot\AAD\Admin Portal";RecordPrefix=@("adminportal")}
$keyvaultCertInfo = @{Path="$publicCertRoot\AAD\KeyVault";RecordPrefix=@("*.vault")}
$keyvaultAdminCertInfo = @{Path="$publicCertRoot\AAD\KeyVaultInternal";RecordPrefix=@("*.adminvault")}
$acsTableCertInfo = @{Path="$publicCertRoot\AAD\ACSTable";RecordPrefix=@("*.table")}
$acsQueueCertInfo = @{Path="$publicCertRoot\AAD\ACSQueue";RecordPrefix=@("*.queue")}
$acsBlobCertInfo = @{Path="$publicCertRoot\AAD\ACSBlob";RecordPrefix=@("*.blob")}
$adminHostingCertInfo = @{Path="$publicCertRoot\AAD\Admin Extension Host";RecordPrefix=@("*.adminhosting")}
$publicHostingCertInfo = @{Path="$publicCertRoot\AAD\Public Extension Host";RecordPrefix=@("*.hosting")}
$acrCertInfo = @{Path="$publicCertRoot\AAD\Container Registry";RecordPrefix=@("*.azsacr")}

$adfsCertInfo = @{Path="$publicCertRoot\ADFS\ADFS";RecordPrefix=@("adfs")}
$graphCertInfo = @{Path="$publicCertRoot\ADFS\Graph";RecordPrefix=@("graph")}
$armADFSPublicCertInfo = @{Path="$publicCertRoot\ADFS\ARM Public";RecordPrefix=@("management")}
$armADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\ARM Admin";RecordPrefix=@("adminmanagement")}
$publicADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Public Portal";RecordPrefix=@("portal")}
$adminADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Admin Portal";RecordPrefix=@("adminportal")}
$keyvaultADFSCertInfo = @{Path="$publicCertRoot\ADFS\KeyVault";RecordPrefix=@("*.vault")}
$keyvaultADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\KeyVaultInternal";RecordPrefix=@("*.adminvault")}
$acsTableADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSTable";RecordPrefix=@("*.table")}
$acsQueueADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSQueue";RecordPrefix=@("*.queue")}
$acsBlobADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSBlob";RecordPrefix=@("*.blob")}
$adminHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Admin Extension Host";RecordPrefix=@("*.adminhosting")}
$publicHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Public Extension Host";RecordPrefix=@("*.hosting")}
$acrADFSCertInfo = @{Path="$publicCertRoot\ADFS\Container Registry";RecordPrefix=@("*.azsacr")}

# This function appends the region and domainFQDN to the RecordPrefix/es and returns the array for multiple endpoints
function New-DNSList
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [string[]]
        $RecordPrefixList,

        [Parameter(Mandatory=$true)]
        [String]
        $RegionExternalFQDN
   )

    $dnsListCreated = @()
    foreach ($RecordPrefix in $RecordPrefixList)
    {
        $dnsListCreated += $RecordPrefix + ".$RegionExternalFQDN"
    }
    return $dnsListCreated
}

function New-SelfSignedCertificateWrapper
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string[]]
        $DnsRecord,

        [Parameter(Mandatory=$true)]
        [SecureString]
        $CertificatePassword,

        [Parameter(Mandatory=$true)]
        [string]
        $OutFilePath,

        [Parameter(Mandatory=$true)]
        [string]
        $RootCertThumbprint
    )

    # Creating if the path does not exist as SecretRotation BVT needs it
    if(-not (Test-Path $OutFilePath))
    {
        $null = New-Item -Path $OutFilePath -ItemType Directory
    }

    if(-not (Get-ChildItem -Path $OutFilePath -Filter '*.pfx'))
    {
        $OutFilePath = Join-Path -Path $OutFilePath -ChildPath "SSL.pfx"

        $rootCert = ( Get-ChildItem -path $RootCertThumbprint )

        $certThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -Signer $rootCert -KeyExportPolicy Exportable -Verbose:$false

        $certPath = $myCertPath + $certThumbprint.Thumbprint

        # It can sometimes take a few seconds to populate the store
        $retryCount = 0;

        while(-not (Test-Path $certPath))
        {
            Start-Sleep -Seconds 1

            if($retryCount++ -eq 10)
            {
                throw ($LocalizedData.FailedCreatingCert -f $certPath)
            }
        }

        $null = Export-PfxCertificate -FilePath $OutFilePath -Cert $certPath -Password $CertificatePassword -ChainOption BuildChain -Force -Verbose:$false
    }
}

function New-SelfSignedRootCert
{
    [Alias("Create-RootCert")]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $DnsRecord,

        [Parameter(Mandatory=$true)]
        [SecureString]
        $CertificatePassword
    )
    $baseRootCertThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment, CertSign -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Subject "AzureStackSelfSignedRootCert" -FriendlyName "Azs Self Signed RootCert"  -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -TextExtension @("2.5.29.19 ={critical} {text}ca=1&pathlength=3")
    $intermediateRootCertThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment, CertSign -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Subject "AzureStackSelfSignedIntermediate1Cert" -FriendlyName "Azs Self Signed Intermediate 1 Cert"  -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -TextExtension @("2.5.29.19 ={critical} {text}ca=1&pathlength=2") -Signer $baseRootCertThumbprint
    $secondIntermediateRootCertThumbprint = New-SelfSignedCertificate -KeyUsage DigitalSignature, KeyEncipherment, CertSign -HashAlgorithm SHA256 -KeyUsageProperty All -KeyLength 4096 -Subject "AzureStackSelfSignedIntermediate2Cert" -FriendlyName "Azs Self Signed Intermediate 2 Cert"  -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -certstorelocation $myCertPath -dnsname $DnsRecord -TextExtension @("2.5.29.19 ={critical} {text}ca=1&pathlength=0") -Signer $intermediateRootCertThumbprint

    $certPath = $myCertPath + $secondIntermediateRootCertThumbprint.Thumbprint

    # It can sometimes take a few seconds to populate the store
    $retryCount = 0;

    while(-not (Test-Path $certPath))
    {
        Start-Sleep -Seconds 1

        if($retryCount++ -eq 10)
        {
            throw "Failed to create self signed root certificate in store '$certPath'."
        }
    }

    return $certPath
}

# Use for one nodes and internal testing only!
function New-AzureStackSelfSignedCerts
{
    [CmdletBinding(SupportsShouldProcess=$true, ConfirmImpact='Medium')]
    Param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $RegionExternalFQDN,

        [Parameter(Mandatory=$true)]
        [SecureString]
        $CertificatePassword,

        [Parameter(Mandatory=$false)]
        [string]
        $ExternalCertRoot
    )

    if($ExternalCertRoot)
    {
        $publicCertRoot = $ExternalCertRoot

        $armPublicCertInfo = @{Path="$publicCertRoot\AAD\ARM Public";RecordPrefix=@("management")}
        $armAdminCertInfo = @{Path="$publicCertRoot\AAD\ARM Admin";RecordPrefix=@("adminmanagement")}
        $publicPortalCertInfo = @{Path="$publicCertRoot\AAD\Public Portal";RecordPrefix=@("portal")}
        $adminPortalCertInfo = @{Path="$publicCertRoot\AAD\Admin Portal";RecordPrefix=@("adminportal")}
        $keyvaultCertInfo = @{Path="$publicCertRoot\AAD\KeyVault";RecordPrefix=@("*.vault")}
        $keyvaultAdminCertInfo = @{Path="$publicCertRoot\AAD\KeyVaultInternal";RecordPrefix=@("*.adminvault")}
        $acsTableCertInfo = @{Path="$publicCertRoot\AAD\ACSTable";RecordPrefix=@("*.table")}
        $acsQueueCertInfo = @{Path="$publicCertRoot\AAD\ACSQueue";RecordPrefix=@("*.queue")}
        $acsBlobCertInfo = @{Path="$publicCertRoot\AAD\ACSBlob";RecordPrefix=@("*.blob")}
        $adminHostingCertInfo = @{Path="$publicCertRoot\AAD\Admin Extension Host";RecordPrefix=@("*.adminhosting")}
        $publicHostingCertInfo = @{Path="$publicCertRoot\AAD\Public Extension Host";RecordPrefix=@("*.hosting")}
        $acrCertInfo = @{Path="$publicCertRoot\AAD\Container Registry";RecordPrefix=@("*.azsacr")}

        $adfsCertInfo = @{Path="$publicCertRoot\ADFS\ADFS";RecordPrefix=@("adfs")}
        $graphCertInfo = @{Path="$publicCertRoot\ADFS\Graph";RecordPrefix=@("graph")}
        $armADFSPublicCertInfo = @{Path="$publicCertRoot\ADFS\ARM Public";RecordPrefix=@("management")}
        $armADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\ARM Admin";RecordPrefix=@("adminmanagement")}
        $publicADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Public Portal";RecordPrefix=@("portal")}
        $adminADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Admin Portal";RecordPrefix=@("adminportal")}
        $keyvaultADFSCertInfo = @{Path="$publicCertRoot\ADFS\KeyVault";RecordPrefix=@("*.vault")}
        $keyvaultADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\KeyVaultInternal";RecordPrefix=@("*.adminvault")}
        $acsTableADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSTable";RecordPrefix=@("*.table")}
        $acsQueueADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSQueue";RecordPrefix=@("*.queue")}
        $acsBlobADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSBlob";RecordPrefix=@("*.blob")}
        $adminHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Admin Extension Host";RecordPrefix=@("*.adminhosting")}
        $publicHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Public Extension Host";RecordPrefix=@("*.hosting")}
        $acrADFSCertInfo = @{Path="$publicCertRoot\ADFS\Container Registry";RecordPrefix=@("*.azsacr")}
    }
    else
    {
        Write-VerboseLog $LocalizedData.CreatingExternalSelfSignedCerts
    }

    # getting rootCert thumbprint
    $rootCertThumbprint = New-SelfSignedRootCert -DnsRecord "$RegionExternalFQDN" -CertificatePassword $CertificatePassword

    # AAD certs
    New-SelfSignedCertificateWrapper -OutFilePath $armPublicCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armPublicCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $armAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $publicPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $adminPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    #Creating three different ACS certs for Blob, Queue and Table: Another option is customers can provide one wilcard cert for all these three
    New-SelfSignedCertificateWrapper -OutFilePath $acsTableCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsTableCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsQueueCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsQueueCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsBlobCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsBlobCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    #Creating two additional Extension Host Certs
    New-SelfSignedCertificateWrapper -OutFilePath $adminHostingCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminHostingCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $publicHostingCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicHostingCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    #Creating Container Registry cert, which is optional but required in BCDR pipeline
    New-SelfSignedCertificateWrapper -OutFilePath $acrCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acrCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint

    # ADFS certs
    New-SelfSignedCertificateWrapper -OutFilePath $adfsCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adfsCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $graphCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $graphCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $armADFSPublicCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armADFSPublicCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $armADFSAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $armADFSAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $publicADFSPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicADFSPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $adminADFSPortalCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminADFSPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultADFSAdminCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $keyvaultADFSAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    #Creating three different ACS certs for Blob, Queue and Table: Another option is customers can provide one wilcard cert for all these three
    New-SelfSignedCertificateWrapper -OutFilePath $acsTableADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsTableADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsQueueADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsQueueADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsBlobADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acsBlobADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    #Creating two additional Extension Host Certs
    New-SelfSignedCertificateWrapper -OutFilePath $adminHostingADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $adminHostingADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $publicHostingADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $publicHostingADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    #Creating Container Registry cert, which is optional but required in BCDR pipeline
    New-SelfSignedCertificateWrapper -OutFilePath $acrADFSCertInfo.Path -DnsRecord (New-DNSList -RecordPrefixList $acrADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
}

function Test-AzureStackCerts
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [SecureString]
        $CertificatePassword,

        [Parameter(Mandatory=$true)]
        [string]
        $ExpectedDomainFQDN,

        [Parameter(Mandatory=$false)]
        [bool]
        $UseADFS = $false,

        [Parameter(Mandatory=$false)]
        [bool]
        $isACRInstalled = $false,

        [Parameter(Mandatory=$false)]
        [bool]
        $WarnOnSelfSigned = $true,

        [Parameter(Mandatory=$false)]
        [string]
        $PfxFilesPath,

        [Parameter(Mandatory=$false)]
        [Alias('SecretRotation')]
        [ValidateSet('SameRootOnly','AnyTrustedRoot','')]
        [string] $RootValidation = ''
    )
    $thisFunction = $MyInvocation.MyCommand.Name
    $publicCertHelperModuleVersion = $MyInvocation.MyCommand.Module.Version
    if (!$PfxFilesPath)
    {
        Write-Log -Message ($LocalizedData.ValidatingCerts -f $publicCertHelperModuleVersion) -Type Info -Function $thisfunction
    }
    else
    {
        # we are in external cert rotation, need to use path provided by customer
        Write-Log -Message ($LocalizedData.ValidatingCerts -f $publicCertHelperModuleVersion) -Type Info -Function $thisfunction
        $publicCertRoot = $PfxFilesPath

        $armPublicCertInfo = @{Path="$publicCertRoot\AAD\ARM Public";RecordPrefix=@("management")}
        $armAdminCertInfo = @{Path="$publicCertRoot\AAD\ARM Admin";RecordPrefix=@("adminmanagement")}
        $publicPortalCertInfo = @{Path="$publicCertRoot\AAD\Public Portal";RecordPrefix=@("portal")}
        $adminPortalCertInfo = @{Path="$publicCertRoot\AAD\Admin Portal";RecordPrefix=@("adminportal")}
        $keyvaultCertInfo = @{Path="$publicCertRoot\AAD\KeyVault";RecordPrefix=@("*.vault")}
        $keyvaultAdminCertInfo = @{Path="$publicCertRoot\AAD\KeyVaultInternal";RecordPrefix=@("*.adminvault")}
        $acsTableCertInfo = @{Path="$publicCertRoot\AAD\ACSTable";RecordPrefix=@("*.table")}
        $acsQueueCertInfo = @{Path="$publicCertRoot\AAD\ACSQueue";RecordPrefix=@("*.queue")}
        $acsBlobCertInfo = @{Path="$publicCertRoot\AAD\ACSBlob";RecordPrefix=@("*.blob")}
        $adminHostingCertInfo = @{Path="$publicCertRoot\AAD\Admin Extension Host";RecordPrefix=@("*.adminhosting")}
        $publicHostingCertInfo = @{Path="$publicCertRoot\AAD\Public Extension Host";RecordPrefix=@("*.hosting")}
        $acrCertInfo = @{Path="$publicCertRoot\AAD\Container Registry";RecordPrefix=@("*.azsacr")}

        $adfsCertInfo = @{Path="$publicCertRoot\ADFS\ADFS";RecordPrefix=@("adfs")}
        $graphCertInfo = @{Path="$publicCertRoot\ADFS\Graph";RecordPrefix=@("graph")}
        $armADFSPublicCertInfo = @{Path="$publicCertRoot\ADFS\ARM Public";RecordPrefix=@("management")}
        $armADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\ARM Admin";RecordPrefix=@("adminmanagement")}
        $publicADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Public Portal";RecordPrefix=@("portal")}
        $adminADFSPortalCertInfo = @{Path="$publicCertRoot\ADFS\Admin Portal";RecordPrefix=@("adminportal")}
        $keyvaultADFSCertInfo = @{Path="$publicCertRoot\ADFS\KeyVault";RecordPrefix=@("*.vault")}
        $keyvaultADFSAdminCertInfo = @{Path="$publicCertRoot\ADFS\KeyVaultInternal";RecordPrefix=@("*.adminvault")}
        $acsTableADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSTable";RecordPrefix=@("*.table")}
        $acsQueueADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSQueue";RecordPrefix=@("*.queue")}
        $acsBlobADFSCertInfo = @{Path="$publicCertRoot\ADFS\ACSBlob";RecordPrefix=@("*.blob")}
        $adminHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Admin Extension Host";RecordPrefix=@("*.adminhosting")}
        $publicHostingADFSCertInfo = @{Path="$publicCertRoot\ADFS\Public Extension Host";RecordPrefix=@("*.hosting")}
        $acrADFSCertInfo = @{Path="$publicCertRoot\ADFS\Container Registry";RecordPrefix=@("*.azsacr")}
    }

    if($UseADFS)
    {
        $allCertInfo = @(
                        $adfsCertInfo,
                        $graphCertInfo,
                        $armADFSPublicCertInfo,
                        $armADFSAdminCertInfo,
                        $publicADFSPortalCertInfo,
                        $adminADFSPortalCertInfo,
                        $keyvaultADFSCertInfo,
                        $keyvaultADFSAdminCertInfo,
                        $acsTableADFSCertInfo,
                        $acsQueueADFSCertInfo,
                        $acsBlobADFSCertInfo,
                        $adminHostingADFSCertInfo,
                        $publicHostingADFSCertInfo
        )
        # Validate ACR Certificate only if ACR has been installed on the stamp
        if($isACRInstalled)
        {
            $allCertInfo += $acrADFSCertInfo
        }
    }
    else
    {
        $allCertInfo = @(
                        $armPublicCertInfo,
                        $armAdminCertInfo,
                        $publicPortalCertInfo,
                        $adminPortalCertInfo,
                        $keyvaultCertInfo,
                        $keyvaultAdminCertInfo,
                        $acsTableCertInfo,
                        $acsQueueCertInfo,
                        $acsBlobCertInfo,
                        $adminHostingCertInfo,
                        $publicHostingCertInfo
        )
        # Validate ACR Certificate only if ACR has been installed on the stamp
        if($isACRInstalled)
        {
            $allCertInfo += $acrCertInfo
        }
    }

    $allCertResults = @()
    $allCertResults += $allCertInfo | ForEach-Object { `
        Write-Log -Message "Launching Test-Certificate with Path = $($PSITEM.path), ExpectedPrefix = $($PSITEM.RecordPrefix), ExpectedDomainFQDN = $ExpectedDomainFQDN, WarnOnSelfSigned = $WarnOnSelfSigned, RootValidation = $RootValidation" -Type Info -Function $thisfunction
        $testCertificateParams = @{
            CertificatePath         = $PSITEM.Path
            CertificatePassword     = $CertificatePassword
            certConfig              = @{DNSName = $PSITEM.RecordPrefix;IncludeTests = 'All';ExcludeTests = 'CNG Key';pfxPath = $PSITEM.path}
            ExpectedDomainFQDN      = $ExpectedDomainFQDN
            WarnOnSelfSigned        = $WarnOnSelfSigned
            RootValidation          = $RootValidation
        }

        Test-Certificate @testCertificateParams
    }
    return $allCertResults
}

function Test-Certificate
{
    Param
    (
        # Path of folder which contains the cert
        [Parameter(Mandatory=$true)]
        [string]
        $CertificatePath,

        [Parameter(Mandatory=$true)]
        [SecureString]
        $CertificatePassword,

        [Parameter(Mandatory=$true)]
        [string]
        $ExpectedDomainFQDN,

        [Parameter(Mandatory=$false)]
        [bool]
        $WarnOnSelfSigned = $true,

        [Parameter(Mandatory=$false)]
        [Alias('SecretRotation')]
        [ValidateSet('SameRootOnly','AnyTrustedRoot','')]
        [string] $RootValidation = '',

        [Parameter(Mandatory=$false)]
        [Hashtable]$certConfig
    )

    $thisFunction = $MyInvocation.MyCommand.Name
    $ErrorActionPreference = 'SilentlyContinue'

    if(-not (Test-Path -Path $CertificatePath))
    {
        # Terminating error for install, need to throw
        throw ($LocalizedData.IncorrectPath -f $CertificatePath)
    }

    $script:pfxFile = Get-ChildItem -Path $CertificatePath -Filter '*.pfx' | ForEach-Object FullName
    Write-Log -Message "Test PFX Certificate $pfxfile" -Type info -Function $thisFunction
    if($pfxFile.Count -ne 1)
    {
        # Terminating error for install
        Write-Log -Message ($LocalizedData.MoreThanOneCert -f $CertificatePath) -Type Error -Function $thisFunction
    }

    if(-not (Test-Path -Path $pfxFile))
    {
        # Terminating error for install
        Write-Log ($LocalizedData.MissingCertificate) -Type Error -Function $thisFunction
    }

    if ((Get-Command Get-Content).Parameters.AsByteStream) {
        [byte[]]$pfxBinary = Get-Content -Path $pfxFile -AsByteStream
    }
    else {
        [byte[]]$pfxBinary = Get-Content -Path $pfxFile -Encoding Byte
    }
    #$certConfig.pfxPath = $pfxFile


    $params = @{
        CertificateBinary       = $pfxBinary
        CertificatePassword     = $CertificatePassword
        ExpectedDomainFQDN      = $ExpectedDomainFQDN
        WarnOnSelfSigned        = $WarnOnSelfSigned
        RootValidation          = $RootValidation
        certConfig              = $certConfig
    }
    Test-AzsCertificate @params
}

function Test-AzsCertificate
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [byte[]] $CertificateBinary,

        [Parameter(Mandatory=$false)]
        [ValidateNotNull()]
        [SecureString] $CertificatePassword,

        [Parameter(Mandatory=$false)]
        [string] $ExpectedDomainFQDN,

        [Parameter(Mandatory=$false)]
        [bool] $WarnOnSelfSigned = $true,

        [Parameter(Mandatory=$false)]
        [Alias('SecretRotation')]
        [ValidateSet('SameRootOnly','AnyTrustedRoot','')]
        [string] $RootValidation = '',

        [Parameter(Mandatory=$false)]
        [Hashtable]$certConfig

    )
    $thisFunction = $MyInvocation.MyCommand.Name
    $results = @()

    $ExpectedPrefix = $certConfig.DnsName

    # Get Relative Path
    # Depending on the entry point (deployment, secret rotation, standalone) the path can be in 1 of 2 places
    if ($pfxFile -or $certConfig.pfxPath)
    {
        if ($pfxFile) {
            $pf = Get-item -Path $pfxFile
        }
        else {
            $pf = Get-item -Path $certConfig.pfxPath
        }
        $pathValue = $pf.Directory.Name + '\' + $pf.name
        Write-Host "Testing: $pathValue"
        # Check PFX encryption first in case we have difficulty with encryption used and defer printing result until after import attempt for formatting purposes
        $pfxEncryptCheck = Test-PFXEncryption -pfxfile $pf.fullname -pfxpassword $certificatePassword -certConfig $certConfig
        $results += $pfxEncryptCheck
        if ($pfxEncryptCheck.result -eq 'Fail')
        {
            Write-Result -in $pfxEncryptCheck
            Write-Result -in $results.failureDetail
            throw "Unable to continue until PFX Encryption is TripleDES-SHA1"
        }
    }

    # Begin Checks
    $ErrorActionPreference = 'Stop' # make sure errors are not suppressed during import.
    if ($CertificatePassword)
    {
        try
        {
            $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($CertificateBinary, $CertificatePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)
            $includePFXChecks = $true
        }
        catch
        {
            # if the exception is bad password and encryption check failed/warned, give additional help about ensuring Triple-DES encryption.
            if ($_.Exception.ErrorRecord -match 'The specified network password is not correct' -and $pfxEncryptCheck.result -ne 'OK')
            {
                Write-Log -Message ($LocalizedData.BadPasswordAndUnknownEncryption -f $_.Exception.Message.Replace("`r`n","")) -Type Error -Function $thisFunction
                throw ($LocalizedData.BadPasswordAndUnknownEncryption -f $_.Exception.Message.Replace("`r`n",""))
            }
            elseif ($_.Exception.ErrorRecord -match 'Cannot find the requested object')
            {
                Write-Log -Message ($LocalizedData.ErrorOnX509Import) -Type Error -Function $thisFunction
                throw $LocalizedData.ErrorOnX509Import
            }
            else
            {
                Write-Log -Message ("Importing Certificate failed with message: {0}" -f $_.Exception.Message.Replace("`r`n","")) -Type Error -Function $thisFunction
                throw ("Importing Certificate failed with message: {0}" -f $_.Exception.Message.Replace("`r`n",""))
            }
        }
    }
    else {
        $cert.Import($CertificateBinary)
        $includePFXChecks = $false
    }

    Write-Host ("Thumbprint: {0}" -f ($cert | Get-ThumbprintMask))
    # if PFXEncryption Check exists write the result to screen.
    if ($pfxEncryptCheck)
    {
        Write-Result -in $pfxEncryptCheck
    }

    $ErrorActionPreference = 'SilentlyContinue' #Setting erroraction to continue so tests can complete and user gets full break down of all issues.

    $expiryCheck = Test-CertificateExpiry -cert $cert -certConfig $certConfig
    Write-Result -in $expiryCheck
    $results += $expiryCheck

    $signatureAlgorithmCheck = Test-SignatureAlgorithm -x509 $cert -certConfig $certConfig
    Write-Result -in $signatureAlgorithmCheck
    $results += $signatureAlgorithmCheck

    $dnsNamesCheck = Test-DnsNames -cert $cert -ExpectedDomainFQDN $ExpectedDomainFQDN -certConfig $certConfig
    Write-Result -in $dnsNamesCheck
    $results += $dnsNamesCheck

    $keyUsageCheck = Test-KeyUsage -cert $cert -certConfig $certConfig
    Write-Result -in $keyUsageCheck
    $results += $keyUsageCheck

    $keySizeCheck = Test-KeySize -cert $cert -certConfig $certConfig
    Write-Result -in $keySizeCheck
    $results += $keySizeCheck

    $httpCDPCheck = Test-HttpCdp -cert $cert -certConfig $certConfig
    Write-Result -in $httpCDPCheck
    $results += $httpCDPCheck

    if ($includePFXChecks)
    {
        $pfxParseResult = Test-Pfx -certificateBinary $CertificateBinary -certificatePassword $certificatePassword
        if ($pfxParseResult.Result -eq 'Fail')
        {
            Write-Log -Message ("Unable to Parse PFX. Ensure PFX is correctly formatted. Error: {0}" -f ($pfxParseResult.failureDetail -join ';')) -Type Error -Function $thisFunction
            throw ("Unable to Parse PFX. Ensure PFX is correctly formatted. Error: {0}" -f ($pfxParseResult.failureDetail -join ';'))
        }
        $pfxData = $pfxParseResult.outputObject
        $pfxParseResult.outputObject = "" # clear the output object
        Write-Result -in $pfxParseResult
        $results += $pfxParseResult

        $privateKeyCheck = Test-PrivateKey -cert $cert -certConfig $certConfig
        Write-Result -in $privateKeyCheck
        $results += $privateKeyCheck

        $selfSignedCheck = Test-CertificateChain -pfxData $pfxData -certConfig $certConfig
        Write-Result -in $selfSignedCheck
        $results += $selfSignedCheck

        $chainOrderCheck = Test-CertificateChainOrder -pfxData $pfxData -certConfig $certConfig
        Write-Result -in $chainOrderCheck
        $results += $chainOrderCheck

        # Only run check for other certificates if cert chain is good and dnsnames are good to avoid noise.
        if ($selfSignedCheck.result -eq 'OK' -AND $dnsNamesCheck.result -eq 'OK')
        {
            $otherCertificateCheck = Test-OtherCertificates -pfxData $pfxData -ExpectedPrefix $ExpectedPrefix -ExpectedDomainFQDN $ExpectedDomainFQDN -certConfig $certConfig
        }
        Else
        {
            $hash = @{'Test' = 'Other Certificates'; 'Result' = 'Skipped'; 'FailureDetail' = $LocalizedData.TestOtherCertificatesSkipped; 'outputObject' = $null}
            $otherCertificateCheck = New-Object PSObject -Property $hash
        }
        Write-Result -in $otherCertificateCheck
        $results += $otherCertificateCheck

        if (-not $standalone -AND $RootValidation -ne '')
        {
            if ($RootValidation -eq 'SameRootOnly')
            {
                $rootCheck = Test-CertificateRoot -pfx $pfxData -certConfig $certConfig
                Write-Result -in $rootCheck
                $results += $rootCheck
            }
            else
            {
                #Check trust without using pfx as extra store
                $chainTest = Test-TrustedChain -cert $cert -retryCount 3 -intervalSeconds 5 -certConfig $certConfig
                if ($chainTest.Result -eq 'Fail')
                {
                    #create certificate collection object
                    $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
                    $pfxdata.EndEntityCertificates + $pfxData.OtherCertificates | ForEach-Object {$collection.add($_) | Out-Null}

                    #create new chain
                    $chain = New-Object Security.Cryptography.X509Certificates.X509Chain
                    $chain.ChainPolicy.ExtraStore.AddRange($collection)

                    #Check trust with pfx as extra store
                    $chainTest = Test-TrustedChain -chain $chain -cert $cert -retryCount 3 -intervalSeconds 5
                }
                else
                {
                    Write-Log -Message ("Chain trust passed with {0} - {1}. No further action." -f $chainTest.Result,($chainTest.Chain.ChainStatus.Status -join ',')) -Type Info -Function $thisFunction
                }
                Write-Result -in $chainTest
                $results += $chainTest
            }
        }
    }

    # add relative path, thumbprint, certificateID
    $results | add-member -NotePropertyName CertificateId -NotePropertyValue ([guid]::NewGuid().ToString())
    $results | add-member -NotePropertyName Path -NotePropertyValue $pathValue
    $results | add-member -NotePropertyName Thumbprint -NotePropertyValue $($cert | Get-ThumbprintMask)

    if ($results.result -match "Fail|Warning")
    {
        Write-Result -in $results.failureDetail
    }
    return $results
}

function Test-Pfx
{
    param ([byte[]]$certificateBinary, [securestring]$certificatePassword)
    $thisFunction = $MyInvocation.MyCommand.Name

    $test = 'Parse PFX'
    Write-Log -Message "Parse PFX binary with password" -Type Info -Function $thisFunction
    $parseResult = Open-PfxData -certificateBinary $certificateBinary -certificatePassword $certificatePassword

    if ($parseResult.Success)
    {
        $tmpParseResult = Open-PfxData -certificateBinary $certificateBinary
        if ($tmpParseResult.Success)
        {
            $result = 'Warning'
            $failureDetail = ($LocalizedData.UnprotectedPublicCertInfo)
            Write-Log -Message $LocalizedData.UnprotectedPublicCertInfo -Type Warning -Function $thisFunction
        }
        else
        {
            $result = 'OK'
            if ($tmpParseResult.ErrorCode -eq 86)
            {
                Write-Log -Message "Parsing PFX binary privacy success" -Type Info -Function $thisFunction
            }
            else
            {
                Write-Log -Message ("Parsing PFX binary without password with error code 0x{0:x}" -f $($tmpParseResult.ErrorCode)) -Type Warn -Function $thisFunction
            }
        }
    }
    else
    {
        $result = 'Fail'

        if ($parseResult.ErrorCode -in @(0x80092002, 0x0D))
        {
            $failureDetail = "Pfx data is Invalid"
            # Terminating error for install
            Write-Log -Message "Pfx data is Invalid" -Type Error -Function $thisFunction
        }
        elseif ($parseResult.ErrorCode -eq 86)
        {
            $failureDetail = "Pfx password is Invalid"
            # Terminating error for install
            Write-Log -Message $failureDetail -Type Error -Function $thisFunction
        }
        else
        {
            # Terminating error for install
            $errorMessage = "Parsing PFX binary with error code 0x{0:x}" -f $($parseResult.ErrorCode)
            $failureDetail = $errorMessage
            Write-Log -Message $errorMessage -Type Error -Function $thisFunction
        }
    }

    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $parseResult}
    $object = New-Object PSObject -Property $hash

    $object
}

function Test-SignatureAlgorithm
{
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$x509,[Hashtable]$certConfig)
    # Name for log and name for test
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Signature Algorithm'

    # config to run test by
    if ($certConfig.HashAlgorithm -eq 'default' -or $null -eq $certConfig.HashAlgorithm)
    {
        $blockedAlgorithms = 'SHA1RSA'
    }
    else
    {
        $blockedAlgorithms = $certConfig.HashAlgorithm
        Write-Log -Message ('Using user-defined Signature Algorithms {0} for test.' -f ($blockedAlgorithms -join ',')) -Type Info -Function $thisFunction
    }

    # run test if applicable
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        Write-Log -Message ('Checking Signature Algorithm is not {0}' -f ($blockedAlgorithms -join ',')) -Type Info -Function $thisFunction
        $signatureAlgorithm = $x509.SignatureAlgorithm.FriendlyName
        Write-Log -Message ('Signature Algorithm is {0}' -f $signatureAlgorithm) -Type Info -Function $thisFunction
        if ($signatureAlgorithm -in $blockedAlgorithms)
        {
            $result = 'Fail'
            $failureDetail = ($LocalizedData.SignatureAlgorithmInvalid -f $signatureAlgorithm, ($blockedAlgorithms -join ','))
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
        else
        {
            $result = 'OK'
            Write-Log -Message ('Signature Algorithm is not {0}' -f ($blockedAlgorithms -join ',')) -Type Info -Function $thisFunction
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    # return output
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-PrivateKey
{
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[Hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Private Key'

    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        $privateKeyFailed = @()
        $failureDetail = @()
        try
        {
            Write-Log -Message 'Checking Private Key exists' -Type Info -Function $thisFunction
            if(-not $cert.HasPrivateKey)
            {
                $privateKeyFailed +=  $true
                $failureDetail += ($LocalizedData.NoPrivateKey)
                Write-Log -Message ($LocalizedData.NoPrivateKey) -Type Warn -Function $thisFunction
            }
            Else
            {
                # Check if certificate has been exported from user store.
                Write-Log -Message 'Private Key Exists, checking Local Machine Key Attribute' -Type Info -Function $thisFunction
                $key = [System.Security.Cryptography.X509Certificates.RSACertificateExtensions]::GetRSAPrivateKey($cert)
                Write-Log -Message ('Private Key Provider: {0}' -f $key.Key.Provider) -Type Info -Function $thisFunction
                if(-not $key.Key.IsMachineKey)
                {
                    $privateKeyFailed += $true
                    $failureDetail += ($LocalizedData.NotMachineKeyStore)
                    Write-Log -Message ($LocalizedData.NotMachineKeyStore) -Type Warn -Function $thisFunction
                }
                else
                {
                    $privateKeyFailed += $false
                    Write-Log -Message 'Private Key Exists and Local Machine Key Attribute exists' -Type Info -Function $thisFunction
                }
                if('CNG key' -notin $certConfig.ExcludeTests)
                {
                    Write-Log -Message 'Checking PaaS for CNG key...' -Type Info -Function $thisFunction
                    if($key.Key.Provider -eq 'Microsoft Software Key Storage Provider')
                    {
                        $privateKeyFailed += $true
                        $failureDetail += "CNG Certificate detected, support for this certificate type may not currently be available. Please use https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-get-pki-certs to generate the certificates."
                        Write-Log -Message 'Microsoft Software Key Storage Provider cannot be used for this certificate.' -Type Warn -Function $thisFunction
                    }
                }
            }
        }
        catch
        {
            $privateKeyFailed += $true
            $failureDetail += $_.exception.message
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
        if ($privateKeyFailed -notcontains $true)
        {
            $result = 'OK'
        }
        else
        {
            $result = 'Fail'
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-CertificateChain
{
    param ([Hashtable]$pfxData,[Hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Cert Chain'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        Write-Log -Message 'Checking Certificate chain' -Type Info -Function $thisFunction
        # Check for chain of trust. Not validating signature algorithm on root or intermediates

        $otherCertificates = $pfxData.OtherCertificates
        $cert = $pfxData.EndEntityCertificates

        if(-not $otherCertificates)
        {
            Write-Log -Message 'No other certificates found in pfx' -Type Info -Function $thisFunction
            if($cert.Issuer -eq $cert.Subject)
            {
                $failureDetail = ($LocalizedData.SelfSignedCertificate -f $cert.Subject)
                Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
                $result = 'Fail'
            }
            else
            {
            $result = 'Fail'
            $failureDetail = ($LocalizedData.NoChainOfTrust)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
            }
        }
        else
        {
            if ($cert.Issuer -in $otherCertificates.subject)
            {
            $result = 'OK'
            Write-Log -Message ('The issuer certificate from {0} is included in the PFX' -f $cert.Issuer) -Type Info -Function $thisFunction
            }
            Else
            {
            $result = 'Fail'
            $failureDetail = ($LocalizedData.NoChainOfTrust)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
            }
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-DNSNames
{
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,$ExpectedDomainFQDN,[Hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'DNS Names'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        # Get the records between cn="<records>"
        $records = @()
        $records = $cert.DnsNameList.Unicode
        $recordsString = $records -join ', '
        Write-Log -Message ('DNS Names on certificate: {0}' -f $recordsString) -Type Info -Function $thisFunction
        $dnsNameFailed = @()
        foreach($prefix in $certConfig.DnsName)
        {
            # make prefix and fqdn array and join by '.' because if one doesn't exist it will not leave periods.
            if ($ExpectedDomainFQDN) {
                $fullExpectedRecord = ($prefix,$ExpectedDomainFQDN) -join '.'
            }
            else {
                $fullExpectedRecord = $prefix
            }

            Write-Log -Message ('Testing for full expected record: {0}' -f $fullExpectedRecord) -Type Info -Function $thisFunction
            if($records -eq $fullExpectedRecord)
            {
                $dnsNameFailed += $false
                Write-Log -Message ('Records: {0} match: {1}' -f $recordsString,($fullExpectedRecord -join ', ')) -Type Info -Function $thisFunction
                continue
            }
            elseif ($records -eq "*." + $fullExpectedRecord.split('.',2)[1])
            {
                $dnsNameFailed += $false
                Write-Log -Message ('Records: {0} match wildcard: {1}' -f $recordsString,("\*." + $fullExpectedRecord.split('.',2)[1])) -Type Info -Function $thisFunction
            }
            else
            {
                $failureDetail += ($LocalizedData.MissingRecord -f @($recordsString, $fullExpectedRecord))
                Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
                $dnsNameFailed += $true
            }
            if ($prefix -eq 'adfs')
            {
                Write-Log -Message ('Testing ADFS certificate subject {0} for ADFS compatability' -f $cert.subject) -Type Info -Function $thisFunction
                if ($cert.SubjectName -notlike '*.*')
                {
                    $dnsNameFailed += $true
                    $failureDetail += ($LocalizedData.DotlessADFSSubject -f $cert.Subject,$fullExpectedRecord)
                    Write-Log -Message ($LocalizedData.DotlessADFSSubject -f $cert.Subject,$fullExpectedRecord) -Type Warn -Function $thisFunction
                }
                else
                {
                    Write-Log -Message ('ADFS certificate subject {0} compatible for ADFS' -f $cert.subject) -Type Info -Function $thisFunction
                }
            }
        }
        if ($dnsNameFailed -notcontains $true)
        {
            $result = 'OK'
        }
        else
        {
            $result = 'Fail'
            $failureDetail += ($LocalizedData.CheckDocumentation)
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-KeyUsage
{
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Key Usage'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        # Validating KeyUsage should have Digital Signature, and Key Encipherment.
        # EnhancedKeyUsage should have Server Authentication and Client Authentication
        # Data Encipherment no longer required

        if ($certConfig.KeyUsage -eq 'default' -or $null -eq $certConfig.KeyUsage) {
            Write-Log -Message ('Testing for default Key Usage') -Type Info -Function $thisFunction
            $keyUsageArray = $certificateDefaults.KeyUsage.Keys
        }
        else {
            [array]$keyUsageArray = $certConfig.KeyUsage
        }

        if ($certConfig.EnhancedKeyUsage -eq 'default' -or $null -eq $certConfig.EnhancedKeyUsage) {
            Write-Log -Message ('Testing for default Enhanced Key Usage') -Type Info -Function $thisFunction
            $enhancedKeyUsageArray = $certificateDefaults.EnhancedKeyUsage.Keys | Foreach-Object { $certificateDefaults.EnhancedKeyUsage[$PSITEM].Oid }
        }
        else {
            Write-Log -Message ('Testing for custom Enhanced Key Usage') -Type Info -Function $thisFunction
            $certConfig.EnhancedKeyUsage = $certConfig.EnhancedKeyUsage | Foreach-Object { ConvertTo-Oid $PSITEM }
            [array]$enhancedKeyUsageArray = $certConfig.EnhancedKeyUsage | Foreach-Object { $PSITEM.Value }
        }

        # Create emtpy arrays for result handling.
        $keyUsageFailed = @()
        $failureDetail = @()

        $keyUsage = $cert.Extensions.KeyUsages
        $enhancedKeyUsage = $cert.EnhancedKeyUsageList.ObjectId

        # Check KeyUsage
        Write-Log -Message ('Testing for expected Key Usage {0}' -f ($keyUsageArray -join ',')) -Type Info -Function $thisFunction
        Write-Log -Message ('Certificate key usage is {0}' -f ($keyUsage -join ',')) -Type Info -Function $thisFunction
        foreach ($requiredKeyUsage in $keyUsageArray)
        {
            if ($keyUsage -notmatch $requiredKeyUsage)
            {
                $keyUsageFailed += $true
                $failureDetail += ($LocalizedData.IncorrectKeyUsage -f $keyUsage,($requiredKeyUsage -join ','))
                Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
            }
            else
            {
                $keyUsageFailed += $false
            }
        }

        # Check Enhanced KeyUsage
        Write-Log -Message ('Testing for expected Enhanced Key Usage {0}' -f (($certConfig.EnhancedKeyUsage | Foreach-Object { "{0} ({1})" -f $PSITEM.FriendlyName,$PSITEM.Value }) -join ',')) -Type Info -Function $thisFunction
        Write-Log -Message ('Certificate Enhanced key usage is {0}' -f ($enhancedKeyUsage -join ',')) -Type Info -Function $thisFunction
        foreach ($requiredEku in $enhancedKeyUsageArray)
        {
            if ($enhancedKeyUsage -notcontains $requiredEku)
            {
                $keyUsageFailed += $true
                if ($enhancedKeyUsage)
                {
                    $CertFriendlyNames = $cert.EnhancedKeyUsageList.FriendlyName | Foreach-Object {if (!$PSITEM) { 'Custom Oid' }else { $PSITEM }}
                    $RequiredUsageStrings = $certConfig.EnhancedKeyUsage | Foreach-Object { "{0} ({1})" -f $PSITEM.FriendlyName,$PSITEM.Value }
                    $failureDetail += ($LocalizedData.IncorrectEnhancedKeyUsage -f ($CertFriendlyNames -join ','),($RequiredUsageStrings -join ','))
                    Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
                }
                else
                {
                    $failureDetail += ($LocalizedData.IncorrectEnhancedKeyUsage -f '[Missing]',($enhancedKeyUsageArray -join ','))
                    Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
                }
            }
            else
            {
                $keyUsageFailed += $false
            }
        }

        # Check overall results
        if ($keyUsageFailed -notcontains $true)
        {
            $result = 'OK'
            Write-Log -Message 'Certificate key usage succeeded' -Type Info -Function $thisFunction
        }
        else
        {
            $result = 'Fail'
            Write-Log -Message 'Certificate key usage failed' -Type Warn -Function $thisFunction
        }

    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = ($failureDetail | Sort-Object | Get-Unique); 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-CertificateChainOrder
{
    param ([Hashtable]$pfxData,[Hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Chain Order'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        Write-Log -Message 'Checking Certificate Chain Order' -Type Info -Function $thisFunction
        # Validating cert chain order
        $otherCertificates = $pfxData.OtherCertificates

        if ($otherCertificates[-1].Issuer -ne $otherCertificates[-1].Subject)
        {
            $result = 'Fail'
            $failureDetail = ($LocalizedData.IncorrectCertChainOrder)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
        Else
        {
            $result = 'OK'
            Write-Log -Message 'Certificate Chain Order succeeded' -Type Info -Function $thisFunction
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }

    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

Function Test-OtherCertificates
{
    param ([Hashtable]$pfxData,[string]$ExpectedDomainFQDN,[Hashtable]$certConfig)

    $test = 'Other Certificates'
    $thisFunction = $MyInvocation.MyCommand.Name
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        Write-Log -Message "Checking Pfx file for additional certificates" -Type Info -Function $thisFunction

        $otherCertificates = $pfxData.OtherCertificates
        $cert = $pfxData.EndEntityCertificates
        $allcerts = $otherCertificates + @($cert)

        $otherCertificatesFailed = @()
        foreach ($prefix in $certConfig.DNSName)
        {
            # Apply literal to any wildcard on the expect DNSName.
            # make prefix and fqdn array and join by '.' because if one doesn't exist it will not leave periods.
            if ($ExpectedDomainFQDN) {
                $expectedDNSName = (($prefix,$ExpectedDomainFQDN) -join '.') -replace "\*", "\*"
            }
            else {
                $expectedDNSName = $prefix -replace "\*", "\*"
            }


            Write-Log -Message "Checking Pfx file for additional certificate with context of DNS Name: $prefix.$ExpectedDomainFQDN" -Type Info -Function $thisFunction
            # Find expected certificate according to DNSName literally or by matching wildcard.
            $targetCert = $allcerts | Where-Object {$_.dnsnamelist.unicode -match $expectedDNSName -OR $_.dnsnamelist.unicode -match "\*." + $expectedDNSName.split('.',2)[1]}

            # Remove all certs that are the target cert or an issuer of any certificate in the array.
            $unexpectedCerts = $allcerts | Where-Object {$_.Subject -notin $allcerts.Issuer -AND $_.thumbprint -ne $targetCert.Thumbprint}

            # Find expected certs for logging.
            $validCerts = $allcerts | Where-Object {$_.thumbprint -notin $unexpectedCerts.thumbprint}

            if ($unexpectedCerts)
            {
                $otherCertificatesFailed += $true
                $failureDetail += ($LocalizedData.UnwantedCertificatesInPfx -f ($unexpectedCerts | Get-ThumbprintMask),($validCerts | Get-ThumbprintMask))
                Write-Log -Message $failureDetail -Type Warning -Function $thisFunction
            }
            Else
            {
                $otherCertificatesFailed += $false
            }
        }

        if ($otherCertificatesFailed -notcontains $true)
        {
            $result = 'OK'
            Write-Log -Message ('No additional certificates were detected. Validcert thumbprints {0}' -f ($validCerts | Get-ThumbprintMask)) -Type Info -Function $thisFunction
        }
        else
        {
            $result = 'Fail'
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}
function Test-KeySize
{
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[Hashtable]$certConfig)
    $test = 'Key Length'
    $thisFunction = $MyInvocation.MyCommand.Name

    if ($certConfig.KeyLength -eq 'default' -or $null -eq $certConfig.KeyLength)
    {
        $keySizeLowerLimit = 2048
    }
    else
    {
        [int]$keySizeLowerLimit = $certConfig.KeyLength
    }

    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        #get the key length of the public key.
        $keySize = $cert.publickey.key.KeySize

        Write-Log -Message ("Checking Certificate for Key Length {0}" -f $keySizeLowerLimit) -Type Info -Function $thisFunction
        Write-Log -Message ("Certificate Key Length {0}" -f $keySize) -Type Info -Function $thisFunction
        if ($keySize -lt $keySizeLowerLimit)
        {
            $result = 'Fail'
            $failureDetail = ($LocalizedData.WrongKeySize -f $keySize,$keySizeLowerLimit)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
        else
        {
            $result = 'OK'
            Write-Log -Message 'Certificate Key Length succeeded' -Type Info -Function $thisFunction
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-PFXEncryption
{
    # mandatory check
    param ($pfxFile, [ValidateNotNullOrEmpty()][SecureString]$pfxpassword,[Hashtable]$certConfig)
    $test = 'PFX Encryption'
    $thisFunction = $MyInvocation.MyCommand.Name
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        Write-Log -Message ("Checking PFX Encryption is tripleDES-SHA1.") -Type Info -Function $thisFunction
        $networkCredential = New-Object System.Net.NetworkCredential -ArgumentList @("", $pfxpassword)
        $plainCertPassword = $networkCredential.Password

        try
        {
            $cmd = "-p {0} -dumpPFX `"{1}`"" -f $plainCertPassword,$pfxfile
            $certutilOutputPath = "$ENV:TEMP\AzsRCCertUtil.log"
            $null = Start-Process -FilePath certutil.exe `
                        -ArgumentList $cmd `
                        -WindowStyle Hidden `
                        -PassThru `
                        -Wait `
                        -RedirectStandardOutput $certutilOutputPath

            $certutilOutput = Get-Content -path $certutilOutputPath
            $TripleDESMatch = $certutilOutput | Select-String -SimpleMatch "1.2.840.113549.1.12.1.3"
            if ($TripleDESMatch)
            {
                Write-Log -Message ('PFX {0} Encryption is tripleDES-SHA1. CertUtil output {1}' -f $pfxFile,($TripleDESMatch -join ' ::Match:: ')) -Type Info -Function $thisFunction
                $result = 'OK'
            }
            else
            {
                if ($certutilOutput -match 'CertUtil: Unknown arg: -dumppfx')
                {
                    $failureDetail = $LocalizedData.DumpPfxParamFail
                    $result = 'Skipped'
                }
                else
                {
                    $failureDetail = $LocalizedData.IncorrectPFXEncryption -f (($certutilOutput | Select-String -SimpleMatch "1.2.840.113549") -join "` r`n::Match:: ")
                    $result = 'Warning'
                }
                Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
            }
        }
        catch
        {
            Write-Log -Message ('Unable to determine PFX encryption. Run CertUtil -dumppfx <filename> to check encryption. Checking PFX encryption failed with exception: {0}`n OID dump: {1}' -f $_.exception,(($certutilOutput | Select-String -SimpleMatch "1.2.840.113549") -join "` r`n::Match:: ")) -Type Warn -Function $thisFunction
            $failureDetail = $LocalizedData.IncorrectPFXEncryption
            $result = 'Fail'
        }
        finally
        {
            Remove-item $certutilOutputPath -Force
            Clear-Variable -Name pfxFile
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Get-CDP {
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert)
    $crlext = $cert.Extensions | Where-Object { $_.Oid.FriendlyName -eq 'CRL Distribution Points' }
    $crldata = $crlext.RawData
    for ($i = 0 ; $i -lt $crldata.Count ; $i++) {
        if ($crldata[$i] -eq 0x86) {
            if ($crldata[$i + 1] -band 0x80) {
                #long length
                $start = $i + 3
                $end = $crldata[$i + 2] + $start - 1
            }
            else {
                #short length
                $start = $i + 2
                $end = $crldata[$i + 1] + $start - 1
            }
            [System.Text.Encoding]::ASCII.GetString($crldata[$start..$end])
        }
    }
}


function Test-HttpCdp
{
    # fail if CDP HTTP is not present
    # fail if CDP HTTP is not contactable
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[Hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'HTTP CRL'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        $cdps = Get-CDP -cert $cert
        $httpCdp = $cdps | Where-Object {$_ -like 'http://*'}

        Write-Log -Message 'Checking Http CDP EndPoint' -Type Info -Function $thisFunction
        $failureDetail = @()
        if ($cdps)
        {
            if ($httpCdp)
            {
                $result = 'OK'
                Write-Log -Message ('HTTP exists {0}. Success.' -f ($httpCdp -join ',')) -Type Info -Function $thisFunction
            }
            else
            {
                $result = 'Fail'
                $failureDetail += $LocalizedData.HttpCdpFail -f ($cdps -join ',')
                Write-Log -Message ($failureDetail -join '. ') -Type Error -Function $thisFunction
            }
        }
        Else
        {
            $result = 'Skipped'
            $failureDetail = ($LocalizedData.NoCDP)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }

    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}



function Import-AzsCertificate
{
    param ($pfxPath, [securestring]$pfxPassword, [string]$CertStoreLocation = 'cert:\localmachine\trust')
    $thisFunction = $MyInvocation.MyCommand.Name

    try
    {
        Write-Log -Message ('Importing PFX certificate from {0} to {1}' -f $pfxPath,$CertStoreLocation) -Type Info -Function $thisFunction
        $certificate = Import-PfxCertificate -Exportable -CertStoreLocation $CertStoreLocation -Password $pfxPassword -FilePath $pfxPath
        Write-Log -Message 'Import complete' -Type Info -Function $thisFunction
    }
    catch
    {
        Write-Log -Message ('Import failed: {0}' -f $_.exception) -Type Error -Function $thisFunction
    }
    $certificate
}

function Export-AzsCertificate
{
    param ($filePath, $certPath, [securestring]$pfxPassword)
    $thisFunction = $MyInvocation.MyCommand.Name

    try
    {
        Write-Log -Message ('Exporting PFX certificate from {0} to {1}' -f $certPath,$filePath) -Type Info -Function $thisFunction
        $null = Export-PfxCertificate -FilePath $filePath -ChainOption BuildChain -Cert $certPath -Password $pfxPassword -NoProperties -Force -CryptoAlgorithmOption TripleDES_SHA1
        Write-Log -Message 'Export complete' -Type Info -Function $thisFunction
    }
    catch
    {
        if ($_.CategoryInfo.Reason -eq 'ParameterBindingException' -AND $_.Exception.ErrorId -eq 'NamedParameterNotFound' -AND $_.Exception.ParameterName -eq 'CryptoAlgorithmOption')
        {
            Write-Log -Message 'Unable to force Crypto Algorithm to TripleDES_SHA1. Retrying with default value.' -Type Warn -Function $thisFunction
            $null = Export-PfxCertificate -FilePath $filePath -ChainOption BuildChain -Cert $certPath -Password $pfxPassword -NoProperties -Force
            Write-Log -Message 'Export complete' -Type Info -Function $thisFunction
        }
        else
        {
            Write-Log -Message ('Export failed: {0}' -f $_.exception) -Type Error -Function $thisFunction
        }
    }
}

function Write-Result
{
    param([psobject]$in)
    if ($in.test)
    {
        Write-Host ("`t{0}: " -f $($in.Test)) -noNewLine
        if ($in.Result -eq 'OK')
        {
            Write-Host 'OK' -foregroundcolor Green
        }
        elseif ($in.Result -eq 'WARNING')
        {
            Write-Host 'Warning' -foregroundcolor Yellow
        }
        elseif ($in.Result -eq 'Skipped')
        {
            Write-Host 'Skipped' -foregroundcolor White
        }
        elseif ($in.Result -eq 'SkippedByConfig')
        {
            Write-Host 'SkippedByConfig' -foregroundcolor DarkGray
        }
        else
        {
            Write-Host 'Fail' -foregroundcolor Red
        }
    }
    else
    {
        Write-Host "`Details:"
        $in | ForEach-Object {if($_){Write-Host "[-] $_" -foregroundcolor Yellow}}
        Write-Host ("Additional help URL {0}" -f "https://aka.ms/AzsRemediateCerts")
    }
}

function Write-Log
{
    param([string]$Message,
          [string]$Type = 'verbose',
          [string]$Function
         )
    # if InstallAzureStackCommon is loaded and ScriptLog global variable exists,
    # we are in install and will push to install log, otherwise, do a standalone log
    $pii = $($ENV:USERDNSDOMAIN),$($ENV:COMPUTERNAME),$($ENV:USERNAME),$($ENV:USERDOMAIN) | Foreach-Object {
        if ($null -ne $PSITEM) {
            $PSITEM
        }
    }
    $redact = $pii -join '|'
    $message = [regex]::replace($Message,$redact,"[*redacted*]")
    if ((-not $standalone) -AND (Get-Module InstallAzureStackCommon) -AND (Get-Variable -Name ScriptLog -Scope Global -ea SilentlyContinue))
    {
        if ($type -match 'verbose|info')
        {
            Write-VerboseLog $message
        }
        elseif ($type -eq 'warn')
        {
            Write-WarningLog $message
        }
        elseif ($type -eq 'error')
        {
            Write-TerminatingErrorLog $message
        }
    }
    Else
    {
        $outfile = "$PSScriptRoot\CertChecker.log"
        $entry = "[{0}] [{1}] [{2}] {3}" -f ([datetime]::now).tostring(), $type, $function, $Message
        $entry | Out-File -FilePath $outfile -Append -Force
    }
}

function Get-ThumbprintMask
{
    [cmdletbinding()]
    [OutputType([string])]
    Param ([Parameter(ValueFromPipelinebyPropertyName=$True)]$thumbprint)
    Begin
    {
        $thumbprintMasks = @()
    }
    Process
    {
        $thumbprintMasks += foreach ($thumb in $thumbprint)
        {
            try
            {
                if (($thumb.length - 12) -gt 0)
                {
                    $firstSix = $thumb.Substring(0,6)
                    $lastSix = $thumb.Substring(($thumb.length - 6),6)
                    $middleN = '*' * ($thumb.length - 12)
                    $thumbprintMask = '{0}{1}{2}' -f $firstSix,$middleN, $lastSix
                }
                else
                {
                    throw ("Error applying thumbprint mask from thumbprint starting with {0} and length of {1}" -f $thumbprint.Substring(0,10),$thumbprint.Length)
                }

            }
            catch
            {
                $_.exception
            }
            $thumbprintMask
        }
    }
    End
    {
        $thumbprintMasks -join ','
    }
}

function Open-PfxData {
    param ([byte[]]$certificateBinary, [securestring]$certificatePassword)

    $thisFunction = $MyInvocation.MyCommand.Name

    $Source = @'
using System;
using System.Runtime.InteropServices;
 
namespace AzureStack.PartnerToolkit
{
    [StructLayout(LayoutKind.Sequential)]
    public struct CRYPT_DATA_BLOB
    {
        public int cbData;
        public IntPtr pbData;
    }
 
    public class Crypto
    {
        [DllImport("Crypt32.dll", SetLastError = true)]
        public static extern IntPtr PFXImportCertStore(
            ref CRYPT_DATA_BLOB pPfx,
            [MarshalAs(UnmanagedType.LPWStr)] String szPassword,
            uint dwFlags);
 
        [DllImport("Crypt32.DLL", SetLastError = true)]
        public static extern IntPtr CertEnumCertificatesInStore(
            IntPtr storeProvider,
            IntPtr prevCertContext
        );
 
        [DllImport("Crypt32.dll", SetLastError = true)]
        public static extern Boolean CertCloseStore(
            IntPtr hCertStore,
            Int32 dwFlags
        );
 
        [DllImport("CRYPT32.DLL", EntryPoint = "CertGetCertificateContextProperty", CharSet = CharSet.Unicode, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern Boolean CertGetCertificateContextProperty(
            [In] IntPtr pCertContext,
            [In] Int32 dwPropId,
            [Out] IntPtr pvData,
            [In, Out] ref Int32 pcbData);
    }
}
'@


    Add-Type -TypeDefinition $Source -Language CSharp

    Write-Log -Message "Marshalling PFX binary..." -Type Verbose -Function $thisFunction
    $pPfxBinary = New-Object AzureStack.PartnerToolkit.CRYPT_DATA_BLOB
    $pPfxBinary.cbData = $certificateBinary.Length
    $pPfxBinary.pbData = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($certificateBinary.Length)
    [System.Runtime.InteropServices.Marshal]::Copy($certificateBinary, 0, $pPfxBinary.pbData, $certificateBinary.Length)

    Write-Log -Message "Convert cert password to string..." -Type Verbose -Function $thisFunction
    if ($certificatePassword)
    {
        $networkCredential = New-Object System.Net.NetworkCredential -ArgumentList @("", $certificatePassword)
        $plainCertPassword = $networkCredential.Password
    }
    else
    {
        $plainCertPassword = ""
    }

    try
    {
        # PKCS12_OBJECT_LOCATOR_ALL_IMPORT_FLAGS (0x8250) | CRYPT_EXPORTABLE (0x00000001)
        # PKCS12_OBJECT_LOCATOR_ALL_IMPORT_FLAGS =
        # PKCS12_ALWAYS_CNG_KSP (0x00000200) |
        # PKCS12_NO_PERSIST_KEY (0x00008000) |
        # PKCS12_IMPORT_SILENT (0x00000040) |
        # PKCS12_INCLUDE_EXTENDED_PROPERTIES (0x0010)
        $importFlag = 0x8251
        Write-Log -Message "Parsing PFX binary with flag $importFlag ..." -Type Verbose -Function $thisFunction
        $hCertStore = [AzureStack.PartnerToolkit.Crypto]::PFXImportCertStore([ref]$pPfxBinary, $plainCertPassword, $importFlag)
        if ($hCertStore -eq [System.IntPtr]::Zero)
        {
            $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()

            if (-not $plainCertPassword)
            {
                # PKCS12_ONLY_NOT_ENCRYPTED_CERTIFICATES (0x0800) | PKCS12_INCLUDE_EXTENDED_PROPERTIES (0x00000010)
                $importFlag = 0x0810
                Write-Log -Message "Parsing PFX binary with error. Try to parse without password with flag $importFlag again..." -Type Verbose -Function $thisFunction

                $hCertStore = [AzureStack.PartnerToolkit.Crypto]::PFXImportCertStore([ref]$pPfxBinary, $plainCertPassword, $importFlag)
            }

            if ($hCertStore -eq [System.IntPtr]::Zero)
            {
                return @{
                    Success = $false
                    ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
                }
            }
        }

        Write-Log -Message "Retrieving the certs from the temp store..." -Type Verbose -Function $thisFunction
        $ret = @{
            EndEntityCertificates = @()
            OtherCertificates = @()
        }
        $currentCertContext = 0
        while ($true)
        {
            $currentCertContext = [System.IntPtr]([AzureStack.PartnerToolkit.Crypto]::CertEnumCertificatesInStore($hCertStore, $currentCertContext))

            if ($currentCertContext -ne [System.IntPtr]::Zero)
            {
                $newCert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($currentCertContext)

                if (Get-EndEntityCertificate $currentCertContext)
                {
                    $ret.EndEntityCertificates += @($newCert);
                }
                else
                {
                    $ret.OtherCertificates += @($newCert);
                }
                continue
            }
            break
        }

        $ret.Success = $true
        return $ret
    }
    finally
    {
        if ($hCertStore -ne [System.IntPtr]::Zero)
        {
            if (-not ([AzureStack.PartnerToolkit.Crypto]::CertCloseStore($hCertStore, 0)))
            {
                $ErrorCode = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
                Write-Log -Message "Close cert store with error code $ErrorCode" -Type Warning -Function $thisFunction
            }
        }
    }
}

function Get-EndEntityCertificate
{
    param ([System.IntPtr] $pCertificate)

    $privateKeyPropIds = @(
        5, # CERT_KEY_CONTEXT_PROP_ID
        2 # CERT_KEY_PROV_INFO_PROP_ID
    )

    foreach ($propId in $privateKeyPropIds)
    {
        $cbData = 0

        if ([AzureStack.PartnerToolkit.Crypto]::CertGetCertificateContextProperty(
                $pCertificate,
                $propId,
                [System.IntPtr]::Zero,
                [ref]$cbData))
        {
            return $true
        }
    }

    return $false
}

function New-CertificateCollection
{
    param ([hashtable]$pfxdata)
    $thisFunction = $MyInvocation.MyCommand.Name
    #create array of all certificates from pfx package
    $otherCertificates = $pfxData.OtherCertificates
    $cert = $pfxData.EndEntityCertificates
    $allcerts = $otherCertificates + @($cert)
    Write-Log -Message ('Building certificate collection.') -Type Info -Function $thisFunction
    # create collection of all certificates
    $collection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection
    $allcerts | ForEach-Object {$collection.add($PSITEM) | Out-Null}
    Write-Log -Message ('Building certificate collection complete.') -Type Info -Function $thisFunction
    #return collection
    return $collection
}

function Test-TrustedChain
{
    #Function to test certificate chain, will retry (configurable) 3 times with (configurable) 5 seconds intervals
    param ([Security.Cryptography.X509Certificates.X509Chain]$chain,[System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[int]$retryCount = 3,[int]$intervalSeconds = 5,[Hashtable]$certConfig )
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Trusted Chain'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        Write-Log -Message ('Testing Chain Trust for {0}' -f ($cert | Get-ThumbprintMask)) -Type Info -Function $thisFunction
        $failureDetail = @()
        try
        {
            #create empty chain is one was not passed to the function
            if(-not $chain)
            {
                $chain = New-Object Security.Cryptography.X509Certificates.X509Chain
            }
            if ($chain.ChainPolicy.extrastore)
            {
                Write-Log -Message $localizedData.ChainCheckExtraStore -Type Info -Function $thisfunction
            }
            else
            {
                Write-Log -Message $localizedData.ChainCheckNoStore -Type Info -Function $thisfunction
            }

            # If no CDP info exists on the certificate disable the revocation check.
            $cdpInfo = $cert.Extensions | Where-Object {$_.oid.Value -eq '2.5.29.31'}
            if (-not $cdpInfo)
            {
                $chain.ChainPolicy.RevocationMode = 'NoCheck'
                Write-Log -Message $localizedData.RevocationModeNoCheck -Type Warn -Function $thisfunction
            }
            else
            {
                Write-Log -Message $localizedData.RevocationModeDefault -Type Info -Function $thisfunction
            }
            # Attempt to build the chain
            do
            {
                $chainResult = $chain.build($cert)
                $chainFailureReasons = $chain.ChainStatus.status
                $retry++
                #sleep if unsuccessful and none CRL error present
                if (-not $chainResult -AND $retry -lt $retryCount -AND $chainFailureReasons)
                {
                    Write-Log -Message ($localizedData.ChainCheckRetry -f ($chainFailureReasons -join ','),$retry) -Type Warn -Function $thisfunction
                    start-Sleep -Seconds $intervalSeconds
                }
            }
            while (-not $chainResult -AND $retry -le $retryCount)

            #Interpret result
            if ($chainResult)
            {
                Write-Log -Message $localizedData.ChainCheckSuccess -Type Info -Function $thisfunction
                $result = 'OK'
            }
            else
            {
                Write-Log -Message ($localizedData.ChainCheckFailed -f ($chainFailureReasons -join ',')) -Type Warn -Function $thisfunction
                $failureDetail = $chain.ChainStatus.StatusInformation -join ''
                $result = 'Fail'
            }

            # Downgrade result and add failure detail if revocation was disabled
            if ($chain.ChainPolicy.RevocationMode -eq 'NoCheck')
            {
                switch ( $result )
                {
                    'OK' {$result = 'Warning'}
                    'Fail' {$result = 'Fail'}
                }
                $failureDetail += $localizedData.RevocationModeNoCheck
            }
        }
        catch
        {
            $result = 'Fail'
            $failureDetail = ($localizedData.TestException -f  $test, $_.exception)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
        finally
        {
            if ($chain)
            {
                $chain.Dispose()
            }
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $chain}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-CertificateRoot
{
    #test if root is the same as stored on ercs machine
    param ($pfx,
            [ValidateScript({Test-Path -Path $_ -PathType Container})]
            $rootpath = "$ENV:SystemDrive\ExternalCerts\Root",
            [Hashtable]$certConfig
            )
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Match Root'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        try
        {
            $rootCerts = Get-ChildItem -Path $rootpath -Recurse -Filter *.cer
            Write-Log -Message ($LocalizedData.RootCertificateFoundOnStamp -f $rootCerts.count, $rootpath) -Type Info -Function $thisFunction
            if (-not $rootCerts)
            {
                $result = 'Fail'
                $failureDetail = ($LocalizedData.RootCertNotOnDisk -f $rootpath)
            }
            else
            {
                foreach ($rootCert in $rootCerts)
                {
                    Write-Log -Message ("Loading {0} for same root comparison" -f $rootCert.fullname) -Type Info -Function $thisFunction
                    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2($rootCert.fullname)
                    $rootThumbprint = $cert.Thumbprint
                    $pfxIssuer = $pfx.OtherCertificates | Where-Object subject -eq $pfx.EndEntityCertificates.Issuer
                    if ($pfxIssuer.thumbprint -eq $rootThumbprint)
                    {
                        Write-Log -Message ($LocalizedData.RootCertificateMatch -f ($pfxIssuer | Get-ThumbprintMask),($cert | Get-ThumbprintMask)) -Type Info -Function $thisFunction
                        $result = 'OK'
                        break
                    }
                    else
                    {
                        $failureDetail = $LocalizedData.RootCertificateNotMatch -f ($pfxIssuer | Get-ThumbprintMask),($cert | Get-ThumbprintMask)
                        Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
                        $result = 'Fail'
                    }
                }
            }
        }
        catch
        {
            $result = 'Fail'
            $failureDetail = ($localizedData.TestException -f  $test, $_.exception.message)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-CertificateExpiry
{
    # block if certificate expiry <= threshold (default 7 days)
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$cert,[int]$expiryThresholdDays = 7,[Hashtable]$certConfig)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Expiry Date'
    if (!$certConfig -or (($certConfig.IncludeTests -eq 'All' -or $test -in $certConfig.IncludeTests) -and $test -notin $certConfig.ExcludeTests))
    {
        try
        {
            $thresholdDateTime = [System.DateTime]::Now.AddDays($expiryThresholdDays)
            $certExpiry = $cert.NotAfter
            if ($certExpiry -le $thresholdDateTime)
            {
                $result = 'Fail'
                Write-Log -Message ($localizedData.ExpiryFailure -f $certExpiry,($cert | Get-ThumbprintMask),$thresholdDateTime  ) -Type Warn -Function $thisFunction
                if ($certExpiry -le [System.DateTime]::Now) {
                    $failureDetail = $localizedData.ExpiredFailureDetail
                }
                else {
                    $failureDetail = $localizedData.ExpiryFailureDetail -f $certExpiry, $expiryThresholdDays
                }
            }
            else
            {
                $result = 'OK'
                Write-Log -Message ($localizedData.ExpirySuccess -f $certExpiry,($cert | Get-ThumbprintMask),$thresholdDateTime ) -Type Info -Function $thisFunction
            }
        }
        catch
        {
            $result = 'Fail'
            $failureDetail = ($localizedData.TestException -f  $test, $_.exception.message)
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
    }
    else
    {
        $result = 'SkippedByConfig'
        $failureDetail = ($LocalizedData.SkippedByConfig -f $test)
        Write-Log -Message $failureDetail -Type Info -Function $thisFunction
    }
    $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function ConvertTo-Oid {
    param ($in)
    $thisFunction = $MyInvocation.MyCommand.Name
    try {
        # user may have passed in
        Write-Log -Message $in -Type Info -Function $thisFunction
        if ($in -is [string]) {
            $oid = [System.Security.Cryptography.Oid]::new($in)
            if (!$oid.FriendlyName -or !$oid.Value) {
                throw ("Unable to convert {0} into Oid" -f $in)
            }
        }
        elseif ($in -is [Hashtable]) {
            $oid = $in.Keys | ForEach-Object { [System.Security.Cryptography.Oid]::new($in[$PSITEM],$PSITEM) }
        }
        else {
            throw ("Unable to convert {0} into Oid" -f $in)
        }
        return $oid
    }
    catch {
        throw ("Unable to convert {0} into Oid. Make sure the input has a valid name and Oid. Error {1}" -f $in,$_.exception.message)
    }
}
# SIG # Begin signature block
# MIIoKgYJKoZIhvcNAQcCoIIoGzCCKBcCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCC7LZpSGdLoUJsn
# RgFbiLz/beOuCnlh/cEApQ0gEACRMaCCDXYwggX0MIID3KADAgECAhMzAAADrzBA
# DkyjTQVBAAAAAAOvMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMjMxMTE2MTkwOTAwWhcNMjQxMTE0MTkwOTAwWjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDOS8s1ra6f0YGtg0OhEaQa/t3Q+q1MEHhWJhqQVuO5amYXQpy8MDPNoJYk+FWA
# hePP5LxwcSge5aen+f5Q6WNPd6EDxGzotvVpNi5ve0H97S3F7C/axDfKxyNh21MG
# 0W8Sb0vxi/vorcLHOL9i+t2D6yvvDzLlEefUCbQV/zGCBjXGlYJcUj6RAzXyeNAN
# xSpKXAGd7Fh+ocGHPPphcD9LQTOJgG7Y7aYztHqBLJiQQ4eAgZNU4ac6+8LnEGAL
# go1ydC5BJEuJQjYKbNTy959HrKSu7LO3Ws0w8jw6pYdC1IMpdTkk2puTgY2PDNzB
# tLM4evG7FYer3WX+8t1UMYNTAgMBAAGjggFzMIIBbzAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQURxxxNPIEPGSO8kqz+bgCAQWGXsEw
# RQYDVR0RBD4wPKQ6MDgxHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEW
# MBQGA1UEBRMNMjMwMDEyKzUwMTgyNjAfBgNVHSMEGDAWgBRIbmTlUAXTgqoXNzci
# tW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3JsMGEG
# CCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDovL3d3dy5taWNyb3NvZnQu
# Y29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDExXzIwMTEtMDctMDguY3J0
# MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIBAISxFt/zR2frTFPB45Yd
# mhZpB2nNJoOoi+qlgcTlnO4QwlYN1w/vYwbDy/oFJolD5r6FMJd0RGcgEM8q9TgQ
# 2OC7gQEmhweVJ7yuKJlQBH7P7Pg5RiqgV3cSonJ+OM4kFHbP3gPLiyzssSQdRuPY
# 1mIWoGg9i7Y4ZC8ST7WhpSyc0pns2XsUe1XsIjaUcGu7zd7gg97eCUiLRdVklPmp
# XobH9CEAWakRUGNICYN2AgjhRTC4j3KJfqMkU04R6Toyh4/Toswm1uoDcGr5laYn
# TfcX3u5WnJqJLhuPe8Uj9kGAOcyo0O1mNwDa+LhFEzB6CB32+wfJMumfr6degvLT
# e8x55urQLeTjimBQgS49BSUkhFN7ois3cZyNpnrMca5AZaC7pLI72vuqSsSlLalG
# OcZmPHZGYJqZ0BacN274OZ80Q8B11iNokns9Od348bMb5Z4fihxaBWebl8kWEi2O
# PvQImOAeq3nt7UWJBzJYLAGEpfasaA3ZQgIcEXdD+uwo6ymMzDY6UamFOfYqYWXk
# ntxDGu7ngD2ugKUuccYKJJRiiz+LAUcj90BVcSHRLQop9N8zoALr/1sJuwPrVAtx
# HNEgSW+AKBqIxYWM4Ev32l6agSUAezLMbq5f3d8x9qzT031jMDT+sUAoCw0M5wVt
# CUQcqINPuYjbS1WgJyZIiEkBMIIHejCCBWKgAwIBAgIKYQ6Q0gAAAAAAAzANBgkq
# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x
# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv
# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5
# IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEwOTA5WjB+MQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYDVQQDEx9NaWNyb3NvZnQg
# Q29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
# CgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+laUKq4BjgaBEm6f8MMHt03
# a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc6Whe0t+bU7IKLMOv2akr
# rnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4Ddato88tt8zpcoRb0Rrrg
# OGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+lD3v++MrWhAfTVYoonpy
# 4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nkkDstrjNYxbc+/jLTswM9
# sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6A4aN91/w0FK/jJSHvMAh
# dCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmdX4jiJV3TIUs+UsS1Vz8k
# A/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL5zmhD+kjSbwYuER8ReTB
# w3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zdsGbiwZeBe+3W7UvnSSmn
# Eyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3T8HhhUSJxAlMxdSlQy90
# lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS4NaIjAsCAwEAAaOCAe0w
# ggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRIbmTlUAXTgqoXNzcitW2o
# ynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD
# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBDuRQFTuHqp8cx0SOJNDBa
# BgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny
# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3JsMF4GCCsG
# AQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3dy5taWNyb3NvZnQuY29t
# L3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFfMDNfMjIuY3J0MIGfBgNV
# HSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEFBQcCARYzaHR0cDovL3d3
# dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1hcnljcHMuaHRtMEAGCCsG
# AQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkAYwB5AF8AcwB0AGEAdABl
# AG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn8oalmOBUeRou09h0ZyKb
# C5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7v0epo/Np22O/IjWll11l
# hJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0bpdS1HXeUOeLpZMlEPXh6
# I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/KmtYSWMfCWluWpiW5IP0
# wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvyCInWH8MyGOLwxS3OW560
# STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBpmLJZiWhub6e3dMNABQam
# ASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJihsMdYzaXht/a8/jyFqGa
# J+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYbBL7fQccOKO7eZS/sl/ah
# XJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbSoqKfenoi+kiVH6v7RyOA
# 9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sLgOppO6/8MO0ETI7f33Vt
# Y5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtXcVZOSEXAQsmbdlsKgEhr
# /Xmfwb1tbWrJUnMTDXpQzTGCGgowghoGAgEBMIGVMH4xCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNp
# Z25pbmcgUENBIDIwMTECEzMAAAOvMEAOTKNNBUEAAAAAA68wDQYJYIZIAWUDBAIB
# BQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQwHAYKKwYBBAGCNwIBCzEO
# MAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMOrcvL0sL7nm+MLO15BdSgJ
# 8LaRkhVe0uc2dDeGK5vfMEIGCisGAQQBgjcCAQwxNDAyoBSAEgBNAGkAYwByAG8A
# cwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20wDQYJKoZIhvcNAQEB
# BQAEggEAzcK1yCveOEC+t4ctybaPlvMY8jBYaimeDBeocWBbg+sJqogtNACflBpS
# cObL41nR1wWRjXNTCsjR4RExh41brqqnmJEJhm1DHH33MFzYMlOjFKQaWYV8qucv
# vWJshgZU6K8N9BtF+5ZaHzcTZGPITbPO3qTD/PTalSJwldmhgPFzxtJhlrNAKAe9
# 4I0qgR3fpPzZkSAWbBiRu8JzIYyBbcDyYQe9pPEyXh5CGRU2Ufdi7EjaqJdsxn2M
# S/XP/ryBcT+n7SbAapDwb1Rpv57O0v1C380sex1abBg6oLhoyQg5IHt058MdoRfy
# Vl6Dc6iwnqBzMiFM8hb5no1ThceFGaGCF5QwgheQBgorBgEEAYI3AwMBMYIXgDCC
# F3wGCSqGSIb3DQEHAqCCF20wghdpAgEDMQ8wDQYJYIZIAWUDBAIBBQAwggFSBgsq
# hkiG9w0BCRABBKCCAUEEggE9MIIBOQIBAQYKKwYBBAGEWQoDATAxMA0GCWCGSAFl
# AwQCAQUABCCI1iNt1byF0x/Ersb+V8eEJgYdfzFpDBiU4Np/Y8j8KwIGZfxn/4EG
# GBMyMDI0MDMyNTA3MjU0MS4xNzRaMASAAgH0oIHRpIHOMIHLMQswCQYDVQQGEwJV
# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE
# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1l
# cmljYSBPcGVyYXRpb25zMScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0w
# NUUwLUQ5NDcxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2Wg
# ghHqMIIHIDCCBQigAwIBAgITMwAAAe3hX8vV96VdcwABAAAB7TANBgkqhkiG9w0B
# AQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE
# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYD
# VQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAeFw0yMzEyMDYxODQ1
# NDFaFw0yNTAzMDUxODQ1NDFaMIHLMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz
# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv
# cnBvcmF0aW9uMSUwIwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25z
# MScwJQYDVQQLEx5uU2hpZWxkIFRTUyBFU046ODkwMC0wNUUwLUQ5NDcxJTAjBgNV
# BAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCoMMJskrrqapycLxPC1H7zD7g88NpbEaQ6SjcTIRbz
# CVyYQNsz8TaL1pqFTEAPL1X7ojL4/EaEW+UjNqZs/ayMyW4YIpFPZP2x4FBMVCdd
# seF2i+aMMjDHi0LcTQZxM2s3mFMrCZAWSfLYXYDIimFBz8j0oLWGy3VgLmBTKM4x
# Lqv7DZUz8B2SoAmbEtp62ngSl0hOoN73SFwE+Y24SvGQMWhykpG+vXDwcpWvwDe+
# TgnrLR7ATRFXN5JS26dm2yy6SYFMRYnME3dMHCQ/UQIQQNC8nLmIvdKkAoWEMXtJ
# sGEo3QrM2S2SBv4PpHRzRukzTtP+UAceGxM9JyrwUQP5OCEmW6YchEyRDSwP4hU9
# f7B0Ayh14Pw9vJo7jewNjeMPIkmneyLSi0ruv2ox/xRGtcJ9yBNC5BaRktjz7stP
# aojR+PDA2fuBtCo8xKlkt53mUb7AY+CZHHqhLm76pdMF6BHv2TvwlVBeQRN22Xja
# VVRwCgjgJnNewt7PejcrpUn0qHLgLq+1BN1DzYukWkTr7wT0zl0iXr+NtqUkWSOn
# WRfe8N21tB6uv3VkW8nFdChtbbZZz24peLtJEZuNrN8Xf9PTPMzZXDJBI1EciR/9
# 1QcGoZFmVbFVb2rUIAs01+ZkewvbhmGVDefX9oZG4/K4gGUsTvTW+r1JZMxUT2Mw
# qQIDAQABo4IBSTCCAUUwHQYDVR0OBBYEFM4b8Oz33hAqBEfKlAZf0NKh4CIZMB8G
# A1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRYMFYwVKBSoFCG
# Tmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY3Jvc29mdCUy
# MFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEFBQcBAQRgMF4w
# XAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY2Vy
# dHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAoMSkuY3J0MAwG
# A1UdEwEB/wQCMAAwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwgwDgYDVR0PAQH/BAQD
# AgeAMA0GCSqGSIb3DQEBCwUAA4ICAQCd1gK2Rd+eGL0eHi+iE6/qDY8sbbsO4ema
# ncp6KPN+xq5ZAatiBR4jmRRhm+9Vik0Fo0DLWi/N28bFI7dXYw09p3vCipbjy4Eo
# ifm0Nud7/4U30i9+7RvW7XOQ3rx37+U7vq9lk6yYpGCNp0jlJ188/CuRPgqJnfq5
# EdeafH2AoG46hKWTeB7DuXasGt6spJOenGedSre34MWZqeTIQ0raOItZnFuGDy4+
# xoD1qRz2QW+u2gCHaG8AQjhYUM4uTi9t6kttj6c7Xamr2zrWuceDhz7sKLttLTJ7
# ws5YrA2I8cTlbMAf2KW0GVjKbYGd+LZGduEK7/7fs4GUkMqc51FsNdG1n+zgc7zH
# u2oGGeCBg4s8ZR0ZFyx7jsgm9sSFCKQ5CsbAvlr/60Ndk5TeMR8Js2kNUicu2CqZ
# 03833TsvTgk7iD1KLgfS16HEvjN6m4VKJKgjJ7OJJzabtS4JQgUnJrIZfyosk4D1
# 8rZni9pUwN03WgTmd10WTwiZOu4g8Un6iKcPMY/iFqTu4ntkzFUxBBpbFG6k1CIN
# ZmoirEWmCtG3lyZ2IddmjtIefTkIvGWb4Jxzz7l2m/E2kGOixDJHsahZVmwsoNvh
# y5ku/inU++dXHzw+hlvqTSFT89rIFVhcmsWPDJPNRSSpMhoJ33V2Za/lkKcbkUM0
# SbQgS9qsdzCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZI
# hvcNAQELBQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw
# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x
# MjAwBgNVBAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAy
# MDEwMB4XDTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMC
# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV
# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoIC
# AQDk4aZM57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25Phdg
# M/9cT8dm95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPF
# dvWGUNzBRMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6
# GnszrYBbfowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBp
# Dco2LXCOMcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50Zu
# yjLVwIYwXE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3E
# XzTdEonW/aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0
# lBw0gg/wEPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1q
# GFphAXPKZ6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ
# +QuJYfM2BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PA
# PBXbGjfHCBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkw
# EgYJKwYBBAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxG
# NSnPEP8vBO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARV
# MFMwUQYMKwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWlj
# cm9zb2Z0LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAK
# BggrBgEFBQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC
# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX
# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI
# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j
# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG
# 9w0BAQsFAAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0x
# M7U518JxNj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmC
# VgADsAW+iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449
# xvNo32X2pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wM
# nosZiefwC2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDS
# PeZKPmY7T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2d
# Y3RILLFORy3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxn
# GSgkujhLmm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+Crvs
# QWY9af3LwUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokL
# jzbaukz5m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL
# 6Xu/OHBE0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggNN
# MIICNQIBATCB+aGB0aSBzjCByzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEn
# MCUGA1UECxMeblNoaWVsZCBUU1MgRVNOOjg5MDAtMDVFMC1EOTQ3MSUwIwYDVQQD
# ExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNloiMKAQEwBwYFKw4DAhoDFQDu
# HayKTCaYsYxJh+oWTx6uVPFw+aCBgzCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w
# IFBDQSAyMDEwMA0GCSqGSIb3DQEBCwUAAgUA6auDtjAiGA8yMDI0MDMyNTA1MDE0
# MloYDzIwMjQwMzI2MDUwMTQyWjB0MDoGCisGAQQBhFkKBAExLDAqMAoCBQDpq4O2
# AgEAMAcCAQACAgSxMAcCAQACAhOGMAoCBQDprNU2AgEAMDYGCisGAQQBhFkKBAIx
# KDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMHoSChCjAIAgEAAgMBhqAwDQYJKoZI
# hvcNAQELBQADggEBAH53yl4LqtggDakO1OOFXhgbKLqpunBVgzXoN316dCstvwmG
# PUpuVjM6JWIfRpKHPedNYu/ZVebIjgUC0tKtvcY431Ms+44zAxCh5qFZfXskBKQ7
# RM3QP4xFiRHH2lLZVlyu6MNmwmMbexF1rmp07+ybugNIuOYyMgXjEo1pbcsczYr3
# s/yl7jo9dowhWeE7Y/WM5Ngl2ODBESXovpAcjoqTfPqxY5yjCSWrVGUjOwAZq5bn
# x/wTBy3g9S6v/Yba56ue6jJKh0JfRIO2MVt0bR8OT/NoAOkHpfBuWH2RkVaW+8rd
# foowERXaXK2w18XiwtO+QrhDQB4zf/Vl3tPLw1gxggQNMIIECQIBATCBkzB8MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy
# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAe3hX8vV96VdcwABAAAB7TAN
# BglghkgBZQMEAgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8G
# CSqGSIb3DQEJBDEiBCC41xCq4ucfRVC4U88vKS9Befjqwhqwra3S9q/REKDkqDCB
# +gYLKoZIhvcNAQkQAi8xgeowgecwgeQwgb0EII0uDWg0CFseKxK3A16l1wrIwrsS
# DrXZ6xSf0F4xbMo5MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh
# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD
# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw
# MTACEzMAAAHt4V/L1felXXMAAQAAAe0wIgQgYiHnag3xiwbNnjuggVO5NAu7kyPh
# /yJ9NSjOyG1joXIwDQYJKoZIhvcNAQELBQAEggIAlC/CNjZ8g+z2Rv8NuTZrgsIo
# obJJ+QLlUcSYk0kmhNcQHg5o/m/Xlvjyb5Qxu6JnRpuyA4/xXujOiTgo3gEWHQnp
# jewiz+Z1oey5OoGMMyBENE49Xi39ktDqLHF4JbRo1EMm8iqOyrI6J++ca/kiHOx9
# fjaRbNw7vTt3NgkJv7znZOPJF2zH/YauDY8CHMn1lrahypMv/caCVzQwrzlpclaF
# 8Hb2wH6cXODcc5eBfwyXiwrqRQGkvFJBJs3/OTJ+gRSJ64WqHl9VHhvZD5b0+F+J
# ZYiAkEg5zqVUyiKbb9ReATTjGYHkGv1ASHLn2CLPVamsZT6c2suxJvasd/AXjeht
# WwGOTaEzgiV1QUMQXYHigvZSPx1BLbWdm4rL8LLB+ySdIGU/B1XwfHWv5/AEVDja
# 47JcH/xwhMOEKjQCeFDTpOFQeU4/zTgq/kzqtpZfuqUfNhR6lYo+M92fT9e6I+Hh
# 6ZqTQlzgGe2YbUU44NrHNwAeGDTkJdIhQpSO0w9nGKkY/PTWvzpWYPF6+34Q+DYr
# 3ZVMPQ5KJTJxo7v860eDnL6uVdcehrCl1xT8bObvjlVzUPMnkmkUODdySsK0/Sp1
# krQyh/8Dhy3e/nU84mTo7Pn122/DOJaO1Y+h7Opm+ofWzikSBewvJ6fYKcP7pEke
# 2VanHm4NJYCfswv/3/A=
# SIG # End signature block