How to send a client Certificate with libcurl on Windows (C/C++)

Hello there,

if you have a C/C++ project with libcurl (compiled with WinSSL or SChannel) you won't be able to send a SSL client certificate from Windows Certificate Store (Run => mmc.exe | Add Complement | Certificates ) directly.

This is important if the server is configured with mutual authentication, the TLS/SSL connections which it accepts. https://en.wikipedia.org/wiki/Mutual_authentication

As you just read, you can add support to libcurl... but it needs a bit of work and knowledge: SChannel API, libcurl sources...

There is another solution, libcurl compiled with OpenSSL.

Now, with OpenSSL, we must provide the CA chain (root, intermediates certificates and so on...) from Windows Store to libcurl.

Both CA chain and client certificate must be converted from Windows Store 'format' to OpenSSL 'format' to use in libcurl parameters. (PFX, PKCS12, X.509...)

You will need have used certificates, libcurl and OpenSSL so I will skip info and handling some errors in the code snippets.

First step - CA bundle file

We must generate a file with public CA (certificate authorities) for CURLOPT_CAINFO parameter. The disadvantage is we must export ALL certificates because we don't know what certificate the server will send to us. So, we don't want to create or update this file for each new connection, we can do the first time the app starts and/or one time per day, or when we think any of the certificates can be expired.

To do this we need CryptoAPI ("crypt32.lib") and use it via pragma or IDE options.

We will need to initialize *OpenSSL and CAPI engine as it follows:

(*Initializing OpenSSL depends on which version you use: implicit or explicit)

//...

static ENGINE* static_Engine = NULL;

//...

one_of_yours_init_constructors()
{
 //... after initialising OpenSSL (it depends)
 ENGINE_load_capi();

 static_Engine = ENGINE_by_id("capi");

 if (static_Engine)
 {
  ENGINE_init(static_Engine);
 }
 //...
}


Next we extract and create a PEM file from local Windows Certificate Store, because those certificates are public there is no need to protect the file, everyone can access to them on Internet:


bool  ExtractCAs_From_WindowsStore_CreatePEM ( char* FileName )
{
 bool bRet = false;

 if (FileName != nullptr )
 {
  int lNumAds = 0;
  HCERTSTORE hStore;
  PCCERT_CONTEXT pContext = NULL;
  X509 *x509;

  //2 iterations to get certificates, first from 'CA' and then 'ROOT' from Windows Store
  char achStores[2][5] = { "CA", "ROOT" };

  FILE* pFile = fopen(FileName, "w+t");

  if (pFile != nullptr)
  {
   for (int i = 0; i < 2; i++)
   {
    hStore = CertOpenSystemStoreA(0, achStores[i]);

    if (hStore)
    {
     while (pContext = CertEnumCertificatesInStore(hStore, pContext))
     {
      x509 = NULL;
      x509 = d2i_X509(NULL, (const unsigned char**)&(pContext->pbCertEncoded), pContext->cbCertEncoded);

      if (x509)
      {
       if (PEM_write_X509(pFile, x509))
       {
        lNumAds++;
       }

       X509_free(x509);
      }
     }

     CertFreeCertificateContext(pContext);
     CertCloseStore(hStore, 0);
    }
    else
    {
     break;
    }
   }

   if (lNumAds > 0) //there is at least one added!
   {
    bRet = true;
   }

   fclose(pFile);
  }
 }

 return bRet;
}

Call this method when the program starts or before the first connection is made.

And set the option (file name) to the cURL handle (connection to be made):


char achCABundle[] = "cabundle.pem";
//...
ExtractCAs_From_WindowsStore_CreatePEM ( achCABundle );
//...
curl_easy_setopt( handleCURL, CURLOPT_CAINFO, achCABundle);


Second step - Client certificate (public key)

For the client certificate we will need two PEM files: one for its public key (to send to server) and one for its private key only for our program (libcurl TLS/SSL).

Here we extract the public key and generate the PEM file that we set via the two latest sentences (cURL options) on the following code snippet:


