Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dual sign a binary with Authenticode?

Short question

How can I get information about multiple code signing certificates from an executable (.EXE/.DLL)?

Expected answer

The final accepted answer should propose a way to get all certificates in C#. Concept / pseudo code is ok, I don't expect you to write the full source.

For an intermediate answer suggesting a tool, please see my question on Security.StackExchange.

Long question

I am researching whether we could use multiple code signing certificates on a plugin (.DLL) to check whether it has been officially tested or not. This is the procedure:

  • the plugin DLL is signed by the vendor just like any other application
  • the plugin DLL comes into a test lab and undergoes a set of tests
  • the plugin DLL gets signed again by the test lab so that the application using the DLL can find out whether it is using a tested plugin or not

It seems possible to sign a DLL a second time using

 signtool /v /f <pfx> /as <dll>

Indications that this may have worked:

  • the file increases in size
  • the tool prints a success message

However, there are some issues showing the second signature:

  • although Windows Explorer says "Signature list", it shows only one certificate
  • the C# X509Certificate.CreateFromSignedFile() method can only return one certificate

At the moment I'm actually trying my code on an EXE file rather than a DLL file, but that shouldn't matter. The EXE is already signed with a trusted root certificate and a timestamp. The second signature is created with my own certificate following these steps currently without a timestamp.

Things I did before asking the question:

  • search on Stackoverflow for existing answers
  • search for tools on Google

The only related question I found so far is How does one correctly dual-sign with a timestamp but it doesn't have an answer.

like image 209
Thomas Weller Avatar asked Feb 04 '14 08:02

Thomas Weller


2 Answers

I have recently implemented code to do this myself. I can't post the full solution as it is embedded in a larger static analysis tool, but the code for a working bare-bones C# console application that enumerates the Authenticode signatures in a specified file path is provided below using the WinVerifyTrust() Windows API function with assistance from this Knowledge Base article.

Things to note:

  1. Enumerating more than one certificate is only supported on Windows 8 and Windows Server 2012 (or later). Earlier versions of Windows will only ever report there being zero or one certificates.
  2. The code as provided here only handles validly signed files and files with no Authenticode signature. It does not properly handle files with invalid signatures. This is left as an excercise for the reader.

Here's the code:

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;

