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:

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.