Automatisieren Sie die Signatur des EV-Codes (Extended Validation)


79

Wir haben kürzlich ein DigiCert EV-Codesignaturzertifikat gekauft. Wir können EXE-Dateien mit signtool.exe signieren. Jedes Mal, wenn wir eine Datei signieren, werden Sie zur Eingabe des SafeNet eToken-Kennworts aufgefordert.

Wie können wir diesen Prozess ohne Benutzereingriff automatisieren, indem wir das Passwort irgendwo speichern / zwischenspeichern?


Die Frage " Wie sicher sind die Kennwortabfragen des SafeNet eToken 5110 oder ähnlicher kryptografischer Hardwaretoken? " Ist etwas verwandt. Sollte sie jemals eine Antwort erhalten, sollte sie für diejenigen von Interesse sein, die beurteilen, ob die Kennworteingabe automatisiert werden soll. Wenn jemand, der dieses oder ein ähnliches Token besitzt, dies liest, wenn Sie versuchen können, es zu "hacken" und diese Frage zu beantworten, wäre dies sehr dankbar :)
gbr

Leider wird die Antwort, die für mich funktioniert hat und die die meisten Stimmen erhält, am Ende der Antwortliste
angezeigt.

Nur ein Kopf hoch, bevor Sie eine dieser Lösungen ausprobieren. Hardware-Token haben den Zähler "Token-Kennwortwiederholungen verbleiben" (kann im SafeNet-Authentifizierungsclient überprüft werden). Stellen Sie beim Experimentieren sicher, dass es aus offensichtlichen Gründen niemals Null erreicht. Andernfalls wird Ihr Hardware-Token wahrscheinlich dauerhaft gesperrt und Sie müssen einen neuen bestellen! Habe das auf die harte
Tour

Die Antwort von Simon funktioniert leider nicht mehr (siehe meinen Kommentar zur Antwort ). Und die Antwort von Austin funktioniert nicht nur, sondern ist auch imo besser.
Martin Prikryl

Antworten:


64

Es gibt keine Möglichkeit, den Anmeldedialog AFAIK zu umgehen. Sie können den SafeNet-Authentifizierungsclient jedoch so konfigurieren, dass er nur einmal pro Anmeldesitzung abgefragt wird.

Ich zitiere das SAC-Dokument (gefunden nach der Installation in \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chmKapitel ' Client Settings', ' Enabling Client Logon') hier:

Wenn die Einzelanmeldung aktiviert ist, können Benutzer während jeder Computersitzung mit nur einer Anforderung für das Token-Kennwort auf mehrere Anwendungen zugreifen. Dies verringert die Notwendigkeit für den Benutzer, sich bei jeder Anwendung separat anzumelden.

Um diese standardmäßig deaktivierte Funktion zu aktivieren, gehen Sie zu den erweiterten SAC-Einstellungen und aktivieren Sie das Kontrollkästchen "Einzelanmeldung aktivieren":

Geben Sie hier die Bildbeschreibung ein

Starten Sie Ihren Computer neu und er sollte nur noch einmal zur Eingabe des Token-Kennworts auffordern. In unserem Fall müssen pro Build mehr als 200 Binärdateien signiert werden. Dies ist also ein absolutes Muss .

Andernfalls finden Sie hier einen kleinen C # -Konsolen-Beispielcode (entspricht m1st0), mit dem Sie automatisch auf Anmeldedialoge reagieren können (muss wahrscheinlich als Administrator ausgeführt werden) (Sie müssen in Ihrem Konsolenprojekt auf ( UIAutomationClient.dllund UIAutomationTypes.dll) verweisen :

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
Dies ist vielleicht keine offizielle Antwort von DigiCert, aber ihre Antwort ist scheiße, und diese ist fantastisch! Danke für die Hilfe!
Lordjeb

2
+1 für eine korrekte Antwort. Es überrascht mich zu sehen, wie Leute Skripte entwickeln, um Benutzereingaben und dergleichen zu automatisieren, und den Zweck, wirklich ein Passwort zu haben, zunichte machen. Alles, was sie wissen mussten, war, wo diese Option war. Ich bezweifle, dass diese Option jemals verschwinden wird, da die Emittenten verstehen, dass Entwickler das Kennwort nicht jedes Mal eingeben können, wenn eine Binärdatei signiert wird.
Dyasta

