WDACSimulation/Compare-SignerAndCertificate.psm1
Function Compare-SignerAndCertificate { <# .SYNOPSIS The function that compares the signer information from the WDAC policy XML file with the certificate details of the signed file .INPUTS WDACConfig.SimulationInput .OUTPUTS System.Collections.Hashtable .PARAMETER SimulationInput The SimulationInput object that contains the necessary information for the simulation #> [CmdletBinding()] [OutputType([System.Collections.Hashtable])] param( [Parameter(Mandatory = $true)][WDACConfig.SimulationInput]$SimulationInput ) Begin { [System.Boolean]$Verbose = $PSBoundParameters.Verbose.IsPresent ? $true : $false . "$([WDACConfig.GlobalVars]::ModuleRootPath)\CoreExt\PSDefaultParameterValues.ps1" # Get the extended file attributes [WDACConfig.ExFileInfo]$ExtendedFileInfo = [WDACConfig.ExFileInfo]::GetExtendedFileInfo($SimulationInput.FilePath) } Process { # Loop through each signer in the signer information array, these are the signers in the XML policy file foreach ($Signer in $SimulationInput.SignerInfo) { # Make sure it's an allowed signer and not a denier if ($Signer.IsAllowed -ne $true) { continue } # Write-Debug -Message "Checking the signer: $($Signer.Name)" # If the signer has any EKUs, try to match it with the file's EKU OIDs if ($Signer.HasEKU) { Write-Debug -Message 'The signer has EKUs' # Write-Debug -Message "The current file has $($SimulationInput.EKUOIDs.Count) EKUs" # Check if any of the Signer's OIDs match any of the file's certificates' OIDs (which are basically Leaf certificates' EKU OIDs) # This is used for all levels, not just WHQL levels [System.Boolean]$EKUsMatch = $false foreach ($EKU in $Signer.CertEKU) { if ($SimulationInput.EKUOIDs -and $SimulationInput.EKUOIDs.Contains($EKU)) { [System.Boolean]$EKUsMatch = $true break } } # If both the file and signer had EKUs and they match if ($EKUsMatch) { Write-Debug -Message "The EKUs of the signer matched with the file's EKUs" # If the signer and file have matching EKUs and the signer is WHQL then start checking for OemID if ($Signer.IsWHQL) { Write-Debug -Message 'The signer is WHQL' # At this point the file is definitely WHQL-Signed # Get the WHQL chain packages by checking for any chain whose leaf certificate contains the WHQL EKU OID [WDACConfig.ChainPackage[]]$WHQLChainPackagesCandidates = $SimulationInput.AllFileSigners.Where({ $_.LeafCertificate.Certificate.EnhancedKeyUsageList.ObjectId.Contains('1.3.6.1.4.1.311.10.3.5') }) # HashSet to store all of the Opus data from the WHQL chain packages candidates $Current_Chain_Opus = New-Object -TypeName 'System.Collections.Generic.HashSet[System.String]' # List of [WDACConfig.OpusSigner] objects which are pairs of each Intermediate Certificate TBSHash and its corresponding SubjectCN $OpusSigners = New-Object -TypeName 'System.Collections.Generic.List[WDACConfig.OpusSigner]' # Loop through each candidate WHQL chain package foreach ($ChainPackage in $WHQLChainPackagesCandidates) { # Try to get the Opus data of the current chain (essentially the current chain's leaf certificate) try { $CurrentOpusData = ([WDACConfig.Opus]::GetOpusData($ChainPackage.SignedCms)).CertOemID } catch {} # If there was Opus data if ($CurrentOpusData.count -gt 0) { # Add the Opus data to the HashSet [System.Void]$Current_Chain_Opus.Add($CurrentOpusData) } # Capture the details of the WHQL signers, aka Intermediate certificate(s) of the signer package that had WHQL EKU # In case there are more than 1 intermediate certificates in the chain, add all of them to the HashSets # regardless of whether they have Opus data or not because we'll use these data for the WHQL level too and that level doesn't require Opus data match foreach ($IntermediateCert in $ChainPackage.IntermediateCertificates) { # Add the current TBSHash and SubjectCN pair of the intermediate certificate to the list $OpusSigners.Add( [WDACConfig.OpusSigner]::New( $IntermediateCert.TBSValue, $IntermediateCert.SubjectCN ) ) } } # Flag indicating if the Opus data of the current signer matched with one of the file's leaf certificates Opus data # Making it eligible for WHQLFilePublisher and WHQLPublisher levels # if true, CertOemID of the signer matches the EKU Opus data of the file (This should belong to the leaf certificate of the file as it's the one with EKUs) [System.Boolean]$OpusMatch = $Current_Chain_Opus.Contains($Signer.CertOemID) # Loop through each OpusSigner # This is to ensure when a file is signed by more than 1 WHQL signer then it will be properly validated as these are pairs of TBSHash and SubjectCN of each WHQL signer's details foreach ($OpusSigner in $OpusSigners) { # Check if the selected file's signer chain's intermediate certificates match the current signer's details if (($Signer.CertRoot -eq $OpusSigner.TBSHash) -and ($Signer.Name -eq $OpusSigner.SubjectCN)) { # At this point the file meets the criteria for one of the WHQL levels # Indicating it's WHQLFilePublisher signer if ($OpusMatch -and $Signer.FileAttrib) { [System.Collections.Hashtable[]]$CandidateFileAttrib = foreach ($Attrib in $signer.FileAttrib.GetEnumerator()) { if ($ExtendedFileInfo.Version -ge $Attrib.Value.MinimumFileVersion) { $Attrib.Value } } # Loop over all of the candidate file attributes (if they exists) to find a match with the file's extended info if ($null -ne $CandidateFileAttrib) { foreach ($FileAttrib in $CandidateFileAttrib.GetEnumerator()) { foreach ($KeyItem in ('OriginalFileName', 'InternalName', 'ProductName', 'Version', 'FileDescription')) { if (($null -ne $ExtendedFileInfo.$KeyItem) -and ($ExtendedFileInfo.$KeyItem -eq $FileAttrib.$KeyItem)) { [WDACConfig.VerboseLogger]::Write("The SpecificFileNameLevel is $KeyItem") # If there was a match then assign the $KeyItem which is the name of the SpecificFileNameLevel option to the $CurrentFileInfo.SpecificFileNameLevelMatchCriteria <# ELIGIBILITY CHECK FOR LEVELS: WHQLFilePublisher CRITERIA: 1) The signer's CertRoot (referring to the TBS value in the xml file which belongs to the intermediate cert of the file signed by Microsoft) Matches the TBSValue of the file's certificate that belongs to Microsoft WHQL program 2) The signer's name (Referring to the one in the XML file) matches the same Intermediate certificate's SubjectCN, the certificate that belongs to Microsoft WHQL program 3) The signer's CertEKU points to the WHQL EKU OID and one of the file's leaf certificates contains this EKU OID 4) The signer's CertOemID matches one of the Opus data of the file's certificates (Leaf certificates as they are the ones with EKUs) 5) The signer's FileAttribRef(s) point to the same file that is currently being investigated #> return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'WHQLFilePublisher', $KeyItem, $OpusSigner.SubjectCN, $null, # Intentionally not collecting this info when forming the OpusSigners but the info is there if needed $null, # Intentionally not collecting this info when forming the OpusSigners but the info is there if needed $OpusSigner.TBSHash, $SimulationInput.FilePath )) } } } } } <# ELIGIBILITY CHECK FOR LEVELS: WHQLPublisher CRITERIA: 1) The signer's CertRoot (referring to the TBS value in the xml file which belongs to the intermediate cert of the file signed by Microsoft) Matches the TBSValue of the file's certificate that belongs to Microsoft WHQL program 2) The signer's name (Referring to the one in the XML file) matches the same Intermediate certificate's SubjectCN, the certificate that belongs to Microsoft WHQL program 3) The signer's CertEKU points to the WHQL EKU OID and one of the file's leaf certificates contains this EKU OID 4) The signer's CertOemID matches one of the Opus data of the file's certificates (Leaf certificates as they are the ones with EKUs) #> elseif ($OpusMatch) { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'WHQLPublisher', $null, $OpusSigner.SubjectCN, $null, $null, $OpusSigner.TBSHash, $SimulationInput.FilePath )) } <# ELIGIBILITY CHECK FOR LEVELS: WHQL CRITERIA: 1) The signer's CertRoot (referring to the TBS value in the xml file which belongs to the intermediate cert of the file signed by Microsoft) Matches the TBSValue of the file's certificate that belongs to Microsoft WHQL program 2) The signer's name (Referring to the one in the XML file) matches the same Intermediate certificate's SubjectCN, the certificate that belongs to Microsoft WHQL program 3) The signer's CertEKU points to the WHQL EKU OID and one of the file's leaf certificates contains this EKU OID #> else { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'WHQL', $null, $OpusSigner.SubjectCN, $null, $null, $OpusSigner.TBSHash, $SimulationInput.FilePath )) } } } if ($Signer.IsWHQL -and $EKUsMatch) { # If the Signer has EKU, it was WHQL EKU but there was no WHQL level match made with the file's properties then skip the current signer # as the rest of the levels are not applicable for a WHQL type of signer Continue } } # else { # If the signer isn't WHQL, just a regular signer with EKU and they matched with the file's EKUs # Then do nothing and let the normal rules below handle them # } } else { Write-Debug -Message "The signer had EKUs but they didn't match with the file's EKUs" # If the signer has EKU but it didn't match with the file's EKU then skip the current signer # as it shouldn't be used for any other levels Continue } } # Loop through each certificate chain foreach ($Chain in $SimulationInput.AllFileSigners) { # Loop over each intermediate certificate in the chain foreach ($IntermediateCert in $Chain.IntermediateCertificates) { <# ELIGIBILITY CHECK FOR LEVELS: FilePublisher, Publisher, SignedVersion CRITERIA: 1) 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 one of the file's intermediate certificates 2) The signer's name (Referring to the one in the XML file) matches the same Intermediate certificate's SubjectCN 3) The signer's CertPublisher (aka Leaf Certificate's CN used in the xml policy) matches the current chain's leaf certificate's SubjectCN #> if (($Signer.CertRoot -eq $IntermediateCert.TBSValue) -and ($Signer.Name -eq $IntermediateCert.SubjectCN) -and ($Signer.CertPublisher -eq $Chain.LeafCertificate.SubjectCN)) { # Check if the matched signer has FileAttrib indicating that it was generated either with FilePublisher or SignedVersion level if ($Signer.FileAttrib) { [System.Collections.Hashtable[]]$CandidateFileAttrib = foreach ($Attrib in $signer.FileAttrib.GetEnumerator()) { if ($ExtendedFileInfo.Version -ge $Attrib.Value.MinimumFileVersion) { $Attrib.Value } } # If the signer has a file attribute with a wildcard file name, then it's a SignedVersion level signer # These signers have only 1 FileAttribRef and only point to a single FileAttrib # If a SignedVersion signer applies to multiple files, the version number of the FileAttrib is set to the minimum version of the files if (($CandidateFileAttrib.count -eq 1) -and ($CandidateFileAttrib.OriginalFileName -eq '*')) { return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'SignedVersion', 'Version', $IntermediateCert.SubjectCN, $IntermediateCert.IssuerCN, $IntermediateCert.NotAfter, $IntermediateCert.TBSValue, $SimulationInput.FilePath )) } # Loop over all of the candidate file attributes (if they exists) to find a match with the file's extended info if ($null -ne $CandidateFileAttrib) { foreach ($FileAttrib in $CandidateFileAttrib.GetEnumerator()) { # Loop over all of the keys in the extended file info to see which one of them is a match, to determine the SpecificFileNameLevel option foreach ($KeyItem in ('OriginalFileName', 'InternalName', 'ProductName', 'Version', 'FileDescription')) { if (($null -ne $ExtendedFileInfo.$KeyItem) -and ($ExtendedFileInfo.$KeyItem -eq $FileAttrib.$KeyItem)) { [WDACConfig.VerboseLogger]::Write("The SpecificFileNameLevel is $KeyItem") # If there was a match then assign the $KeyItem which is the name of the SpecificFileNameLevel option to the $CurrentFileInfo.SpecificFileNameLevelMatchCriteria # And break out of the loop by validating the signer as suitable for FilePublisher level return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'FilePublisher', $KeyItem, $IntermediateCert.SubjectCN, $IntermediateCert.IssuerCN, $IntermediateCert.NotAfter, $IntermediateCert.TBSValue, $SimulationInput.FilePath )) } } } } } # If the Signer matched and it doesn't have a FileAttrib, then it's a Publisher level signer else { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'Publisher', $null, $IntermediateCert.SubjectCN, $IntermediateCert.IssuerCN, $IntermediateCert.NotAfter, $IntermediateCert.TBSValue, $SimulationInput.FilePath )) } } <# ELIGIBILITY CHECK FOR LEVELS: PcaCertificate, RootCertificate CRITERIA: 1) 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 one of the file's intermediate certificates 2) The signer's name (Referring to the one in the XML file) matches the same Intermediate certificate's SubjectCN #> elseif (($Signer.CertRoot -eq $IntermediateCert.TBSValue) -and ($Signer.Name -eq $IntermediateCert.SubjectCN)) { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'PcaCertificate/RootCertificate', $null, $IntermediateCert.SubjectCN, $IntermediateCert.IssuerCN, $IntermediateCert.NotAfter, $IntermediateCert.TBSValue, $SimulationInput.FilePath )) } } <# ELIGIBILITY CHECK FOR LEVELS: LeafCertificate CRITERIA: 1) The Signer's CertRoot (referring to the TBS value in the xml file, which belongs to the leaf certificate of the file when LeafCertificate level is used) matches the TBSValue of the file's Leaf certificate certificates 2) The signer's name (Referring to the one in the XML file) matches the Leaf certificate's SubjectCN #> if (($Signer.CertRoot -eq $Chain.LeafCertificate.TBSValue) -and ($Signer.Name -eq $Chain.LeafCertificate.SubjectCN)) { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'LeafCertificate', $null, $Chain.LeafCertificate.SubjectCN, $Chain.LeafCertificate.IssuerCN, $Chain.LeafCertificate.NotAfter, $Chain.LeafCertificate.TBSValue, $SimulationInput.FilePath )) } #Region ROOT CERTIFICATE ELIGIBILITY CHECK # This is regardless of how many certificates exist in the current chain <# ELIGIBILITY CHECK FOR LEVELS: FilePublisher, Publisher, SignedVersion CRITERIA: 1) The signer's CertRoot (referring to the TBS value in the xml file which belongs to the Root Certificate of the file when there is only 1 Element in the chain) Matches the TBSValue of the file's root certificate 2) The signer's name (Referring to the one in the XML file) matches the same Root certificate's SubjectCN 3) The signer's CertPublisher matches the Root certificate's SubjectCN #> if (($Signer.CertRoot -eq $Chain.RootCertificate.TBSValue) -and ($Signer.Name -eq $Chain.RootCertificate.SubjectCN) -and ($Signer.CertPublisher -eq $Chain.RootCertificate.SubjectCN)) { # Check if the matched signer has FileAttrib indicating that it was generated either with FilePublisher or SignedVersion level if ($Signer.FileAttrib) { # Get all of the File Attributes associated with the signer and check if the file's version is greater than or equal to the minimum version in them [System.Collections.Hashtable[]]$CandidateFileAttrib = foreach ($Attrib in $signer.FileAttrib.GetEnumerator()) { if ($ExtendedFileInfo.Version -ge $Attrib.Value.MinimumFileVersion) { $Attrib.Value } } # If the signer has a file attribute with a wildcard file name, then it's a SignedVersion level signer # These signers have only 1 FileAttribRef and only point to a single FileAttrib # If a SignedVersion signer applies to multiple files, the version number of the FileAttrib is set to the minimum version of the files if (($CandidateFileAttrib.count -eq 1) -and ($CandidateFileAttrib.OriginalFileName -eq '*')) { return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'SignedVersion', 'Version', $Chain.RootCertificate.SubjectCN, $Chain.RootCertificate.IssuerCN, $Chain.RootCertificate.NotAfter, $Chain.RootCertificate.TBSValue, $SimulationInput.FilePath )) } # Loop over all of the candidate file attributes (if they exists) to find a match with the file's extended info if ($null -ne $CandidateFileAttrib) { foreach ($FileAttrib in $CandidateFileAttrib.GetEnumerator()) { foreach ($KeyItem in ('OriginalFileName', 'InternalName', 'ProductName', 'Version', 'FileDescription')) { if (($null -ne $ExtendedFileInfo.$KeyItem) -and ($ExtendedFileInfo.$KeyItem -eq $FileAttrib.$KeyItem)) { [WDACConfig.VerboseLogger]::Write("The SpecificFileNameLevel is $KeyItem") # If there was a match then assign the $KeyItem which is the name of the SpecificFileNameLevel option to the $CurrentFileInfo.SpecificFileNameLevelMatchCriteria # And break out of the loop by validating the signer as suitable for FilePublisher level return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'FilePublisher', $KeyItem, $Chain.RootCertificate.SubjectCN, $Chain.RootCertificate.IssuerCN, $Chain.RootCertificate.NotAfter, $Chain.RootCertificate.TBSValue, $SimulationInput.FilePath )) } } } } } # If the Signer matched and it doesn't have a FileAttrib, then it's a Publisher level signer else { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'Publisher', $null, $Chain.RootCertificate.SubjectCN, $Chain.RootCertificate.IssuerCN, $Chain.RootCertificate.NotAfter, $Chain.RootCertificate.TBSValue, $SimulationInput.FilePath )) } } <# ELIGIBILITY CHECK FOR LEVELS: PcaCertificate, RootCertificate (LeafCertificate will also generate the same type of signer) CRITERIA: 1) The signer's CertRoot (referring to the TBS value in the xml file which belongs to the Root Certificate of the file when there is only 1 Element in the chain) Matches the TBSValue of the file's root certificate 2) The signer's name (Referring to the one in the XML file) matches the same Root certificate's SubjectCN #> elseif (($Signer.CertRoot -eq $Chain.RootCertificate.TBSValue) -and ($Signer.Name -eq $Chain.RootCertificate.SubjectCN)) { # If the signer has FileAttributes meaning it's either WHQLFilePublisher, FilePublisher or SignedVersion then do not use it for other levels if ($Signer.FileAttribRef) { Continue } return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $true, $Signer.ID, $Signer.Name, $Signer.CertRoot, $Signer.CertPublisher, $Signer.SignerScope, $Signer.FileAttribRef, 'PcaCertificate/RootCertificate', $null, $Chain.RootCertificate.SubjectCN, $Chain.RootCertificate.IssuerCN, $Chain.RootCertificate.NotAfter, $Chain.RootCertificate.TBSValue, $SimulationInput.FilePath )) } #Endregion ROOT CERTIFICATE ELIGIBILITY CHECK } } # The file is signed but the signer wasn't found in the policy file that allows it return ([WDACConfig.SimulationOutput]::New( ([System.IO.Path]::GetFileName($SimulationInput.FilePath)), 'Signer', $false, $null, $null, $null, $null, $null, $null, 'Not Allowed', $null, $null, $null, $null, $null, $SimulationInput.FilePath )) } } Export-ModuleMember -Function 'Compare-SignerAndCertificate' |