One friend of mine asked about how to get signature creation time in PowerShell. When we sign a file, optionally (but recommended) we can timestamp the signature, thus approving that the file was signed at the certain moment and wasn't re-signed later. Some useful details about signatures and timestamps you can read in my previous article: Digital signatures.

Here is what we see in the UI:

Digital Signature Details

We see the following fields:

  • Name — name of the signer. This name is retrieved from the first CN attribute of the certificate subject.
  • E-mail — signer e-mail. E-mail address is retrieved from the E attribute of the certificate subject.
  • Signing time — this is the timestamp. The timestamp is digitally signed by the timestamp signing certificate (see the Countersignatures field).

Windows PowerShell has a built-in cmdlet to view and verify the signature: Get-AuthenticodeSignature. Let's see how it works:

[↓] [vPodans] Get-AuthenticodeSignature "c:\Program Files (x86)\PowerGUI\ScriptEditor.exe" | fl *


SignerCertificate      : [Subject]
                           E=support@quest.com, CN=Quest Software, O=Quest Software, C=US

                         [Issuer]
                           CN=GlobalSign ObjectSign CA, OU=ObjectSign CA, O=GlobalSign nv-sa, C=BE

                         [Serial Number]
                           0100000000011E46409D36

                         [Not Before]
                           17.12.2008 19:48:02

                         [Not After]
                           17.12.2011 19:48:02

                         [Thumbprint]
                           5C90629F628A3A5D64A4E300E28F3DEF19711EAC

TimeStamperCertificate : [Subject]
                           CN=VeriSign Time Stamping Services Signer - G2, O="VeriSign, Inc.", C=US

                         [Issuer]
                           CN=VeriSign Time Stamping Services CA, O="VeriSign, Inc.", C=US

                         [Serial Number]
                           3825D7FAF861AF9EF490E726B5D65AD5

                         [Not Before]
                           15.06.2007 3:00:00

                         [Not After]
                           15.06.2012 2:59:59

                         [Thumbprint]
                           ADA8AAA643FF7DC38DD40FA4C97AD559FF4846DE

Status                 : Valid
StatusMessage          : Signature verified.
Path                   : C:\Program Files (x86)\PowerGUI\ScriptEditor.exe



[↓] [vPodans]

The command displays signer certificate, status and timestamping certificates (countersignature certificate). However, it doesn't display the timestamp value. Unfortunately, there are no .NET classes, properties or methods to display it. When .NET lacks, CryptoAPI rulles! I wrote a simple wrapper for Get-AuthenticodeSignature cmdlet named Get-AuthenticodeSignatureEx:

