/* * Java Card PKI applet - ISO7816 compliant Java Card applet. * * Copyright (C) 2009 Wojciech Mostowski, woj@cs.ru.nl * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ package net.sourceforge.javacardsign.applet; import javacard.framework.Util; import javacard.framework.JCSystem; /** * Encapsulates the file system for the PKI applet. * * @author Wojciech Mostowski <woj@cs.ru.nl> * */ final class FileSystem { final static short MASTER_FILE_ID = (short) 0x3F00; final static byte PERM_FREE = 0; final static byte PERM_PIN = 1; private static final byte DIR = -1; private Object[] efFiles = null; private byte[] efPerms = null; private short totalFiles = 0; /** Stores the file structure information for this file system. * The initial contents of this array should be following the pattern below, * see also the documentation in the pkihostapi library, the PKIPersoService class. * * The hierarchical structure for the file system in our * applet. The data is as follows, concatenated in sequence: * * byte 0: -1/0 -1 for DF, 0 for EF * byte 1, 2: fid msb, fid lsb * byte 3: index to the parent in this array, -1 of root node * byte 4: for EF the SFI of this file * for DF number of children nodes, the list of indexes to the * children follow. * * When EF files are created the first byte (initially 0) of the * according file in this structure is replaced with the index to * the {@link #efFiles}, where the reference to the file array * is stored. */ byte[] fileStructure = null; private short[] fileStructureIndex; FileNotFoundException fnfe; /** * Create a new file system for maxFiles maximum number of files. * * @param maxFiles * the maximum number of files. */ FileSystem(short maxFiles) { efFiles = new Object[maxFiles]; efPerms = new byte[maxFiles]; fileStructureIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); fnfe = new FileNotFoundException(); } /** * Create a new file * * @param fid * the ID of the file to be create * @param length * the file contents length * @param perm * the permission byte, see {@link #PERM_FREE}, * {@link #PERM_PIN} * @return whether the file was successfully created */ boolean createFile(short fid, short length, byte perm) { if (totalFiles == efFiles.length) { return false; } try { short index = searchId((short) 0, fid); efFiles[totalFiles] = new byte[length]; efPerms[totalFiles] = perm; fileStructure[index] = (byte) totalFiles; totalFiles++; return true; } catch (FileNotFoundException e) { return false; } } /** * Returns the array with the contents of the given file * * @param index * the index to the file * @return the array with the contents of the file */ byte[] getFile(short index) { try { return (byte[]) efFiles[index]; } catch (ArrayIndexOutOfBoundsException aioobe) { return null; } } /** * Returns the permission byte of the given file * * @param index * the index to the file * @return the permission byte of the file */ byte getPerm(short index) { return efPerms[index]; } /** * Get the index to the currently selected file, -1 if none selected. * * @return the index to the currently selected file */ short getCurrentIndex() { short index = (short) (fileStructureIndex[0] - 1); if (index == -1) { return -1; } return fileStructure[index]; } /** * Selects the file by the file identifier - global search from the root. * * @param id * id of the file to be selected * @return whether selection was successful */ boolean selectEntryAbsolute(short id) { try { fileStructureIndex[0] = (short) (searchId((short) 0, id) + 1); return true; } catch (FileNotFoundException fnfe) { return false; } } /** * Select the parent file of the currently selected file, if possible. * * @return whether selection was successful */ boolean selectEntryParent() { try { short index = (short) (fileStructureIndex[0] - 1); if (index == -1 || fileStructure[index] != DIR) { return false; } index = fileStructure[(short) (index + 1)]; if (index == -1) { return false; } fileStructureIndex[0] = (short) (index + 1); return true; } catch (ArrayIndexOutOfBoundsException aioobe) { return false; } } /** * Select the EF or DF file under the currently selected file. * * @param id * the id of the file to be selected * @param ef * whether the file to be selected is EF or DF * @return whether selection was successful */ boolean selectEntryUnderCurrent(short id, boolean ef) { short index = (short) (fileStructureIndex[0] - 1); if (index == -1) { return false; } try { index = findEntryRelative(index, id); if ((fileStructure[index] != DIR) == ef) { fileStructureIndex[0] = (short) (index + 1); return true; } } catch (FileNotFoundException fnfe) { } return false; } /** * Select the file by path. * * @param path * the array with the path data * @param offset * offset to that array * @param length * the length of the path * @param master * if true the path is from the root, otherwise from the * currently selected file * @return whether selection was successful */ boolean selectEntryByPath(byte[] path, short offset, short length, boolean master) { short index = master ? 0 : (short) (fileStructureIndex[0] - 1); if (index == -1) { return false; } try { index = findEntryPath(index, path, offset, length); fileStructureIndex[0] = (short) (index + 1); return true; } catch (FileNotFoundException fnfe) { return false; } } /** * Find the index the file specified by SFI under the current (if exists) DF * file * * @param sfi * the SFI of the file to find the index for * @return the index to the file, -1 if not found */ short findCurrentSFI(byte sfi) { try { short start = (short) (fileStructureIndex[0] - 1); if (start == -1 || fileStructure[start] != DIR) { return -1; } short childNum = fileStructure[(short) (start + 4)]; for (short i = 0; i < childNum; i++) { short index = fileStructure[(short) (start + (short) (i + 5))]; if (fileStructure[index] != DIR) { if (fileStructure[(short) (index + 4)] == sfi) return index; } } } catch (ArrayIndexOutOfBoundsException aioobe) { } return -1; } private short findEntryRelative(short start, short id) throws FileNotFoundException { try { if (fileStructure[start] != DIR) { throw fnfe; } short childNum = fileStructure[(short) (start + 4)]; for (short i = 0; i < childNum; i++) { short index = fileStructure[(short) (start + (short) (5 + i))]; short fid = Util.getShort(fileStructure, (short) (index + 1)); if (fid == id) { return index; } } } catch (ArrayIndexOutOfBoundsException aioobe) { } throw fnfe; } private short findEntryPath(short start, byte[] path, short offset, short length) throws FileNotFoundException { try { if (length == 0) { return start; } short id = Util.makeShort(path[offset], path[(short) (offset + 1)]); start = findEntryRelative(start, id); offset += 2; length = (short) (length - 2); return findEntryPath(start, path, offset, length); } catch (ArrayIndexOutOfBoundsException aioobe) { throw fnfe; } } /** * Searches for an index to the file specified by the id in the file * structure starting from position start. * * @param start * starting position to search * @param id * the id of the file that is searched * @return the index of the file, if found * @throws FileNotFoundException * when file not found */ short searchId(short start, short id) throws FileNotFoundException { return searchId(this.fileStructure, (short) 0, start, (short) this.fileStructure.length, id); } /** * Searches for an index to the file specified by the id in the file * structure starting from position start. * * @param fileStructureArray * the array with the file structure * @param shift * the shift in the input array (e.g. when the array is the APDU * with the header bytes) * @param start * starting position to search * @param lastOffset * the last valid offset in the input array * @param id * the id of the file that is searched * @return the index of the file, if found * @throws ArrayIndexOutOfBoundsException * when start and lastOffset point outside of the input array * @throws FileNotFoundException * when file not found */ short searchId(byte[] fileStructureArray, short shift, short start, short lastOffset, short id) throws ArrayIndexOutOfBoundsException, FileNotFoundException { if (start < 0 || start > (short) (lastOffset - 5)) { // This sould produce ArrayIndexOutOfBoundsException fileStructureArray[fileStructureArray.length] = (byte) 0xFF; } short fid = Util.getShort(fileStructureArray, (short) (start + 1)); if (fid == id) { return start; } if (fileStructureArray[start] != DIR) { throw fnfe; } else { short childNum = fileStructureArray[(short) (start + 4)]; if (start > (short) ((short) (lastOffset - 5) - childNum)) { fileStructureArray[fileStructureArray.length] = (byte) 0xFF; } for (short i = 0; i < childNum; i++) { try { return searchId( fileStructureArray, shift, (short) (fileStructureArray[(short) (start + (short) (5 + i))] + shift), lastOffset, id); } catch (FileNotFoundException e) { } } } throw fnfe; } }