JDK-8163251 : Hard coded loop limit prevents reading of smart card data greater than 8k
  • Type: Bug
  • Component: security-libs
  • Sub-Component: javax.smartcardio
  • Affected Version: 8u77
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: other
  • CPU: x86
  • Submitted: 2016-04-03
  • Updated: 2020-08-02
  • Resolved: 2020-02-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 8 Other
11.0.7Fixed 8u241Fixed openjdk8u272Fixed
Related Reports
Relates :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Windows 10 but this should be occurring on all OSs

EXTRA RELEVANT SYSTEM CONFIGURATION :
generic smart card reader for contact/contactless smart cards (omnikey 5321)

A DESCRIPTION OF THE PROBLEM :
based on the open JDK files
in 
sun/security/smartcardio/ChannelImpld.java 
private byte[] doTransmit(byte[] command) throws CardException

there is a check in the loop to retrieve all the data in response to a card commnad. This check has the following erroneous error check (around line 185):

if (++k >= 32) {
     throw new CardException("Could not obtain response");
}

this check prevents reading of response data > 8k and worse throws an incorrect exception. 

The default limit should be increased and possibly have the ability to be configured. Additionally the exception message needs to be corrected.


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Get a smart card containing a structure with more than 8k of data (mine has 10k).
Smart card reader



EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
ResponseAPDU should return succesfully and contain >8k of data.
ACTUAL -
Invalid exception is thrown.

Exception in thread "main" javax.smartcardio.CardException: Could not obtain response
	at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:186)
	at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
	at com.tvec.smartcardio.ErrorTest.main(ErrorTest.java:58)


ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" javax.smartcardio.CardException: Could not obtain response
	at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:186)
	at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
	at com.tvec.smartcardio.ErrorTest.main(ErrorTest.java:58)


REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
package com.tvec.smartcardio;

import java.security.NoSuchAlgorithmException;

import javax.smartcardio.Card;
import javax.smartcardio.CardChannel;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
import javax.smartcardio.CommandAPDU;
import javax.smartcardio.ResponseAPDU;
import javax.smartcardio.TerminalFactory;

import com.tvec.smart_card.pcsc.PCSCConstants.ShareMode;

public class ErrorTest {
  public static void main(String args[]) throws CardException {
    // PCSCWrapper wrapper = new PCSCWrtapper();

    Card card = null;
    String readerName = "OMNIKEY CardMan 5x21 0";
    String protocol = "T=1";
    ShareMode shareMode = ShareMode.Exclusive;

    // TerminalFactory terminalFactory = TerminalFactory.getDefault();
    TerminalFactory terminalFactory = null;
    try {

      java.security.Provider provider = java.security.Security.getProvider("SunPCSC");

      terminalFactory = TerminalFactory.getInstance("PC/SC", null, provider);

      CardTerminal terminal = terminalFactory.terminals().getTerminal(
          readerName);
      card = terminal.connect(protocol);
      if (shareMode == ShareMode.Exclusive) {
        card.beginExclusive();
      }

      CardChannel cardChannel = card.getBasicChannel();
      ResponseAPDU responseAPDU = null;

      // verify for access to large container in my test case
      CommandAPDU inCommandAPDU = new CommandAPDU(
          ErrorTest.toSignedByteArray("0020008008313233343536ffff"));
      responseAPDU = cardChannel.transmit(inCommandAPDU);
      System.out.println(Integer.toHexString(responseAPDU.getSW()));

      // send command that returns a response greater than 8k
      inCommandAPDU = new CommandAPDU(
          ErrorTest.toSignedByteArray("00cb3fff055c035fc108"));

      // exception will be thrown here when accessing large container.
      // javax.smartcardio.CardException: Could not obtain response
      // at
      // sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:186)
      // at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)

      responseAPDU = cardChannel.transmit(inCommandAPDU);

      System.out.println(Integer.toHexString(responseAPDU.getSW()));
    } catch (NoSuchAlgorithmException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
  }

  public static byte[] toSignedByteArray(String in) {
    // convert the string into bytes and add them to the out array
    in = in.toLowerCase();
    if (in.startsWith("0x")) {
      // pull off the 0x
      in = in.substring(2);
    }
    // pad with 0 to make length a multiple of 2
    // 2 chars make a byte
    if (in.length() % 2 > 0) {
      in = "0" + in;
    }

    // build the output array
    byte[] out = new byte[in.length() / 2];
    for (int i = 0; i < out.length; i++) {
      out[i] = (byte) ((Character.digit(in.charAt(i * 2), 16) << 4) + Character
          .digit(in.charAt(i * 2 + 1), 16));
    }

    return out;
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
I wrote my own provider using the open JDK source as a guide and a Windows JNI smartcard library. In my implementation I added a maximum loop try variable and increased the limit to 100 from 32. This however does not solve the issue for other platforms which I would like to deploy to.


Comments
Fix Request (8u) Same rationale as 11u backport. Patch applies with reshuffling and copyright line adjustment. 8u RFR (acked by zgu): https://mail.openjdk.java.net/pipermail/jdk8u-dev/2020-July/012288.html
29-07-2020

8u candidate: https://mail.openjdk.java.net/pipermail/jdk8u-dev/2020-July/012288.html
28-07-2020

Fix request (13u) Requesting backport to 13u for parity with 11u, applies cleanly.
02-06-2020

Fix Request (11u) This fixes the artificial limitation that is not in line with the spec (see the review thread for more details), and keeps codebases in sync (I see 11.0.8-oracle). Patch applies cleanly to 11u, passes tier{1,2,3} tests.
18-02-2020

URL: https://hg.openjdk.java.net/jdk/jdk/rev/220415dfb4ac User: igerasim Date: 2020-02-18 00:32:32 +0000
18-02-2020