在 C# 中使用 CryptUI 库对字节数组进行签名

问题描述 投票:0回答:1

我能够成功使用此页面上的信息,使用以下代码使用 x509 证书(.pfx 文件)对文件进行数字签名:

    const Int32 CRYPTUI_WIZ_NO_UI = 1;
    const Int32 CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE = 1;
    const Int32 CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 1;
    
    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;
    }

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

    public bool SignExecutable(string certificatePath, string applicationPath, string certificatePassword)
    {
        var cert = new X509Certificate2(certificatePath, certificatePassword);
        var pSigningCertContext = cert.Handle;

        var digitalSignInfo = default(CRYPTUI_WIZ_DIGITAL_SIGN_INFO);
        digitalSignInfo = new CRYPTUI_WIZ_DIGITAL_SIGN_INFO()
        {
            dwSize = Marshal.SizeOf(digitalSignInfo),
            dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE,
            pwszFileName = applicationPath,
            dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_CERT,
            pSigningCertContext = pSigningCertContext,
            pwszTimestampURL = null,
            dwAdditionalCertChoice = 0,
            pSignExtInfo = IntPtr.Zero
        };

        var pSignContext = default(IntPtr);
        return CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, IntPtr.Zero, null, ref digitalSignInfo, ref pSignContext));
    }

但是,这个过程要求我首先将文件写入磁盘。我需要做的是在将文件写入磁盘之前对内存中的文件内容进行数字签名(因为它是动态生成的)。 CryptUI 文档据说支持这一点,所以我修改了代码如下:

    const Int32 CRYPTUI_WIZ_NO_UI = 1;
    const Int32 CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_BLOB = 2;
    const Int32 CRYPTUI_WIZ_DIGITAL_SIGN_CERT = 1;

    public struct CRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO
    {
        public Int32 dwSize;
        public IntPtr pGuidSubject;
        public Int32 cbBlob;
        public IntPtr pbBlob;
        public string pwszDisplayName;
    }

    struct CRYPTUI_WIZ_DIGITAL_SIGN_INFO
    {
        public Int32 dwSize;
        public Int32 dwSubjectChoice;
        public IntPtr pSignBlobInfo;
        public Int32 dwSigningCertChoice;
        public IntPtr pSigningCertContext;
        public string pwszTimestampURL;
        public Int32 dwAdditionalCertChoice;
        public IntPtr pSignExtInfo;
    }

    struct CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT
    {
        public Int32 dwSize;
        public Int32 cbBlob;
        public IntPtr pbBlob;
    };

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

    public byte[] SignExecutableBLOB(string certificatePath, byte[] applicationContent, string certificatePassword)
    {
        var cert = new X509Certificate2(certificatePath, certificatePassword);
        var pSigningCertContext = cert.Handle;

        int size = Marshal.SizeOf(applicationContent[0]) * applicationContent.Length;
        var blobInfo = default(CRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO);
        blobInfo = new CRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO()
        {
            dwSize = Marshal.SizeOf(blobInfo),
            pGuidSubject = IntPtr.Zero,
            cbBlob = size,
            pbBlob = Marshal.AllocHGlobal(size),
            pwszDisplayName = null
        };
        Marshal.Copy(applicationContent, 0, blobInfo.pbBlob, size);

        IntPtr pBlobInfo = Marshal.AllocHGlobal(Marshal.SizeOf(blobInfo));
        Marshal.StructureToPtr(blobInfo, pBlobInfo, false);

        var digitalSignInfo = default(CRYPTUI_WIZ_DIGITAL_SIGN_INFO);
        digitalSignInfo = new CRYPTUI_WIZ_DIGITAL_SIGN_INFO()
        {
            dwSize = Marshal.SizeOf(digitalSignInfo),
            dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_BLOB,
            pSignBlobInfo = pBlobInfo,
            dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_CERT,
            pSigningCertContext = pSigningCertContext,
            pwszTimestampURL = null,
            dwAdditionalCertChoice = 0,
            pSignExtInfo = IntPtr.Zero
        };

        try
        {
            var pSignContext = default(CRYPTUI_WIZ_DIGITAL_SIGN_CONTEXT);
            if (!CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, IntPtr.Zero, null, ref digitalSignInfo, ref pSignContext))
                throw new Win32Exception($"An error occurred attempting to digitally sign the content");
            
            var signedApplicationContent = new byte[pSignContext.cbBlob];
            Marshal.Copy(pSignContext.pbBlob, signedApplicationContent, 0, pSignContext.cbBlob);
            return signedApplicationContent;
        }
        finally
        {
            Marshal.FreeHGlobal(pBlobInfo);
            Marshal.FreeHGlobal(blobInfo.pbBlob);
        }
    }

