Private/Threat.Signature.ps1
|
# Authenticode signature verification (Windows-native). Get-OffsetSignature wraps the # platform's real trust validation (Get-AuthenticodeSignature / WinVerifyTrust) and shapes an # OffsetInspect.SignatureInfo. The shaping (ConvertTo-OISignatureInfo) is a pure function # unit-tested against fixture objects, so it runs cross-platform in CI without a live signature; # only the live provider call in the public cmdlet is Windows-only. This answers a provenance # question static parsing cannot: not just "is there a cert" but "is the signature valid and # trusted on this machine, and who signed it" - a complement to imphash and the Rich-header # RichHash (imports vs build toolchain vs signer). function Get-OIMemberValue { # StrictMode-safe property read: returns the member's value, or $null when the object lacks # it. Works on real .NET objects (Signature, X509Certificate2) and pscustomobject fixtures, # and tolerates PowerShell versions where SignatureType/IsOSBinary may be absent. [CmdletBinding()] param( [AllowNull()]$InputObject, [Parameter(Mandatory = $true)][string]$Name ) if ($null -eq $InputObject) { return $null } $member = $InputObject.PSObject.Properties[$Name] if ($member) { return $member.Value } return $null } function Get-OICommonName { # Pull the CN (common name) out of an X.500 subject/issuer for a readable label, falling # back to the full distinguished name. [CmdletBinding()] param([AllowNull()][string]$DistinguishedName) if ([string]::IsNullOrWhiteSpace($DistinguishedName)) { return $null } foreach ($part in $DistinguishedName -split ',') { $trimmed = $part.Trim() if ($trimmed -match '^CN=(.+)$') { return $Matches[1].Trim() } } return $DistinguishedName.Trim() } function ConvertTo-OISignatureInfo { <# Pure: shape a Get-AuthenticodeSignature result (or any compatible object) into an OffsetInspect.SignatureInfo. Every optional member is read defensively so it is safe under Set-StrictMode and across PowerShell versions. Unit-tested with fixtures - no live signature required. #> [CmdletBinding()] param( [Parameter(Mandatory = $true)] $Signature, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string]$File, [int64]$FileSize = -1 ) $status = [string](Get-OIMemberValue -InputObject $Signature -Name 'Status') if ([string]::IsNullOrEmpty($status)) { $status = 'UnknownError' } $signerCert = Get-OIMemberValue -InputObject $Signature -Name 'SignerCertificate' $tsaCert = Get-OIMemberValue -InputObject $Signature -Name 'TimeStamperCertificate' # A signer certificate is present for both embedded and catalog signatures (even when the # status is not Valid, e.g. NotTrusted/HashMismatch), and absent when there is no signature # (NotSigned) or the format can't be checked - so it is the reliable "is it signed" signal. $isSigned = ($null -ne $signerCert) $signerSubject = Get-OIMemberValue -InputObject $signerCert -Name 'Subject' $signerNotAfter = Get-OIMemberValue -InputObject $signerCert -Name 'NotAfter' $signerExpired = $null if ($signerNotAfter -is [datetime]) { $signerExpired = ((Get-Date) -gt $signerNotAfter) } $signatureType = Get-OIMemberValue -InputObject $Signature -Name 'SignatureType' $signatureTypeString = if ($null -ne $signatureType) { [string]$signatureType } else { $null } $isOsBinary = Get-OIMemberValue -InputObject $Signature -Name 'IsOSBinary' $result = [pscustomobject]@{ File = $File FileSize = $FileSize IsSigned = $isSigned Status = $status StatusMessage = Get-OIMemberValue -InputObject $Signature -Name 'StatusMessage' SignatureType = $signatureTypeString IsCatalogSigned = ($signatureTypeString -eq 'Catalog') IsOSBinary = if ($null -ne $isOsBinary) { [bool]$isOsBinary } else { $null } SignerName = Get-OICommonName -DistinguishedName $signerSubject SignerSubject = $signerSubject SignerIssuer = Get-OIMemberValue -InputObject $signerCert -Name 'Issuer' SignerThumbprint = Get-OIMemberValue -InputObject $signerCert -Name 'Thumbprint' SignerSerial = Get-OIMemberValue -InputObject $signerCert -Name 'SerialNumber' SignerNotBefore = Get-OIMemberValue -InputObject $signerCert -Name 'NotBefore' SignerNotAfter = $signerNotAfter SignerExpired = $signerExpired IsTimestamped = [bool]$tsaCert TimestamperName = Get-OICommonName -DistinguishedName (Get-OIMemberValue -InputObject $tsaCert -Name 'Subject') Warnings = @() } $result.PSObject.TypeNames.Insert(0, 'OffsetInspect.SignatureInfo') return $result } |