3
Ich kann von TeamCity aus bestätigen, dass dies funktioniert (solange für den TeamCity-Windows-Dienst das Kontrollkästchen "Dienst mit Desktop interagieren lassen" aktiviert ist). Wir mussten auch den Passworteingabeprozess in einem anderen Thread ausführen und den Dienst "Interactive Services Detection" auf unserer Build-Maschine deaktivieren. Wir haben einen C # -Wrapper um signtool erstellt, der die Signierung durchgeführt und die Passworteingabe wie oben beschrieben in einer eigenständigen App behandelt hat. Ich kann nicht glauben, wie viele Hürden wir überwinden mussten, um dies zum Laufen zu bringen, aber für alle anderen im selben Boot sollten Sie sich auf die oben beschriebene C # -Methode konzentrieren ...
Alan Spark

1
Zu Ihrer Information ... die Symantec EV Certs verwenden auch SafeNet. Wir mussten eine umständliche Lösung für diesen Prozess entwickeln, aber nachdem wir Ihre Antwort gelesen und die Konsolen-App implementiert haben, hat dies unseren Erstellungsprozess immens unterstützt. Vielen Dank. Tolle Lösung für einen schlecht strukturierten Codesignaturprozess.
Zoltan

1
Ich benutze diese nützliche Lösung seit einiger Zeit erfolgreich. Aber jetzt, wenn es auf einem neuen Computer mit Windows 10 Pro 2004 mit SafeNet Client 9.0.34 x64 für Windows 8 und höher eingerichtet wird, funktioniert es nicht mehr. Wo ist eine neue Passwortabfrage. Es scheint sich um eine integrierte Windows-Eingabeaufforderung zu handeln, anstatt wie zuvor um die benutzerdefinierte SafeNet-Eingabeaufforderung. Das Kennwortfeld der neuen Eingabeaufforderung ist nicht automatisierbar (es wird nicht im AutomationElementBaum angezeigt ). Ich weiß nicht, ob es irgendwie umgangen werden kann. Aber wenn ich diese Frage noch einmal besuche und die Antwort von @Austin finde, glaube ich, dass es sowieso eine bessere Lösung ist.
Martin Prikryl

31

Wenn Sie die Antworten in diesem Thread erweitern, können Sie das Token-Kennwort mit dem Standardprogramm signtool von Microsoft bereitstellen.

0. Öffnen Sie den SafeNet-Client in der erweiterten Ansicht

Die Installationspfade können variieren, aber für mich wird der SafeNet-Client installiert für: C:\Program Files\SafeNet\Authentication\SAC\x64\SACTools.exe

Klicken Sie auf das Zahnradsymbol oben rechts, um die "erweiterte Ansicht" zu öffnen. Erweiterte SafeNet-Ansicht

1. Exportieren Sie Ihr öffentliches Zertifikat vom SafeNet-Client in eine Datei Exportieren des Zertifikats in eine Datei

2. Suchen Sie den Namen Ihres privaten Schlüsselcontainers
Name des privaten Schlüsselcontainers

3. Finden Sie Ihren Lesernamen Lesername

4. Formatieren Sie alles zusammen

Der eToken CSP verfügt über versteckte (oder zumindest nicht weit verbreitete) Funktionen zum Parsen des Token-Passworts aus dem Containernamen.

Das Format ist eines der folgenden

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

Wo:

  • reader ist der "Lesername" auf der SafeNet-Client-Benutzeroberfläche
  • password ist Ihr Token-Passwort
  • name ist der "Containername" in der SafeNet-Client-Benutzeroberfläche

Vermutlich müssen Sie den Namen des Lesers angeben, wenn Sie mehr als einen Leser angeschlossen haben - da ich nur einen Leser habe, kann ich dies nicht bestätigen.

5. Übergeben Sie die Informationen an signtool

  • /f certfile.cer
  • /csp "eToken Base Cryptographic Provider"
  • /k "<value from step 4>"
  • Alle anderen Signtool-Flags, die Sie benötigen

Beispiel signtool Befehl wie folgt

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

Einige Bilder aus dieser Antwort: https://stackoverflow.com/a/47894907/5420193


2
Funktioniert super, leider fand ich das nach zwei Tagen Graben und Implementieren eines eigenen "Signtools": D thx
Lukáš Koten

Warnung: Diese Lösung kann Sie von Ihrem Hardware-Token sperren, wenn Sie das falsche Passwort eingeben, sogar nur zweimal! Irgendwie verringerte sich der Zähler für die verbleibenden Kennwortwiederholungen von 15 auf 3, nachdem ich den Befehl nur einmal mit einem ungültigen Kennwort ausgeführt hatte. Die Lösung "etokensign.exe" scheint jedoch in Ordnung zu sein. Der verbleibende Kennwortzähler wurde nach einem ungültigen Kennwortversuch von 15 auf 14 verringert.
Eisbecher

