Resources/Resources2.ps1

# Importing the $PSDefaultParameterValues to the current session, prior to everything else
. "$ModuleRootPath\CoreExt\PSDefaultParameterValues.ps1"

# Defining a custom object to store the signer information
class Signer {
    [System.String]$ID
    [System.String]$Name
    [System.String]$CertRoot
    [System.String]$CertPublisher
}

Function Get-SignerInfo {
    <#
    .SYNOPSIS
        Function that takes an XML file path as input and returns an array of Signer objects
    .INPUTS
        System.IO.FileInfo
    .OUTPUTS
        Signer[]
    .PARAMETER XmlFilePath
        The XML file path that the user selected for WDAC simulation.
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)][System.IO.FileInfo]$XmlFilePath
    )
    begin {
        # Load the XML file
        $Xml = [System.Xml.XmlDocument](Get-Content -Path $XmlFilePath)
    }
    process {
        # Select the Signer nodes
        [System.Object[]]$Signers = $Xml.SiPolicy.Signers.Signer

        # Create an empty array to store the output
        [Signer[]]$Output = @()

        # Loop through each Signer node and extract the information
        foreach ($Signer in $Signers) {
            # Create a new Signer object and assign the properties
            [Signer]$SignerObj = [Signer]::new()
            $SignerObj.ID = $Signer.ID
            $SignerObj.Name = $Signer.Name
            $SignerObj.CertRoot = $Signer.CertRoot.Value
            $SignerObj.CertPublisher = $Signer.CertPublisher.Value

            # Add the Signer object to the output array
            $Output += $SignerObj
        }
    }
    end {
        # Return the output array
        return $Output
    }
}

Function Get-TBSCertificate {
    <#
    .SYNOPSIS
        Function to calculate the TBS value of a certificate
    .INPUTS
        System.Security.Cryptography.X509Certificates.X509Certificate2
    .OUTPUTS
        System.String
    .PARAMETER Cert
        The certificate that is going to be used to retrieve its TBS value
    #>

    [CmdletBinding()]
    param (
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$Cert
    )

    # Get the raw data of the certificate
    [System.Byte[]]$RawData = $Cert.RawData

    # Create an ASN.1 reader to parse the certificate
    [System.Formats.Asn1.AsnReader]$AsnReader = New-Object -TypeName System.Formats.Asn1.AsnReader -ArgumentList $RawData, ([System.Formats.Asn1.AsnEncodingRules]::DER)

    # Read the certificate sequence
    [System.Formats.Asn1.AsnReader]$Certificate = $AsnReader.ReadSequence()

    # Read the TBS (To be signed) value of the certificate
    $TbsCertificate = $Certificate.ReadEncodedValue()

    # Read the signature algorithm sequence
    [System.Formats.Asn1.AsnReader]$SignatureAlgorithm = $Certificate.ReadSequence()

    # Read the algorithm OID of the signature
    [System.String]$AlgorithmOid = $SignatureAlgorithm.ReadObjectIdentifier()

    # Define a hash function based on the algorithm OID
    switch ($AlgorithmOid) {
        '1.2.840.113549.1.1.4' { $HashFunction = [System.Security.Cryptography.MD5]::Create() ; break }
        '1.2.840.10040.4.3' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break }
        '2.16.840.1.101.3.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() ; break }
        '2.16.840.1.101.3.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() ; break }
        '2.16.840.1.101.3.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() ; break }
        '1.2.840.10045.4.1' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break }
        '1.2.840.10045.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() ; break }
        '1.2.840.10045.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() ; break }
        '1.2.840.10045.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() ; break }
        '1.2.840.113549.1.1.5' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break }
        '1.2.840.113549.1.1.11' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() ; break }
        '1.2.840.113549.1.1.12' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() ; break }
        '1.2.840.113549.1.1.13' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() ; break }
        # sha-1WithRSAEncryption
        '1.3.14.3.2.29' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() ; break }
        default { throw "No handler for algorithm $AlgorithmOid" }
    }

    # Compute the hash of the TBS value using the hash function
    [System.Byte[]]$Hash = $HashFunction.ComputeHash($TbsCertificate.ToArray())

    # Convert the hash to a hex string
    [System.String]$HexStringOutput = [System.BitConverter]::ToString($Hash) -replace '-', ''

    # Return the output
    return $HexStringOutput
}

