创建临时客户端证书(包括私钥)



作为我正在编写的应用程序的一部分,我希望我的程序在本地机器上临时安装一个证书,并使WinHTTP在连接到web服务器时将其用作客户端证书。这样做的目的是帮助保护web服务器免受未经授权的访问(这个证书只是一个安全层-我知道有人可以从。exe中提取它)。我不希望用户必须安装证书,也不希望在应用程序不运行时将证书留在PC上。

此刻,我正在尝试这个:

从。p12文件手动安装证书

使用c++应用程序从本地证书中获取二进制数据并在我的应用程序中进入C数组(使用CryptExportKey和PCCERT_CONTEXT::pbCertEncoded)

卸载证书

应用程序启动时:

为证书打开临时存储

m_certificateStoreHandle = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL );

调用CertAddEncodedCertificateToStore添加证书

CertAddEncodedCertificateToStore( m_certificateStoreHandle,
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
reinterpret_cast< const BYTE * >( certificateData ),
dataSize, 
CERT_STORE_ADD_REPLACE_EXISTING,
&m_clientCertificate )

调用CryptAcquireContext以找到存储私钥的地方(我在每次运行时更改密钥的名称—理想情况下,我计划使用CRYPT_VERIFYCONTEXT使密钥非持久化,但现在可以忽略这一点)

HCRYPTPROV cryptProvider = NULL;
HCRYPTKEY cryptKey = NULL;
CryptAcquireContext( &cryptProvider, "MyTestKeyNumber123", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET )

调用CryptImportKey将私钥加载到密钥存储

CryptImportKey( cryptProvider, reinterpret_cast< BYTE * >( privateKey ), keySize, 0, CRYPT_EXPORTABLE, &cryptKey )

调用CertSetCertificateContextProperty将证书链接到私钥

char containerName[128];
DWORD containerNameSize = ARRAY_NUM_BYTES(containerName);
char providerName[128];
DWORD providerNameSize = ARRAY_NUM_BYTES(providerName);
CryptGetProvParam(cryptProvider, PP_CONTAINER, reinterpret_cast<byte *>(containerName), &containerNameSize, 0)
CryptGetProvParam(cryptProvider, PP_NAME, reinterpret_cast<byte *>(providerName), &providerNameSize, 0)
WCHAR containerNameWide[128];
convertCharToWChar(containerNameWide, containerName);
WCHAR providerNameWide[128];
convertCharToWChar(providerNameWide, providerName);
CRYPT_KEY_PROV_INFO privateKeyData;
neMemZero(&privateKeyData, sizeof(privateKeyData));
privateKeyData.pwszContainerName = containerNameWide;
privateKeyData.pwszProvName = providerNameWide;
privateKeyData.dwProvType = 0;
privateKeyData.dwFlags = CRYPT_SILENT;
privateKeyData.dwKeySpec = AT_KEYEXCHANGE;
if ( CertSetCertificateContextProperty( m_clientCertificate, CERT_KEY_PROV_INFO_PROP_ID, 0, &privateKeyData ) )

验证我可以访问私钥(工作,输出与我硬编码到应用程序中的数据完全相同的数据)

byte privateKeyBuffer[2048];
DWORD privateKeyBufferSize = ARRAY_NUM_BYTES(privateKeyBuffer);
memZero(privateKeyBuffer, privateKeyBufferSize);
if(CryptExportKey(cryptKey, 0, PRIVATEKEYBLOB, 0, privateKeyBuffer, &privateKeyBufferSize))
{
   TRACE("Got private key!");
   LOG_BUFFER(privateKeyBuffer, privateKeyBufferSize);
}

尝试验证客户端证书是否按预期工作

char certNameBuffer[128] = "";
char certUrlBuffer[128] = "";
CertGetNameString(testValue, CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, certNameBuffer, ARRAY_NUM_BYTES(certNameBuffer));
CertGetNameString(testValue, CERT_NAME_URL_TYPE , 0, NULL, certUrlBuffer, ARRAY_NUM_BYTES(certUrlBuffer));
TRACE("SSL Certificate %s [%s]", certNameBuffer, certUrlBuffer);
HCRYPTPROV_OR_NCRYPT_KEY_HANDLE privateKey;
DWORD privateKeyType;
BOOL freeKeyAfter = false;
if(CryptAcquireCertificatePrivateKey(testValue, CRYPT_ACQUIRE_NO_HEALING, NULL, &privateKey, &privateKeyType, &freeKeyAfter))
{
    HCRYPTPROV privateKeyProvider = static_cast<HCRYPTPROV>(privateKey);
    HCRYPTKEY privateKeyHandle;
    if(CryptGetUserKey(privateKeyProvider, privateKeyType, &privateKeyHandle))
    {
        NEbyte privateKeyBuffer[2048];
        DWORD privateKeyBufferSize = NE_ARRAY_NUM_BYTES(privateKeyBuffer);
        neMemZero(privateKeyBuffer, privateKeyBufferSize);
        if(CryptExportKey(privateKeyHandle, 0, PRIVATEKEYBLOB, 0, privateKeyBuffer, &privateKeyBufferSize))
        {
            NE_TRACE("Got private key!");
            HTTP_LOG_BUFFER(neGetGlobalTraceLog(), "Key", "", privateKeyBuffer, privateKeyBufferSize);
        }

在这个阶段,找到了私钥,但是对CryptExportKey的调用因NTE_BAD_KEY_STATE而失败。当我尝试使用WinHTTP的客户端证书时,我得到ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY。如果有人想知道,我告诉WinHTTP使用以下代码的客户端证书:

if ( !WinHttpSetOption( handle,
                                WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
                                const_cast<PCERT_CONTEXT>(m_clientCertificate),
                                sizeof( CERT_CONTEXT ) ) )
        {
            HTTP_LOG_ERROR( getLog(), "Setting the client certificate failed with error code %x", GetLastError() );
        }

我看到它的方式,直到我能以某种方式链接私钥和证书在一起,使它,以便我可以使用cryptacquirecercertificateprivatekey与CryptExportKey获得密钥数据回来,WinHTTP不站是成功的机会。

有什么想法,为什么我似乎不能得到我的证书使用私钥?

我没有设法使这种方法工作。相反,我最终使用了PFXImportCertStore以及包含我想要使用的证书和私钥的原始.p12文件。

从我所看到的,似乎PFXImportCertStore将在内存中创建一个新的存储。我唯一没能弄清楚的是,私钥是否也存储在内存中,或者它是否最终永久存储在PC上的某个地方。如果我找到了答案,我会更新这个答案。

m_clientCertificateStoreHandle = PFXImportCertStore(&pfxData, certificatePassword, 0);
if(NULL != m_clientCertificateStoreHandle)
{
m_clientCertificateHandle = CertFindCertificateInStore( m_clientCertificateStoreHandle, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0, CERT_FIND_ANY, NULL, NULL );
}

最新更新