1
Wunderbar, das hat bei mir mit dem SafeNet USB-Dongle, der von Sectigo zu mir kam, perfekt funktioniert.
JacobJ

1
Soweit ich weiß, ist diese Syntax nirgendwo öffentlich dokumentiert. Ich habe diese Funktionalität durch Reverse Engineering der Treiberbinärdatei in IDA Pro gefunden.
Austin Morton

1
Dies ist besser als die anderen Antworten, die gefälschte Passworteingaben in GUI-Eingabeaufforderungen enthalten. Dies funktioniert unter anderem auch in nicht interaktiven Windows-Sitzungen. Und die anderen Antworten scheinen nicht mit den neuesten Tools zu funktionieren .
Martin Prikryl

18

Wenn Sie diese Antwort erweitern , kann dies mithilfe von CryptAcquireContext und CryptSetProvParam automatisiert werden , um die Token-PIN programmgesteuert und CryptUIWizDigitalSign einzugeben , , um die Signatur programmgesteuert durchzuführen. Ich habe eine Konsolen-App (Code unten) erstellt, die als Eingabe die Zertifikatdatei (exportiert durch Klicken mit der rechten Maustaste auf das Zertifikat im SafeNet Authentication Client und Auswahl von "Exportieren ..."), den Namen des privaten Schlüsselcontainers (im SafeNet Authentication Client) verwendet. die Token-PIN, die Zeitstempel-URL und den Pfad der zu signierenden Datei. Diese Konsolen-App funktionierte, wenn sie vom TeamCity-Build-Agenten aufgerufen wurde, mit dem das USB-Token verbunden war.

Anwendungsbeispiel:
etokensign.exe c:\CodeSigning.cert CONTAINER PIN http://timestamp.digicert.com C:\program.exe

Code:

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

Exportieren des Zertifikats in eine Datei:
Exportieren des Zertifikats in eine Datei

Name des privaten Schlüsselcontainers:
Name des privaten Schlüsselcontainers


2
Dies sollte die akzeptierte Antwort sein, es funktioniert wie ein Zauber!
Shawn

1
Dies ist perfekt. Insbesondere nachdem mir klar wurde, dass ich mit diesem Tool nur eine Dummy-Datei signieren muss. Wenn die "Einzelanmeldung" aktiviert ist (SafeNet-Treiber), funktionieren alle nachfolgenden Schritte mit dem Standard-Signtool. Dies ist sehr nützlich für das Signieren von Office Addins (VSTO), die ein anderes Tool verwenden, und bedeutet auch, dass für mein Build-Skript / meinen Build-Prozess nur minimale Änderungen erforderlich waren.
Stefan Egli

Diese Antwort ist eine gute Ergänzung zu der von avzhatkin. Zu diesem Zeitpunkt steht der Code kurz vor dem Ersetzen von signtools.exe. Dieses Programm müsste Cross-Signing unterstützen. Glücklicherweise gibt es jetzt einen weiteren SO-Beitrag, der Cross-Signing durchführen kann .
SDC

Diese Antwort hat mir am meisten geholfen. Ich habe beim Erstellen von VS2017 eine externe Referenz verpasst, aber als ich einige Pragma-Kommentare hinzufügte, wie hier vorgeschlagen , gelang es mir, Bamboo (CI / CD von Atlassian) zum Signieren zu bringen.
HTBR

11

Ich bin ein Beta-Tool, das dabei hilft, den Build-Prozess zu automatisieren.

Es ist eine Client-Server-Windows-Anwendung. Sie können den Server auf einem Computer starten, auf dem ein EV-Token eingefügt ist. Geben Sie beim Start der serverseitigen Anwendung das Kennwort für das Token ein. Danach können Sie Dateien remote signieren. Die clientseitige Anwendung ersetzt signtool.exe vollständig, sodass Sie vorhandene Build-Skripte verwenden können.

Der Quellcode befindet sich hier: https://github.com/SirAlex/RemoteSignTool

Bearbeiten: Wir haben dieses Tool erfolgreich für die Codesignatur im letzten Halbjahr rund um die Uhr auf unserem Build-Server verwendet. Alles funktioniert gut.


1
Wie sicher ist dieser Ansatz? Bedeutet das nicht, dass jeder, der über HTTP eine Verbindung zu Ihrem Signaturserver herstellen kann, jede gewünschte Binärdatei mit Ihrem EV-Zertifikat signieren kann?
Gene Pavlovsky

