2

I'm developing a piece of code in which I need to create an RSA key pair.

My java card is an FM1280-ID006 card which has written in its information sheet that It supports java card 2.2.2 specification. When I try to create an RSA key pair

KeyPair(KeyPair.ALG_RSA,KeyBuilder.LENGTH_RSA_2048)

and install it on the card above, nothing unusual happens and I'm done successfully (90 00).

but as I call the method genKeyPair(), something happens in the card which I do not understand why. I use GPShell as below:

send_apdu -sc 1 -APDU 8000000000 // initialize
Command --> 8000000000
Wrapped command --> 8000000000
send_APDU() returns 0x8010002F (A communications error with the smart card has been detected. Retry the operation.)

It is worthy to mention that this problem on key pair creation does not happen when creating a 1024 key pair

KeyPair(KeyPair.ALG_RSA,KeyBuilder.LENGTH_RSA_1024)

and it is successfully created and used by RSA cipher and so on as:

send_apdu -sc 1 -APDU 8000000000 // initialize
Command --> 8000000000
Wrapped command --> 8000000000
Response <-- 9000
send_APDU() returns 0x80209000 (9000: Success. No error.)
command time: 3432 ms

Does my card support KeyPair 2048 as the information sheet says supporting javacard 2.2.2?


Appendix 1: Java Code:

package net.sourceforge.globalplatform.jc.hasincard;

import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.*;

import java.security.acl.Owner;
import java.util.Random;


public class HasinCardApplet extends Applet {

   final static byte APPLET_CLA = (byte)0x80;
   final static byte INITIALIZE = (byte)0x00;
   final static byte INSERT_MESSAGE = (byte)0x01;
   final static byte CHECK_MESSAGE = (byte)0x02;
   final static byte GET_HASH = (byte)0x03;
   final static byte GET_SIGN = (byte)0x04;
   final static byte VERIFY_SIGN = (byte)0x05;
   final static byte VERIFY_PIN = (byte)0x06;
   final static byte CHANGE_PIN = (byte)0x07;
   final static byte UNLOCK_PIN = (byte)0x08;
   final static byte TEST = (byte)0x09;

   final static short SW_MESSAGE_NOT_INSERTED = (short)0x6300;
   final static short SW_KEY_IS_INITIALIZED = (short)0x6301;
   final static short SW_HASH_NOT_SET = (short)0x6302;
   final static short SW_SIGN_NOT_SET = (short)0x6303;
   final static short SW_CARD_IS_LOCKED = (short)0x6304;
   final static short SW_VERIFICATION_FAILED = (short)0x6305;
   final static short SW_PIN_VERIFICATION_REQUIRED = (short)0x6306;
   final static short SW_NEW_PIN_TOO_LONG = (short)0x6307;
   final static short SW_NEW_PIN_TOO_SHORT = (short)0x6308;
   final static short SW_PUK_VERIFICATION_REJECTED = (short)0x6309;
   final static short SW_PUK_VERIFICATION_FAILED = (short)0x630A;

   final static byte PIN_TRY_LIMIT = (byte)3;
   final static byte MAX_PIN_SIZE = (byte)16;
   final static byte MIN_PIN_SIZE = (byte)4;
   final static byte PUK_LEN = (byte)16;
   final static byte PUK_TRY_LIMIT = (byte)3;

   private static byte[] Message;
   private static byte[] Hash;
   private static byte[] Sign;
   private short message_len = 0;
   private short hash_len = 0;
   private short sign_len = 0;
   private static boolean key_initialization_flag = false;
   private static boolean insert_message_flag = false;
   private static boolean hash_set_flag = false;
   private static boolean sign_set_flag = false;
   MessageDigest mDigest;
   KeyPair rsaKey;
   Cipher rsaCipher;
   OwnerPIN userPin;
   RandomData rnd;
   byte[] PUK;
   boolean init_flag;
   byte puk_try_count;

