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.
I used AutoHotKey to automate the password entry using the script following. We have been trying to make a Web based front end for our developers to send the binaries to the Windows box with this script running so that it can be signed and returned.
I must note that what I shared is not completely not secure, but we also hit this issue requiring either purchasing signing keys for each developer or assigning a job of a signing manager that would approve the signature of released software. I believe those are the better, secure processes--that once things pass quality assurance and are approved for release, they can be officially signed. However, smaller company needs may dictate that this be done in some other automated way.
I originally used osslsigncode on Linux (before EV certificates) to automate signing of Windows executables (since we had a Linux server doing a lot of work for developer ease and collaboration). I have contacted the developer of osslsigncode to see if he can make use of the DigiCert SafeNet tokens to help automate it in a different way since I can see them on Linux. His reply provided hope but I am unsure of any progress and I could not dedicate more time to help
There is no way to bypass the login dialog AFAIK, but what you can do is configure the SafeNet Authentication Client so it only asks it once per login session.
I quote the SAC doc (found once installed in \ProgramFiles\SafeNet\Authentication\SAC\SACHelp.chm, chapter 'Client Settings', 'Enabling Client Logon') here:
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.
To enable this feature which is disabled by default, go to SAC advanced settings, and check the "enable single logon" box:
Restart your computer, and it should now only prompt for the token password once. In our case, we have more than 200 binaries to sign per each build, so this is a total must.
Otherwise, here is a small C# console sample code (equivalent to m1st0 one) that allows you to respond automatically to logon dialogs (probably needs to run as admin) (you need to reference from your console project (UIAutomationClient.dll and 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();
}
}
}
I'm made beta tool which will help to automate build process.
It's Client-Server windows application. You can start server on computer where EV token inserted. Enter password for token on server-side application startup. After this you can sign files remotely.
Client side application fully replaces signtool.exe so you can use existing build scripts.
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()
Save your password into passwd.txt, and after that run
python disableAutoprompt.py
From SafeNet Authentication Client - configuration > Client Settings > Advanced > Enable Single Log On
option can be enabled to minimize amount of password prompts, but
it does not fully disable them (Tested on version 10.4.26.0)
Actually on Windows you can specify the token password fully programmatically. This can be done by creating a context (CryptAcquireContext) with flag CRYPT_SILENT using token name in form "\\.\AKS ifdh 0" or token container name, which is some guid visible in cerificate properties in the Authentication Client application. You then need to use CryptSetProvParam with parameter PP_SIGNATURE_PIN to specify your token password. After that the process can use certificates on that token to sign files. Note: once you create the context is seems to just work for current process entirely, no need to pass it to other Crypto API functions or anything. But feel free to comment if you find a situation when some more efforts will be required. Edit: added code sample
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;
}
}
Expanding on this answer, this can be automated using CryptAcquireContext and CryptSetProvParam to enter the token PIN programmatically and CryptUIWizDigitalSign to perform the signing programmatically. I created a console app (code below) that takes as input the certificate file (exported by right clicking the certificate in SafeNet Authentication Client and selecting "Export..."), the private key container name (found in SafeNet Authentication Client), the token PIN, timestamp URL, and the path of the file to sign. This console app worked when called by the TeamCity build agent where the USB token was connected.
Example Usage: etokensign.exe c:\CodeSigning.cert CONTAINER PIN http://timestamp.digicert.com C:\program.exe
Sign the file using the token, root / cross certificates when required and the EV certificate loaded into memory.
HRESULT hr = SignAppxPackage(cert, FILETOSIGN);
Using SignerSignEx2():
The file is signed using SignerSignEx2() which needs to be loaded into memory using LoadLibrary() and 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();
}
Time Stamping
Further, you must Time Stamp your signed file and do that using a Time Stamping authority to which you connect.
That is done by securely checking a Time Stamping server via URL for the current date and time. Each Signing authority have their own Time Stamping server. Time Stamping is an extra step in the Code Signing process, but when it comes to EV Code Signing it is a requirement which adds an additional layer of security to the signed PE.
For that reason, add to your code a check whether the user is connected to the Internet.
DWORD dwReturnedFlag;
if (InternetGetConnectedState(&dwReturnedFlag,0) == NULL) // use https://learn.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;
}
This works within teamcity build, and no need for an active account log in in teamcity build agent.
Edit : this solution does not work anymore for me, globalsign changed the timestamp url to http://rfc3161timestamp.globalsign.com/advanced. Since that i cant sign with TokenPassword/ContainerName anymore. The only solution I found is to unable single logon and make sure the server does not logoff (i run a video on the server so my account is not loggued off automatically). this is a quick and dirty solution but the only one I found. Thank you globalsign for your poor support.