/* * Copyright 2008 Federal Chancellery Austria and * Graz University of Technology * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package at.gv.egiz.smcc.reader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Map; import javax.smartcardio.Card; import javax.smartcardio.CardChannel; import javax.smartcardio.CardException; import javax.smartcardio.CardTerminal; import javax.smartcardio.ResponseAPDU; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import at.gv.egiz.smcc.CancelledException; import at.gv.egiz.smcc.ChangeReferenceDataAPDUSpec; import at.gv.egiz.smcc.NewReferenceDataAPDUSpec; import at.gv.egiz.smcc.PINConfirmationException; import at.gv.egiz.smcc.PINFormatException; import at.gv.egiz.smcc.PINOperationAbortedException; import at.gv.egiz.smcc.PINSpec; import at.gv.egiz.smcc.ResetRetryCounterAPDUSpec; import at.gv.egiz.smcc.SignatureCardException; import at.gv.egiz.smcc.TimeoutException; import at.gv.egiz.smcc.VerifyAPDUSpec; import at.gv.egiz.smcc.pin.gui.ModifyPINGUI; import at.gv.egiz.smcc.pin.gui.PINGUI; import at.gv.egiz.smcc.util.SMCCHelper; /** * * @author Clemens Orthacker */ public class PinpadCardReader extends DefaultCardReader { public static final int PIN_ENTRY_POLLING_INTERVAL = 10; protected final static Log log = LogFactory.getLog(PinpadCardReader.class); protected byte bEntryValidationCondition = 0x02; // validation key pressed protected byte bTimeOut = 0x3c; // 60sec (= max on ReinerSCT) protected byte bTimeOut2 = 0x00; // default (attention with SCM) protected byte wPINMaxExtraDigitH = 0x00; // min pin length zero digits protected byte wPINMaxExtraDigitL = 0x0c; // max pin length 12 digits /** * supported features and respective control codes */ protected Map features; protected boolean VERIFY, MODIFY, VERIFY_DIRECT, MODIFY_DIRECT; public PinpadCardReader(CardTerminal ct, Map features) { super(ct); if (features == null) { throw new NullPointerException("Pinpad card reader does not support any features"); } this.features = features; if (features.containsKey(FEATURE_VERIFY_PIN_START) && features.containsKey(FEATURE_GET_KEY_PRESSED) && features.containsKey(FEATURE_VERIFY_PIN_FINISH)) { VERIFY = true; } if (features.containsKey(FEATURE_MODIFY_PIN_START) && features.containsKey(FEATURE_GET_KEY_PRESSED) && features.containsKey(FEATURE_MODIFY_PIN_FINISH)) { MODIFY = true; } if (features.containsKey(FEATURE_VERIFY_PIN_DIRECT)) { VERIFY_DIRECT = true; } if (features.containsKey(FEATURE_MODIFY_PIN_DIRECT)) { MODIFY_DIRECT = true; } if (name != null) { name = name.toLowerCase(); //ReinerSCT: http://support.reiner-sct.de/downloads/LINUX // http://www.linux-club.de/viewtopic.php?f=61&t=101287&start=0 //old: REINER SCT CyberJack 00 00 //new (CCID): 0C4B/0300 Reiner-SCT cyberJack pinpad(a) 00 00 //Snow Leopard: Reiner-SCT cyberJack pinpad(a) 00 00 //display: REINER SCT CyberJack 00 00 if(name.startsWith("gemplus gempc pinpad") || name.startsWith("gemalto gempc pinpad")) { log.debug("setting custom wPINMaxExtraDigitH (0x04) for " + name); wPINMaxExtraDigitH = 0x04; log.debug("setting custom wPINMaxExtraDigitL (0x08) for " + name); wPINMaxExtraDigitL = 0x08; } else if (name.startsWith("omnikey cardman 3621")) { 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")) { log.debug("setting custom bTimeOut (0x3c) for " + name); bTimeOut = 0x3c; log.debug("setting custom bTimeOut2 (0x0f) for " + name); bTimeOut2 = 0x0f; } else if (name.startsWith("cherry smartboard xx44")) { log.debug("setting custom wPINMaxExtraDigitH (0x01) for " + name); wPINMaxExtraDigitH = 0x01; } } } @Override public boolean hasFeature(Byte feature) { return features.containsKey(feature); } private void VERIFY_PIN_START(Card icc, byte[] PIN_VERIFY) throws CardException { int ioctl = features.get(FEATURE_VERIFY_PIN_START); if (log.isTraceEnabled()) { log.trace("VERIFY_PIN_START (" + Integer.toHexString(ioctl) + ") " + SMCCHelper.toString(PIN_VERIFY)); } byte[] resp = icc.transmitControlCommand(ioctl, PIN_VERIFY); if (resp != null && resp.length > 0) { if (resp[0] == (byte) 0x57) { log.error("Invalid parameter in PIN_VERIFY structure"); throw new CardException("ERROR_INVALID_PARAMETER"); } else { log.error("unexpected response to VERIFY_PIN_START: " + SMCCHelper.toString(resp)); throw new CardException("unexpected response to VERIFY_PIN_START: " + SMCCHelper.toString(resp)); } } } private byte GET_KEY_PRESSED(Card icc) throws CardException { int ioctl = features.get(FEATURE_GET_KEY_PRESSED); byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]); if (resp != null && resp.length == 1) { // if (log.isTraceEnabled()) { // log.trace("response " + SMCCHelper.toString(resp)); // } return resp[0]; } log.error("unexpected response to GET_KEY_PRESSED: " + SMCCHelper.toString(resp)); throw new CardException("unexpected response to GET_KEY_PRESSED: " + SMCCHelper.toString(resp)); } private byte[] VERIFY_PIN_FINISH(Card icc) throws CardException { int ioctl = features.get(FEATURE_VERIFY_PIN_FINISH); if (log.isTraceEnabled()) { log.trace("VERIFY_PIN_FINISH (" + Integer.toHexString(ioctl) + ")"); } byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]); if (resp != null && resp.length == 2) { if (log.isTraceEnabled()) { log.trace("response " + SMCCHelper.toString(resp)); } return resp; } log.error("unexpected response to VERIFY_PIN_FINISH: " + SMCCHelper.toString(resp)); throw new CardException("unexpected response to VERIFY_PIN_FINISH: " + SMCCHelper.toString(resp)); } private void MODIFY_PIN_START(Card icc, byte[] PIN_MODIFY) throws CardException { int ioctl = features.get(FEATURE_MODIFY_PIN_START); if (log.isTraceEnabled()) { log.trace("MODFIY_PIN_START (" + Integer.toHexString(ioctl) + ") " + SMCCHelper.toString(PIN_MODIFY)); } byte[] resp = icc.transmitControlCommand(ioctl, PIN_MODIFY); if (resp != null && resp.length > 0) { if (resp[0] == (byte) 0x57) { log.error("Invalid parameter in PIN_MODIFY structure"); throw new CardException("ERROR_INVALID_PARAMETER"); } else { log.error("unexpected response to MODIFY_PIN_START: " + SMCCHelper.toString(resp)); throw new CardException("unexpected response to MODIFY_PIN_START: " + SMCCHelper.toString(resp)); } } } private byte[] MODIFY_PIN_FINISH(Card icc) throws CardException { int ioctl = features.get(FEATURE_MODIFY_PIN_FINISH); if (log.isTraceEnabled()) { log.trace("MODIFY_PIN_FINISH (" + Integer.toHexString(ioctl) + ")"); } byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]); if (resp != null && resp.length == 2) { if (log.isTraceEnabled()) { log.trace("response " + SMCCHelper.toString(resp)); } return resp; } log.error("unexpected response to MODIFY_PIN_FINISH: " + SMCCHelper.toString(resp)); throw new CardException("unexpected response to MODIFY_PIN_FINISH: " + SMCCHelper.toString(resp)); } private byte[] VERIFY_PIN_DIRECT(Card icc, byte[] PIN_VERIFY) throws CardException { int ioctl = features.get(FEATURE_VERIFY_PIN_DIRECT); if (log.isTraceEnabled()) { log.trace("VERIFY_PIN_DIRECT (" + Integer.toHexString(ioctl) + ") " + SMCCHelper.toString(PIN_VERIFY)); } byte[] resp = icc.transmitControlCommand(ioctl, PIN_VERIFY); if (log.isTraceEnabled()) { log.trace("response " + SMCCHelper.toString(resp)); } return resp; } private byte[] verifyPin(Card icc, byte[] PIN_VERIFY, PINGUI pinGUI) throws SignatureCardException, CardException, InterruptedException { // pinGUI.enterPIN(pinSpec, retries); log.debug("VERIFY_PIN_START [" + FEATURES[FEATURE_VERIFY_PIN_START] + "]"); VERIFY_PIN_START(icc, PIN_VERIFY); byte resp; do { resp = GET_KEY_PRESSED(icc); if (resp == (byte) 0x00) { synchronized(this) { try { wait(PIN_ENTRY_POLLING_INTERVAL); } catch (InterruptedException ex) { log.error("interrupted in VERIFY_PIN"); } } } else if (resp == (byte) 0x0d) { log.debug("GET_KEY_PRESSED: 0x0d (user confirmed)"); break; } else if (resp == (byte) 0x2b) { log.trace("GET_KEY_PRESSED: 0x2b (user entered valid key 0-9)"); pinGUI.validKeyPressed(); } else if (resp == (byte) 0x1b) { log.debug("GET_KEY_PRESSED: 0x1b (user cancelled VERIFY_PIN via cancel button)"); break; // returns 0x6401 } else if (resp == (byte) 0x08) { log.debug("GET_KEY_PRESSED: 0x08 (user pressed correction/backspace button)"); pinGUI.correctionButtonPressed(); } else if (resp == (byte) 0x0e) { log.debug("GET_KEY_PRESSED: 0x0e (timeout occured)"); break; // return 0x6400 } else if (resp == (byte) 0x40) { log.debug("GET_KEY_PRESSED: 0x40 (PIN_Operation_Aborted)"); throw new PINOperationAbortedException("PIN_Operation_Aborted (0x40)"); } else if (resp == (byte) 0x0a) { log.debug("GET_KEY_PRESSED: 0x0a (all keys cleared"); pinGUI.allKeysCleared(); } else { log.error("unexpected response to GET_KEY_PRESSED: " + Integer.toHexString(resp)); throw new CardException("unexpected response to GET_KEY_PRESSED: " + Integer.toHexString(resp)); } } while (true); return VERIFY_PIN_FINISH(icc); } /** * does not display the first pin dialog (enterCurrentPIN or enterNewPIN, depends on bConfirmPIN), * since this is easier to do in calling modify() */ private byte[] modifyPin(Card icc, byte[] PIN_MODIFY, ModifyPINGUI pinGUI, PINSpec pINSpec) throws PINOperationAbortedException, CardException { byte pinConfirmations = (byte) 0x00; //b0: new pin not entered (0) / entered (1) //b1: current pin not entered (0) / entered (1) byte bConfirmPIN = PIN_MODIFY[9]; // if ((bConfirmPIN & (byte) 0x02) == 0) { // log.debug("no current PIN entry requested"); // pinGUI.enterNewPIN(pINSpec); // } else { // log.debug("current PIN entry requested"); // pinGUI.enterCurrentPIN(pINSpec, retries); // } log.debug("MODIFY_PIN_START [" + FEATURES[FEATURE_MODIFY_PIN_START] + "]"); MODIFY_PIN_START(icc, PIN_MODIFY); byte resp; while (true) { resp = GET_KEY_PRESSED(icc); if (resp == (byte) 0x00) { synchronized(this) { try { wait(PIN_ENTRY_POLLING_INTERVAL); } catch (InterruptedException ex) { log.error("interrupted in MODIFY_PIN"); } } } else if (resp == (byte) 0x0d) { if (log.isTraceEnabled()) { log.trace("requested pin confirmations: 0b" + Integer.toBinaryString(bConfirmPIN & 0xff)); log.trace("performed pin confirmations: 0b" + Integer.toBinaryString(pinConfirmations & 0xff)); } log.debug("GET_KEY_PRESSED: 0x0d (user confirmed)"); if (pinConfirmations == bConfirmPIN) { break; } else if ((bConfirmPIN & (byte) 0x02) == 0 || (pinConfirmations & (byte) 0x02) == (byte) 0x02) { // no current pin entry or current pin entry already performed if ((pinConfirmations & (byte) 0x01) == 0) { // new pin pinConfirmations |= (byte) 0x01; pinGUI.confirmNewPIN(pINSpec); } // else: new pin confirmed } else { // current pin entry pinConfirmations |= (byte) 0x02; pinGUI.enterNewPIN(pINSpec); } } else if (resp == (byte) 0x2b) { log.trace("GET_KEY_PRESSED: 0x2b (user entered valid key 0-9)"); pinGUI.validKeyPressed(); } else if (resp == (byte) 0x1b) { log.debug("GET_KEY_PRESSED: 0x1b (user cancelled VERIFY_PIN via cancel button)"); break; // returns 0x6401 } else if (resp == (byte) 0x08) { log.debug("GET_KEY_PRESSED: 0x08 (user pressed correction/backspace button)"); pinGUI.correctionButtonPressed(); } else if (resp == (byte) 0x0e) { log.debug("GET_KEY_PRESSED: 0x0e (timeout occured)"); break; // return 0x6400 } else if (resp == (byte) 0x40) { log.debug("GET_KEY_PRESSED: 0x40 (PIN_Operation_Aborted)"); throw new PINOperationAbortedException("PIN_Operation_Aborted (0x40)"); } else if (resp == (byte) 0x0a) { log.debug("GET_KEY_PRESSED: 0x0a (all keys cleared"); pinGUI.allKeysCleared(); } else { log.error("unexpected response to GET_KEY_PRESSED: " + Integer.toHexString(resp)); throw new CardException("unexpected response to GET_KEY_PRESSED: " + Integer.toHexString(resp)); } } pinGUI.finish(); return MODIFY_PIN_FINISH(icc); } private byte[] MODIFY_PIN_DIRECT(Card icc, byte[] PIN_MODIFY) throws CardException { int ioctl = features.get(FEATURE_MODIFY_PIN_DIRECT); if (log.isTraceEnabled()) { log.trace("MODIFY_PIN_DIRECT (" + Integer.toHexString(ioctl) + ") " + SMCCHelper.toString(PIN_MODIFY)); } byte[] resp = icc.transmitControlCommand(ioctl, PIN_MODIFY); if (log.isTraceEnabled()) { log.trace("response " + SMCCHelper.toString(resp)); } return resp; } protected byte[] createPINModifyStructure(NewReferenceDataAPDUSpec apduSpec, PINSpec pinSpec) { ByteArrayOutputStream s = new ByteArrayOutputStream(); // bTimeOut s.write(bTimeOut); // bTimeOut2 s.write(bTimeOut2); // bmFormatString s.write(1 << 7 // system unit = byte | (0xF & apduSpec.getPinPosition()) << 3 | (0x1 & apduSpec.getPinJustification() << 2) | (0x3 & apduSpec.getPinFormat())); // bmPINBlockString s.write((0xF & apduSpec.getPinLengthSize()) << 4 | (0xF & apduSpec.getPinLength())); // bmPINLengthFormat s.write(// system unit = bit (0xF & apduSpec.getPinLengthPos())); // bInsertionOffsetOld s.write(0x00); // bInsertionOffsetNew s.write(apduSpec.getPinInsertionOffsetNew()); // wPINMaxExtraDigit s.write(Math.min(pinSpec.getMaxLength(), wPINMaxExtraDigitL)); s.write(Math.max(pinSpec.getMinLength(), wPINMaxExtraDigitH)); // bConfirmPIN s.write(0x01); // bEntryValidationCondition s.write(bEntryValidationCondition); // bNumberMessage s.write(0x02); // wLangId English (United States), see http://www.usb.org/developers/docs/USB_LANGIDs.pdf s.write(0x09); s.write(0x04); // bMsgIndex1 s.write(0x01); // bMsgIndex2 s.write(0x02); // bMsgIndex3 s.write(0x00); // bTeoPrologue s.write(0x00); s.write(0x00); s.write(0x00); // ulDataLength s.write(apduSpec.getApdu().length); s.write(0x00); s.write(0x00); s.write(0x00); // abData try { s.write(apduSpec.getApdu()); } catch (IOException e) { // As we are dealing with ByteArrayOutputStreams no exception is to be // expected. throw new RuntimeException(e); } return s.toByteArray(); } protected byte[] createPINModifyStructure(ChangeReferenceDataAPDUSpec apduSpec, PINSpec pinSpec) { //TODO bInsertionOffsetOld (0x00), bConfirmPIN (0x01), bNumberMessage (0x02), bMsgIndex1/2/3 ByteArrayOutputStream s = new ByteArrayOutputStream(); // bTimeOut s.write(bTimeOut); // bTimeOut2 s.write(bTimeOut2); // bmFormatString s.write(1 << 7 // system unit = byte | (0xF & apduSpec.getPinPosition()) << 3 | (0x1 & apduSpec.getPinJustification() << 2) | (0x3 & apduSpec.getPinFormat())); // bmPINBlockString s.write((0xF & apduSpec.getPinLengthSize()) << 4 | (0xF & apduSpec.getPinLength())); // bmPINLengthFormat s.write(// system unit = bit (0xF & apduSpec.getPinLengthPos())); // bInsertionOffsetOld (0x00 for no old pin?) s.write(apduSpec.getPinInsertionOffsetOld()); // bInsertionOffsetNew s.write(apduSpec.getPinInsertionOffsetNew()); // wPINMaxExtraDigit s.write(Math.min(pinSpec.getMaxLength(), wPINMaxExtraDigitL)); s.write(Math.max(pinSpec.getMinLength(), wPINMaxExtraDigitH)); // bConfirmPIN s.write(0x03); // bEntryValidationCondition s.write(bEntryValidationCondition); // bNumberMessage s.write(0x03); // wLangId English (United States), see http://www.usb.org/developers/docs/USB_LANGIDs.pdf s.write(0x09); s.write(0x04); // bMsgIndex1 s.write(0x00); // bMsgIndex2 s.write(0x01); // bMsgIndex3 s.write(0x02); // bTeoPrologue s.write(0x00); s.write(0x00); s.write(0x00); // ulDataLength s.write(apduSpec.getApdu().length); s.write(0x00); s.write(0x00); s.write(0x00); // abData try { s.write(apduSpec.getApdu()); } catch (IOException e) { // As we are dealing with ByteArrayOutputStreams no exception is to be // expected. throw new RuntimeException(e); } return s.toByteArray(); } protected byte[] createPINVerifyStructure(VerifyAPDUSpec apduSpec, PINSpec pinSpec) { ByteArrayOutputStream s = new ByteArrayOutputStream(); // bTimeOut s.write(bTimeOut); // bTimeOut2 s.write(bTimeOut2); // bmFormatString s.write(1 << 7 // system unit = byte | (0xF & apduSpec.getPinPosition()) << 3 | (0x1 & apduSpec.getPinJustification() << 2) | (0x3 & apduSpec.getPinFormat())); // bmPINBlockString s.write((0xF & apduSpec.getPinLengthSize()) << 4 | (0xF & apduSpec.getPinLength())); // bmPINLengthFormat s.write(// system unit = bit (0xF & apduSpec.getPinLengthPos())); // wPINMaxExtraDigit s.write(Math.min(pinSpec.getMaxLength(), wPINMaxExtraDigitL)); // max PIN length s.write(Math.max(pinSpec.getMinLength(), wPINMaxExtraDigitH)); // min PIN length // bEntryValidationCondition s.write(bEntryValidationCondition); // bNumberMessage s.write(0x01); // wLangId s.write(0x09); s.write(0x04); // bMsgIndex s.write(0x00); // bTeoPrologue s.write(0x00); s.write(0x00); s.write(0x00); // ulDataLength s.write(apduSpec.getApdu().length); s.write(0x00); s.write(0x00); s.write(0x00); // abData try { s.write(apduSpec.getApdu()); } catch (IOException e) { // As we are dealing with ByteArrayOutputStreams no exception is to be // expected. throw new RuntimeException(e); } return s.toByteArray(); } @Override public ResponseAPDU verify(CardChannel channel, VerifyAPDUSpec apduSpec, PINGUI pinGUI, PINSpec pinSpec, int retries) throws SignatureCardException, CardException, InterruptedException { ResponseAPDU resp = null; byte[] s = createPINVerifyStructure(apduSpec, pinSpec); Card icc = channel.getCard(); if (VERIFY) { pinGUI.enterPIN(pinSpec, retries); resp = new ResponseAPDU(verifyPin(icc, s, pinGUI)); } else if (VERIFY_DIRECT) { pinGUI.enterPINDirect(pinSpec, retries); log.debug("VERIFY_PIN_DIRECT [" + FEATURES[FEATURE_VERIFY_PIN_DIRECT] + "]"); resp = new ResponseAPDU(VERIFY_PIN_DIRECT(icc, s)); } else { log.warn("falling back to default pin-entry"); return super.verify(channel, apduSpec, pinGUI, pinSpec, retries); } switch (resp.getSW()) { case 0x6400: log.debug("SPE operation timed out."); throw new TimeoutException(); case 0x6401: log.debug("SPE operation was cancelled by the 'Cancel' button."); throw new CancelledException(); case 0x6403: log.debug("User entered too short or too long PIN " + "regarding MIN/MAX PIN length."); throw new PINFormatException(); case 0x6480: log.debug("SPE operation was aborted by the 'Cancel' operation " + "at the host system."); case 0x6b80: log.info("Invalid parameter in passed structure."); default: return resp; } } @Override public ResponseAPDU modify(CardChannel channel, ChangeReferenceDataAPDUSpec apduSpec, ModifyPINGUI pinGUI, PINSpec pinSpec, int retries) throws SignatureCardException, CardException, InterruptedException { ResponseAPDU resp = null; byte[] s = createPINModifyStructure(apduSpec, pinSpec); Card icc = channel.getCard(); if (MODIFY) { pinGUI.enterCurrentPIN(pinSpec, retries); resp = new ResponseAPDU(modifyPin(icc, s, pinGUI, pinSpec)); } else if (MODIFY_DIRECT) { pinGUI.modifyPINDirect(pinSpec, retries); log.debug("MODIFY_PIN_DIRECT [" + FEATURES[FEATURE_MODIFY_PIN_DIRECT] + "]"); resp = new ResponseAPDU(MODIFY_PIN_DIRECT(icc, s)); } else { log.warn("falling back to default pin-entry"); return super.modify(channel, apduSpec, pinGUI, pinSpec, retries); } switch (resp.getSW()) { case 0x6400: log.debug("SPE operation timed out."); throw new TimeoutException(); case 0x6401: log.debug("SPE operation was cancelled by the 'Cancel' button."); throw new CancelledException(); case 0x6402: log.debug("Modify PIN operation failed because two 'new PIN' " + "entries do not match"); throw new PINConfirmationException(); case 0x6403: log.debug("User entered too short or too long PIN " + "regarding MIN/MAX PIN length."); throw new PINFormatException(); case 0x6480: log.debug("SPE operation was aborted by the 'Cancel' operation " + "at the host system."); case 0x6b80: log.info("Invalid parameter in passed structure."); default: return resp; } } @Override public ResponseAPDU modify(CardChannel channel, NewReferenceDataAPDUSpec apduSpec, ModifyPINGUI pinGUI, PINSpec pinSpec) throws SignatureCardException, CardException, InterruptedException { ResponseAPDU resp = null; byte[] s = createPINModifyStructure(apduSpec, pinSpec); Card icc = channel.getCard(); if (MODIFY) { pinGUI.enterNewPIN(pinSpec); resp = new ResponseAPDU(modifyPin(icc, s, pinGUI, pinSpec)); } else if (MODIFY_DIRECT) { pinGUI.modifyPINDirect(pinSpec, -1); log.debug("MODIFY_PIN_DIRECT [" + FEATURES[FEATURE_MODIFY_PIN_DIRECT] + "]"); resp = new ResponseAPDU(MODIFY_PIN_DIRECT(icc, s)); } else { log.warn("falling back to default pin-entry"); return super.modify(channel, apduSpec, pinGUI, pinSpec); } switch (resp.getSW()) { case 0x6400: log.debug("SPE operation timed out."); throw new TimeoutException(); case 0x6401: log.debug("SPE operation was cancelled by the 'Cancel' button."); throw new CancelledException(); case 0x6402: log.debug("Modify PIN operation failed because two 'new PIN' " + "entries do not match"); throw new PINConfirmationException(); case 0x6403: log.debug("User entered too short or too long PIN " + "regarding MIN/MAX PIN length."); throw new PINFormatException(); case 0x6480: log.debug("SPE operation was aborted by the 'Cancel' operation " + "at the host system."); case 0x6b80: log.info("Invalid parameter in passed structure."); default: return resp; } } @Override public ResponseAPDU modify(CardChannel channel, ResetRetryCounterAPDUSpec apduSpec, ModifyPINGUI pinGUI, PINSpec pinSpec, int retries) throws InterruptedException, CardException, SignatureCardException { //TODO return modify(channel, (ChangeReferenceDataAPDUSpec) apduSpec, pinGUI, pinSpec, retries); } }