自动执行扩展验证(EV)代码签名


80

我们最近购买了DigiCert EV代码签名证书。我们可以使用signtool.exe对.exe文件进行签名。但是,每次我们签名文件时,都会提示您输入SafeNet eToken密码。

我们如何通过将密码存储/缓存在某个地方来自动执行此过程而无需用户干预?


问题“ SafeNet eToken 5110或类似密码硬件令牌的密码提示的安全性如何? ”在某种程度上是相关的,如果它得到答案,则对于那些评估是否自动执行密码输入的人员应该引起关注。就我而言,如果当前拥有该令牌或类似令牌的某人读到此内容,那么如果您可以尝试“破解”它并回答该问题,将不胜感激:)
gbr

不幸的是,对我有用并且获得最多投票的答案出现在答案列表的末尾,所以请不要浪费时间直接去看Simon Mourier的答案stackoverflow.com/a/26126701/27194
NDepend团队的Patrick

尝试任何一种解决方案之前,请先抬头。硬件令牌具有“令牌​​密码重试剩余”计数器(可以在SafeNet身份验证客户端中检查)。在进行实验时,请确保由于明显的原因它永远不会达到零。否则,您可能会被永久锁定在硬件令牌之外,并且您将必须订购新的令牌!艰难地学习了这一点……
圣代

不幸的是,西蒙的答案不再起作用(请参阅我对答案的评论)。奥斯汀的答案不仅有效,而且无论如何还是更好。
Martin Prikryl

Answers:


64

无法绕过登录对话框AFAIK,但是您可以做的是配置SafeNet身份验证客户端,使其在每个登录会话中仅询问一次。

我在这里引用了SAC doc(安装在\ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chmClient Settings”,“ Enabling Client Logon”一章后找到):

启用单点登录后,用户在每次计算机会话期间仅需一个令牌令牌密码即可访问多个应用程序。这减轻了用户分别登录每个应用程序的需要。

要启用默认情况下禁用的此功能,请转到SAC高级设置,然后选中“启用单点登录”框:

在此处输入图片说明

重新启动计算机,现在它应该仅提示输入一次令牌密码。在我们的例子中,每个构建有200多个二进制文件要签名,因此这是必须的

否则,这是一个小的C#控制台示例代码(相当于m1st0 one),它使您能够自动响应登录对话框(可能需要以admin身份运行)(您需要从控制台项目(UIAutomationClient.dllUIAutomationTypes.dll)进行引用:

using System;
using System.Windows.Automation;

namespace AutoSafeNetLogon {
   class Program {
      static void Main(string[] args) {
         SatisfyEverySafeNetTokenPasswordRequest("YOUR_TOKEN_PASSWORD");
      }


      static void SatisfyEverySafeNetTokenPasswordRequest(string password) {
         int count = 0;
         Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, (sender, e) =>
         {
            var element = sender as AutomationElement;
            if (element.Current.Name == "Token Logon") {
               WindowPattern pattern = (WindowPattern)element.GetCurrentPattern(WindowPattern.Pattern);
               pattern.WaitForInputIdle(10000);
               var edit = element.FindFirst(TreeScope.Descendants, new AndCondition(
                   new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit),
                   new PropertyCondition(AutomationElement.NameProperty, "Token Password:")));

               var ok = element.FindFirst(TreeScope.Descendants, new AndCondition(
                   new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button),
                   new PropertyCondition(AutomationElement.NameProperty, "OK")));

               if (edit != null && ok != null) {
                  count++;
                  ValuePattern vp = (ValuePattern)edit.GetCurrentPattern(ValuePattern.Pattern);
                  vp.SetValue(password);
                  Console.WriteLine("SafeNet window (count: " + count + " window(s)) detected. Setting password...");

                  InvokePattern ip = (InvokePattern)ok.GetCurrentPattern(InvokePattern.Pattern);
                  ip.Invoke();
               } else {
                  Console.WriteLine("SafeNet window detected but not with edit and button...");
               }
            }
         });

         do {
            // press Q to quit...
            ConsoleKeyInfo k = Console.ReadKey(true);
            if (k.Key == ConsoleKey.Q)
               break;
         }
         while (true);
         Automation.RemoveAllEventHandlers();
      }
   }
}

10
这可能不是DigiCert的官方答案,但他们的答案很糟糕,这个太棒了!谢谢您的帮助!
lordjeb 2015年

2
+1为正确答案。看到人们开发脚本来自动执行用户输入之类的事情令我感到惊讶,这真的使密码的实现无法实现,而他们所需要知道的就是该选项在哪里。我怀疑该选项是否会消失,因为发行人理解开发人员无法在每次签名二进制文件时都输入密码。
dyasta '16

