Автоматизуйте підписання коду розширеної перевірки (EV)


82

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

Як ми можемо автоматизувати цей процес, без втручання користувача, зберігаючи / кешуючи пароль десь?


Питання " Наскільки безпечними є підказки щодо пароля для SafeNet eToken 5110 або подібних криптографічних апаратних токенів? " Дещо пов'язане, якщо воно коли-небудь отримає відповідь, воно повинно бути цікавим для тих, хто оцінює, чи слід автоматизувати введення пароля. Як я вже розумію, якщо хтось, хто на даний момент володіє цим чи подібним маркером, прочитає це, якщо ви спробуєте його "зламати" і відповісте на це запитання, це буде вдячне :)
gbr

до жаль , відповідь , який працював для мене , і отримує найбільшу кількість з'являються голосування в кінці списку відповідей, так що не втрачайте час і відразу перейти до Симона Мурье відповідає stackoverflow.com/a/26126701/27194
Патрік з команди NDepend

Просто зверніть увагу на спробу будь-якого з цих рішень. Апаратні маркери мають лічильник "Залишився спробу повторного введення пароля" (можна перевірити в клієнті автентифікації SafeNet). Під час експериментів переконайтесь, що він ніколи не досягає нуля зі зрозумілих причин. В іншому випадку ви, можливо, назавжди втратите доступ до апаратного маркера, і вам доведеться замовити новий! Навчився цього важким шляхом ...
Sundae

Відповідь Саймона, на жаль, більше не працює (див. Мій коментар до відповіді ). І відповідь Остіна не тільки спрацьовує, але в будь-якому разі є кращою.
Martin Prikryl

Відповіді:


64

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

Я цитую документ SAC (знайдений після встановлення у \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chmрозділі ` Client Settings`, ' Enabling Client Logon') тут:

Коли ввімкнено єдиний вхід, користувачі можуть отримувати доступ до декількох програм лише з одним запитом на пароль маркера протягом кожного сеансу комп'ютера. Це полегшує потребу користувача входити в кожну програму окремо.

Щоб увімкнути цю функцію, яка вимкнена за замовчуванням, перейдіть до розширених налаштувань SAC і встановіть прапорець "увімкнути єдиний вхід":

введіть тут опис зображення

Перезавантажте комп’ютер, і тепер він повинен лише один раз запитувати пароль маркера. У нашому випадку ми маємо підписувати більше 200 бінарних файлів за кожну збірку, тож це загальна необхідність .

В іншому випадку, ось невеликий зразок коду консолі C # (еквівалентний коду m1st0), який дозволяє вам автоматично відповідати на діалоги входу (можливо, потрібно запускати як адміністратор) (вам потрібно посилатися з вашого проекту консолі ( UIAutomationClient.dllі UIAutomationTypes.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

2
+1 за правильну відповідь. Мене вражає, коли люди розробляють сценарії для автоматизації введення користувачем та ін., Перемагаючи мету справді мати пароль, і все, що їм потрібно було знати, - це де цей варіант. Я сумніваюся, що ця опція коли-небудь зникне, оскільки емітенти розуміють, що розробники не можуть вводити пароль кожного разу, коли підписується двійковий файл.
діаста

3
Я можу підтвердити, що це працює від TeamCity (доки в службі TeamCity Windows встановлено прапорець "Дозволити службі взаємодіяти з робочим столом"). Нам також потрібно було запустити процес введення пароля в іншому потоці та вимкнути службу "Виявлення інтерактивних служб" на нашій машині побудови. Ми створили обгортку C # навколо signtool, яка виконувала підпис і обробляла введення пароля, як зазначено вище, все в автономному додатку. Я не можу повірити, скільки перешкод нам довелося подолати, щоб це працювало, але для тих, хто в цьому ж човні, зосередьтесь на методі C #, описаному вище ...
Алан Спарк,

1
FYI ... сертифікати Symantec EV також використовують SafeNet. Нам довелося створити незграбне рішення навколо цього процесу, але після прочитання вашої відповіді та реалізації консольного додатка це надзвичайно допомогло нашому процесу збірки. Дякую. Чудове рішення для погано архітектурного процесу підписання коду.
Золтан

1
Я вже деякий час успішно використовую це корисне рішення. Але зараз при налаштуванні на новій машині з Windows 10 Pro 2004 з клієнтом SafeNet 9.0.34 x64 для Windows 8 і новіших версій він більше не працює. Де запит нового пароля. Здається, це вбудований Windows, замість користувацького підказки SafeNet, як раніше. І вікно пароля нового підказки не є автоматичним (воно не відображається в AutomationElementдереві). Я не знаю, чи можна це якось обійти. Але відвідавши це запитання ще раз і знайшовши відповідь від @Austin, я вважаю, що це все-таки краще рішення.
Martin Prikryl

34

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

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

Імовірно, ви повинні вказати ім'я зчитувача, якщо у вас підключено більше одного зчитувача - оскільки у мене є лише один зчитувач, я не можу це підтвердити.

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
Лукаш Котен

Попередження. Це рішення може заблокувати ваш апаратний маркер, якщо ви введете неправильний пароль, навіть лише двічі! Якось пароль повторних спроб введення лічильника зменшився з 15 до 3 після того, як я виконав команду лише один раз із недійсним паролем. Здається, рішення "etokensign.exe" працює нормально, залишок лічильника паролів зменшився, як слід, з 15 до 14 після однієї невірної спроби введення пароля.
Sundae

1
Чудово, це ідеально для мене спрацювало з USB-ключем SafeNet, який прийшов до мене від Sectigo.
JacobJ

