Automatizar la firma de código de validación extendida (EV)

82

Recientemente compramos un certificado de firma de código de DigiCert EV. Podemos firmar archivos .exe usando signtool.exe. Sin embargo, cada vez que firmamos un archivo, solicita la contraseña de SafeNet eToken.

¿Cómo podemos automatizar este proceso, sin la intervención del usuario, almacenando / almacenando en caché la contraseña en algún lugar?

decasteljau
fuente
La pregunta " ¿Qué tan seguras son las solicitudes de contraseña de SafeNet eToken 5110 o tokens de hardware criptográficos similares? " Está algo relacionada, si alguna vez obtiene una respuesta, debería ser de interés para aquellos que evalúan si automatizar la entrada de la contraseña. Como estoy en ello, si alguien que posee en la actualidad eso o de poco valor similares lee esto, si usted puede tratar de "hackear" y responder a esta cuestión sería muy apreciada :)
GBR
desafortunadamente, la respuesta que funcionó para mí y que obtiene la mayor cantidad de votos aparece al final de la lista de respuestas, así que no pierda su tiempo y vaya directamente a las respuestas de Simon Mourier stackoverflow.com/a/26126701/27194
Patrick del equipo NDepend
Solo un aviso antes de intentar cualquiera de estas soluciones. Los tokens de hardware tienen un contador de "Reintentos de contraseña de token restantes" (se puede verificar en el cliente de autenticación de SafeNet). Cuando experimente, asegúrese de que nunca llegue a cero por razones obvias. De lo contrario, probablemente quedará bloqueado permanentemente de su token de hardware y tendrá que solicitar uno nuevo. Aprendí esto de la manera difícil ...
Sundae
Desafortunadamente, la respuesta de Simon ya no funciona (vea mi comentario a la respuesta ). Y la respuesta de Austin no solo funciona, sino que también es mejor.
Martin Prikryl

Respuestas:

64

No hay forma de omitir el diálogo de inicio de sesión AFAIK, pero lo que puede hacer es configurar el cliente de autenticación de SafeNet para que solo lo pregunte una vez por sesión de inicio de sesión.

Cito el documento SAC (que se encuentra una vez instalado en el \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chmcapítulo ' Client Settings', ' Enabling Client Logon') aquí:

Cuando se habilita el inicio de sesión único, los usuarios pueden acceder a múltiples aplicaciones con una sola solicitud de la contraseña del token durante cada sesión de computadora. Esto alivia la necesidad de que el usuario inicie sesión en cada aplicación por separado.

Para habilitar esta función que está deshabilitada de manera predeterminada, vaya a la configuración avanzada de SAC y marque la casilla "habilitar inicio de sesión único":

ingrese la descripción de la imagen aquí

Reinicie su computadora, y ahora solo debería solicitar la contraseña del token una vez. En nuestro caso, tenemos más de 200 binarios para firmar por cada compilación, por lo que es una necesidad total .