3
我可以从TeamCity确认这是可行的(只要TeamCity Windows服务的“允许服务与桌面交互”框被打勾)。我们还需要在另一个线程中运行密码输入过程,并在构建计算机上禁用“交互式服务检测”服务。我们围绕signtool创建了一个C#包装器,该包装器在一个自包含的应用程序中执行签名并如上所述处理密码输入。我不敢相信要完成这项工作,我们需要克服多少障碍,但是对于同一条船上的任何其他人,请专注于上述C#方法...
Alan Spark

1
仅供参考... Symantec EV Certs也使用SafeNet。我们必须围绕此过程构建一个简单的解决方案,但是在阅读了您的答案并实现了控制台应用程序之后,这极大地帮助了我们的构建过程。谢谢。架构欠佳的代码签名过程的绝佳解决方案。
Zoltan

1
我已经成功使用这种有用的解决方案一段时间了。但是现在,当在装有适用于Windows 8及更高版本的SafeNet客户端9.0.34 x64的Windows 10 Pro 2004的新计算机上进行设置时,它不再起作用。新密码提示在哪里。它似乎是Windows内置的,而不是像以前一样的自定义SafeNet提示符。并且新提示的密码框不是自动的(不会在AutomationElement树中显示)。我不知道它是否可以解决。但是,再次访问此问题并找到@Austin的答案,无论如何我相信这是一个更好的解决方案。
Martin Prikryl

32

扩展此线程中已经存在的答案,可以使用Microsoft的标准signtool程序提供令牌密码。

0.在高级视图中打开SafeNet客户端

安装路径可能会有所不同,但是对我而言,SafeNet客户端已安装为: C:\Program Files\SafeNet\Authentication\SAC\x64\SACTools.exe

单击右上角的齿轮图标以打开“高级视图”。 SafeNet高级视图

1.将您的公共证书从SafeNet客户端导出到文件中 将证书导出到文件

2.查找您的私钥容器名称
私钥容器名称

3.找到您的读者姓名 读者姓名

4.一起格式化

eToken CSP具有隐藏(或至少未广泛宣传)的功能,可以从容器名称中解析令牌密码。

格式为以下之一

[]=name
[reader]=name
[{{password}}]=name
[reader{{password}}]=name

哪里:

  • reader 是SafeNet客户端用户界面中的“读者名称”
  • password 是您的令牌密码
  • name 是SafeNet客户端UI中的“容器名称”

如果您连接了多个阅读器,则大概必须指定阅读器名称-因为我只有一个阅读器,所以我无法确认。

5.将信息传递给signtool

  • /f certfile.cer
  • /csp "eToken Base Cryptographic Provider"
  • /k "<value from step 4>"
  • 您需要的任何其他signtool标志

示例signtool命令如下

signtool sign /f mycert.cer /csp "eToken Base Cryptographic Provider" /k "[{{TokenPasswordHere}}]=KeyContainerNameHere" myfile.exe

从此答案中获取的一些图像:https : //stackoverflow.com/a/47894907/5420193


2
伟大的作品,遗憾的是,我发现这两天后,挖掘和实现自己的“signtool”:d THX
卢卡斯Koten

警告:如果您输入错误的密码(甚至两次),此解决方案可能会使您无法使用硬件令牌!在我使用无效密码仅执行一次命令后,密码重试剩余计数器以某种方式从15减少到3。尽管“ etokensign.exe”解决方案似乎可以正常运行,但是一次无效的密码尝试后,剩余的密码计数器应从15减少到14。
圣代

1
太好了,这对我来说非常适合使用Sectigo提供的SafeNet USB软件狗。
JacobJ

1
据我所知,这种语法没有在任何地方公开记录。我通过对IDA Pro中的驱动程序二进制文件进行反向工程发现了此功能。
奥斯汀·莫顿

1
这比伪密码输入到GUI提示的其他答案更好。其中,即使在非交互式Windows会话中也可以使用。其他答案似乎不适用于最新工具
Martin Prikryl

19

扩展此答案,可以使用CryptAcquireContextCryptSetProvParam以编程方式输入令牌PIN,以及使用CryptUIWizDigitalSign以编程方式执行签名来自动执行此操作。我创建了一个控制台应用程序(下面的代码),将证书文件(通过在SafeNet Authentication Client中右键单击证书并选择“导出...”导出),私钥容器名称(在SafeNet Authentication Client中找到)导出为输入,令牌PIN,时间戳URL和要签名的文件的路径。当连接USB令牌的TeamCity构建代理调用该控制台应用程序时,它可以工作。

