Reset your USB device on Ubuntu when you can't unplug it

Hello there,

sometime ago an USB device stopped working at a POS -Point of Sale- after it was running for a long period.

Unknown reason, after searching, it had some kind of EMP (electromagnetic pulse): something happens between the USB connectors (motherboard), cable... whatever.

Manual solution was not valid: unplug/plug the USB cable or powercord when the problem appeared (we know it as soon as it doesn't respond the operations).

So the action is to reset its module/driver (OS) and the USB device.


First issue

The OS module in this case is CDC_ACM, ok, but the path to communicate with the device (using netcat/socat tools) is not the same to restart it.

We must guess as much as device's information as we can to guarante that we reset the exact one.

How do we do this?

Let's assume the terminal path is "/dev/ttyACM0", device is connected, open your terminal and type this:
>udevadm info -q path -n /dev/ttyACM0
/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0

With this we get an address of the device where we'll get extract more info about it.

The following command shows:
>udevadm info -a -p /devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0

Udevadm info starts with the device specified by the devpath and then
walks up the chain of parent devices. It prints for every device
found, all possible attributes in the udev rules key format.
A rule to match, can be composed by the attributes of the device
and the attributes from one single parent device.

  looking at device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0/tty/ttyACM0':
    KERNEL=="ttyACM0"
    SUBSYSTEM=="tty"
    DRIVER==""

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2/1-1.2:1.0':
    KERNELS=="1-1.2:1.0"
    SUBSYSTEMS=="usb"
    DRIVERS=="cdc_acm"
    ATTRS{bInterfaceClass}=="02"
    ATTRS{bmCapabilities}=="6"
    ATTRS{bInterfaceSubClass}=="02"
    ATTRS{bInterfaceProtocol}=="01"
    ATTRS{bNumEndpoints}=="01"
    ATTRS{supports_autosuspend}=="1"
    ATTRS{bAlternateSetting}==" 0"
    ATTRS{bInterfaceNumber}=="00"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2':
    KERNELS=="1-1.2"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="1.2"
    ATTRS{idVendor}=="11cb"
    ATTRS{speed}=="12"
    ATTRS{bNumInterfaces}==" 2"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="4"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}==" 50mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="c0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="0"
    ATTRS{bcdDevice}=="0100"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 1.10"
    ATTRS{urbnum}=="86"
    ATTRS{manufacturer}=="ACME Inc"
    ATTRS{removable}=="removable"
    ATTRS{idProduct}=="0219"
    ATTRS{bDeviceClass}=="02"
    ATTRS{product}=="Trident USB Device 1.1"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1/1-1':
    KERNELS=="1-1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="01"
    ATTRS{devpath}=="1"
    ATTRS{idVendor}=="8087"
    ATTRS{speed}=="480"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="2"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="  0mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="4"
    ATTRS{bcdDevice}=="0000"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="54"
    ATTRS{removable}=="fixed"
    ATTRS{idProduct}=="0024"
    ATTRS{bDeviceClass}=="09"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0/usb1':
    KERNELS=="usb1"
    SUBSYSTEMS=="usb"
    DRIVERS=="usb"
    ATTRS{bDeviceSubClass}=="00"
    ATTRS{bDeviceProtocol}=="00"
    ATTRS{devpath}=="0"
    ATTRS{idVendor}=="1d6b"
    ATTRS{speed}=="480"
    ATTRS{bNumInterfaces}==" 1"
    ATTRS{bConfigurationValue}=="1"
    ATTRS{bMaxPacketSize0}=="64"
    ATTRS{authorized_default}=="1"
    ATTRS{busnum}=="1"
    ATTRS{devnum}=="1"
    ATTRS{configuration}==""
    ATTRS{bMaxPower}=="  0mA"
    ATTRS{authorized}=="1"
    ATTRS{bmAttributes}=="e0"
    ATTRS{bNumConfigurations}=="1"
    ATTRS{maxchild}=="2"
    ATTRS{bcdDevice}=="0305"
    ATTRS{avoid_reset_quirk}=="0"
    ATTRS{quirks}=="0x0"
    ATTRS{serial}=="0000:00:1a.0"
    ATTRS{version}==" 2.00"
    ATTRS{urbnum}=="24"
    ATTRS{manufacturer}=="Linux 3.5.0-54-generic ehci_hcd"
    ATTRS{removable}=="unknown"
    ATTRS{idProduct}=="0002"
    ATTRS{bDeviceClass}=="09"
    ATTRS{product}=="EHCI Host Controller"

  looking at parent device '/devices/pci0000:00/0000:00:1a.0':
    KERNELS=="0000:00:1a.0"
    SUBSYSTEMS=="pci"
    DRIVERS=="ehci_hcd"
    ATTRS{irq}=="23"
    ATTRS{subsystem_vendor}=="0x1043"
    ATTRS{broken_parity_status}=="0"
    ATTRS{class}=="0x0c0320"
    ATTRS{companion}==""
    ATTRS{consistent_dma_mask_bits}=="32"
    ATTRS{dma_mask_bits}=="32"
    ATTRS{local_cpus}=="ff"
    ATTRS{device}=="0x1c2d"
    ATTRS{uframe_periodic_max}=="100"
    ATTRS{msi_bus}==""
    ATTRS{local_cpulist}=="0-7"
    ATTRS{vendor}=="0x8086"
    ATTRS{subsystem_device}=="0x844d"

  looking at parent device '/devices/pci0000:00':
    KERNELS=="pci0000:00"
    SUBSYSTEMS==""
    DRIVERS==""