char achPublicClientCert[] = "public_cert.pem";
char achClientCertName[] = "OUR CLIENT CERT NAME FROM WINDOWS STORE";

Extract_PublicKey_ClientCert_FromWindowsStore_And_GeneratePEM(achPublicClientCert, achClientCertName, 0);

curl_easy_setopt( handleCURL, CURLOPT_SSLCERTTYPE, "PEM" );
curl_easy_setopt( handleCURL, CURLOPT_SSLCERT, achPublicClientCert );


As you see our new method ('Extract_PublicKey_ClientCert_FromWindowsStore_And_GeneratePEM') has 3 parameters:
- File name to store public cert.
- Name to use for extracting the certificate from Windows Store.
- An integer to indicate which certificate extract in case there are two or more with same Name:
0 - the first one we find, 1 - the first one that expires, 2 - the last one that expires

We will find our client certificate by subject and "MY" store from Windows Certificate Store, so if you need another approach to find it, it should be straightforward. Let's see its code snippet:


bool Extract_PublicKey_ClientCert_FromWindowsStore_And_GeneratePEM(char* FileName, const char* CertName,
                                                            unsigned int WhatCertificate)
{
 bool blRet = false;

 if (FileName != nullptr && CertName != nullptr && strlen(CertName) > 0)
 {
  HCERTSTORE  hMemoryStore;
  HCERTSTORE  hSystemStore;

  SYSTEMTIME st;
  GetLocalTime(&st);
  char achStoreString[] = { "MY" };

  PCCERT_CONTEXT  pDesiredCert = NULL;
  std::string strCer(CertName);
  int size = MultiByteToWideChar(CP_UTF8, 0, &strCer[0], (int)strCer.size(), NULL, 0);
  std::wstring wstrCert(size, 0);
  MultiByteToWideChar(CP_UTF8, 0, &strCer[0], (int)strCer.size(), &wstrCert[0], size);

  std::string strMy(achStoreString);
  size = MultiByteToWideChar(CP_UTF8, 0, &strMy[0], (int)strMy.size(), NULL, 0);
  std::wstring wstrMy(size, 0);
  MultiByteToWideChar(CP_UTF8, 0, &strMy[0], (int)strMy.size(), &wstrMy[0], size);

  //to compare certs
  FILETIME sSystemTime;
  SystemTimeToFileTime(&st, &sSystemTime);
  bool blValid = false;
  bool blFromLocalMachine = false;

  CRYPT_KEY_PROV_INFO *plInfoCert = NULL;

  //From CurrentUser first and if it's not from LocalMachine
  for (int i = 0; i < 2 && pDesiredCert == NULL; i++)
  {
   hSystemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL,
    ((i == 0) ? CERT_SYSTEM_STORE_CURRENT_USER : CERT_SYSTEM_STORE_LOCAL_MACHINE) |
    CERT_STORE_OPEN_EXISTING_FLAG |
    CERT_STORE_READONLY_FLAG,
    wstrMy.c_str());

   if (hSystemStore)
   {
    FILETIME sCompareTime;
    ZeroMemory(&sCompareTime, sizeof(sCompareTime));
    int nlIdxCert = -1;
    bool blChooseNow = true;

    if (WhatCertificate == 1 || WhatCertificate == 2)
    {
     blChooseNow = false; //two steps
    }

   back:
    int nlIdx = 0;
    pDesiredCert = NULL;
    blValid = false;

    do
    {
     pDesiredCert = CertFindCertificateInStore(
      hSystemStore,
      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
      0,
      //by subject (use engine with pinfo for searching key we want... check e_capi.c for more detail)
      CERT_FIND_SUBJECT_STR, 
      wstrCert.c_str(),
      pDesiredCert);

     //What certificate to extract, comparing times
     if (pDesiredCert)
     {
      blValid = true;
      FILETIME slBefore = pDesiredCert->pCertInfo->NotBefore;
      FILETIME slAfter = pDesiredCert->pCertInfo->NotAfter;

      //expired?
      if (CompareFileTime(&slBefore, &sSystemTime) > 0)
      {
       blValid = false;
      }
      else
      {
       if (CompareFileTime(&sSystemTime, &slAfter) > 0)
       {
        blValid = false;
       }
      }

      if (blValid) //we're done
      {
       blValid = false;

       if (!blChooseNow)
       {
        //At the beginning, we save it, to compare later
        if (sCompareTime.dwHighDateTime == 0 && sCompareTime.dwLowDateTime == 0)
        {
         nlIdxCert = nlIdx;
         sCompareTime = slAfter;
        }
        else
        {
         bool blSwitch = false;

         switch (WhatCertificate)
         {
          case 1: //before

           if (CompareFileTime(&slAfter, &sCompareTime) < 0)
           {
            blSwitch = true;
           }

           break;

          case 2: //after

           if (CompareFileTime(&slAfter, &sCompareTime) > 0)
           {
            blSwitch = true;
           }

           break;
         }

         if (blSwitch)
         {
          nlIdxCert = nlIndice;
          sCompareTime = slAfter;
         }
        }
       }
       else
       {
        //Time to choose or already chosen before!
        if ((nlIdxCert == -1) || (nlIdxCert == nlIdx))
        {
         DWORD dwlLen = 0;

         if (CertGetCertificateContextProperty
         (pDesiredCert, CERT_KEY_PROV_INFO_PROP_ID, NULL, &dwlLen))
         {
          plInfoCert = (CRYPT_KEY_PROV_INFO*)malloc(dwlLen);

          if (plInfoCert)
          {
           memset(plInfoCert, 0x0, dwlLen);

           if (CertGetCertificateContextProperty
           (pDesiredCert, CERT_KEY_PROV_INFO_PROP_ID, plInfoCert, &dwlLen))
           {

            if (i == 1)
            {
             blFromLocalMachine = true;
            }

            blValid = true;

            break; //end
           }
           else
           {
            free(plInfoCert);
            plInfoCert = NULL;

            blValid = false;
           }
          }
          else
          {
           blValid = false;
          }
         }
         else
         {
          blValid = false;
         }
        }
       }
      }
     }

     nlIdx++;

    } while (pDesiredCert != NULL);

    //Time to choose by the saved index
    if (!blChooseNow && nlIdxCert != -1)
    {
     blChooseNow = true;
     goto back;
    }

    CertCloseStore(hSystemStore, 0);
   }
  }

  if ((blValid) && (pDesiredCert != NULL))
  {

   hMemoryStore = CertOpenStore(
    CERT_STORE_PROV_MEMORY,    // Memory store
    0,                         // Encoding type
           // not used with a memory store
    NULL,                      // Use the default provider
    0,                         // No flags
    NULL);

   if (hMemoryStore)
   {
    //we add it to our memory store
    if (CertAddCertificateContextToStore(
     hMemoryStore,                // Store handle
     pDesiredCert,                // Pointer to a certificate
     CERT_STORE_ADD_USE_EXISTING,
     NULL))
    {
     //We provide an internal random password in the process (PFXExportCertStoreEx)
     char achPassword[256] = { 0 };
     srand(time(NULL));

     for (int i = 0; i < 255; i++)
     {
      achPassword[i] = rand() % 28 + 'a'; //28 alphabet letters
     }

     std::string strCerKey(achPassword);
     int size = MultiByteToWideChar(CP_UTF8, 0, &strCerKey[0], (int)strCerKey.size(), NULL, 0);
     std::wstring wstrCertKey(size, 0);
     MultiByteToWideChar(CP_UTF8, 0, &strCerKey[0], (int)strCerKey.size(), &wstrCertKey[0], size);

     SecureZeroMemory(achPassword, sizeof(achPassword));

     CRYPT_DATA_BLOB slPFX;
     ZeroMemory(&slPFX, sizeof(slPFX));

     //We don't know how big it's, two calls needed (first call to PFXExportCertStoreEx)
     blRet = PFXExportCertStoreEx(hMemoryStore, &slPFX, wstrCertClave.c_str(), NULL, EXPORT_PRIVATE_KEYS |
                                 REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY);

     if (!blRet) //fail or it has not private keys
     {
      X509* cert = d2i_X509(NULL,
       (const unsigned char **)&pDesiredCert->pbCertEncoded,
       pDesiredCert->cbCertEncoded);

      if (cert)
      {
       blRet = WriteX509_PEM (FileName, cert);
      }
      else
      {
       blRet = false;
      }

      X509_free(cert);
     }
     else
     {
      blRet = false;

      if (slPFX.cbData > 0) //reserve size
      {
       slPFX.pbData = (BYTE*)malloc(sizeof(BYTE)*slPFX.cbData);

       if (slPFX.pbData)
       {
        //second call
        if (PFXExportCertStoreEx(hMemoryStore, &slPFX, wstrCertClave.c_str(), NULL, EXPORT_PRIVATE_KEYS |
                                 REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY))
        {
         EVP_PKEY *pkey = NULL;
         X509 *cert = NULL;
         STACK_OF(X509) *ca = NULL;

         BIO* plBio = BIO_new_mem_buf(slPFX.pbData, slPFX.cbData);

         if (plBio)
         {
          PKCS12 *p12; //transform
          p12 = d2i_PKCS12_bio(plBio, NULL);

          if (p12)
          {
           if (PKCS12_parse(p12, strCerKey.c_str(), &pkey, &cert, &ca))
           {
            if (cert)
            {
             blRet = WriteX509_PEM(FileName, cert);
            }
           }
          }

          if (ca)
          {
           sk_X509_pop_free(ca, X509_free);
          }

          if (cert)
          {
           X509_free(cert);
          }

          if (pkey)
          {
           EVP_PKEY_free(pkey);
          }

          BIO_free_all(plBio);

          if (p12)
          {
           PKCS12_free(p12);
          }
         }
        }

        free(slPFX.pbData);
       }
      }
     }

     strCerKey.clear();
     wstrCertKey.clear();

    }

    CertCloseStore(hMemoryStore, 0);
   }

   if (pDesiredCert)
   {
    CertFreeCertificateContext(pDesiredCert);
   }

   if (plInfoCert)
   {
    free(plInfoCert);
    plInfoCert = NULL;
   }

  }
 }

 return blRet;
}