用法示例:
etokensign.exe c:\CodeSigning.cert CONTAINER PIN http://timestamp.digicert.com C:\program.exe

码:

#include <windows.h>
#include <cryptuiapi.h>
#include <iostream>
#include <string>

const std::wstring ETOKEN_BASE_CRYPT_PROV_NAME = L"eToken Base Cryptographic Provider";

std::string utf16_to_utf8(const std::wstring& str)
{
    if (str.empty())
    {
        return "";
    }

    auto utf8len = ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), NULL, 0, NULL, NULL);
    if (utf8len == 0)
    {
        return "";
    }

    std::string utf8Str;
    utf8Str.resize(utf8len);
    ::WideCharToMultiByte(CP_UTF8, 0, str.data(), str.size(), &utf8Str[0], utf8Str.size(), NULL, NULL);

    return utf8Str;
}

struct CryptProvHandle
{
    HCRYPTPROV Handle = NULL;
    CryptProvHandle(HCRYPTPROV handle = NULL) : Handle(handle) {}
    ~CryptProvHandle() { if (Handle) ::CryptReleaseContext(Handle, 0); }
};

HCRYPTPROV token_logon(const std::wstring& containerName, const std::string& tokenPin)
{
    CryptProvHandle cryptProv;
    if (!::CryptAcquireContext(&cryptProv.Handle, containerName.c_str(), ETOKEN_BASE_CRYPT_PROV_NAME.c_str(), PROV_RSA_FULL, CRYPT_SILENT))
    {
        std::wcerr << L"CryptAcquireContext failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
        return NULL;
    }

    if (!::CryptSetProvParam(cryptProv.Handle, PP_SIGNATURE_PIN, reinterpret_cast<const BYTE*>(tokenPin.c_str()), 0))
    {
        std::wcerr << L"CryptSetProvParam failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
        return NULL;
    }

    auto result = cryptProv.Handle;
    cryptProv.Handle = NULL;
    return result;
}

int wmain(int argc, wchar_t** argv)
{
    if (argc < 6)
    {
        std::wcerr << L"usage: etokensign.exe <certificate file path> <private key container name> <token PIN> <timestamp URL> <path to file to sign>\n";
        return 1;
    }

    const std::wstring certFile = argv[1];
    const std::wstring containerName = argv[2];
    const std::wstring tokenPin = argv[3];
    const std::wstring timestampUrl = argv[4];
    const std::wstring fileToSign = argv[5];

    CryptProvHandle cryptProv = token_logon(containerName, utf16_to_utf8(tokenPin));
    if (!cryptProv.Handle)
    {
        return 1;
    }

    CRYPTUI_WIZ_DIGITAL_SIGN_EXTENDED_INFO extInfo = {};
    extInfo.dwSize = sizeof(extInfo);
    extInfo.pszHashAlg = szOID_NIST_sha256; // Use SHA256 instead of default SHA1

    CRYPT_KEY_PROV_INFO keyProvInfo = {};
    keyProvInfo.pwszContainerName = const_cast<wchar_t*>(containerName.c_str());
    keyProvInfo.pwszProvName = const_cast<wchar_t*>(ETOKEN_BASE_CRYPT_PROV_NAME.c_str());
    keyProvInfo.dwProvType = PROV_RSA_FULL;

    CRYPTUI_WIZ_DIGITAL_SIGN_CERT_PVK_INFO pvkInfo = {};
    pvkInfo.dwSize = sizeof(pvkInfo);
    pvkInfo.pwszSigningCertFileName = const_cast<wchar_t*>(certFile.c_str());
    pvkInfo.dwPvkChoice = CRYPTUI_WIZ_DIGITAL_SIGN_PVK_PROV;
    pvkInfo.pPvkProvInfo = &keyProvInfo;

    CRYPTUI_WIZ_DIGITAL_SIGN_INFO signInfo = {};
    signInfo.dwSize = sizeof(signInfo);
    signInfo.dwSubjectChoice = CRYPTUI_WIZ_DIGITAL_SIGN_SUBJECT_FILE;
    signInfo.pwszFileName = fileToSign.c_str();
    signInfo.dwSigningCertChoice = CRYPTUI_WIZ_DIGITAL_SIGN_PVK;
    signInfo.pSigningCertPvkInfo = &pvkInfo;
    signInfo.pwszTimestampURL = timestampUrl.c_str();
    signInfo.pSignExtInfo = &extInfo;

    if (!::CryptUIWizDigitalSign(CRYPTUI_WIZ_NO_UI, NULL, NULL, &signInfo, NULL))
    {
        std::wcerr << L"CryptUIWizDigitalSign failed, error " << std::hex << std::showbase << ::GetLastError() << L"\n";
        return 1;
    }

    std::wcout << L"Successfully signed " << fileToSign << L"\n";
    return 0;
}