   private HasinCardApplet (byte[] bArray,short bOffset,byte bLength)
   {
       Message         = new byte[512];
       Hash            = new byte[512];
       Sign            = new byte[512];
       userPin         = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
       rsaKey          = new KeyPair(KeyPair.ALG_RSA,KeyBuilder.LENGTH_RSA_2048);
       rsaCipher       = Cipher.getInstance(Cipher.ALG_RSA_PKCS1, false);
       mDigest         = MessageDigest.getInstance(MessageDigest.ALG_SHA_256,false);
       rnd             = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
       PUK             = new byte[PUK_LEN];
       generate_random(PUK, (byte) 16);
       init_flag       = false;
       puk_try_count   = (byte) 0;

       byte[] InstParam                = new byte[(byte)64];
       byte iLen                       = bArray[bOffset];
       bOffset                         = (short) (bOffset+iLen+1);
       byte cLen                       = bArray[bOffset];
       bOffset                         = (short) (bOffset+cLen+1);
       byte aLen                       = bArray[bOffset];
       Util.arrayCopy(bArray,(short) (bOffset +1),InstParam,(short)0,(short)aLen);
       byte CPassLen = InstParam[0];
       byte IPassLen = InstParam[CPassLen+1];
       userPin.update(InstParam,(short)1,CPassLen);

       register();

   }

   public static void install(byte[] bArray, short bOffset, byte bLength)
   {
       new HasinCardApplet(bArray, bOffset, bLength);
   }

   public boolean select()
   {
       Util.arrayFillNonAtomic(Message, (short) 0, (short)512, (byte) 0x00);
       Util.arrayFillNonAtomic(Hash, (short) 0, (short)512, (byte) 0x00);
       Util.arrayFillNonAtomic(Sign, (short) 0, (short)512, (byte) 0x00);
       return true;
   }

   public void deselect()
   {

   }

   public void process(APDU apdu)
   {
      if (selectingApplet())
      {
          if(!init_flag) {
              send_puk(apdu);
          }
          return;
      }

      byte[] buffer = apdu.getBuffer();
      if (buffer[ISO7816.OFFSET_CLA] == APPLET_CLA) {

          switch (buffer[ISO7816.OFFSET_INS]) {

              case VERIFY_PIN:
                  verify_pin(apdu);
                  break;

              case CHANGE_PIN:
                  change_pin(apdu);
                  break;

              case UNLOCK_PIN:
                  unlock_pin(apdu);
                  break;

              case INITIALIZE:
                  initialize();
                  break;

              case INSERT_MESSAGE:
                  insert_message(apdu);
                  break;

              case CHECK_MESSAGE:
                  check_message(apdu);
                  break;

              case GET_HASH:
                  hash_message();
                  get_hash(apdu);
                  break;

              case GET_SIGN:
                  hash_message();
                  sign_message();
                  get_sign(apdu);
                  break;

              case VERIFY_SIGN:
                  verify_sign(apdu);
                  break;

              case TEST:
                  test(apdu);
                  break;

              default:
                  ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
          }
      } else {
          ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
      }
   }

   private void initialize() {
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(key_initialization_flag) {
           ISOException.throwIt(SW_KEY_IS_INITIALIZED);
       }
       rsaKey.genKeyPair();
       key_initialization_flag = true;
       init_flag = true;
   }

   private void insert_message(APDU apdu) {
       byte[] buffer = apdu.getBuffer();
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(buffer[ISO7816.OFFSET_P1] == (byte) 0x01) {
           // reset Message
           message_len = 0;
       }
       short LC = apdu.setIncomingAndReceive();
       Util.arrayCopy(buffer, (short) (ISO7816.OFFSET_CDATA), Message, message_len, LC);
       message_len = (short) (message_len + LC);
       insert_message_flag = true;
   }

   private void check_message(APDU apdu) {
       byte[] buffer = apdu.getBuffer();
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(insert_message_flag) {
           apdu.setIncomingAndReceive();
           Util.arrayCopy(Message, (short) 0, buffer, (short) 0, message_len);
           apdu.setOutgoingAndSend((short) 0, message_len);
       } else {
           ISOException.throwIt(SW_MESSAGE_NOT_INSERTED);
       }
   }

   private void hash_message() {
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(insert_message_flag) {
           mDigest.reset();
           hash_len = mDigest.doFinal(Message, (short) 0, message_len, Hash, (short) 0);
           hash_set_flag = true;
       } else {
           ISOException.throwIt(SW_MESSAGE_NOT_INSERTED);
       }
   }

   private void sign_message() {
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(insert_message_flag) {
           rsaCipher.init(rsaKey.getPrivate(), Cipher.MODE_ENCRYPT);
           sign_len = rsaCipher.doFinal(Hash, (short) 0, hash_len, Sign, (short) 0);
           sign_set_flag = true;
       } else {
           ISOException.throwIt(SW_MESSAGE_NOT_INSERTED);
       }
   }

