Click here to Skip to main content
Click here to Skip to main content
Go to top

An UMDF Driver for a Virtual Smart Card Reader

, 30 Jan 2014
Rate this:
Please Sign up or sign in to vote.
A simple implementation of a driver for a virtual smart card reader, based on UMDF

Introduction

Working with smart cards and PKI stuff is an interesting field. You can see the state-of-art of computer security and how it can be used in the real environment from real users. But, sometimes, debugging and testing applications that work with smart cards is a real pain, especially when you have to deal with negative test cases and, as it often happens, you don't have many test smart cards to play with. What if you accidentally block a PIN? Or your CSP issues a wrong command, leaving the card in an inconsistent state? These and many other issues are quite common in this field, so one of the first things I realized when I started to work with smart cards was that I needed an emulator: something to play with without the risk of doing any damage. In this article, I will not speak about smart card OS emulation (perhaps it will be covered in the future...), but about a driver for a virtual smart card reader.
Searching the internet for virtual drivers leads you to find many interesting resources, but not the “guide for dummies” that I was hoping to find. I’m not an expert in driver developing; this is not by any means an article on “how to write drivers”. I’m just explaining my approach to a new subject, hoping that it will be useful for someone.

An alternative approach to the driver is just writing your own version on winscard.dll, and put it in the folder of the application you wish to debug. That's easier, in some cases, but has some drawbacks:

  • To fully emulate the behavior of Windows Smart Card Resource Manager, you must implement lots of functions.
  • It could be a pain to implement functions like SCardGetStatusChange, specially if you should mix real and simulated readers.
  • You can't replace system's real winscard.dll, since it's subject to system file protection, so it could be tricky to override it in some applications.

Having tried both approaches, I think that developing a driver is better, having learned some base lessons on how to do it (or having this article as a guide Smile | :) ).

Background

It needed just a few clicks on Google to realize that, to keep things easy, I had to use UMDF (User Mode Driver Framework) as a basis for the development of the driver. From my point of view, and my understanding of the subject, the main reasons are:

  • If you make a mistake, you don't get an ugly blue screen - so, easy developing
  • You can debug your code with your old good user mode debugger - eg. VS2008 - no need for kernel mode debugging - so, easy debugging
  • In my case performance is not critical, and the little overhead introduced by the framework is not a problem

These are the reasons that led me to use UMDF. Considering the little effort and the satisfaction with the result, I think it was a good choice.
The code is base on the UMDFSkeleton sample of WDK 7.1. I will first comment on the important points of the code, then I will explain the installation procedure.
As an addiction, the virtual card reader will communicate with a desktop application to provide the virtual smart card behavior; so, we'll see some IPC between a UMDF driver and a user process.

A Look at an UMDF Driver Structure

As I said, UMDF simplifies the development of a driver a lot. You just need to write some COM (actually, COM-like) objects implementing some core interfaces and that's it. Let's take a look at how it all works.

A user mode driver is like a COM object. So, like a COM object, we are building a DLL that exposes a DllGetClassObject function, that will be called by the UMDF framework to obtain a ClassFactory to create the actual driver object.
With ATL, it is very easy to create COM objects, so we'll use it to further simplify our job. The only function exposed by the DLL is:

STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out LPVOID* ppv) 
{ 
    return _AtlModule.DllGetClassObject(rclsid, riid, ppv); 
}  

Nothing strange here. The object we are creating (CMyDriver) must implement the IDriverEntry interface, that defines the main entry points of our driver. We can use the OnInitialize method to do all initialization stuff before the actual job begins, but it is not needed in our case.

The OnDeviceAdd method is called by the framework whenever a device is connected to the system that is managed by our driver. In our case, we create a CMyDriver object (through CMyDevice::CreateInstance method), that will hold a reference to a IWDFDevice object, created by the CreateDevice function.

Update:

Some users asked me to have more than one instance of the virtual reader; in this updated version I added this possibility using a single instance of the driver. A configuration file "BixVReader.ini" is used to read how many virtual reader should be registered with the driver, and some more configuration parameters, as we'll se in the following "Update" sections.

This is the initialization of CMyDriver:

 HRESULT
CMyDevice::CreateInstance(
                          __in IWDFDriver *FxDriver,
                          __in IWDFDeviceInitialize * FxDeviceInit
                          )

{
    inFunc
    SectionLogger a(__FUNCTION__);
    CComObject<CMyDevice>* device = NULL;
    HRESULT hr;

    OutputDebugString(L"[BixVReader]CreateInstance");    //
    // Allocate a new instance of the device class.
    //
    hr = CComObject<CMyDevice>::CreateInstance(&device);

    if (device==NULL)
    {
        return E_OUTOFMEMORY;
    }

    //
    // Initialize the instance.
    //
    device->AddRef();

    OutputDebugString(L"[BixVReader]SetLockingConstraint");    //
    FxDeviceInit->SetLockingConstraint(WdfDeviceLevel);

    CComPtr<IUnknown> spCallback;
    OutputDebugString(L"[BixVReader]QueryInterface");    //
    hr = device->QueryInterface(IID_IUnknown, (void**)&spCallback);

    CComPtr<IWDFDevice> spIWDFDevice;
    if (SUCCEEDED(hr))
    {
        OutputDebugString(L"[BixVReader]CreateDevice");    //
        hr = FxDriver->CreateDevice(FxDeviceInit, spCallback, &spIWDFDevice);
    }

    device->numInstances=GetPrivateProfileInt(L"Driver",L"NumReaders",1,L"BixVReader.ini");    

    WSADATA wsaData;
    int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) {
        OutputDebugString(L"[BixVReader]Error at WSAStartup()\n");
    }

    for (int i=0;i<device->numInstances;i++) {
        OutputDebugString(L"[BixVReader]CreateDeviceInterface");    //
        wchar_t name[10];
        swprintf(name,L"DEV%i",i);

        if (spIWDFDevice->CreateDeviceInterface(&SmartCardReaderGuid,name)!=0)    
            OutputDebugString(L"[BixVReader]CreateDeviceInterface Failed");
    }

    SAFE_RELEASE(device);    
    return hr;
}  

We don't want synchronization issues, so we use SetLockingConstraint(WdfDeviceLevel): only one event handler of the device can run at a given moment. Then we ask the IWDFDriver object to create a IWDFDevice.