Aux method 'WriteX509_PEM' used by the previous method:


bool WriteX509_PEM (char* FileName, void* pX509)
{
 bool blRet = false;
 X509* x509 = (X509*)pX509;

 if ( (FileName != nullptr) && (x509 != nullptr) )
 {
  FILE* plFPub = fopen(FileName, "w+t");

  if (plFPub)
  {
   if (PEM_write_X509(plFPub, x509))
   {
    blRet = true;
   }

   fclose(plFPub);
  }
 }

 return blRet;
}

Third step - Client certificate (private key)

This step is very similar to the previous one, however we will protect the PEM file (private key of our client certificate) with a random password each time we generate the file.

We should not call these client certificate methods every time, only the first time (connection), when the certificate name may change, when the certificate could expiry (every hour) or whatever you think.

So, we declare the password of private key PEM file as a global variable and we protect it with WinCrypt API functions. We must set to libcurl parameters  the password, the file name and its format (PEM):


//...
#include "wincrypt.h"

char gachPrivateKeyPEMPassword[256]; //global, a long password
//...

//libCURL connection
char achPrivateClientCert[] = "private_cert.pem";
char achClientCertName[] = "OUR CLIENT CERT NAME FROM WINDOWS STORE";

//...

//Unprotect password variable of private key PEM file
CryptUnprotectMemory(gachPrivateKeyPEMPassword, sizeof(gachPrivateKeyPEMPassword),
                     CRYPTPROTECTMEMORY_SAME_PROCESS);