   private void get_hash(APDU apdu) {
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(hash_set_flag) {
           byte[] buffer = apdu.getBuffer();
           Util.arrayCopy(Hash, (short) 0, buffer, (short) 0, hash_len);
           apdu.setOutgoingAndSend((short) 0, hash_len);
       } else {
           ISOException.throwIt(SW_HASH_NOT_SET);
       }
   }

   private void get_sign(APDU apdu) {
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       if(sign_set_flag) {
           byte[] buffer = apdu.getBuffer();
           Util.arrayCopy(Sign, (short) 0, buffer, (short) 0, sign_len);
           apdu.setOutgoingAndSend((short) 0, sign_len);
       } else {
           ISOException.throwIt(SW_SIGN_NOT_SET);
       }
   }

   private void verify_sign(APDU apdu) {
       byte[] buffer = apdu.getBuffer();
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       apdu.setIncomingAndReceive();
       rsaCipher.init(rsaKey.getPublic(), Cipher.MODE_DECRYPT);
       short LC = rsaCipher.doFinal(Sign, (short) 0, sign_len, buffer, (short)0);
       apdu.setOutgoingAndSend((short)0,LC);
   }

   private void verify_pin(APDU apdu) {
       byte[] buffer = apdu.getBuffer();
       if(userPin.getTriesRemaining() == (byte)0)
           ISOException.throwIt(SW_CARD_IS_LOCKED);
       short LC = (byte)(apdu.setIncomingAndReceive());
       if ( userPin.check(buffer, ISO7816.OFFSET_CDATA, (byte)LC) == false )
           ISOException.throwIt(SW_VERIFICATION_FAILED);
   }

   private void change_pin(APDU apdu) {
       byte[] buffer = apdu.getBuffer();
       if(userPin.getTriesRemaining() == (byte)0)
           ISOException.throwIt(SW_CARD_IS_LOCKED);
       if ( ! userPin.isValidated() )
           ISOException.throwIt(SW_PIN_VERIFICATION_REQUIRED);
       short LC = (byte)(apdu.setIncomingAndReceive());
       if(LC > MAX_PIN_SIZE)
           ISOException.throwIt(SW_NEW_PIN_TOO_LONG);
       if(LC < MIN_PIN_SIZE)
           ISOException.throwIt(SW_NEW_PIN_TOO_SHORT);
       userPin.update(buffer, (short) ISO7816.OFFSET_CDATA, (byte)LC);
   }

   private void unlock_pin(APDU apdu){
       if(puk_try_count >= PUK_TRY_LIMIT){
           ISOException.throwIt(SW_PUK_VERIFICATION_REJECTED);
       }
       byte[] buffer = apdu.getBuffer();
       apdu.setIncomingAndReceive();
       if (Util.arrayCompare(PUK, (short) 0, buffer, (short) (ISO7816.OFFSET_CDATA), (short) PUK_LEN) == 0) {
           userPin.resetAndUnblock();
           puk_try_count = (byte) 0;
           return;
       } else {
           puk_try_count = (byte)(puk_try_count + (byte)1);
           ISOException.throwIt(SW_PUK_VERIFICATION_FAILED);
       }
   }

   private void generate_random(byte[] buffer,byte len){
       rnd.generateData(buffer, (short) 0, (short) len);
   }

   private void send_puk(APDU apdu){
       apdu.setIncomingAndReceive();
       byte[] buffer = apdu.getBuffer();
       Util.arrayCopy(PUK,(short)0,buffer,(short)0,(short)PUK_LEN);
       apdu.setOutgoingAndSend((short) 0, (short) PUK_LEN);
   }

}

Appendix 2: GPShell 1.4.4 APDUs:

