40

我们最近购买了DigiCert EV代码签名证书。我们可以使用signtool.exe签署.exe文件。但是,每次我们签署文件时,都会提示输入SafeNet eToken密码。自动化扩展验证(EV)代码签名

如何在没有用户干预的情况下通过在某处存储/缓存密码来自动执行此过程?

+0

问题“[?如何安全是SafeNet公司的eToken 5110或类似的加密硬件令牌的密码提示](https://security.stackexchange.com/questions/159645/如何安全地使用密码提示安全的网络-ecoken-5110或类似的加密算法)“有点相关,如果它得到了答案,那么应该对那些评估是否自动化的人感兴趣密码输入。正如我所知道的,如果有人现在拥有这个或类似的标记读取这个,如果你可以尝试“破解”它并回答这个问题,将不胜感激:) – gbr

回答

4

得到了Digicert答案:

Unfortunately, part of the security with the EV Code Signing Certificate is that you must enter the password everytime. There is not a way to automate it.

+0

我们得到了同样的回应,虽然他们是寻找一个解决方案,他们没有时间框架,以确定何时可用。他们知道这个SO帖子,尽管希望他们会意识到这是一个多少问题。 –

3

我以前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上看到它们。他的回复提供了希望,但我不确定是否有任何进展,并且我无法投入更多时间来帮助

+0

查看其他答案。每个会话只能解锁一次,这对大多数用户来说已经足够了。 –

38

无法绕过登录对话框AFAIK,但您可以执行的操作是配置SafeNet身份验证客户端,它每次登录会话一次。

我引用SAC(DOC一旦发现安装在\ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chm,章 'Client Settings', 'Enabling Client Logon')在这里:

When single logon is enabled, users can access multiple applications with only one request for the Token Password during each computer session. This alleviates the need for the user to log on to each application separately.

要启用此功能,默认情况下禁用,去SAC高级设置,并勾选“启用单点登录”框:

enter image description here

重新启动计算机,并且现在应该只提示输入令牌密码一次。在我们的情况下,我们每个版本都有超过200个二进制文件签名,因此总共需要必须

否则,这里是一个小的C#控制台示例代码(相当于m1st0之一),使您可以自动响应登录对话框(可能需要以管理员身份运行):

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(); 
    } 
+7

这可能不是DigiCert的正式答案,但他们的答案很糟糕,而且这个答案很棒!谢谢您的帮助! – lordjeb

+2

对于正确的答案+1。让我感到惊讶的是,看到人们开发脚本来自动化用户输入等,这实际上破坏了输入密码的目的,并且他们需要知道的只是这个选项的用途。我怀疑这个选项永远不会消失,因为发行人理解开发人员在每次签署二进制文件时都无法输入密码。 –

+3

我可以确认这是从TeamCity工作的(只要TeamCity Windows服务具有“允许服务与桌面交互”框)。我们还需要在另一个线程中运行密码输入过程,并在我们的构建机器上禁用“交互式服务检测”服务。我们创建了一个围绕signtool的C#包装器,它执行签名并处理上面的密码条目,所有这些都在一个自包含的应用程序中。我无法相信我们必须跨越多少障碍才能完成这项工作,但对于同一艘船上的其他人而言,只关注上述C#方法...... –

2

我我的情况Digicert问题如果您已经拥有EV证书,则免费获得CI的标准(OV)证书。

我知道这不是解决方案,但如果你不能将令牌放入服务器(云服务器),这是要走的路。

7

我做了测试工具,这将有助于自动化构建过程。

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

源代码在这里:https://github.com/SirAlex/RemoteSignTool

编辑:我们成功地使用这个工具的代码我们构建服务器上签上半年的全天候。一切正常。该工具的

1

Python的变种:

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() 

另外,我还发现,oVirt控制台有默认的行为发送锁定到Windows。 您需要在服务器选项和设置自动登录中禁用它。

2

其实在Windows上,您可以完全以编程方式指定令牌密码。这可以通过创建带有标志CRYPT_SILENT的上下文(CryptAcquireContext)来完成,使用形式为“\\。\ AKS ifdh 0”的标记名称或标记容器名称,这是标识属性在验证客户端应用程序中的某些guid中可见的。然后您需要使用带参数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

有趣。似乎很有希望,你应该恕我直言详细说明(增强解释,提供代码等) –

+0

请发表一个完整的例子。这听起来非常有用 – dten

+0

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

1

扩展在this answer,这可以通过使用CryptAcquireContextCryptSetProvParam以编程和CryptUIWizDigitalSign输入令牌PIN以编程执行签名被自动化。我创建了一个控制台应用程序(下面的代码),其中输入证书文件(通过右键单击SafeNet身份验证客户端中的证书并选择“导出...”导出),私钥密钥容器名称(可在SafeNet身份验证客户端中找到)令牌PIN,时间戳URL以及要签名的文件的路径。该控制台应用程序在由TeamCity构建代理调用USB标记的情况下工作。

实例应用:
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; 
} 

导出证书到文件:
Exporting the Certificate to a File

私人密钥容器名称:
Private Key Container Name