These objects are the actual objects maintained by UMDF through which we interact with the underlying driver and device. Since these objects are tightly coupled, in CMyDevice we keep a reference to the IWDFDevice object.

Moreover, we need to call CreateDeviceInterface to create an interface for the device of a type specified by a GUID for each virtual reader managed by the driver. In our case, a Smart Card Reader. Each interface has a name assigned ("DEVx" in our case) to distinguish the recipient of a particular request in the queue (more on this later). This interface is automatically enabled by the framework.

We should note, at this point, that our CMyDevice objects implement some interfaces:

  • IPnpCallbackHardware
  • IPnpCallback
  • IRequestCallbackCancel

In the CreateDevice call, we passed spCallback (a pointer to the IUnknown interface of CMyDriver), to inform the UMDF framework that we want to be notified about some events. The framework, according to the interfaces implemented by the callback object, calls its methods when specific events are fired.

IPnpCallbackHardware contains methods to manage hardware insertion and removal.
IPnpCallback contains methods to manage lifetime events on the driver.
IRequestCallbackCancel contains method to manage deletion of I/O Request received by the device. We'll see it in detail later.

The first notification received by our driver is OnPrepareHardware: the hardware is ready and the driver should prepare to use it:

HRESULT CMyDevice::OnPrepareHardware(
                                     __in IWDFDevice* pWdfDevice
                                     )
{
    inFunc
        SectionLogger a(__FUNCTION__);
    // Store the IWDFDevice pointer
    m_pWdfDevice = pWdfDevice;

    // Configure the default IO Queue
    HRESULT hr = CMyQueue::CreateInstance(m_pWdfDevice, this);

    return hr;
}

We create the default queue for this driver, and attach a callback interface to it (a CMyQueue object that implements IQueueCallbackDeviceIoControl, that will receive I/O events notifications). A driver queue receives all I/O requests from the system when applications try to interact with our device.

Update:

The life cycle of a driver has several states that should be managed to avoid incorrect behaviours: a device starts in an idle state and is switched to a working state, and in case of various system events (shutdown, hibernation) can be disabled and re-enabled. Without going in deeper detail, the state we are interested in is called D0 state (device fully functional). We'll intercept transitions to and from this state to perform tasks concerned with device functionality. For out virtual driver this just means that we start and stop inter-process communication. We'll see later how this works. The UMDF framewrok exposes two functions to do this job, wich are OnD0Entry and OnD0Exit:

 HRESULT CMyDevice::OnD0Entry(IN IWDFDevice*  pWdfDevice,IN WDF_POWER_DEVICE_STATE  previousState) {
    SectionLogger a(__FUNCTION__);
    UNREFERENCED_PARAMETER(pWdfDevice);
    UNREFERENCED_PARAMETER(previousState);

    numInstances=GetPrivateProfileInt(L"Driver",L"NumReaders",1,L"BixVReader.ini");    
    readers.resize(numInstances);

    for (int i=0;i<numInstances;i++) {
        wchar_t section[300];
        char sectionA[300];
        swprintf(section,L"Reader%i",i);
        sprintf(sectionA,"Reader%i",i);

        int rpcType=GetPrivateProfileInt(section,L"RPC_TYPE",0,L"BixVReader.ini");
        if (rpcType==0)
            readers[i]=new PipeReader();
        else if (rpcType==1)
            readers[i]=new TcpIpReader();

        readers[i]->instance=i;
        readers[i]->device=this;
        GetPrivateProfileStringA(sectionA,"VENDOR_NAME","Bix",readers[i]->vendorName,300,"BixVReader.ini");
        GetPrivateProfileStringA(sectionA,"VENDOR_IFD_TYPE","VIRTUAL_CARD_READER",readers[i]->vendorIfdType,300,"BixVReader.ini");
        readers[i]->deviceUnit=GetPrivateProfileInt(section,L"DEVICE_UNIT",i,L"BixVReader.ini");
        readers[i]->protocol=0;

        readers[i]->init(section);

        DWORD pipeThreadID;
        readers[i]->serverThread=CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)ServerFunc,readers[i],0,&pipeThreadID);
    }

    return S_OK;
}
HRESULT CMyDevice::OnD0Exit(IN IWDFDevice*  pWdfDevice,IN WDF_POWER_DEVICE_STATE  newState) {
    SectionLogger a(__FUNCTION__);
    UNREFERENCED_PARAMETER(pWdfDevice);
    UNREFERENCED_PARAMETER(newState);
    // dovrei fermare tutti i thread in ascolto dei lettori
    shutDown();
    return S_OK;
}

void CMyDevice::shutDown() {
    SectionLogger a(__FUNCTION__);
    for (int i=0;i<numInstances;i++) {
        readers[i]->shutdown();
        delete readers[i];
    }
    numInstances=0;
} 

In the updated version of the driver we are also reading the configuration of the driver from BixVReader.ini. The parameters we can set are:

  • NumReaders: the number of virtual readers
  • RPC_PORT_BASE: the base port for Tcp/Ip communication
  • RPC_TYPE: are we using pipes or tcp/ip to communicate with the virtual reader app?
  • VENDOR_NAME: part of the the reader name returned by SCardListReaders
  • VENDOR_IFD_TYPE: part of the the reader name returned by SCardListReaders
  • DEVICE_UNIT: part of the the reader name returned by SCardListReaders (name, ifd type and device unit are concatenated to obtain the final reader name)
  • PIPE_NAME: named pipe for requests from driver to virtual reader (if pipes are used)
  • PIPE_EVENT_NAME: named pipe for events notification from virtual reader to driver (if pipes are used)
  • TCP_PORT: port number for requests from driver to virtual reader (if tpc/ip is used)
  • TCP_EVENT_PORT: port number for events notification from virtual reader to driver (if tpc/ip is used)

The BixVReader.ini file has to be placed in %SystemRoot% to be found by the driver. If this file is not present, or some parameters are not specificed, the following default values are used:

  • NumReaders: 1 virtual reader
  • RPC_PORT_BASE: 29500
  • RPC_TYPE: pipes
  • VENDOR_NAME: "Bix"
  • VENDOR_IFD_TYPE: "VIRTUAL_SCARD_READER"
  • DEVICE_UNIT: the index number of the virtual reader
  • PIPE_NAME: "SCardSimulatorDriver" followed by the reader index
  • PIPE_EVENT_NAME: "SCardSimulatorDriverEvents" followed by the reader index
  • TCP_PORT: PortBase + (reader index * 2)
  • TCP_EVENT_PORT: PortBase + (reader index * 2) +1

