Автоматизировать расширенную проверку кода (EV)

Недавно мы приобрели сертификат подписи кода DigiCert EV. Мы можем подписать.exe файлы, используя signtool.exe. Однако каждый раз, когда мы подписываем файл, он запрашивает пароль SafeNet eToken.

Как мы можем автоматизировать этот процесс без вмешательства пользователя, храня / кэшируя пароль где-то?

15 ответов

Решение

Получил ответ от Digicert:

К сожалению, часть безопасности с сертификатом подписи кода EV заключается в том, что вы должны вводить пароль каждый раз. Нет способа автоматизировать это.

Расширяя ответы уже в этой теме, можно предоставить пароль токена, используя стандартную программу signtool от Microsoft.

1. Экспортируйте ваш публичный сертификат в файл из SafeNet Client.

2. Найдите имя контейнера вашего личного ключа

3. Найдите свое имя читателя

4. Отформатируйте все вместе

EToken CSP имеет скрытую (или, по крайней мере, не очень широко разрекламированную) функциональность для анализа пароля токена из имени контейнера.

Формат один из следующих

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

Куда:

  • reader имя читателя из пользовательского интерфейса SafeNet Client
  • password ваш пароль токена
  • name имя контейнера из пользовательского интерфейса SafeNet Client

Предположительно, вы должны указать имя читателя, если у вас есть более одного подключенного считывателя - так как у меня только один читатель, я не могу подтвердить это.

5. Передайте информацию в signtool

  • /f certfile.cer
  • /csp "eToken Base Cryptographic Provider"
  • /k "<value from step 4>"
  • любые другие необходимые вам флаги

Пример команды signtool выглядит следующим образом

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

Некоторые изображения взяты из этого ответа: /questions/30680091/avtomatizirovat-rasshirennuyu-proverku-koda-ev/30680095#30680095

Невозможно обойти диалог входа в систему AFAIK, но вы можете настроить клиент аутентификации SafeNet таким образом, чтобы он запрашивал его только один раз за сеанс входа в систему.

Я цитирую документацию SAC (найдена однажды установленной в \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chmГлава 'Client Settings','Enabling Client Logon') Вот:

Если включен единый вход в систему, пользователи могут получать доступ к нескольким приложениям только с одним запросом пароля токена во время каждого сеанса компьютера. Это избавляет пользователя от необходимости входить в каждое приложение отдельно.

Чтобы включить эту функцию, которая по умолчанию отключена, перейдите к дополнительным настройкам SAC и установите флажок "включить единый вход":

Перезагрузите компьютер, и теперь он должен только один раз запросить пароль токена. В нашем случае у нас есть более 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();
    }

Развернув этот ответ, это можно автоматизировать с помощью CryptAcquireContext и CryptSetProvParam для программного ввода PIN-кода токена и CryptUIWizDigitalSign для программного подписания. Я создал консольное приложение (код ниже), которое принимает в качестве входных данных файл сертификата (экспортируется путем щелчка правой кнопкой мыши по сертификату в клиенте аутентификации 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;
}

Экспорт сертификата в файл:
Экспорт сертификата в файл

Имя контейнера закрытого ключа:
Имя контейнера с закрытым ключом

signtool.exe sign /fd sha256 /f "signature.cer" /csp "eToken Base Cryptographic Provider" /kc "[{{пароль токена здесь}}]= имя контейнера здесь" "ConsoleApp1.exe"

Используйте Microsoft Windows SDK 10 для signtool

Я сделал бета-инструмент, который поможет автоматизировать процесс сборки.

Это клиент-серверное приложение Windows. Вы можете запустить сервер на компьютере, где установлен EV-токен. Введите пароль для токена при запуске приложения на стороне сервера. После этого вы можете подписывать файлы удаленно. Клиентское приложение полностью заменяет signtool.exe, поэтому вы можете использовать существующие сценарии сборки.

Исходный код находится здесь: https://github.com/SirAlex/RemoteSignTool

Изменить: Мы успешно использовали этот инструмент для подписи кода в течение последнего полугодия 24x7 на нашем сервере сборки. Все работает отлично.

На самом деле в Windows вы можете указать пароль токена полностью программно. Это может быть сделано путем создания контекста ( CryptAcquireContext) с флагом CRYPT_SILENT с использованием имени токена в форме "\\.\AKS ifdh 0" или имени контейнера токена, который является некоторой подсказкой, видимой в определенных свойствах в приложении клиента аутентификации. Затем вам нужно использовать CryptSetProvParam с параметром PP_SIGNATURE_PIN, чтобы указать свой пароль токена. После этого процесс может использовать сертификаты на этом токене для подписи файлов.
Примечание: как только вы создаете контекст, похоже, он полностью работает для текущего процесса, нет необходимости передавать его другим функциям 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;
    }
}

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. Вам необходимо отключить его в настройках сервера и настроить автологин.