1
Наскільки мені відомо, цей синтаксис ніде не публічно документований. Я знайшов цю функцію шляхом зворотного проектування двійкового файлу драйвера в IDA Pro.
Остін Мортон,

1
Це краще, ніж інші відповіді, які вводять підроблені паролі в підказки графічного інтерфейсу. Серед іншого, це працює навіть у неінтерактивних сеансах Windows. А інші відповіді, здається, не працюють із найновішими інструментами .
Martin Prikryl

19

Розширюючи цю відповідь , це можна автоматизувати за допомогою 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;
}

Експорт сертифіката у файл:
Експорт сертифіката у файл

Назва контейнера із закритим ключем:
Назва контейнера із закритим ключем


2
Це має бути прийнятою відповіддю, це працює як шарм!
Шон

1
Це ідеально. Зокрема, після того, як я зрозумів, що цим інструментом мені потрібно лише підписати якийсь фіктивний файл. Якщо ввімкнено "єдиний вхід" (драйвер SafeNet), усі наступні кроки працюють зі стандартним signtool. Це дуже корисно для підписання аддінів Office (VSTO), які використовують інший інструмент, а також означало, що для мого сценарію / процесу збірки були потрібні лише мінімальні зміни.
Stefan Egli,

Ця відповідь є гарним доповненням до тієї, яку надав Авжаткін. На даний момент код близький до заміни signtools.exe. Ця програма повинна підтримувати перехресне підписання. На щастя, є ще одна публікація SO, яка тепер має виконувати перехресне підписання .
sdc

Ця відповідь у підсумку допомогла мені найбільше. Я пропустив зовнішнє посилання під час створення VS2017, але додаючи деякі коментарі до прагми, як запропоновано тут, мені вдалося змусити Bamboo (CI / CD від Atlassian) підписати.
HTBR

11

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

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

Вихідний код знаходиться тут: https://github.com/SirAlex/RemoteSignTool

Редагувати: Ми успішно використовували цей інструмент для підписання коду протягом останнього півріччя 24x7 на нашому сервері збірки. Все працює нормально.


1
Наскільки безпечний такий підхід? Чи не означає це, що кожен, хто може підключитися до вашого сервера підписання за допомогою HTTP, може підписати будь-який двійковий файл, який хоче, вашим сертифікатом EV?
Ген Павловський,

6

Насправді в Windows ви можете повністю програмно вказати маркерний пароль. Це можна зробити, створивши контекст ( CryptAcquireContext ) із прапором CRYPT_SILENT, використовуючи ім'я маркера у формі "\\. \ AKS ifdh 0" або ім'я контейнера маркерів, яке є деяким керівництвом, видимим у властивостях сертифікації в програмі Authentication Client. Потім вам потрібно використовувати 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;
    }
}

1
Цікаво. Здається багатообіцяючим, вам слід докласти ІМХО цього (вдосконалити пояснення, надати код тощо)
Саймон Мур'є

Будь ласка, опублікуйте повний приклад. Це звучить дійсно корисні
dten

дякую за додаткові деталі. це керівництво, про яке ви згадуєте? read.pudn.com/downloads128/ebook/549477/eToken_SDK_3_50[1].pdf
dten

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

Думаю, ви викликаєте цю функцію OpenToken (L "\\\\. \\ AKS ifdh 0", <маркерний пароль>) ... ну вона у мене спрацювала!
Michael Haephrati

5

Я використовував 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. Його відповідь дала надію, але я не впевнений у будь-якому прогресі, і я не міг приділити більше часу, щоб допомогти


Дивіться іншу відповідь. Існує можливість розблокувати лише один раз за сеанс, що достатньо для більшості користувачів.
діаста

5

signtool.exe sign / fd sha256 / f "sign.cer" / csp "eToken Base Cryptographic Provider" / kc "[{{token password here}}] = Назва контейнера тут" "ConsoleApp1.exe"

Використовуйте Microsoft Windows SDK 10 для signtool


1
Чудово! Цей однокласний лайнер, безумовно, принижує всі інші відповіді (хоча спочатку я був здивований, як зрозуміти моє "ім'я контейнера", поки не знайшов інструкцій у відповіді draketb вище).
Dan Z

5

Встановіть https://chocolatey.org/docs/installation (Це можна зробити за допомогою однієї команди з адміністративного командного рядка)

(Перезапустити командний рядок)

Пригнічуйте постійні підказки 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 () не працюватимуть. / ЗДИХАННЯ. Спасибі Microsoft. Ось чому ми не можемо мати приємних речей.
Джон Роча

2

Отримав відповідь від Digicert:

На жаль, частина безпеки сертифіката підпису коду EV полягає в тому, що ви повинні вводити пароль щоразу. Немає способу його автоматизувати.


Ми отримали однакову відповідь, хоча вони шукають рішення, у якого немає часових рамок, коли воно може бути доступним. Вони знають про цю публікацію SO, хоча, сподіваємось, вони зрозуміють, наскільки це проблема.
Алан Спарк

Ми знайшли шлях навколо нього: github.com/mareklinka/SafeNetTokenSigner/issues/8
gunslingor

2

У моєму випадку Digicert видає стандартний (OV) сертифікат для CI безкоштовно, якщо у вас вже є сертифікат EV.

Я знаю, що це не рішення, але якщо ви не можете помістити маркер на сервер (хмарний сервер), це шлях.


1

Я роблю це так:

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

Дивіться цю статтю, яку я писав .


1

Я використовую сертифікат глобального знаку, і вони чудово сказали те саме.

Неможливо написати підпис за допомогою стандартного підпису коду 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 build, і немає необхідності активного входу в обліковий запис в агенті teambuild build.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.