//Generate a new PEM file and a new password value
Extract_PrivateKey_ClientCert_FromWindowsStore_And_GeneratePEM(achPrivateClientCert, 
                                        gachPrivateKeyPEMPassword, achClientCertName, 0);

curl_easy_setopt( handleCURL, CURLOPT_KEYPASSWD, gachPrivateKeyPEMPassword);
curl_easy_setopt( handleCURL, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt( handleCURL, CURLOPT_SSLKEY, achPrivateClientCert);

//Protect password
CryptProtectMemory(gachPrivateKeyPEMPassword, sizeof(gachPrivateKeyPEMPassword),
                     CRYPTPROTECTMEMORY_SAME_PROCESS);
//...

The method to extract private key from the certificate and generate the PEM file (using another aux method):

bool Extract_PrivateKey_ClientCert_FromWindowsStore_And_GeneratePEM(char* FileName, char* Password,
                                                     const char* CertName, unsigned int WhatCertificate)
{
 bool blRet = false;

 if (FileName != nullptr && CertName != nullptr && strlen(CertName) > 0 && Password != nullptr)
 {
  HCERTSTORE  hMemoryStore;
  HCERTSTORE  hSystemStore;
  
  SYSTEMTIME st;
  GetLocalTime(&st);
  char achStringStore[] = { "MY" };

  PCCERT_CONTEXT  pDesiredCert = NULL;
  std::string strCer(CertName);
  int size = MultiByteToWideChar(CP_UTF8, 0, &strCer[0], (int)strCer.size(), NULL, 0);
  std::wstring wstrCert(size, 0);
  MultiByteToWideChar(CP_UTF8, 0, &strCer[0], (int)strCer.size(), &wstrCert[0], size);

  std::string strMy(achStringStore);
  size = MultiByteToWideChar(CP_UTF8, 0, &strMy[0], (int)strMy.size(), NULL, 0);
  std::wstring wstrMy(size, 0);
  MultiByteToWideChar(CP_UTF8, 0, &strMy[0], (int)strMy.size(), &wstrMy[0], size);

  FILETIME sSystemTime;
  SystemTimeToFileTime(&st, &sSystemTime);
  bool blValid = false;
  bool blFromLocalMachine = false;

  CRYPT_KEY_PROV_INFO *plInfoCert = NULL;

  //Current User and if it's not there, we look at Local Machine
  for (int i = 0; i < 2 && pDesiredCert == NULL; i++)
  {
   hSystemStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, NULL,
    ((i == 0) ? CERT_SYSTEM_STORE_CURRENT_USER : CERT_SYSTEM_STORE_LOCAL_MACHINE) |
    CERT_STORE_OPEN_EXISTING_FLAG |
    CERT_STORE_READONLY_FLAG,
    wstrMy.c_str());

   if (hSystemStore)
   {
    FILETIME sCompareTime;
    ZeroMemory(&sCompareTime, sizeof(sCompareTime));
    int nlIdxCert = -1;
    bool blChooseNow = true;

    if (WhatCertificate == 1 || WhatCertificate == 2)
    {
     blChooseNow = false;
    }

   back:
    int nlIdx = 0;
    pDesiredCert = NULL;
    blValid = false;

    do
    {
     pDesiredCert = CertFindCertificateInStore(
      hSystemStore,
      X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
      0,
      CERT_FIND_SUBJECT_STR, //by subject
      wstrCert.c_str(),
      pDesiredCert);

     //Expired?
     if (pDesiredCert)
     {
      blValid = true;
      FILETIME slBefore = pDesiredCert->pCertInfo->NotBefore;
      FILETIME slAfter = pDesiredCert->pCertInfo->NotAfter;

      if (CompareFileTime(&slBefore, &sSystemTime) > 0)
      {
       blValid = false;
      }
      else
      {
       if (CompareFileTime(&sSystemTime, &slAfter) > 0)
       {
        blValid = false;
       }
      }

      if (blValid) //we're done
      {
       blValid = false;

       if (!blChooseNow)
       {
        //first comparison?
        if (slCompareTime.dwHighDateTime == 0 && sCompareTime.dwLowDateTime == 0)
        {
         nlIdxCert = nlIdx;
         sCompareTime = slAfter;
        }
        else
        {
         bool blSwitch = false;

         switch (WhatCertificate)
         {
         case 1: //before?

          if (CompareFileTime(&slAfter, &sCompareTime) < 0)
          {
           blSwitch = true;
          }

          break;

         case 2: //after?

          if (CompareFileTime(&slAfter, &sCompareTime) > 0)
          {
           blSwitch = true;
          }

          break;
         }

         if (blSwitch)
         {
          nlIdxCert = nlIdx;
          sCompareTime = slAfter;
         }
        }
       }
       else
       {
        //Time to choose
        if ((nlIdxCert == -1) || (nlIdxCert == nlIdx))
        {
         DWORD dwlLen = 0;

         if (CertGetCertificateContextProperty
         (pDesiredCert, CERT_KEY_PROV_INFO_PROP_ID, NULL, &dwlLen))
         {
          plInfoCert = (CRYPT_KEY_PROV_INFO*)malloc(dwlLen);

          if (plInfoCert)
          {
           memset(plInfoCert, 0x0, dwlLen);

           if (CertGetCertificateContextProperty
           (pDesiredCert, CERT_KEY_PROV_INFO_PROP_ID, plInfoCert, &dwlLen))
           {
            if (i == 1)
            {
             blFromLocalMachine = true;
            }

            blValid = true;

            break; //end
           }
           else
           {
            free(plInfoCert);
            plInfoCert = NULL;

            blValid = false;
           }
          }
          else
          {
           blValid = false;
          }
         }
         else
         {
          blValid = false;
         }
        }
       }
      }
     }

     nlIdx++;

    } while (pDesiredCert != NULL);

    //Time to choose if it wasn't
    if (!blChooseNow && nlIdxCert != -1)
    {
     blChooseNow = true;
     goto back;
    }

    CertCloseStore(hSystemStore, 0);
   }
  }

  if ((blValid) && (pDesiredCert != NULL))
  {

   hMemoryStore = CertOpenStore(
    CERT_STORE_PROV_MEMORY,    // Memory store
    0,                         // Encoding type
           // not used with a memory store
    NULL,                      // Use the default provider
    0,                         // No flags
    NULL);

   if (hMemoryStore)
   {
    //we add it
    if (CertAddCertificateContextToStore(
     hMemoryStore,                // Store handle
     pDesiredCert,                // Pointer to a certificate
     CERT_STORE_ADD_USE_EXISTING,
     NULL))
    {
     char alchPassword[256] = { 0 };
     srand(time(NULL));

     for (int i = 0; i < 255; i++)
     {
      alchPassword[i] = rand() % 28 + 'a';
     }

     std::string strCerKey(alchPassword);
     int size = MultiByteToWideChar(CP_UTF8, 0, &strCerKey[0], (int)strCerKey.size(), NULL, 0);
     std::wstring wstrCertKey(size, 0);
     MultiByteToWideChar(CP_UTF8, 0, &strCerKey[0], (int)strCerKey.size(), &wstrCertKey[0], size);

     SecureZeroMemory(alchPassword, sizeof(alchPassword));

     CRYPT_DATA_BLOB slPFX;
     ZeroMemory(&slPFX, sizeof(slPFX));

     //first call (we don't know size)
     blRet = PFXExportCertStoreEx(hMemoryStore, &slPFX, wstrCertKey.c_str(), NULL, EXPORT_PRIVATE_KEYS |
                               REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY); 

     //We use CAPI engine if crypt32 does not allow us to export private keys from the client certificate
     if (!blRet) 
     {
      //ENGINE_ctrl options provided by your favourite search engine, there is not 'official' documentation

      ENGINE_ctrl(static_Engine, ENGINE_CMD_BASE + 4, plInfoCert->dwKeySpec, NULL, 
          NULL); //CAPI_CMD_KEYTYPE (ENGINE_CMD_BASE + 4)
      ENGINE_ctrl(static_Engine, ENGINE_CMD_BASE + 8, plInfoCert->dwProvType, NULL, 
          NULL); //CAPI_CMD_SET_CSP_TYPE (ENGINE_CMD_BASE + 8)

      int len_0 = (int)wcslen(plInfoCert->pwszProvName) + 1;
      int nsize = WideCharToMultiByte(CP_UTF8, 0, plInfoCert->pwszProvName, len_0, NULL, 0, NULL, NULL);
      std::string strlProvName(nsize, 0);
      WideCharToMultiByte(CP_UTF8, 0, plInfoCert->pwszProvName, len_0, &strlProvName[0], nsize, 
                             NULL, NULL);
      //CAPI_CMD_SET_CSP_NAME (ENGINE_CMD_BASE + 7)
      ENGINE_ctrl(static_Engine, ENGINE_CMD_BASE + 7, strlProvName.length(), (void*)strlProvName.c_str(), 
                   NULL); 

      //Lookup by containername, neither subject nor friendly name
      //CAPI_CMD_LOOKUP_METHOD (ENGINE_CMD_BASE + 11)
      ENGINE_ctrl(static_Engine, ENGINE_CMD_BASE + 11, 3, NULL, NULL); 

      //CurrentUser or LocalMachine
      ENGINE_ctrl(static_Engine, ENGINE_CMD_BASE + 13, (blFromLocalMachine) ? 1 : 0, NULL, NULL);

      //Transform containername {xxxx-xxxx-xxxx-xxxx}
      len_0 = (int)wcslen(plInfoCert->pwszContainerName) + 1;
      nsize = WideCharToMultiByte(CP_UTF8, 0, plInfoCert->pwszContainerName, len_0, NULL, 0, NULL, NULL);
      std::string strlContainerName(nsize, 0);
      WideCharToMultiByte(CP_UTF8, 0, plInfoCert->pwszContainerName, len_0, &strlContainerName[0], nsize, NULL, 
                            NULL);

      EVP_PKEY *key = ENGINE_load_private_key(static_Engine, strlContainerName.c_str(), 0, 0); //private key

      if (key)
      {
       blRet = WritePrivateKey_PEM(FileName, Password, key);

       EVP_PKEY_free(key);
      }
     }
     else //it's not protected by password (export)
     {
      blRet = false;

      if (slPFX.cbData > 0) //size len
      {
       slPFX.pbData = (BYTE*)malloc(sizeof(BYTE)*slPFX.cbData);

       if (slPFX.pbData)
       {
        //second call
        if (PFXExportCertStoreEx(hMemoryStore, &slPFX, wstrCertClave.c_str(), NULL, EXPORT_PRIVATE_KEYS |
            REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY))
        {
         EVP_PKEY *pkey = NULL;
         X509 *cert = NULL;
         STACK_OF(X509) *ca = NULL;

         BIO* plBio = BIO_new_mem_buf(slPFX.pbData, slPFX.cbData);

         if (plBio)
         {
          PKCS12 *p12; //transform
          p12 = d2i_PKCS12_bio(plBio, NULL);

          if (p12)
          {
           if (PKCS12_parse(p12, strCerClave.c_str(), &pkey, &cert, &ca))
           {
            if (pkey)
            {
             blRet = WritePrivateKey_PEM(FileName, Password, key);
            }
           }
          }

          if (ca)
          {
           sk_X509_pop_free(ca, X509_free);
          }

          if (cert)
          {
           X509_free(cert);
          }

          if (pkey)
          {
           EVP_PKEY_free(pkey);
          }

          BIO_free_all(plBio);

          if (p12)
          {
           PKCS12_free(p12);
          }
         }
        }

        free(slPFX.pbData);
       }
      }
     }

     strCerKey.clear();
     wstrCertKey.clear();

    }

    CertCloseStore(hMemoryStore, 0);
   }

   if (pDesiredCert) //close
   {
    CertFreeCertificateContext(pDesiredCert);
   }

   if (plInfoCert)
   {
    free(plInfoCert);
    plInfoCert = NULL;
   }
  }
 }

 return blRet;
}

