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

$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")}

$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")}

# This function appends the region and domainFQDN to the RecordPrefix/es and returns the array for multiple endpoints
function CreateDnsList
{
    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 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,

        # TODO: #1763850 - Remove this parameter when extension host release.
        # At that time, certs for extension host are required
        [Parameter(Mandatory=$false)]
        [switch] $ExtensionHostFeature = $false
    )

    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")}

        $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")}
    }
    else
    {
        Write-VerboseLog $LocalizedData.CreatingExternalSelfSignedCerts
    }

    # getting rootCert thumbprint
    $rootCertThumbprint = Create-RootCert -DnsRecord "$RegionExternalFQDN" -CertificatePassword $CertificatePassword

    # AAD certs
    New-SelfSignedCertificateWrapper -OutFilePath $armPublicCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $armPublicCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $armAdminCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $armAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $publicPortalCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $publicPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $adminPortalCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $adminPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $keyvaultCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultAdminCertInfo.Path -DnsRecord (CreateDnsList -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 (CreateDnsList -RecordPrefixList $acsTableCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsQueueCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $acsQueueCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsBlobCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $acsBlobCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    
    # ADFS certs
    New-SelfSignedCertificateWrapper -OutFilePath $adfsCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $adfsCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $graphCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $graphCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $armADFSPublicCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $armADFSPublicCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $armADFSAdminCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $armADFSAdminCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $publicADFSPortalCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $publicADFSPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $adminADFSPortalCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $adminADFSPortalCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultADFSCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $keyvaultADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $keyvaultADFSAdminCertInfo.Path -DnsRecord (CreateDnsList -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 (CreateDnsList -RecordPrefixList $acsTableADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsQueueADFSCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $acsQueueADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    New-SelfSignedCertificateWrapper -OutFilePath $acsBlobADFSCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $acsBlobADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint

    # TODO: #1763850 - Remove the "if" when extension host release.
    # At that time, certs for extension host are required
    if ($ExtensionHostFeature)
    {
        # ADD certs
        New-SelfSignedCertificateWrapper -OutFilePath $adminHostingCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $adminHostingCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
        New-SelfSignedCertificateWrapper -OutFilePath $publicHostingCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $publicHostingCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
    
        # ADFS certs
        New-SelfSignedCertificateWrapper -OutFilePath $adminHostingADFSCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $adminHostingADFSCertInfo.RecordPrefix -RegionExternalFQDN $RegionExternalFQDN) -CertificatePassword $CertificatePassword -RootCertThumbprint $rootCertThumbprint
        New-SelfSignedCertificateWrapper -OutFilePath $publicHostingADFSCertInfo.Path -DnsRecord (CreateDnsList -RecordPrefixList $publicHostingADFSCertInfo.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]
        $WarnOnSelfSigned = $true,

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

        # TODO: #1763850 - Remove this parameter when extension host release.
        # At that time, certs for extension host are required
        [Parameter(Mandatory=$false)]
        [switch] $ExtensionHostFeature = $false,

        [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")}

        $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")}
    }

    if($UseADFS)
    {
        $allCertInfo = @($adfsCertInfo, $graphCertInfo, $armADFSPublicCertInfo, $armADFSAdminCertInfo, $publicADFSPortalCertInfo, $adminADFSPortalCertInfo, $keyvaultADFSCertInfo, $keyvaultADFSAdminCertInfo, $acsTableADFSCertInfo, $acsQueueADFSCertInfo, $acsBlobADFSCertInfo)        
    
        # TODO: #1763850 - Remove the "if" when extension host release.
        # At that time, certs for extension host are required
        if ($ExtensionHostFeature)
        {
            $allCertInfo += @($adminHostingADFSCertInfo, $publicHostingADFSCertInfo)
        }
    }
    else
    {
        $allCertInfo = @($armPublicCertInfo, $armAdminCertInfo, $publicPortalCertInfo, $adminPortalCertInfo, $keyvaultCertInfo, $keyvaultAdminCertInfo, $acsTableCertInfo, $acsQueueCertInfo, $acsBlobCertInfo)
    
        # TODO: #1763850 - Remove the "if" when extension host release.
        # At that time, certs for extension host are required
        if ($ExtensionHostFeature)
        {
            $allCertInfo += @($adminHostingCertInfo, $publicHostingCertInfo)
        }
    }

    $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
        Test-Certificate -CertificatePath $PSITEM.Path -CertificatePassword $CertificatePassword -ExpectedPrefix $PSITEM.RecordPrefix -ExpectedDomainFQDN $ExpectedDomainFQDN -WarnOnSelfSigned $WarnOnSelfSigned -RootValidation $RootValidation
    }
    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[]]
        $ExpectedPrefix,

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

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

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

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

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

    $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
    }
    # Get Relative Path
    $pf = Get-item -Path $pfxFile
    $pathValue = $pf.Directory.Name + '\' + $pf.name
    Write-Host "Testing: $pathValue"

    $pfxBinary = Get-Content -Path $pfxFile -Encoding Byte

    $params = @{
        CertificateBinary       = $pfxBinary
        CertificatePassword     = $CertificatePassword
        ExpectedPrefix          = $ExpectedPrefix
        ExpectedDomainFQDN      = $ExpectedDomainFQDN
        WarnOnSelfSigned        = $WarnOnSelfSigned
        RootValidation          = $RootValidation
    }
    Test-AzSCertificate @params
}

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

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

        [Parameter(Mandatory=$false)]
        [ValidateNotNull()]
        [string[]] $ExpectedPrefix,

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

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

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

        [Parameter(Mandatory=$false)]
        [switch] $IsPaaS

    )    
    $thisFunction = $MyInvocation.MyCommand.Name
    $ErrorActionPreference = 'SilentlyContinue'
    $results = @()

    # Check PFX encryption first in case we have difficulty with encryption used and defer printing result until after import attempt for formatting purposes
    if ($pfxFile)
    {
        $pfxEncryptCheck = Test-PFXEncryption -pfxfile $pfxFile -pfxpassword $certificatePassword
        $results += $pfxEncryptCheck
    }
    else
    {
        Write-Log -Message "PFX Encryption Check skipped. PfxFile not provided. No further action." -Type Info -Function $thisFunction
    }
    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
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    if ($CertificatePassword)
    {
        try
        {
            $cert.Import($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",""))    
            }
            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
    }

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

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

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

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

    if ($includePFXChecks)
    {
        $pfxParseResult = Parse-Pfx -certificateBinary $CertificateBinary -certificatePassword $certificatePassword
        $pfxData = $pfxParseResult.outputObject
        $pfxParseResult.outputObject = "" # clear the output object
        Write-Result -in $pfxParseResult
        $results += $pfxParseResult

        $privateKeyCheck = Test-PrivateKey -cert $cert -IsPaas:$IsPaaS
        Write-Result -in $privateKeyCheck
        $results += $privateKeyCheck

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

        $chainOrderCheck = Test-CertificateChainOrder -pfxData $pfxData
        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
        }
        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
                Write-Result -in $rootCheck
                $results += $rootCheck
            }
            else
            {
                #Check trust without using pfx as extra store
                $chainTest = Test-TrustedChain -cert $cert -retryCount 3 -intervalSeconds 5
                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 Parse-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 = ParsePfxData -certificateBinary $certificateBinary -certificatePassword $certificatePassword

    if ($parseResult.Success)
    {
        $tmpParseResult = ParsePfxData -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 New-TemporaryCertificate
{
    [CmdletBinding(SupportsShouldProcess=$True,ConfirmImpact="Medium")]
    $thisFunction = $MyInvocation.MyCommand.Name
    # Create a random name for the certificate
    $guid = (New-Guid).ToString()
    Write-Log -Message "Creating Temporary Certificate with name $guid" -Type Info -Function $thisFunction
    # OID for document encryption
    $oid = New-Object System.Security.Cryptography.Oid "1.3.6.1.4.1.311.80.1"
    $oidCollection = New-Object System.Security.Cryptography.OidCollection
    $null = $oidCollection.Add($oid)

    # Create enhanced key usage extension that allows document encryption
    $extension = New-Object System.Security.Cryptography.X509Certificates.X509EnhancedKeyUsageExtension $oidCollection,$true 

    # Create certificate
    $certificate = New-SelfSignedCertificate -DnsName "cn=$guid" `
        -KeyLength 2048 `
        -KeySpec KeyExchange `
        -HashAlgorithm SHA256 `
        -KeyExportPolicy Exportable `
        -KeyUsageProperty All `
        -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" `
        -KeyUsage KeyEncipherment, DataEncipherment `
        -Extension $extension `
        -NotAfter ([DateTime]::Now.AddDays(1)) 
    Write-Log -Message "Certificate Created in $($certificate.PSPath)" -Type Info -Function $thisFunction
    $certificate
}

function Test-SignatureAlgorithm
{
    param ([System.Security.Cryptography.X509Certificates.X509Certificate2]$x509)
    $thisFunction = $MyInvocation.MyCommand.Name
    $cert = $x509
    $test = 'Signature Algorithm'
    Write-Log -Message 'Checking Signature Algorithm is not sha1' -Type Info -Function $thisFunction
    $signatureAlgorithm = $cert.SignatureAlgorithm.FriendlyName
    Write-Log -Message ('Signature Algorithm {0}' -f $signatureAlgorithm) -Type Info -Function $thisFunction
    if ($signatureAlgorithm -eq "sha1RSA")
    {
        $result = 'Fail'
        $failureDetail = ($LocalizedData.SignatureAlgorithmInvalid)
        Write-Log -Message $LocalizedData.SignatureAlgorithmInvalid -Type Warn -Function $thisFunction
    }
    else
    {
        $result = 'OK'
        Write-Log -Message 'Signature Algorithm is not sha1' -Type Info -Function $thisFunction
    }
    $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,[switch]$IsPaaS)
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Private Key'
    $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($IsPaaS)
            {
                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 += "PaaS certificates do not currently support CNG Certificates. Please use https://docs.microsoft.com/en-us/azure/azure-stack/azure-stack-get-pki-certs to generate the PaaS certificates."
                    Write-Log -Message 'Microsoft Software Key Storage Provider cannot be used for PaaS certificates.' -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'
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

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

    $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
        }
    }
    $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,$ExpectedPrefix,$ExpectedDomainFQDN)
    $thisFunction = $MyInvocation.MyCommand.Name

    $test = 'DNS Names'

    if ($ExpectedPrefix -and $ExpectedDomainFQDN)
    {
        # 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 $ExpectedPrefix)
        {
            $fullExpectedRecord = "$prefix.$ExpectedDomainFQDN"
            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 = 'Skipped'
        $failureDetail = $LocalizedData.DnsCheckSkipped
    }

    $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)
    $thisFunction = $MyInvocation.MyCommand.Name

    # Validating KeyUsage should have Digital Signature, and Key Encipherment.
    # EnhancedKeyUsage should have Server Authentication and Client Authentication
    # Data Encipherment no longer required
    $test = 'Key Usage'
    $keyUsageDS = "DigitalSignature"
    $keyUsageKE = "KeyEncipherment"
    $keyUsageSrvAuth = '1.3.6.1.5.5.7.3.1'
    $keyUsageClientAuth = '1.3.6.1.5.5.7.3.2'
    
    # 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},{1}' -f $keyUsageDS,$keyUsageKE) -Type Info -Function $thisFunction
    Write-Log -Message ('Certificate key usage is {0}' -f ($keyUsage -join ',')) -Type Info -Function $thisFunction
    if (!(($keyUsage -match $keyUsageDS) -and ($keyUsage -match $keyUsageKE)))
    {
        $keyUsageFailed += $true
        $failureDetail += ($LocalizedData.IncorrectKeyUsage -f $keyUsage)
        Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
    }
    Else
    {
        $keyUsageFailed += $false
    }

    # Check Enhanced KeyUsage
    Write-Log -Message ('Testing for expected Enhanced Key Usage {0},{1}' -f $keyUsageSrvAuth, $keyUsageClientAuth) -Type Info -Function $thisFunction
    Write-Log -Message ('Certificate key usage is {0}' -f ($enhancedKeyUsage -join ',')) -Type Info -Function $thisFunction
    if (!(($enhancedKeyUsage -contains $keyUsageSrvAuth) -and ($enhancedKeyUsage -contains $keyUsageClientAuth)))
    {
        $keyUsageFailed += $true
        if ($enhancedKeyUsage)
        {
            $failureDetail += ($LocalizedData.IncorrectEnhancedKeyUsage -f ($cert.EnhancedKeyUsageList.FriendlyName -join ','))
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
        }
        else 
        {
            $failureDetail += ($LocalizedData.IncorrectEnhancedKeyUsage -f '[Missing]')
            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
    }
    
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-CertificateChainOrder
{
    param ([Hashtable]$pfxData)
    $thisFunction = $MyInvocation.MyCommand.Name
    Write-Log -Message 'Checking Certificate Chain Order' -Type Info -Function $thisFunction
    # Validating cert chain order
    $test = '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
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

Function Test-OtherCertificates
{
    param ([Hashtable]$pfxData,[string[]]$ExpectedPrefix,[string]$ExpectedDomainFQDN)
    
    $test = 'Other Certificates'
    $thisFunction = $MyInvocation.MyCommand.Name
    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 $ExpectedPrefix)
    {
        # Apply literal to any wildcard on the expect DNSName.
        $expectedDNSName = "$prefix.$ExpectedDomainFQDN" -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 = 'WARNING'
    }
        
    $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)
    $test = 'Key Size'
    $thisFunction = $MyInvocation.MyCommand.Name
    #get the key length of the public key.
    $keySize = $cert.publickey.key.KeySize

    Write-Log -Message "Checking Certificate for Key Size" -Type Info -Function $thisFunction
    Write-Log -Message ("Key Size {0}" -f $keySize) -Type Info -Function $thisFunction
    if ($keySize -lt 2048)
    {
        $result = 'Fail'
        $failureDetail = ($LocalizedData.WrongKeySize -f $keySize)
        Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
    }
    else
    {
        $result = 'OK'
        Write-Log -Message 'Certificate key size succeeded' -Type Info -Function $thisFunction
    }

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

function Test-PFXEncryption
{
    param ($pfxFile, [ValidateNotNullOrEmpty()][SecureString]$pfxpassword)
    $test = 'PFX Encryption'
    $thisFunction = $MyInvocation.MyCommand.Name

    $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 
        {
            $failureDetail = $LocalizedData.IncorrectPFXEncryption -f (($certutilOutput | Select-String -SimpleMatch "1.2.840.113549") -join "` r`n::Match:: ")
            Write-Log -Message $failureDetail -Type Warn -Function $thisFunction
            $result = 'Warn'
        }
    }
    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
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}