namespace ReadAuthenticodeSignatures
{
    internal static class Program
    {
        internal static void Main(string[] args)
        {
            string fileName = args[0];

            IntPtr hWind = IntPtr.Zero;

            Guid WINTRUST_ACTION_GENERIC_VERIFY_V2 = new Guid("{00AAC56B-CD44-11d0-8CC2-00C04FC295EE}");
            byte[] actionIdBytes = WINTRUST_ACTION_GENERIC_VERIFY_V2.ToByteArray();

            IntPtr pcwszFilePath = Marshal.StringToHGlobalAuto(fileName);

            try
            {
                WINTRUST_FILE_INFO File = new WINTRUST_FILE_INFO()
                {
                    cbStruct = Marshal.SizeOf(typeof(WINTRUST_FILE_INFO)),
                    pcwszFilePath = pcwszFilePath,
                    hFile = IntPtr.Zero,
                    pgKnownSubject = IntPtr.Zero,
                };

                IntPtr ptrFile = Marshal.AllocHGlobal(File.cbStruct);

                try
                {
                    Marshal.StructureToPtr(File, ptrFile, false);

                    WINTRUST_DATA WVTData = new WINTRUST_DATA()
                    {
                        cbStruct = Marshal.SizeOf(typeof(WINTRUST_DATA)),
                        pPolicyCallbackData = IntPtr.Zero,
                        pSIPClientData = IntPtr.Zero,
                        dwUIChoice = WTD_UI_NONE,
                        fdwRevocationChecks = WTD_REVOKE_NONE,
                        dwUnionChoice = WTD_CHOICE_FILE,
                        pFile = ptrFile,
                        dwStateAction = WTD_STATEACTION_IGNORE,
                        hWVTStateData = IntPtr.Zero,
                        pwszURLReference = IntPtr.Zero,
                        dwProvFlags = WTD_REVOCATION_CHECK_NONE,
                        dwUIContext = WTD_UICONTEXT_EXECUTE,
                        pSignatureSettings = IntPtr.Zero,
                    };

                    // N.B. Use of this member is only supported on Windows 8 and Windows Server 2012 (and later)
                    WINTRUST_SIGNATURE_SETTINGS signatureSettings = default(WINTRUST_SIGNATURE_SETTINGS);

                    bool canUseSignatureSettings = Environment.OSVersion.Version > new Version(6, 2, 0, 0);
                    IntPtr pSignatureSettings = IntPtr.Zero;

                    if (canUseSignatureSettings)
                    {
                        // Setup WINTRUST_SIGNATURE_SETTINGS to get the number of signatures in the file
                        signatureSettings = new WINTRUST_SIGNATURE_SETTINGS()
                        {
                            cbStruct = Marshal.SizeOf(typeof(WINTRUST_SIGNATURE_SETTINGS)),
                            dwIndex = 0,
                            dwFlags = WSS_GET_SECONDARY_SIG_COUNT,
                            cSecondarySigs = 0,
                            dwVerifiedSigIndex = 0,
                            pCryptoPolicy = IntPtr.Zero,
                        };

                        pSignatureSettings = Marshal.AllocHGlobal(signatureSettings.cbStruct);
                    }

                    try
                    {
                        if (pSignatureSettings != IntPtr.Zero)
                        {
                            Marshal.StructureToPtr(signatureSettings, pSignatureSettings, false);
                            WVTData.pSignatureSettings = pSignatureSettings;
                        }

                        IntPtr pgActionID = Marshal.AllocHGlobal(actionIdBytes.Length);

                        try
                        {
                            Marshal.Copy(actionIdBytes, 0, pgActionID, actionIdBytes.Length);

                            IntPtr pWVTData = Marshal.AllocHGlobal(WVTData.cbStruct);

                            try
                            {
                                Marshal.StructureToPtr(WVTData, pWVTData, false);

                                int hRESULT = WinVerifyTrust(hWind, pgActionID, pWVTData);

                                if (hRESULT == 0)
                                {
                                    if (pSignatureSettings != IntPtr.Zero)
                                    {
                                        // Read back the signature settings
                                        signatureSettings = (WINTRUST_SIGNATURE_SETTINGS)Marshal.PtrToStructure(pSignatureSettings, typeof(WINTRUST_SIGNATURE_SETTINGS));
                                    }

                                    int signatureCount = signatureSettings.cSecondarySigs + 1;

                                    Console.WriteLine("File: {0}", fileName);
                                    Console.WriteLine("Authenticode signatures: {0}", signatureCount);
                                    Console.WriteLine();

                                    for (int dwIndex = 0; dwIndex < signatureCount; dwIndex++)
                                    {
                                        if (pSignatureSettings != IntPtr.Zero)
                                        {
                                            signatureSettings.dwIndex = dwIndex;
                                            signatureSettings.dwFlags = WSS_VERIFY_SPECIFIC;

                                            Marshal.StructureToPtr(signatureSettings, pSignatureSettings, false);
                                        }

                                        WVTData.dwStateAction = WTD_STATEACTION_VERIFY;
                                        WVTData.hWVTStateData = IntPtr.Zero;

                                        Marshal.StructureToPtr(WVTData, pWVTData, false);

                                        hRESULT = WinVerifyTrust(hWind, pgActionID, pWVTData);

                                        try
                                        {
                                            if (hRESULT == 0)
                                            {
                                                WVTData = (WINTRUST_DATA)Marshal.PtrToStructure(pWVTData, typeof(WINTRUST_DATA));

                                                IntPtr ptrProvData = WTHelperProvDataFromStateData(WVTData.hWVTStateData);

                                                CRYPT_PROVIDER_DATA provData = (CRYPT_PROVIDER_DATA)Marshal.PtrToStructure(ptrProvData, typeof(CRYPT_PROVIDER_DATA));

                                                for (int idxSigner = 0; idxSigner < provData.csSigners; idxSigner++)
                                                {
                                                    IntPtr ptrProvSigner = WTHelperGetProvSignerFromChain(ptrProvData, idxSigner, false, 0);

                                                    CRYPT_PROVIDER_SGNR ProvSigner = (CRYPT_PROVIDER_SGNR)Marshal.PtrToStructure(ptrProvSigner, typeof(CRYPT_PROVIDER_SGNR));
                                                    CMSG_SIGNER_INFO Signer = (CMSG_SIGNER_INFO)Marshal.PtrToStructure(ProvSigner.psSigner, typeof(CMSG_SIGNER_INFO));

                                                    if (Signer.HashAlgorithm.pszObjId != IntPtr.Zero)
                                                    {
                                                        string objId = Marshal.PtrToStringAnsi(Signer.HashAlgorithm.pszObjId);

                                                        if (objId != null)
                                                        {
                                                            Oid hashOid = Oid.FromOidValue(objId, OidGroup.All);

                                                            if (hashOid != null)
                                                            {
                                                                Console.WriteLine("Hash algorithm of signature {0}: {1}.", dwIndex + 1, hashOid.FriendlyName);
                                                            }
                                                        }
                                                    }

                                                    IntPtr ptrCert = WTHelperGetProvCertFromChain(ptrProvSigner, idxSigner);

                                                    CRYPT_PROVIDER_CERT cert = (CRYPT_PROVIDER_CERT)Marshal.PtrToStructure(ptrCert, typeof(CRYPT_PROVIDER_CERT));

                                                    if (cert.cbStruct > 0)
                                                    {
                                                        X509Certificate2 certificate = new X509Certificate2(cert.pCert);
                                                        Console.WriteLine("Certificate thumbprint of signature {0}: {1}", dwIndex + 1, certificate.Thumbprint);
                                                    }

                                                    if (ProvSigner.sftVerifyAsOf.dwHighDateTime != provData.sftSystemTime.dwHighDateTime &&
                                                        ProvSigner.sftVerifyAsOf.dwLowDateTime != provData.sftSystemTime.dwLowDateTime)
                                                    {
                                                        DateTime timestamp = DateTime.FromFileTimeUtc(((long)ProvSigner.sftVerifyAsOf.dwHighDateTime << 32) | (uint)ProvSigner.sftVerifyAsOf.dwLowDateTime);
                                                        Console.WriteLine("Timestamp of signature {0}: {1}", dwIndex + 1, timestamp);
                                                    }
                                                }
                                            }
                                        }
                                        finally
                                        {
                                            WVTData.dwStateAction = WTD_STATEACTION_CLOSE;
                                            Marshal.StructureToPtr(WVTData, pWVTData, false);

                                            hRESULT = WinVerifyTrust(hWind, pgActionID, pWVTData);
                                        }

                                        Console.WriteLine();
                                    }
                                }
                                else if ((uint)hRESULT == 0x800b0100)
                                {
                                    Console.WriteLine("{0} has no Authenticode signatures.", fileName);
                                }
                            }
                            finally
                            {
                                Marshal.FreeHGlobal(pWVTData);
                            }
                        }
                        finally
                        {
                            Marshal.FreeHGlobal(pgActionID);
                        }
                    }
                    finally
                    {
                        if (pSignatureSettings != IntPtr.Zero)
                        {
                            Marshal.FreeHGlobal(pSignatureSettings);
                        }
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(ptrFile);
                }
            }
            finally
            {
                Marshal.FreeHGlobal(pcwszFilePath);
            }

            Console.WriteLine("Press enter to exit.");
            Console.ReadLine();
        }

        private const int SGNR_TYPE_TIMESTAMP = 0x00000010;
        private const int WTD_UI_NONE = 2;
        private const int WTD_CHOICE_FILE = 1;
        private const int WTD_REVOKE_NONE = 0;
        private const int WTD_REVOKE_WHOLECHAIN = 1;
        private const int WTD_STATEACTION_IGNORE = 0;
        private const int WTD_STATEACTION_VERIFY = 1;
        private const int WTD_STATEACTION_CLOSE = 2;
        private const int WTD_REVOCATION_CHECK_NONE = 16;
        private const int WTD_REVOCATION_CHECK_CHAIN = 64;
        private const int WTD_UICONTEXT_EXECUTE = 0;
        private const int WSS_VERIFY_SPECIFIC = 0x00000001;
        private const int WSS_GET_SECONDARY_SIG_COUNT = 0x00000002;

        [DllImport("wintrust.dll")]
        private static extern int WinVerifyTrust(IntPtr hWind, IntPtr pgActionID, IntPtr pWVTData);

        [DllImport("wintrust.dll")]
        private static extern IntPtr WTHelperProvDataFromStateData(IntPtr hStateData);

        [DllImport("wintrust.dll")]
        private static extern IntPtr WTHelperGetProvSignerFromChain(IntPtr pProvData, int idxSigner, bool fCounterSigner, int idxCounterSigner);

        [DllImport("wintrust.dll")]
        private static extern IntPtr WTHelperGetProvCertFromChain(IntPtr pSgnr, int idxCert);

        [StructLayout(LayoutKind.Sequential)]
        private struct WINTRUST_DATA
        {
            internal int cbStruct;
            internal IntPtr pPolicyCallbackData;
            internal IntPtr pSIPClientData;
            internal int dwUIChoice;
            internal int fdwRevocationChecks;
            internal int dwUnionChoice;
            internal IntPtr pFile;
            internal int dwStateAction;
            internal IntPtr hWVTStateData;
            internal IntPtr pwszURLReference;
            internal int dwProvFlags;
            internal int dwUIContext;
            internal IntPtr pSignatureSettings;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct WINTRUST_SIGNATURE_SETTINGS
        {
            internal int cbStruct;
            internal int dwIndex;
            internal int dwFlags;
            internal int cSecondarySigs;
            internal int dwVerifiedSigIndex;
            internal IntPtr pCryptoPolicy;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct WINTRUST_FILE_INFO
        {
            internal int cbStruct;
            internal IntPtr pcwszFilePath;
            internal IntPtr hFile;
            internal IntPtr pgKnownSubject;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_PROVIDER_DATA
        {
            internal int cbStruct;
            internal IntPtr pWintrustData;
            internal bool fOpenedFile;
            internal IntPtr hWndParent;
            internal IntPtr pgActionID;
            internal IntPtr hProv;
            internal int dwError;
            internal int dwRegSecuritySettings;
            internal int dwRegPolicySettings;
            internal IntPtr psPfns;
            internal int cdwTrustStepErrors;
            internal IntPtr padwTrustStepErrors;
            internal int chStores;
            internal IntPtr pahStores;
            internal int dwEncoding;
            internal IntPtr hMsg;
            internal int csSigners;
            internal IntPtr pasSigners;
            internal int csProvPrivData;
            internal IntPtr pasProvPrivData;
            internal int dwSubjectChoice;
            internal IntPtr pPDSip;
            internal IntPtr pszUsageOID;
            internal bool fRecallWithState;
            internal System.Runtime.InteropServices.ComTypes.FILETIME sftSystemTime;
            internal IntPtr pszCTLSignerUsageOID;
            internal int dwProvFlags;
            internal int dwFinalError;
            internal IntPtr pRequestUsage;
            internal int dwTrustPubSettings;
            internal int dwUIStateFlags;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_PROVIDER_SGNR
        {
            internal int cbStruct;
            internal System.Runtime.InteropServices.ComTypes.FILETIME sftVerifyAsOf;
            internal int csCertChain;
            internal IntPtr pasCertChain;
            internal int dwSignerType;
            internal IntPtr psSigner;
            internal int dwError;
            internal int csCounterSigners;
            internal IntPtr pasCounterSigners;
            internal IntPtr pChainContext;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_PROVIDER_CERT
        {
            internal int cbStruct;
            internal IntPtr pCert;
            internal bool fCommercial;
            internal bool fTrustedRoot;
            internal bool fSelfSigned;
            internal bool fTestCert;
            internal int dwRevokedReason;
            internal int dwConfidence;
            internal int dwError;
            internal IntPtr pTrustListContext;
            internal bool fTrustListSignerCert;
            internal IntPtr pCtlContext;
            internal int dwCtlError;
            internal bool fIsCyclic;
            internal IntPtr pChainElement;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_ALGORITHM_IDENTIFIER
        {
            internal IntPtr pszObjId;
            internal CRYPT_INTEGER_BLOB Parameters;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CMSG_SIGNER_INFO
        {
            internal int dwVersion;
            internal CRYPT_INTEGER_BLOB Issuer;
            internal CRYPT_INTEGER_BLOB SerialNumber;
            internal CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;
            internal CRYPT_ALGORITHM_IDENTIFIER HashEncryptionAlgorithm;
            internal CRYPT_INTEGER_BLOB EncryptedHash;
            internal CRYPT_ATTRIBUTES AuthAttrs;
            internal CRYPT_ATTRIBUTES UnauthAttrs;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_INTEGER_BLOB
        {
            internal int cbData;
            internal IntPtr pbData;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct CRYPT_ATTRIBUTES
        {
            internal int cAttr;
            internal IntPtr rgAttr;
        }
    }
}

Running the application against the SQL Server 2014 SP1 installer gives the following output on Windows 8.1:

File: SQLServer2014SP1-KB3058865-x64-ENU.exe
Authenticode signatures: 2

Hash algorithm of signature 1: sha1.
Certificate thumbprint of signature 1: 67B1757863E3EFF760EA9EBB02849AF07D3A8080
Timestamp of signature 1: 22/04/2015 06:03:40

Hash algorithm of signature 2: sha256.
Certificate thumbprint of signature 2: 76DAF3E30F95B244CA4D6107E0243BB97F7DF965
Timestamp of signature 2: 22/04/2015 06:03:51

Press enter to exit.
like image 104
Martin Costello Avatar answered Oct 14 '22 13:10

Martin Costello


Give Mono a try. It's able to pull all of the file's Authenticode certificates in one line!

    using Mono.Security.Authenticode;

    AuthenticodeDeformatter monoFileCert = new AuthenticodeDeformatter("System.Windows.dll");
    Console.WriteLine($"Found certificates {monoFileCert.Certificates.Count}");

https://github.com/mono/mono/blob/master/mcs/class/Mono.Security/Mono.Security.Authenticode/AuthenticodeDeformatter.cs

Usage example: https://github.com/mono/mono/blob/master/mcs/tools/security/chktrust.cs

like image 27
Sam Karim Avatar answered Oct 14 '22 15:10

Sam Karim