将证书导出到文件:
将证书导出到文件

私钥容器名称:
私钥容器名称


2
这个应该是公认的答案,它就像一个魅力!
肖恩

1
太棒了。特别是在意识到我只需要使用此工具签署一些虚拟文件之后。如果启用了“单一登录”(SafeNet驱动程序),则所有后续步骤都可以使用标准signtool。这对于使用其他工具签署Office加载项(VSTO)很有用,并且还意味着构建脚本/过程只需要进行最小的更改。
Stefan Egli

这个答案是avzhatkin提供的答案的很好的补充。此时,该代码已接近替换signtools.exe。该程序将需要支持交叉签名。幸运的是,现在还有另一个SO帖子可以执行交叉签名
sdc

这个答案对我最大的帮助。在VS2017中进行构建时,我错过了一个外部参考,但是在按此处建议添加一些实用注释时,我设法让Bamboo(Atlassian的CI / CD)进行了签名。
HTBR

11

我已经制作了Beta版工具,该工具将有助于自动化构建过程。

它是Client-Server Windows应用程序。您可以在插入了EV令牌的计算机上启动服务器。在服务器端应用程序启动时输入令牌密码。之后,您可以远程签名文件。客户端应用程序完全替换了signtool.exe,因此您可以使用现有的构建脚本。

源代码位于此处:https : //github.com/SirAlex/RemoteSignTool

编辑:我们成功使用此工具在我们的构建服务器上进行了上半年的24x7的代码签名。一切正常。


1
这种方法有多安全?这是否意味着可以使用HTTP连接到您的签名服务器的任何人都可以使用您的EV证书对他们想要的任何二进制文件进行签名?
Gene Pavlovsky

6

实际上,在Windows上,您可以完全以编程方式指定令牌密码。这可以通过使用标记形式CRYPT_SILENT创建一个上下文(CryptAcquireContext)来完成,该上下文使用形式为“ \\。\ AKS ifdh 0”的令牌名称或令牌容器名称,这在Authentication Client应用程序的特定属性中可见。然后,您需要使用带有参数PP_SIGNATURE_PIN的CryptSetProvParam来指定令牌密码。之后,该过程可以使用该令牌上的证书对文件进行签名。
注意:一旦创建了上下文,似乎就可以完全用于当前进程,而无需将其传递给其他Crypto API函数或其他任何函数。但是,如果您发现需要进一步努力的情况,请随时发表评论。
编辑:添加了代码示例

HCRYPTPROV OpenToken(const std::wstring& TokenName, const std::string& TokenPin)
{
    const wchar_t DefProviderName[] = L"eToken Base Cryptographic Provider";

    HCRYPTPROV hProv = NULL;
    // Token naming can be found in "eToken Software Developer's Guide"
    // Basically you can either use "\\.\AKS ifdh 0" form
    // Or use token's default container name, which looks like "ab-c0473610-8e6f-4a6a-ae2c-af944d09e01c"
    if(!CryptAcquireContextW(&hProv, TokenName.c_str(), DefProviderName, PROV_RSA_FULL, CRYPT_SILENT))
    {
        DWORD Error = GetLastError();
        //TracePrint("CryptAcquireContext for token %ws failed, error 0x%08X\n", TokenName.c_str(), Error);
        return NULL;
    }
    if(!CryptSetProvParam(hProv, PP_SIGNATURE_PIN, (BYTE*)TokenPin.c_str(), 0))
    {
        DWORD Error = GetLastError();
        //TracePrint("Token %ws unlock failed, error 0x%08X\n", TokenName.c_str(), Error);
        CryptReleaseContext(hProv, 0);
        return NULL;
    }
    else
    {
        //TracePrint("Unlocked token %ws\n", TokenName.c_str());
        return hProv;
    }
}

1
有趣。似乎很有希望,您应该对此进行详尽的说明(增强解释,提供代码等)
Simon Mourier

请发布完整的示例。这听起来真的很有用
dten

感谢额外的细节。这是您提到的指南吗?read.pudn.com/downloads128/ebook/549477/eToken_SDK_3_50[1].pdf
dten

我相信它不是我的确切版本,但它似乎包含有关创建上下文和提供PIN的类似信息,尽管用于不同的使用场景。
avzhatkin