The aux function 'WritePrivateKey_PEM' generates a random password protecting the private key PEM file written:

bool WritePrivateKey_PEM(char* FileName, char* Password, void* pEVPPKEY)
{
 bool blRet = false;
 EVP_PKEY* pkey = (EVP_PKEY*)pEVPPKEY;

 if ((FileName != nullptr) && (pkey != nullptr) && (Password != nullptr))
 {
  ZeroMemory(Password, sizeof(char)*256);

  //random password
  for (int i = 0; i < (sizeof(char) * 256) - 1; i++)
  {
   alchPass[i] = rand() % 28 + 'a';
  }

  FILE* plFPri = fopen(FileName, "w+t");

  if (plFPri)
  {
   if (PEM_write_PrivateKey(plFPri, pkey, EVP_aes_256_cbc(),
    (unsigned char*)Password, int(strlen(Password)), NULL, NULL))
   {
    blRet = true;
   }

   fclose(plFPri);
  }
 }

 return blRet;
}

Summarize

When setting up a cURL connection (SSL/TLS) the code should be similar to the following:


//...

//CA bundle
char achCABundle[] = "cabundle.pem";
ExtractCAs_From_WindowsStore_CreatePEM ( achCABundle );

curl_easy_setopt( handleCURL, CURLOPT_CAINFO, achCABundle);

