使用硬件令牌进行 EV 代码签名






4.91/5 (7投票s)
如何使用硬件令牌签名 PE 文件
背景
在 Secured Globe, Inc.,我们使用 Comodo EV 代码签名证书来签名我们的产品。Comodo 发送一个 SafeNet eToken,其他供应商也一样。类似的产品有 Digicert's。
内核模式代码签名
内核驱动程序签名需要内核模式代码签名。这种签名使用交叉证书完成。在 Windows 中,交叉证书允许操作系统内核拥有一个单独受信任的 Microsoft 根机构,并将信任链扩展到多个商业 CA,这些 CA 颁发软件发布者证书 (SPC),这些证书用于代码签名软件,以便在 Windows 上分发、安装和加载。
EV 代码签名阶段
正如 这篇博文中所解释的那样,EV 代码签名需要以下步骤:
- 连接到硬件令牌。
- 使用根证书(用于 内核模式代码签名)
- 使用交叉证书(用于内核模式代码签名)
- 使用 EV 证书、私钥和根/交叉证书进行代码签名。
证书上下文
证书上下文包含证书的编码和解码表示。cert store 函数返回的证书上下文必须通过调用 CertFreeCertificateContext
函数释放。可以调用 CertDuplicateCertificateContext
函数来制作副本(也必须通过调用 CertFreeCertificateContext
释放)。
typedef struct _CERT_CONTEXT
{
DWORD dwCertEncodingType;
BYTE *pbCertEncoded;
DWORD cbCertEncoded;
PCERT_INFO pCertInfo;
HCERTSTORE hCertStore;
} CERT_CONTEXT, *PCERT_CONTEXT;
typedef const CERT_CONTEXT *PCCERT_CONTEXT;
打开令牌
当我们需要访问硬件令牌时,我们可以通过编程方式访问它。
我们需要定义两个常量:
#define SAFENET_TOKEN L"\\\\.\\AKS ifdh 0"
#define EV_PASS "<your private key here"
Safenet eToken 的默认名称为 "\\\\.\\AKS ifdh 0"。
第二个参数是你的私钥。
然后我们按如下方式调用 OpenToken
:
PCCERT_CONTEXT cert = OpenToken(SAFENET_TOKEN, EV_PASS);
返回值是 PCCERT_CONTEXT
,这意味着我们可以用它来进行代码签名,就像我们使用常规的代码签名证书一样。
以下是 OpenToken
的代码:
//
PCCERT_CONTEXT OpenToken(const std::wstring& TokenName, const std::string& TokenPin)
{
const wchar_t DefProviderName[] = L"eToken Base Cryptographic Provider";
HCRYPTPROV hProv = NULL;
if (!CryptAcquireContextW(&hProv, TokenName.c_str(),
DefProviderName, PROV_RSA_FULL, CRYPT_SILENT))
{
DWORD Error = GetLastError();
wprintf(L"Opening token %ws has failed while calling CryptAcquireContext,
error 0x%08X\n", TokenName.c_str(), Error);
MessageBox(NULL, L"You must insert the Token to your USB port", L"", MB_OK);
return NULL;
}
if (!CryptSetProvParam(hProv, PP_SIGNATURE_PIN, (BYTE*)TokenPin.c_str(), 0))
{
DWORD Error = GetLastError();
wprintf(L"Failed to unlock token %ws! Error 0x%08X\n", TokenName.c_str(), Error);
CryptReleaseContext(hProv, 0);
return NULL;
}
else
{
BOOL bStatus = FALSE;
DWORD dwErr = 0;
DWORD dwFlags = CRYPT_FIRST;
PCCERT_CONTEXT pContextArray[128];
DWORD dwContextArrayLen = 0;
HCRYPTKEY hKey = NULL;
LPBYTE pbCert = NULL;
DWORD dwCertLen = 0;
PCCERT_CONTEXT pCertContext = NULL;
DWORD pKeySpecs[2] = { AT_KEYEXCHANGE, AT_SIGNATURE };
wprintf(L"Successfully unlocked token %ws\n", TokenName.c_str());
bStatus = CryptAcquireContext(&hProv,
TokenName.c_str(),
DefProviderName,
PROV_RSA_FULL,
0);
if (!bStatus)
{
dwErr = GetLastError();
goto end;
}
// convert the container name to unicode
// Acquire a context on the current container
if (CryptAcquireContext(&hProv,
TokenName.c_str(),
DefProviderName,
PROV_RSA_FULL,
0))
{
// Loop over all the key specs
for (int i = 0; i < 2; i++)
{
if (CryptGetUserKey(hProv,
pKeySpecs[i],
&hKey))
{
if (CryptGetKeyParam(hKey,
KP_CERTIFICATE,
NULL,
&dwCertLen,
0))
{
pbCert = (LPBYTE)LocalAlloc(0, dwCertLen);
if (!pbCert)
{
dwErr = GetLastError();
goto end;
}
if (CryptGetKeyParam(hKey,
KP_CERTIFICATE,
pbCert,
&dwCertLen,
0))
{
pCertContext = CertCreateCertificateContext(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pbCert,
dwCertLen);
if (pCertContext)
{
wprintf(L"Signing file\n");
pContextArray[dwContextArrayLen++] = pCertContext;
return pCertContext;
}
}
LocalFree(pbCert);
}
CryptDestroyKey(hKey);
hKey = NULL;
}
}
CryptReleaseContext(hProv, 0);
hProv = NULL;
}
dwFlags = 0;
end:
while (dwContextArrayLen--)
{
CertFreeCertificateContext(pContextArray[dwContextArrayLen]);
}
if (hKey)
CryptDestroyKey(hKey);
if (hProv)
CryptReleaseContext(hProv, 0);
return NULL;
}
}
//
签名文件
我们需要使用的 Windows API 调用是 SignerSignEx2()。
在我们通过硬件令牌或直接打开证书(非 EV 代码签名证书)后,我们调用 SignAppxPackage()
。
这个函数需要使用 LoadLibrary() 和 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();
}
时间戳
你必须使用时间戳机构对你签名的文件进行时间戳,并连接到该机构。
这是通过安全地通过 URL 检查时间戳服务器以获取当前日期和时间来完成的。 每个签名机构都有自己的时间戳服务器。 时间戳是代码签名过程中的一个额外步骤,但对于 EV 代码签名而言,这是一个要求,它为签名的 PE 增加了一个额外的安全层。
签名前的初步检查
在我们可以签名 PE 之前,我们检查以下内容:
- 我们是否有
CertAuthority_ROOT
的有效路径? - 我们是否有
CertAuthority_RSA
的有效路径? - 我们是否有
CROSSCERTPATH
的有效路径? - 我们是否连接到 Internet? (我们需要 Internet 连接才能正确地为签名文件添加时间戳)。
从文件加载证书
假设我们有过程中使用的某个证书的有效路径,我们需要将其加载到内存中。 我编写了以下函数来执行此操作。
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);
}
这是 FormMemoryCertStore
函数:
std::tuple<DWORD, DWORD, std::string> FormMemoryCertStore
(const std::vector<std::shared_ptr<const CERT_CONTEXT> >& Certs
, DWORD FlagsForAddCertToStore
, std::shared_ptr<void>* ResultStore)
{
HCERTSTORE hTmpMemoryStore = ::CertOpenStore(CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL);
if (hTmpMemoryStore == NULL)
{
return std::make_tuple(E_FAIL, ::GetLastError(), "CertOpenStore(Memory Store)");
}
for (unsigned int i = 0; i < Certs.size(); ++i)
{
int Result = ::CertAddCertificateContextToStore(hTmpMemoryStore
, Certs[i].get()
, FlagsForAddCertToStore
, NULL);
if (Result == 0)
{
DWORD dwLastError = ::GetLastError();
::CertCloseStore(hTmpMemoryStore, 0);
return std::make_tuple(E_FAIL, dwLastError, "CertAddCertificateContextToStore");
}
}
//All certificates are successfully placed in the repository
// - I specify the returned parameter
*ResultStore = std::shared_ptr<void>
(hTmpMemoryStore, std::bind(::CertCloseStore, std::placeholders::_1, 0));
return std::make_tuple(0, 0, "");
}
这是 ReadFileToVector
函数:
std::tuple<DWORD, DWORD, std::string> ReadFileToVector
(const std::wstring& FileName
, std::vector<unsigned char>* ResultData)
{
HANDLE hFile = ::CreateFile
(FileName.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
return std::make_tuple(E_FAIL, ::GetLastError(),
"CreateFile(GENERIC_READ, OPEN_EXISTING,)");
}
std::shared_ptr<void> sptrTemp(hFile, ::CloseHandle);
DWORD dwDataLen = ::GetFileSize(hFile, NULL);
if (dwDataLen == INVALID_FILE_SIZE)
{
return std::make_tuple(E_FAIL, ::GetLastError(), "GetFileSize");
}
ResultData->resize(dwDataLen);
DWORD dwNumberOfReadData = 0;
DWORD Result = ::ReadFile(hFile, &(*ResultData)[0],
static_cast<DWORD>(ResultData->size()), &dwNumberOfReadData, NULL);
if (Result == 0)
{
return std::make_tuple(E_FAIL, ::GetLastError(), "ReadFile");
}
return std::make_tuple(0, 0, "");
}
从内存加载证书
在我们从文件加载证书后,我们需要将其加载到内存中。 这是使用以下函数完成的:
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, "");
}
如本文所示,嵌入在硬件令牌中的证书已从令牌加载到内存中。 现在,这就是我们访问它的方式:
std::vector<unsigned char> dataCertEV(signingCertContext->pbCertEncoded,
signingCertContext->pbCertEncoded + signingCertContext->cbCertEncoded);
SignAppxPackage 函数
现在,我们准备好使用 SignAppxPackge()
函数,如下所示:
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;
}
因此,如果 FILETOSIGN
包含我们想要签名的 PE 名称,我们的整个代码签名过程包括以下函数调用:
PCCERT_CONTEXT cert = OpenToken(SAFENET_TOKEN, EV_PASS);
HRESULT hr = SignAppxPackage(cert, FILETOSIGN);
将内核驱动程序提交给 Microsoft 实验室
内核驱动程序需要按照本文所述进行 EV 代码签名和交叉签名。 然后,你需要将驱动程序的文件打包到一个 cab 文件中并对其进行签名。 这是我写的一篇 文章,解释了如何做到这一点。
关注点
历史
- 2018 年 12 月 15 日:初始版本