Use a pinpad reader with macOS CryptoTokenKit: TKSmartCardUserInteractionForPINOperation
The API proposed by Apple to use a smart card and a smart card reader is CryptoTokenKit. I already wrote about this API in:
- macOS Sierra and CryptoTokenKit API
- "PC/SC" sample in Objective-C (synchronous)
- PCSC sample in Objective-C
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 methoduserInteractionForSecurePINVerificationWithPINFormat:
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> 0x9000This 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 ofTKSmartCardUserInteractionForPINOperation
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.