Question

I'm trying to use CryptoAPI from C# code to add SHA256 timestamps to signed assemblies. Here is the code I'm using:

Signer.TimestampSignedAssembly("MyAssembly.exe", "http://tsa.starfieldtech.com");

Signer class:

public static class Signer
{
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SIGNER_SUBJECT_INFO
    {
        public uint cbSize;
        public IntPtr pdwIndex;
        public uint dwSubjectChoice;
        public SubjectChoiceUnion Union1;
        [StructLayoutAttribute(LayoutKind.Explicit)]
        internal struct SubjectChoiceUnion
        {
            [FieldOffsetAttribute(0)]
            public IntPtr pSignerFileInfo;
            [FieldOffsetAttribute(0)]
            public IntPtr pSignerBlobInfo;
        }
    }

    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SIGNER_FILE_INFO
    {
        public uint cbSize;
        public IntPtr pwszFileName;
        public IntPtr hFile;
    }

    [DllImport("Mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int SignerTimeStampEx2(
        uint dwFlags,               // DWORD
        IntPtr pSubjectInfo,        // SIGNER_SUBJECT_INFO
        string pwszHttpTimeStamp,   // LPCWSTR
        uint dwAlgId,               // ALG_ID
        IntPtr psRequest,           // PCRYPT_ATTRIBUTES
        IntPtr pSipData,            // LPVOID 
        out IntPtr ppSignerContext  // SIGNER_CONTEXT
        );

    public static void TimestampSignedAssembly(string appPath, string tsaServer)
    {
        if (tsaServer == null) throw new ArgumentNullException("tsaServer");

        var pSubjectInfo = IntPtr.Zero;            
        try
        {                
            pSubjectInfo = CreateSignerSubjectInfo(appPath);
            TimestampSignedAssembly(pSubjectInfo, tsaServer);
        }
        finally
        {                
            if (pSubjectInfo != IntPtr.Zero)
            {
                Marshal.DestroyStructure(pSubjectInfo, typeof(SIGNER_SUBJECT_INFO));
            }                
        }
    }

    private static IntPtr CreateSignerSubjectInfo(string pathToAssembly)
    {
        var info = new SIGNER_SUBJECT_INFO
        {
            cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_SUBJECT_INFO)),
            pdwIndex = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(uint)))
        };
        var index = 0;
        Marshal.StructureToPtr(index, info.pdwIndex, false);

        info.dwSubjectChoice = 0x1; //SIGNER_SUBJECT_FILE
        var assemblyFilePtr = Marshal.StringToHGlobalUni(pathToAssembly);

        var fileInfo = new SIGNER_FILE_INFO
        {
            cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_FILE_INFO)),
            pwszFileName = assemblyFilePtr,
            hFile = IntPtr.Zero
        };

        info.Union1 = new SIGNER_SUBJECT_INFO.SubjectChoiceUnion
        {
            pSignerFileInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIGNER_FILE_INFO)))
        };

        Marshal.StructureToPtr(fileInfo, info.Union1.pSignerFileInfo, false);

        IntPtr pSubjectInfo = Marshal.AllocHGlobal(Marshal.SizeOf(info));
        Marshal.StructureToPtr(info, pSubjectInfo, false);

        return pSubjectInfo;
    }

    /* 
        Here CryptoAPI function SignerTimeStampEx2 called.
    */
    private static void TimestampSignedAssembly(IntPtr pSubjectInfo, string tsaServer)
    {            
        IntPtr context;
        var hResult = SignerTimeStampEx2(
            0x1,            // I have not found anywhere what value should have this parameter!
            pSubjectInfo,   
            tsaServer,      
            0x0000800c,     // 256 bit SHA hashing algorithm. This value taken form here: http://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
            IntPtr.Zero,    
            IntPtr.Zero,
            out context
            );

        if (hResult != 0)
        {
            throw new Exception(string.Format("Error occured when adding timestamp - Error code: 0x{0:X}", hResult));
        }
    }
}   

Despite the fact that I pass to SignerTimeStampEx2 function an argument (dwAlgId), indicating that it is necessary to add SHA256 timestamp (0x0000800c), SHA1 timestamp is always generated.

Has anyone encountered with this problem? What I'm doing wrong? What values should I set for dwFlags and dwAlgId parameters?

Thanks in advance!

Was it helpful?

Solution

dwFlags needs to be SIGNER_TIMESTAMP_RFC3161 (2). The reason you get an access violation is that SignerTimeStampEx2() is documented incorrectly. It expects the algorithm as a PCSTR rather than a DWORD. If you pass 0x800C it'll try to dereference that as a pointer, leading to the AV. So replace ALG_ID dwAlgId in the function declaration with PCSTR pszTimeStampAlgorithmOid. Pass szOID_NIST_sha256 to it, which should be defined as "2.16.840.1.101.3.4.2.1".