function Get-AuthenticodeSignatureEx {
<#
.ForwardHelpTargetName Get-AuthenticodeSignature
#>
[CmdletBinding()]
    param(
        [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
        [String[]]$FilePath
    )
    begin {
$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
);
"@
        Add-Type -AssemblyName System.Security
        Add-Type -MemberDefinition $signature -Namespace PKI -Name Crypt32
    }
    process {
        Get-AuthenticodeSignature $PSBoundParameters | ForEach-Object {
            $Output = $_
            if ($Output.SignerCertificate -ne $null) {
                $pdwMsgAndCertEncodingType =  0
                $pdwContentType =  0
                $pdwFormatType =  0
                [IntPtr]$phCertStore = [IntPtr]::Zero
                [IntPtr]$phMsg = [IntPtr]::Zero
                [IntPtr]$ppvContext = [IntPtr]::Zero
                $return = [PKI.Crypt32]::CryptQueryObject(
                    1,
                    $Output.Path,
                    16382,
                    14,
                    $null,
                    [ref]$pdwMsgAndCertEncodingType,
                    [ref]$pdwContentType,
                    [ref]$pdwFormatType,
                    [ref]$phCertStore,
                    [ref]$phMsg,
                    [ref]$ppvContext
                )
                $pcbData = 0
                $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg,29,0,$null,[ref]$pcbData)
                $pvData = New-Object byte[] -ArgumentList $pcbData
                $return = [PKI.Crypt32]::CryptMsgGetParam($phMsg,29,0,$pvData,[ref]$pcbData)
                $SignedCms = New-Object Security.Cryptography.Pkcs.SignedCms
                $SignedCms.Decode($pvData)
                foreach ($Infos in $SignedCms.SignerInfos) {
                    foreach ($CounterSignerInfos in $Infos.CounterSignerInfos) {
                        $sTime = ($CounterSignerInfos.SignedAttributes | ?{$_.Oid.Value -eq "1.2.840.113549.1.9.5"}).Values | `
                        Where-Object {$_.SigningTime -ne $null}
                    }
                }
                $Output | Add-Member -MemberType NoteProperty -Name SigningTime -Value $sTime.SigningTime.ToLocalTime() -PassThru -Force
                [void][PKI.Crypt32]::CryptMsgClose($phMsg)
                [void][PKI.Crypt32]::CertCloseStore($phCertStore,0)
            } else {
                $Output
            }
        }
    }
    end {}
}

The command usage is completely the same as for native Get-AuthenticodeSignature (if you run 'get-help Get-AuthenticodeSignatureEx' command, you will be redirected to original command help content). And the same example:

[↓] [vPodans] Get-AuthenticodeSignatureEx "c:\Program Files (x86)\PowerGUI\ScriptEditor.exe" | fl *


SigningTime            : 04.10.2011 17:06:35
SignerCertificate      : [Subject]
                           E=support@quest.com, CN=Quest Software, O=Quest Software, C=US

                         [Issuer]
                           CN=GlobalSign ObjectSign CA, OU=ObjectSign CA, O=GlobalSign nv-sa, C=BE

                         [Serial Number]
                           0100000000011E46409D36

                         [Not Before]
                           17.12.2008 19:48:02

                         [Not After]
                           17.12.2011 19:48:02

                         [Thumbprint]
                           5C90629F628A3A5D64A4E300E28F3DEF19711EAC

TimeStamperCertificate : [Subject]
                           CN=VeriSign Time Stamping Services Signer - G2, O="VeriSign, Inc.", C=US

                         [Issuer]
                           CN=VeriSign Time Stamping Services CA, O="VeriSign, Inc.", C=US

                         [Serial Number]
                           3825D7FAF861AF9EF490E726B5D65AD5

                         [Not Before]
                           15.06.2007 3:00:00

                         [Not After]
                           15.06.2012 2:59:59

                         [Thumbprint]
                           ADA8AAA643FF7DC38DD40FA4C97AD559FF4846DE

Status                 : Valid
StatusMessage          : Signature verified.
Path                   : C:\Program Files (x86)\PowerGUI\ScriptEditor.exe



[↓] [vPodans]

Look at the first property. Now you see a new property named SigningTime which contains desired value. And the rest properties still are there. At this time I can't tell, why the time in UI is GMT+2 (as my time zone), but .NET displays the time for GMT+3 (after adjusting the time to local time zone).

I've decided to add this code to my PowerShell PKI Module as an extended type system (ETS). This property will appear every time the System.Management.Automation.Signature object is created (without additional cmdlet).

HTH.


Share this article:

Comments:

Midhul Mukund
Midhul Mukund 07.06.2017 00:05 (GMT+3) Retrieve timestamp attribute from digital signature

The output format of Get-AuthenticodeSignature with format list is not correct in the later operating system(windows 10). I am not getting a timestamp information when using the same command in windows 10 and windows7&8 works without any issues. Am i missing something?

Mohit
Mohit 19.07.2019 04:20 (GMT+3) Retrieve timestamp attribute from digital signature

I am getting the same issue on windows 10. I use this function and was working perfect on window 7 and since migration to windows 10 does not work anymore. Any help is appreciated.


Post your comment:

Please, solve this little equation and enter result below. Captcha