We have several devices and we want to reset one USB device, not all.

So we analyze the above output checking for some attributes that can match with our 'ACME' device: manufacturer and product (name). SUBSYSTEMS and DRIVERS attributes must be "usb".

If you know more about the device like idVendor, idProduct... you will have more precision to find it.

Ok, it seems our device info is in the section (path):
'/devices/pci0000:00/0000:00:1a.0/usb1/1-1/1-1.2'


Second issue

Unfortunately, this path cannot be used to reset the device, but it has info we need.

To get the 'real' path of the USB device, we need to find the 'busnum' and 'devnum' attributes from the device section:

    ATTRS{busnum}=="1"
    ATTRS{devnum}=="4"

Finally we can compose the USB path:
/dev/bus/usb/{busnum}/{devnum} or
/dev/bus/usb/001/004


Resetting the module and USB device

In terminal you can unload/load the CDC_ACM module like this (admin mode):
>rmmod cdc_acm
>modprobe cdc_acm

After that, in C/C++ using the USB path we do the following:
    int fd = open(USBdevicePath, O_WRONLY);
    if (fd < 0) {       
        printf ("Error opening USB device path\n");
    }
    else
    {
        printf("Resetting USB device %s\n", USBdevicePath);

        rc = ioctl(fd, USBDEVFS_RESET, 0);

        if (rc < 0) {
          printf ("Error in ioctl\n");
        }
        else
        {
          printf("Reset successful\n");
        }

        close(fd);
     }


If there is not any error, your USB device will respond again at your commands.

Generate dump file for fatal C++ exceptions (Windows)

You have already checked your trace or log files from your application (C++) on production environment (Release) and you have not clue where the program really crashes.

It's time to generate a dump file and analyze it with Microsoft WinDbg tool.

Speaking about code, when can we generate this dump file? The short answer is when the crash happens, but when it does, we normally can't control the program flow.

If your program is windowed (GUI), text-mode (console) or a service, it differs the mode which you can do it: Messaging system (WM_QUIT, WM_CLOSE...), signals (SIGABRT, SIGTERM...)... 

Even these crashes generate exceptions we usually can't catch (try-catch). These exceptions are invalid memory access, memory corruption, invalid parameters... and Windows (OS) shows us a windowed message like "The program cannot continue" or the app dies silently. We have usual suspects and we start with trial/error tests in development to guess what it can be happening.

Ok, no more bla bla bla...

Dump File


Before we generate the dump file, we must set various exception handlers and try to prevent that others (third-party DLLs, headers or tools of our process) handle them.

#include <windows.h>
#include <winuser.h>
#include <dbghelp.h>
#include <signal.h>
#include <mutex>
#include <string>