De lo contrario, aquí hay un pequeño código de muestra de consola de C # (equivalente a m1st0 uno) que le permite responder automáticamente a los cuadros de diálogo de inicio de sesión (probablemente deba ejecutarse como administrador) (debe hacer referencia desde su proyecto de consola ( UIAutomationClient.dlly 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();
      }
   }
}
Simon Mourier
fuente
10
Puede que esta no sea una respuesta oficial de DigiCert, pero su respuesta apesta, ¡y esta es increíble! ¡Gracias por la ayuda!
lordjeb
2
+1 para una respuesta correcta. Me sorprende ver a la gente desarrollando scripts para automatizar la entrada del usuario y demás, frustrando el propósito de tener una contraseña realmente, y todo lo que necesitaban saber era dónde estaba esta opción. Dudo que esta opción desaparezca alguna vez, ya que los emisores entienden que los desarrolladores no pueden escribir la contraseña cada vez que se firma un binario.
dyasta
3
Puedo confirmar que esto funciona desde TeamCity (siempre que el servicio TeamCity Windows tenga marcada la casilla "Permitir que el servicio interactúe con el escritorio"). También necesitábamos ejecutar el proceso de entrada de contraseña en otro hilo y deshabilitar el servicio "Detección de servicios interactivos" en nuestra máquina de compilación. Creamos un contenedor de C # alrededor de signtool que realizaba la firma y manejaba la entrada de contraseña como se indicó anteriormente, todo en una aplicación autónoma. No puedo creer cuántos obstáculos tuvimos que cruzar para que esto funcione, pero para cualquier otra persona en el mismo barco, concéntrese en el método C # descrito anteriormente ...
Alan Spark
1
Para su información ... los certificados de Symantec EV también utilizan SafeNet. Tuvimos que construir una solución tonta en torno a este proceso, pero después de leer su respuesta e implementar la aplicación de consola, esto ha ayudado enormemente a nuestro proceso de construcción. Gracias. Gran solución para un proceso de firma de código con una arquitectura deficiente.
Zoltan
1
He estado utilizando con éxito esta útil solución durante un tiempo. Pero ahora, al configurarlo en una máquina nueva con Windows 10 Pro 2004 con el cliente SafeNet 9.0.34 x64 para Windows 8 y versiones posteriores, ya no funciona. ¿Dónde se solicita una nueva contraseña? Parece ser uno integrado en Windows, en lugar del indicador personalizado de SafeNet como antes. Y el cuadro de contraseña del nuevo mensaje no se puede automatizar (no está expuesto en el AutomationElementárbol). No sé si se puede solucionar de alguna manera. Pero al volver a visitar esta pregunta y encontrar la respuesta de @Austin, creo que es una mejor solución de todos modos.
Martin Prikryl
34

Ampliando las respuestas que ya están en este hilo, es posible proporcionar la contraseña del token utilizando el programa signtool estándar de microsoft.

0. Abra SafeNet Client en Vista avanzada

Las rutas de instalación pueden variar, pero para mí, el cliente de SafeNet está instalado para: C:\Program Files\SafeNet\Authentication\SAC\x64\SACTools.exe

Haga clic en el ícono de ajustes en la esquina superior derecha para abrir la "vista avanzada". Vista avanzada de SafeNet

1. Exporte su certificado público a un archivo desde SafeNet Client Exportación del certificado a un archivo

2. Busque el nombre del contenedor de su clave privada
Nombre del contenedor de clave privada

3. Busque el nombre de su lector Nombre del lector

4. Formatee todo junto

El eToken CSP tiene una funcionalidad oculta (o al menos no ampliamente publicitada) para analizar la contraseña del token fuera del nombre del contenedor.

El formato es uno de los siguientes

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

Dónde:

  • reader es el "nombre del lector" de la interfaz de usuario de SafeNet Client
  • password es tu contraseña de token
  • name es el "Nombre del contenedor" de la interfaz de usuario de SafeNet Client

Es de suponer que debe especificar el nombre del lector si tiene más de un lector conectado, ya que solo tengo un lector, no puedo confirmarlo.

5. Pasa la información a signtool

  • /f certfile.cer
  • /csp "eToken Base Cryptographic Provider"
  • /k "<value from step 4>"
  • cualquier otra bandera de signtool que necesite

Ejemplo de comando signtool como sigue

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

Algunas imágenes tomadas de esta respuesta: https://stackoverflow.com/a/47894907/5420193

