package org.satochip.applet; import org.satochip.applet.MemoryManager; import javacard.framework.Util; import javacard.framework.ISOException; /** * Object Manager Class * <p> * * Objects are linked in a list in the dynamic memory. No smart search is done * at the moment. * <p> * * Object fields: * * <pre> * short next (2 byte) * short obj_class (2 bytes) * short obj_id (2 bytes) * byte[] ACL (6 bytes) * short obj_size (2 bytes) * byte[] data * </pre> * * TODO - Could we definitively avoid a map enforcing the ID (equal to the * memory address, i.e.) - security implications ? * */ public class ObjectManager { public final static byte OBJ_ACL_SIZE = (byte) 6; private final static byte OBJ_HEADER_SIZE = (byte) (6 + OBJ_ACL_SIZE + 2); private final static byte OBJ_H_NEXT = (byte) 0; // Short size; private final static byte OBJ_H_CLASS = (byte) 2; // Short ocj_class; private final static byte OBJ_H_ID = (byte) 4; // Short obj_id; private final static byte OBJ_H_ACL = (byte) 6; // Byte[OBJ_ACL_SIZE] acl; private final static byte OBJ_H_SIZE = (byte) 12; // Short size; private final static byte OBJ_H_DATA = (byte) 14; /** There have been memory problems on the card */ public final static short SW_NO_MEMORY_LEFT = (short) 0x9C01; /** * Size of an Object Record filled by getFirstRecord() or getNextRecord(): * ID, Size, ACL */ public final static short RECORD_SIZE = (short) (4 + 4 + OBJ_ACL_SIZE); /** * Iterator on objects. Stores the offset of the last retrieved object's * record. */ private short it; /** The Memory Manager object */ private MemoryManager mem = null; /** Map for fast search of objects (unimplemented) */ // static Map map; /** Head of the objects' list */ private short obj_list_head = MemoryManager.NULL_OFFSET; /** * Constructor for the ObjectManager class. * * @param mem_ref * The MemoryManager object to be used to allocate objects' * memory. */ public ObjectManager(MemoryManager mem_ref) { mem = mem_ref; // map = new Map(); obj_list_head = MemoryManager.NULL_OFFSET; } /** * Creates an object with specified parameters. Throws a SW_NO_MEMORY_LEFT * exception if cannot allocate the memory. Does not check if object exists. * * @param type * Object Type * @param id * Object ID (Type and ID form a generic 4 bytes identifier) * @param acl_buf * Java byte array containing the ACL for the new object * @param acl_offset * Offset at which the ACL starts in acl_buf[] * @return The memory base address for the object. It can be used in * successive calls to xxxFromAddress() methods. */ public short createObject(short type, short id, short size, byte[] acl_buf, short acl_offset) { /* Allocate memory for new object */ short base = mem.alloc((short) (size + OBJ_HEADER_SIZE)); if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_NO_MEMORY_LEFT); /* New obj will be inserted in the head of the list */ mem.setShort(base, OBJ_H_NEXT, obj_list_head); mem.setShort(base, OBJ_H_CLASS, type); mem.setShort(base, OBJ_H_ID, id); mem.setShort(base, OBJ_H_SIZE, size); mem.setBytes(base, OBJ_H_ACL, acl_buf, acl_offset, OBJ_ACL_SIZE); obj_list_head = base; /* Add to the map */ // map.addEntry(type, id, base); // Return base address return (short) (base + OBJ_HEADER_SIZE); } /** Creates an object with the maximum available size */ public short createObjectMax(short type, short id, byte[] acl_buf, short acl_offset) { short obj_size = mem.getMaxSize(); if (obj_size == (short) 0) ISOException.throwIt(SW_NO_MEMORY_LEFT); /* * The object's real size must take into account that * extra bytes are * needed for the header */ return createObject(type, id, (short) (obj_size - OBJ_HEADER_SIZE), acl_buf, acl_offset); } /** * Clamps an object freeing the unused memory * * @param type * Object Type * @param id * Object ID (Type and ID form a generic 4 bytes identifier) * @param new_size * The new object size (must be less than current size) * @return True if clamp was possible, false otherwise */ public boolean clampObject(short type, short id, short new_size) { short base = getEntry(type, id); if (base == (short) MemoryManager.NULL_OFFSET) ISOException.throwIt((short) 0x9C07); // Delegate every check to the Memory Manager if (mem.realloc(base, (short) (new_size + OBJ_HEADER_SIZE))) { mem.setShort(base, OBJ_H_SIZE, new_size); return true; } return false; } /** Set the object's ACL. Unused at the moment. */ private void setACL(short type, short id, byte[] acl_buf, short acl_offset) { short base = getEntry(type, id); mem.setBytes(base, OBJ_H_ACL, acl_buf, acl_offset, OBJ_ACL_SIZE); } /** * Allow or disallow read on object given the logged identities * * @param base * The object base address as returned from getBaseAddress() * @param logged_ids * The current logged in identities as stored in * CardEdge.logged_ids */ public boolean authorizeReadFromAddress(short base, short logged_ids) { return authorizeOp(mem.getShort(base, (short) (OBJ_H_ACL - OBJ_HEADER_SIZE)), logged_ids); } /** * Allow or unallow write on object given the logged identities * * @param base * The object base address as returned from getBaseAddress() * @param logged_ids * The current logged in identities as stored in * CardEdge.logged_ids */ public boolean authorizeWriteFromAddress(short base, short logged_ids) { return authorizeOp(mem.getShort(base, (short) (OBJ_H_ACL + (short) 2 - OBJ_HEADER_SIZE)), logged_ids); } /** * Allow or unallow delete on object given the logged identities * * @param base * The object base address as returned from getBaseAddress() * @param logged_ids * The current logged in identities as stored in * CardEdge.logged_ids */ public boolean authorizeDeleteFromAddress(short base, short logged_ids) { return authorizeOp(mem.getShort(base, (short) (OBJ_H_ACL + (short) 4 - OBJ_HEADER_SIZE)), logged_ids); } /** * Check if logged in identities satisfy requirements for an operation * * @param required_ids * The required identities as from an ACL short * @param logged_ids * The current logged in identities as stored in * CardEdge.logged_ids */ private boolean authorizeOp(short required_ids, short logged_ids) { return ((required_ids != (short) 0xFFFF) && (((short) (required_ids & logged_ids)) == required_ids)); } /** Write data at the specified location in an object */ // public void setObjectData(short type, short id, short dst_offset, // byte[] src_data, short src_offset, // short len) { // // TODO: short dst_base = map.getEntry(type, id); // short dst_base = getEntry(type, id); // mem.setBytes(dst_base, dst_offset, src_data, src_offset, len); // } // /** Read data from the specified location in an object */ // public void getObjectData(byte[] dst_data, short dst_offset, // short type, short id, short src_offset, // short len) { // // TODO: short dst_base = map.getEntry(type, id); // short src_base = getEntry(type, id); // mem.getBytes(dst_data, dst_offset, src_base, src_offset, len); // } /** * Destroy the specified object * * @param type * Object Type * @param id * Object ID (Type and ID form a generic 4 bytes identifier) * @param secure * If true, object memory is zeroed before being released. */ public void destroyObject(short type, short id, boolean secure) { short base = obj_list_head; short prev = MemoryManager.NULL_OFFSET; boolean found = false; while ((!found) && (base != MemoryManager.NULL_OFFSET)) { if ((mem.getShort(base, OBJ_H_CLASS) == type) && (mem.getShort(base, OBJ_H_ID) == id)) found = true; else { prev = base; base = mem.getShort(base, OBJ_H_NEXT); } } if (found) { // Unlink object from the list if (prev != MemoryManager.NULL_OFFSET) { mem.setShort(prev, OBJ_H_NEXT, mem.getShort(base, OBJ_H_NEXT)); } else { obj_list_head = mem.getShort(base, OBJ_H_NEXT); } // Zero memory if required if (secure) Util.arrayFillNonAtomic(mem.getBuffer(), (short) (base + OBJ_HEADER_SIZE), mem.getShort(base, OBJ_H_SIZE), (byte) 0x00); // Free memory mem.free(base); } } /** * Returns the header base address (offset) for the specified object * <p> * Object header is found at the returned offset, while object data starts * right after the header * <p> * This performs a linear search, so performance issues could arise as the * number of objects grows If object is not found, then returns NULL_OFFSET * * @param type * Object Type * @param id * Object ID (Type and ID form a generic 4 bytes identifier) * @return The starting offset of the object or NULL_OFFSET if the object is * not found. */ private short getEntry(short type, short id) { /* * This is a stupid linear search. It's fine for a few objects. TODO: * Use a map for high number of objects */ short base = obj_list_head; while (base != MemoryManager.NULL_OFFSET) { if ((mem.getShort(base, OBJ_H_CLASS) == type) && (mem.getShort(base, OBJ_H_ID) == id)) return base; base = mem.getShort(base, OBJ_H_NEXT); } return MemoryManager.NULL_OFFSET; } /** * Returns the data base address (offset) for an object. * <p> * The base address can be used for further calls to xxxFromAddress() * methods * <p> * This function should only be used if performance issue arise. * setObjectData() and getObjectData() should be used, instead. * * @param type * Object Type * @param id * Object ID (Type and ID form a generic 4 bytes identifier) * @return The starting offset of the object. At this location */ public short getBaseAddress(short type, short id) { short base = getEntry(type, id); if (base == MemoryManager.NULL_OFFSET) return MemoryManager.NULL_OFFSET; else return ((short) (base + OBJ_HEADER_SIZE)); } /** * Checks if an object exists * * @param type * The object type * @param id * The object ID * @return true if object exists */ public boolean exists(short type, short id) { short base = getEntry(type, id); return (base != MemoryManager.NULL_OFFSET); } /** Returns object size from the base address */ public short getSizeFromAddress(short base) { return mem.getShort((short) (base - OBJ_HEADER_SIZE + OBJ_H_SIZE)); } /** * Resets the objects iterator and retrieves the information record of the * first object, if any. * <p> * * @param buffer * The byte array into which the record will be copied * @param offset * The offset in buffer[] at which the record will be copied * @return True if an object was found. False if there are no objects. * * @see #getNextRecord */ public boolean getFirstRecord(byte[] buffer, short offset) { it = obj_list_head; return getNextRecord(buffer, offset); } /** * Retrieves the information record of the next object, if any. * <p> * * @param buffer * The byte array into which the record will be copied * @param offset * The offset in buffer[] at which the record will be copied * @return True if an object was found. False if there are no more objects * to inspect. * @see #getFirstRecord */ public boolean getNextRecord(byte[] buffer, short offset) { if (it == MemoryManager.NULL_OFFSET) return false; // Setting Object Class Util.setShort(buffer, offset, mem.getShort(it, OBJ_H_CLASS)); // Setting Object ID Util.setShort(buffer, (short) (offset + 2), mem.getShort(it, OBJ_H_ID)); // Setting Size's M.S.Short to zero. Util.setShort(buffer, (short) (offset + 4), (short) 0); // Setting Size's L.S.Short Util.setShort(buffer, (short) (offset + 6), mem.getShort(it, (short) OBJ_H_SIZE)); // Setting ACL Util.arrayCopyNonAtomic(mem.getBuffer(), (short) (it + OBJ_H_ACL), buffer, (short) (offset + 8), OBJ_ACL_SIZE); // Advance iterator it = mem.getShort(it, OBJ_H_NEXT); return true; } /** * Compare an object's ACL with the provided ACL. * * @param base * The object base address, as returned from getBaseAddress() * @param acl * The buffer containing the ACL * @return True if the ACLs are equal */ public boolean compareACLFromAddress(short base, byte[] acl) { return (Util.arrayCompare(mem.getBuffer(), (short) (base - OBJ_HEADER_SIZE + OBJ_H_ACL), acl, (short) 0, OBJ_ACL_SIZE) == (byte) 0); } } // class MemoryManager