Search code examples
smartcardjavacardapdupcscpyscard

Sending and receiving data using same T=1 APDU


I'm trying to send an APDU that contains some command data, and then expect some data in response from the card. I'm using this example code by Ludovic Rousseau as a starting to point (modified code below).

The APDU I'm sending is the following:

0x80 0x02 0x00 0x00 0x08   0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08   0x08

I.e. I've picked CLA 0x80, INS 0x02, not using P1 and P2, Lc and Le both 0x08.

The data buffer I get back only contains 0x90 0x00.

I've checked which protocol gets negotiated - that's T=1, as expected. If it were T=0, I would expect to get a 61XX-series answer (see this related question).

Every other APDU format works just fine (i.e. empty, only sending or only receiving data). Is there something I'm overlooking here?

// source: https://ludovicrousseau.blogspot.nl/2010/04/pcsc-sample-in-c.html
// This is based on code by Ludovic Rousseau, modified to match our example

#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 selectapdu[] = { 0x00, 0xA4, 0x04, 0x00, 0x0A,
                       0x01, 0x02, 0x03, 0x04, 0x05,
                       0x48, 0x45, 0x4C, 0x4C, 0x4F };
 BYTE echoapdu[] = { 0x80, 0x02, 0x00, 0x00, 0x08,
                      0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
                      0x08 };

 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:
  printf("T0\n");
   pioSendPci = *SCARD_PCI_T0;
   break;

  case SCARD_PROTOCOL_T1:
  printf("T1\n");
   pioSendPci = *SCARD_PCI_T1;
   break;
 }

// selecting the application

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

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

// sending a non-empty APDU that expects a reply

 dwRecvLength = sizeof(pbRecvBuffer);
 printf("sent (%d): ",  sizeof(echoapdu));
 for(i=0; i<sizeof(echoapdu); i++)
  printf("%02X ", echoapdu[i]);
 printf("\n");
 rv = SCardTransmit(hCard, &pioSendPci, echoapdu, sizeof(echoapdu),
  NULL, pbRecvBuffer, &dwRecvLength);
 CHECK("SCardTransmit", rv)

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

// disconnecting

 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;
}

This gives as output:

reader name: OMNIKEY AG CardMan 3121 00 00
T1
response (2): 90 00 
sent (14): 80 02 00 00 08 01 02 03 04 05 06 07 08 08 
response (2): 90 00 

When I try to do the same thing in Python using pyscard, everything works as expected, i.e. calling data, sw1, sw2 = connection.transmit(...) with the same APDU bytes as input makes data contain the expected data.

This makes me believe that the relevant code on the card is fine (but also posted below for completeness).

private void getEcho(APDU apdu) {
    byte[] buffer = apdu.getBuffer();
    short numBytes = (short) (buffer[ISO7816.OFFSET_LC] & 0x00FF);
    short bytesRead = apdu.setIncomingAndReceive();
    short pos = 0;

    while (pos < numBytes) {
        Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, transientMemory, pos, bytesRead);
        pos += bytesRead;
        bytesRead = apdu.receiveBytes(ISO7816.OFFSET_CDATA);
    }
    apdu.setOutgoing();
    apdu.setOutgoingLength(numBytes);
    apdu.sendBytesLong(transientMemory, (short)0, bytesRead);
}

Solution

  • I'm not sure why you would receive the correct data through pyscard. However, using bytesRead as the length in apdu.sendBytesLong() is obviously a mistake:

    • This would either result zero bytes being sent. This is the case if all bytes of the command data fit into the APDU buffer and were already retrieved through setIncomingAndReceive(). In that case, apdu.receiveBytes(ISO7816.OFFSET_CDATA) will return zero and bytesRead would be zero upon invoking sendBytesLong().
    • Or it would result in only a few bytes being sent. This is the case if there were more bytes in the command data field than would fit into the APDU buffer. In that case, bytesRead would be set to the number (N) of bytes that were received with the last call of apdu.receiveBytes(ISO7816.OFFSET_CDATA). sendBytesLong() would return exactly this amount (N) of bytes from the beginning of the command data.

    Consequently, the count should probably be numBytes:

    apdu.sendBytesLong(transientMemory, (short)0, numBytes);