This is a sample BixVReader.ini file:

[Driver]
NumReaders=2

[Reader0]
RPC_TYPE=0
VENDOR_NAME=VirtualCard
VENDOR_IFD_TYPE=BixReader
DECIVE_UNIT=0

[Reader1]
RPC_TYPE=1
VENDOR_NAME=VirtualCard
VENDOR_IFD_TYPE=BixReader
DECIVE_UNIT=1

In this sample we have 2 readers, Named "VirtualCard BixReader 0" and "VirtualCard BixReader 1". Number 0 accepts connections from named pipes, number 1 from Tcp/Ip.

All these parameters are kept in a Reader class, that implements all the reader specific functions.

At this point, our driver is ready to receive requests and send appropriate responses to the system. This happens by means of calls made to the CMyQueue::OnDeviceIoControl:

STDMETHODIMP_ (void) CMyQueue::OnDeviceIoControl(
    __in IWDFIoQueue*     pQueue,
    __in IWDFIoRequest*   pRequest,
    __in ULONG            ControlCode,
         SIZE_T           InputBufferSizeInBytes,
         SIZE_T           OutputBufferSizeInBytes
    )
{
    m_pParentDevice->ProcessIoControl(pQueue,pRequest,ControlCode,InputBufferSizeInBytes,OutputBufferSizeInBytes);    
} 

This functions simply passes the request to the device class:

void CMyDevice::ProcessIoControl(__in IWDFIoQueue*     pQueue,
                                 __in IWDFIoRequest*   pRequest,
                                 __in ULONG            ControlCode,
                                 SIZE_T           inBufSize,
                                 SIZE_T           outBufSize)
{
    inFunc
        SectionLogger a(__FUNCTION__);
    UNREFERENCED_PARAMETER(pQueue);
    wchar_t log[300];
    swprintf(log,L"[BixVReader][IOCT]IOCTL %08X - In %i Out %i",ControlCode,inBufSize,outBufSize);
    OutputDebugString(log);

    //SectionLocker lock(m_RequestLock);
    int instance=0;
    {    
        CComPtr<IWDFFile>   pFileObject;
        pRequest->GetFileObject(&pFileObject);

        if (pFileObject != NULL)
        {
            DWORD logLen=300;
            pFileObject->RetrieveFileName(log,&logLen);
            instance=_wtoi(log+(logLen-2));
        }
    }
    Reader &reader=*readers[instance];

    if (ControlCode==IOCTL_SMARTCARD_GET_ATTRIBUTE) {
        reader.IoSmartCardGetAttribute(pRequest,inBufSize,outBufSize);
        return;
    }
    else if (ControlCode==IOCTL_SMARTCARD_IS_PRESENT) {
        reader.IoSmartCardIsPresent(pRequest,inBufSize,outBufSize);
        return;
    }
    else if (ControlCode==IOCTL_SMARTCARD_GET_STATE) {
        reader.IoSmartCardGetState(pRequest,inBufSize,outBufSize);
        return;
    }
    else if (ControlCode==IOCTL_SMARTCARD_IS_ABSENT) {
        reader.IoSmartCardIsAbsent(pRequest,inBufSize,outBufSize);
        return;
    }
    else if (ControlCode==IOCTL_SMARTCARD_POWER) {
        reader.IoSmartCardPower(pRequest,inBufSize,outBufSize);
        return;
    }    
    else if (ControlCode==IOCTL_SMARTCARD_SET_ATTRIBUTE) {
        reader.IoSmartCardSetAttribute(pRequest,inBufSize,outBufSize);
        return;
    }
    else if (ControlCode==IOCTL_SMARTCARD_SET_PROTOCOL) {
        reader.IoSmartCardSetProtocol(pRequest,inBufSize,outBufSize);
        return;
    }
    else if (ControlCode==IOCTL_SMARTCARD_TRANSMIT) {
        reader.IoSmartCardTransmit(pRequest,inBufSize,outBufSize);
        return;
    }
    swprintf(log,L"[BixVReader][IOCT]ERROR_NOT_SUPPORTED:%08X",ControlCode);
    OutputDebugString(log);
    pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);

    return;
} 

OnDeviceIoControl is called when the driver receives a request, and it just dispatches the request to the CMyDevice object. ControlCode contains the IO control code of the request, and through the pRequest object, we gain access to the associated input and output memory buffers.

Update

Since more than virtual one reader are present, we need to determine which one this I/O request addresses. This is done using the GetFileObject function, and the RetrieveFileName method of the IWDFFile interface. The file name ends with the "DEVx" string we set in the interface creation. The string parsing method is quite rude, but works, if you have less than 10 readers ( ...more 10 readers? What you need to do with them??). The request is then forwarded to the Reader object corresponding to the desired virtual reader.

The memory buffers are accessed through IWDFMemory objects. The interface is quite straightforward, and doesn't need many explications.
In the code, there are some helper functions to set and get an integer, a buffer or a string to and from the output and input buffers.

I/O Control Codes

Let's see which are the I/O control codes that a Smart Card Reader driver can receive:

IOCTL_SMARTCARD_GET_ATTRIBUTE

Quite easy. We just need to answer some easy questions: which is the vendor name, the reader name, the device unit (in case we have more than one reader with the same name), the communication protocol we support (according to the ATR) and the ATR string of the inserted card. The ATR is the only element that requires to communicate with the virtual card, as we'll see later.

These I/O requests are immediately completed, so pRequest->CompleteWithInformation is called at the end of the ProcessIoControl method.

Update

Since we added complexity with different reader types, reader names and RPC methods, we provide some information to the virtual smart card application with the reader configuration; so I implemented some custom values to read configuration data of a virtual reader:

void Reader::IoSmartCardGetAttribute(IWDFIoRequest* pRequest,SIZE_T inBufSize,SIZE_T outBufSize) {
    UNREFERENCED_PARAMETER(inBufSize);
    
    wchar_t log[300]=L"";
    char temp[300];

    DWORD code=getInt(pRequest);
    swprintf(log,L"[BixVReader][GATT]  - code %0X",code);
    OutputDebugString(log);

    switch(code) {
        case SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA009):
            // custom attribute; RPC_TYPE
            OutputDebugString(L"[BixVReader][GATT]RPC_TYPE");
            setInt(device,pRequest,rpcType);
            return;
        case SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA00a):
            // custom attribute; PipeName
            if (rpcType==0) {    
                PipeReader *pipe=(PipeReader *)this;
                OutputDebugString(L"[BixVReader][GATT]PIPE_NAME");
                sprintf(temp,"%S",pipe->pipeName);
                setString(device,pRequest,(char*)temp,(int)outBufSize);
            }
            else {
                SectionLocker lock(device->m_RequestLock);
                pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);
            }
            return;
        case SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA00b):
            // custom attribute; EventPipeName
            if (rpcType==0) {    
                PipeReader *pipe=(PipeReader *)this;
                OutputDebugString(L"[BixVReader][GATT]EVENT_PIPE_NAME");
                sprintf(temp,"%S",pipe->pipeEventName);
                setString(device,pRequest,(char*)temp,(int)outBufSize);
            }
            else {
                SectionLocker lock(device->m_RequestLock);
                pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);
            }
            return;
        case SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA00c):
            // custom attribute; TCP port
            if (rpcType==1) {    
                TcpIpReader *tcpIp=(TcpIpReader *)this;
                OutputDebugString(L"[BixVReader][GATT]PORT");
                setInt(device,pRequest,tcpIp->port);
            }
            else {
                SectionLocker lock(device->m_RequestLock);
                pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);
            }
            return;
        case SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA00d):
            // custom attribute; TCP event port
            if (rpcType==1) {    
                TcpIpReader *tcpIp=(TcpIpReader *)this;
                OutputDebugString(L"[BixVReader][GATT]EVENT_PORT");
                setInt(device,pRequest,tcpIp->eventPort);
            }
            else {
                SectionLocker lock(device->m_RequestLock);
                pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);
            }
            return;
        case SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA00e):
            // custom attribute; TCP base port
            if (rpcType==1) {    
                TcpIpReader *tcpIp=(TcpIpReader *)this;
                OutputDebugString(L"[BixVReader][GATT]BASE_PORT");
                setInt(device,pRequest,tcpIp->portBase);
                tcpIp;
            }
            else {
                SectionLocker lock(device->m_RequestLock);
                pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);
            }
            return;
        case SCARD_ATTR_CHARACTERISTICS:
            // 0x00000000 No special characteristics
            OutputDebugString(L"[BixVReader][GATT]SCARD_ATTR_CHARACTERISTICS");
            setInt(device,pRequest,0);
            return;
        case SCARD_ATTR_VENDOR_NAME:
            OutputDebugString(L"[BixVReader][GATT]SCARD_ATTR_VENDOR_NAME");
            setString(device,pRequest,vendorName,(int)outBufSize);
            return;
        case SCARD_ATTR_VENDOR_IFD_TYPE:
            OutputDebugString(L"[BixVReader][GATT]SCARD_ATTR_VENDOR_IFD_TYPE");
            setString(device,pRequest,vendorIfdType,(int)outBufSize);
            return;
        case SCARD_ATTR_DEVICE_UNIT:
            OutputDebugString(L"[BixVReader][GATT]SCARD_ATTR_DEVICE_UNIT");
            setInt(device,pRequest,deviceUnit);
            return;
        case SCARD_ATTR_ATR_STRING:
            OutputDebugString(L"[BixVReader][GATT]SCARD_ATTR_ATR_STRING");
            BYTE ATR[100];
            DWORD ATRsize;
            if (!QueryATR(ATR,&ATRsize))
            {
                SectionLocker lock(device->m_RequestLock);
                pRequest->CompleteWithInformation(STATUS_NO_MEDIA, 0);                    
                return;
            }
            setBuffer(device,pRequest,ATR,ATRsize);
            return;
        case SCARD_ATTR_CURRENT_PROTOCOL_TYPE:
            OutputDebugString(L"[BixVReader][GATT]SCARD_ATTR_CURRENT_PROTOCOL_TYPE");
            setInt(device,pRequest,protocol); // T=0 or T=1
            return;
        default: {
            swprintf(log,L"[BixVReader][GATT]ERROR_NOT_SUPPORTED:%08X",code);
            OutputDebugString(log);
            SectionLocker lock(device->m_RequestLock);
            pRequest->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), 0);
        }
    }
}  

To read these configuration values you need to connect to the reader in SCARD_DIRECT mode and send some SCardGetAttribute commands. This code should return the RpcType used by the first virtual reader:

SCardConnect(hContext,"Bix VIRTUAL_CARD_READER 0",SCARD_SHARE_DIRECT, 0,&hCard, &dwProtocol);
DWORD iRpcType;
DWORD iRpcTypeLen=sizeof(DWORD);
SCardGetAttrib(hCard,SCARD_ATTR_VALUE(SCARD_CLASS_VENDOR_DEFINED, 0xA009), (LPBYTE)&iRpcType, &iRpcTypeLen);

The data types returned by SCardGetAttrib for custom attributes are the following:

  • RPC_PORT_BASE: DWORD
  • RPC_TYPE: DWORD
  • VENDOR_NAME: Variable length string
  • VENDOR_IFD_TYPE: Variable length string
  • DEVICE_UNIT: DWORD
  • PIPE_NAME: Variable length string
  • PIPE_EVENT_NAME: Variable length string
  • TCP_PORT: DWORD
  • TCP_EVENT_PORT: DWORD

IOCTL_SMARTCARD_IS_PRESENT and IOCTL_SMARTCARD_IS_ABSENT

This is a bit trickier. We are asked if a smart card is in the reader. This is the code:

void Reader::IoSmartCardIsPresent(IWDFIoRequest* pRequest,SIZE_T inBufSize,SIZE_T outBufSize) {
    UNREFERENCED_PARAMETER(inBufSize);
    UNREFERENCED_PARAMETER(outBufSize);
    
    OutputDebugString(L"[BixVReader][IPRE]IOCTL_SMARTCARD_IS_PRESENT");
    if (CheckATR()) {
        // there's a smart card present, so complete the request
        SectionLocker lock(device->m_RequestLock);
        pRequest->CompleteWithInformation(STATUS_SUCCESS, 0);
    }
    else {
        // there's no smart card present, so leave the request pending; it will be completed later
        SectionLocker lock(device->m_RequestLock);
        waitInsertIpr=pRequest;
        IRequestCallbackCancel *callback;
        device->QueryInterface(__uuidof(IRequestCallbackCancel),(void**)&callback);
        pRequest->MarkCancelable(callback);
        callback->Release();
    }
}  

