title: Creating a temporary client certificate (including a private key)
body: <p>As part of an application I am writing, I wish for my program to temporarily install a certificate on the local machine and for WinHTTP to use it as the client certificate when connecting to a web server. The aim of this is help protect the web server from unauthorised access (this certificate is only a layer of the security - I know someone could extract it from the .exe). I do not want the user to have to install the certificate and I do not want the certificate to be left on the PC when the application is not running.</p>
  
  <p>At the moment, I'm trying this:</p>
  
  <p>Install the certificate manually from a .p12 file</p>
  
  <p>Use a C++ application to get the binary data out of the local certificate and into a C array in my application (using CryptExportKey and PCCERT_CONTEXT::pbCertEncoded)</p>
  
  <p>Uninstall the certificate</p>
  
  <p>When the application boots:</p>
  
  <p>Open a temporary store for the certificate</p>
  
  <pre><code>m_certificateStoreHandle = CertOpenStore( CERT_STORE_PROV_MEMORY, 0, NULL, 0, NULL );
  </code></pre>
  
  <p>Call CertAddEncodedCertificateToStore to add the certificate</p>
  
  <pre><code>CertAddEncodedCertificateToStore( m_certificateStoreHandle,
  X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
  reinterpret_cast&lt; const BYTE * &gt;( certificateData ),
  dataSize, 
  CERT_STORE_ADD_REPLACE_EXISTING,
  &amp;m_clientCertificate )
  </code></pre>
  
  <p>Call CryptAcquireContext to get somewhere to store the private key (I change the name of the key on each run for the moment - ideally I plan to use CRYPT_VERIFYCONTEXT to make the key non-persistant, but that's something to ignore for now)</p>
  
  <pre><code>HCRYPTPROV cryptProvider = NULL;
  HCRYPTKEY cryptKey = NULL;
  CryptAcquireContext( &amp;cryptProvider, "MyTestKeyNumber123", NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET )
  </code></pre>
  
  <p>Call CryptImportKey to load the private key into the key store</p>
  
  <pre><code>CryptImportKey( cryptProvider, reinterpret_cast&lt; BYTE * &gt;( privateKey ), keySize, 0, CRYPT_EXPORTABLE, &amp;cryptKey )
  </code></pre>
  
  <p>Call CertSetCertificateContextProperty to link the certificate to the private key</p>
  
  <pre><code>char containerName[128];
  DWORD containerNameSize = ARRAY_NUM_BYTES(containerName);
  char providerName[128];
  DWORD providerNameSize = ARRAY_NUM_BYTES(providerName);
  
  CryptGetProvParam(cryptProvider, PP_CONTAINER, reinterpret_cast&lt;byte *&gt;(containerName), &amp;containerNameSize, 0)
  CryptGetProvParam(cryptProvider, PP_NAME, reinterpret_cast&lt;byte *&gt;(providerName), &amp;providerNameSize, 0)
  
  WCHAR containerNameWide[128];
  convertCharToWChar(containerNameWide, containerName);
  WCHAR providerNameWide[128];
  convertCharToWChar(providerNameWide, providerName);
  
  CRYPT_KEY_PROV_INFO privateKeyData;
  neMemZero(&amp;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, &amp;privateKeyData ) )
  </code></pre>
  
  <p>Verify I can access the private key (Works, outputs exactly the same data as I have hardcoded into the application)</p>
  
  <pre><code>byte privateKeyBuffer[2048];
  DWORD privateKeyBufferSize = ARRAY_NUM_BYTES(privateKeyBuffer);
  memZero(privateKeyBuffer, privateKeyBufferSize);
  if(CryptExportKey(cryptKey, 0, PRIVATEKEYBLOB, 0, privateKeyBuffer, &amp;privateKeyBufferSize))
  {
     TRACE("Got private key!");
     LOG_BUFFER(privateKeyBuffer, privateKeyBufferSize);
  }
  </code></pre>
  
  <p>Attempt to verify the client certificate works as expected</p>
  
  <pre><code>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, &amp;privateKey, &amp;privateKeyType, &amp;freeKeyAfter))
  {
      HCRYPTPROV privateKeyProvider = static_cast&lt;HCRYPTPROV&gt;(privateKey);
      HCRYPTKEY privateKeyHandle;
      if(CryptGetUserKey(privateKeyProvider, privateKeyType, &amp;privateKeyHandle))
      {
          NEbyte privateKeyBuffer[2048];
          DWORD privateKeyBufferSize = NE_ARRAY_NUM_BYTES(privateKeyBuffer);
          neMemZero(privateKeyBuffer, privateKeyBufferSize);
          if(CryptExportKey(privateKeyHandle, 0, PRIVATEKEYBLOB, 0, privateKeyBuffer, &amp;privateKeyBufferSize))
          {
              NE_TRACE("Got private key!");
              HTTP_LOG_BUFFER(neGetGlobalTraceLog(), "Key", "", privateKeyBuffer, privateKeyBufferSize);
          }
  </code></pre>
  
  <p>At this stage, the private key is found but the call to CryptExportKey fails with NTE_BAD_KEY_STATE. When I try to use the client certificate with WinHTTP, I get ERROR_WINHTTP_CLIENT_CERT_NO_ACCESS_PRIVATE_KEY. If anyone is wondering, I tell WinHTTP to use the client certificate with this code:</p>
  
  <pre><code>if ( !WinHttpSetOption( handle,
                                  WINHTTP_OPTION_CLIENT_CERT_CONTEXT,
                                  const_cast&lt;PCERT_CONTEXT&gt;(m_clientCertificate),
                                  sizeof( CERT_CONTEXT ) ) )
          {
              HTTP_LOG_ERROR( getLog(), "Setting the client certificate failed with error code %x", GetLastError() );
          }
  </code></pre>
  
  <p>The way I see it, until I can somehow link the private key and the certificate together and make it so that I can use CryptAcquireCertificatePrivateKey with CryptExportKey to get the key data back out, WinHTTP doesn't stand a chance of being succesful. </p>
  
  <p>Any thoughts as to why I can't seem to get my certificate to use the private key?</p>