我猜您将这个函数称为OpenToken(L“ \\\\。\\ AKS ifdh 0”,<令牌密码>)...对我来说很好!
Michael Haephrati

5

我使用AutoHotKey使用以下脚本自动输入密码。我们一直在尝试为开发人员提供一个基于Web的前端,以便在运行此脚本的情况下将二进制文件发送到Windows框,以便可以对其进行签名和返回。

  Loop
  {   
    Sleep 2000

    if (WinExist("Token Logon"))
    {   
      WinActivate ; use the window found above
      SendInput [your_password]
      SendInput {Enter}
    }   
    if (WinExist("DigiCert Certificate Utility for Windows©"))
    {   
      WinActivate ; use the window found above
      SendInput [your_password]
      SendInput {Enter}
    }   
  } 

我必须注意,我共享的内容并不完全不安全,但是我们也遇到了这个问题,需要为每个开发人员购买签名密钥,或者分配签名经理的工作来批准发布软件的签名。我相信这些是更好,更安全的流程,一旦事情通过了质量保证并被批准发布,就可以正式签名。但是,较小的公司需求可能会要求以其他自动化方式完成此操作。

我最初在Linux上(在EV证书之前)使用osslsigncode来自动签名Windows可执行文件(因为我们有Linux服务器为减轻开发人员的工作量和协作而做了很多工作)。我已经联系osslsigncode的开发人员,以查看他是否可以利用DigiCert SafeNet令牌以不同的方式帮助其自动化,因为我可以在Linux上看到它们。他的答复提供了希望,但是我不确定任何进展,我不能花更多的时间来帮助


查看其他答案。每个会话有一个仅解锁一次的选项,对于大多数用户来说就足够了。
dyasta '16

5

安装https://chocolatey.org/docs/installation (可以使用管理命令提示符中的一个命令来完成)

(重新启动命令提示符)

Supress Choco每次安装的持续提示:

choco feature enable -n=allowGlobalConfirmation

使用以下命令安装python:

choco install python

(重新启动命令提示符)安装其他python模块:

pip install pypiwin32

将以下文本保存到disableAutoprompt.py

import pywintypes
import win32con
import win32gui
import time



DIALOG_CAPTION = 'Token Logon'
DIALOG_CLASS = '#32770'
PASSWORD_EDIT_ID = 0x3ea
TOKEN_PASSWORD_FILE = 'password.txt'
SLEEP_TIME = 10


def get_token_password():
    password = getattr(get_token_password, '_password', None)
    if password is None:
        with open(TOKEN_PASSWORD_FILE, 'r') as f:
            password = get_token_password._password = f.read()

    return password

def enumHandler(hwnd, lParam):
    if win32gui.IsWindowVisible(hwnd):
        if win32gui.GetWindowText(hwnd) == DIALOG_CAPTION and win32gui.GetClassName(hwnd) == DIALOG_CLASS:
            print('Token logon dialog has been detected, trying to enter password...')
            try:
                ed_hwnd = win32gui.GetDlgItem(hwnd, PASSWORD_EDIT_ID)
                win32gui.SendMessage(ed_hwnd, win32con.WM_SETTEXT, None, get_token_password())
                win32gui.PostMessage(ed_hwnd, win32con.WM_KEYDOWN, win32con.VK_RETURN, 0)
                print('Success.')
            except Exception as e:
                print('Fail: {}'.format(str(e)))
                return False

    return True


def main():
    while True:
        try:
            win32gui.EnumWindows(enumHandler, None)
            time.sleep(SLEEP_TIME)
        except pywintypes.error as e:
            if e.winerror != 0:
                raise e


if __name__ == '__main__':
    print('Token unlocker has been started...')
    print('DO NOT CLOSE THE WINDOW!')
    main()

将密码保存到passwd.txt中,然后运行

python disableAutoprompt.py

SafeNet Authentication Client-配置> Client Settings> Advanced>Enable Single Log On 选项可启用,以尽量减少密码提示的数量,但并不完全禁用它们(测试版10.4.26.0)

C#应用程序(例如https://github.com/ganl/safenetpass)无法在锁定屏幕上运行,但可以在此python脚本下运行。


这个脚本很棒,而且我可以使用Yubikey加密狗轻松地使其适应我的需要。但是,Windows 10破坏了它。Windows 10更改为XAML,因此win32gui.xxxx()函数将不起作用。/叹。谢谢微软。这就是为什么我们不能拥有美好的事物的原因。
John Rocha

4

