/* * Proyecto CCIDroid. Driver para utilizacion de tarjetas CCID en el sistema operativo * Android. * * El proyecto CCIDroid es un conector para la comunicacion entre sistemas Android y * lectores de SmartCard USB segun el estandar CCID. Diseno inicial desarrollado para * su integracion con el Controlador Java de la Secretaria de Estado de Administraciones * Publicas para el DNI electronico. * * Copyright (C) 2012 Instituto Nacional de las Tecnologias de la Comunicacion (INTECO) * * Este programa es software libre y utiliza un licenciamiento dual (LGPL 2.1+ * o EUPL 1.1+), lo cual significa que los usuarios podran elegir bajo cual de las * licencias desean utilizar el codigo fuente. Su eleccion debera reflejarse * en las aplicaciones que integren o distribuyan el Controlador, ya que determinara * su compatibilidad con otros componentes. * * El Controlador puede ser redistribuido y/o modificado bajo los terminos de la * Lesser GNU General Public License publicada por la Free Software Foundation, * tanto en la version 2.1 de la Licencia, o en una version posterior. * * El Controlador puede ser redistribuido y/o modificado bajo los terminos de la * European Union Public License publicada por la Comision Europea, * tanto en la version 1.1 de la Licencia, o en una version posterior. * * Deberia recibir una copia de la GNU Lesser General Public License, si aplica, junto * con este programa. Si no, consultelo en <http://www.gnu.org/licenses/>. * * Deberia recibir una copia de la European Union Public License, si aplica, junto * con este programa. Si no, consultelo en <http://joinup.ec.europa.eu/software/page/eupl>. * * Este programa es distribuido con la esperanza de que sea util, pero * SIN NINGUNA GARANTIA; incluso sin la garantia implicita de comercializacion * o idoneidad para un proposito particular. */ package es.inteco.labs.android.usb.device; import android.hardware.usb.UsbDevice; import android.hardware.usb.UsbManager; import android.os.SystemClock; import android.util.Log; import es.gob.jmulticard.HexUtils; import es.inteco.labs.android.usb.device.ccid.instruction.UsbCommand; import es.inteco.labs.android.usb.device.ccid.instruction.UsbInstructionFactory; import es.inteco.labs.android.usb.device.ccid.response.UsbResponse; import es.inteco.labs.android.usb.device.data.ATR; import es.inteco.labs.android.usb.device.exception.NotAvailableUSBDeviceException; import es.inteco.labs.android.usb.device.exception.UsbCommandTransmissionException; import es.inteco.labs.android.usb.device.exception.UsbDeviceException; import es.inteco.labs.android.usb.device.exception.UsbResponseException; import es.inteco.labs.android.usb.device.exception.UsbSmartCardChannelException; /** Dispositivo USB SmartCard CCID conectado. * @author Jose Luis Escanciano Garcia */ public final class SmartCardUsbDevice extends AnyUSBDevice{ private static final int MAX_TRANSMIT_RETRIES = 3; private static final int MAX_RECONNECT_CHANNEL_RETRIES = 3; private static final int RETRY_TIMEOUT = 1000; private SmartCardChannel channel; private int flag_transmit_retries = 0; private int flag_reconnect_channel = 0; /** Constructor. * @param usbDev Lector de tarjetas USB CCID. * @param usbManager Gestor de dispositivos USB de Android * @throws UsbDeviceException */ public SmartCardUsbDevice(final UsbManager usbManager, final UsbDevice usbDev) throws UsbDeviceException { super(usbManager, usbDev); } /** Indica si la tarjeta está presente en el lector de tarjetas. * @return <code>true</code> si la tarjeta está presente en el lector de tarjetas, * <code>false</code> en caso contrario * @throws NotAvailableUSBDeviceException */ public boolean isCardPresent() throws NotAvailableUSBDeviceException{ try { final UsbResponse response = getSlotStatus(); return isCardPresent(response); } catch (final UsbCommandTransmissionException e) { this.close(); this.channel = this.open(); Log.w("es.gob.afirma", "Error de transmision USB: " + e); //$NON-NLS-1$//$NON-NLS-2$ return false; } catch (final UsbDeviceException e) { Log.w("es.gob.afirma", "Error en dispositivo USB: " + e); //$NON-NLS-1$//$NON-NLS-2$ return false; } catch (final UsbSmartCardChannelException e) { // Es necesario reconectar releaseChannel(); return false; } } /** Indica si la tarjeta está presente y activa en el lector de tarjetas * @return <code>true</code> si la tarjeta está presente y activa en el lector de tarjetas, * <code>false</code> en caso contrario * @throws NotAvailableUSBDeviceException */ public boolean isCardActive() throws NotAvailableUSBDeviceException{ try { final UsbResponse response = getSlotStatus(); return isCardPresent(response) && isCardActive(response); } catch (final UsbCommandTransmissionException e) { this.close(); this.channel = this.open(); Log.w("es.gob.afirma", "Error de transmision USB: " + e); //$NON-NLS-1$//$NON-NLS-2$ return false; } catch (final UsbDeviceException e) { Log.w("es.gob.afirma", "Error en dispositivo USB: " + e); //$NON-NLS-1$//$NON-NLS-2$ return false; } catch (final UsbSmartCardChannelException e) { // Es necesario reconectar releaseChannel(); return false; } } /** Indica si la tarjeta está presente en el lector de tarjetas * @param slotStatus Respuesta USB a una peticion getSlotStatus * @return <code>true</code> si la tarjeta está presente en el lector de tarjetas, * <code>false</code> en caso contrario */ private static boolean isCardPresent(final UsbResponse slotStatus){ return slotStatus != null && slotStatus.getIccStatus() != UsbResponse.ICC_STATUS_NOT_PRESENT ? true : false; } /** Indica si la tarjeta está presente y activa en el lector de tarjetas * @param slotStatus Respuesta USB a una peticion getSlotStatus * @return <code>true</code> si la tarjeta está presente y activa en el lector de tarjetas, <code>false</code> en caso contrario */ private static boolean isCardActive(final UsbResponse slotStatus){ return slotStatus != null && slotStatus.getIccStatus() == UsbResponse.ICC_STATUS_ACTIVE ? true : false; } /** Envía una petición <i>getSlotStatus</i> al lector. * @return * @throws UsbDeviceException * @throws UsbCommandTransmissionException * @throws UsbSmartCardChannelException * @throws NotAvailableUSBDeviceException */ protected UsbResponse getSlotStatus() throws UsbDeviceException, UsbCommandTransmissionException, UsbSmartCardChannelException, NotAvailableUSBDeviceException{ final UsbCommand getStatus = UsbInstructionFactory.getInstance().getSlotStatusCommand(); return getChannel().transmit(getStatus); } /** Manda un reset al dispositivo CCID. * @return ATR devuelto por el dispositivo * @throws UsbDeviceException * @throws NotAvailableUSBDeviceException */ public ATR resetCCID() throws UsbDeviceException, NotAvailableUSBDeviceException{ if(!isCardPresent()){ throw new UsbDeviceException("No se ha detectado una tarjeta en el lector"); //$NON-NLS-1$ } try { //PowerOff final UsbCommand powerOffCommand = UsbInstructionFactory.getInstance().getIccPowerOffCommand(); final UsbResponse powerOffResponse = getChannel().transmit(powerOffCommand); if(!powerOffResponse.isOk()){ throw new UsbDeviceException("Imposible enviar PowerOff al terminal [" + HexUtils.hexify(new byte[]{powerOffResponse.getStatus()}, false) + "] - (" + HexUtils.hexify(new byte[]{powerOffResponse.getError()}, false) + ")"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } //PowerOn final UsbCommand powerOnCommand = UsbInstructionFactory.getInstance().getIccPowerOnCommand(); final UsbResponse powerOnResponse = getChannel().transmit(powerOnCommand); if(powerOnResponse.isOk()){ return new ATR(powerOnResponse.getDataBytes()); } throw new UsbDeviceException("Imposible enviar PowerOn al terminal"); //$NON-NLS-1$ } catch (final UsbCommandTransmissionException e) { throw new UsbDeviceException(e); } catch (final UsbResponseException e){ throw new UsbDeviceException(e); } catch (final UsbSmartCardChannelException e) { // Es necesario reconectar if(this.flag_reconnect_channel++ < MAX_RECONNECT_CHANNEL_RETRIES) { releaseChannel(); return resetCCID(); } throw new UsbDeviceException(e); } } /** Envía una APDU al dispositivo. * @param apdu Comando APDU * @return Respuesta al envío (APDU respuesta) * @throws UsbDeviceException * @throws NotAvailableUSBDeviceException */ public byte[] transmit(final byte[] apdu) throws UsbDeviceException, NotAvailableUSBDeviceException{ try{ final UsbCommand xfrBlock = UsbInstructionFactory.getInstance().getXfrBlockCommand(apdu); final UsbResponse response = getChannel().transmit(xfrBlock); if(response.isOk()){ this.flag_transmit_retries = 0; this.flag_reconnect_channel = 0; return response.getDataBytes(); } throw new UsbDeviceException("Error en la transmision de la APDU"); //$NON-NLS-1$ } catch (final UsbCommandTransmissionException e) { // Es necesario reconectar if(this.flag_transmit_retries++ < MAX_TRANSMIT_RETRIES) { SystemClock.sleep(RETRY_TIMEOUT); return transmit(apdu); } throw new UsbDeviceException(e); } catch (final UsbResponseException e) { throw new UsbDeviceException(e); } catch (final UsbSmartCardChannelException e) { if(this.flag_reconnect_channel++ < MAX_RECONNECT_CHANNEL_RETRIES) { releaseChannel(); return transmit(apdu); } throw new UsbDeviceException(e); } } /** Indica si se ha abierto el canal con el dispositivo * @return <code>true</code> si se ha abierto el canal con el dispositivo, <code>false</code> * en caso contrario */ public boolean isOpen(){ return this.channel != null; } /** Abre la conexión con un dispositivo SmartCard. * @return Conexión con un dispositivo SmartCard * @throws NotAvailableUSBDeviceException */ public SmartCardChannel open() throws NotAvailableUSBDeviceException { try { return this.getChannel(); } catch (final UsbDeviceException e) { throw new NotAvailableUSBDeviceException("Error en la apertura del canal: " + e, e); //$NON-NLS-1$ } } /** Cierra la conexión con un dispositivo SmartCard. * @return <code>true</code> si el dispositivo quedó cerrado tras la llamada, <code>false</code> * en caso contrario */ public boolean close() { if(this.isOpen()){ return this.releaseChannel(); } return true; } /** Solicita el canal de comunicación a través del interfaz (0) del dispositivo * @return Canal de comunicación USB * @throws UsbDeviceException * @throws NotAvailableUSBDeviceException */ private SmartCardChannel getChannel() throws UsbDeviceException, NotAvailableUSBDeviceException{ if(this.channel != null){ return this.channel; } if(getUsbInterface() != null){ if (!getUsbDeviceConnection().claimInterface(getUsbInterface(), true)) { throw new NotAvailableUSBDeviceException("Imposible acceder al interfaz del dispositivo USB"); //$NON-NLS-1$ } this.channel = new SmartCardChannel(getUsbDeviceConnection(), getUsbInterface()); return this.channel; } throw new UsbDeviceException("usbInterface cannot be NULL"); //$NON-NLS-1$ } /** Libera el canal de comunicación establecido a través del interfaz (0) del dispositivo. */ private boolean releaseChannel(){ if(this.channel != null){ this.channel = null; return getUsbDeviceConnection().releaseInterface(getUsbInterface()); } return true; } }