/**************************************************************************** * 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.common.apdu.utils; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import org.openecard.common.apdu.ReadBinary; import org.openecard.common.apdu.ReadRecord; import org.openecard.common.apdu.Select; import org.openecard.common.apdu.Select.MasterFile; import org.openecard.common.apdu.UpdateRecord; import org.openecard.common.apdu.common.CardCommandAPDU; import org.openecard.common.apdu.common.CardCommandStatus; import org.openecard.common.apdu.common.CardResponseAPDU; import org.openecard.common.apdu.exception.APDUException; import org.openecard.common.interfaces.Dispatcher; import org.openecard.common.tlv.TLVException; import org.openecard.common.tlv.iso7816.DataElements; import org.openecard.common.tlv.iso7816.FCP; import org.openecard.common.util.ShortUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * * @author Moritz Horsch <horsch@cdc.informatik.tu-darmstadt.de> * @author Dirk Petrautzki <petrautzki@hs-coburg.de> * @author Simon Potzernheim <potzernheim@hs-coburg.de> */ public class CardUtils { private static final Logger logger = LoggerFactory.getLogger(CardUtils.class); /** * Selects the Master File. * * @param dispatcher Dispatcher * @param slotHandle Slot handle * @throws APDUException */ public static void selectMF(Dispatcher dispatcher, byte[] slotHandle) throws APDUException { CardCommandAPDU selectMF = new Select.MasterFile(); selectMF.transmit(dispatcher, slotHandle); } /** * Selects a File. * * @param dispatcher Dispatcher * @param slotHandle Slot handle * @param fileID File ID * @return The CardResponseAPDU from the selection of the file * @throws APDUException */ public static CardResponseAPDU selectFile(Dispatcher dispatcher, byte[] slotHandle, short fileID) throws APDUException { return selectFile(dispatcher, slotHandle, ShortUtils.toByteArray(fileID)); } /** * Selects a File. * * @param dispatcher Dispatcher * @param slotHandle Slot handle * @param fileID File ID * @return CardREsponseAPDU containing the File Control Parameters * @throws APDUException */ public static CardResponseAPDU selectFile(Dispatcher dispatcher, byte[] slotHandle, byte[] fileID) throws APDUException { Select selectFile; CardResponseAPDU result = null; // respect the possibility that fileID could be a path int i = 0; while (i < fileID.length) { if (fileID[0] == (byte) 0x3F && fileID[1] == (byte) 0x00) { selectFile = new MasterFile(); i = i + 2; } else if (i == fileID.length - 2) { selectFile = new Select.ChildFile(new byte[]{fileID[i], fileID[i + 1]}); selectFile.setFCP(); i = i + 2; } else { selectFile = new Select.ChildDirectory(new byte[]{fileID[i], fileID[i + 1]}); i = i + 2; } result = selectFile.transmit(dispatcher, slotHandle); } return result; } /** * Reads a file. * * @param dispatcher Dispatcher * @param slotHandle Slot handle * @param fcp File Control Parameters * @return File content * @throws APDUException */ public static byte[] readFile(FCP fcp, Dispatcher dispatcher, byte[] slotHandle) throws APDUException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); // Read 255 bytes per APDU byte length = (byte) 0xFF; int i = 0; CardResponseAPDU response; try { do { if (! isRecordEF(fcp)) { CardCommandAPDU readBinary = new ReadBinary((short) (i * (length & 0xFF)), length); // 0x6A84 code for the estonian identity card. The card returns this code // after the last read process. response = readBinary.transmit(dispatcher, slotHandle, CardCommandStatus.response(0x9000, 0x6282, 0x6A84)); } else { CardCommandAPDU readRecord = new ReadRecord((byte) i); response = readRecord.transmit(dispatcher, slotHandle, CardCommandStatus.response(0x9000, 0x6282)); } if (! Arrays.equals(response.getTrailer(), new byte[] {(byte) 0x6A, (byte) 0x84})) { baos.write(response.getData()); } i++; } while (response.isNormalProcessed()); baos.close(); } catch (IOException e) { throw new APDUException(e); } return baos.toByteArray(); } /** * Selects and reads a file. * * @param dispatcher Dispatcher * @param slotHandle Slot handle * @param fileID File ID * @return File content * @throws APDUException */ @Deprecated public static byte[] readFile(Dispatcher dispatcher, byte[] slotHandle, short fileID) throws APDUException { return readFile(dispatcher, slotHandle, ShortUtils.toByteArray(fileID)); } /** * Selects and reads a file. * * @param dispatcher Dispatcher * @param slotHandle Slot handle * @param fileID File ID * @return File content * @throws APDUException */ @Deprecated public static byte[] readFile(Dispatcher dispatcher, byte[] slotHandle, byte[] fileID) throws APDUException { CardResponseAPDU selectResponse = selectFile(dispatcher, slotHandle, fileID); FCP fcp = null; try { fcp = new FCP(selectResponse.getData()); } catch (TLVException e) { logger.warn("Couldn't get File Control Parameters from Select response.", e); } return readFile(fcp, dispatcher, slotHandle); } public static byte[] selectReadFile(Dispatcher dispatcher, byte[] slotHandle, short fileID) throws APDUException { return readFile(dispatcher, slotHandle, fileID); } public static byte[] selectReadFile(Dispatcher dispatcher, byte[] slotHandle, byte[] fileID) throws APDUException { return readFile(dispatcher, slotHandle, fileID); } private static boolean isRecordEF(FCP fcp) { if (fcp == null) { // TODO inspect EF.ATR as described in ISO/IEC 7816-4 Section 8.4 return false; } else { DataElements dataElements = fcp.getDataElements(); if (dataElements.isLinear() || dataElements.isCyclic()) { return true; } else { return false; } } } public static void writeFile(Dispatcher dispatcher, byte[] slotHandle, byte[] fileID, byte[] data) throws APDUException { CardResponseAPDU selectResponse = selectFile(dispatcher, slotHandle, fileID); FCP fcp = null; try { fcp = new FCP(selectResponse.getData()); } catch (TLVException e) { logger.warn("Couldn't get File Control Parameters from Select response.", e); } writeFile(fcp, dispatcher, slotHandle, data); } private static void writeFile(FCP fcp, Dispatcher dispatcher, byte[] slotHandle, byte[] data) throws APDUException { if (isRecordEF(fcp)) { UpdateRecord updateRecord = new UpdateRecord(data); updateRecord.transmit(dispatcher, slotHandle); } else { // TODO implement writing for non record files throw new UnsupportedOperationException("Not yet implemented."); } } }