#pragma comment(lib, "Dbghelp.lib")

void setExceptionHandlers()
{
 SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
 SetUnhandledExceptionFilter(unhandledException);
 AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)&VectoredHandler);
 _set_invalid_parameter_handler(invalidParameter);
 _set_purecall_handler(pureVirtualCall);
 signal(SIGABRT, sigAbortHandler); 
 _set_abort_behavior(0, 0);
 EnableCrashingOnCrashes(); 
 PreventSetUnhandledExceptionFilter(); 
}

We should call this method as soon as our program starts, in our main thread or even another like our log subsystem (thread).

Let's analyze the calls our last method does.

SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
We use 'SetErrorMode' to do not display message boxes, Windows Error Reporting dialog...
SetUnhandledExceptionFilter(unhandledException);
We create a central method ('unhandledException') to catch exceptions and then we use 'SetUnhandledExceptionFilter' to bind exceptions like memory access violations, division by zero...
static std::mutex unhandledExceptionMx;

static LONG WINAPI unhandledException(EXCEPTION_POINTERS* excpInfo = NULL)
{
 unhandledExceptionMx.lock();

 if (excpInfo == NULL) 
 {
  __try
  {
   RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
  }
  __except (exceptionHandler(GetExceptionInformation()), EXCEPTION_EXECUTE_HANDLER)
  {
  }
 }
 else
 {
  exceptionHandler(excpInfo);
 }

 unhandledExceptionMx.unlock();

 return EXCEPTION_EXECUTE_HANDLER;
}

Two or more exceptions can be raised at same time so we set up a mutex to serialize access (unhandledExceptionMx). A new custom method ('exceptionHandler') appears and it will be called when we have info about the exceptions which it has just happened, but if we haven't info, we must raise an exception (programmatically as a breakpoint in debug mode) to generate a context/info and then call our exception handler method.

Finally in this method ('exceptionHandler') we'll generate the dump file and can take other actions.

static void exceptionHandler(EXCEPTION_POINTERS* excpInfo)
{
 //Generate MiniDump
 MINIDUMP_EXCEPTION_INFORMATION mei;
 mei.ClientPointers = FALSE;
 mei.ThreadId = GetCurrentThreadId();
 mei.ExceptionPointers = ExceptionInfo;

 string strlFich = "minidump.txt";

 HANDLE hFile = NULL;
 
 hFile = CreateFileA(strlFich.c_str(), GENERIC_WRITE, FALSE, NULL, CREATE_NEW, 0, NULL);

 if (hFile != NULL)
 {
  MiniDumpWriteDump(
   GetCurrentProcess(),
   GetCurrentProcessId(),
   hFile,
   MiniDumpNormal,
   &mei,
   nullptr,
   nullptr
  );

  CloseHandle(hFile);
 }

 //***TO DO: Take another action to finish or close whetever we want, even print/log a message.
}

To do this, we fill a struct (MINIDUMP_EXCEPCTION_INFORMATION), create a file Handle and use MiniDumpWriteDump function.

Back to our main function ('setExceptionHandlers'), the next line is:
AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)&VectoredHandler);
It is for catching memory heap exceptions and the method ('VectoredHandler') does the following:
LONG CALLBACK VectoredHandler(_EXCEPTION_POINTERS *ExceptionInfo)
{
 LONG lRet = EXCEPTION_CONTINUE_SEARCH;

 if (0xC0000374 == ExceptionInfo->ExceptionRecord->ExceptionCode)
 {
  unhandledException(ExceptionInfo);
  lRet = EXCEPTION_EXECUTE_HANDLER;
 }

 return lRet;
}
The following methods are self-descriptive in ('setExceptionHandlers'):
_set_invalid_parameter_handler(invalidParameter);
_set_purecall_handler(pureVirtualCall);
signal(SIGABRT, sigAbortHandler);
_set_abort_behavior(0, 0);
They call the next custom methods to catch invalid parameter, virtual call and abort exceptions:
static void invalidParameter(const wchar_t* expr, const wchar_t* func,
 const wchar_t* file, unsigned int line, uintptr_t reserved)
{
        //in release mode we don't usually have info to get expr, func, file or line data
 unhandledException();
}