Я использовал AutoHotKey для автоматизации ввода пароля, используя следующий скрипт. Мы пытались сделать веб-интерфейс для наших разработчиков, чтобы он отправлял двоичные файлы в коробку 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}
    }   
  } 

Я должен отметить, что то, чем я поделился, не совсем небезопасно, но мы также решили эту проблему, потребовав либо приобрести ключи подписи для каждого разработчика, либо назначить работу менеджера по подписи, который утвердил бы подпись выпущенного программного обеспечения. Я считаю, что это лучшие, безопасные процессы - что когда вещи проходят проверку качества и утверждаются к выпуску, они могут быть официально подписаны. Тем не менее, потребности небольших компаний могут диктовать, что это может быть сделано другим автоматическим способом.

Первоначально я использовал osslsigncode в Linux (до EV-сертификатов) для автоматизации подписывания исполняемых файлов Windows (поскольку у нас был сервер Linux, выполняющий большую работу для облегчения работы разработчиков и совместной работы). Я связался с разработчиком osslsigncode, чтобы узнать, сможет ли он использовать токены DigiCert SafeNet, чтобы помочь автоматизировать их по-другому, поскольку я вижу их в Linux. Его ответ дал надежду, но я не уверен в каком-либо прогрессе, и я не мог посвятить больше времени, чтобы помочь

В моем случае Digicert выдает сертификат Standard (OV) для CI бесплатно, если у вас уже есть сертификат EV.

Я знаю, что это не решение, но если вы не можете поместить токен на сервер (облачный сервер), то это путь.

Я использую сертификат globalsign, и они красиво сказали то же самое.

Невозможно записать подпись со стандартной подписью кода EV, они продвигают использование платформы HSM.

... что намного превышает мой бюджет. Вопреки тому, что они сказали, мне удалось заставить его работать:

      "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

Это работает в сборке teamcity, и нет необходимости в активной учетной записи, входящей в агент сборки teamcity.

Изменить : это решение больше не работает для меня, globalsign изменил URL-адрес временной метки на http://rfc3161timestamp.globalsign.com/advanced . С тех пор я больше не могу подписаться с помощью TokenPassword / ContainerName. Единственное решение, которое я нашел, - это невозможность единого входа в систему и обеспечение того, чтобы сервер не выходил из системы (я запускаю видео на сервере, чтобы моя учетная запись не выходила из системы автоматически). это быстрое и грязное решение, но единственное, что я нашел. Спасибо, globalsign, за вашу плохую поддержку.

Я делаю это так:

  1. Откройте токен

    PCCERT_CONTEXT cert = OpenToken(SAFENET_TOKEN, EV_PASS);

  2. Подпишите файл с помощью токена, корневых / перекрестных сертификатов, когда это необходимо, и сертификата EV, загруженного в память.

    HRESULT hr = SignAppxPackage (сертификат, 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. По этой причине добавьте в свой код проверку, подключен ли пользователь к Интернету.

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;
}

Смотрите эту статью, которую я написал.

работает как шарм , но это очень важно отметить , что это требует версий уточненного из.

С устаревшей версией (моя была с 2016 года) я получил ошибку

      Error information: "CryptExportPublicKeyInfoEx failed" (87/0x57)

Вы можете получить последнюю версию, установив Windows SDK. По крайней мере, на момент написания этой статьи версия, поставляемая с SDK, поддерживает .

Требование актуального signtool.exeбыл отмечен в одном из комментариев к Описываются Остин Мортон методметоду, описанному Остином Мортоном,метод, описанный Остином Мортоном но мне потребовалось время, чтобы его найти. Может быть, это поможет кому-то быстрее найти проблему.

Нам удалось автоматизировать как подписание кода Windows EV, так и нотариальное заверение Apple на одном Mac mini с помощью install4j.

  • всплывающее окно с PIN-кодом
  • Сертификат подписи кода Sectigo EV
  • SafeNet eToken 5110

Если install4j может это сделать, то должен быть способ и для вашего стека технологий.

Наше облачное решение eSigner для подписи сертификатов EV Code Signing на SSL.com устраняет необходимость в аппаратных токенах и значительно упрощает автоматическую подпись (например, CI/CD) с использованием сертификатов EV Code Signing.

Подробнее см. здесь https://www.ssl.com/how-to/automate-esigner-ev-code-signing/

Демонстрационную службу подписи также можно найти по адресу https://try.esigner.com .

Другие вопросы по тегам