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?
passwords
code-signing
authenticode
code-signing-certificate
decasteljau
fuente
fuente
Respuestas:
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.chm
capítulo 'Client Settings
', 'Enabling Client Logon
') aquí: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":
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.dll
yUIAutomationTypes.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(); } } }
fuente
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.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".
1. Exporte su certificado público a un archivo desde SafeNet Client
2. Busque el nombre del contenedor de su clave privada
3. Busque el nombre de su 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
Dónde:
reader
es el "nombre del lector" de la interfaz de usuario de SafeNet Clientpassword
es tu contraseña de tokenname
es el "Nombre del contenedor" de la interfaz de usuario de SafeNet ClientEs 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>"
Ejemplo de comando signtool como sigue
Algunas imágenes tomadas de esta respuesta: https://stackoverflow.com/a/47894907/5420193
fuente
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:
Nombre del contenedor de clave privada:
fuente
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.
fuente
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
fuente
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.
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.
fuente
Utilice Microsoft Windows SDK 10 para signtool
fuente
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:
Instale Python, usando el comando:
(Reinicie el símbolo del sistema) Instale el módulo de Python adicional:
Guarde el siguiente texto en
disableAutoprompt.py
:Guarde su contraseña en passwd.txt, y luego ejecute
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.
fuente
Recibí una respuesta de Digicert:
fuente
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.
fuente
La forma en que lo hago es:
Abre la ficha
PCCERT_CONTEXT cert = OpenToken (SAFENET_TOKEN, EV_PASS);
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 ():
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.
Cargar un certificado desde un archivo
Cargar un certificado en la memoria
Una vez que se ha cargado el certificado tras acceder al token de hardware, lo cargamos:
Finalmente, la firma se realiza en la siguiente función:
Vea este artículo que escribí .
fuente
Estoy usando un certificado de señal global y amablemente dijeron lo mismo.
... que está mucho más allá de mi presupuesto. Al contrario de lo que dijeron, logré que funcionara:
=> este comando devuelve el siguiente error:
Realmente no entiendo este problema. PERO si ejecuta otra vez el siguiente comando, funciona
Esto funciona dentro de teamcity build y no es necesario iniciar sesión en una cuenta activa en teamcity build agent.
fuente