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
    )
    $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" -Type Info -Function $thisfunction
        Test-Certificate -CertificatePath $PSITEM.Path -CertificatePassword $CertificatePassword -ExpectedPrefix $PSITEM.RecordPrefix -ExpectedDomainFQDN $ExpectedDomainFQDN -WarnOnSelfSigned $WarnOnSelfSigned
    }
    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
    )

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

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

    $params = @{
        PfxFile                 = $pfxFile
        CertificatePassword     = $CertificatePassword
        ExpectedPrefix          = $ExpectedPrefix
        ExpectedDomainFQDN      = $ExpectedDomainFQDN
        WarnOnSelfSigned        = $WarnOnSelfSigned
    }
    Test-AzSCertificate @params
}

function Test-AzSCertificate
{
    Param
    (
        [Parameter(Mandatory=$true)]
        [string]
        $PfxFile,

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

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

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

        [Parameter(Mandatory=$false)]
        [bool]
        $WarnOnSelfSigned = $true
    )    
    $thisFunction = $MyInvocation.MyCommand.Name
    $ErrorActionPreference = 'SilentlyContinue'
    $results = @()

    if(-not (Test-Path -Path $pfxFile))
    {
        # Terminating error for install
        Write-Log ($LocalizedData.MissingCertificate) -Type Error -Function $thisFunction
    }
    # Get Relative Path and warn on pre-1803 ACS folder structure.
    $pf = Get-item -Path $pfxFile
    $pathValue = $pf.Directory.Name + '\' + $pf.name
    Write-Host "Testing: $pathValue"
    if ($pf.Directory.Name -eq 'ACS')
    {
        Write-Warning "Pre-1803 certificate structure. The folder structure for Azure Stack 1803 and above is: ACSBlob, ACSQueue, ACSTable instead of ACS folder. Refer to deployment documentation for further information."
    }

    # Get x509 object
    $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2
    $cert.Import($pfxFile, $CertificatePassword, [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::DefaultKeySet)

    # Begin Checks
    $readPFX = Read-Pfx -pfxFile $pfxFile -certificatePassword $certificatePassword
    Write-Result -in $readPFX
    $results += $readPfx
    $pfx = $readPFx.outputObject

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

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

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

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

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

    $keySizeCheck = Test-KeySize -pfx $pfx
    Write-Result -in $keySizeCheck
    $results += $keySizeCheck
    
    $chainOrderCheck = Test-CertificateChainOrder -pfx $pfx
    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 -pfx $pfx -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
    
    # add relative path to output
    $results | add-member -NotePropertyName Path -NotePropertyValue $pathValue

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

function Read-Pfx
{
    param ([string]$pfxFile, [securestring]$certificatePassword)
    $thisFunction = $MyInvocation.MyCommand.Name
    try
    {
        $test = 'Read PFX'
        Write-Log -Message "Reading pfxfile: $pfxFile with password" -Type Info -Function $thisFunction
        $pfx = Get-PfxData â€“FilePath $pfxFile -Password $CertificatePassword
        $result = 'OK'
        Try
        {
            Write-Log -Message "Reading pfxfile: $pfxFile without password" -Type Info -Function $thisFunction
            Get-PfxData â€“FilePath $pfxFile
            $result = 'WARNING'
            $failureDetail = ($LocalizedData.UnprotectedPublicCertInfo)
            Write-Log -Message $LocalizedData.UnprotectedPublicCertInfo -Type Warning -Function $thisFunction
        }
        Catch
        {
            if($_.exception.message.Contains("0x80070056"))
            {
                # do nothing
                Write-Log -Message "Pfxfile: $pfxFile privacy success" -Type Info -Function $thisFunction
            }
            else
            {
                Write-Log -Message $_.exception -Type Warn -Function $thisFunction
            }
        }
    }
    catch
    {
        [string]$exceptionMessage = $_.Exception.Message
        $invalidCertDataCode = "0x8007000d"
        $invalidCertPwdCode = "0x80070056"
        $invalidCertNteBadDataCode = "0x80090005"

        if($exceptionMessage.Contains($invalidCertDataCode))
        {
            $result = 'Fail'
            $failureDetail = "Pfx data is Invalid"
            # Terminating error for install
            Write-Log -Message "Pfx data is Invalid" -Type Error -Function $thisFunction
        }
        elseif($exceptionMessage.Contains($invalidCertPwdCode))
        {
            $result = 'Fail'
            $failureDetail = "Pfx password is Invalid"
            # Terminating error for install
            Write-Log -Message "Pfx password is Invalid" -Type Error -Function $thisFunction
        }
        elseif($exceptionMessage.Contains($invalidCertNteBadDataCode))
        {
            $result = 'Fail'
            $failureDetail = "The certificate has been exported from a Local User store"
            # Terminating error for install
            Write-Log -Message "The certificate has been exported from a Local User store" -Type Error -Function $thisFunction
        }
        else
        {
            # Terminating error for install
            $result = 'Fail'
            $failureDetail = $_.exception
            Write-Log -Message $_.exception -Type Error -Function $thisFunction
        }
    }
    Finally
    {
        $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $pfx}
        $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
    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]$x509)
    $thisFunction = $MyInvocation.MyCommand.Name
    $cert = $x509
    $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)
            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
            }
        }
    }
    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 ([Microsoft.CertificateServices.Commands.PfxData]$pfx)
    $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'
    if(-not $pfx.OtherCertificates)
    {
        Write-Log -Message 'No othercertificates found in pfx' -Type Info -Function $thisFunction
        if($pfx.EndEntityCertificates.Issuer -eq $pfx.EndEntityCertificates.Subject)
        {
            $failureDetail = ($LocalizedData.SelfSignedCertificate -f $pfx.EndEntityCertificates.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 ($pfx.EndEntityCertificates.Issuer -in $pfx.OtherCertificates.subject)
        {
           $result = 'OK'
           Write-Log -Message ('The issuer certificate from {0} is included in the PFX' -f $pfx.EndEntityCertificates.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 ([Microsoft.CertificateServices.Commands.PfxData]$pfx,$ExpectedPrefix,$ExpectedDomainFQDN)
    $thisFunction = $MyInvocation.MyCommand.Name

    $test = 'DNS Names'
    # Get the records between cn="<records>"
    $records = @()   
    $records = $pfx.EndEntityCertificates.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 ($dnsNameFailed -notcontains $true)
    {
        $result = 'OK'
    }
    else
    {
        $result = 'Fail'
        $failureDetail += ($LocalizedData.CheckDocumentation)
    }
    $hash = @{'Test' = $test; 'Result' = $result; 'FailureDetail' = $failureDetail; 'outputObject' = $null}
    $object = New-Object PSObject -Property $hash
    $object
}

function Test-KeyUsage
{
    param ([Microsoft.CertificateServices.Commands.PfxData]$pfx)
    $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 = $pfx.EndEntityCertificates[0].Extensions.KeyUsages
    $enhancedKeyUsage = $pfx.EndEntityCertificates[0].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 ($pfx.EndEntityCertificates[0].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 ([Microsoft.CertificateServices.Commands.PfxData]$pfx)
    $thisFunction = $MyInvocation.MyCommand.Name
    Write-Log -Message 'Checking Certificate Chain Order' -Type Info -Function $thisFunction
    # Validating cert chain order
    $test = 'Chain Order'
    if ($pfx.OtherCertificates[-1].Issuer -ne $pfx.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 ([Microsoft.CertificateServices.Commands.PfxData]$pfx,[string[]]$ExpectedPrefix,[string]$ExpectedDomainFQDN)
    
    $test = 'Other Certificates'
    $thisFunction = $MyInvocation.MyCommand.Name
    Write-Log -Message "Checking Pfx file for additional certificates" -Type Info -Function $thisFunction

    # Join all certificates in one array
    $allcerts = @($pfx.otherCertificates + $pfx.EndEntityCertificates)

    $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.thumbprint -join ','),($validCerts.thumbprint -join ',') )
            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.thumbprint -join ',')) -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 ([Microsoft.CertificateServices.Commands.PfxData]$pfx)
    $test = 'Key Size'
    $thisFunction = $MyInvocation.MyCommand.Name
    #get the key length of the public key.
    $keySize = $pfx.EndEntityCertificates.publickey.key.KeySize

    Write-Log -Message "Checking Pfx file for Key Size" -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 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 -ProtectPrivateKey NONE
            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
        Write-Log -Message 'Export complete' -Type Info -Function $thisFunction
    }
    catch
    {
        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}}
    }
}

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
    }
}
# SIG # Begin signature block
# MIIdqAYJKoZIhvcNAQcCoIIdmTCCHZUCAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB
# gjcCAQSgWzBZMDQGCisGAQQBgjcCAR4wJgIDAQAABBAfzDtgWUsITrck0sYpfvNR
# AgEAAgEAAgEAAgEAAgEAMCEwCQYFKw4DAhoFAAQUo8+QDaALRzImi5v4zdqe5uGd
# 8dqgghhUMIIEwjCCA6qgAwIBAgITMwAAALwLLhp7irHHkQAAAAAAvDANBgkqhkiG
# 9w0BAQUFADB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G
# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEw
# HwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwHhcNMTYwOTA3MTc1ODQ3
# WhcNMTgwOTA3MTc1ODQ3WjCBsjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEMMAoGA1UECxMDQU9DMScwJQYDVQQLEx5uQ2lwaGVyIERTRSBFU046
# MTJCNC0yRDVGLTg3RDQxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl
# cnZpY2UwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCrW5IBRZQaAQPT
# HTCSXDRgGi/lbqVTqt3Mp5XqqbEkIZowQp8M/Gyv+1TmRpbFaQIQ4oQ7AqZRsvd+
# PMGtZjo6vUBRyeLKpnHq1a9XYeiGkoGaJu/98Ued3Z+sFD45bhzi6tLzY6kq98KI
# YqK7XsI76kqVU3oIyiETzzoANwuXUNSnm9lAN3l/G8xgDm/3qBWMSjkBvg2GeZ57
# 3WqYP6fImkO9U0bRtuIr6mybzvXUUO+rg6hhdrEnLGI4QQ7frEWReYeyMlgjC7VR
# aJy2gomkh+sEmxxivphgOuJrtPgUhdIlyTkUTtyudNUd/6gTE4zt9TsmFf5wGCsx
# pbZqKFW3AgMBAAGjggEJMIIBBTAdBgNVHQ4EFgQUyHJk5pJfz0FWFyn1nlRJFHyq
# /vcwHwYDVR0jBBgwFoAUIzT42VJGcArtQPt2+7MrsMM1sw8wVAYDVR0fBE0wSzBJ
# oEegRYZDaHR0cDovL2NybC5taWNyb3NvZnQuY29tL3BraS9jcmwvcHJvZHVjdHMv
# TWljcm9zb2Z0VGltZVN0YW1wUENBLmNybDBYBggrBgEFBQcBAQRMMEowSAYIKwYB
# BQUHMAKGPGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljcm9z
# b2Z0VGltZVN0YW1wUENBLmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAQEAPiS9UtetZjCdEkaanFlC+NU/Ti+PUD6O+P6yCASPI6+qK20t
# B16FXJg7rXRee3c/E2wcyWuxeL/0oLkj4LunxQoDDhoOjM9w9SnrWjki/kbkEdbg
# i1Pl4ebDSu+6Six3fdRrLowgkQwXxkCoUWwyFS9dL5BbC5lSzHlOiXiWVlc94vr3
# 9sMaoqsxl6A6Ud9YvbohYuiKJsdpSrLW97wXO66h+Cx289JckOmomW1Zum3ppfgp
# +5lJJBxySomU08S8G5QOOrvjO4KsQ55eHHVWJXhnGL+zhghaSf5TIQuDdohDOnNb
# +FImqnwn3++hmpbkAVWdFUNDNlJemia/hMH9vzCCBgEwggPpoAMCAQICEzMAAADE
# 6Yn4eoFQ6f8AAAAAAMQwDQYJKoZIhvcNAQELBQAwfjELMAkGA1UEBhMCVVMxEzAR
# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2ln
# bmluZyBQQ0EgMjAxMTAeFw0xNzA4MTEyMDIwMjRaFw0xODA4MTEyMDIwMjRaMHQx
# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt
# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xHjAcBgNVBAMTFU1p
# Y3Jvc29mdCBDb3Jwb3JhdGlvbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC
# ggEBAIiKuCTDB4+agHkV/CZg/HKILPr0o5eIlka3o8tfiS86My4ekXj6fKkfggG1
# essavAPKRuvFmff7BB3yhQr/Im6h8mc9xScY5Sgf9QSUQWPs47oVjO0TmjXeOHBU
# bzvsrUUJMEnBvo8wmQzLdsn3c5UWd9GLu5THCIUg7R6oNfFxwuB0AEuK0tyR69Z4
# /o36rWCIPb25H65il7/FhLGQrtavK9NU+zXazXGS5h7/7HFry38IdnTgEFFI1PEA
# yEhMowc15VkN/XycyOZa44X11poPH46m5IQXwdbKnx0Bx/1IpxOSM5chSDL4wiSi
# ALK+U8qDbilbge84boDzu+wTC+sCAwEAAaOCAYAwggF8MB8GA1UdJQQYMBYGCisG
# AQQBgjdMCAEGCCsGAQUFBwMDMB0GA1UdDgQWBBTL1mKEz2A56v9nwlzSyLurt8MT
# mDBSBgNVHREESzBJpEcwRTENMAsGA1UECxMETU9QUjE0MDIGA1UEBRMrMjMwMDEy
# K2M4MDRiNWVhLTQ5YjQtNDIzOC04MzYyLWQ4NTFmYTIyNTRmYzAfBgNVHSMEGDAW
# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v
# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw
# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov
# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx
# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB
# AAYWH9tXwlDII0+iUXjX7fj9zb3VwPH5G1btU8hpRwXVxMvs4vyZW5VfETgowAVF
# E+CaeYi8Zqvbu+sCVSO3PSN4QW2u+PEAWpSZihzMCZXQmhxEMKmlFse6R1v1KzSL
# n49YN8NOHK8iyhDN2IIQqTXwriLIjySmgYvfJxzkZh2JPi7/VwNNwW6DoDLrtLMv
# UFZdBrEVjMgdY7dzDOPWeiYPKpZFpzKDPpY+V0l3I4n+sRDHiuUIFVHFK1oxWzlq
# lqikiGuWKG/xxK7qvUUXzGJOgbVUGkeOmKVtwG4nxvgnH8jtIKkLsfHOC5qU4mqd
# aYOhNtdtIP6F1f/DuJc2Cf49FMGYFKnAhszvgsGrVSRDGLVIhXiG0PnSnT8Z2RSJ
# 542faCSIaDupx4BOJucIIUxj/ZyTFU0ztVZgT9dKuTiO/y7dsV+kQ2vJeM+xu2uP
# g2yHcqrqpfuf3RrWOfxkyW0+COV8g7GtvKO6e8+WVqR6WMsSR2LSIe/8PMQxC/cv
# PmSlN29gUD+3RJBPoAuLvn5Y9sdnh2HbnpjEyIzLb0fhwC6U7bH2sDBt7GpJqOmW
# dsi9CMT+O/WuczcGslbPGdS79ZTKhxzygGoBT7YbgXOz01siPzpYGN+I7mfESacv
# 3CWLPV7Q7DREkR28kQx2gj7vxNgtoQQCjkj5790CzwOiMIIGBzCCA++gAwIBAgIK
# YRZoNAAAAAAAHDANBgkqhkiG9w0BAQUFADBfMRMwEQYKCZImiZPyLGQBGRYDY29t
# MRkwFwYKCZImiZPyLGQBGRYJbWljcm9zb2Z0MS0wKwYDVQQDEyRNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkwHhcNMDcwNDAzMTI1MzA5WhcNMjEw
# NDAzMTMwMzA5WjB3MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQ
# MA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9u
# MSEwHwYDVQQDExhNaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EwggEiMA0GCSqGSIb3
# DQEBAQUAA4IBDwAwggEKAoIBAQCfoWyx39tIkip8ay4Z4b3i48WZUSNQrc7dGE4k
# D+7Rp9FMrXQwIBHrB9VUlRVJlBtCkq6YXDAm2gBr6Hu97IkHD/cOBJjwicwfyzMk
# h53y9GccLPx754gd6udOo6HBI1PKjfpFzwnQXq/QsEIEovmmbJNn1yjcRlOwhtDl
# KEYuJ6yGT1VSDOQDLPtqkJAwbofzWTCd+n7Wl7PoIZd++NIT8wi3U21StEWQn0gA
# SkdmEScpZqiX5NMGgUqi+YSnEUcUCYKfhO1VeP4Bmh1QCIUAEDBG7bfeI0a7xC1U
# n68eeEExd8yb3zuDk6FhArUdDbH895uyAc4iS1T/+QXDwiALAgMBAAGjggGrMIIB
# pzAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQjNPjZUkZwCu1A+3b7syuwwzWz
# DzALBgNVHQ8EBAMCAYYwEAYJKwYBBAGCNxUBBAMCAQAwgZgGA1UdIwSBkDCBjYAU
# DqyCYEBWJ5flJRP8KuEKU5VZ5KShY6RhMF8xEzARBgoJkiaJk/IsZAEZFgNjb20x
# GTAXBgoJkiaJk/IsZAEZFgltaWNyb3NvZnQxLTArBgNVBAMTJE1pY3Jvc29mdCBS
# b290IENlcnRpZmljYXRlIEF1dGhvcml0eYIQea0WoUqgpa1Mc1j0BxMuZTBQBgNV
# HR8ESTBHMEWgQ6BBhj9odHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9w
# cm9kdWN0cy9taWNyb3NvZnRyb290Y2VydC5jcmwwVAYIKwYBBQUHAQEESDBGMEQG
# CCsGAQUFBzAChjhodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01p
# Y3Jvc29mdFJvb3RDZXJ0LmNydDATBgNVHSUEDDAKBggrBgEFBQcDCDANBgkqhkiG
# 9w0BAQUFAAOCAgEAEJeKw1wDRDbd6bStd9vOeVFNAbEudHFbbQwTq86+e4+4LtQS
# ooxtYrhXAstOIBNQmd16QOJXu69YmhzhHQGGrLt48ovQ7DsB7uK+jwoFyI1I4vBT
# Fd1Pq5Lk541q1YDB5pTyBi+FA+mRKiQicPv2/OR4mS4N9wficLwYTp2Oawpylbih
# OZxnLcVRDupiXD8WmIsgP+IHGjL5zDFKdjE9K3ILyOpwPf+FChPfwgphjvDXuBfr
# Tot/xTUrXqO/67x9C0J71FNyIe4wyrt4ZVxbARcKFA7S2hSY9Ty5ZlizLS/n+YWG
# zFFW6J1wlGysOUzU9nm/qhh6YinvopspNAZ3GmLJPR5tH4LwC8csu89Ds+X57H21
# 46SodDW4TsVxIxImdgs8UoxxWkZDFLyzs7BNZ8ifQv+AeSGAnhUwZuhCEl4ayJ4i
# IdBD6Svpu/RIzCzU2DKATCYqSCRfWupW76bemZ3KOm+9gSd0BhHudiG/m4LBJ1S2
# sWo9iaF2YbRuoROmv6pH8BJv/YoybLL+31HIjCPJZr2dHYcSZAI9La9Zj7jkIeW1
# sMpjtHhUBdRBLlCslLCleKuzoJZ1GtmShxN1Ii8yqAhuoFuMJb+g74TKIdbrHk/J
# mu5J4PcBZW+JC33Iacjmbuqnl84xKf8OxVtc2E0bodj6L54/LlUWa8kTo/0wggd6
# MIIFYqADAgECAgphDpDSAAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQG
# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQg
# Um9vdCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDla
# Fw0yNjA3MDgyMTA5MDlaMH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5n
# dG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9y
# YXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEw
# ggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS6
# 8rZYIZ9CGypr6VpQqrgGOBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15
# ZId+lGAkbK+eSZzpaF7S35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+er
# CFDPs0S3XdjELgN1q2jzy23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVc
# eaVJKecNvqATd76UPe/74ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGM
# XeiJT4Qa8qEvWeSQOy2uM1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/
# U7qcD60ZI4TL9LoDho33X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwj
# p6lm7GEfauEoSZ1fiOIlXdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwC
# gl/bwBWzvRvUVUvnOaEP6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1J
# MKerjt/sW5+v/N2wZuLBl4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3co
# KPHtbcMojyyPQDdPweGFRInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfe
# nk70lrC8RqBsmNLg1oiMCwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAw
# HQYDVR0OBBYEFEhuZOVQBdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoA
# UwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQY
# MBaAFHItOgIxkEO5FAVO4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6
# Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1
# dDIwMTFfMjAxMV8wM18yMi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAC
# hkJodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1
# dDIwMTFfMjAxMV8wM18yMi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4D
# MIGDMD8GCCsGAQUFBwIBFjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3Bz
# L2RvY3MvcHJpbWFyeWNwcy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBs
# AF8AcABvAGwAaQBjAHkAXwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcN
# AQELBQADggIBAGfyhqWY4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjD
# ctFtg/6+P+gKyju/R6mj82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw
# /WvjPgcuKZvmPRul1LUdd5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkF
# DJvtaPpoLpWgKj8qa1hJYx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3z
# Dq+ZKJeYTQ49C/IIidYfwzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEn
# Gn+x9Cf43iw6IGmYslmJaG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1F
# p3blQCplo8NdUmKGwx1jNpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0Qax
# dR8UvmFhtfDcxhsEvt9Bxw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AAp
# xbGbpT9Fdx41xtKiop96eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//W
# syNodeav+vyL6wuA6mk7r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqx
# P/uozKRdwaGIm1dxVk5IRcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIEvjCC
# BLoCAQEwgZUwfjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEo
# MCYGA1UEAxMfTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAMTp
# ifh6gVDp/wAAAAAAxDAJBgUrDgMCGgUAoIHSMBkGCSqGSIb3DQEJAzEMBgorBgEE
# AYI3AgEEMBwGCisGAQQBgjcCAQsxDjAMBgorBgEEAYI3AgEVMCMGCSqGSIb3DQEJ
# BDEWBBSL6HGEOjSTY3VidLE4TAuh42uAPjByBgorBgEEAYI3AgEMMWQwYqBIgEYA
# TQBpAGMAcgBvAHMAbwBmAHQAIABBAHoAdQByAGUAUwB0AGEAYwBrACAAUABhAHIA
# dABuAGUAcgBUAG8AbwBsAGsAaQB0oRaAFGh0dHA6Ly9Db2RlU2lnbkluZm8gMA0G
# CSqGSIb3DQEBAQUABIIBAHFrnu/IcQkdprEHyM2UJZEUxQoEJGxHx9bnV4hmVRNT
# lO0eqQxmC+LmgKeBEPtSAn5MyqD06eMht9uEi3aOJZ+PufKksyxkFB7PsAmQqpUk
# JYjaYgSMNmHkyh6RfI/VjiFEuAiLKL9kWQ9HWMAnWgX1a1b9Lul7cpaZMpNxVYZt
# YlwH7FPu94XGRvu5vmF4M1WMiYczTt2vm/HmbdGFs9D6eiIxBOb9p3eN02xyqnyf
# Fk4hKdAgjPfVCriNHtDUUgXXqhdeiA90IBJ7E/E1lhlbLWoqWgndFopvw9a5Fv5o
# 9uAphfBR3Rb8EqLM+HmzwCZCOB1hEERuzQjC0iPMAuihggIoMIICJAYJKoZIhvcN
# AQkGMYICFTCCAhECAQEwgY4wdzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp
# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw
# b3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBAhMzAAAA
# vAsuGnuKsceRAAAAAAC8MAkGBSsOAwIaBQCgXTAYBgkqhkiG9w0BCQMxCwYJKoZI
# hvcNAQcBMBwGCSqGSIb3DQEJBTEPFw0xODA0MjcwOTI5MzBaMCMGCSqGSIb3DQEJ
# BDEWBBSf9c/jfSbrX8gAZnyiEHn1Zsd8HjANBgkqhkiG9w0BAQUFAASCAQCJ3SQV
# cvZNaDPYhT1aguwfmAYbhpC8cLIZb/5GAlc0xiy3Q+lrp6JXeWngzFXhM5oBlxA2
# 8boc2M4VhCAmqtXuHDrcmY4F+1P1FQhdsklG4e3fW0wRJ9Sfhhcgukx914OnH1xW
# 6FRXnFNaNTfAJfJ86wmlWrAwpUq/UQ/t5jdpOkd6Yhl160SIklIkWYM8KqLRTbVo
# mtWELQLxFq7qDyoKYt7idKMToqGQRbLmaLwvgpcGEp9XA/2Vg7qiEXU5pR3JnmiM
# wE3rsHGzN49k8aCqa2XCdiTZd335tfz7rMHTg7Tv1sHBpM9AsCLaQk6Lzi77gLqW
# 8kUq5fn3LmMrHASx
# SIG # End signature block