Austin Morton
fuente
2
Funciona muy bien, lamentablemente encontré esto después de dos días excavando e implementando mi propio "signtool": D thx
Lukáš Koten
Advertencia: esta solución puede bloquear su token de hardware si ingresa la contraseña incorrecta, ¡incluso solo dos veces! De alguna manera, el contador restante de reintentos de contraseña disminuyó de 15 a 3 después de que ejecuté el comando solo una vez con una contraseña no válida. Sin embargo, la solución "etokensign.exe" parece funcionar bien, el contador de contraseña restante disminuyó como debería de 15 a 14 después de un intento de contraseña no válida.
Sundae
1
Maravilloso, esto funcionó perfectamente para mí con el dongle USB SafeNet que me llegó de Sectigo.
JacobJ
1
Hasta donde yo sé, esta sintaxis no está documentada públicamente en ninguna parte. Encontré esta funcionalidad mediante la ingeniería inversa del binario del controlador en IDA Pro.
Austin Morton
1
Esto es mejor que las otras respuestas que introducen contraseñas falsas en las indicaciones de la GUI. Entre otros, esto funciona incluso en sesiones de Windows no interactivas. Y las otras respuestas parecen no funcionar con las últimas herramientas .
Martin Prikryl
19

Ampliando esta respuesta , esto se puede automatizar usando CryptAcquireContext y CryptSetProvParam para ingresar el PIN del token mediante programación y CryptUIWizDigitalSign para realizar la firma mediante programación. Creé una aplicación de consola (código a continuación) que toma como entrada el archivo de certificado (exportado haciendo clic derecho en el certificado en SafeNet Authentication Client y seleccionando "Exportar ..."), el nombre del contenedor de clave privada (que se encuentra en SafeNet Authentication Client), el PIN del token, la URL de la marca de tiempo y la ruta del archivo para firmar. Esta aplicación de consola funcionó cuando la llamó el agente de compilación de TeamCity donde estaba conectado el token USB.

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

Código:

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

Exportación del certificado a un archivo:
Exportación del certificado a un archivo

Nombre del contenedor de clave privada:
Nombre del contenedor de clave privada

draketb
fuente
2
Esta debería ser la respuesta aceptada, ¡funciona de maravilla!
shawn
1
Esto es perfecto. En particular, después de darme cuenta de que solo necesito firmar un archivo ficticio con esta herramienta. Si el "inicio de sesión único" está habilitado (controlador SafeNet), todos los pasos posteriores funcionan con el signtool estándar. Esto es muy útil para firmar complementos de Office (VSTO), que usan una herramienta diferente y también significa que solo se requieren cambios mínimos para mi proceso / script de compilación.
Stefan Egli
Esta respuesta es una buena adición a la proporcionada por avzhatkin. En este punto, el código está a punto de reemplazar signtools.exe. Este programa debería ser compatible con la firma cruzada. Afortunadamente, hay otra publicación SO ahora para realizar firmas cruzadas .
sdc
Esta respuesta terminó ayudándome más. Me perdí una referencia externa al compilar en VS2017, pero al agregar algunos comentarios de pragma como se sugiere aquí , logré que Bamboo (CI / CD de Atlassian) firmara.
HTBR
11

Soy una herramienta beta que ayudará a automatizar el proceso de construcción.

Es una aplicación de Windows Cliente-Servidor. Puede iniciar el servidor en la computadora donde se insertó el token EV. Ingrese la contraseña para el token al iniciar la aplicación del lado del servidor. Después de esto, puede firmar archivos de forma remota. La aplicación del lado del cliente reemplaza completamente signtool.exe para que pueda usar los scripts de compilación existentes.

Código fuente ubicado aquí: https://github.com/SirAlex/RemoteSignTool

Editar: utilizamos con éxito esta herramienta para la firma de código durante la última mitad del año 24x7 en nuestro servidor de compilación. Todo funciona bien.

Aleksey Kharlanov
fuente
1
¿Qué tan seguro es este enfoque? ¿No significa que cualquiera que pueda conectarse a su servidor de firmas con HTTP, puede firmar cualquier binario que desee con su certificado EV?
Gene Pavlovsky
6