function Import-AzsCertificate
{
    param ($pfxPath, [securestring]$pfxPassword)
    $thisFunction = $MyInvocation.MyCommand.Name

    $storePath = 'cert:\localmachine\trust'
    
    try
    {
        Write-Log -Message ('Importing PFX certificate from {0} to {1}' -f $pfxPath,$storePath) -Type Info -Function $thisFunction
        $certificate = Import-PfxCertificate -Exportable -CertStoreLocation $storePath -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
        }
        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
    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 ParsePfxData {
    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.Auto, 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 (IsEECert $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 IsEECert
{
    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 )
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Trusted Chain'
    
    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()
        }
    }
    $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")
    $thisFunction = $MyInvocation.MyCommand.Name
    $test = 'Match Root'
    
    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
                $cert.Import($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
    }
    $hash = @{'Result' = $result; 'test' = $test; 'failuredetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}
# SIG # Begin signature block
# MIIkiAYJKoZIhvcNAQcCoIIkeTCCJHUCAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBp9T7CeQt3qygr
# uz/PpaeRbVEbQKsRGAFv/0Js1UdLQaCCDYEwggX/MIID56ADAgECAhMzAAABA14l
# HJkfox64AAAAAAEDMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD
# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p
# bmcgUENBIDIwMTEwHhcNMTgwNzEyMjAwODQ4WhcNMTkwNzI2MjAwODQ4WjB0MQsw
# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u
# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy
# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
# AQDRlHY25oarNv5p+UZ8i4hQy5Bwf7BVqSQdfjnnBZ8PrHuXss5zCvvUmyRcFrU5
# 3Rt+M2wR/Dsm85iqXVNrqsPsE7jS789Xf8xly69NLjKxVitONAeJ/mkhvT5E+94S
# nYW/fHaGfXKxdpth5opkTEbOttU6jHeTd2chnLZaBl5HhvU80QnKDT3NsumhUHjR
# hIjiATwi/K+WCMxdmcDt66VamJL1yEBOanOv3uN0etNfRpe84mcod5mswQ4xFo8A
# DwH+S15UD8rEZT8K46NG2/YsAzoZvmgFFpzmfzS/p4eNZTkmyWPU78XdvSX+/Sj0
# NIZ5rCrVXzCRO+QUauuxygQjAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE
# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUR77Ay+GmP/1l1jjyA123r3f3QP8w
# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1
# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDM3OTY1MB8GA1UdIwQYMBaAFEhu
# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu
# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w
# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3
# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx
# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAn/XJ
# Uw0/DSbsokTYDdGfY5YGSz8eXMUzo6TDbK8fwAG662XsnjMQD6esW9S9kGEX5zHn
# wya0rPUn00iThoj+EjWRZCLRay07qCwVlCnSN5bmNf8MzsgGFhaeJLHiOfluDnjY
# DBu2KWAndjQkm925l3XLATutghIWIoCJFYS7mFAgsBcmhkmvzn1FFUM0ls+BXBgs
# 1JPyZ6vic8g9o838Mh5gHOmwGzD7LLsHLpaEk0UoVFzNlv2g24HYtjDKQ7HzSMCy
# RhxdXnYqWJ/U7vL0+khMtWGLsIxB6aq4nZD0/2pCD7k+6Q7slPyNgLt44yOneFuy
# bR/5WcF9ttE5yXnggxxgCto9sNHtNr9FB+kbNm7lPTsFA6fUpyUSj+Z2oxOzRVpD
# MYLa2ISuubAfdfX2HX1RETcn6LU1hHH3V6qu+olxyZjSnlpkdr6Mw30VapHxFPTy
# 2TUxuNty+rR1yIibar+YRcdmstf/zpKQdeTr5obSyBvbJ8BblW9Jb1hdaSreU0v4
# 6Mp79mwV+QMZDxGFqk+av6pX3WDG9XEg9FGomsrp0es0Rz11+iLsVT9qGTlrEOla
# P470I3gwsvKmOMs1jaqYWSRAuDpnpAdfoP7YO0kT+wzh7Qttg1DO8H8+4NkI6Iwh
# SkHC3uuOW+4Dwx1ubuZUNWZncnwa6lL2IsRyP64wggd6MIIFYqADAgECAgphDpDS
# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0
# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0
# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla
# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT
# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB
# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG
# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S
# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz
# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7
# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u
# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33
# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl
# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP
# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB
# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF
# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM
# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ
# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud
# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO
# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0
# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p
# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y
# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB
# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw
# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA
# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY
# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj
# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd
# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ
# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf
# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ
# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j
# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B
# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96
# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7
# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I
# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIWXTCCFlkCAQEwgZUwfjELMAkG
# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx
# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z
# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAQNeJRyZH6MeuAAAAAABAzAN
# BglghkgBZQMEAgEFAKCB3jAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor
# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLGnx27HV
# 1B2bZycwRT3mGK5ByDsTnudmANppwkSpDHswcgYKKwYBBAGCNwIBDDFkMGKgSIBG
# AE0AaQBjAHIAbwBzAG8AZgB0ACAAQQB6AHUAcgBlAFMAdABhAGMAawAgAFAAYQBy
# AHQAbgBlAHIAVABvAG8AbABrAGkAdKEWgBRodHRwOi8vQ29kZVNpZ25JbmZvIDAN
# BgkqhkiG9w0BAQEFAASCAQBjtj3ZZuwr7ZaJdH2ciBLKxUUi4G8ng+vgk2MLJkRr
# j4X29wqJPVZDYjLBXaAyifn5Mn+H8Svj8y81+7f25qTp4xkM88V0IcJnO7y8FPy5
# O4+lkW50rEFgTW00Y0vkox6SXcE5QjnrZZa8TrjHy6R+JoTNTWaTbuQyFfqwu/aU
# wkTNxrdygczfjDaJohRBP0psCTM1IcrxwMHGyzPFNogXLMYmqcj+saJ3C6c9wXgW
# GDlFh8i3uantd0l1FTgqzy/VjwuSIbBuPUtpJWGCQpSPfDrsd4mJTBoxXEIcaKln
# AISQgFDtvRoJsh7Pa9sXsghfi8fw9xM974R3x45ZPrmEoYITtzCCE7MGCisGAQQB
# gjcDAwExghOjMIITnwYJKoZIhvcNAQcCoIITkDCCE4wCAQMxDzANBglghkgBZQME
# AgEFADCCAVgGCyqGSIb3DQEJEAEEoIIBRwSCAUMwggE/AgEBBgorBgEEAYRZCgMB
# MDEwDQYJYIZIAWUDBAIBBQAEIN7KFd1xEslosT2UP10HLnccWxITgZIqI98pF0s3
# zev0AgZb2dVntHoYEzIwMTgxMTAxMjE1NDAyLjI2M1owBwIBAYACAfSggdSkgdEw
# gc4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS
# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsT
# IE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFs
# ZXMgVFNTIEVTTjpCQkVDLTMwQ0EtMkRCRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRp
# bWUtU3RhbXAgU2VydmljZaCCDx8wggT1MIID3aADAgECAhMzAAAAziDjflBqaKQu
# AAAAAADOMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpX
# YXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQg
# Q29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAy
# MDEwMB4XDTE4MDgyMzIwMjYyNloXDTE5MTEyMzIwMjYyNlowgc4xCzAJBgNVBAYT
# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD
# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP
# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpC
# QkVDLTMwQ0EtMkRCRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy
# dmljZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALZuqc5mW8FROtzh
# CWfSsJhR053TD7Dm6d1KK1Uh7borGwYHXbvBH2N2P3xV30coZv9i3hSveAhiw5Nu
# k51M9kJzd6EvOqc+AOYYQgbPJfDM7kz6v3UnIXMZp6BGtJ4clInlokr36sFBR+7i
# 3Z2TstfgpmPUUJ2tQZYCtrVeskZFZV+CLpVC8KJAyrozwrg2cOWDCdLMi/EGfmCM
# SRN6Abgu6cSPP93mHOzUMsjyJAXw8BtBIxndgxXxqfZwW0Y1Xw6YuHe8Lvg9/G4W
# PzteFHCcvDm19B5NNOL2QKDHULqRfprhMyYbUBZQxjyhGT1PJ+Ypki4NRGkf2mAq
# H+XlGVUCAwEAAaOCARswggEXMB0GA1UdDgQWBBRIGDHQuoCoi6fiIrVPtje/5swF
# fzAfBgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEug
# SaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9N
# aWNUaW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsG
# AQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Rp
# bVN0YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoG
# CCsGAQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBrfqH1SHZs5dnFkunNJZaYsXho
# QTrK/sEKg+3wSF/odLwBO7qQUtl7bVrjkC/tYzB3iRMyiznlTb4mI5iS0tkh/hXW
# 8UYVCoTNlfDJVQzewSSLt67Wt13uMgsUF3zFkxnNkhHdQQLhU44KWYIz70Josofo
# U3Lj3pAhx2DEWeVhWSircxr0ujXn5u71q/Vr3vXxjbG+FmHwTUMfHNj7KYu7qPZ6
# 6WQCLdiAQjvx7idF1l2Dc7D48fp8oS1MACNpN8RqaxM0DB4Q1yoXe7xulRtA3QlM
# jOT8PzRVI+nr/u84m6QcIXKCRbReFTbmBckPFXHwb0D8nAq3heGRJNhztxJJMIIG
# cTCCBFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UE
# BhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAc
# BgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0
# IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1
# WhcNMjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCC
# ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9p
# lGt0VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEw
# WbEwRA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeG
# MoQedGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJ
# UGKxXf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw
# 2k4GkbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0C
# AwEAAaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ
# 80N7fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8E
# BAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2U
# kFvXzpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5j
# b20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmww
# WgYIKwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29m
# dC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYD
# VR0gAQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6
# Ly93d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYI
# KwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0
# AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9
# naOhIW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtR
# gkQS+7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzy
# mXlKkVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCf
# Mkon/VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3D
# nKOiPPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs
# 9/S/fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110
# mCIIYdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL
# 2IK0cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffI
# rE7aKLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxE
# PJdQcdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc
# 1bN+NR4Iuto229Nfj950iEkSoYIDrTCCApUCAQEwgf6hgdSkgdEwgc4xCzAJBgNV
# BAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4w
# HAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29m
# dCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVT
# TjpCQkVDLTMwQ0EtMkRCRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAg
# U2VydmljZaIlCgEBMAkGBSsOAwIaBQADFQCJbpW8N5eBAbWZtcqyCvkAi19tkaCB
# 3jCB26SB2DCB1TELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEp
# MCcGA1UECxMgTWljcm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJzAlBgNV
# BAsTHm5DaXBoZXIgTlRTIEVTTjo1N0Y2LUMxRTAtNTU0QzErMCkGA1UEAxMiTWlj
# cm9zb2Z0IFRpbWUgU291cmNlIE1hc3RlciBDbG9jazANBgkqhkiG9w0BAQUFAAIF
# AN+Fb34wIhgPMjAxODExMDEyMDI2MzhaGA8yMDE4MTEwMjIwMjYzOFowdDA6Bgor
# BgEEAYRZCgQBMSwwKjAKAgUA34VvfgIBADAHAgEAAgIUKTAHAgEAAgIaqDAKAgUA
# 34bA/gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMBoAowCAIBAAID
# FuNgoQowCAIBAAIDB6EgMA0GCSqGSIb3DQEBBQUAA4IBAQArnqB9rZ429aCRRYJh
# PXSswt49M1KI5V5WY3dKqyiFXLqFsqH3Jt+S1TMvDmO5AYl1ICAIOsEOVFj8rs76
# 3ZPU1XKFUIGZR0tUaN5m17r00ABKt1tpI+YY1kEa2rW/zxi4Db+GvKrDfK10HbPF
# Yb4wvRJqGONOwMVsah6WDdnNx8aUtJAIPHssgC11IqQpvmm3QXkpTVGZ16toMP9R
# P0UZzyXA03ywB3xCoDHPbxwkbWXzLLpeZQJwLnEWHgyFMBth37R3Wk20PQkho6dA
# 7aPGpgJHpnJ2P1/xnq/xjaPRxgaJuBHWTPKzbJaLO49OqiBw/uEK4L50VACetR41
# gA6rMYIC9TCCAvECAQEwgZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAC
# EzMAAADOION+UGpopC4AAAAAAM4wDQYJYIZIAWUDBAIBBQCgggEyMBoGCSqGSIb3
# DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQxIgQgnmp6yme9yLM9BuSE
# Ea68EKGDWPlaBuXHJw6jN74RssUwgeIGCyqGSIb3DQEJEAIMMYHSMIHPMIHMMIGx
# BBSJbpW8N5eBAbWZtcqyCvkAi19tkTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMw
# EQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVN
# aWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0
# YW1wIFBDQSAyMDEwAhMzAAAAziDjflBqaKQuAAAAAADOMBYEFHJEarTEkpASOs18
# FuX0qRNDkJqIMA0GCSqGSIb3DQEBCwUABIIBABArOi7RXVl2JXRJQ9fBewXTtZ8Y
# MMsHTb6GN1Y5YzyTNJcPnUQ9Q8aMu0c6941ZcHnQCLwT2tbflq2htdw0071pXPOn
# PoYDC0taid/5gmtPVSfTP9POAynAhGZWcA7LMdhsXsvaJ0ZB5QkM0XBjA/Qr23CB
# dFp+VYq0qCRicEl6+xT2ut8pNGF+w3wc9cl/XEOnEZdI1MVYzVsmtquTltTjh1t0
# 5vRvNvnoqyVc8qzjj1g4e/DPbO/BW+RpjMTqfRJuoUrYOMxgDeGJaS0aLbSM7Mdk
# zgVLfbF9VtWZtHE52kXMF9oGUY3ANbgF8iVSnmQlrruR7ga9F3LoCNNLIkQ=
# SIG # End signature block