6

Tatsächlich können Sie unter Windows das Token-Passwort vollständig programmgesteuert angeben. Dies kann durch Erstellen eines Kontexts ( CryptAcquireContext ) mit dem Flag CRYPT_SILENT unter Verwendung des Token-Namens in der Form "\\. \ AKS ifdh 0" oder des Token-Containernamens erfolgen, der in den Zertifikateigenschaften in der Authentication Client-Anwendung angezeigt wird. Anschließend müssen Sie CryptSetProvParam mit dem Parameter PP_SIGNATURE_PIN verwenden, um Ihr Token-Kennwort anzugeben. Danach kann der Prozess Zertifikate für dieses Token verwenden, um Dateien zu signieren.
Hinweis: Sobald Sie den Kontext erstellt haben, scheint er nur für den aktuellen Prozess vollständig zu funktionieren. Sie müssen ihn nicht mehr an andere Crypto API-Funktionen oder ähnliches übergeben. Sie können jedoch gerne einen Kommentar abgeben, wenn Sie eine Situation finden, in der weitere Anstrengungen erforderlich sind.
Bearbeiten: Codebeispiel hinzugefügt

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
Interessant. Scheint vielversprechend, sollten Sie meiner Meinung nach näher darauf eingehen (Erklärung verbessern, Code bereitstellen usw.)
Simon Mourier

Bitte posten Sie ein vollständiges Beispiel. Das klingt wirklich nützlich
dten