We try to communicate with the virtual card to ask its ATR; if the request succeeds, there's a card in the reader, otherwise not. In the first case, we just complete the I/O request to confirm that a card is present.

In the second case, we are actually starting to monitor the reader for card insertion. The I/O request is left pending (if we do not call CompleteWithInformation, the UMDF framework automatically handles the pending request), and it will be completed as soon as a card is inserted. We just store the pointer to the pending request in waitInsertIpr to remember that this request is still open.
Moreover ,we should call pRequest->MarkCancelable to inform the framework that this request is cancellable (in case the device is deactivated, or when the system is shut down). CMyDevice implements IRequestCallbackCancel, so it can be notified of the deletion request.

For IOCTL_SMARTCARD_IS_ABSENT it is obviously the opposite: the I/O request is completed when the smart card is removed.

  • IOCTL_SMARTCARD_GET_STATE

    We are queried for the device state. This is quite easy, in our case: we just support two states: card absent and card present with protocol negotiated. In a real driver, we should of course handle more precise states. We just ask the virtual card ATR to check if it is present or not.

  • IOCTL_SMARTCARD_POWER

    The card should be reset or unpowered. In case of a reset, we also return the ATR (if the virtual card is present).

  • IOCTL_SMARTCARD_SET_ATTRIBUTE

    We could just ignore it. We just return SUCCESS in case the SCARD_ATTR_DEVICE_IN_USE parameter is set.

  • IOCTL_SMARTCARD_TRANSMIT

    A command APDU should be sent to the smart card, and we should return the response. Not difficult, just some stuff to communicate to and from the virtual smart card handling process. We should remember to remove the SCARD_IO_REQUEST structure before the APDU, and insert it before the response.

IPC with the Virtual Smart Card Process

Obviously, a driver can't have a user interface. But if I need to change the behavior of the virtual smart card, perhaps load and save its state, or just simulate its insertion and removal from the virtual reader, I definitely need a user interface to do it! So, the virtual reader driver should happily communicate with the outer world, sending requests and receiving responses to a process that will simulate the virtual smart card behavior. But - because there's always a but - perhaps we should remember that a driver, even a user mode driver, is not exactly a simple application. In this case, the problem is that this application lives in Session 0, isolated from the rest of the Session 1 - world.

I will not explain in detail the concept of Session 0 and 1, and the isolation of Session 0 in Vista and later OS (also because I'm not an authority in this subject). I will just say that a Session 0 and a Session 1 process can't communicate in some IPC modes: no thread and window messages, no Memory Mapped Files and no global names, so no shared synchronization objects (I also don't want the session 1 application to be elevated)... so, I see two alternatives:

  • Named pipes
  • TCP/IP

Update

In the first version of the article only named pipes were implemented. Now I implemented both. They have almost the same behaviour, so, according to your domain, you can have both alternatives.

First, we see the pipe server function:

 DWORD PipeReader::startServer() {
	SECURITY_ATTRIBUTES sa;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.bInheritHandle = FALSE;  
	CreateMyDACL(&sa);

	wchar_t temp[300];
	swprintf(temp,L"\\\\.\\pipe\\%s",pipeName);
	HANDLE _pipe=CreateNamedPipe(temp,PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,PIPE_UNLIMITED_INSTANCES,0,0,0,&sa);
	swprintf(temp,L"\\\\.\\pipe\\%s",pipeEventName);
	HANDLE _eventpipe=CreateNamedPipe(temp,PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE,PIPE_UNLIMITED_INSTANCES,0,0,0,&sa);
	wchar_t log[300];
	swprintf(log,L"[BixVReader]Pipe created:%s:%08x",pipeName,_pipe);
	OutputDebugString(log);

	while (true) {
			BOOL ris=ConnectNamedPipe(_pipe,NULL);
			if (ris==0) {
				swprintf(log,L"[BixVReader]Pipe NOT connected:%x",GetLastError());
				OutputDebugString(log);
			}
			else {
				swprintf(log,L"[BixVReader]Pipe connected");
				OutputDebugString(log);
				}
			ris=ConnectNamedPipe(_eventpipe,NULL);
			if (ris==0) {
				swprintf(log,L"[BixVReader]Event Pipe NOT connected:%x",GetLastError());
				OutputDebugString(log);
			}
			else {
				swprintf(log,L"[BixVReader]Event Pipe connected");
				OutputDebugString(log);
			}
			pipe=_pipe;
			eventpipe=_eventpipe;
			if (waitInsertIpr!=NULL) {
				SectionLocker lock(device->m_RequestLock);
				// if I'm waiting for card insertion, verify if there's a card present
				if (initProtocols()) {
					if (waitInsertIpr->UnmarkCancelable()==S_OK)
						waitInsertIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
					waitInsertIpr=NULL;
					state=SCARD_SWALLOWED;					
				}
			}
			
			while (true) {
				// wait for a command
				DWORD command=0;
				DWORD read=0;
				if (!ReadFile(eventpipe,&command,sizeof(DWORD),&read,NULL)) {
					state=SCARD_ABSENT;
					OutputDebugString(L"[BixVReader]Pipe error");
					powered=0;
					pipe=NULL;
					eventpipe=NULL;
					if (waitRemoveIpr!=NULL) {// card inserted
						SectionLocker lock(device->m_RequestLock);
						OutputDebugString(L"[BixVReader]complete Wait Remove");
						if (waitRemoveIpr->UnmarkCancelable()==S_OK) {
							OutputDebugString(L"[BixVReader]Wait Remove Unmarked");
							waitRemoveIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
							OutputDebugString(L"[BixVReader]Wait Remove Completed");
						}
						waitRemoveIpr=NULL;
					}
					if (waitInsertIpr!=NULL) {// card removed
						SectionLocker lock(device->m_RequestLock);
						OutputDebugString(L"[BixVReader]cancel Wait Remove");
						if (waitInsertIpr->UnmarkCancelable()==S_OK) {
							OutputDebugString(L"[BixVReader]Wait Insert Unmarked");
							waitInsertIpr->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_CANCELLED), 0);
							OutputDebugString(L"[BixVReader]Wait Insert Cancelled");
						}
						waitInsertIpr=NULL;
					}
					DisconnectNamedPipe(_pipe);
					DisconnectNamedPipe(_eventpipe);
					break;
				}
				OutputDebugString(L"[BixVReader]Pipe data");
				if (command==0)
					powered=0;
				if (command==0 && waitRemoveIpr!=NULL) {// card removed
					SectionLocker lock(device->m_RequestLock);
					state=SCARD_ABSENT;
					if (waitRemoveIpr->UnmarkCancelable()==S_OK) {						
						waitRemoveIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
					}
					waitRemoveIpr=NULL;
				}
				else if (command==1 && waitInsertIpr!=NULL) {// card inserted
					SectionLocker lock(device->m_RequestLock);
					state=SCARD_SWALLOWED;
					initProtocols();
					if (waitInsertIpr->UnmarkCancelable()==S_OK) {
						waitInsertIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
					}
					waitInsertIpr=NULL;
				}
			}
	}
	OutputDebugString(L"[BixVReader]Pipe quit!!!");
	return 0;
}