到目前为止,所有尝试运行此代码都会导致错误 0x80070057(参数不正确)。我尝试了很多小的改变,但我似乎找不到正确的方法来让这个调用成功。我很确定这与托管代码和非托管代码之间的数据编组不当有关,但我无法弄清楚。任何帮助将不胜感激。

c# cryptography marshalling digital-signature x509certificate
1个回答
2
投票

几天前我偶然发现了这个悬而未决的问题,因为我正在与同样的事情作斗争。我的公司(Gibson Research Company)销售商业软件产品(SpinRite),它是混合 DOS/Windows 可执行文件。每个许可的可执行文件都是为其购买者定制的,因此我们需要我们的网络服务器能够即时签署这些下载。就像发布这个问题的人一样,将文件写入文件系统只是为了对其进行签名,然后发送给其用户,这会很混乱(而且是错误的)。执行此操作的正确方法是将文件保存在 RAM 中,对其进行签名,将签名的文件数据流式传输给其所有者,然后释放 RAM 分配。

令人恼火的是令人惊讶缺乏有关 Windows 加密 API 使用的文档。这甚至是错误的,因为 Microsoft 的“CRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO”结构的关键“pGuidSubject”成员的文档将其描述为“指向 GUID 的指针,其中包含标识要加载的会话启动协议 (SIP) 函数的 GUID”。问题是,这里的“SIP”并不代表“会话发起协议”……它代表“主题接口包”。

今天之前我从来没有成为过这里的会员。但我过去从这里得到了很多帮助,所以我决定是时候回馈了。事情是这样的:

正如我们在上面发帖者的原始问题中看到的,当他给 Windows 一个文件名时,他就能够进行签名工作。它起作用的原因是,这允许 Windows 检查文件以确定如何签署该特定类型的文件。但是,如果我们想让 Windows 在 RAM 中签署一个无定形的“blob”,我们需要告诉 Windows 我们希望它签署什么样的“blob”...而这就是“pGuidSubject”的位置结构成员出现。:) 在 CRYPTUI_WIZ_DIGITAL_SIGN_BLOB_INFO 结构中,我们需要提供一个 GUID,告诉 Windows 我们要签名的 blob 是一个 Windows 可执行文件。

那么,我们在哪里可以找到 GUID?通常这样的 GUID 会在头文件中的某个位置预定义。但在本例中并非如此。为了使其更加“开放”,微软定义了一个可以提供示例文件的函数,它将返回该文件类型的 SIP“主题接口包”GUID。其功能是:

CryptSIPRetrieveSubjectGuid

如果您给它一个示例 Windows 可执行文件、一个空句柄和一个指向要填充的空 GUID 的指针,它将返回:

{C689AAB8-8E78-11D0-8C47-00C04FC295EE}

这就是使签名 blob 发挥作用所需的缺失的魔法咒语。只需将“pGuidSubject”结构内存指向该 GUID 即可。如果您想让 Windows 签署其他类型的 blob,只需将其放入文件中,将“CryptSIPRetrieveSubjectGuid”指向它,它将返回一个可用于“内存中”签名的 GUID。 :)

我希望这对其他有此需求的人有用。让我惊讶的是,我们需要填补微软令人惊讶的文档(甚至示例代码)的缺乏,但这就是我们今天的处境。至少这个社区可以弥补它的不足。

© www.soinside.com 2019 - 2024. All rights reserved.