danke für die extra details. Ist dies der Leitfaden, den Sie erwähnen? read.pudn.com/downloads128/ebook/549477/eToken_SDK_3_50[1‹.pdf
dten

Ich glaube, es ist nicht die genaue Version, die ich hatte, aber es scheint ähnliche Informationen über das Erstellen von Kontext und das Bereitstellen der PIN zu enthalten, allerdings für verschiedene Verwendungsszenarien.
Avzhatkin

Ich denke, Sie nennen diese Funktion OpenToken (L "\\\\. \\ AKS ifdh 0", <Token-Kennwort>) ... nun, es hat bei mir funktioniert!
Michael Haephrati

5

Ich habe AutoHotKey verwendet, um die Passworteingabe mithilfe des folgenden Skripts zu automatisieren. Wir haben versucht, ein webbasiertes Frontend für unsere Entwickler zu erstellen, um die Binärdateien mit diesem Skript an die Windows-Box zu senden, damit sie signiert und zurückgegeben werden können.

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

Ich muss beachten, dass das, was ich geteilt habe, nicht vollständig nicht sicher ist, aber wir sind auch auf dieses Problem gestoßen, bei dem entweder Signaturschlüssel für jeden Entwickler gekauft oder ein Job eines Signaturmanagers zugewiesen werden muss, der die Signatur der freigegebenen Software genehmigt. Ich glaube, das sind die besseren und sichereren Prozesse - sobald die Dinge die Qualitätssicherung bestanden und zur Freigabe freigegeben wurden, können sie offiziell unterzeichnet werden. Kleinere Unternehmensanforderungen können jedoch vorschreiben, dass dies auf eine andere automatisierte Weise erfolgt.

Ich habe ursprünglich Osslsigncode unter Linux (vor EV-Zertifikaten) verwendet, um das Signieren von ausführbaren Windows-Dateien zu automatisieren (da wir einen Linux-Server hatten, der viel Arbeit für die Benutzerfreundlichkeit und Zusammenarbeit leistete). Ich habe den Entwickler von osslsigncode kontaktiert, um zu prüfen, ob er die DigiCert SafeNet-Token verwenden kann, um sie auf andere Weise zu automatisieren, da ich sie unter Linux sehen kann. Seine Antwort gab Hoffnung, aber ich bin mir über keine Fortschritte sicher und konnte nicht mehr Zeit für Hilfe aufwenden


Siehe die andere Antwort. Es besteht die Möglichkeit, nur einmal pro Sitzung zu entsperren, was für die meisten Benutzer ausreicht.
Dyasta

5

Installieren https://chocolatey.org/docs/installation (kann mit einem Befehl an der administrativen Eingabeaufforderung ausgeführt werden)

(Eingabeaufforderung neu starten)

Unterdrücken Sie die ständige Aufforderung von choco für jede Installation:

choco feature enable -n=allowGlobalConfirmation

Installieren Sie Python mit dem folgenden Befehl:

choco install python

(Eingabeaufforderung neu starten) Installieren Sie ein zusätzliches Python-Modul:

pip install pypiwin32

Speichern Sie folgenden Text in 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()

Speichern Sie Ihr Passwort in passwd.txt und führen Sie es anschließend aus

python disableAutoprompt.py

Aus SafeNet Authentication Client- Konfiguration> Client Settings> Advanced>Enable Single Log On Option aktiviert werden kann Menge Kennwortabfragen zu minimieren, aber es sie nicht vollständig deaktivieren (Getestet auf Version 10.4.26.0)

Die C # -Anwendung (z. B. https://github.com/ganl/safenetpass ) funktioniert nicht mit aktiviertem Sperrbildschirm, aber mit diesem Python-Skript.


Dieses Skript ist fantastisch und ich konnte es problemlos an meine Bedürfnisse anpassen, indem ich mit einem Yubikey-Dongle arbeitete. Windows 10 bricht es jedoch. Windows 10 wechselt zu XAML und daher funktionieren die Funktionen win32gui.xxxx () nicht. /SEUFZER. Vielen Dank an Microsoft. Deshalb können wir keine schönen Dinge haben.
John Rocha

3

signtool.exe sign / fd sha256 / f "signieren.cer" / csp "eToken Base Cryptographic Provider" / kc "[{{Token-Passwort hier}}] = Containername hier" "ConsoleApp1.exe"

Verwenden Sie Microsoft Windows SDK 10 für signtool


1
Hervorragend! Dieser Einzeiler beschämt sicherlich alle anderen Antworten (obwohl ich zuerst verwirrt war, wie ich meinen "Containernamen" herausfinden sollte, bis ich die Anweisungen in Draketbs Antwort oben fand).
Dan Z

2

Ich habe eine Antwort von Digicert erhalten:

Leider besteht ein Teil der Sicherheit mit dem EV-Codesignaturzertifikat darin, dass Sie das Passwort jedes Mal eingeben müssen. Es gibt keine Möglichkeit, dies zu automatisieren.


Wir haben die gleiche Antwort erhalten, obwohl sie nach einer Lösung suchen, für die sie keinen Zeitrahmen haben, wann eine verfügbar sein könnte. Sie sind sich dieses SO-Beitrags bewusst, aber hoffentlich werden sie erkennen, wie groß das Problem ist.
Alan Spark

Wir haben einen Weg gefunden, um das zu umgehen
Revolverheld

2

In meinem Fall stellt Digicert ein Standardzertifikat (OV) für das CI kostenlos aus, wenn Sie bereits über ein EV-Zertifikat verfügen.

Ich weiß, dass dies nicht die Lösung ist, aber wenn Sie das Token nicht auf dem Server (einem Cloud-Server) ablegen können, ist dies der richtige Weg.


1

Ich mache es so:

  1. Öffnen Sie den Token

    PCCERT_CONTEXT cert = OpenToken (SAFENET_TOKEN, EV_PASS);

  2. Signieren Sie die Datei bei Bedarf mit dem Token, den Root- / Cross-Zertifikaten und dem in den Speicher geladenen EV-Zertifikat.

    HRESULT hr = SignAppxPackage (cert, FILETOSIGN);

Verwenden von SignerSignEx2 ():

Die Datei wird mit SignerSignEx2 () signiert, das mit LoadLibrary () und GetProcAddress () in den Speicher geladen werden muss:

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

Zeitstempeln

Außerdem müssen Sie Ihre signierte Datei mit einem Zeitstempel versehen und dies mit einer Zeitstempelberechtigung tun, mit der Sie eine Verbindung herstellen.

Dies erfolgt durch sicheres Überprüfen eines Zeitstempelservers über eine URL auf das aktuelle Datum und die aktuelle Uhrzeit. Jede Signaturbehörde verfügt über einen eigenen Zeitstempelserver. Das Zeitstempeln ist ein zusätzlicher Schritt im Code-Signierprozess. Wenn es jedoch um das Signieren von EV-Codes geht, ist dies eine Anforderung, die dem signierten PE eine zusätzliche Sicherheitsebene hinzufügt. Fügen Sie aus diesem Grund Ihrem Code eine Überprüfung hinzu, ob der Benutzer mit dem Internet verbunden ist.

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

Laden eines Zertifikats aus einer Datei

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

Laden eines Zertifikats in den Speicher

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, "");
}

Nachdem das Zertifikat nach dem Zugriff auf das Hardware-Token geladen wurde, laden wir es:

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

Schließlich erfolgt die Signierung in der folgenden Funktion:

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

Siehe diesen Artikel, den ich geschrieben habe .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.