The TcpIp server is almost identical:

 DWORD TcpIpReader::startServer() {
	breakSocket=false;

	wchar_t log[300];
	while (true) {
			fd_set readfds;

			FD_ZERO(&readfds);
			// Set server socket to set
			FD_SET(socket, &readfds);

			// Timeout parameter
			timeval tv = { 0 };
			tv.tv_sec = 5;

			while(true) {
				if (breakSocket)
					return 0;
				FD_SET(socket, &readfds);
				int ret = select(0, &readfds, NULL, NULL, &tv);
				if (ret > 0)
					break;
				if (ret<0) {
					wchar_t log[100];
					DWORD err=WSAGetLastError();
					swprintf(log,L"[BixVReader]wsa err:%x",err);
					OutputDebugString(log);
					if (err==0x2736) {
						socket=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,0);
						sockaddr_in Service;
						Service.sin_family = AF_INET;
						Service.sin_addr.s_addr = inet_addr("127.0.0.1");
						Service.sin_port = htons((u_short)(port));
						bind(socket, (SOCKADDR *) &Service, sizeof (Service));
						listen(socket, 1);

						FD_ZERO(&readfds);
						// Set server socket to set
						FD_SET(socket, &readfds);
					}
				}
			}

			SOCKET AcceptEventSocket;

			AcceptSocket = accept(socket, NULL, NULL);
			closesocket(socket);
			socket=NULL;
			if (AcceptSocket == INVALID_SOCKET)
				return 0;

			swprintf(log,L"[BixVReader]Socket connected:%i",AcceptSocket);
			OutputDebugString(log);

			FD_ZERO(&readfds);
			// Set server socket to set
			FD_SET(eventsocket, &readfds);

			while(true) {
				if (breakSocket)
					return 0;
				FD_SET(eventsocket, &readfds);
				int ret = select(0, &readfds, NULL, NULL, &tv);
				if (ret > 0)
					break;
				if (ret<0) {
					DWORD err=WSAGetLastError();
					swprintf(log,L"[BixVReader]wsa err:%x",err);
					OutputDebugString(log);
					if (err==0x2736) {
						eventsocket=WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP,NULL,0,0);
						sockaddr_in eventService;
						eventService.sin_family = AF_INET;
						eventService.sin_addr.s_addr = inet_addr("127.0.0.1");
						eventService.sin_port = htons((u_short)(eventPort));
						bind(eventsocket, (SOCKADDR *) &eventService, sizeof (eventService));
						listen(eventsocket, 1);

						FD_ZERO(&readfds);
						// Set server socket to set
						FD_SET(eventsocket, &readfds);
					}
				}
			}

			AcceptEventSocket = accept(eventsocket, NULL, NULL);
			closesocket(eventsocket);
			eventsocket=NULL;
			if (AcceptEventSocket == INVALID_SOCKET)
				return 0;

			swprintf(log,L"[BixVReader]Event Socket connected:%i",AcceptEventSocket);
			OutputDebugString(log);

			if (waitInsertIpr!=NULL) {
				// if I'm waiting for card insertion, verify if there's a card present
				if (initProtocols()) {
					SectionLocker lock(device->m_RequestLock);
					if (waitInsertIpr->UnmarkCancelable()==S_OK)
						waitInsertIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
					waitInsertIpr=NULL;
					state=SCARD_SWALLOWED;
				}
			}
			
			while (true) {
				// wait for a command
				DWORD command=0;
				int read=0;
				if ((read=recv(AcceptEventSocket,(char*)&command,sizeof(DWORD),MSG_WAITALL))<=0) {
					state=SCARD_ABSENT;
					OutputDebugString(L"[BixVReader]Socket error");
					powered=0;
					::shutdown(AcceptSocket,SD_BOTH);
					::shutdown(AcceptEventSocket,SD_BOTH);
					if (waitRemoveIpr!=NULL) {// card inserted
						OutputDebugString(L"[BixVReader]complete Wait Remove");
						SectionLocker lock(device->m_RequestLock);
						if (waitRemoveIpr->UnmarkCancelable()==S_OK) {
							OutputDebugString(L"[BixVReader]Wait Remove Unmarked");
							waitRemoveIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
							OutputDebugString(L"[BixVReader]Wait Remove Completed");
						}
						waitRemoveIpr=NULL;
					}
					if (waitInsertIpr!=NULL) {// card removed
						OutputDebugString(L"[BixVReader]cancel Wait Remove");
						SectionLocker lock(device->m_RequestLock);
						if (waitInsertIpr->UnmarkCancelable()==S_OK) {
							OutputDebugString(L"[BixVReader]Wait Insert Unmarked");
							waitInsertIpr->CompleteWithInformation(HRESULT_FROM_WIN32(ERROR_CANCELLED), 0);
							OutputDebugString(L"[BixVReader]Wait Insert Cancelled");
						}
						waitInsertIpr=NULL;
					}
					break;
				}
				OutputDebugString(L"[BixVReader]Socket data");
				if (command==0)
					powered=0;
				if (command==0 && waitRemoveIpr!=NULL) {// card removed
					SectionLocker lock(device->m_RequestLock);
					state=SCARD_ABSENT;
					if (waitRemoveIpr->UnmarkCancelable()==S_OK)						
						waitRemoveIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
					waitRemoveIpr=NULL;
				}
				else if (command==1 && waitInsertIpr!=NULL) {// card inserted
					SectionLocker lock(device->m_RequestLock);
					state=SCARD_SWALLOWED;
					initProtocols();					
					if (waitInsertIpr->UnmarkCancelable()==S_OK)
						waitInsertIpr->CompleteWithInformation(STATUS_SUCCESS, 0);
					waitInsertIpr=NULL;
				}
			}
	}
	OutputDebugString(L"[BixVReader]Socket quit!!!");
	return 0;
}
  