signtool.exe标志/ fd sha256 / f“ signing.cer” / csp“ eToken基本加密提供程序” / kc“ [{{这里是令牌密码”}} =这里的容器名称“” ConsoleApp1.exe“

使用Microsoft Windows SDK 10签名工具


1
高超!这种单线的做法肯定会使所有其他答案感到羞耻(尽管起初我很困惑如何找出我的“容器名称”,直到我在上面的draketb答案中找到说明为止)。
Dan Z

2

得到了Digicert的答案:

不幸的是,EV代码签名证书的部分安全性是您每次必须输入密码。没有办法使其自动化。


尽管他们正在寻找一种解决方案,但他们没有确定何时可用的时间表,但我们得到了相同的答复。他们知道这个SO帖子,但是希望他们能意识到这是一个很大的问题。
艾伦·斯巴克

我们找到了一个办法解决它:github.com/mareklinka/SafeNetTokenSigner/issues/8
gunslingor

2

我的情况是Digicert为CI颁发标准(OV)证书,如果您已经有EV证书,则免费。

我知道这不是解决方案,但是如果您不能将令牌放入服务器(云服务器)中,这就是方法。


1

我这样做的方式是:

  1. 打开令牌

    PCCERT_CONTEXT cert = OpenToken(SAFENET_TOKEN,EV_PASS);

  2. 使用令牌,根/交叉证书(必要时)和EV证书加载到内存中对文件签名

    HRESULT hr = SignAppxPackage(cert,FILETOSIGN);

使用SignerSignEx2():

该文件使用SignerSignEx2()签名,需要使用LoadLibrary()和GetProcAddress()将其加载到内存中:

// Type definition for invoking SignerSignEx2 via GetProcAddress
typedef HRESULT(WINAPI *SignerSignEx2Function)(
    DWORD,
    PSIGNER_SUBJECT_INFO,
    PSIGNER_CERT,
    PSIGNER_SIGNATURE_INFO,
    PSIGNER_PROVIDER_INFO,
    DWORD,
    PCSTR,
    PCWSTR,
    PCRYPT_ATTRIBUTES,
    PVOID,
    PSIGNER_CONTEXT *,
    PVOID,
    PVOID);

// Load the SignerSignEx2 function from MSSign32.dll
HMODULE msSignModule = LoadLibraryEx(
    L"MSSign32.dll",
    NULL,
    LOAD_LIBRARY_SEARCH_SYSTEM32);

if (msSignModule)
{
    SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>(
        GetProcAddress(msSignModule, "SignerSignEx2"));
    if (SignerSignEx2)
    {
        hr = SignerSignEx2(
            signerParams.dwFlags,
            signerParams.pSubjectInfo,
            signerParams.pSigningCert,
            signerParams.pSignatureInfo,
            signerParams.pProviderInfo,
            signerParams.dwTimestampFlags,
            signerParams.pszAlgorithmOid,
            signerParams.pwszTimestampURL,
            signerParams.pCryptAttrs,
            signerParams.pSipData,
            signerParams.pSignerContext,
            signerParams.pCryptoPolicy,
            signerParams.pReserved);
    }
    else
    {
        DWORD lastError = GetLastError();
        hr = HRESULT_FROM_WIN32(lastError);
    }

    FreeLibrary(msSignModule);
}
else
{
    DWORD lastError = GetLastError();
    hr = HRESULT_FROM_WIN32(lastError);
}

// Free any state used during app package signing
if (sipClientData.pAppxSipState)
{
    sipClientData.pAppxSipState->Release();
}

时标

此外,您必须对签名文件进行时间戳记,并使用所连接的时间戳记授权机构进行签名。

通过使用URL安全地检查时间戳服务器来获取当前日期和时间,即可完成此操作。每个签名机构都有自己的时间戳服务器。时间戳是代码签名过程中的一个额外步骤,但是当涉及到EV代码签名时,这是一项要求,它为签名的PE添加了额外的安全性。因此,在代码中添加一个检查用户是否已连接到Internet。

DWORD dwReturnedFlag;
if (InternetGetConnectedState(&dwReturnedFlag,0) == NULL) // use https://docs.microsoft.com/en-us/windows/desktop/api/netlistmgr/nf-netlistmgr-inetworklistmanager-getconnectivity
{
    wprintf(L"Certificate can't be dated with no Internet connection\n");
    return 1;
}

从文件加载证书

std::tuple<DWORD, DWORD, std::string> GetCertificateFromFile
(const wchar_t*                         FileName
    , std::shared_ptr<const CERT_CONTEXT>*   ResultCert)
{
    std::vector<unsigned char> vecAsn1CertBuffer;
    auto tuple_result = ReadFileToVector(FileName, &vecAsn1CertBuffer);

    if (std::get<0>(tuple_result) != 0)
    {
        return tuple_result;
    }

    return GetCertificateFromMemory(vecAsn1CertBuffer, ResultCert);
}

将证书加载到内存中

std::tuple<DWORD, DWORD, std::string> GetCertificateFromMemory
(const std::vector<unsigned char>&      CertData
    , std::shared_ptr<const CERT_CONTEXT>*   ResultCert)
{
    const CERT_CONTEXT* crtResultCert = ::CertCreateCertificateContext
    (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING
        , &CertData[0]
        , static_cast<DWORD>(CertData.size()));
    if (crtResultCert == NULL)
    {
        return std::make_tuple(E_FAIL
            , ::GetLastError()
            , "CertCreateCertificateContext");
    }

    *ResultCert = std::shared_ptr<const CERT_CONTEXT>(crtResultCert
        , ::CertFreeCertificateContext);
    return std::make_tuple(0, 0, "");
}

在访问硬件令牌后加载证书之后,我们将其加载:

std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
        signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);