SignerTimeStampEx3() is also incorrectly incorrectly documented. pszTimeStampAlgorithmOid should be declared as PCSTR rather than as PCWSTR.

In my experience, code signing and time stamping are more reliable if you specify both the file name and an open Win32 file handle in the SIGNER_FILE_INFO structure.

Whether you will actually get an SHA-256 time stamp also depends on the time stamping service you're using. http://tsa.starfieldtech.com, http://timestamp.globalsign.com/ and http://timestamp.comodoca.com/rfc3161 issue SHA-256 timestamps. Other services may issue SHA-1 time stamps even when requesting an SHA-256 time stamp.

OTHER TIPS

I got it working finally. Here is the complete code of the Timestamper class:

public static class Timestamper
{
    [StructLayout(LayoutKind.Sequential)]
    struct SIGNER_SUBJECT_INFO
    {
        public uint cbSize;
        public IntPtr pdwIndex;
        public uint dwSubjectChoice;
        public SubjectChoiceUnion Union1;
        [StructLayoutAttribute(LayoutKind.Explicit)]
        internal struct SubjectChoiceUnion
        {
            [FieldOffsetAttribute(0)]
            public IntPtr pSignerFileInfo;
            [FieldOffsetAttribute(0)]
            public IntPtr pSignerBlobInfo;
        }
    }

    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SIGNER_FILE_INFO
    {
        public uint cbSize;
        public IntPtr pwszFileName;
        public IntPtr hFile;
    }

    [DllImport("Mssign32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int SignerTimeStampEx2(
        uint dwFlags,                    // DWORD
        IntPtr pSubjectInfo,             // SIGNER_SUBJECT_INFO
        string pwszHttpTimeStamp,        // LPCWSTR
        IntPtr pszTimeStampAlgorithmOid, // PCSTR
        IntPtr psRequest,                // PCRYPT_ATTRIBUTES
        IntPtr pSipData,                 // LPVOID 
        out IntPtr ppSignerContext       // SIGNER_CONTEXT
     );

    public static void TimestampSignedAssembly(string appPath, string tsaServer)
    {
        if (tsaServer == null) throw new ArgumentNullException("tsaServer");

        IntPtr pSubjectInfo = IntPtr.Zero;
        try
        {
            pSubjectInfo = CreateSignerSubjectInfo(appPath);
            TimestampSignedAssembly(pSubjectInfo, tsaServer);
        }
        finally
        {
            if (pSubjectInfo != IntPtr.Zero)
            {
                Marshal.DestroyStructure(pSubjectInfo, typeof(SIGNER_SUBJECT_INFO));
            }
        }
    }

    private static IntPtr CreateSignerSubjectInfo(string pathToAssembly)
    {
        SIGNER_SUBJECT_INFO info = new SIGNER_SUBJECT_INFO
        {
            cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_SUBJECT_INFO)),
            pdwIndex = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(uint)))
        };

        int index = 0;
        Marshal.StructureToPtr(index, info.pdwIndex, false);

        info.dwSubjectChoice = 0x1; //SIGNER_SUBJECT_FILE
        IntPtr assemblyFilePtr = Marshal.StringToHGlobalUni(pathToAssembly);

        SIGNER_FILE_INFO fileInfo = new SIGNER_FILE_INFO
        {
            cbSize = (uint)Marshal.SizeOf(typeof(SIGNER_FILE_INFO)),
            pwszFileName = assemblyFilePtr,
            hFile = IntPtr.Zero
        };

        info.Union1 = new SIGNER_SUBJECT_INFO.SubjectChoiceUnion
        {
            pSignerFileInfo = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SIGNER_FILE_INFO)))
        };

        Marshal.StructureToPtr(fileInfo, info.Union1.pSignerFileInfo, false);

        IntPtr pSubjectInfo = Marshal.AllocHGlobal(Marshal.SizeOf(info));
        Marshal.StructureToPtr(info, pSubjectInfo, false);

        return pSubjectInfo;
    }

    /* 
        Here CryptoAPI function SignerTimeStampEx2 called.
    */
    private static void TimestampSignedAssembly(IntPtr pSubjectInfo, string tsaServer)
    {
        IntPtr context;
        int hResult = SignerTimeStampEx2(
            0x2, // SIGNER_TIMESTAMP_RFC3161
            pSubjectInfo,
            tsaServer,
            Marshal.StringToHGlobalAnsi("2.16.840.1.101.3.4.2.1"), // szOID_NIST_sha256 constant, SHA256 hashing algorithm.
            IntPtr.Zero,
            IntPtr.Zero,
            out context
         );

        if (hResult != 0)
        {
            throw new Exception(string.Format("Error occured when adding timestamp - Error code: 0x{0:X}", hResult));
        }
    }
}

Usage example:

Timestamper.TimestampSignedAssembly("Assembly.exe", "http://timestamp.comodoca.com/?td=sha256");
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top