En realidad, en Windows puede especificar la contraseña del token de forma totalmente programática. Esto se puede hacer creando un contexto ( CryptAcquireContext ) con el indicador CRYPT_SILENT usando el nombre del token en forma "\\. \ AKS ifdh 0" o el nombre del contenedor del token, que es un guid visible en las propiedades del certificado en la aplicación Cliente de autenticación. Luego, debe usar CryptSetProvParam con el parámetro PP_SIGNATURE_PIN para especificar su contraseña de token. Después de eso, el proceso puede usar certificados en ese token para firmar archivos.
Nota: una vez que crea el contexto, parece que solo funciona para el proceso actual por completo, no es necesario pasarlo a otras funciones de la API de cifrado ni nada. Pero no dude en comentar si encuentra una situación en la que se requerirán más esfuerzos.
Editar: muestra de código agregado

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;
    }
}
avzhatkin
fuente
1
Interesante. Parece prometedor, en mi humilde opinión deberías dar más detalles (mejorar la explicación, proporcionar el código, etc.)
Simon Mourier
Publique un ejemplo completo. Esto suena realmente útil
dten
gracias por los detalles adicionales. ¿Es esta la guía que mencionas? read.pudn.com/downloads128/ebook/549477/eToken_SDK_3_50[1].pdf
dten
Creo que no es la versión exacta que tenía, pero parece contener información similar sobre cómo crear contexto y proporcionar el PIN, aunque para diferentes escenarios de uso.
avzhatkin
Supongo que llamas a esta función OpenToken (L "\\\\. \\ AKS ifdh 0", <contraseña del token>) ... ¡bueno, funcionó para mí!
Michael Haephrati
5

Solía AutoHotKey para automatizar la introducción de la contraseña usando la siguiente secuencia de comandos. Hemos estado intentando crear una interfaz web para que nuestros desarrolladores envíen los archivos binarios a la caja de Windows con este script en ejecución para que pueda ser firmado y devuelto.

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

Debo señalar que lo que compartí no es completamente seguro, pero también abordamos este problema que requiere comprar claves de firma para cada desarrollador o asignar un trabajo de administrador de firma que aprobaría la firma del software lanzado. Creo que esos son los procesos mejores y más seguros: que una vez que las cosas pasan el control de calidad y se aprueban para su publicación, se pueden firmar oficialmente. Sin embargo, las necesidades de las empresas más pequeñas pueden exigir que esto se haga de otra forma automatizada.

Originalmente usé osslsigncode en Linux (antes de los certificados EV) para automatizar la firma de ejecutables de Windows (ya que teníamos un servidor Linux haciendo mucho trabajo para facilitar la colaboración y el desarrollador). Me he puesto en contacto con el desarrollador de osslsigncode para ver si puede hacer uso de los tokens de DigiCert SafeNet para ayudar a automatizarlo de una manera diferente, ya que puedo verlos en Linux. Su respuesta brindó esperanza, pero no estoy seguro de ningún progreso y no podría dedicar más tiempo para ayudar.

m1st0
fuente
Vea la otra respuesta. Hay una opción para desbloquear solo una vez por sesión, que es suficiente para la mayoría de los usuarios.
dyasta
5

signtool.exe sign / fd sha256 / f "signing.cer" / csp "eToken Base Cryptographic Provider" / kc "[{{contraseña de token aquí}}] = Nombre del contenedor aquí" "ConsoleApp1.exe"

Utilice Microsoft Windows SDK 10 para signtool

Lakmal
fuente
1
¡Magnífico! Esta frase de una sola línea ciertamente avergüenza a todas las demás respuestas (aunque al principio me desconcertó cómo averiguar mi "Nombre del contenedor", hasta que encontré las instrucciones en la respuesta de draketb anterior).
Dan Z
5

Instale https://chocolatey.org/docs/installation (se puede hacer usando un comando desde el símbolo del sistema administrativo)

(Reiniciar el símbolo del sistema)

Suprima los avisos constantes de choco para cada instalación:

choco feature enable -n=allowGlobalConfirmation

Instale Python, usando el comando:

