Resources2.ps1
# For testing purposes #$FilePath = '' #$SignedFilePath = '' #$XmlFilePath = '' # Defining a custom object to store the signer information class Signer { [string]$ID [string]$Name [string]$CertRoot [string]$CertPublisher } # Function that takes an XML file path as input and returns an array of Signer objects function Get-SignerInfo { param( [Parameter(Mandatory = $true)][string]$XmlFilePath ) # Load the XML file and select the Signer nodes $xml = [xml](Get-Content $XmlFilePath) $Signers = $xml.SiPolicy.Signers.Signer # Create an empty array to store the output [System.Object[]]$output = @() # Loop through each Signer node and extract the information foreach ($signer in $signers) { # Create a new Signer object and assign the properties $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 } # Return the output array return $output } # Function to calculate the TBS of a certificate function Get-TBSCertificate { param ($Cert) # Get the raw data of the certificate $RawData = $Cert.RawData # Create an ASN.1 reader to parse the certificate $AsnReader = New-Object System.Formats.Asn1.AsnReader -ArgumentList $RawData, ([System.Formats.Asn1.AsnEncodingRules]::DER) # Read the certificate sequence $Certificate = $AsnReader.ReadSequence() # Read the TBS (To be signed) value of the certificate $TbsCertificate = $Certificate.ReadEncodedValue() # Read the signature algorithm sequence $SignatureAlgorithm = $Certificate.ReadSequence() # Read the algorithm OID of the signature $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() } '1.2.840.10040.4.3' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() } '2.16.840.1.101.3.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() } '2.16.840.1.101.3.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() } '2.16.840.1.101.3.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() } '1.2.840.10045.4.1' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() } '1.2.840.10045.4.3.2' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() } '1.2.840.10045.4.3.3' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() } '1.2.840.10045.4.3.4' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() } '1.2.840.113549.1.1.5' { $HashFunction = [System.Security.Cryptography.SHA1]::Create() } '1.2.840.113549.1.1.11' { $HashFunction = [System.Security.Cryptography.SHA256]::Create() } '1.2.840.113549.1.1.12' { $HashFunction = [System.Security.Cryptography.SHA384]::Create() } '1.2.840.113549.1.1.13' { $HashFunction = [System.Security.Cryptography.SHA512]::Create() } default { throw "No handler for algorithm $AlgorithmOid" } } # Compute the hash of the TBS value using the hash function $Hash = $HashFunction.ComputeHash($TbsCertificate.ToArray()) # Convert the hash to a hex string and return it return [System.BitConverter]::ToString($hash) -replace '-', '' } # Helps get the 2nd aka nested signer/signature of the dual signed files # https://www.sysadmins.lv/blog-en/reading-multiple-signatures-from-signed-file-with-powershell.aspx # https://www.sysadmins.lv/disclaimer.aspx function Get-AuthenticodeSignatureEx { [CmdletBinding()] param( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [String[]]$FilePath # The path of the file(s) to get the signature of ) begin { # Define the signature of the Crypt32.dll library functions to use $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 -ErrorAction SilentlyContinue # Define some constants for the CryptQueryObject function parameters $CERT_QUERY_OBJECT_FILE = 0x1 $CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED = 0x400 $CERT_QUERY_FORMAT_FLAG_BINARY = 0x2 # Define a helper function to get the timestamps of the countersigners function getTimeStamps($SignerInfo) { [System.Object[]]$retValue = @() foreach ($CounterSignerInfos in $Infos.CounterSignerInfos) { # Get the signing time attribute from the countersigner info object $sTime = ($CounterSignerInfos.SignedAttributes | Where-Object { $_.Oid.Value -eq '1.2.840.113549.1.9.5' }).Values | ` Where-Object { $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 Get-AuthenticodeSignature $FilePath | ForEach-Object { $Output = $_ # Store the output object in a variable if ($null -ne $Output.SignerCertificate) { # If the output object has a signer certificate property # Initialize some variables to store the output parameters of the CryptQueryObject function $pdwMsgAndCertEncodingType = 0 $pdwContentType = 0 $pdwFormatType = 0 [IntPtr]$phCertStore = [IntPtr]::Zero [IntPtr]$phMsg = [IntPtr]::Zero [IntPtr]$ppvContext = [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 (!$return) { return } # If the function fails, return nothing $pcbData = 0 # Initialize a variable to store the size of the PKCS #7 message data # 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 (!$return) { return } # If the function fails, return nothing $pvData = New-Object byte[] -ArgumentList $pcbData # Create a byte array to store the PKCS #7 message data # Call the CryptMsgGetParam function again to get the PKCS #7 message data $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg, 29, 0, $pvData, [ref]$pcbData) $SignedCms = New-Object Security.Cryptography.Pkcs.SignedCms # Create a SignedCms object to decode the PKCS #7 message data $SignedCms.Decode($pvData) # Decode the PKCS #7 message data and populate the SignedCms object properties $Infos = $SignedCms.SignerInfos[0] # Get the first signer info object from the SignedCms object # 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 = getTimeStamps $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 { $_.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 { $_.Oid.Value -eq '1.3.6.1.4.1.311.2.4.1' } $SignedCms2 = New-Object Security.Cryptography.Pkcs.SignedCms # Create another SignedCms object to decode the nested signature data $SignedCms2.Decode($value.RawData) # Decode the nested signature data and populate the SignedCms object properties $Output | Add-Member -MemberType NoteProperty -Name NestedSignature -Value $null $Infos = $SignedCms2.SignerInfos[0] # Get the first signer info object from the nested signature SignedCms object # Create a custom object with some properties of the nested signature, such as signer certificate, digest algorithm and timestamps $nested = New-Object psobject -Property @{ SignerCertificate = $Infos.Certificate DigestAlgorithm = $Infos.DigestAlgorithm.FriendlyName TimeStamps = getTimeStamps $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) } else { # If the output object does not have a signer certificate property # Return the output object as it is $Output } } } end {} } # A function to get all the certificates from a signed file or a certificate object and output a Collection function Get-SignedFileCertificates { param ( # Define two sets of parameters, one for the FilePath and one for the CertObject [Parameter()] [string]$FilePath, [Parameter(ValueFromPipeline = $true)] [System.Security.Cryptography.X509Certificates.X509Certificate2]$X509Certificate2 ) # Create an X509Certificate2Collection object $CertCollection = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2Collection # 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) } # Return the collection return $CertCollection } # A function to detect Root, Intermediate and Leaf certificates function Get-CertificateDetails { 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, # 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 [Parameter(ParameterSetName = 'Based on File Path')] [Parameter(ParameterSetName = 'Based on Certificate')] [switch]$IntermediateOnly, [Parameter(ParameterSetName = 'Based on File Path')] [Parameter(ParameterSetName = 'Based on Certificate')] [switch]$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 { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } } else { # The "| Where-Object {$_ -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 { $_.EnhancedKeyUsageList.FriendlyName -ne 'Time Stamping' } | Where-Object { $_ -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 $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 $SubjectCN = $matches['InitialRegexTest2'] -like '*"*' ? ($Element.Certificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest2'] $Element.Certificate.Issuer -match 'CN=(?<InitialRegexTest3>.*?),.*' | Out-Null $IssuerCN = $matches['InitialRegexTest3'] -like '*"*' ? ($Element.Certificate.Issuer -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest3'] # Get the TBS value of the certificate using the Get-TBSCertificate function $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 $CertificateUsingAlternativeMethod = [System.Security.Cryptography.X509Certificates.X509Certificate]::CreateFromSignedFile($FilePath) $CertificateUsingAlternativeMethod.Subject -match 'CN=(?<InitialRegexTest4>.*?),.*' | Out-Null [string]$TestAgainst = $matches['InitialRegexTest4'] -like '*"*' ? ((Get-AuthenticodeSignature -FilePath $FilePath).SignerCertificate.Subject -split 'CN="(.+?)"')[1] : $matches['InitialRegexTest4'] if ($IntermediateOnly) { $FinalObj = $Obj | Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result Where-Object { $_.SubjectCN -ne $TestAgainst } | # To omit the Leaf certificate Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property return $FinalObj } elseif ($LeafCertificate) { $FinalObj = $Obj | Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result Where-Object { $_.SubjectCN -eq $TestAgainst } | # To get the Leaf certificate Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property return $FinalObj } } # If nested certificate is being processed and X509Certificate2 object is passed elseif ($X509Certificate2) { if ($IntermediateOnly) { $FinalObj = $Obj | Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result Where-Object { $_.SubjectCN -ne $LeafCNOfTheNestedCertificate } | # To omit the Leaf certificate Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property return $FinalObj } elseif ($LeafCertificate) { $FinalObj = $Obj | Where-Object { $_.SubjectCN -ne $_.IssuerCN } | # To omit Root certificate from the result Where-Object { $_.SubjectCN -eq $LeafCNOfTheNestedCertificate } | # To get the Leaf certificate Group-Object -Property TBSValue | ForEach-Object { $_.Group[0] } # To make sure the output values are unique based on TBSValue property return $FinalObj } } } <# # Function that shows the details of certificates. E.g, All intermediate certs, Leaf cert or the entire chain, depending on optional switch parameters function Get-CertificateDetails { # Use the param keyword to define the parameters param ( # Make the FilePath parameter mandatory and validate that it is a valid file path [Parameter()] [ValidateScript({ Test-Path $_ -PathType Leaf })] [string]$FilePath, $X509Certificate2, [switch]$IntermediateOnly, [switch]$AllCertificates, [switch]$LeafCertificate ) if ($FilePath) { # Get the certificate from the file path $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $FilePath } # if file path isn't used and instead a X509Certificate2 is provided then assign it directly to the $Cert variable elseif ($X509Certificate2) { $Cert = $X509Certificate2 } # Build the certificate chain $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) # Check the value of the switch parameters if ($IntermediateOnly) { # If IntermediateOnly is present, loop through the chain elements and display only the intermediate certificates for ($i = 1; $i -lt $Chain.ChainElements.Count - 1; $i++) { # Create a custom object with the certificate properties $Element = $Chain.ChainElements[$i] # Extract the data after CN= in the subject and issuer properties $SubjectCN = ($Element.Certificate.Subject -split '(?:^|,)CN=|,')[1] $IssuerCN = ($Element.Certificate.Issuer -split '(?:^|,)CN=|,')[1] # Get the TBS value of the certificate using the Get-TBSCertificate function $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 } # Display the object Write-Output $Obj } } elseif ($AllCertificates) { # 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 $SubjectCN = ($Element.Certificate.Subject -split '(?:^|,)CN=|,')[1] $IssuerCN = ($Element.Certificate.Issuer -split '(?:^|,)CN=|,')[1] # Get the TBS value of the certificate using the Get-TBSCertificate function $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 } # Display the object Write-Output $obj } } elseif ($LeafCertificate) { # If LeafCertificate is present, create a custom object with the leaf certificate properties # Extract the data after CN= in the subject and issuer properties $SubjectCN = ($Chain.ChainElements[0].Certificate.Subject -split '(?:^|,)CN=|,')[1] $IssuerCN = ($Chain.ChainElements[0].Certificate.Issuer -split '(?:^|,)CN=|,')[1] # Get the TBS value of the certificate using the Get-TBSCertificate function $TbsValue = Get-TBSCertificate -cert $Chain.ChainElements[0].Certificate # Create a custom object with the extracted properties and the TBS value $Obj = [pscustomobject]@{ SubjectCN = $SubjectCN IssuerCN = $IssuerCN NotAfter = $Chain.ChainElements[0].Certificate.NotAfter TBSValue = $TbsValue } # Display the object Write-Output 'Leaf Certificate:' Write-Output $obj } else { # If none of the switch parameters are present, display a message to inform the user of their options Write-Output 'Please specify one of the following switch parameters to get certificate details: -IntermediateOnly, -AllCertificates, or -LeafCertificate.' } } #> # 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 function Compare-SignerAndCertificate { param( [Parameter(Mandatory = $true)][string]$XmlFilePath, [Parameter(Mandatory = $true)] [string]$SignedFilePath ) # Get the signer information from the XML file path using the Get-SignerInfo function $SignerInfo = Get-SignerInfo -XmlFilePath $XmlFilePath # An array to store the details of the main certificate of the signed file [System.Object[]]$CertificateDetails = @() # 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 certificate details from the signed file path using the Get-CertificateDetails function with the -IntermediateOnly parameter $CertificateDetails = Get-CertificateDetails -IntermediateOnly -FilePath $SignedFilePath # Get the Nested certificate of the signed file, if any $ExtraCertificateDetails = Get-AuthenticodeSignatureEx -FilePath $SignedFilePath # Extract it from the nested property $NestedCertificate = ($ExtraCertificateDetails).NestedSignature.SignerCertificate if ($null -ne $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 } # Declare $LeafCertificateDetails as an array [System.Object[]]$LeafCertificateDetails = @() # Declare $NestedLeafCertificateDetails as an array [System.Object[]]$NestedLeafCertificateDetails = @() # Get the leaf certificate details of the Main Certificate from the signed file path $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 $CertificateDetails) { # 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 if ($Signer.CertRoot -eq $Certificate.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 { $ComparisonResult.CertRootMatch = $true # meaning one of the TBS values of the file's intermediate certs is in the xml file signers' TBS values } # Check if the signer's name (Referring to the one in the XML file) 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 $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 $ComparisonResults += $ComparisonResult } # Return the comparison results array return $ComparisonResults } # Define 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 function Get-FileRuleOutput ($xmlPath) { # Load the xml file into a variable $xml = [xml](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 $hashvalue = $filerule.Hash # Extract the hash type from the FriendlyName attribute using regex $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 $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 $OutPutHashInfoProcessing = $OutPutHashInfoProcessing | Where-Object { $_.hashtype -eq 'Hash Sha256' } # Return the output array return $OutPutHashInfoProcessing } <# NOT USED ANYMORE # Define a function to compare two xml files and return an array of objects with a custom property for the comparison result function Compare-XmlFiles ($refXmlPath, $tarXmlPath) { # Load the reference xml file and create an output array using the Get-FileRuleOutput function $refoutput = Get-FileRuleOutput -xmlPath $refXmlPath # Load the target xml file and create an output array using the Get-FileRuleOutput function $taroutput = Get-FileRuleOutput -xmlPath $tarXmlPath # make sure they are not empty if ($refoutput -and $taroutput) { # Compare the output arrays using the Compare-Object cmdlet with the -Property parameter # Specify the HashValue property as the property to compare # Use the -PassThru parameter to return the original input objects # Use the -IncludeEqual parameter to include the objects that are equal in both arrays $comparison = Compare-Object -ReferenceObject $refoutput -DifferenceObject $taroutput -Property HashValue -PassThru -IncludeEqual # Create an empty array to store the output objects [System.Object[]]$OutPutHashComparison = @() # Loop through each object in the comparison array foreach ($object in $comparison) { # Create a custom property called Comparison and assign it a value based on the SideIndicator property switch ($object.SideIndicator) { '<=' { $comparison = 'Only in reference' } '=>' { $comparison = 'Only in target' } '==' { $comparison = 'Both' } } # Add the Comparison property to the object using the Add-Member cmdlet $object | Add-Member -MemberType NoteProperty -Name Comparison -Value $comparison # Add the object to the output array $OutPutHashComparison += $object } # Return the output array return $OutPutHashComparison } } #> |