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

Passwords 自动执行扩展验证(EV)代码签名,passwords,code-signing,authenticode,code-signing-certificate,Passwords,Code Signing,Authenticode,Code Signing Certificate,我们最近购买了DigiCert EV代码签名证书。我们可以使用signtool.exe对.exe文件进行签名。但是,每次我们对文件进行签名时,它都会提示输入SafeNet eToken密码 我们如何在没有用户干预的情况下,通过将密码存储/缓存到某个地方来自动化此过程?从Digicert获得了一个答案: 不幸的是,EV代码签名证书的部分安全性在于每次都必须输入密码。 没有一种方法可以使它自动化 我曾经使用下面的脚本自动输入密码。我们一直在尝试制作一个基于Web的前端,让我们的开发人员在运行此脚本时

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


我们如何在没有用户干预的情况下,通过将密码存储/缓存到某个地方来自动化此过程?

从Digicert获得了一个答案:

不幸的是,EV代码签名证书的部分安全性在于每次都必须输入密码。 没有一种方法可以使它自动化

我曾经使用下面的脚本自动输入密码。我们一直在尝试制作一个基于Web的前端,让我们的开发人员在运行此脚本时将二进制文件发送到Windows box,以便可以对其进行签名和返回

  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证书之前)来自动签署Windows可执行文件(因为我们有一台Linux服务器,它为开发人员的方便和协作做了大量工作)。我已经联系了osslsigncode的开发人员,看看他是否可以使用DigiCert SafeNet令牌来帮助以不同的方式实现自动化,因为我可以在Linux上看到它们。他的回答带来了希望,但我不确定是否有任何进展,我无法花更多的时间来帮助他

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

我引用SAC文档(安装在
\ProgramFiles\SafeNet\Authentication\SAC\sachhelp.chm
,章节“
客户端设置”
,“
启用客户端登录”
”)如下:

启用单一登录时,用户可以访问多个应用程序 在每台计算机上只请求一次令牌密码 一场这减轻了用户登录到每个站点的需要 单独申请

要启用此默认禁用的功能,请转到SAC高级设置,并选中“启用单一登录”框:

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

否则,这里有一个小的C#控制台示例代码(相当于m1st0),允许您自动响应登录对话框(可能需要以管理员身份运行)(您需要从控制台项目中引用(
UIAutomationClient.dll
UIAutomationTypes.dll
):

使用系统;
使用System.Windows.Automation;
命名空间自动安全登录{
班级计划{
静态void Main(字符串[]参数){
满足您的密码请求(“您的令牌密码”);
}
静态void SatisfyEverySafeNetTokenPasswordRequest(字符串密码){
整数计数=0;
Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent、AutomationElement.RootElement、TreeScope.Children,(发件人,e)=>
{
var元素=发送方作为AutomationElement;
if(element.Current.Name==“令牌登录”){
WindowPattern=(WindowPattern)元素.GetCurrentPattern(WindowPattern.pattern);
WaitForInputIdle模式(10000);
var edit=element.FindFirst(TreeScope.substands,new和condition(
新属性条件(AutomationElement.ControlTypeProperty、ControlType.Edit),
新属性条件(AutomationElement.NameProperty,“令牌密码:”);
var ok=element.FindFirst(TreeScope.subjects,new和condition(
新属性条件(AutomationElement.ControlTypeProperty、ControlType.Button),
新属性条件(AutomationElement.NameProperty,“OK”);
if(编辑!=null&&ok!=null){
计数++;
ValuePattern vp=(ValuePattern)edit.GetCurrentPattern(ValuePattern.Pattern);
vp.SetValue(密码);
Console.WriteLine(“检测到SafeNet窗口(计数:“+count+”窗口)。正在设置密码…”);
InvokePattern ip=(InvokePattern)ok.GetCurrentPattern(InvokePattern.Pattern);
ip.Invoke();
}否则{
Console.WriteLine(“检测到安全网窗口,但未使用编辑和按钮…”);
}
}
});
做{
//按Q退出。。。
ConsoleKeyInfo k=Console.ReadKey(true);
if(k.Key==ConsoleKey.Q)
打破
}
虽然(正确);
Automation.RemoveAllEventHandlers();
}
}
}

如果您已经拥有EV证书,我的案例数据中心将免费为CI颁发标准(OV)证书


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

我是一个测试版工具,它将有助于自动化构建过程

这是客户端服务器windows应用程序。您可以在插入EV令牌的计算机上启动服务器。在服务器端应用程序启动时输入令牌的密码。之后,您可以远程签署文件。 客户端应用程序完全替换signtool.exe,因此您可以使用现有的生成脚本

源代码位于此处:

编辑:上半年,我们在构建服务器上全天候成功地使用此工具进行代码签名。一切正常。

安装 (可以使用管理c中的一个命令完成
choco feature enable -n=allowGlobalConfirmation
choco install python
pip install pypiwin32
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()
python disableAutoprompt.py
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;
    }
}
// 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();
}
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;
}
[]=name
[reader]=name
[{{password}}]=name
[reader{{password}}]=name
signtool sign /f mycert.cer /csp "eToken Base Cryptographic Provider" /k "[{{TokenPasswordHere}}]=KeyContainerNameHere" myfile.exe
"C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\signtool.exe" sign /fd sha256 /f "MyCertificate.cer" /csp "eToken Base Cryptographic Provider" /kc "[{{TokenPassword}}]=ContainerTame" "FileToSign"
Error information: "CryptExportPublicKeyInfoEx failed" (87/0x57)
"C:\Program Files (x86)\Microsoft SDKs\ClickOnce\SignTool\SignTool.exe" sign /tr http://timestamp.globalsign.com/scripts/timestamp.dll "MyFileToSign" 
Done Adding Additional Store
Successfully signed: MyFileToSign