/**************************************************************************** * Copyright (C) 2012 ecsec GmbH. * All rights reserved. * Contact: ecsec GmbH (info@ecsec.de) * * This file is part of the Open eCard App. * * GNU General Public License Usage * This file may be used under the terms of the GNU General Public * License version 3.0 as published by the Free Software Foundation * and appearing in the file LICENSE.GPL included in the packaging of * this file. Please review the following information to ensure the * GNU General Public License version 3.0 requirements will be met: * http://www.gnu.org/copyleft/gpl.html. * * Other Usage * Alternatively, this file may be used in accordance with the terms * and conditions contained in a signed written agreement between * you and ecsec GmbH. * ***************************************************************************/ package org.openecard.ifd.scio.wrapper; import iso.std.iso_iec._24727.tech.schema.DisplayCapabilityType; import iso.std.iso_iec._24727.tech.schema.IFDStatusType; import iso.std.iso_iec._24727.tech.schema.KeyPadCapabilityType; import iso.std.iso_iec._24727.tech.schema.SlotStatusType; import java.math.BigInteger; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Map; import javax.smartcardio.ATR; import javax.smartcardio.Card; import javax.smartcardio.CardException; import javax.smartcardio.CardNotPresentException; import javax.smartcardio.CardTerminal; import org.openecard.common.ECardConstants; import org.openecard.common.ifd.PACECapabilities; import org.openecard.common.util.ByteUtils; import org.openecard.ifd.scio.IFDException; import org.openecard.ifd.scio.IFDUtils; import org.openecard.ifd.scio.reader.ExecutePACERequest; import org.openecard.ifd.scio.reader.ExecutePACEResponse; import org.openecard.ifd.scio.reader.PCSCFeatures; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Tobias Wich <tobias.wich@ecsec.de> */ public class SCTerminal { private static final Logger _logger = LoggerFactory.getLogger(SCTerminal.class); private final CardTerminal terminal; private final SCWrapper scwrapper; // capabilities entries private Boolean acoustic = null; private Boolean optic = null; private boolean dispCapRead = false; private DisplayCapabilityType dispCap = null; private boolean keyCapRead = false; private KeyPadCapabilityType keyCap = null; private List<PACECapabilities.PACECapability> PACECapabilities = null; // card if available private SCCard scCard = null; public SCTerminal(CardTerminal terminal, SCWrapper scwrapper) { this.terminal = terminal; this.scwrapper = scwrapper; } public String getName() { return terminal.getName(); } public boolean isCardPresent() { try { return terminal.isCardPresent(); } catch (CardException ex) { return false; } } public boolean isConnected() { Boolean result = scCard != null; return result.booleanValue(); } public synchronized SCCard getCard() throws IFDException { if (scCard == null) { IFDException ex = new IFDException(ECardConstants.Minor.IFD.NO_CARD, "No card inserted in terminal."); _logger.warn(ex.getMessage(), ex); throw ex; } return scCard; } public synchronized IFDStatusType getStatus() throws IFDException { try { IFDStatusType status = new IFDStatusType(); status.setIFDName(getName()); status.setConnected(true); // set slot status type SlotStatusType stype = new SlotStatusType(); status.getSlotStatus().add(stype); boolean cardPresent = isCardPresent(); stype.setCardAvailable(cardPresent); stype.setIndex(IFDUtils.getSlotIndex(getName())); // get card status and stuff if (cardPresent) { if (isConnected()) { ATR atr = scCard.getATR(); stype.setATRorATS(atr.getBytes()); } else { // connect ourselves Card c = terminal.connect("*"); ATR atr = c.getATR(); stype.setATRorATS(atr.getBytes()); c.disconnect(false); } } // ifd status completely constructed return status; } catch (Exception ex) { IFDException ifdex = new IFDException(ex); _logger.warn(ifdex.getMessage(), ifdex); throw ifdex; } } public boolean equals(String ifdName) { Boolean result = terminal.getName().equals(ifdName); return result.booleanValue(); } public boolean equals(CardTerminal other) { Boolean result = terminal.getName().equals(other.getName()); return result.booleanValue(); } /// /// state changing methods /// void updateTerminal() { if (! isCardPresent()) { scCard = null; } else { try { if (scCard != null) { // check if it is the same card, else remove Card newCard = terminal.connect("*"); if (! scCard.equalCardObj(newCard)) { scCard = null; } } } catch (CardException ex) { // error means delete it anyways scCard = null; } } } public synchronized SCChannel connect() throws IFDException { byte[] handle = scwrapper.createHandle(ECardConstants.CONTEXT_HANDLE_DEFAULT_SIZE); // connect card if needed if (! isConnected()) { try { Card c = terminal.connect("*"); scCard = new SCCard(c, this); } catch (CardNotPresentException ex) { IFDException ifdex = new IFDException(ECardConstants.Minor.IFD.NO_CARD, ex.getMessage()); _logger.warn(ifdex.getMessage(), ifdex); throw ifdex; } catch (CardException ex) { IFDException ifdex = new IFDException(ex); _logger.warn(ifdex.getMessage(), ifdex); throw ifdex; } } try { SCChannel scChannel = scCard.addChannel(handle); return scChannel; } catch (CardException ex) { IFDException ifdex = new IFDException(ex.getMessage()); _logger.warn(ifdex.getMessage(), ifdex); throw ifdex; } } // for use in release context synchronized void disconnect() throws CardException { if (isConnected()) { scCard.disconnect(); } } synchronized void removeCard() { scCard = null; } public synchronized boolean isAcousticSignal() throws IFDException { if (acoustic == null) { // no way to ask PCSC this question return false; } return acoustic.booleanValue(); } public synchronized boolean isOpticalSignal() throws IFDException { if (acoustic == null) { // no way to ask PCSC this question return false; } return acoustic.booleanValue(); } public synchronized DisplayCapabilityType getDisplayCapability() throws IFDException { if (dispCapRead == false) { if (isConnected()) { try { Map<Integer,Integer> features = getCard().getFeatureCodes(); if (features.containsKey(PCSCFeatures.IFD_DISPLAY_PROPERTIES)) { byte[] data = getCard().controlCommand(features.get(PCSCFeatures.IFD_DISPLAY_PROPERTIES), new byte[0]); if (data != null && data.length == 4) { int lineLength = ByteUtils.toInteger(Arrays.copyOfRange(data, 0, 2)); int numLines = ByteUtils.toInteger(Arrays.copyOfRange(data, 2, 4)); if (lineLength > 0 && numLines > 0) { dispCap = new DisplayCapabilityType(); dispCap.setIndex(BigInteger.ZERO); dispCap.setColumns(BigInteger.valueOf(lineLength)); dispCap.setLines(BigInteger.valueOf(numLines)); } } } // regardless whether the data has been successfully extracted, or not, the data has been read dispCapRead = true; } catch (CardException ex) { throw new IFDException(ex); } } } return dispCap; } public synchronized KeyPadCapabilityType getKeypadCapability() throws IFDException { if (keyCapRead == false) { if (isConnected()) { try { Map<Integer,Integer> features = getCard().getFeatureCodes(); if (features.containsKey(PCSCFeatures.IFD_PIN_PROPERTIES)) { byte[] data = getCard().controlCommand(features.get(PCSCFeatures.IFD_PIN_PROPERTIES), new byte[0]); if (data != null && data.length == 4) { int wcdLayout = ByteUtils.toInteger(Arrays.copyOfRange(data, 0, 2)); byte entryValidation = data[2]; byte timeOut2 = data[3]; // TODO: extract number of keys somehow } } // regardless whether the data has been successfully extracted, or not, the data has been read keyCapRead = true; } catch (CardException ex) { throw new IFDException(ex); } } } return keyCap; } public synchronized byte[] executeCtrlCode(int featureCode, byte[] command) throws IFDException { if (isConnected()) { try { Map<Integer,Integer> features = getCard().getFeatureCodes(); if (features.containsKey(featureCode)) { Integer code = features.get(featureCode); byte[] result = getCard().controlCommand(code, command); return result; } else { throw new IFDException("The requested control code is not supported by the terminal"); } } catch (CardException ex) { throw new IFDException(ex); } } throw new IFDException(ECardConstants.Minor.Disp.INVALID_CHANNEL_HANDLE, "No connection is established with the reader."); } private synchronized Integer getPaceCtrlCode() throws IFDException { if (isConnected()) { try { Map<Integer,Integer> features = getCard().getFeatureCodes(); if (features.containsKey(PCSCFeatures.EXECUTE_PACE)) { return features.get(PCSCFeatures.EXECUTE_PACE); } } catch (CardException ex) { throw new IFDException(ex); } } return null; } public synchronized boolean supportsPace() throws IFDException { return getPaceCtrlCode() != null; } public List<PACECapabilities.PACECapability> getPACECapabilities() throws IFDException { List<PACECapabilities.PACECapability> result = new LinkedList<PACECapabilities.PACECapability>(); if (PACECapabilities == null) { if (isConnected()) { if (supportsPace()) { int ctrlCode = getPaceCtrlCode(); byte[] getCapabilityRequest = new ExecutePACERequest(ExecutePACERequest.Function.GetReaderPACECapabilities).toBytes(); try { byte[] response = getCard().controlCommand(ctrlCode, getCapabilityRequest); ExecutePACEResponse paceResponse = new ExecutePACEResponse(response); if (paceResponse.isError()) { throw new IFDException(paceResponse.getResult()); } PACECapabilities cap = new PACECapabilities(paceResponse.getData()); PACECapabilities = cap.getFeaturesEnum(); result.addAll(PACECapabilities); } catch (CardException e) { IFDException ex = new IFDException(e); throw ex; } } } } else { result.addAll(PACECapabilities); } return Collections.unmodifiableList(result); } private synchronized Integer getPinCompareCtrlCode() throws IFDException { if (isConnected()) { try { Map<Integer,Integer> features = getCard().getFeatureCodes(); if (features.containsKey(PCSCFeatures.VERIFY_PIN_DIRECT)) { return features.get(PCSCFeatures.VERIFY_PIN_DIRECT); } } catch (CardException ex) { throw new IFDException(ex); } } return null; } public synchronized boolean supportsPinCompare() throws IFDException { return getPinCompareCtrlCode() != null; } }