//Public & private key from client certificate
char achPublicClientCert[] = "public_cert.pem";
char achPrivateClientCert[] = "private_cert.pem";
char achClientCertName[] = "OUR CLIENT CERT NAME FROM WINDOWS STORE";

Extract_PublicKey_ClientCert_FromWindowsStore_And_GeneratePEM(achPublicClientCert, achClientCertName, 0);

curl_easy_setopt( handleCURL, CURLOPT_SSLCERTTYPE, "PEM" );
curl_easy_setopt( handleCURL, CURLOPT_SSLCERT, achPublicClientCert );

//Unprotect password variable of private key PEM file
CryptUnprotectMemory(gachPrivateKeyPEMPassword, sizeof(gachPrivateKeyPEMPassword),
                     CRYPTPROTECTMEMORY_SAME_PROCESS);

Extract_PrivateKey_ClientCert_FromWindowsStore_And_GeneratePEM(achPrivateClientCert, 
                                       gachPrivateKeyPEMPassword, achClientCertName, 0);

curl_easy_setopt( handleCURL, CURLOPT_KEYPASSWD, gachPrivateKeyPEMPassword);
curl_easy_setopt( handleCURL, CURLOPT_SSLKEYTYPE, "PEM");
curl_easy_setopt( handleCURL, CURLOPT_SSLKEY, achPrivateClientCert);

//Protect password
CryptProtectMemory(gachPrivateKeyPEMPassword, sizeof(gachPrivateKeyPEMPassword),
                    CRYPTPROTECTMEMORY_SAME_PROCESS);

//...

*The prefixes for some variables (a, b, n, ch, l...) mean 'array', 'char', 'bool', 'integer', 'local', 'global'... a custom hungarian notation.

*Important: The function PFXExportCertStoreEx is not totally thread-safe at this moment (Crypto API), it can generate crashes when exporting same PFX.  The program may fail randomly with "crypt32!_TlgEnableCallback".
To avoid it, for example you can add a mutex when calling the functions Extract_PublicKey/PrivateKey. 

Please feel free to comment and sorry for any bugs you find on code snippets.

No hay comentarios:

Publicar un comentario

How to compile OpenSSL with Visual Studio

Hi there! Sometimes there is not precompiled OpenSSL available for the latest Visual C++ runtime, and you need it. Here it is a quick gu...