Core/Test-CiPolicy.psm1

Function Test-CiPolicy {
    [CmdletBinding()]
    [OutputType([System.Boolean], [System.Security.Cryptography.X509Certificates.X509Certificate2[]])]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'XML File')]
        [System.IO.FileInfo]$XmlFile,

        [ValidateScript({ [System.IO.File]::Exists($_) })]
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ParameterSetName = 'CIP File')]
        [System.IO.FileInfo]$CipFile
    )
    Begin {
        [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false
        . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1"
    }

    process {

        # If a CI XML file is being tested
        if ($PSCmdlet.ParameterSetName -eq 'XML File' -and $PSBoundParameters.ContainsKey('XmlFile')) {

            # Check if the schema file exists in the system drive
            if (-NOT ([System.IO.File]::Exists([WDACConfig.GlobalVars]::CISchemaPath))) {
                Throw "The Code Integrity Schema file could not be found at: $([WDACConfig.GlobalVars]::CISchemaPath)"
            }

            # Check if the XML file exists - performing this check here instead of ValidateScript of the parameter produces a better error message when this function is called from within other main cmdlets' parameters.
            if (-NOT ([System.IO.File]::Exists($XmlFile))) {
                Throw "The file $XmlFile does not exist."
            }

            # Assign the schema file path to a variable
            [System.IO.FileInfo]$SchemaFilePath = ([WDACConfig.GlobalVars]::CISchemaPath)
            # Define a script block to handle validation errors
            [System.Management.Automation.ScriptBlock]$ValidationEventHandler = { Throw $args[1].Exception }

            # Create an XML reader object from the schema file path
            [System.Xml.XmlReader]$XmlReader = [System.Xml.XmlReader]::Create($SchemaFilePath)
            # Read the schema object from the XML reader
            [System.Xml.Schema.XmlSchemaObject]$XmlSchemaObject = [System.Xml.Schema.XmlSchema]::Read($XmlReader, $ValidationEventHandler)

            # Create a variable to store the validation result
            [System.Boolean]$IsValid = $false

            try {
                # Create an XML document object
                [System.Xml.XmlDocument]$Xml = New-Object -TypeName System.Xml.XmlDocument
                # Add the schema object to the XML document
                [System.Void]$Xml.Schemas.Add($XmlSchemaObject)
                # Load the XML file to the XML document
                $Xml.Load($XmlFile)
                # Validate the XML document against the schema object
                $Xml.Validate({
                        # Throw an exception if the validation fails
                        Throw ([PsCustomObject] @{
                                XmlFile   = $XmlFile
                                Exception = $args[1].Exception
                            })
                    })

                # If the validation succeeds, set the IsValid variable to $true
                $IsValid = $true
            }
            catch {
                # Rethrow the exception
                Throw $_
            }
            finally {
                # Close the XML reader object
                $XmlReader.Close()
            }

            # Return the validation result
            Return $IsValid
        }

        # If a CI binary is being tested
        elseif ($PSCmdlet.ParameterSetName -eq 'CIP File' -and $PSBoundParameters.ContainsKey('CipFile')) {

            try {

                # Create a new SignedCms object to store the signed message
                [System.Security.Cryptography.Pkcs.SignedCms]$SignedCryptoMsgSyntax = New-Object -TypeName System.Security.Cryptography.Pkcs.SignedCms

                # Decode the signed message from the file specified by $CipFile
                # The file is read as a byte array because the SignedCms.Decode() method expects a byte array as input
                # https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.pkcs.signedcms.decode
                $SignedCryptoMsgSyntax.Decode((Get-Content -LiteralPath $CipFile -AsByteStream -Raw))

                # Return an array of X509Certificate2 objects that represent the certificates used to sign the message
                Return [System.Security.Cryptography.X509Certificates.X509Certificate2[]]$SignedCryptoMsgSyntax.Certificates

            }
            catch {
                Write-Verbose -Message "The file $CipFile does not contain a valid signature." -Verbose
                Return $null
            }
        }
    }
    <#
.SYNOPSIS
    Tests the Code Integrity Policy XML file against the Code Integrity Schema.
    It can also display the signer information from a signed Code Integrity policy .CIP binary file. Get-AuthenticodeSignature cmdlet does not show signers in .CIP files.
.DESCRIPTION
    The Test-CiPolicy cmdlet can test a Code Integrity (WDAC) Policy.
    If you input a XML file, it will validate it against the Schema file located at: "$Env:SystemDrive\Windows\schemas\CodeIntegrity\cipolicy.xsd"
    and returns a boolean value indicating whether the XML file is valid or not.
 
    If you input a signed binary Code Integrity Policy file, it will return the signer information from the file.
.PARAMETER XmlFile
    The Code Integrity Policy XML file to test. Supports file picker GUI.
.PARAMETER CipFile
    The binary Code Integrity Policy file to test for signers. Supports file picker GUI.
.LINK
    https://github.com/HotCakeX/Harden-Windows-Security/wiki/Test-CiPolicy
.INPUTS
    [System.IO.FileInfo]
.OUTPUTS
    System.Boolean
    System.Security.Cryptography.X509Certificates.X509Certificate2[]
.EXAMPLE
    Test-CiPolicy -XmlFile "C:\path\to\policy.xml"
.EXAMPLE
    Test-CiPolicy -CipFile "C:\Users\Admin\{C5F45D1A-97F7-42CF-84F1-40755F1AEB97}.cip"
    #>

}