I imagine this bug is also present in El Capitan and Yosemite but I have not checked.
SCardEndTransaction() ends all the transactions
SCardEndTransaction() does not work as it should, at least it does not work as the pcsc-lite version on GNU/Linux.If you start a new PC/SC transaction inside a PC/SC transaction you expect the card to be available to another application only after all the transactions have ended.
For example if, in one application, you do:
A.
SCardBeginTransaction()
B.
SCardBeginTransaction()
C.
SCardEndTransaction()
D.
SCardEndTransaction()
You may expect that the card is available again for another application after the transaction is ended in step D.
What happens on macOS Sierra is that the transaction is ended at step C instead. So all the card APDU exchanges between steps C and D are not protected by the PC/SC transaction and can be mixed with APDU exchanges from any other applications.
It looks like PC/SC on macOS does not include a nested transaction counter. The first call to
SCardEndTransaction()
will end the transaction.The sample code above is very simple and may seem stupid. But maybe the steps B and C are executed in another function of your application and then more hidden.
The macOS Sierra behaviour may produce very bad side effects. Imagine you use transactions to restrict access to the card private key after verifying the card PIN code. Then another application could also use the private key (between steps C and D) before the PIN is unverified just before step D.
See also
Apple bug report #33940052 "Smart card PC/SC transactions can't be nested"Closed by Apple as a duplicate of #33752531.
Sample code
#include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef __APPLE__ #include <PCSC/winscard.h> #include <PCSC/wintypes.h> #else #include <winscard.h> #endif #define pcsc_error(fct) printf(fct ": %s 0x%08lX\n", pcsc_stringify_error(err), (long)err) int main(void) { LPSTR mszReaders; DWORD err, cchReaders = 0; SCARDCONTEXT hContext = 0; SCARDHANDLE hCard = 0; err = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &hContext); if (err != SCARD_S_SUCCESS) { pcsc_error("ScardEstablishedContext"); return -1; } err = SCardListReaders(hContext, NULL, NULL, &cchReaders); if (err != 0) { pcsc_error("ScardListReaders"); return -1; } mszReaders = calloc(cchReaders, sizeof(char)); if (!mszReaders) { printf("calloc\n"); return -1; } err = SCardListReaders(hContext,NULL, mszReaders, &cchReaders); if (err != SCARD_S_SUCCESS) { pcsc_error("ScardListReaders"); return -1; } printf("Using reader: %s\n", mszReaders); DWORD dwActiveProtocol; printf("connect\n"); err = SCardConnect(hContext, mszReaders, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol); if (err != SCARD_S_SUCCESS) { pcsc_error("SCardConnect"); return 0; } printf("transaction 1\n"); err = SCardBeginTransaction(hCard); if (err != SCARD_S_SUCCESS) pcsc_error("SCardBeginTransaction"); printf("transaction 2\n"); err = SCardBeginTransaction(hCard); if (err != SCARD_S_SUCCESS) pcsc_error("SCardBeginTransaction"); printf("release transaction 2\n"); err = SCardEndTransaction(hCard, SCARD_LEAVE_CARD); if (err != SCARD_S_SUCCESS) pcsc_error("SCardBeginTransaction"); printf("Start me again and press enter\n"); getchar(); printf("release transaction 1\n"); err = SCardEndTransaction(hCard, SCARD_LEAVE_CARD); if (err != SCARD_S_SUCCESS) pcsc_error("SCardBeginTransaction"); err = SCardDisconnect(hCard, SCARD_LEAVE_CARD); if (err != SCARD_S_SUCCESS) pcsc_error("SCardDisconnect"); SCardReleaseContext(hContext); return 0; }
Result (on Sierra)
$ CFLAGS="-framework PCSC" make main cc -framework PCSC main.c -o main
First execution:
$ ./main_Mac Using reader: Gemalto PC Twin Reader connect transaction 1 transaction 2 release transaction 2 Start me again and press enter
Second execution in another window:
$ ./main_Mac Using reader: Gemalto PC Twin Reader connect transaction 1 transaction 2 release transaction 2 Start me again and press enter
Expected result (on Debian)
$ CFLAGS=`pkg-config --cflags libpcsclite` LDFLAGS=`pkg-config --libs libpcsclite` make main cc -pthread -I/usr/include/PCSC -lpcsclite main.c -o main
First execution:
$ ./main_Linux Using reader: Gemalto PC Twin Reader 00 00 connect transaction 1 transaction 2 release transaction 2 Start me again and press enter
Second execution in another window:
$ ./main_Linux Using reader: Gemalto PC Twin Reader 00 00 connect
Here you notice that the second execution is blocked at the "connect" step. Once you press "Enter" for the first execution then the transaction is released and the second execution can continue.