The API proposed by Apple to use a smart card and a smart card reader is
CryptoTokenKit. I already wrote about this API in:
CryptoTokenKit is an equivalent of the
PC/SC API defined by the
PC/SC workgroup and implemented by Microsoft in Windows and by
pcsc-lite for Unixes. The PC/SC API is also known as WinSCard.
Pinpad reader
The idea of a pinpad reader it so submit the user secret PIN code to the card without entering it on the computer. The PIN is entered on the smart card reader and is sent directly to the smart card inserted in the smart card reader. The computer (PC, Mac, whatever) never has access to the PIN code.
A pinpad reader is often used with banking applications. Even if your system is compromised the PIN is safe.
You can have a list of pinpad readers at my
Reader selection page.
I will use a
Gemalto Ezio Bluetooth reader in USB mode for the demo.
TKSmartCardUserInteractionForPINOperation
Apple provides a way to use a pinpad reader using CryptoTokenKit with the class
TKSmartCardUserInteractionForPINOperation.
In the example I will use the subclass
TKSmartCardUserInteractionForSecurePINVerification and the method
userInteractionForSecurePINVerificationWithPINFormat:APDU:PINByteOffset:.
The parameter
PINFormat
of class
TKSmartCardPINFormat offers services similar to what can be found in
PC/SC version 2 Part 10 for using a pinpad reader but with a better interface design.
Source code
#import <Foundation/Foundation.h>
#import <CryptoTokenKit/CryptoTokenKit.h>
int main(int argc, const char * argv[]) {
TKSmartCardSlotManager * mngr;
mngr = [TKSmartCardSlotManager defaultManager];
// Use the first reader/slot found
if ([mngr.slotNames count] == 0)
{
NSLog(@"No reader found");
return -1;
}
NSString *slotName = (NSString *)mngr.slotNames[0];
NSLog(@"slotName: %@", slotName);
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
// connect to the slot
[mngr getSlotWithName:slotName reply:^(TKSmartCardSlot *slot)
{
// connect to the card
TKSmartCard *card = [slot makeSmartCard];
if (nil == card)
{
NSLog(@"No card found");
// signals end of getSlotWithName block
dispatch_semaphore_signal(sem);
return;
}
// begin a session
[card beginSessionWithReply:^(BOOL success, NSError *error)
{
if (success)
{
NSData *response;
UInt16 sw;
TKSmartCardPINFormat *PINFormat;
TKSmartCardUserInteractionForSecurePINVerification *userInter;
NSData *APDUTemplate;
// explicitly set the CLA byte even if 0 is already the default value
card.cla = 0x00;
// send select applet APDU
uint8_t aid[] = {0xA0, 0x00, 0x00, 0x00, 0x18, 0xFF, 0x01};
NSData *data = [NSData dataWithBytes:aid length:sizeof aid];
response = [card sendIns:0xA4 p1:0x04 p2:0x00 data:data le:nil sw:&sw error:&error];
if (nil == response)
{
NSLog(@"sendIns error: %@", error);
[card endSession];
// signals end of beginSessionWithReply block
dispatch_semaphore_signal(sem);
return;
}
NSLog(@"Response select applet: %@ 0x%04X", response, sw);
const UInt8 template[] = {card.cla, 0x20, 0x00, 0x80, 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
APDUTemplate = [NSData dataWithBytes:template length:sizeof(template)];
PINFormat = [[TKSmartCardPINFormat alloc] init];
PINFormat.PINBitOffset = 0;
// VerifyPIN
data = [NSData dataWithBytes:template length:sizeof template];
userInter = [card userInteractionForSecurePINVerificationWithPINFormat:PINFormat APDU:data PINByteOffset:0];
if (nil == userInter)
{
NSLog(@"userInteractionForSecurePINVerificationWithPINFormat returned nil. Are you using a pinpad reader?");
[card endSession];
// signals end of beginSessionWithReply block
dispatch_semaphore_signal(sem);
}
else
{
NSLog(@"Enter the PIN on the pinpad");
[userInter runWithReply:^(BOOL success, NSError *error)
{
if (success)
{
NSLog(@"success");
// give some time to the reader to display a message before the next APDU
sleep(1);
NSLog(@"resultData: %@", [userInter resultData]);
NSLog(@"resultSW: %04X", [userInter resultSW]);
UInt16 sw;
// send PIN dump
uint8_t param[] = {0x09};
NSData *data = [NSData dataWithBytes:param length:sizeof param];
NSData *response = [card sendIns:0x40 p1:0x00 p2:0x00 data:data le:@0 sw:&sw error:&error];
if (nil == response)
NSLog(@"sendIns error: %@", error);
else
NSLog(@"Response PIN dump: %@ 0x%04X", response, sw);
[card endSession];
// signals end of beginSessionWithReply block
dispatch_semaphore_signal(sem);
}
else
{
NSLog(@"Error: %@", error);
[card endSession];
// signals end of beginSessionWithReply block
dispatch_semaphore_signal(sem);
}
}];
}
}
else
{
NSLog(@"Session error: %@", error);
// signals end of getSlotWithName block
dispatch_semaphore_signal(sem);
}
}];
}];
// wait for the asynchronous blocks to finish
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
Output
slotName: Gemalto Ezio Shield
Response select applet: <> 0x9000
Enter the PIN on the pinpad
success
resultData: <>
resultSW: 9000
Response PIN dump: <00200080 08313233 34353637 38> 0x9000
At the beginning
Application started
PIN code entered in the reader
PIN code validated by the card
This last picture is out of focus. I am sorry for that. Note that it is not easy to hold the smartphone, validate on the pinpad and take the picture with only two hands.
Comments
Source code auto documented
I will not comment the code here. Comments are already present in the source code.
Entitlements
Do not forget to create an
.entitlements file in Xcode (enable App Sandbox). Then add the
com.apple.security.smartcard property and set it to
YES.
If you do not do that access to the CryptoTokenKit API will be denied and your output will be something like:
[smartcard] ctk: connecting to slot registration server failed
No reader found
[smartcard] connection to slot registration server failed
Without a pinpad reader
If you do not use a reader that support PIN verification using a pinpad then the method
userInteractionForSecurePINVerificationWithPINFormat:
will return
nil
instead of a
TKSmartCardUserInteractionForSecurePINVerification
object.
Be careful to check the returned value.
Test applet
I use a smart card with a specific test applet. The Java applet source code is
available.
From the execution Output you can see the line:
Response PIN dump: <00200080 08313233 34353637 38> 0x9000
This is a special debug command I use to check what exactly the reader has sent to the card. The PIN may have many different formats and padding. Here you see the PIN "12345678" (Oops, it is no more a secret) has been sent as the ASCII codes for "1" (0x31), "2" (0x32), etc.
Your may need a different coding or padding. Have a look at the different
TKSmartCardPINFormat parameters like
charset
,
minPINLength
,
maxPINLength
,
PINJustification
,
PINBitOffset
, etc.
Asynchronous runWithReply:
The
runWithReply: method is asynchronous. So be careful to not end the card session before the user has entered its PIN. In the code I use a semaphore but you can use something else, or nothing, depending on your application.
Non-CCID readers?
Because of a bug in, I think, CryptoTokenKit I was not able to use the Gemalto Ezio Bluetooth reader in Bluetooth mode. The reader is not detected as a pinpad reader. This reader works fine with the PC/SC API on macOS and the pinpad feature can be used in Bluetooth mode.
My guess is that CryptoTokenKit detects that a reader is a pinpad reader by reading the bPINSupport byte directly from the CCID USB descriptor instead of using the driver
IFDHControl(CM_IOCTL_GET_FEATURE_REQUEST, ...) call to check if
FEATURE_VERIFY_PIN_DIRECT
is supported. So only CCID pinpad readers may be supported.
I opened a bug at Apple "CryptoTokenKit does not detect my Bluetooth smart card reader as a pinpad reader" #34648641.
Source code and blog licence
If you do plan to reuse (part of) my code please read the blog licence bellow and be sure it is OK for you to conform with it, or contact me. See also
My blog messages license.
Conclusion
I do not know many applications that use CryptoTokenKit to interact with a smart card. When I was debugging my code I searched for examples of use of
TKSmartCardUserInteractionForPINOperation
but could not find any.
I would not be surprised if my code is the first public example of CryptoTokenKit to use a pinpad reader.