First of all, we create two Named Pipes (or Tcp/Ip sockets). Why two? Easy: The first (SCardSimulatorDriver) is for requests from the driver to the virtual smart card; the second (SCardSimulatorDriverEvents) is for event notifications from the virtual smart card to the driver (insertion and removal).
With ConnectNamedPipe, we wait for a client to connect. The call is blocking, so until someone opens the pipe, the thread stands waiting.

The utility function CreateMyDACL (straight from MSDN) is used to set the appropriate DACL for the Named Pipe. In fact, if we used NULL as DACL, the object would inherit the default settings of the parent process, and it would become inaccessible to client applications. Our custom DACL grants access to authenticated users, so all user processes are allowed to connect.

In case of Tcp/Ip, we close the listening socket soon after a client connects, since this is a single-client scenario.

When the connection is established, we check if a card is already inserted and we are monitoring (waitInsertIpr and CheckATR()); in this case, in fact, it is not sure that the virtual card would notify the driver, since there's no insertion event. The driver would continue to believe that there's no card inserted.

Update:

This first version of this driver supported just T=1 protocol. Now it parses the ATR to look for TDi bytes that define accepted protocols. So, when a client connects, we ask its ATR to know if a specific protocol is available.

Then the main communication loop starts. The driver thread stands waiting for events from the virtual smart card, calling ReadFile. The data sent on this pipe is trivial: a single DWORD with value 0 for removal and 1 for insertion. When an event arrives, if we are waiting for that event (waitRemoveIpr or waitInsertIpr), we complete that I/O request accordingly. Since these requests were marked as cancellable, we need to unmak them with UnmarkCancelable and, if possible, complete them (UnmarkCancelable could fail if it is too late and the request was already cancelled) (note that in the OnCancel callback, we don't need to call UnmarkCancelable).

If a ReadFile call fails, it means that we lost the connection with the virtual card. Perhaps the application was closed, and so the pipes (or sockets). In this case, we simply notify the removal of the card (if requested) and we restart waiting for a new client to connect.

Let's see what happens when the driver needs to send a request to the virtual smart card; the method QueryTransmit does this job:

 bool PipeReader::QueryTransmit(BYTE *APDU,int APDUlen,BYTE *Resp,int *Resplen) {
	if (pipe==NULL)
		return false;
	DWORD command=2;
	DWORD read=0;
	if (!WriteFile(pipe,&command,sizeof(DWORD),&read,NULL)) {
		pipe=NULL;
		return false;
	}
	DWORD dwAPDUlen=(DWORD)APDUlen;
	if (!WriteFile(pipe,&dwAPDUlen,sizeof(DWORD),&read,NULL)) {
		pipe=NULL;
		return false;
	}
	if (!WriteFile(pipe,APDU,APDUlen,&read,NULL)) {
		pipe=NULL;
		return false;
	}
	FlushFileBuffers(pipe);
	DWORD dwRespLen=0;
	if (!ReadFile(pipe,&dwRespLen,sizeof(DWORD),&read,NULL)) {
		pipe=NULL;
		return false;
	}
	if (!ReadFile(pipe,Resp,dwRespLen,&read,NULL)) {
		pipe=NULL;
		return false;
	}
	(*Resplen)=(int)dwRespLen;
	return true;
} 

First we check if a pipe is connected; otherwise, we return a fail. Then we write on the pipe the APDU command. The protocol is very simple:

  • a DWORD containing the command code (TRANSMIT=2)
  • a DWORD containing the length of the APDU
  • the APDU buffer

We don't need anything more... we just wait for the response to come. we read a DWORD containing the length of the response, and the response buffer. That's it. If any operation in the pipe fails, probably the communication is broken, so we return a fail and we wait for a new connection to come.

Every time we write something on the pipe, we should always call FlushFileBuffers to be sure that the buffer is sent to the other side of the pipe and is not buffered; otherwise, the listening application could not receive our data.

Notes

This implementation of IPC is very simple. A bit too simple. The I/O requests are synchronized, but the events received from the virtual smart cards are not. so, before setting the pipe handle to NULL, it would be wise to acquire a lock on it... I'm lazy, it works 99% of times and I just need it for testing purposes... so I didn't do it. Shame on me.

Update: I'm not lazy anymore!

From the very first version of this driver I always found a flaw that puzzeled me: sometimes the virtual reader simply disappeared from the reader list returned by SCardListReaders. I tried many times to fix it without success, until I took a look at this sample from Microsoft WinDDK 8.1. In this sample, every IWDFIoRequest related operation is surrounded by a CriticalSection Enter/Leave. It was Columbus's egg! I did the same and the stability of the driver gratly improved. I never discovered where and why such a concurrent access happens, but I'm convinced that multithreading requires a leap of faith. So, in this version the access to the request object is synchronized, and the driver is more stable.

Virtual Smart Card Application

Update

During the update of this article I wrote a full .net implementation of an ISO7816 virtual smart card.
Since the virtual smart card project is rather complex, I decided to separate it in a different article. The article is in draft but I hope to release it as soon as possible.  

Compile, Install and Debug

No surprise, to compile the driver you need to install WDK 7.1. Just 620MB to download from Microsoft and we are ready to build the driver. Compiling a driver is not exactly the same as compiling a regular DLL. For example, the subsystem is not WINDOWS but is NATIVE; and the library path should be set accordingly to the architecture for which the driver is being compiled. This is the reason why we don't have a Visual Studio Solution for it, but we'll use the build environment that comes with the WDK, already correctly configured, from a command line. If you installed the WDK correctly, in the start menu, you have the links to various environments: Start > Programs > Windows Drivers Kit > WDK 7600.16385.1 > Build Environments > %OS version% > %TargetCPU% Free/Checked Build Environment.
The Checked Build environment is like a Debug build: optimizations are turned off and conditional code for debugging is included.
The Free Build is like a Release build: the code is optimized and debugging code is disabled.
Note that even in a Free Build, you can use a user mode debugger to step through your code.

To compile the virtual driver, open the correct Build Environment, cd to the directory that contains the code and the subdir for the correct OS version (tested by now on Win 7 and Win XP - the settings of the sources file are slightly different), and just type build.
Et voilà, the DLL is built! ... as long as there's no compilation error. Note that in this build environments, warnings are treated as errors!. They are not reported by the build tool, but they are dumped to a file named, for example, "buildfre_win7_amd64.wrn", if you are building a driver for windows 7, x64, Free Build.
To configure the build tool, modify the sources file, adding new source files or libraries to link against if needed.

Once the driver if built, you can find the DLL in the folder (if Win7, x64, Free) objfre_win7_amd64\amd64, together with the .inf file for installation.
To install the driver, we'll use the DevCon utility that comes with WDK (in %WinDDK%\tools\devcon\%Architecture%). Also, we need to copy the file WUDFUpdate_01009.dll (in %WinDDK%\redist\wdf\%Architecture%) to the path where our DLL is located.
To install the driver, run from an elevated shell: 

devcon install BixVReader.inf root\BixVirtualReader

The syntax of devcon install is:

devcon [-r] install <inf> <hwid> 

Where <inf> is the path to the .inf file, and <hwid> is the hardware ID of the device that we are installing. It should match the ID in the .inf file at the line:

%ReaderDeviceName%=VReader_Install,root\BixVirtualReader  

The -r switch asks to reboot only if a reboot is required.
If the installation succeeds, the user is asked for confirmation, since the driver is not signed, and the virtual reader is installed.

BixVReader/Device.gif

You can download the zip archive with the compiled binaries for Windows 7, 32 and 64 bits and Windows XP. In the zip file, you can find the DLL and the inf file, but you'll need devcon.exe for your architecture (it is not redistributable, but is part of WDK). The driver was ONLY tested under Win7 x64 

If you start the Virtual Smart Card application, you should be able to connect to the virtual card. In Win7, as soon as you insert the card, some minidrivers will try to communicate with the card, sending various commands (as long as the driver simulates just one reader. See here "Smart card solutions that are not compatible with Plug and Play" - Multislot smart card readers that create only one device node for all available slots in the smart card reader. This is exactly the situation of the minidriver ). Since the card always answers 9000 (that is "OK"), a minidriver could be faked to match it, and you'll see a known card in the device manager! In the log listbox of the application, you'll see the APDUs sent to the card.

Debugging the driver is actually very easy: since it's a user-mode driver, you can use a user-mode debugger: Visual Studio, for example. All you have to do is attach to a running process, and select WUDFHost.exe. Since you could have more than one UMDF driver in your system, you could also have more than one instance of WUDFHost.exe. Which is the correct one? You can use Procexp from Sysinternals to search which processes load your DLL, and check the PID of the correct WUDFHost.exe. Remember that both Procexp and Visual Studio should be Run as Administrator, otherwise they will not be able to attach to the driver process. 

When you need to re-compile and re-test your driver, you need to update the driver DLL file.
You can do it simply launching the command: 

devcon update BixVReader.inf root\BixVirtualReader  

from the same shell used to install it.

Note that the device will be disabled, updated and reenabled, but if you have pending I/O requests, the system will not be able to disable it, and you will need to reboot the system for changes to take place.

In that case, you can do the following:

  • Open Device Manager
  • Disable the virtual device
  • If there are still pending I/O requests, kill the process of WUDFHost.exe (the one that hosts BixVReader.dll)
  • Update the driver with the command line above
  • Enable the device

As soon as you enable the device, the driver is loaded and executed. If you need to debug your driver from the very beginning, you can set the registry key
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\WUDF\Services\{193a1820-d9ac-4997-8c55-be817523f6aa}/HostProcessDbgBreakOnStartto the number of seconds that you want the host process to wait for a debugger to connect before starting.

It is useful, for debugging purposes, to have a trace or a kind of output from the driver. Even if WDK has a built in tracing system, through the WPP trace, I preferred to use a simple OutputDebugString. Just for speed of development, I preferred to use a well-known method instead of learning a new one, but this is just personal taste... Using Dbgview, also from Sysinternals, Run as Administrator, or attaching a debugger, you can easily see the trace from your driver.  

The .INF File

The .INF file used to install the virtual device is almost identical to the one from the UMDFSkeleton example. Just one row was added:

UmdfKernelModeClientPolicy=AllowKernelModeClients 

To allow a kernel-mode driver to load above the user-mode driver and to deliver requests from the kernel-mode to the user-mode driver.
I'm not exactly sure of which kernel mode driver runs above the virtual reader driver, but removing this line from the inf file, we simply do not get any I/O request notifications in our Queue object.

Conclusion

As I already said above, this is not by far an article on how a driver should be made. I'm quite sure that an expert driver developer would scream looking at my code. This is just a development tool for who, like me, works often on smart cards and needs to "play" with them. I hope that it will be useful for someone, and of course I wait for some serious driver developer to tell me how it should have been done. Smile | :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Fabio Ottavi
Engineer
Italy Italy
No Biography provided

Comments and Discussions

 
QuestionBuilding driver's sources [modified] PinmemberZharroo12-Dec-12 2:01 
AnswerRe: Building your sources PinmemberFabio Ottavi12-Dec-12 2:15 
GeneralRe: Building your sources PinmemberZharroo12-Dec-12 2:28 
GeneralRe: Building your sources PinmemberZharroo12-Dec-12 3:49 
AnswerRe: Building your sources PinmemberFabio Ottavi13-Dec-12 1:15 
GeneralRe: Building your sources PinmemberZharroo13-Dec-12 1:41 
AnswerRe: Building your sources PinmemberFabio Ottavi14-Dec-12 1:42 
GeneralRe: Building your sources PinmemberZharroo17-Jan-13 22:50 
GeneralRe: Building your sources PinmemberMember 109180098-Aug-14 7:52 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140916.1 | Last Updated 30 Jan 2014
Article Copyright 2010 by Fabio Ottavi
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid