Important!

Blog moved to https://blog.apdu.fr/

I moved my blog from https://ludovicrousseau.blogspot.com/ to https://blog.apdu.fr/ . Why? I wanted to move away from Blogger (owne...

Showing posts with label C. Show all posts
Showing posts with label C. Show all posts

Wednesday, May 27, 2015

PCSC sample in C for UEFI

To continue the list of PC/SC wrappers initiated more than four years ago with "PC/SC sample in different languages" I now present a PC/SC sample written in C but for UEFI.

UEFI

UEFI stands for Unified Extensible Firmware Interface. UEFI is an evolution of EFI. EFI should replace BIOS on "modern" "Intel PC" computers.

Any 64-bits Intel (or compatible) PC computer should have UEFI (and maybe also a BIOS for compatibility).

See my previous article "UEFI Smart Card Reader Protocol" for some more details.

Sample source code

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/BaseMemoryLib.h>

#include <Protocol/SmartCardReader.h>

int HelloWorld(EFI_SMART_CARD_READER_PROTOCOL *SmartCardReader)
{
    EFI_STATUS  Status;
    UINT32 ActiveProtocol;
    int i;
    UINT8 CAPDU_select[] = {0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01};
    UINT8 CAPDU_command[] = {0x00, 0x00, 0x00, 0x00};
    UINTN CAPDULength, RAPDULength;
    UINT8 RAPDU[256+2];

    /*
     * SCardConnect
     */
    Status = SmartCardReader->SCardConnect(SmartCardReader,
        SCARD_AM_CARD,
        SCARD_CA_COLDRESET,
        SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1,
        &ActiveProtocol);
    if (EFI_ERROR(Status))
    {
        Print(L"ERROR: SCardConnect: %d\n", Status);
        return 0;
    }

    /*
     * SCardTransmit Select
     */
    CAPDULength = sizeof CAPDU_select;
    RAPDULength = sizeof RAPDU;
    Status = SmartCardReader->SCardTransmit(SmartCardReader,
        CAPDU_select, CAPDULength,
        RAPDU, &RAPDULength);
    if (EFI_ERROR(Status))
    {
        Print(L"ERROR: SCardTransmit: %d\n", Status);
        return 0;
    }
    Print(L"RAPDU: ");
    for (i=0; i<RAPDULength; i++)
        Print(L"%02X ", RAPDU[i]);
    Print(L"\n");

    /*
     * SCardTransmit Command
     */
    CAPDULength = sizeof CAPDU_command;
    RAPDULength = sizeof RAPDU;
    Status = SmartCardReader->SCardTransmit(SmartCardReader,
        CAPDU_command, CAPDULength,
        RAPDU, &RAPDULength);
    if (EFI_ERROR(Status))
    {
        Print(L"ERROR: SCardTransmit: %d\n", Status);
        return 0;
    }
    Print(L"RAPDU: ");
    for (i=0; i<RAPDULength; i++)
        Print(L"%02X ", RAPDU[i]);
    Print(L"\n");
    for (i=0; i<RAPDULength; i++)
        Print(L"%c", RAPDU[i]);
    Print(L"\n");

    /*
     * SCardDisconnect
     */
    Status = SmartCardReader->SCardDisconnect(SmartCardReader,
        SCARD_CA_NORESET);
    if (EFI_ERROR(Status))
    {
        Print(L"ERROR: SCardDisconnect: %d\n", Status);
        return 0;
    }

    return 0;
}

INTN
EFIAPI
ShellAppMain (
  IN UINTN Argc,
  IN CHAR16 **Argv
  )
{
    EFI_STATUS  Status;
    UINTN       HandleIndex, HandleCount;
    EFI_HANDLE  *DevicePathHandleBuffer = NULL;
    EFI_SMART_CARD_READER_PROTOCOL *SmartCardReader;

    /* EFI_SMART_CARD_READER_PROTOCOL */
    Status = gBS->LocateHandleBuffer(
            ByProtocol,
            &gEfiSmartCardReaderProtocolGuid,
            NULL,
            &HandleCount,
            &DevicePathHandleBuffer);

    if (EFI_ERROR(Status))
    {
        Print(L"ERROR: Get EFI_SMART_CARD_READER_PROTOCOL count fail.\n");
        return 0;
    }

    Print(L"Found %d reader(s)\n", HandleCount);
    for (HandleIndex = 0; HandleIndex < HandleCount; HandleIndex++)
    {
        ZeroMem(&SmartCardReader, sizeof SmartCardReader);

        Status = gBS->HandleProtocol(
                DevicePathHandleBuffer[HandleIndex],
                &gEfiSmartCardReaderProtocolGuid,
                (VOID**)&SmartCardReader);

        if (EFI_ERROR(Status))
        {
            Print(L"ERROR: Open Protocol fail.\n");
            gBS->FreePool(DevicePathHandleBuffer);
            return 0;
        }

        HelloWorld(SmartCardReader);
    }
    gBS->FreePool(DevicePathHandleBuffer);

    return(0);
}

Output

Found 1 reader(s)
RAPDU: 90 00 
RAPDU: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 90 00 
Hello world!

Test platform

I used a Dell laptop model E6420 (shipped in August 2013) with an integrated Broadcom smart card reader. This reader was already in the "should work" list of my CCID driver. Of course the SmartCardReader API was not included in the UEFI. I had to write the driver myself.

I made tests with older Dell laptops and had problems with the USB UEFI layer. It looks like UEFI is still a work in progress and bugs/limitations are common.

I started by using a software simulator qemu + UEFI but I could not use a USB device with the simulator. So I rapidly had to use a real hardware. Developing a UEFI driver involves a lot of reboot to try a new version of the driver.

Remarks

I added error checks and logging feature in the source code.

The API is not high level but mostly the direct equivalent of WinSCard (the classic C API for smart cards).

Conclusion

It was fun to work on UEFI and learn a new technology.

UEFI is very powerful. Time will tell if the SmartCardReader API will be deployed and used.

Thursday, April 15, 2010

PCSC sample in C

Here is the PCSC sample in C language I promised in PC/SC sample in different languages.

C Source code


#ifdef WIN32
#undef UNICODE
#endif

#include <stdio.h>
#include <stdlib.h>

#ifdef __APPLE__
#include <PCSC/winscard.h>
#include <PCSC/wintypes.h>
#else
#include <winscard.h>
#endif

#ifdef WIN32
static char *pcsc_stringify_error(LONG rv)
{
 static char out[20];
 sprintf_s(out, sizeof(out), "0x%08X", rv);

 return out;
}
#endif

#define CHECK(f, rv) \
 if (SCARD_S_SUCCESS != rv) \
 { \
  printf(f ": %s\n", pcsc_stringify_error(rv)); \
  return -1; \
 }

int main(void)
{
 LONG rv;

 SCARDCONTEXT hContext;
 LPTSTR mszReaders;
 SCARDHANDLE hCard;
 DWORD dwReaders, dwActiveProtocol, dwRecvLength;

 SCARD_IO_REQUEST pioSendPci;
 BYTE pbRecvBuffer[258];
 BYTE cmd1[] = { 0x00, 0xA4, 0x04, 0x00, 0x0A, 0xA0,
  0x00, 0x00, 0x00, 0x62, 0x03, 0x01, 0x0C, 0x06, 0x01 };
 BYTE cmd2[] = { 0x00, 0x00, 0x00, 0x00 };

 unsigned int i;

 rv = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext);
 CHECK("SCardEstablishContext", rv)

#ifdef SCARD_AUTOALLOCATE
 dwReaders = SCARD_AUTOALLOCATE;

 rv = SCardListReaders(hContext, NULL, (LPTSTR)&mszReaders, &dwReaders);
 CHECK("SCardListReaders", rv)
#else
 rv = SCardListReaders(hContext, NULL, NULL, &dwReaders);
 CHECK("SCardListReaders", rv)

 mszReaders = calloc(dwReaders, sizeof(char));
 rv = SCardListReaders(hContext, NULL, mszReaders, &dwReaders);
 CHECK("SCardListReaders", rv)
#endif
 printf("reader name: %s\n", mszReaders);

 rv = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED,
  SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
 CHECK("SCardConnect", rv)

 switch(dwActiveProtocol)
 {
  case SCARD_PROTOCOL_T0:
   pioSendPci = *SCARD_PCI_T0;
   break;

  case SCARD_PROTOCOL_T1:
   pioSendPci = *SCARD_PCI_T1;
   break;
 }
 dwRecvLength = sizeof(pbRecvBuffer);
 rv = SCardTransmit(hCard, &pioSendPci, cmd1, sizeof(cmd1),
  NULL, pbRecvBuffer, &dwRecvLength);
 CHECK("SCardTransmit", rv)

 printf("response: ");
 for(i=0; i<dwRecvLength; i++)
  printf("%02X ", pbRecvBuffer[i]);
 printf("\n");

 dwRecvLength = sizeof(pbRecvBuffer);
 rv = SCardTransmit(hCard, &pioSendPci, cmd2, sizeof(cmd2),
  NULL, pbRecvBuffer, &dwRecvLength);
 CHECK("SCardTransmit", rv)

 printf("response: ");
 for(i=0; i<dwRecvLength; i++)
  printf("%02X ", pbRecvBuffer[i]);
 printf("\n");

 rv = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
 CHECK("SCardDisconnect", rv)

#ifdef SCARD_AUTOALLOCATE
 rv = SCardFreeMemory(hContext, mszReaders);
 CHECK("SCardFreeMemory", rv)

#else
 free(mszReaders);
#endif

 rv = SCardReleaseContext(hContext);

 CHECK("SCardReleaseContext", rv)

 return 0;
}

Makefile

# Linux
PCSC_CFLAGS := $(shell pkg-config --cflags libpcsclite)
LDFLAGS := $(shell pkg-config --libs libpcsclite)

# Mac OS X
#PCSC_CFLAGS := -framework PCSC

CFLAGS += $(PCSC_CFLAGS)

sample: sample.c

clean:
 rm -f sample

Outputs

The output is "Hello world!" which is in hex:

$ echo -n 'Hello world!' | xxd -g 1
0000000: 48 65 6c 6c 6f 20 77 6f 72 6c 64 21   Hello world!

GNU/Linux and Mac OS X

reader name: Gemplus GemPC Twin 00 00
response: 90 00 
response: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 90 00

Windows

D:\Documents and Settings\lroussea\My Documents\Visual Studio 2008\Projects\
sample\Debug> sample.exe
reader name: Gemplus USB Smart Card Reader 0
response: 90 00
response: 48 65 6C 6C 6F 20 77 6F 72 6C 64 21 90 00

Lessons learned

Reader name

The reader names are different on different plate forms. GNU/Linux and Mac OS X use the same PC/SC driver and the same (mostly) pcsc-lite so the name of an identical reader is the same. But Windows uses a different driver with a different PC/SC reader name.

Do not use the PC/SC reader name to identify a specific reader if you want to be portable.

SCardTransmit

pioRecvPci argument may be NULL (as in the sample) but shall not be left uninitialised on Windows. Otherwise SCardTransmit returns an error 0xE. This error code does not look like a WinSCard error code and is not documented in the SCardTransmit() API AFAIK.

UNICODE

If UNICODE is defined under Windows then the reader name is in UTF-16 and not easy to manipulate in a portable way. But I am not a Windows expert.

Unix uses UTF-8 so the name is the same in ASCII and UTF-8.

SCARD_AUTOALLOCATE

Mac OS X does not (yet) support memory (auto) allocation in the PC/SC layer. So you have to use a double call mechanism. One first call to get the size to allocate and one second call with a buffer allocated at the correct size.

In the example the memory auto allocation is used to get the list of connected readers. We see here that the C language is very low level regarding memory allocation.

Memory allocated by PC/SC has to be free-ed by PC/SC using SCardFreeMemory()

PCSC framework

Mac OS X uses Frameworks instead of classical Unix libraries. The difference (for our example) is the header files are not stored in /usr/include/ but in /System/Library/Frameworks/PCSC.framework/Headers/. You do not specify the header path to the compiler using -Ipath but use -framework PCSC instead. The compiler (gcc) takes care to find and use the correct framework.

pcsc_stringify_error()

pcsc_stringify_error() is a function specific to pcsc-lite. So we have to either not use it at all in a portable source code or re-implement a version on Windows. I decided to rewrite a limited version on Windows.

wintypes.h

Mac OS X does not use the exact same API definition than Windows or GNU/Linux for the WinSCard functions. Only standards types are used instead of Windows ones. For example:

int32_t SCardConnect(SCARDCONTEXT hContext,
        const char *szReader,
        uint32_t dwShareMode,
        uint32_t dwPreferredProtocols,
        LPSCARDHANDLE phCard,
        uint32_t *pdwActiveProtocol);

instead of

LONG SCardConnect(SCARDCONTEXT hContext,
 LPCSTR szReader,
 DWORD dwShareMode,
 DWORD dwPreferredProtocols,
 LPSCARDHANDLE phCard,
 LPDWORD pdwActiveProtocol);

The advantage is that the types used on Mac OS X are clearly defined by inttypes.h.

For example ULONG is defined as

typedef unsigned long ULONG;

but the long type from the C-language is different on Windows and GNU/Linux.

GNU/Linux (and Mac OS X) are LLP64 systems but Windows is a LP64 system. See 64-bit. So on GNU/Linux a long is 64-bits on a 64-bits CPU and on Windows a long is 32-bits on a 64-bits CPU.

To avoid problems Apple decided to explicitly fix the size using uint32_t.


Flattr this