Function Get-AuthenticodeSignatureEx {
    <#
    .SYNOPSIS
        Helps to get the 2nd aka nested signer/signature of the dual signed files
    .NOTES
        This function is used in a very minimum capacity by the WDACConfig module and it's modified to meet the WDACConfig's requirements
    .LINK
        https://www.sysadmins.lv/blog-en/reading-multiple-signatures-from-signed-file-with-powershell.aspx
        https://www.sysadmins.lv/disclaimer.aspx
    .PARAMETER FilePath
        The path of the file(s) to get the signature of
    .INPUTS
        System.String[]
    .OUTPUTS
        System.Management.Automation.Signature
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [System.String[]]$FilePath
    )

    begin {

        # Define the signature of the Crypt32.dll library functions to use
        [System.String]$Signature = @'
    [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptQueryObject(
        int dwObjectType,
        [MarshalAs(UnmanagedType.LPWStr)]
        string pvObject,
        int dwExpectedContentTypeFlags,
        int dwExpectedFormatTypeFlags,
        int dwFlags,
        ref int pdwMsgAndCertEncodingType,
        ref int pdwContentType,
        ref int pdwFormatType,
        ref IntPtr phCertStore,
        ref IntPtr phMsg,
        ref IntPtr ppvContext
    );
    [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptMsgGetParam(
        IntPtr hCryptMsg,
        int dwParamType,
        int dwIndex,
        byte[] pvData,
        ref int pcbData
    );
    [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CryptMsgClose(
        IntPtr hCryptMsg
    );
    [DllImport("crypt32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern bool CertCloseStore(
        IntPtr hCertStore,
        int dwFlags
    );
'@


        # Load the System.Security assembly to use the SignedCms class
        Add-Type -AssemblyName 'System.Security' -ErrorAction SilentlyContinue
        # Add the Crypt32.dll library functions as a type
        Add-Type -MemberDefinition $Signature -Namespace 'PKI' -Name 'Crypt32' -Language CSharp -ErrorAction SilentlyContinue

        # Define some constants for the CryptQueryObject function parameters
        [System.Int16]$CERT_QUERY_OBJECT_FILE = 0x1
        [System.Int32]$CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400
        [System.Int16]$CERT_QUERY_FORMAT_FLAG_BINARY = 0x2

        # Define a helper function to get the timestamps of the countersigners
        function Get-TimeStamps {
            param (
                $SignerInfo
            )

            [System.Object[]]$RetValue = @()

            foreach ($CounterSignerInfos in $Infos.CounterSignerInfos) {
                # Get the signing time attribute from the countersigner info object
                $STime = ($CounterSignerInfos.SignedAttributes | Where-Object -FilterScript { $_.Oid.Value -eq '1.2.840.113549.1.9.5' }).Values | `
                    Where-Object -FilterScript { $null -ne $_.SigningTime }
                # Create a custom object with the countersigner certificate and signing time properties
                $TsObject = New-Object psobject -Property @{
                    Certificate = $CounterSignerInfos.Certificate
                    SigningTime = $STime.SigningTime.ToLocalTime()
                }
                # Add the custom object to the return value array
                $RetValue += $TsObject
            }
            # Return the array of custom objects with countersigner info
            $RetValue

        }
    }
    process {
        # For each file path, get the authenticode signature using the built-in cmdlet
        foreach ($Output in Get-AuthenticodeSignature $FilePath) {

            # Initialize some variables to store the output parameters of the CryptQueryObject function
            [System.Int64]$PdwMsgAndCertEncodingType = 0
            [System.Int64]$PdwContentType = 0
            [System.Int64]$PdwFormatType = 0
            [System.IntPtr]$PhCertStore = [System.IntPtr]::Zero
            [System.IntPtr]$PhMsg = [System.IntPtr]::Zero
            [System.IntPtr]$PpvContext = [System.IntPtr]::Zero

            # Call the CryptQueryObject function to get the handle of the PKCS #7 message from the file path
            $Return = [PKI.Crypt32]::CryptQueryObject(
                $CERT_QUERY_OBJECT_FILE,
                $Output.Path,
                $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
                $CERT_QUERY_FORMAT_FLAG_BINARY,
                0,
                [ref]$pdwMsgAndCertEncodingType,
                [ref]$pdwContentType,
                [ref]$pdwFormatType,
                [ref]$phCertStore,
                [ref]$phMsg,
                [ref]$ppvContext
            )
            # If the function fails, return nothing
            if (!$Return) { return }

            # Initialize a variable to store the size of the PKCS #7 message data
            [System.Int64]$PcbData = 0

            # Call the CryptMsgGetParam function to get the size of the PKCS #7 message data
            $Return = [PKI.Crypt32]::CryptMsgGetParam($phMsg, 29, 0, $null, [ref]$PcbData)

            # If the function fails, return nothing
            if (!$Return) { return }

            # Create a byte array to store the PKCS #7 message data
            $PvData = New-Object -TypeName 'System.Byte[]' -ArgumentList $PcbData

            # Call the CryptMsgGetParam function again to get the PKCS #7 message data
            $Return = [PKI.Crypt32]::CryptMsgGetParam($PhMsg, 29, 0, $PvData, [System.Management.Automation.PSReference]$PcbData)

            # Create a SignedCms object to decode the PKCS #7 message data
            [System.Security.Cryptography.Pkcs.SignedCms]$SignedCms = New-Object -TypeName 'Security.Cryptography.Pkcs.SignedCms'

            # Decode the PKCS #7 message data and populate the SignedCms object properties
            $SignedCms.Decode($PvData)

            # Get the first signer info object from the SignedCms object
            $Infos = $SignedCms.SignerInfos[0]

            # Add some properties to the output object, such as TimeStamps, DigestAlgorithm and NestedSignature
            $Output | Add-Member -MemberType NoteProperty -Name TimeStamps -Value $null
            $Output | Add-Member -MemberType NoteProperty -Name DigestAlgorithm -Value $Infos.DigestAlgorithm.FriendlyName

            # Call the helper function to get the timestamps of the countersigners and assign it to the TimeStamps property
            $Output.TimeStamps = Get-TimeStamps -SignerInfo $Infos

            # Check if there is a nested signature attribute in the signer info object by looking for the OID 1.3.6.1.4.1.311.2.4.1
            $second = $Infos.UnsignedAttributes | Where-Object -FilterScript { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' }

            if ($Second) {

                # If there is a nested signature attribute
                # Get the value of the nested signature attribute as a raw data byte array
                $value = $Second.Values | Where-Object -FilterScript { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' }

                # Create another SignedCms object to decode the nested signature data
                [System.Security.Cryptography.Pkcs.SignedCms]$SignedCms2 = New-Object -TypeName 'Security.Cryptography.Pkcs.SignedCms'

                # Decode the nested signature data and populate the SignedCms object properties
                $SignedCms2.Decode($value.RawData)
                $Output | Add-Member -MemberType NoteProperty -Name NestedSignature -Value $null

                # Get the first signer info object from the nested signature SignedCms object
                $Infos = $SignedCms2.SignerInfos[0]

                # Create a custom object with some properties of the nested signature, such as signer certificate, digest algorithm and timestamps
                $Nested = New-Object -TypeName 'psobject' -Property @{
                    SignerCertificate = $Infos.Certificate
                    DigestAlgorithm   = $Infos.DigestAlgorithm.FriendlyName
                    TimeStamps        = Get-TimeStamps -SignerInfo $Infos
                }
                # Assign the custom object to the NestedSignature property of the output object
                $Output.NestedSignature = $Nested
            }
            # Return the output object with the added properties
            $Output

            # Close the handles of the PKCS #7 message and the certificate store
            [void][PKI.Crypt32]::CryptMsgClose($PhMsg)
            [void][PKI.Crypt32]::CertCloseStore($PhCertStore, 0)
        }
    }
    end {}
}

Function Get-SignedFileCertificates {
    <#
    .SYNOPSIS
        A function to get all the certificates from a signed file or a certificate object and output a Collection
    .PARAMETER FilePath
        Optional parameter, the function will get all the certificates from this file if this parameter is used
    .PARAMETER X509Certificate2
        Optional parameter, the function will get all the certificates from this certificate object if this parameter is used
    .INPUTS
        System.String
        System.Security.Cryptography.X509Certificates.X509Certificate2
    .OUTPUTS
        System.Security.Cryptography.X509Certificates.X509Certificate2Collection
    #>

    [CmdletBinding()]
    param (
        [Parameter()]
        [System.String]$FilePath,
        [Parameter(ValueFromPipeline = $true)]
        [System.Security.Cryptography.X509Certificates.X509Certificate2]$X509Certificate2
    )

    begin {
        # Create an X509Certificate2Collection object
        [System.Security.Cryptography.X509Certificates.X509Certificate2Collection]$CertCollection = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2Collection
    }

    process {
        # Check which parameter set is used
        if ($FilePath) {
            # If the FilePath parameter is used, import all the certificates from the file
            $CertCollection.Import($FilePath, $null, 'DefaultKeySet')
        }
        elseif ($X509Certificate2) {
            # If the CertObject parameter is used, add the certificate object to the collection
            $CertCollection.Add($X509Certificate2)
        }
    }

    end {
        # Return the collection
        return $CertCollection
    }
}

Function Get-CertificateDetails {
    <#
    .SYNOPSIS
        A function to detect Root, Intermediate and Leaf certificates
    .INPUTS
        System.String
        System.Management.Automation.SwitchParameter
    .OUTPUTS
        System.Object[]
    .PARAMETER FilePath
        Path to a signed file
    .PARAMETER X509Certificate2
        An X509Certificate2 object
    .PARAMETER IntermediateOnly
        Indicates that the function will only return the Intermediate certificate details
    .PARAMETER LeafCertificate
        Indicates that the function will only return the Leaf certificate details
    .PARAMETER LeafCNOfTheNestedCertificate
        This is used only for when -X509Certificate2 parameter is used, so that we can filter out the Leaf certificate and only get the Intermediate certificates at the end of this function
    #>

    [CmdletBinding()]
    param (
        [Parameter(ParameterSetName = 'Based on File Path', Mandatory = $true)]
        [System.String]$FilePath,

        [Parameter(ParameterSetName = 'Based on Certificate', Mandatory = $true)]
        $X509Certificate2,

        [Parameter(ParameterSetName = 'Based on Certificate')]
        [System.String]$LeafCNOfTheNestedCertificate,

        [Parameter(ParameterSetName = 'Based on File Path')]
        [Parameter(ParameterSetName = 'Based on Certificate')]
        [System.Management.Automation.SwitchParameter]$IntermediateOnly,

        [Parameter(ParameterSetName = 'Based on File Path')]
        [Parameter(ParameterSetName = 'Based on Certificate')]
        [System.Management.Automation.SwitchParameter]$LeafCertificate
    )

    # An array to hold objects
    [System.Object[]]$Obj = @()

    if ($FilePath) {
        # Get all the certificates from the file path using the Get-SignedFileCertificates function
        $CertCollection = Get-SignedFileCertificates -FilePath $FilePath | Where-Object -FilterScript { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' }
    }
    else {
        # The "| Where-Object -FilterScript {$_ -ne 0}" part is used to filter the output coming from Get-AuthenticodeSignatureEx function that gets nested certificate
        $CertCollection = Get-SignedFileCertificates -X509Certificate2 $X509Certificate2 | Where-Object -FilterScript { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } | Where-Object -FilterScript { $_ -ne 0 }
    }

    # Loop through each certificate in the collection and call this function recursively with the certificate object as an input
    foreach ($Cert in $CertCollection) {

        # Build the certificate chain
        [System.Security.Cryptography.X509Certificates.X509Chain]$Chain = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Chain

        # Set the chain policy properties
        $chain.ChainPolicy.RevocationMode = 'NoCheck'
        $chain.ChainPolicy.RevocationFlag = 'EndCertificateOnly'
        $chain.ChainPolicy.VerificationFlags = 'NoFlag'

        [void]$Chain.Build($Cert)

        # If AllCertificates is present, loop through all chain elements and display all certificates
        foreach ($Element in $Chain.ChainElements) {
            # Create a custom object with the certificate properties

            # Extract the data after CN= in the subject and issuer properties
            # When a common name contains a comma ',' then it will automatically be wrapped around double quotes. E.g., "Skylum Software USA, Inc."
            # The methods below are conditional regex. Different patterns are used based on the availability of at least one double quote in the CN field, indicating that it had comma in it so it had been enclosed with double quotes by system

            $Element.Certificate.Subject -match 'CN=(?<InitialRegexTest2>.*?),.*' | Out-Null
            [System.String]$SubjectCN = $matches['InitialRegexTest2'] -like '*"*' ? ($Element.Certificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest2']

            $Element.Certificate.Issuer -match 'CN=(?<InitialRegexTest3>.*?),.*' | Out-Null
            [System.String]$IssuerCN = $matches['InitialRegexTest3'] -like '*"*' ? ($Element.Certificate.Issuer -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest3']

            # Get the TBS value of the certificate using the Get-TBSCertificate function
            [System.String]$TbsValue = Get-TBSCertificate -cert $Element.Certificate
            # Create a custom object with the extracted properties and the TBS value
            $Obj += [pscustomobject]@{
                SubjectCN = $SubjectCN
                IssuerCN  = $IssuerCN
                NotAfter  = $element.Certificate.NotAfter
                TBSValue  = $TbsValue
            }
        }
    }

    if ($FilePath) {

        # The reason the commented code below is not used is because some files such as C:\Windows\System32\xcopy.exe or d3dcompiler_47.dll that are signed by Microsoft report a different Leaf certificate common name when queried using Get-AuthenticodeSignature
        # (Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -match 'CN=(?<InitialRegexTest4>.*?),.*' | Out-Null

        [System.Security.Cryptography.X509Certificates.X509Certificate]$CertificateUsingAlternativeMethod = [System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromSignedFile($FilePath)
        $CertificateUsingAlternativeMethod.Subject -match 'CN=(?<InitialRegexTest4>.*?),.*' | Out-Null

        [System.String]$TestAgainst = $matches['InitialRegexTest4'] -like '*"*' ? ((Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest4']

        if ($IntermediateOnly) {
            # ($_.SubjectCN -ne $_.IssuerCN) -> To omit Root certificate from the result
            # ($_.SubjectCN -ne $TestAgainst) -> To omit the Leaf certificate

            $FinalObj = $Obj |
            Where-Object -FilterScript { ($_.SubjectCN -ne $_.IssuerCN) -and ($_.SubjectCN -ne $TestAgainst) } |
            Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property

            return [System.Object[]]$FinalObj
        }
        elseif ($LeafCertificate) {
            # ($_.SubjectCN -ne $_.IssuerCN) -> To omit Root certificate from the result
            # ($_.SubjectCN -eq $TestAgainst) -> To get the Leaf certificate

            $FinalObj = $Obj |
            Where-Object -FilterScript { ($_.SubjectCN -ne $_.IssuerCN) -and ($_.SubjectCN -eq $TestAgainst) } |
            Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property

            return [System.Object[]]$FinalObj
        }

    }
    # If nested certificate is being processed and X509Certificate2 object is passed
    elseif ($X509Certificate2) {

        if ($IntermediateOnly) {
            # ($_.SubjectCN -ne $_.IssuerCN) -> To omit Root certificate from the result
            # ($_.SubjectCN -ne $LeafCNOfTheNestedCertificate) -> To omit the Leaf certificate

            $FinalObj = $Obj |
            Where-Object -FilterScript { ($_.SubjectCN -ne $_.IssuerCN) -and ($_.SubjectCN -ne $LeafCNOfTheNestedCertificate) } |
            Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property

            return [System.Object[]]$FinalObj
        }
        elseif ($LeafCertificate) {
            # ($_.SubjectCN -ne $_.IssuerCN) -> To omit Root certificate from the result
            # ($_.SubjectCN -eq $LeafCNOfTheNestedCertificate) -> To get the Leaf certificate

            $FinalObj = $Obj |
            Where-Object -FilterScript { ($_.SubjectCN -ne $_.IssuerCN) -and ($_.SubjectCN -eq $LeafCNOfTheNestedCertificate) } |
            Group-Object -Property TBSValue | ForEach-Object -Process { $_.Group[0] } # To make sure the output values are unique based on TBSValue property

            return [System.Object[]]$FinalObj
        }
    }
}

Function Compare-SignerAndCertificate {
    <#
    .SYNOPSIS
        a function that takes WDAC XML policy file path and a Signed file path as inputs and compares the output of the Get-SignerInfo and Get-CertificateDetails functions
    .INPUTS
        System.String
    .OUTPUTS
        System.Object[]
    .PARAMETER XmlFilePath
        Path to a WDAC XML file
    .PARAMETER SignedFilePath
        Path to a signed file
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)][System.String]$XmlFilePath,
        [Parameter(Mandatory = $true)][System.String]$SignedFilePath
    )

    # Get the signer information from the XML file path using the Get-SignerInfo function
    [Signer[]]$SignerInfo = Get-SignerInfo -XmlFilePath $XmlFilePath

    # An array to store the details of the Primary certificate's Intermediate certificate(s) of the signed file
    [System.Object[]]$PrimaryCertificateIntermediateDetails = @()

    # An array to store the details of the Nested certificate of the signed file
    [System.Object[]]$NestedCertificateDetails = @()

    # An array to store the final comparison results of this function
    [System.Object[]]$ComparisonResults = @()

    # Get the intermediate certificate(s) details of the Primary certificate from the signed file using the Get-CertificateDetails function
    [System.Object[]]$PrimaryCertificateIntermediateDetails = Get-CertificateDetails -IntermediateOnly -FilePath $SignedFilePath

    # Get the Nested (Secondary) certificate of the signed file, if any
    [System.Management.Automation.Signature]$ExtraCertificateDetails = Get-AuthenticodeSignatureEx -FilePath $SignedFilePath

    # Extract the Nested (Secondary) certificate from the nested property, if any
    $NestedCertificate = ($ExtraCertificateDetails).NestedSignature.SignerCertificate

    if ($null -ne [System.Security.Cryptography.X509Certificates.X509Certificate2]$NestedCertificate) {
        # First get the CN of the leaf certificate of the nested Certificate
        $NestedCertificate.Subject -match 'CN=(?<InitialRegexTest1>.*?),.*' | Out-Null
        $LeafCNOfTheNestedCertificate = $matches['InitialRegexTest1'] -like '*"*' ? ($NestedCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest1']

        # Send the nested certificate along with its Leaf certificate's CN to the Get-CertificateDetails function with -IntermediateOnly parameter in order to only get the intermediate certificates of the Nested certificate
        $NestedCertificateDetails = Get-CertificateDetails -IntermediateOnly -X509Certificate2 $NestedCertificate -LeafCNOfTheNestedCertificate $LeafCNOfTheNestedCertificate
    }

    # Get the leaf certificate details of the Main Certificate from the signed file path
    [System.Object]$LeafCertificateDetails = Get-CertificateDetails -LeafCertificate -FilePath $SignedFilePath

    # Get the leaf certificate details of the Nested Certificate from the signed file path, if it exists
    if ($null -ne $NestedCertificate) {
        # append an X509Certificate2 object to the array
        $NestedLeafCertificateDetails = Get-CertificateDetails -LeafCertificate -X509Certificate2 $NestedCertificate -LeafCNOfTheNestedCertificate $LeafCNOfTheNestedCertificate
    }

    # Loop through each signer in the signer information array
    foreach ($Signer in $SignerInfo) {
        # Create a custom object to store the comparison result for this signer
        $ComparisonResult = [pscustomobject]@{
            SignerID            = $Signer.ID
            SignerName          = $Signer.Name
            SignerCertRoot      = $Signer.CertRoot
            SignerCertPublisher = $Signer.CertPublisher
            CertSubjectCN       = $null
            CertIssuerCN        = $null
            CertNotAfter        = $null
            CertTBSValue        = $null
            CertRootMatch       = $false
            CertNameMatch       = $false
            CertPublisherMatch  = $false
            FilePath            = $SignedFilePath # Add the file path to the object
        }

        # Loop through each certificate in the certificate details array of the Main Cert
        foreach ($Certificate in $PrimaryCertificateIntermediateDetails) {

            # Check if the signer's CertRoot (referring to the TBS value in the xml file which belongs to an intermediate cert of the file)...
            # ...matches the TBSValue of the file's certificate (TBS values of one of the intermediate certificates of the file since -IntermediateOnly parameter is used earlier and that's what FilePublisher level uses)
            # So this checks to see if the Signer's TBS value in xml matches any of the TBS value(s) of the file's intermediate certificate(s), if it does, that means that file is allowed to run by the WDAC engine

            # Or if the Signer's CertRoot matches the TBS value of the file's primary certificate's Leaf Certificate
            # This can happen with other rules than FilePublisher etc.
            if (($Signer.CertRoot -eq $Certificate.TBSValue) -or ($Signer.CertRoot -eq $LeafCertificateDetails.TBSValue)) {

                # Assign the certificate properties to the comparison result object and set the CertRootMatch to true based on further conditions
                $ComparisonResult.CertSubjectCN = $Certificate.SubjectCN
                $ComparisonResult.CertIssuerCN = $Certificate.IssuerCN
                $ComparisonResult.CertNotAfter = $Certificate.NotAfter
                $ComparisonResult.CertTBSValue = $Certificate.TBSValue

                # if the signed file has nested certificate, only set a flag instead of setting the entire CertRootMatch property to true
                if ($null -ne $NestedCertificate) {
                    $CertRootMatchPart1 = $true
                }
                else {
                    # meaning one of the TBS values of the file's intermediate certs or File's Primary Leaf Certificate's TBS value is in the xml file signers' TBS values
                    $ComparisonResult.CertRootMatch = $true
                }

                # Check if the signer's name (Referring to the one in the XML file) matches the Intermediate certificate's SubjectCN or Leaf Certificate's SubjectCN
                if (($Signer.Name -eq $Certificate.SubjectCN) -or ($Signer.Name -eq $LeafCertificateDetails.SubjectCN)) {
                    # Set the CertNameMatch to true
                    $ComparisonResult.CertNameMatch = $true # this should naturally be always true like the CertRootMatch because this is the CN of the same cert that has its TBS value in the xml file in signers
                }

                # Check if the signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the leaf certificate's SubjectCN (of the file)
                if ($Signer.CertPublisher -eq $LeafCertificateDetails.SubjectCN) {

                    # if the signed file has nested certificate, only set a flag instead of setting the entire CertPublisherMatch property to true
                    if ($null -ne $NestedCertificate) {
                        $CertPublisherMatchPart1 = $true
                    }
                    else {
                        $ComparisonResult.CertPublisherMatch = $true
                    }
                }

                # Break out of the inner loop whether we found a match for this signer or not
                break
            }
        }

        # Nested Certificate TBS processing, if it exists
        if ($null -ne $NestedCertificate) {

            # Loop through each certificate in the NESTED certificate details array
            foreach ($Certificate in $NestedCertificateDetails) {

                # Check if the signer's CertRoot (referring to the TBS value in the xml file which belongs to an intermediate cert of the file)...
                # ...matches the TBSValue of the file's certificate (TBS values of one of the intermediate certificates of the file since -IntermediateOnly parameter is used earlier and that's what FilePublisher level uses)
                # So this checks to see if the Signer's TBS value in xml matches any of the TBS value(s) of the file's intermediate certificate(s), if yes, that means that file is allowed to run by WDAC engine
                if ($Signer.CertRoot -eq $Certificate.TBSValue) {

                    # Assign the certificate properties to the comparison result object and set the CertRootMatch to true
                    $ComparisonResult.CertSubjectCN = $Certificate.SubjectCN
                    $ComparisonResult.CertIssuerCN = $Certificate.IssuerCN
                    $ComparisonResult.CertNotAfter = $Certificate.NotAfter
                    $ComparisonResult.CertTBSValue = $Certificate.TBSValue

                    # When file has nested signature, only set a flag instead of setting the entire property to true
                    $CertRootMatchPart2 = $true

                    # Check if the signer's Name matches the Intermediate certificate's SubjectCN
                    if ($Signer.Name -eq $Certificate.SubjectCN) {
                        # Set the CertNameMatch to true
                        $ComparisonResult.CertNameMatch = $true # this should naturally be always true like the CertRootMatch because this is the CN of the same cert that has its TBS value in the xml file in signers
                    }

                    # Check if the signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the leaf certificate's SubjectCN (of the file)
                    if ($Signer.CertPublisher -eq $LeafCNOfTheNestedCertificate) {
                        # If yes, set the CertPublisherMatch to true for this comparison result object
                        $CertPublisherMatchPart2 = $true
                    }

                    # Break out of the inner loop whether we found a match for this signer or not
                    break
                }
            }
        }

        # if the signed file has nested certificate
        if ($null -ne $NestedCertificate) {

            # check if both of the file's certificates (Nested and Main) are available in the Signers in xml policy
            if (($CertRootMatchPart1 -eq $true) -and ($CertRootMatchPart2 -eq $true)) {
                $ComparisonResult.CertRootMatch = $true # meaning all of the TBS values of the double signed file's intermediate certificates exists in the xml file's signers' TBS values
            }
            else {
                $ComparisonResult.CertRootMatch = $false
            }

            # check if Lean certificate CN of both of the file's certificates (Nested and Main) are available in the Signers in xml policy
            if (($CertPublisherMatchPart1 -eq $true) -and ($CertPublisherMatchPart2 -eq $true)) {
                $ComparisonResult.CertPublisherMatch = $true
            }
            else {
                $ComparisonResult.CertPublisherMatch = $false
            }
        }

        # Add the comparison result object to the comparison results array
        [System.Object[]]$ComparisonResults += $ComparisonResult
    }

    # Return the comparison results array
    return $ComparisonResults
}

Function Get-FileRuleOutput {
    <#
    .SYNOPSIS
        a function to load an xml file and create an output array of custom objects that contain the file rules that are based on file hashes
    .PARAMETER XmlPath
        Path to the XML file that user selected for WDAC simulation
    .INPUTS
        System.IO.FileInfo
    .OUTPUTS
        System.Object[]
    #>

    [CmdletBinding()]
    param(
        [parameter(Mandatory = $true)]
        [System.IO.FileInfo]$XmlPath
    )

    # Load the xml file into a variable
    $Xml = [System.Xml.XmlDocument](Get-Content -Path $XmlPath)

    # Create an empty array to store the output
    [System.Object[]]$OutputHashInfoProcessing = @()

    # Loop through each file rule in the xml file
    foreach ($FileRule in $Xml.SiPolicy.FileRules.Allow) {

        # Extract the hash value from the Hash attribute
        [System.String]$Hashvalue = $FileRule.Hash

        # Extract the hash type from the FriendlyName attribute using regex
        [System.String]$HashType = $FileRule.FriendlyName -replace '.* (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$', '$1'

        # Extract the file path from the FriendlyName attribute using regex
        [System.IO.FileInfo]$FilePathForHash = $FileRule.FriendlyName -replace ' (Hash (Sha1|Sha256|Page Sha1|Page Sha256|Authenticode SIP Sha256))$', ''

        # Create a custom object with the three properties
        $Object = [PSCustomObject]@{
            HashValue       = $Hashvalue
            HashType        = $HashType
            FilePathForHash = $FilePathForHash
        }

        # Add the object to the output array if it is not a duplicate hash value
        if ($OutputHashInfoProcessing.HashValue -notcontains $Hashvalue) {
            $OutputHashInfoProcessing += $Object
        }
    }

    # Only show the Authenticode Hash SHA256
    [System.Object[]]$OutputHashInfoProcessing = $OutputHashInfoProcessing | Where-Object -FilterScript { $_.hashtype -eq 'Hash Sha256' }

    # Return the output array
    return $OutputHashInfoProcessing
}

# SIG # Begin signature block
# MIILkgYJKoZIhvcNAQcCoIILgzCCC38CAQExDzANBglghkgBZQMEAgEFADB5Bgor
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBSfufJTxfuC6Fa
# KJJjsf+/FjtYStp8L2XwvMJYpL8KUaCCB9AwggfMMIIFtKADAgECAhMeAAAABI80
# LDQz/68TAAAAAAAEMA0GCSqGSIb3DQEBDQUAME8xEzARBgoJkiaJk/IsZAEZFgNj
# b20xIjAgBgoJkiaJk/IsZAEZFhJIT1RDQUtFWC1DQS1Eb21haW4xFDASBgNVBAMT
# C0hPVENBS0VYLUNBMCAXDTIzMTIyNzExMjkyOVoYDzIyMDgxMTEyMTEyOTI5WjB5
# MQswCQYDVQQGEwJVSzEeMBwGA1UEAxMVSG90Q2FrZVggQ29kZSBTaWduaW5nMSMw
# IQYJKoZIhvcNAQkBFhRob3RjYWtleEBvdXRsb29rLmNvbTElMCMGCSqGSIb3DQEJ
# ARYWU3B5bmV0Z2lybEBvdXRsb29rLmNvbTCCAiIwDQYJKoZIhvcNAQEBBQADggIP
# ADCCAgoCggIBAKb1BJzTrpu1ERiwr7ivp0UuJ1GmNmmZ65eckLpGSF+2r22+7Tgm
# pEifj9NhPw0X60F9HhdSM+2XeuikmaNMvq8XRDUFoenv9P1ZU1wli5WTKHJ5ayDW
# k2NP22G9IPRnIpizkHkQnCwctx0AFJx1qvvd+EFlG6ihM0fKGG+DwMaFqsKCGh+M
# rb1bKKtY7UEnEVAsVi7KYGkkH+ukhyFUAdUbh/3ZjO0xWPYpkf/1ldvGes6pjK6P
# US2PHbe6ukiupqYYG3I5Ad0e20uQfZbz9vMSTiwslLhmsST0XAesEvi+SJYz2xAQ
# x2O4n/PxMRxZ3m5Q0WQxLTGFGjB2Bl+B+QPBzbpwb9JC77zgA8J2ncP2biEguSRJ
# e56Ezx6YpSoRv4d1jS3tpRL+ZFm8yv6We+hodE++0tLsfpUq42Guy3MrGQ2kTIRo
# 7TGLOLpayR8tYmnF0XEHaBiVl7u/Szr7kmOe/CfRG8IZl6UX+/66OqZeyJ12Q3m2
# fe7ZWnpWT5sVp2sJmiuGb3atFXBWKcwNumNuy4JecjQE+7NF8rfIv94NxbBV/WSM
# pKf6Yv9OgzkjY1nRdIS1FBHa88RR55+7Ikh4FIGPBTAibiCEJMc79+b8cdsQGOo4
# ymgbKjGeoRNjtegZ7XE/3TUywBBFMf8NfcjF8REs/HIl7u2RHwRaUTJdAgMBAAGj
# ggJzMIICbzA8BgkrBgEEAYI3FQcELzAtBiUrBgEEAYI3FQiG7sUghM++I4HxhQSF
# hqV1htyhDXuG5sF2wOlDAgFkAgEIMBMGA1UdJQQMMAoGCCsGAQUFBwMDMA4GA1Ud
# DwEB/wQEAwIHgDAMBgNVHRMBAf8EAjAAMBsGCSsGAQQBgjcVCgQOMAwwCgYIKwYB
# BQUHAwMwHQYDVR0OBBYEFOlnnQDHNUpYoPqECFP6JAqGDFM6MB8GA1UdIwQYMBaA
# FICT0Mhz5MfqMIi7Xax90DRKYJLSMIHUBgNVHR8EgcwwgckwgcaggcOggcCGgb1s
# ZGFwOi8vL0NOPUhPVENBS0VYLUNBLENOPUhvdENha2VYLENOPUNEUCxDTj1QdWJs
# aWMlMjBLZXklMjBTZXJ2aWNlcyxDTj1TZXJ2aWNlcyxDTj1Db25maWd1cmF0aW9u
# LERDPU5vbkV4aXN0ZW50RG9tYWluLERDPWNvbT9jZXJ0aWZpY2F0ZVJldm9jYXRp
# b25MaXN0P2Jhc2U/b2JqZWN0Q2xhc3M9Y1JMRGlzdHJpYnV0aW9uUG9pbnQwgccG
# CCsGAQUFBwEBBIG6MIG3MIG0BggrBgEFBQcwAoaBp2xkYXA6Ly8vQ049SE9UQ0FL
# RVgtQ0EsQ049QUlBLENOPVB1YmxpYyUyMEtleSUyMFNlcnZpY2VzLENOPVNlcnZp
# Y2VzLENOPUNvbmZpZ3VyYXRpb24sREM9Tm9uRXhpc3RlbnREb21haW4sREM9Y29t
# P2NBQ2VydGlmaWNhdGU/YmFzZT9vYmplY3RDbGFzcz1jZXJ0aWZpY2F0aW9uQXV0
# aG9yaXR5MA0GCSqGSIb3DQEBDQUAA4ICAQA7JI76Ixy113wNjiJmJmPKfnn7brVI
# IyA3ZudXCheqWTYPyYnwzhCSzKJLejGNAsMlXwoYgXQBBmMiSI4Zv4UhTNc4Umqx
# pZSpqV+3FRFQHOG/X6NMHuFa2z7T2pdj+QJuH5TgPayKAJc+Kbg4C7edL6YoePRu
# HoEhoRffiabEP/yDtZWMa6WFqBsfgiLMlo7DfuhRJ0eRqvJ6+czOVU2bxvESMQVo
# bvFTNDlEcUzBM7QxbnsDyGpoJZTx6M3cUkEazuliPAw3IW1vJn8SR1jFBukKcjWn
# aau+/BE9w77GFz1RbIfH3hJ/CUA0wCavxWcbAHz1YoPTAz6EKjIc5PcHpDO+n8Fh
# t3ULwVjWPMoZzU589IXi+2Ol0IUWAdoQJr/Llhub3SNKZ3LlMUPNt+tXAs/vcUl0
# 7+Dp5FpUARE2gMYA/XxfU9T6Q3pX3/NRP/ojO9m0JrKv/KMc9sCGmV9sDygCOosU
# 5yGS4Ze/DJw6QR7xT9lMiWsfgL96Qcw4lfu1+5iLr0dnDFsGowGTKPGI0EvzK7H+
# DuFRg+Fyhn40dOUl8fVDqYHuZJRoWJxCsyobVkrX4rA6xUTswl7xYPYWz88WZDoY
# gI8AwuRkzJyUEA07IYtsbFCYrcUzIHME4uf8jsJhCmb0va1G2WrWuyasv3K/G8Nn
# f60MsDbDH1mLtzGCAxgwggMUAgEBMGYwTzETMBEGCgmSJomT8ixkARkWA2NvbTEi
# MCAGCgmSJomT8ixkARkWEkhPVENBS0VYLUNBLURvbWFpbjEUMBIGA1UEAxMLSE9U
# Q0FLRVgtQ0ECEx4AAAAEjzQsNDP/rxMAAAAAAAQwDQYJYIZIAWUDBAIBBQCggYQw
# GAYKKwYBBAGCNwIBDDEKMAigAoAAoQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGC
# NwIBBDAcBgorBgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQx
# IgQg/hJkoG5rWjwvn5OCIWg4ZamC8CFlilTWGMzFK+oqaR4wDQYJKoZIhvcNAQEB
# BQAEggIAhG3KGEa1oLxWEqkjioHP4TfHOrSsC1bUaD3mVLVlspKuvcy7iXoxko+T
# gI6R2OWehihmKpKIQsGKRM2/x3hcqadmFIVIiD3DjfWqMywdee/P8lAB46yMW0d5
# qdnIofkQTuPdr+OSNRXMcAx4MAJoRMzuIE4uaJk8BsHV04Xj2vZ7geCI+YO+Gn6g
# hf2ZhqiW6HgNE6ef1tGjq/YQap+sv6sSBxVg+hgMYUkBx60pwuTi/p4YK4LAEXQV
# 9nFPojZ5cK0Q5sm4wlhBQXR568A9A4tyVLU6IIfkjGkc+m4kwbszYAT1T2YrQu/R
# pTL/IBjDRTIpdSk3s0n38qJi7cqiDGlow+g3XBUOjlZ0W4Tmbxk5Bb0Q3DUaunT6
# ptoaj9EMOoavglC/OeyLdLS/GGGlUq03snlh62iAuP93yLEl+/SPcSexgGT7PmmP
# oUrb9rWot9OQjsxfv6HnI7VqGmG74j4dYT5snBw41E7aa/0GPY765ybO56lMb/dg
# Fy6PcZgrVad2AWE8klXSTRfxsp/AonGoq9z5Bve72CinyxscOCFuA9uEyKKOhOxA
# dyr9EiVQMk417mThwh65ZQsJZisFvIeJsMVRb4CzmNhxOyijAxDW6PGR7lVq/bj4
# OcuJCgaT818PsgOcB1MiFG50UYHKcDZveuzSU4C0aY7C862IDGo=
# SIG # End signature block