40

Nous avons récemment acheté un certificat de signature de code DigiCert EV. Nous sommes en mesure de signer des fichiers .exe en utilisant signtool.exe. Cependant, chaque fois que nous signons un fichier, il demande le mot de passe SafeNet eToken.Automatiser la signature de code Extended Validation (EV)

Comment pouvons-nous automatiser ce processus, sans intervention de l'utilisateur, en stockant/en cache le mot de passe quelque part?

+0

La question « [? Comment la sécurité sont les mots de passe des invites SafeNet eToken 5110 ou similaires jetons matériels cryptographiques] (https://security.stackexchange.com/questions/159645/ how-safe-the-password-invites-of-the-safenet-etoken-5110-or-similar-cryptogr) "est quelque peu lié, si jamais il obtient une réponse, il devrait être d'intérêt pour ceux qui évaluent si automatiser l'entrée du mot de passe.Comme je suis là, si quelqu'un qui possède actuellement cela ou un jeton similaire lit ceci, si vous pouvez essayer de "pirater" et répondre à cette question, il serait grandement apprécié :) – gbr

Répondre

4

obtenu une réponse de Digicert:

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

+0

Nous avons eu la même réponse, bien qu'ils soient En regardant dans une solution, ils n'ont pas de calendrier pour quand un pourrait être disponible. Ils sont conscients de ce poste, même si, espérons-le, ils se rendront compte à quel point il s'agit d'un problème. –

3

je AutoHotKey pour automatiser l'entrée de mot de passe en utilisant le script suivant. Nous avons essayé de créer un frontend basé sur le Web pour que nos développeurs puissent envoyer les binaires dans la boîte de Windows avec ce script en cours d'exécution afin qu'il puisse être signé et retourné.

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

Je dois souligner que ce que je partageais est pas tout à fait pas sécurisé, mais nous a également frappé cette question nécessite soit des clés de signature d'achat pour chaque développeur ou l'attribution d'un emploi d'un gestionnaire de signature qui approuve la signature d'un logiciel publié . Je crois que ce sont les meilleurs, les processus sécurisés - qu'une fois que les choses passent l'assurance de la qualité et sont approuvés pour la publication, ils peuvent être officiellement signés. Cependant, les besoins des petites entreprises peuvent exiger que cela se fasse d'une manière automatisée.

J'ai utilisé à l'origine osslsigncode sous Linux (avant les certificats EV) pour automatiser la signature des exécutables Windows (puisque nous avions un serveur Linux qui faisait beaucoup de travail pour la facilité de développement et la collaboration). J'ai contacté le développeur de osslsigncode pour voir s'il pouvait utiliser les jetons DigiCert SafeNet pour l'automatiser d'une manière différente puisque je peux les voir sur Linux. Sa réponse a donné de l'espoir mais je ne suis pas sûr de tout progrès et je ne pouvais pas consacrer plus de temps pour aider

+0

Voir les autres réponses. Il y a une option pour déverrouiller seulement une fois par session, ce qui est suffisant pour la plupart des utilisateurs. –

38

Il n'y a aucun moyen de contourner la boîte de dialogue AFAIK, mais ce que vous pouvez faire est de configurer le client d'authentification SafeNet afin qu'il ne demande une fois par session de connexion.

je cite le document de SAC (trouvé une fois installé dans \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chm, chapitre « Client Settings », « Enabling Client Logon ») ici:

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

Pour activer cette fonctionnalité qui est désactivée par défaut, accédez à SAC paramètres avancés, et cochez la case « activer la connexion unique » boîte:

enter image description here

Redémarrez votre ordinateur, et il devrait maintenant inviter uniquement pour le mot de passe jeton une fois. Dans notre cas, nous avons plus de 200 binaires à signer pour chaque build, donc c'est un total doit.

Sinon, voici un petit C# exemple de code de la console (équivalent à m1st0 un) qui vous permet de répondre automatiquement à l'ouverture de session des boîtes de dialogue (probablement besoin pour exécuter en tant qu'administrateur):

static void SatisfyEverySafeNetTokenPasswordRequest(string password) 
    { 
     int count = 0; 
     Automation.AddAutomationEventHandler(WindowPattern.WindowOpenedEvent, AutomationElement.RootElement, TreeScope.Children, (sender, e) => 
     { 
      var element = sender as AutomationElement; 
      if (element.Current.Name == "Token Logon") 
      { 
       WindowPattern pattern = (WindowPattern)element.GetCurrentPattern(WindowPattern.Pattern); 
       pattern.WaitForInputIdle(10000); 
       var edit = element.FindFirst(TreeScope.Descendants, new AndCondition(
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit), 
        new PropertyCondition(AutomationElement.NameProperty, "Token Password:"))); 

       var ok = element.FindFirst(TreeScope.Descendants, new AndCondition(
        new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button), 
        new PropertyCondition(AutomationElement.NameProperty, "OK"))); 

       if (edit != null && ok != null) 
       { 
        count++; 
        ValuePattern vp = (ValuePattern)edit.GetCurrentPattern(ValuePattern.Pattern); 
        vp.SetValue(password); 
        Console.WriteLine("SafeNet window (count: " + count + " window(s)) detected. Setting password..."); 

        InvokePattern ip = (InvokePattern)ok.GetCurrentPattern(InvokePattern.Pattern); 
        ip.Invoke(); 
       } 
       else 
       { 
        Console.WriteLine("SafeNet window detected but not with edit and button..."); 
       } 
      } 
     }); 

     do 
     { 
      // press Q to quit... 
      ConsoleKeyInfo k = Console.ReadKey(true); 
      if (k.Key == ConsoleKey.Q) 
       break; 
     } 
     while (true); 
     Automation.RemoveAllEventHandlers(); 
    } 
+7

Cela peut ne pas être une réponse officielle de DigiCert, mais leur réponse est nulle, et celle-ci est géniale! Merci pour l'aide! – lordjeb

+2

+1 pour une réponse correcte. Cela m'étonne de voir des gens développer des scripts pour automatiser les entrées des utilisateurs et ainsi, en vainant le but d'avoir un mot de passe, et tout ce qu'ils devaient savoir était où cette option était. Je doute que cette option disparaisse, car les émetteurs comprennent que les développeurs ne peuvent pas saisir le mot de passe chaque fois qu'un binaire est signé. –

+3

Je peux confirmer que cela fonctionne à partir de TeamCity (à condition que le service TeamCity Windows ait coché la case «Autoriser le service à interagir avec le bureau»). Nous devions également exécuter le processus d'entrée de mot de passe dans un autre thread et désactiver le service "Interactive Services Detection" sur notre machine de build. Nous avons créé un wrapper C# autour de signtool qui a effectué la signature et géré l'entrée de mot de passe comme ci-dessus, le tout dans une application autonome. Je ne peux pas croire combien d'obstacles nous avons dû traverser pour que cela fonctionne, mais pour quelqu'un d'autre dans le même bateau, concentrez-vous sur la méthode C# décrite ci-dessus ... –

2

Je mon cas question Digicert un certificat Standard (OV) pour le CI, gratuit si vous avez déjà un certificat EV.

Je sais que ce n'est pas la solution, mais si vous ne pouvez pas mettre le jeton dans le serveur (un serveur cloud) c'est la voie à suivre.

7

Je suis fait outil bêta qui aidera à automatiser le processus de construction.

C'est l'application Windows Client-Serveur. Vous pouvez démarrer le serveur sur l'ordinateur où le jeton EV a été inséré. Entrez le mot de passe pour le jeton au démarrage de l'application côté serveur. Après cela, vous pouvez signer des fichiers à distance. L'application côté client remplace complètement signtool.exe pour que vous puissiez utiliser des scripts de génération existants.

code source situé ici: https://github.com/SirAlex/RemoteSignTool

Edit: Nous avons utilisé avec succès cet outil pour la signature du code 24x7 dernière demi-année sur notre serveur de build. Tout fonctionne bien.

1

variante Python de l'outil:

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

De plus, je l'ai trouvé, cette console oVirt a un comportement par défaut pour envoyer verrouillage à Windows. Vous devez le désactiver dans les options du serveur et configurer la connexion automatique.

2

En fait, sous Windows, vous pouvez spécifier le mot de passe du jeton entièrement par programmation. Cela peut être fait en créant un contexte (CryptAcquireContext) avec l'indicateur CRYPT_SILENT en utilisant le nom de jeton sous la forme "\\. \ AKS ifdh 0" ou le nom du conteneur de jetons, qui est visible dans cerificate propriétés dans l'application Authentication Client. Vous devez ensuite utiliser CryptSetProvParam avec le paramètre PP_SIGNATURE_PIN pour spécifier votre mot de passe de jeton. Après cela, le processus peut utiliser des certificats sur ce jeton pour signer des fichiers.
Note: une fois que vous créez le contexte, il semble fonctionner uniquement pour le processus en cours, pas besoin de le passer à d'autres fonctions de l'API Crypto ou quoi que ce soit. Mais n'hésitez pas à commenter si vous trouvez une situation où d'autres efforts seront nécessaires.
Edit: code ajouté exemple

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

Intéressant. Semble prometteur, vous devriez IMHO élaborer sur cela (améliorer l'explication, fournir le code, etc) –

+0

S'il vous plaît poster un exemple complet. Cela semble très utile – dten

+0

merci pour les détails supplémentaires. est-ce le guide que vous mentionnez? http: //read.pudn.com/downloads128/ebook/549477/eToken_SDK_3_50 [1] .pdf – dten

1

expansion sur this answer, cela peut être automatisé à l'aide CryptAcquireContext et CryptSetProvParam pour entrer dans le code PIN de jeton et programmation CryptUIWizDigitalSign pour effectuer la signature d'un programme. J'ai créé une application de console (code ci-dessous) qui prend en entrée le fichier de certificat (exporté en cliquant droit sur le certificat dans SafeNet Authentication Client et en sélectionnant "Exporter ..."), le nom du conteneur de clé privée (trouvé dans SafeNet Authentication Client), le code PIN du jeton, l'URL de l'horodatage et le chemin du fichier à signer. Cette application de console a fonctionné lorsqu'elle a été appelée par l'agent de construction TeamCity sur lequel le jeton USB était connecté.

Exemple d'utilisation:
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; 
} 

Exportation du certificat dans un fichier:
Exporting the Certificate to a File

Nom du conteneur clé privée:
Private Key Container Name

Questions connexes