Question

The Windows SDK ships with a tool called signtool.exe that lets you sign a file with a certificate. I need to do the same thing but in a background service so I'm on the lookout for a library (preferably managed code, but COM will do) to do the same thing. Any ideas?

Found the answer. Here's how to use an X.509 certificate to sign a file in .NET:

CmsSigner signer = new CmsSigner();
signer.Certificate = new X509Certificate2(certificate);

SignedCms content = new SignedCms(new ContentInfo(File.ReadAllBytes(fileToSign)));
content.ComputeSignature(signer, true);
byte[] signedFile = content.Encode();

string signedFileName = fileToSign + ".signed";
File.WriteAllBytes(signedFileName, signedFile);

Console.WriteLine("Signed file: " + signedFileName);

Here, certificate is the path to the .pfx file containing the certificate and fileToSign is the file to sign.

Was it helpful?

Solution

SignTool is using CAPICOM which is the COM wrapper for the Crypto API. You can use either one. If you're going with CAPICOM, you can check the information here.

OTHER TIPS

For the google-Travelers arriving here:

This MSDN forum thread says there is a CryptUIWizDigitalSign API in windows. It also points to a blog article by Alejandro Campos Magencio that shows a sample implementation in VB.NET.

Since a C# version seems to be missing I converted Alejandro's code to C#. Note that the following code only works with files (yet).

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

namespace ConsoleApp1
{
    /// <summary>
    /// Provides code signing functionality via Windows COM Cryptui.dll.
    /// </summary>
    class Signer
    {

        public const Int32 CRYPTUI_WIZ_NO_UI = 1;
        public const Int32 CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE = 1;
        public const Int32 CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 1;

        public struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO
        {
            public Int32 dwSize;
            public Int32 dwSubjectChoice;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string pwszFileName;
            public Int32 dwSigningCertChoice;
            public IntPtr pSigningCertContext;
            public string pwszTimestampURL;
            public Int32 dwAdditionalCertChoice;
            public IntPtr pSignExtInfo;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT
        {
            public Int32 dwSize;
            public Int32 cbBlob;
            public IntPtr pbBlob;
        }

        [DllImport("Cryptui.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CryptUIWizDigitalSign(Int32 dwFlags, IntPtr hwndParent, string pwszWizardTitle, ref CRYPTUI_WIZ_DIGITAL_SIGN_INFO pDigitalSignInfo, ref IntPtr ppSignContext);

        [DllImport("Cryptui.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool CryptUIWizFreeDigitalSignContext(IntPtr pSignContext);

        /// <summary>
        /// Signs the executable at the given path with the given code signing certificate.
        /// </summary>
        /// <example>
        ///    string certPath = @"C:\certs\CodeSigningTestCert.pfx";
        ///    string exePath = @"C:\temp\ConsoleApp2ToBeSigned.exe";
        ///    string certPwd = "myGreatSecurePassword";
        ///    
        ///    try
        ///    {
        ///        string resultingSignature = Signer.SignExecutable(certPath, exePath, certPwd);
        ///    }
        ///    catch (Win32Exception ex)
        ///    {
        ///        Console.WriteLine(ex.Message + ", Native error code: " + ex.NativeErrorCode.ToString());
        ///    }
        ///    catch (Exception ex)
        ///    {
        ///        // Any unexpected errors?
        ///        Console.WriteLine(ex.Message);
        ///    }
        /// 
        /// </example>
        /// <param name="certPath">The absolute path to the PFX file to be used for signing the exe file.</param>
        /// <param name="exePath">The absolute path to the executable to be signed.</param>
        /// <param name="certPwd">The password for the PFX file.</param>
        public static string SignExecutable(string certPath, string exePath, string certPwd)
        {
            X509Certificate2 cert = default(X509Certificate2);

            CRYPTUI_WIZ_DIGITAL_SIGN_INFO digitalSignInfo = default(CRYPTUI_WIZ_DIGITAL_SIGN_INFO);
            CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT signContext = default(CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT);

            IntPtr pSignContext = default(IntPtr);
            IntPtr pSigningCertContext = default(IntPtr);

            // Get certificate context
            cert = new X509Certificate2(certPath, certPwd);
            pSigningCertContext = cert.Handle;

            // Prepare signing info: exe and cert
            digitalSignInfo = new CRYPTUI_WIZ_DIGITAL_SIGN_INFO();
            digitalSignInfo.dwSize = Marshal.SizeOf(digitalSignInfo);
            digitalSignInfo.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE;
            digitalSignInfo.pwszFileName = exePath;
            digitalSignInfo.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_CERT;
            digitalSignInfo.pSigningCertContext = pSigningCertContext;
            digitalSignInfo.pwszTimestampURL = null;
            digitalSignInfo.dwAdditionalCertChoice = 0;
            digitalSignInfo.pSignExtInfo = IntPtr.Zero;

            // Sign exe
            if ((!CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, IntPtr.Zero, null, ref digitalSignInfo, ref pSignContext)))
                throw new Win32Exception(Marshal.GetLastWin32Error(), "CryptUIWizDigitalSign");

            // Get the blob with the signature
            signContext = (CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT)Marshal.PtrToStructure(pSignContext, typeof(CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT));
            byte[] blob = new byte[signContext.cbBlob + 1];
            Marshal.Copy(signContext.pbBlob, blob, 0, signContext.cbBlob);

            // Free blob memory
            if ((!CryptUIWizFreeDigitalSignContext(pSignContext)))
                throw new Win32Exception(Marshal.GetLastWin32Error(), "CryptUIWizFreeDigitalSignContext");

            return System.Text.Encoding.Default.GetString(blob);
        }
    }
}

Hope it helps!

You can't just script your way around it? Write a simple batch file that gives it the correct arguments and input? That is atleast what we do when we see this problem on a UNIX server.

I'm getting the same problem as Sebastien. Looking into the API's, it appears that this is for signing enveloped messages. Authenticode -- the code-signing that signtool does -- is different, which is why the EXE doesn't run after the signing.

I'm still looking for alternatives.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top