static void pureVirtualCall()
{
 unhandledException();
}

static void sigAbortHandler(int sig)
{
 //this is required, otherwise if there is another thread
 //simultaneously tries to abort process will be terminated
 signal(SIGABRT, sigAbortHandler);
 unhandledException();
}
The penultimate function allows our code to handle the exceptions:
void EnableCrashingOnCrashes()
{
 typedef BOOL(WINAPI *tGetPolicy)(LPDWORD lpFlags);
 typedef BOOL(WINAPI *tSetPolicy)(DWORD dwFlags);
 const DWORD EXCEPTION_SWALLOWING = 0x1;

 HMODULE kernel32 = LoadLibraryA("kernel32.dll");
 tGetPolicy pGetPolicy = (tGetPolicy)GetProcAddress(kernel32,
  "GetProcessUserModeExceptionPolicy");
 tSetPolicy pSetPolicy = (tSetPolicy)GetProcAddress(kernel32,
  "SetProcessUserModeExceptionPolicy");

 if (pGetPolicy && pSetPolicy)
 {
  DWORD dwFlags;
  if (pGetPolicy(&dwFlags))
  {
   // Turn off the filter
   pSetPolicy(dwFlags & ~EXCEPTION_SWALLOWING);
  }
 }
 BOOL insanity = FALSE;

 SetUserObjectInformationA(GetCurrentProcess(),
  /*UOI_TIMERPROC_EXCEPTION_SUPPRESSION*/ 7, //value 7 from MSDN, try macro def
  &insanity, sizeof(insanity));
}
And the final line is even the most important because it will be prevent another part of our code (library, DLL...) can mask our exception handlers, whatever our app is 32 or 64 bits:
static BOOL PreventSetUnhandledExceptionFilter()
{
 HMODULE hKernel32 = LoadLibrary(_T("kernel32.dll"));
 if (hKernel32 == NULL) return FALSE;
 void *pOrgEntry = GetProcAddress(hKernel32, "SetUnhandledExceptionFilter");
 if (pOrgEntry == NULL) return FALSE;

#ifdef _M_IX86
 // Code for x86:
 // 33 C0                xor         eax,eax  
 // C2 04 00             ret         4 
 unsigned char szExecute[] = { 0x33, 0xC0, 0xC2, 0x04, 0x00 };
#elif _M_X64
 // 33 C0                xor         eax,eax 
 // C3                   ret  
 unsigned char szExecute[] = { 0x33, 0xC0, 0xC3 };
#else
#error "The following code only works for x86 and x64!"
#endif

 SIZE_T bytesWritten = 0;
 BOOL bRet = WriteProcessMemory(GetCurrentProcess(),
  pOrgEntry, szExecute, sizeof(szExecute), &bytesWritten);
 return bRet;
}

Example

Use the next dummy main method (Visual C++ 2017 with Release Mode) to force a crash:
int main()
{
 setExceptionHandlers();

 int* objectInt = new int;

 delete objectInt;

 printf("Press enter to continue:\n");
 getchar();
 
 delete objectInt; //excep

 printf("Hello!");

    return 0;
}
We'll have a file called 'minidump.txt' in the folder where we run the example. 

Then, we open WinDbg and follow these actions: 

1. File -> Open Crash Dump and select it. 

2. In Command Window, type '.ecxr'

3. If we're lucky then a new window will show up with the function which crashes and we have just finished. If not, in Command Window we will see more info about the exception: register values and the function with memory address, assembler function, etc... and select View -> Call Stack.

4. The Calls Window will show up and wait a second, then we'll have the call stack. We can check some options like Source, Source args... and we must find a function of our code in the stack. Once it is found, we click on it and a new window will appear with our desired function.
If we can't find a function of our program in the stack, we still can use the memory address (hex) of the exception to use it when we debug our Release program. To do this, we must set up a break point as soon as we can, and use the address to try to guess which function can be crashing.


That's all!

Please post any error or improvement in comment section.

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.

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...