mode_211
enable_trace
establish_context
enable_trace
enable_timer
card_connect
command time: 15 ms
select -AID E0E1E2E3E4E501
Command --> 00A4040007E0E1E2E3E4E501
Wrapped command --> 00A4040007E0E1E2E3E4E501
Response <-- 26759506800664A82A242E481CD645C59000
command time: 78 ms
send_apdu -sc 1 -APDU 80060000083132333400000000 // verify userPin
Command --> 80060000083132333400000000
Wrapped command --> 80060000083132333400000000
Response <-- 9000
send_APDU() returns 0x80209000 (9000: Success. No error.)
command time: 16 ms
send_apdu -sc 1 -APDU 8000000000 // initialize
Command --> 8000000000
Wrapped command --> 8000000000
send_APDU() returns 0x8010002F (A communications error with the smart card has been detected. Retry the operation.)
MJay
  • 987
  • 1
  • 13
  • 36
  • 3
    It is a communication error. As i know the WTX request is not sent to reader in time. Because RSA 2048 key pair generation take long time, it needs one or more WTX request, but RSA 1024 key pair can be generated in a short time, no WTX needs. – JavaCardOS Mar 31 '17 at 07:22
  • 1
    In addition to what @JavaCardOS writes above, some readers are buggy and do not respect WTX requests at all and have their own internal timeout (if I remember correctly some Springcard readers behaved this way and Springcard had to send us a new firmware /which they did in a very short time/). So maybe try a different reader. – vlp Mar 31 '17 at 19:39
  • Additionally you might want to try doing any other time consuming operation -- say some busy loop and confirm it is not related to RSA keypair generation...Good luck! – vlp Mar 31 '17 at 19:40
  • @vlp I'It may very well be that there are differences between code run in the on-card Java Card VM and operations performed mostly in the native libs & the co-processor. If the loops crashes the communication then it is very likely the WTX extension but if it doesn't, it can still be the WTX extension not working on the card. It could also be a power consumption error. Also note that the full public key won't fit into a single (short length) response APDU. – Maarten Bodewes Apr 02 '17 at 12:51
  • Thank you @vlp , I will try your suggestion. – MJay Apr 02 '17 at 15:12
  • @MaartenBodewes thank you Mararten for your response, I will use your advice. – MJay Apr 02 '17 at 15:16
  • @MaartenBodewes -- what you write about power consumption reminds me that there might be problems doing RSA over contactless interface (from what I've found this particular card is dual interface). From the APDU trace it seems the RSA key is not sent, so the APDU buffer size should not matter (of course depending how the applet is written). I wonder what outcome will be, @MJay please share your progress...(PS: If you want to experiment with the busy loop way, you might find method `APDU.waitExtension()` interesting, but I have never used it as sane cards usually handle that themselves...) – vlp Apr 02 '17 at 22:28
  • @vlp I tried to check key creation method on other readers. I tried ACR83 and REINERSCT, nothing changed. If this had been a matter of time which is rejected by reader, this procedure should have been finished after reader error, unless card power has been disconnected. – MJay Apr 04 '17 at 04:49
  • @MaartenBodewes don't know why I cannot tag two names at the same time in a comment! I invite you to read the comment above. – MJay Apr 04 '17 at 04:51
  • @MaartenBodewes I also asked the Card vendor why this happens. They answered "Change your `Install and make selectable` APDU from `84 E6 0C 00` to `80 E6 0C 00` and it will work! is this a matter of security?! – MJay Apr 04 '17 at 09:26
  • @vlp please follow the comment above. – MJay Apr 04 '17 at 09:27
  • @MJay I don't see how removing the confidentiality of INSTALL [and make Selectable] makes a change in your situation. OTOH, I don't see your code either, so I cannot tell for sure if it would make a difference. Maybe it's time to post your code? – Maarten Bodewes Apr 04 '17 at 09:59
  • @MaartenBodewes I added code and test APDUs. – MJay Apr 04 '17 at 10:50
  • Could you try moving the initialization of static non-final fields (`key_initialization_flag`, `insert_message_flag`, `hash_set_flag`, `sign_set_flag`, ) into the constructor (i.e. remove the assignment in declaration and move this assignment into the constructor)? Or delete the `static` keyword at all. (AFAIK) There is no static constructor in javacard, so this part might make trouble... – vlp Apr 04 '17 at 18:15
  • @vlp I moved them to constructor and removed the `static` prefix. The problem still remains. – MJay Apr 05 '17 at 05:33
  • @Mjay: I am out of ideas then. The reason I asked you for moving the static initialization to constructor is because I have seen certain cards doing strange things (unexplained communication errors included) when code contained static initializations like this (it needs to be said that those initializations were byte arrays and not primitive types as in your case)..Good luck! – vlp Apr 05 '17 at 12:54
  • @vlp I had some other sort of problem that static byte arrays do, my card stopped responding forever after a few APDUs. For the problem above, I asked the card vendor to help and he responded "you should send install parameter to card as `13C91100112233445566778899AABBCCDDEEFF01` and it will be allowed to use a specific chip for 2048 key pair generation", but this did not work for me! – MJay Apr 05 '17 at 13:18

0 Answers0