最后,通过以下功能完成签名:

HRESULT SignAppxPackage(
    _In_ PCCERT_CONTEXT signingCertContext,
    _In_ LPCWSTR packageFilePath)
{
    HRESULT hr = S_OK;
    if (PathFileExists(CertAuthority_ROOT))
    {
        wprintf(L"Cross Certificate '%s' was found\n", CertAuthority_ROOT);
    }
    else
    {
        wprintf(L"Error: Cross Certificate '%s' was not found\n", CertAuthority_ROOT);
        return 3;
    }
    DWORD dwReturnedFlag;
    if (InternetGetConnectedState(&dwReturnedFlag,0) == NULL) 
    {
        wprintf(L"Certificate can't be dated with no Internet connection\n");
        return 1;
    }
    if (PathFileExists(CertAuthority_RSA))
    {
        wprintf(L"Cross Certificate '%s' was found\n", CertAuthority_RSA);
    }
    else
    {
        wprintf(L"Error: Cross Certificate '%s' was not found\n", CertAuthority_RSA);
        return 2;
    }
    if (PathFileExists(CROSSCERTPATH))
    {
        wprintf(L"Microsoft Cross Certificate '%s' was found\n", CROSSCERTPATH);

    }
    else
    {
        wprintf(L"Error: Microsoft Cross Certificate '%s' was not found\n", CROSSCERTPATH);
        return 3;
    }
    // Initialize the parameters for SignerSignEx2
    DWORD signerIndex = 0;

    SIGNER_FILE_INFO fileInfo = {};
    fileInfo.cbSize = sizeof(SIGNER_FILE_INFO);
    fileInfo.pwszFileName = packageFilePath;

    SIGNER_SUBJECT_INFO subjectInfo = {};
    subjectInfo.cbSize = sizeof(SIGNER_SUBJECT_INFO);
    subjectInfo.pdwIndex = &signerIndex;
    subjectInfo.dwSubjectChoice = SIGNER_SUBJECT_FILE;
    subjectInfo.pSignerFileInfo = &fileInfo;

    SIGNER_CERT_STORE_INFO certStoreInfo = {};
    certStoreInfo.cbSize = sizeof(SIGNER_CERT_STORE_INFO);
    certStoreInfo.dwCertPolicy = SIGNER_CERT_POLICY_STORE;// SIGNER_CERT_POLICY_CHAIN_NO_ROOT;
    certStoreInfo.pSigningCert = signingCertContext;

    // Issuer: 'CertAuthority RSA Certification Authority'
    // Subject 'CertAuthority RSA Extended Validation Code Signing CA'
    auto fileCertAuthorityRsaEVCA = CertAuthority_RSA;
    std::shared_ptr<const CERT_CONTEXT> certCertAuthorityRsaEVCA;
    auto tuple_result = GetCertificateFromFile(fileCertAuthorityRsaEVCA, &certCertAuthorityRsaEVCA);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    std::shared_ptr<const CERT_CONTEXT> certCertEV;
    std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
        signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);
    tuple_result = GetCertificateFromMemory(dataCertEV, &certCertEV);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    // Issuer:  'Microsoft Code Verification Root'
    // Subject: 'CertAuthority RSA Certification Authority'
    auto fileCertCross = CertAuthority_ROOT;
    std::shared_ptr<const CERT_CONTEXT> certCertCross;
    tuple_result = GetCertificateFromFile(fileCertCross, &certCertCross);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    //certificate 1 Issuer  : '<Certificate Provider> RSA Certification Authority'
    //              Subject : '<Certificate Provider> Extended Validation Code Signing CA'
    //
    //certificate 2 Issuer  : '<Certificate Provider> Extended Validation Code Signing CA'
    //              Subject : '<Your company / entity name>'
    //
    //certificate 3 Issuer  : 'Microsoft Code Verification Root'
    //              Subject : '<Certificate Provider> Certification Authority'

    std::vector<std::shared_ptr<const CERT_CONTEXT> > certs;
    certs.push_back(certCertAuthorityRsaEVCA);
    certs.push_back(certCertEV);
    certs.push_back(certCertCross);

    std::shared_ptr<void> resultStore;
    tuple_result = FormMemoryCertStore(certs, CERT_STORE_ADD_NEW, &resultStore);

    if (std::get<0>(tuple_result) != 0)
    {
        std::cout << "Error: " << std::get<0>(tuple_result) << " " << std::get<1>(tuple_result) << " " << std::get<2>(tuple_result) << "\n";
        return std::get<0>(tuple_result);
    }

    certStoreInfo.hCertStore = resultStore.get();
    //--------------------------------------------------------------------

    SIGNER_CERT cert = {};
    cert.cbSize = sizeof(SIGNER_CERT);
    cert.dwCertChoice = SIGNER_CERT_STORE;
    cert.pCertStoreInfo = &certStoreInfo;

    // The algidHash of the signature to be created must match the
    // hash algorithm used to create the app package
    SIGNER_SIGNATURE_INFO signatureInfo = {};
    signatureInfo.cbSize = sizeof(SIGNER_SIGNATURE_INFO);
    signatureInfo.algidHash = CALG_SHA_256;
    signatureInfo.dwAttrChoice = SIGNER_NO_ATTR;

    SIGNER_SIGN_EX2_PARAMS signerParams = {};
    signerParams.pSubjectInfo = &subjectInfo;
    signerParams.pSigningCert = &cert;
    signerParams.pSignatureInfo = &signatureInfo;
    signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_RFC3161;
    signerParams.pszAlgorithmOid = szOID_NIST_sha256;
    //signerParams.dwTimestampFlags = SIGNER_TIMESTAMP_AUTHENTICODE;
    //signerParams.pszAlgorithmOid = NULL;
    signerParams.pwszTimestampURL = TIMESTAMPURL;

    APPX_SIP_CLIENT_DATA sipClientData = {};
    sipClientData.pSignerParams = &signerParams;
    signerParams.pSipData = &sipClientData;

    // Type definition for invoking SignerSignEx2 via GetProcAddress
    typedef HRESULT(WINAPI *SignerSignEx2Function)(
        DWORD,
        PSIGNER_SUBJECT_INFO,
        PSIGNER_CERT,
        PSIGNER_SIGNATURE_INFO,
        PSIGNER_PROVIDER_INFO,
        DWORD,
        PCSTR,
        PCWSTR,
        PCRYPT_ATTRIBUTES,
        PVOID,
        PSIGNER_CONTEXT *,
        PVOID,
        PVOID);

    // Load the SignerSignEx2 function from MSSign32.dll
    HMODULE msSignModule = LoadLibraryEx(
        L"MSSign32.dll",
        NULL,
        LOAD_LIBRARY_SEARCH_SYSTEM32);

    if (msSignModule)
    {
        SignerSignEx2Function SignerSignEx2 = reinterpret_cast<SignerSignEx2Function>(
            GetProcAddress(msSignModule, "SignerSignEx2"));
        if (SignerSignEx2)
        {
            hr = SignerSignEx2(
                signerParams.dwFlags,
                signerParams.pSubjectInfo,
                signerParams.pSigningCert,
                signerParams.pSignatureInfo,
                signerParams.pProviderInfo,
                signerParams.dwTimestampFlags,
                signerParams.pszAlgorithmOid,
                signerParams.pwszTimestampURL,
                signerParams.pCryptAttrs,
                signerParams.pSipData,
                signerParams.pSignerContext,
                signerParams.pCryptoPolicy,
                signerParams.pReserved);
        }
        else
        {
            DWORD lastError = GetLastError();
            hr = HRESULT_FROM_WIN32(lastError);
        }

        FreeLibrary(msSignModule);
    }
    else
    {
        DWORD lastError = GetLastError();
        hr = HRESULT_FROM_WIN32(lastError);
    }

    // Free any state used during app package signing
    if (sipClientData.pAppxSipState)
    {
        sipClientData.pAppxSipState->Release();
    }

    return hr;
}

看到我写的这篇文章

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.