package org.irmacard.chvservice;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.List;
import java.util.Vector;
import javax.smartcardio.CardException;
import net.sourceforge.scuba.util.Hex;
import net.sourceforge.scuba.smartcards.CardService;
import net.sourceforge.scuba.smartcards.CardServiceException;
import net.sourceforge.scuba.smartcards.CommandAPDU;
import net.sourceforge.scuba.smartcards.ResponseAPDU;
import net.sourceforge.scuba.smartcards.TerminalCardService;
public class CardHolderVerificationService extends CardService {
private static final long serialVersionUID = -7992986822145276115L;
public final static int PIN_OK = 1000;
static final String[] FEATURES = new String[]{"NO_FEATURE",
"FEATURE_VERIFY_PIN_START",
"FEATURE_VERIFY_PIN_FINISH",
"FEATURE_MODIFY_PIN_START",
"FEATURE_MODIFY_PIN_FINISH",
"FEATURE_GET_KEY_PRESSED",
"FEATURE_VERIFY_PIN_DIRECT",
"FEATURE_MODIFY_PIN_DIRECT",
"FEATURE_MCT_READER_DIRECT",
"FEATURE_MCT_UNIVERSAL",
"FEATURE_IFD_PIN_PROPERTIES",
"FEATURE_ABORT",
"FEATURE_SET_SPE_MESSAGE",
"FEATURE_VERIFY_PIN_DIRECT_APP_ID",
"FEATURE_MODIFY_PIN_DIRECT_APP_ID",
"FEATURE_WRITE_DISPLAY",
"FEATURE_GET_KEY",
"FEATURE_IFD_DISPLAY_PROPERTIES"};
static final Byte FEATURE_VERIFY_PIN_START = new Byte((byte) 0x01);
static final Byte FEATURE_VERIFY_PIN_FINISH = new Byte((byte) 0x02);
static final Byte FEATURE_MODIFY_PIN_START = new Byte((byte) 0x03);
static final Byte FEATURE_MODIFY_PIN_FINISH = new Byte((byte) 0x04);
static final Byte FEATURE_GET_KEY_PRESSED = new Byte((byte) 0x05);
static final Byte FEATURE_VERIFY_PIN_DIRECT = new Byte((byte) 0x06);
static final Byte FEATURE_MODIFY_PIN_DIRECT = new Byte((byte) 0x07);
static final Byte FEATURE_MCT_READER_DIRECT = new Byte((byte) 0x08);
static final Byte FEATURE_MCT_UNIVERSAL = new Byte((byte) 0x09);
static final Byte FEATURE_IFD_PIN_PROPERTIES = new Byte((byte) 0x0a);
HashMap<Byte, Integer> features;
byte bEntryValidationCondition = 0x02; // validation key pressed
byte bTimeOut = 0x00; // 0x3c; // 60sec (= max on ReinerSCT)
byte bTimeOut2 = 0x00; // default (attention with SCM)
byte wPINMaxExtraDigitMin = 0x00; // min pin length zero digits
byte wPINMaxExtraDigitMax = 0x04; // max pin length 12 digits
private TerminalCardService service;
private List<IPinVerificationListener> pinCallbacks = new Vector<IPinVerificationListener>();
/* Invariant: when no false PIN was entered in the last attempt
* value is null. Otherwise equal to the number of tries left.
*/
private Integer nrTriesLeft = null;
public CardHolderVerificationService(TerminalCardService service) {
this.service = service;
}
/**
* Adds a new listener
* @param cb The listener to add
*/
public void addPinVerificationListener(IPinVerificationListener cb) {
pinCallbacks.add(cb);
}
/**
* Removes a listener
* @param cb The listener to remove
*/
public void removePinVerificationListener(IPinVerificationListener cb) {
pinCallbacks.remove(cb);
}
public void open() throws CardServiceException {
service.open();
}
public boolean isOpen() {
return service.isOpen();
}
public ResponseAPDU transmit(CommandAPDU capdu)
throws CardServiceException {
return service.transmit(capdu);
}
public byte[] transmitControlCommand(int controlCode, byte[] command)
throws CardServiceException {
return service.transmitControlCommand(controlCode, command);
}
public void close() {
service.close();
}
public String getName() {
return "CardHolderVerification: " + service.getName();
}
public int verifyPIN()
throws CardServiceException {
//queryFeatures();
//if (features.containsKey(FEATURE_VERIFY_PIN_DIRECT)) {
// return verifyPinUsingPinpad();
//} else {
return verifyPinUsingDialog();
//}
}
private int verifyPinUsingDialog()
throws CardServiceException {
String pinString = null;
for (IPinVerificationListener l : pinCallbacks) {
pinString = l.userPinRequest(nrTriesLeft);
}
byte[] pinBytes = pinString.getBytes();
byte[] data = new byte[8];
System.arraycopy(pinBytes, 0, data, 0, pinBytes.length);
CommandAPDU c = new CommandAPDU(0, 0x20, 0, 0, data);
//CommandAPDU c = new CommandAPDU(0, 0x20, 0, 0, pinString.getBytes());
System.out.println("C: " + Hex.toHexString(c.getBytes()));
ResponseAPDU r = service.transmit(c);
System.out.println("R: " + Hex.toHexString(r.getBytes()));
return processPinResponse(r.getSW());
}
@SuppressWarnings("unused")
private int verifyPinUsingPinpad()
throws CardServiceException {
try {
setUpReader();
} catch (Exception e) {
e.printStackTrace();
throw new CardServiceException("PIN verification failed: " + e.getMessage());
}
for (IPinVerificationListener l : pinCallbacks) {
l.pinPadPinRequired(nrTriesLeft);
}
int sw = Integer.parseInt(Hex.toHexString(VERIFY_PIN_DIRECT()), 16);
for (IPinVerificationListener l : pinCallbacks) {
l.pinPadPinEntered();
}
return processPinResponse(sw);
}
private int processPinResponse(int sw) throws CardServiceException {
if(sw == 0x9000) {
nrTriesLeft = null;
return PIN_OK;
} else if ((sw & 0xFFF0) == 0x63C0) {
nrTriesLeft = sw & 0x000F;
return nrTriesLeft;
} else {
throw new CardServiceException("PIN verification failed: " + Hex.intToHexString(sw));
}
}
private static int SCARD_CTL_CODE(int code) {
int ioctl;
String os_name = System.getProperty("os.name").toLowerCase();
if (os_name.indexOf("windows") > -1) {
ioctl = (0x31 << 16 | (code) << 2);
} else {
ioctl = 0x42000000 + (code);
}
return ioctl;
}
static int IOCTL_GET_FEATURE_REQUEST = SCARD_CTL_CODE(3400);
protected void setUpReader() throws IOException, NoSuchAlgorithmException,
CardException {
String os = System.getProperty("os.name");
boolean pcsclite = os.toLowerCase().indexOf("windows") < 0;
String name = service.getTerminal().getName().toLowerCase();
if (name.startsWith("gemplus gempc pinpad")
|| name.startsWith("gemalto gempc pinpad")) {
/*
* Gemplus Pinpad - VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:00
* :02:01:09:04:00:00:00:00:0d:00:00:00:00:20:00
* :01:08:20:ff:ff:ff:ff:ff:ff:ff] Linux(?):
* transmitControlCommand() failed:
* sun.security.smartcardio.PCSCException: SCARD_E_NOT_TRANSACTED
* Win7: [6b:80] - VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:08:04
* :02:01:09:04:00:00:00:00:0d:00:00:00:00:20:00
* :01:08:20:ff:ff:ff:ff:ff:ff:ff] Linux(?): response [64:00]
* (18154msec) Win7 (mit bTimeOut 0x3c): [00:40:02:90:00:d2]
*/
wPINMaxExtraDigitMin = 0x04;
wPINMaxExtraDigitMax = 0x08;
} else if (name.startsWith("reiner-sct cyberjack pinpad(a)")) {
// Reiner-SCT cyberJack pinpad(a) (2242245778) 00 00
/*
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:00:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* response [64:00] (14994msec)
*/
} else if (name.startsWith("reiner sct cyberjack")) {
// REINER SCT CyberJack 00 00
/*
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:00:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* response [67:00]
*/
// if (pcsclite) {
// TODO fallback to DefaultReader
// report.write("setting custom bTimeOut (0x0f) for " + name);
// bTimeOut = 0x0f;
// report.write("setting custom bTimeOut2 (0x0f) for " + name);
// bTimeOut2 = 0x0f;
// report.write("setting custom wPINMaxExtraDigitMin (0x04) for " +
// name);
// wPINMaxExtraDigitMin = 0x04;
// report.write("setting custom wPINMaxExtraDigitMax (0x08) for " +
// name);
// wPINMaxExtraDigitMax = 0x08;
// }
} else if (name.startsWith("omnikey cardman 3621")) {
// OmniKey CardMan 3621 00 00
/*
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:00:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* response [90:00] (5204msec)
*/
// log.debug("setting custom wPINMaxExtraDigitH (0x01) for " +
// name);
// wPINMaxExtraDigitH = 0x01;
} else if (name.startsWith("scm spr 532")
|| name.startsWith("scm microsystems inc. sprx32 usb smart card reader")) {
// SCM SPR 532 (60200DC5) 00 00
/*
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:00:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* transmitControlCommand() failed:
* sun.security.smartcardio.PCSCException: SCARD_E_NOT_TRANSACTED
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:01:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* response [64:00] (15543msec)
*/
if (pcsclite) {
wPINMaxExtraDigitMin = 0x01;
}
} else if (name.startsWith("cherry smartboard xx44")) {
if (pcsclite) {
wPINMaxExtraDigitMin = 0x01;
}
} else if (name.startsWith("cherry smartterminal st-2xxx")) {
// Cherry SmartTerminal ST-2XXX (21121010102014) 00 00
/*
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:00:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* transmitControlCommand() failed:
* sun.security.smartcardio.PCSCException: SCARD_E_NOT_TRANSACTED
* VERIFY_PIN_DIRECT (42330006)
* [00:00:89:47:04:0c:01:02:01:09:04:00:
* 00:00:00:0d:00:00:00:00:20:00:01:08:20:ff:ff:ff:ff:ff:ff:ff]
* response [64:00] (15358msec)
*/
if (pcsclite) {
wPINMaxExtraDigitMin = 0x01;
}
}
}
protected void queryFeatures() throws CardServiceException {
features = new HashMap<Byte, Integer>();
try {
byte[] resp = service.transmitControlCommand(
IOCTL_GET_FEATURE_REQUEST, new byte[0]);
for (int i = 0; i < resp.length; i += 6) {
Byte feature = new Byte(resp[i]);
Integer ioctl = new Integer((0xff & resp[i + 2]) << 24)
| ((0xff & resp[i + 3]) << 16)
| ((0xff & resp[i + 4]) << 8) | (0xff & resp[i + 5]);
features.put(feature, ioctl);
}
} catch (Exception e) {
// Apperently we cannot query features, assuming no features
// present;
// FIXME: better describe type, stacktrace says
// javax.smartcardio.CardException
// is thrown, but that should not be possible...
e.printStackTrace();
}
}
protected byte[] VERIFY_PIN_DIRECT() throws CardServiceException {
byte[] PIN_VERIFY = createPINVerifyStructure();
int ioctl = features.get(FEATURE_VERIFY_PIN_DIRECT);
return service.transmitControlCommand(ioctl, PIN_VERIFY);
}
protected byte[] createPINVerifyStructure() {
// VerifyAPDUSpec apduSpec = new VerifyAPDUSpec(
byte[] apdu = new byte[] { (byte) 0x00, (byte) 0x20, (byte) 0x00,
(byte) 0x00, (byte) 0x04, (byte) 0x00, (byte) 0x00,
(byte) 0x00, (byte) 0x00 };
// 1, VerifyAPDUSpec.PIN_FORMAT_BCD, 7, 4, 4);
ByteArrayOutputStream s = new ByteArrayOutputStream();
// bTimeOut
s.write(bTimeOut);
// bTimeOut2
s.write(bTimeOut2);
// bmFormatString [10001001 0x89]
s.write(0x82);
// s.write(1 << 7 // system unit = byte
// | (0xF & 1) << 3 // apduSpec.getPinPosition() (0001 ... pin 1
// // byte after format)
// | (0x1 & 0 << 2) // apduSpec.getPinJustification() (0 ... left
// // justify)
// | (0x3 & 1)); // apduSpec.getPinFormat() (01 ... BCD)
// bmPINBlockString [01000111 0x47]
s.write(0x04);
// s.write((0xF & 4) << 4 // apduSpec.getPinLengthSize() (0100 ... 4 bit
// // pin length)
// | (0xF & 7)); // apduSpec.getPinLength() (0111 ... 7 bytes pin
// // block size)
// bmPINLengthFormat [00000100 0x04]
s.write(0x00);
// s.write(// system unit = bit
// (0xF & 4)); // apduSpec.getPinLengthPos() (00000100 ... pin length
// // position 4 bits)
// wPINMaxExtraDigit (little endian) [0x0c 0x00]
s.write(wPINMaxExtraDigitMax); // max PIN length
s.write(wPINMaxExtraDigitMin); // min PIN length
// bEntryValidationCondition [0x02]
s.write(bEntryValidationCondition);
// bNumberMessage
s.write(0x01);
// wLangId [0x04 0x09 english, little endian]
s.write(0x04);
s.write(0x09);
// bMsgIndex
s.write(0x01);
// bTeoPrologue
s.write(0x00);
s.write(0x00);
s.write(0x00);
// ulDataLength
s.write(apdu.length);
s.write(0x00);
s.write(0x00);
s.write(0x00);
// abData
try {
s.write(apdu);
} catch (IOException e) {
// As we are dealing with ByteArrayOutputStreams no exception is to
// be
// expected.
throw new RuntimeException(e);
}
return s.toByteArray();
}
@Override
public byte[] getATR() throws CardServiceException {
return service.getATR();
}
}