choco install python

(Reinicie el símbolo del sistema) Instale el módulo de Python adicional:

pip install pypiwin32

Guarde el siguiente texto en 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()

Guarde su contraseña en passwd.txt, y luego ejecute

python disableAutoprompt.py

Desde SafeNet Authentication Client- Configuración> Client Settings> Advanced> Enable Single Log On opción se puede activar para minimizar la cantidad de solicitudes de contraseña, pero no desactiva completamente ellos (Probado en la versión 10.4.26.0)

La aplicación C # (por ejemplo, https://github.com/ganl/safenetpass ) no funciona con la pantalla de bloqueo encendida, pero sí funciona con este script de Python.

UN
fuente
Este script es increíble y pude adaptarlo fácilmente a mis necesidades trabajando con un dongle Yubikey. Sin embargo, Windows 10 lo rompe. Windows 10 cambia a XAML, por lo que las funciones win32gui.xxxx () no funcionarán. /SUSPIRO. Gracias Microsoft. Por eso no podemos tener cosas bonitas.
John Rocha
2

Recibí una respuesta de Digicert:

Desafortunadamente, parte de la seguridad del Certificado de firma de código EV es que debe ingresar la contraseña cada vez. No hay forma de automatizarlo.

decasteljau
fuente
Obtuvimos la misma respuesta, aunque están buscando una solución, no tienen un plazo para cuándo podría estar disponible. Aunque están al tanto de esta publicación de SO, es de esperar que se den cuenta de lo problemático que es.
Alan Spark
Encontramos una manera de evitarlo
gunslingor
2

En mi caso, Digicert emite un certificado estándar (OV) para el CI, de forma gratuita si ya tiene un certificado EV.

Sé que esta no es la solución, pero si no puede poner el token en el servidor (un servidor en la nube), este es el camino a seguir.

Ricardo Polo Jaramillo
fuente
1

La forma en que lo hago es:

  1. Abre la ficha

    PCCERT_CONTEXT cert = OpenToken (SAFENET_TOKEN, EV_PASS);

  2. Firme el archivo con el token, los certificados raíz / cruzados cuando sea necesario y el certificado EV cargado en la memoria.

    HRESULT hr = SignAppxPackage (cert, FILETOSIGN);

Usando SignerSignEx2 ():

El archivo está firmado usando SignerSignEx2 () que debe cargarse en la memoria usando LoadLibrary () y 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();
}

Marcando la hora

Además, debe sellar el tiempo en su archivo firmado y hacerlo utilizando una autoridad de sellado de tiempo a la que se conecta.

Esto se hace comprobando de forma segura un servidor de Sellado de tiempo a través de URL para la fecha y hora actuales. Cada autoridad firmante tiene su propio servidor de Sellado de tiempo. El sello de tiempo es un paso adicional en el proceso de firma de código, pero cuando se trata de firma de código EV es un requisito que agrega una capa adicional de seguridad al PE firmado. Por esa razón, agregue a su código una verificación de si el usuario está conectado a Internet.

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

Cargar un certificado desde un archivo

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

Cargar un certificado en la memoria

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

Una vez que se ha cargado el certificado tras acceder al token de hardware, lo cargamos:

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

Finalmente, la firma se realiza en la siguiente función:

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

Vea este artículo que escribí .

Michael Haephrati
fuente
1

Estoy usando un certificado de señal global y amablemente dijeron lo mismo.

No es posible escribir la firma con una firma de código EV estándar, están promoviendo el uso de una plataforma HSM

... que está mucho más allá de mi presupuesto. Al contrario de lo que dijeron, logré que funcionara:

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

=> este comando devuelve el siguiente error:

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

Realmente no entiendo este problema. PERO si ejecuta otra vez el siguiente comando, funciona

"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

Esto funciona dentro de teamcity build y no es necesario iniciar sesión en una cuenta activa en teamcity build agent.

SebastienAuroux
fuente