Search code examples
powershellpinvokex509certificatepkcs#7signer

Getting correct SignerInfos of a file via Powershell?


I am working on a Powershell-Sample to read all certificates and SignerInfos from a file.

Below the code I have so far. It gives me all certs but only the first signer and this is in my case "the wrong one" (the counter-signer). How can I get that solved?

For the given file Windows Explorer shows 2 signers with a CounterSignature each. The info that I need from there is the name of the signer "Carbon Black Inc." but the below script only shows me "Microsoft Windows Third Party Component CA 2012" as the only signer.

What am I missing here?

# reference links:
# embedded C# code template: https://www.sysadmins.lv/blog-en/reading-multiple-signatures-from-signed-file-with-powershell.aspx
# MS sample: https://learn.microsoft.com/en-gb/previous-versions/troubleshoot/windows/win32/get-information-authenticode-signed-executables
# wincrypt header definitions: https://github.com/dwimperl/perl-5.14.2.1-32bit-windows/blob/master/c/i686-w64-mingw32/include/wincrypt.h

cls
Remove-Variable * -ea 0
$errorActionPreference = 'stop'

$FilePath = "C:\WINDOWS\system32\DRIVERS\CbDisk.sys"

Add-Type -TypeDefinition @"
using System;
using System.Security.Cryptography.Pkcs;
using System.Runtime.InteropServices;

public static class Crypt32 {
    [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);

    public static SignedCms GetSignedCmsFromFile(string filePath) {
        int pdwMsgAndCertEncodingType = 0;
        int pdwContentType = 0;
        int pdwFormatType = 0;
        IntPtr phCertStore = IntPtr.Zero;
        IntPtr phMsg = IntPtr.Zero;
        IntPtr ppvContext = IntPtr.Zero;
    
        if (!CryptQueryObject(
            1, filePath, 0x400, 2, 0,
            ref pdwMsgAndCertEncodingType,
            ref pdwContentType,
            ref pdwFormatType,
            ref phCertStore,
            ref phMsg,
            ref ppvContext
        )) {return new SignedCms();}
    
        int pcbData = 0;
        CryptMsgGetParam(phMsg, 29, 0, null, ref pcbData);
        byte[] pvData = new byte[pcbData];
        CryptMsgGetParam(phMsg, 29, 0, pvData, ref pcbData);
        CryptMsgClose(phMsg);

        SignedCms signedCms = new SignedCms();
        signedCms.Decode(pvData);
        return signedCms;
    }
}
"@ -ReferencedAssemblies System.Security

# this gets all certs:
$cms = [Crypt32]::GetSignedCmsFromFile($FilePath)
$certs  = $cms.Certificates
$certs | ft -AutoSize

# this gives me only 1 signer, but not all of them.
# also the CounterSignerInfos are always empty here:
$signers = $cms.SignerInfos

I also tested some embedded C# code to read the number of signers first via CryptMsgGetParam() with parameter CMSG_SIGNER_COUNT_PARAM (=5), but this also tells me, that there is only 1 signer.

As a reference for that file in question here is is the VirusTotal-link showing the File-Details with the certificates and signer information: CbDisk.sys

and here the screenshot from the file explorer: enter image description here


Solution

  • Second signature is co-signature and attached to original signer info as nested SPC signature. It is stored in unauthenticated attributes of top-level signer info with attribute ID = 1.3.6.1.4.1.311.2.4.1: enter image description here

    So, you need to extract nested PKCS#7 object from outer PKCS#7 as follows:

    # follow up to your code, simply append the code below
    $pvData2 = ($cms.SignerInfos[0].UnsignedAttributes | ?{$_.Oid.Value -eq "1.3.6.1.4.1.311.2.4.1"})[0].Values[0].RawData
    $coCms = New-Object Security.Cryptography.Pkcs.SignedCms
    $coCms.Decode($pvData2)
    

    $coCms will contain PKCS#7 of co-signer (Carbon Black in your case). It is a very rough example how you should approach co-signed files. For example, third signature may be added in future. It may be added as another unauthenticated attribute to outer signer info, or as new unauthenticated attribute of nested signature (basically, you need to recursively traverse all unauthenticated attributes and look for attribute with OID=1.3.6.1.4.1.311.2.4.1 and extract PKCS#7 blobs).