/* * Copyright (c) 2001 * BSD ? */ // // $Workfile: CardEdge.java $ // $Revision$ // $Date$ // $Author$ // $Archive: CardEdge $ // $Modtime: 5/02/00 8:48p $ // package com.musclecard.CardEdge; import javacard.framework.APDU; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; import javacard.framework.OwnerPIN; import javacard.framework.SystemException; import javacard.framework.Util; import javacard.security.DESKey; import javacard.security.DSAKey; import javacard.security.DSAPrivateKey; import javacard.security.DSAPublicKey; import javacard.security.Key; import javacard.security.KeyBuilder; import javacard.security.KeyPair; import javacard.security.PrivateKey; import javacard.security.RSAPrivateCrtKey; import javacard.security.RSAPrivateKey; import javacard.security.RSAPublicKey; import javacard.security.RandomData; import javacard.security.Signature; import javacardx.apdu.ExtendedLength; import javacardx.crypto.Cipher; /** * Implements MUSCLE's Card Edge Specification. * <p> * * TODO: * <ul> * <li>Allows maximum number of keys and PINs and total mem to be specified at * the instantiation moment. * <p> * <li>How do transactions fit in the methods ? * <li>Where should we issue begin/end transaction ? * <li>Should we ever abort transaction ? Where ? * <li>Every time there is an "if (avail < )" check, call ThrowDeleteObjects(). * </ul> */ public class CardEdge extends javacard.framework.Applet implements ExtendedLength { /* constants declaration */ // Maximum number of keys handled by the Cardlet private final static byte MAX_NUM_KEYS = (byte) 8; // Maximum number of PIN codes private final static byte MAX_NUM_PINS = (byte) 8; // Maximum number of keys allowed for ExtAuth private final static byte MAX_NUM_AUTH_KEYS = (byte) 6; // Maximum size for the extended APDU buffer for a 2048 bit key: // CLA [1 byte] + INS [1 byte] + P1 [1 byte] + P2 [1 byte] + // LC [3 bytes] + cipher_mode[1 byte] + cipher_direction [1 byte] + // data_location [1 byte] + data_size [2 bytes] + data [256 bytes] // = 268 bytes private final static short EXT_APDU_BUFFER_SIZE = (short) 268; // Minimum PIN size private final static byte PIN_MIN_SIZE = (byte) 4; // Maximum PIN size private final static byte PIN_MAX_SIZE = (byte) 16; // Maximum external authentication tries per key private final static byte MAX_KEY_TRIES = (byte) 5; // Import/Export Object ID private final static short IN_OBJECT_CLA = (short) 0xFFFF; private final static short IN_OBJECT_ID = (short) 0xFFFE; private final static short OUT_OBJECT_CLA = (short) 0xFFFF; private final static short OUT_OBJECT_ID = (short) 0xFFFF; private final static byte KEY_ACL_SIZE = (byte) 6; // Standard public ACL private static byte[] STD_PUBLIC_ACL;/* * = { 0x0000, // Read always allowed * 0x0000, // Write always allowed * 0x0000 // Delete always allowed }; */ private static byte[] acl; // Temporary ACL // code of CLA byte in the command APDU header private final static byte CardEdge_CLA = (byte) 0xB0; /**************************************** * Instruction codes * ****************************************/ // Applet initialization private final static byte INS_SETUP = (byte) 0x2A; // Keys' use and management private final static byte INS_GEN_KEYPAIR = (byte) 0x30; private final static byte INS_IMPORT_KEY = (byte) 0x32; private final static byte INS_EXPORT_KEY = (byte) 0x34; private final static byte INS_COMPUTE_CRYPT = (byte) 0x36; // External authentication private final static byte INS_CREATE_PIN = (byte) 0x40; private final static byte INS_VERIFY_PIN = (byte) 0x42; private final static byte INS_CHANGE_PIN = (byte) 0x44; private final static byte INS_UNBLOCK_PIN = (byte) 0x46; private final static byte INS_LOGOUT_ALL = (byte) 0x60; private final static byte INS_GET_CHALLENGE = (byte) 0x62; private final static byte INS_EXT_AUTH = (byte) 0x38; // Objects' use and management private final static byte INS_CREATE_OBJ = (byte) 0x5A; private final static byte INS_DELETE_OBJ = (byte) 0x52; private final static byte INS_READ_OBJ = (byte) 0x56; private final static byte INS_WRITE_OBJ = (byte) 0x54; // Status information private final static byte INS_LIST_OBJECTS = (byte) 0x58; private final static byte INS_LIST_PINS = (byte) 0x48; private final static byte INS_LIST_KEYS = (byte) 0x3A; private final static byte INS_GET_STATUS = (byte) 0x3C; /** There have been memory problems on the card */ private final static short SW_NO_MEMORY_LEFT = ObjectManager.SW_NO_MEMORY_LEFT; /** Entered PIN is not correct */ private final static short SW_AUTH_FAILED = (short) 0x9C02; /** Required operation is not allowed in actual circumstances */ private final static short SW_OPERATION_NOT_ALLOWED = (short) 0x9C03; /** Required feature is not (yet) supported */ private final static short SW_UNSUPPORTED_FEATURE = (short) 0x9C05; /** Required operation was not authorized because of a lack of privileges */ private final static short SW_UNAUTHORIZED = (short) 0x9C06; /** Required object is missing */ private final static short SW_OBJECT_NOT_FOUND = (short) 0x9C07; /** New object ID already in use */ private final static short SW_OBJECT_EXISTS = (short) 0x9C08; /** Algorithm specified is not correct */ private final static short SW_INCORRECT_ALG = (short) 0x9C09; /** Incorrect P1 parameter */ private final static short SW_INCORRECT_P1 = (short) 0x9C10; /** Incorrect P2 parameter */ private final static short SW_INCORRECT_P2 = (short) 0x9C11; /** No more data available */ private final static short SW_SEQUENCE_END = (short) 0x9C12; /** Invalid input parameter to command */ private final static short SW_INVALID_PARAMETER = (short) 0x9C0F; /** Verify operation detected an invalid signature */ private final static short SW_SIGNATURE_INVALID = (short) 0x9C0B; /** Operation has been blocked for security reason */ private final static short SW_IDENTITY_BLOCKED = (short) 0x9C0C; /** Unspecified error */ private final static short SW_UNSPECIFIED_ERROR = (short) 0x9C0D; /** For debugging purposes */ private final static short SW_INTERNAL_ERROR = (short) 0x9CFF; // Algorithm Type in APDUs private final static byte ALG_RSA = (byte) 0x00; private final static byte ALG_RSA_CRT = (byte) 0x01; private final static byte ALG_DSA = (byte) 0x02; private final static byte ALG_DES = (byte) 0x03; private final static byte ALG_3DES = (byte) 0x04; private final static byte ALG_3DES3 = (byte) 0x05; // Key Type in Key Blobs private final static byte KEY_RSA_PUBLIC = (byte) 0x01; private final static byte KEY_RSA_PRIVATE = (byte) 0x02; private final static byte KEY_RSA_PRIVATE_CRT = (byte) 0x03; private final static byte KEY_DSA_PUBLIC = (byte) 0x04; private final static byte KEY_DSA_PRIVATE = (byte) 0x05; private final static byte KEY_DES = (byte) 0x06; private final static byte KEY_3DES = (byte) 0x07; private final static byte KEY_3DES3 = (byte) 0x08; // KeyBlob Encoding in Key Blobs private final static byte BLOB_ENC_PLAIN = (byte) 0x00; // Cipher Operations admitted in ComputeCrypt() private final static byte OP_INIT = (byte) 0x01; private final static byte OP_PROCESS = (byte) 0x02; private final static byte OP_FINALIZE = (byte) 0x03; // Cipher Directions admitted in ComputeCrypt() private final static byte CD_SIGN = (byte) 0x01; private final static byte CD_VERIFY = (byte) 0x02; private final static byte CD_ENCRYPT = (byte) 0x03; private final static byte CD_DECRYPT = (byte) 0x04; // Cipher Modes admitted in ComputeCrypt() private final static byte CM_RSA_NOPAD = (byte) 0x00; private final static byte CM_RSA_PAD_PKCS1 = (byte) 0x01; private final static byte CM_DSA_SHA = (byte) 0x10; private final static byte CM_DES_CBC_NOPAD = (byte) 0x20; private final static byte CM_DES_ECB_NOPAD = (byte) 0x21; private final static byte DL_APDU = (byte) 0x01; private final static byte DL_OBJECT = (byte) 0x02; private final static byte LIST_OPT_RESET = (byte) 0x00; private final static byte LIST_OPT_NEXT = (byte) 0x01; private final static byte OPT_DEFAULT = (byte) 0x00; // Use JC defaults private final static byte OPT_RSA_PUB_EXP = (byte) 0x01; // RSA: provide public exponent private final static byte OPT_DSA_GPQ = (byte) 0x02; // DSA: provide p,q,g public key parameters // Offsets in buffer[] for key generation private final static short OFFSET_GENKEY_ALG = (short) (ISO7816.OFFSET_CDATA); private final static short OFFSET_GENKEY_SIZE = (short) (ISO7816.OFFSET_CDATA + 1); private final static short OFFSET_GENKEY_PRV_ACL = (short) (ISO7816.OFFSET_CDATA + 3); private final static short OFFSET_GENKEY_PUB_ACL = (short) (OFFSET_GENKEY_PRV_ACL + KEY_ACL_SIZE); private final static short OFFSET_GENKEY_OPTIONS = (short) (OFFSET_GENKEY_PUB_ACL + KEY_ACL_SIZE); private final static short OFFSET_GENKEY_RSA_PUB_EXP_LENGTH = (short) (OFFSET_GENKEY_OPTIONS + 1); private final static short OFFSET_GENKEY_RSA_PUB_EXP_VALUE = (short) (OFFSET_GENKEY_RSA_PUB_EXP_LENGTH + 2); private final static short OFFSET_GENKEY_DSA_GPQ = (short) (OFFSET_GENKEY_OPTIONS + 1); /**************************************** * Instance variables declaration * ****************************************/ // Memory Manager private MemoryManager mem; // Object Manager private ObjectManager om; // Key objects (allocated on demand) private Key[] keys; // Key ACLs private byte[] keyACLs; // Key Tries Left private byte[] keyTries; // Key iterator for ListKeys: it's an offset in the keys[] array. private byte key_it; // True if a GetChallenge() has been issued private boolean getChallengeDone; /* * KeyPair, Cipher and Signature objects * These are allocated on demand * * TODO: Here we could have just 1 Object[] and * make proper casts when * needed */ private Cipher[] ciphers; private Signature[] signatures; // Says if we are using a signature or a cipher object private byte[] ciph_dirs; private KeyPair[] keyPairs; private RandomData randomData; // RandomData class instance // PIN and PUK objects, allocated on demand private OwnerPIN[] pins, ublk_pins; // Buffer for storing extended APDUs private byte[] recvBuffer; /* * Logged identities: this is used for faster access control, so we don't * have to ping each PIN object */ private short logged_ids; /* For the setup function - should only be called once */ private boolean setupDone = false; private byte create_object_ACL; private byte create_key_ACL; private byte create_pin_ACL; /**************************************** * Methods * ****************************************/ private CardEdge(byte[] bArray, short bOffset, byte bLength) { ublk_pins = new OwnerPIN[MAX_NUM_PINS]; pins = new OwnerPIN[MAX_NUM_PINS]; // FIXME: something should be done already here, not only with setup APDU } public static void install(byte[] bArray, short bOffset, byte bLength) { CardEdge wal = new CardEdge(bArray, bOffset, bLength); /* Register the Applet (copied code) */ if (bArray[bOffset] == 0) wal.register(); else wal.register(bArray, (short) (bOffset + 1), (byte) (bArray[bOffset])); } public boolean select() { /* * Application has been selected: Do session cleanup operation */ // Destroy the IO objects (if they exist) if (setupDone) { om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); } LogOutAll(); return true; } public void deselect() { // Destroy the IO objects (if they exist) if (setupDone) { om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); } LogOutAll(); } public void process(APDU apdu) { // APDU object carries a byte array (buffer) to // transfer incoming and outgoing APDU header // and data bytes between card and CAD // At this point, only the first header bytes // [CLA, INS, P1, P2, P3] are available in // the APDU buffer. // The interface javacard.framework.ISO7816 // declares constants to denote the offset of // these bytes in the APDU buffer if (selectingApplet()) ISOException.throwIt(ISO7816.SW_NO_ERROR); byte[] buffer = apdu.getBuffer(); // check SELECT APDU command if ((buffer[ISO7816.OFFSET_CLA] == 0) && (buffer[ISO7816.OFFSET_INS] == (byte) 0xA4)) return; // verify the rest of commands have the // correct CLA byte, which specifies the // command structure if (buffer[ISO7816.OFFSET_CLA] != CardEdge_CLA) ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); byte ins = buffer[ISO7816.OFFSET_INS]; if (!setupDone && (ins != (byte) INS_SETUP)) ISOException.throwIt(SW_UNSUPPORTED_FEATURE); if (setupDone && (ins == (byte) INS_SETUP)) ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); switch (ins) { case INS_SETUP: setup(apdu, buffer); break; case INS_GEN_KEYPAIR: GenerateKeyPair(apdu, buffer); break; case INS_IMPORT_KEY: ImportKey(apdu, buffer); break; case INS_EXPORT_KEY: ExportKey(apdu, buffer); break; case INS_COMPUTE_CRYPT: ComputeCrypt(apdu, buffer); break; case INS_VERIFY_PIN: VerifyPIN(apdu, buffer); break; case INS_CREATE_PIN: CreatePIN(apdu, buffer); break; case INS_CHANGE_PIN: ChangePIN(apdu, buffer); break; case INS_UNBLOCK_PIN: UnblockPIN(apdu, buffer); break; case INS_LOGOUT_ALL: LogOutAll(); break; case INS_GET_CHALLENGE: GetChallenge(apdu, buffer); break; case INS_EXT_AUTH: ExternalAuthenticate(apdu, buffer); break; case INS_CREATE_OBJ: CreateObject(apdu, buffer); break; case INS_DELETE_OBJ: DeleteObject(apdu, buffer); break; case INS_READ_OBJ: ReadObject(apdu, buffer); break; case INS_WRITE_OBJ: WriteObject(apdu, buffer); break; case INS_LIST_PINS: ListPINs(apdu, buffer); break; case INS_LIST_OBJECTS: ListObjects(apdu, buffer); break; case INS_LIST_KEYS: ListKeys(apdu, buffer); break; case INS_GET_STATUS: GetStatus(apdu, buffer); break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } ; } // end of process method /** Setup APDU - initialize the applet * * Incoming data: * PIN0 len + PIN0 + PUK0 len + PUK0 + */ private void setup(APDU apdu, byte[] buffer) { short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); short base = (short) (ISO7816.OFFSET_CDATA); byte numBytes = buffer[base++]; OwnerPIN pin = pins[0]; if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); if (pin != null) { if (pin.getTriesRemaining() == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); if (!pin.check(buffer, base, numBytes)) ISOException.throwIt(SW_AUTH_FAILED); } base += numBytes; byte pin_tries = buffer[base++]; byte ublk_tries = buffer[base++]; numBytes = buffer[base++]; if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); pins[0] = new OwnerPIN(pin_tries, PIN_MAX_SIZE); pins[0].update(buffer, base, numBytes); base += numBytes; numBytes = buffer[base++]; if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); ublk_pins[0] = new OwnerPIN(ublk_tries, PIN_MAX_SIZE); ublk_pins[0].update(buffer, base, numBytes); base += numBytes; pin_tries = buffer[base++]; ublk_tries = buffer[base++]; numBytes = buffer[base++]; if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); pins[1] = new OwnerPIN(pin_tries, PIN_MAX_SIZE); pins[1].update(buffer, base, numBytes); base += numBytes; numBytes = buffer[base++]; if (!CheckPINPolicy(buffer, base, numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); ublk_pins[1] = new OwnerPIN(ublk_tries, PIN_MAX_SIZE); ublk_pins[1].update(buffer, base, numBytes); base += numBytes; base += (short) 2; short mem_size = Util.getShort(buffer, base); base += (short) 2; create_object_ACL = buffer[base++]; create_key_ACL = buffer[base++]; create_pin_ACL = buffer[base++]; mem = new MemoryManager((short) mem_size); om = new ObjectManager(mem); keys = new Key[MAX_NUM_KEYS]; keyACLs = new byte[(short) (MAX_NUM_KEYS * KEY_ACL_SIZE)]; keyTries = new byte[MAX_NUM_KEYS]; for (byte i = (byte) 0; i < (byte) MAX_NUM_KEYS; i++) keyTries[i] = MAX_KEY_TRIES; keyPairs = new KeyPair[MAX_NUM_KEYS]; ciphers = new Cipher[MAX_NUM_KEYS]; signatures = new Signature[MAX_NUM_KEYS]; ciph_dirs = new byte[MAX_NUM_KEYS]; for (byte i = (byte) 0; i < (byte) MAX_NUM_KEYS; i++) ciph_dirs[i] = (byte) 0xFF; logged_ids = 0x00; // No identities logged in getChallengeDone = false; // No GetChallenge() issued so far randomData = null; // Will be created on demand when needed STD_PUBLIC_ACL = new byte[KEY_ACL_SIZE]; for (byte i = (byte) 0; i < (byte) KEY_ACL_SIZE; i += (short) 2) Util.setShort(STD_PUBLIC_ACL, i, (short) 0x0000); // Initialize the extended APDU buffer try { // Try to allocate the extended APDU buffer on RAM memory recvBuffer = JCSystem.makeTransientByteArray((short) EXT_APDU_BUFFER_SIZE, JCSystem.CLEAR_ON_DESELECT); } catch (SystemException e) { // Allocate the extended APDU buffer on EEPROM memory // This is the fallback method, but its usage is really not // recommended // as after ~ 100000 writes it will kill the EEPROM cells... recvBuffer = new byte[EXT_APDU_BUFFER_SIZE]; } setupDone = true; } /********** UTILITY FUNCTIONS **********/ /* * SendData() wraps the setOutgoing(), setLength(), .. stuff * that could be * necessary to be fully JavaCard compliant. */ private void sendData(APDU apdu, byte[] data, short offset, short size) { if (size > EXT_APDU_BUFFER_SIZE) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); apdu.setOutgoing(); apdu.setOutgoingLength(size); apdu.sendBytesLong(data, offset, size); } /* Retrieves the full contents from the apdu object in case of */ /* an extended APDU. */ private void getData(APDU apdu, byte[] src, short bytesRead, byte[] dst) { short recvLen = 0; short apduOffset = bytesRead; Util.arrayCopyNonAtomic(src, (short) 0, dst, (short) 0, apduOffset); do { recvLen = apdu.receiveBytes((short) 0); Util.arrayCopyNonAtomic(src, (short) 0, dst, apduOffset, recvLen); apduOffset += recvLen; } while (recvLen > 0); } /* * Retrieves the Cipher object to be used w/ the specified key * and * algorithm id (Cipher.ALG_XX). * If exists, check it has the proper * algorithm and throws * SW_OP_NOT_ALLOWED if not * If not, creates it */ private Cipher getCipher(byte key_nb, byte alg_id) { if (ciphers[key_nb] == null) { ciphers[key_nb] = Cipher.getInstance(alg_id, false); } else if (ciphers[key_nb].getAlgorithm() != alg_id) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); return ciphers[key_nb]; } /* * Retrieves the Signature object to be used w/ the specified key * and * algorithm id (Signature.ALG_XX). * If exists, check it has the proper * algorithm and throws * SW_OPERATION_NOT_ALLOWED if not * If does not * exist, creates it */ private Signature getSignature(byte key_nb, byte alg_id) { if (signatures[key_nb] == null) { signatures[key_nb] = Signature.getInstance(alg_id, false); } else if (signatures[key_nb].getAlgorithm() != alg_id) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); return signatures[key_nb]; } /** * Retrieves the Key object to be used w/ the specified key number, key type * (KEY_XX) and size. If exists, check it has the proper key type If not, * creates it. * * @return Retrieved Key object or throws SW_UNATUTHORIZED, * SW_OPERATION_NOT_ALLOWED */ private Key getKey(byte key_nb, byte key_type, short key_size) { byte jc_key_type = keyType2JCType(key_type); if (keys[key_nb] == null) { // We have to create the Key /* Check that Identity n.0 is logged */ if ((create_key_ACL == (byte) 0xFF) || (((logged_ids & create_key_ACL) == (short) 0x0000) && (create_key_ACL != (byte) 0x00))) ISOException.throwIt(SW_UNAUTHORIZED); keys[key_nb] = KeyBuilder.buildKey(jc_key_type, key_size, false); } else { // Key already exists: check size & type /* * TODO: As an option, we could just discard and recreate if not of * the correct type, but creates trash objects */ if ((keys[key_nb].getSize() != key_size) || (keys[key_nb].getType() != jc_key_type)) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); } return keys[key_nb]; } // Converts a Applet's Key Type to the JavaCard one. private byte keyType2JCType(byte key_type) { switch (key_type) { case KEY_RSA_PUBLIC: return KeyBuilder.TYPE_RSA_PUBLIC; case KEY_RSA_PRIVATE: return KeyBuilder.TYPE_RSA_PRIVATE; case KEY_RSA_PRIVATE_CRT: return KeyBuilder.TYPE_RSA_CRT_PRIVATE; case KEY_DSA_PUBLIC: return KeyBuilder.TYPE_DSA_PUBLIC; case KEY_DSA_PRIVATE: return KeyBuilder.TYPE_DSA_PUBLIC; case KEY_DES: return KeyBuilder.TYPE_DES; case KEY_3DES: case KEY_3DES3: return KeyBuilder.TYPE_DES; default: ISOException.throwIt(SW_INVALID_PARAMETER); } return (byte) 0; // Avoid compiler warning } // Converts a JavaCard's Key Type to the Applet one. private byte getKeyType(Key key) { switch (key.getType()) { case KeyBuilder.TYPE_RSA_PUBLIC: return KEY_RSA_PUBLIC; case KeyBuilder.TYPE_RSA_PRIVATE: return KEY_RSA_PRIVATE; case KeyBuilder.TYPE_RSA_CRT_PRIVATE: return KEY_RSA_PRIVATE_CRT; case KeyBuilder.TYPE_DSA_PUBLIC: return KEY_DSA_PUBLIC; case KeyBuilder.TYPE_DSA_PRIVATE: return KEY_DSA_PRIVATE; case KeyBuilder.TYPE_DES: if (key.getSize() == (short) 64) return KEY_DES; if (key.getSize() == (short) 128) return KEY_3DES; if (key.getSize() == (short) 192) return KEY_3DES3; default: ISOException.throwIt(SW_INTERNAL_ERROR); } return (byte) 0; // Avoid compiler warning } /** Check from ACL if a key can be read */ boolean authorizeKeyRead(byte key_nb) { short acl_offset = (short) (key_nb * KEY_ACL_SIZE); short required_ids = Util.getShort(keyACLs, acl_offset); return ((required_ids != (short) 0xFFFF) && ((short) (required_ids & logged_ids) == required_ids)); } /** Check from ACL if a key can be overwritten */ boolean authorizeKeyWrite(byte key_nb) { short acl_offset = (short) (key_nb * KEY_ACL_SIZE + 2); short required_ids = Util.getShort(keyACLs, acl_offset); return ((required_ids != (short) 0xFFFF) && ((short) (required_ids & logged_ids) == required_ids)); } /** Check from ACL if a key can be used */ boolean authorizeKeyUse(byte key_nb) { short acl_offset = (short) (key_nb * KEY_ACL_SIZE + 4); short required_ids = Util.getShort(keyACLs, acl_offset); return ((required_ids != (short) 0xFFFF) && ((short) (required_ids & logged_ids) == required_ids)); } /** Returns an ACL that requires current logged in identities. */ byte[] getCurrentACL() { if (acl == null) acl = new byte[KEY_ACL_SIZE]; byte i; for (i = (byte) 0; i < KEY_ACL_SIZE; i += (byte) 2) Util.setShort(acl, i, logged_ids); return acl; } /** Returns an ACL that disables all operations for the application. */ byte[] getRestrictedACL() { if (acl == null) acl = new byte[KEY_ACL_SIZE]; byte i; for (i = (byte) 0; i < KEY_ACL_SIZE; i += (byte) 2) Util.setShort(acl, i, (short) 0xFFFF); return acl; } /** Registers login of strong identity associated with a key number */ private void LoginStrongIdentity(byte key_nb) { logged_ids |= (short) (((short) 0x01) << (key_nb + 8)); } /** * Registers logout of an identity. This must be called anycase when a PIN * verification or external authentication fail */ private void LogoutIdentity(byte id_nb) { logged_ids &= (short) ~(0x0001 << id_nb); } /** Deletes and zeros the IO objects and throws the passed in exception */ private void ThrowDeleteObjects(short exception) { om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); ISOException.throwIt(exception); } /** Checks if PIN policies are satisfied for a PIN code */ private boolean CheckPINPolicy(byte[] pin_buffer, short pin_offset, byte pin_size) { if ((pin_size < PIN_MIN_SIZE) || (pin_size > PIN_MAX_SIZE)) return false; return true; } /**************************************** * APDU handlers * ****************************************/ private void ComputeCrypt(APDU apdu, byte[] apduBuffer) { /* Buffer pointer */ byte[] buffer = apduBuffer; short bytesLeft = apdu.setIncomingAndReceive(); short LC = apdu.getIncomingLength(); short dataOffset = apdu.getOffsetCdata(); if ((short) (LC + dataOffset) > EXT_APDU_BUFFER_SIZE) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); /* Is this an extended APDU? */ if (bytesLeft != LC) { getData(apdu, apduBuffer, (short) (dataOffset + bytesLeft), recvBuffer); buffer = recvBuffer; bytesLeft = LC; } byte key_nb = buffer[ISO7816.OFFSET_P1]; if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS) || (keys[key_nb] == null)) ISOException.throwIt(SW_INCORRECT_P1); /* Enforce Access Control */ if (!authorizeKeyUse(key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); byte op = buffer[ISO7816.OFFSET_P2]; Key key = keys[key_nb]; byte ciph_dir; byte data_location; byte[] src_buff; short src_base; short src_avail; short size; switch (op) { case OP_INIT: if (bytesLeft < 3) ISOException.throwIt(SW_INVALID_PARAMETER); byte ciph_mode = buffer[dataOffset]; ciph_dir = buffer[(short) (dataOffset + 1)]; byte ciph_alg_id; data_location = buffer[(short) (dataOffset + 2)]; switch (data_location) { case DL_APDU: src_buff = buffer; src_base = (short) (dataOffset + 3); src_avail = (short) (bytesLeft - 3); break; case DL_OBJECT: src_buff = mem.getBuffer(); src_base = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID); if (src_base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); src_avail = om.getSizeFromAddress(src_base); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); return; // Compiler warning } if (src_avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(src_buff, src_base); if (src_avail < (short) (2 + size)) ISOException.throwIt(SW_INVALID_PARAMETER); switch (ciph_dir) { case CD_SIGN: case CD_VERIFY: switch (key.getType()) { case KeyBuilder.TYPE_RSA_PUBLIC: case KeyBuilder.TYPE_RSA_PRIVATE: ciph_alg_id = Signature.ALG_RSA_MD5_PKCS1; ISOException.throwIt(SW_UNSUPPORTED_FEATURE); break; case KeyBuilder.TYPE_DSA_PUBLIC: case KeyBuilder.TYPE_DSA_PRIVATE: if (ciph_mode == CM_DSA_SHA) ciph_alg_id = Signature.ALG_DSA_SHA; else { ISOException.throwIt(SW_INVALID_PARAMETER); return; // Compiler warning (ciph_alg_id) } break; default: // DSA Encryption/Decryption is not supported by JavaCard !! ISOException.throwIt(SW_INCORRECT_ALG); return; // Compiler warning (ciph_alg_id) } Signature sign = getSignature(key_nb, ciph_alg_id); if (size == (short) 0) sign.init(key, (ciph_dir == CD_SIGN) ? Signature.MODE_SIGN : Signature.MODE_VERIFY); else sign.init(key, (ciph_dir == CD_SIGN) ? Signature.MODE_SIGN : Signature.MODE_VERIFY, src_buff, (short) (src_base + 2), size); ciph_dirs[key_nb] = ciph_dir; break; case CD_ENCRYPT: case CD_DECRYPT: switch (key.getType()) { case KeyBuilder.TYPE_RSA_PUBLIC: case KeyBuilder.TYPE_RSA_PRIVATE: case KeyBuilder.TYPE_RSA_CRT_PRIVATE: if (ciph_mode == CM_RSA_NOPAD) ciph_alg_id = Cipher.ALG_RSA_NOPAD; else if (ciph_mode == CM_RSA_PAD_PKCS1) ciph_alg_id = Cipher.ALG_RSA_PKCS1; else { ISOException.throwIt(SW_INVALID_PARAMETER); return; } break; case KeyBuilder.TYPE_DES: if (ciph_mode == CM_DES_CBC_NOPAD) ciph_alg_id = Cipher.ALG_DES_CBC_NOPAD; else if (ciph_mode == CM_DES_ECB_NOPAD) ciph_alg_id = Cipher.ALG_DES_ECB_NOPAD; else { ISOException.throwIt(SW_INVALID_PARAMETER); return; } break; case KeyBuilder.TYPE_DSA_PUBLIC: case KeyBuilder.TYPE_DSA_PRIVATE: // DSA Encryption/Decryption is not supported by JavaCard !! ISOException.throwIt(SW_INVALID_PARAMETER); return; default: ISOException.throwIt(SW_INTERNAL_ERROR); return; // Compiler warning (ciph_alg_id unset) } Cipher ciph = getCipher(key_nb, ciph_alg_id); if (size == (short) 0) ciph.init(key, (ciph_dir == CD_ENCRYPT) ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT); else ciph.init(key, (ciph_dir == CD_ENCRYPT) ? Cipher.MODE_ENCRYPT : Cipher.MODE_DECRYPT, src_buff, (short) (src_base + 2), size); ciph_dirs[key_nb] = ciph_dir; break; default: ISOException.throwIt(SW_INVALID_PARAMETER); } break; case OP_PROCESS: case OP_FINALIZE: ciph_dir = ciph_dirs[key_nb]; switch (ciph_dir) { case CD_SIGN: case CD_VERIFY: Signature sign = signatures[key_nb]; if (sign == null) /* * Don't know what is incorrect: just say incorrect * parameters we guess it was specified a wrong key number */ ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); data_location = buffer[dataOffset]; switch (data_location) { case DL_APDU: src_buff = mem.getBuffer(); // Skip Data Location byte. src_base = (short) (dataOffset + 1); src_avail = (short) (bytesLeft - 1); break; case DL_OBJECT: src_buff = mem.getBuffer(); src_base = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID); if (src_base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); src_avail = om.getSizeFromAddress(src_base); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); return; } if (src_avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(src_buff, src_base); // IO objects are allowed to be larger than size of contained // data if (src_avail < (short) (2 + size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (op == OP_PROCESS) sign.update(src_buff, (short) (src_base + 2), size); else { // OP_FINALIZE if (ciph_dir == CD_SIGN) { om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); short dst_base = om.createObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, (short) (sign.getLength() + 2), getCurrentACL(), (short) 0); if (dst_base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_NO_MEMORY_LEFT); short sign_size = sign.sign(src_buff, (short) (src_base + 2), size, mem.getBuffer(), (short) (dst_base + 2)); if (sign_size > sign.getLength()) // We got a buffer overflow (unless we were in // memory end and got an exception...) ISOException.throwIt(SW_INTERNAL_ERROR); mem.setShort(dst_base, sign_size); // Actually send data back (and clear output buffer) // only if location is APDU if (data_location == DL_APDU) { sendData(apdu, mem.getBuffer(), dst_base, (short) (sign_size + 2)); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); } } else { // ciph_dir == CD_VERIFY if (src_avail < (short) (2 + size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); short sign_size = Util.getShort(src_buff, (short) (src_base + 2 + size)); if (src_avail < (short) (2 + size + 2 + sign_size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (sign_size != sign.getLength()) ISOException.throwIt(SW_INVALID_PARAMETER); if (!sign.verify(src_buff, (short) (src_base + 2), size, src_buff, (short) (src_base + 2 + size + 2), sign_size)) ISOException.throwIt(SW_SIGNATURE_INVALID); } } break; case CD_ENCRYPT: case CD_DECRYPT: Cipher ciph = ciphers[key_nb]; if (ciph == null) /* * Don't know what is incorrect: just say incorrect * parameters we guess it was specified a wrong key number */ ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); data_location = buffer[dataOffset]; switch (data_location) { case DL_APDU: src_buff = buffer; src_base = (short) (dataOffset + 1); src_avail = (short) (bytesLeft - 1); break; case DL_OBJECT: src_buff = mem.getBuffer(); src_base = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID); if (src_base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); src_avail = om.getSizeFromAddress(src_base); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); return; } if (src_avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(src_buff, src_base); if (src_avail < (short) (2 + size)) ISOException.throwIt(SW_INVALID_PARAMETER); // TODO: Don't destroy the out obj every time, but keep it om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); // Create object with 2 more bytes for DataChunk Size field short dst_base = om.createObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, (short) (size + 2), getCurrentACL(), (short) 0); if (dst_base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_NO_MEMORY_LEFT); mem.setShort(dst_base, size); if (op == OP_PROCESS) ciph.update(src_buff, (short) (src_base + 2), size, mem.getBuffer(), (short) (dst_base + 2)); else /* op == OP_FINAL */ ciph.doFinal(src_buff, (short) (src_base + 2), size, mem.getBuffer(), (short) (dst_base + 2)); if (data_location == DL_APDU) { // Also copies the Short size information Util.arrayCopyNonAtomic(mem.getBuffer(), dst_base, buffer, (short) 0, (short) (size + 2)); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); sendData(apdu, buffer, (short) 0, (short) (size + 2)); } break; default: // Internal error because it should have been checked on INIT ISOException.throwIt(SW_INTERNAL_ERROR); } break; default: ISOException.throwIt(SW_INCORRECT_P2); } } private void GenerateKeyPair(APDU apdu, byte[] buffer) { short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); byte alg_id = buffer[OFFSET_GENKEY_ALG]; switch (alg_id) { case ALG_RSA: case ALG_RSA_CRT: GenerateKeyPairRSA(buffer); break; case ALG_DSA: GenerateKeyPairDSA(buffer); break; default: ISOException.throwIt(SW_INCORRECT_ALG); } } // Data has already been receive()ed private void GenerateKeyPairRSA(byte[] buffer) { byte prv_key_nb = buffer[ISO7816.OFFSET_P1]; if ((prv_key_nb < 0) || (prv_key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P1); byte pub_key_nb = buffer[ISO7816.OFFSET_P2]; if ((pub_key_nb < 0) || (pub_key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P2); if (pub_key_nb == prv_key_nb) ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); byte alg_id = buffer[OFFSET_GENKEY_ALG]; short key_size = Util.getShort(buffer, OFFSET_GENKEY_SIZE); byte options = buffer[OFFSET_GENKEY_OPTIONS]; RSAPublicKey pub_key = (RSAPublicKey) getKey(pub_key_nb, KEY_RSA_PUBLIC, key_size); PrivateKey prv_key = (PrivateKey) getKey(prv_key_nb, alg_id == ALG_RSA ? KEY_RSA_PRIVATE : KEY_RSA_PRIVATE_CRT, key_size); /* If we're going to overwrite a keyPair's contents, check ACL */ if (pub_key.isInitialized() && !authorizeKeyWrite(pub_key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); if (prv_key.isInitialized() && !authorizeKeyWrite(prv_key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); /* Store private key ACL */ Util.arrayCopy(buffer, OFFSET_GENKEY_PRV_ACL, keyACLs, (short) (prv_key_nb * KEY_ACL_SIZE), KEY_ACL_SIZE); /* Store public key ACL */ Util.arrayCopy(buffer, OFFSET_GENKEY_PUB_ACL, keyACLs, (short) (pub_key_nb * KEY_ACL_SIZE), KEY_ACL_SIZE); switch (options) { case OPT_DEFAULT: /* * As the default was specified, if public key already * exist we * have to invalidate it, otherwise its parameters * would be used * in place of the default ones */ if (pub_key.isInitialized()) pub_key.clearKey(); break; case OPT_RSA_PUB_EXP: short exp_length = Util.getShort(buffer, OFFSET_GENKEY_RSA_PUB_EXP_LENGTH); pub_key.setExponent(buffer, OFFSET_GENKEY_RSA_PUB_EXP_VALUE, exp_length); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); } /* * TODO: Migrate checks on KeyPair on the top, so we avoid resource * allocation on error conditions */ /* * If no keypair was previously used, ok. If different keypairs were * used, or for 1 key there is a keypair but the other key not, then * error If the same keypair object was used previously, check keypair * size & type */ if ((keyPairs[pub_key_nb] == null) && (keyPairs[prv_key_nb] == null)) { keyPairs[pub_key_nb] = new KeyPair(pub_key, prv_key); keyPairs[prv_key_nb] = keyPairs[pub_key_nb]; } else if (keyPairs[pub_key_nb] != keyPairs[prv_key_nb]) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); KeyPair kp = keyPairs[pub_key_nb]; if ((kp.getPublic() != pub_key) || (kp.getPrivate() != prv_key)) // This should never happen according with this Applet policies ISOException.throwIt(SW_INTERNAL_ERROR); // We Rely on genKeyPair() to make all necessary checks about types kp.genKeyPair(); } // Data has already been receive()ed private void GenerateKeyPairDSA(byte[] buffer) { byte prv_key_nb = buffer[ISO7816.OFFSET_P1]; if ((prv_key_nb < 0) || (prv_key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P1); byte pub_key_nb = buffer[ISO7816.OFFSET_P2]; if ((pub_key_nb < 0) || (pub_key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P2); short key_size = Util.getShort(buffer, OFFSET_GENKEY_SIZE); byte options = buffer[OFFSET_GENKEY_OPTIONS]; if (pub_key_nb == prv_key_nb) ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2); DSAPublicKey pub_key = (DSAPublicKey) getKey(pub_key_nb, KEY_DSA_PUBLIC, key_size); DSAPrivateKey prv_key = (DSAPrivateKey) getKey(prv_key_nb, KEY_DSA_PRIVATE, key_size); /* If we're going to overwrite a keyPair's contents, check ACL */ if (pub_key.isInitialized() && !authorizeKeyWrite(pub_key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); if (prv_key.isInitialized() && !authorizeKeyWrite(prv_key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); /* Store private key ACL */ Util.arrayCopy(buffer, OFFSET_GENKEY_PRV_ACL, keyACLs, (short) (prv_key_nb * KEY_ACL_SIZE), KEY_ACL_SIZE); /* Store public key ACL */ Util.arrayCopy(buffer, OFFSET_GENKEY_PUB_ACL, keyACLs, (short) (pub_key_nb * KEY_ACL_SIZE), KEY_ACL_SIZE); switch (options) { case OPT_DEFAULT: // As default params were specified, we have to clear the // public key if already initialized, otherwise their params // would be used. if (pub_key.isInitialized()) pub_key.clearKey(); break; case OPT_DSA_GPQ: short base = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID); if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); short avail = om.getSizeFromAddress(base); if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); DSAGetGPQ(mem.getBuffer(), base, avail, pub_key); om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); } /* * TODO: Migrate checks on KeyPair on the top, so we avoid resource * allocation on error conditions */ /* * If no keypair was previously used, ok. If different keypairs were * used, or for 1 key there is a keypair but the other key not, then * error If the same keypair object was used previously, check keypair * size & type */ if ((keyPairs[pub_key_nb] == null) && (keyPairs[prv_key_nb] == null)) { keyPairs[pub_key_nb] = new KeyPair(pub_key, prv_key); keyPairs[prv_key_nb] = keyPairs[pub_key_nb]; } else if (keyPairs[pub_key_nb] != keyPairs[prv_key_nb]) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); KeyPair kp = keyPairs[pub_key_nb]; if ((kp.getPublic() != pub_key) || (kp.getPrivate() != prv_key)) // This should never happen with this Applet policies ISOException.throwIt(SW_INTERNAL_ERROR); // We Rely on genKeyPair() to make all necessary checks about types try { kp.genKeyPair(); } catch (Exception e) { ISOException.throwIt(SW_UNSPECIFIED_ERROR); } } /** * Reads parameters G, P, Q from a buffer and sets them in a DSA key. * * @param buffer * The buffer * @param base * The offset in buffer[] where parameters start * @param avail * The maximum number of bytes allowed to read in buffer[]. If it * was not possible to read parameters within avail bytes * starting from base, throw a DATA_INVALID exception. * @param key * The destination DSAKey object. * @return The effective number of bytes read */ private short DSAGetGPQ(byte[] buffer, short base, short avail, DSAKey key) { short size; short orig_base = base; if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = Util.getShort(buffer, base); base += (short) 2; // Skip G Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); key.setG(buffer, base, size); base += size; // Skip G Value avail -= size; // avail ok... size = Util.getShort(buffer, base); base += (short) 2; // Skip P Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); key.setP(buffer, base, size); base += size; // Skip P Value avail -= size; // avail ok... size = Util.getShort(buffer, base); base += (short) 2; // Skip Q Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); key.setQ(buffer, base, size); base += size; // Skip Q Value avail -= size; return (short) (base - orig_base); } private void ImportKey(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); byte key_nb = buffer[ISO7816.OFFSET_P1]; if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P1); /* If we're going to overwrite a key contents, check ACL */ if ((keys[key_nb] != null) && keys[key_nb].isInitialized() && !authorizeKeyWrite(key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); // Get memory base offset of the input buffer short base = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID); if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); short avail = om.getSizeFromAddress(base); /*** Start reading key blob ***/ // Check entire blob header if (avail < 4) ISOException.throwIt(SW_INVALID_PARAMETER); // Check Blob Encoding if (mem.getByte(base) != BLOB_ENC_PLAIN) // TODO: Encrypted key blob ? ISOException.throwIt(SW_UNSUPPORTED_FEATURE); base++; // Skip Blob Encoding avail--; byte key_type = mem.getByte(base); base++; // Skip Key Type avail--; short key_size = mem.getShort(base); base += (short) 2; // Skip Key Size avail -= (short) 2; short size; switch (key_type) { case KEY_RSA_PUBLIC: RSAPublicKey rsa_pub_key = (RSAPublicKey) getKey(key_nb, key_type, key_size); if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = mem.getShort(base); base += (short) 2; // Skip Mod Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_pub_key.setModulus(mem.getBuffer(), base, size); base += size; // Skip Mod Value avail -= size; // avail already checked in previous if () size = mem.getShort(base); base += (short) 2; // Skip Exp Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_pub_key.setExponent(mem.getBuffer(), base, size); base += size; // Skip Exp Value avail -= size; break; case KEY_RSA_PRIVATE: RSAPrivateKey rsa_prv_key = (RSAPrivateKey) getKey(key_nb, key_type, key_size); if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = mem.getShort(base); base += (short) 2; // Skip Mod Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key.setModulus(mem.getBuffer(), base, size); base += size; // Skip Mod Value avail -= size; // avail already checked in previous if () size = mem.getShort(base); base += (short) 2; // Skip Exp Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key.setExponent(mem.getBuffer(), base, size); base += size; // Skip Exp Value avail -= size; break; case KEY_RSA_PRIVATE_CRT: RSAPrivateCrtKey rsa_prv_key_crt = (RSAPrivateCrtKey) getKey(key_nb, key_type, key_size); if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = mem.getShort(base); base += (short) 2; // Skip P Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setP(mem.getBuffer(), base, size); base += size; // Skip P Value avail -= size; // avail ok... size = mem.getShort(base); base += (short) 2; // Skip Q Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setQ(mem.getBuffer(), base, size); base += size; // Skip Q Value avail -= size; // avail ok... size = mem.getShort(base); base += (short) 2; // Skip PQ Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setPQ(mem.getBuffer(), base, size); base += size; // Skip PQ Value avail -= size; // avail ok... size = mem.getShort(base); base += (short) 2; // Skip DP1 Size avail -= (short) 2; if (avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setDP1(mem.getBuffer(), base, size); base += size; // Skip DP1 Value avail -= size; // avail ok... size = mem.getShort(base); base += (short) 2; // Skip DQ1 Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); rsa_prv_key_crt.setDQ1(mem.getBuffer(), base, size); base += size; // Skip DQ1 Value avail -= size; break; case KEY_DSA_PRIVATE: DSAPrivateKey dsa_prv_key = (DSAPrivateKey) getKey(key_nb, key_type, key_size); short num_bytes = DSAGetGPQ(mem.getBuffer(), base, avail, dsa_prv_key); base += num_bytes; avail -= num_bytes; if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = mem.getShort(base); base += (short) 2; // Skip X Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); dsa_prv_key.setX(mem.getBuffer(), base, size); base += size; // Skip X Value avail -= size; break; case KEY_DSA_PUBLIC: DSAPublicKey dsa_pub_key = (DSAPublicKey) getKey(key_nb, key_type, key_size); num_bytes = DSAGetGPQ(mem.getBuffer(), base, avail, dsa_pub_key); base += num_bytes; avail -= num_bytes; if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = mem.getShort(base); base += (short) 2; // Skip Y Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); dsa_pub_key.setY(mem.getBuffer(), base, size); base += size; // Skip Y Value avail -= size; break; case KEY_DES: case KEY_3DES: case KEY_3DES3: DESKey des_key = (DESKey) getKey(key_nb, key_type, key_size); if (avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); size = mem.getShort(base); base += (short) 2; // Skip Key Size avail -= (short) 2; if (avail < size) ISOException.throwIt(SW_INVALID_PARAMETER); des_key.setKey(mem.getBuffer(), base); base += size; // Skip Key Value avail -= size; break; default: ISOException.throwIt(SW_INCORRECT_ALG); } // Zero and delete the import object om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); } private void ExportKey(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); byte key_nb = buffer[ISO7816.OFFSET_P1]; if ((key_nb < 0) || (key_nb >= MAX_NUM_KEYS)) ISOException.throwIt(SW_INCORRECT_P1); Key key = keys[key_nb]; if ((key == null) || !key.isInitialized()) ISOException.throwIt(SW_INCORRECT_P1); // Enforce Access Control if (!authorizeKeyRead(key_nb)) ISOException.throwIt(SW_UNAUTHORIZED); // Destroy output object if already exists om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); // Automatically throws SW_NO_MEMORY_LEFT short base = om.createObjectMax(OUT_OBJECT_CLA, OUT_OBJECT_ID, getCurrentACL(), (short) 0); short buffer_size = om.getSizeFromAddress(base); short avail = buffer_size; /* * Initially holds buffer size, after is * used to check buffer overflow */ /*** Start reading key blob ***/ // Check Blob Encoding if (buffer[ISO7816.OFFSET_CDATA] != BLOB_ENC_PLAIN) ISOException.throwIt(SW_UNSUPPORTED_FEATURE); // No need to check avail for all the key header if (avail < 4) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); mem.setByte(base, BLOB_ENC_PLAIN); base++; // Skip Blob Encoding // avail advanced below byte key_type = key.getType(); mem.setByte(base, getKeyType(key)); base++; // avail advanced below short key_size = key.getSize(); mem.setShort(base, key_size); base += (short) 2; // Skip Key Size // keeps into account all the key header avail -= (short) 4; short size; /* * Maximum size of a BigNumber estimated to be equal to the key size + 2 * bytes for the bignum size itself. TODO: Check if true for DSA */ short bn_size = (short) (keys[key_nb].getSize() / 8 + 2); switch (key_type) { case KeyBuilder.TYPE_RSA_PUBLIC: RSAPublicKey pub_key = (RSAPublicKey) key; if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = pub_key.getModulus(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Modulus Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = pub_key.getExponent(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Exponent Size & Value avail -= (short) (2 + size); break; case KeyBuilder.TYPE_RSA_PRIVATE: RSAPrivateKey prv_key = (RSAPrivateKey) key; if (avail < bn_size) ISOException.throwIt(SW_NO_MEMORY_LEFT); size = prv_key.getModulus(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Modulus Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = prv_key.getExponent(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Exponent Size & Value avail -= (short) (2 + size); break; case KeyBuilder.TYPE_RSA_CRT_PRIVATE: RSAPrivateCrtKey prv_key_crt = (RSAPrivateCrtKey) key; if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = prv_key_crt.getP(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip P Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = prv_key_crt.getQ(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Q Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = prv_key_crt.getPQ(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip PQ Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = prv_key_crt.getDP1(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip DP1 Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = prv_key_crt.getDQ1(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip DQ1 Size & Value avail -= (short) (2 + size); break; case KeyBuilder.TYPE_DES: DESKey des_key = (DESKey) key; /* For a DES Key, bn_size contains the exact key length + 2 */ if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = des_key.getKey(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip P Size & Value avail -= (short) (2 + size); break; case KeyBuilder.TYPE_DSA_PUBLIC: DSAPublicKey dsa_pub_key = (DSAPublicKey) key; if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_pub_key.getG(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip G Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_pub_key.getP(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip P Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_pub_key.getQ(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Q Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_pub_key.getY(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Y Size & Value avail -= (short) (2 + size); break; case KeyBuilder.TYPE_DSA_PRIVATE: DSAPrivateKey dsa_prv_key = (DSAPrivateKey) key; if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_prv_key.getG(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip G Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_prv_key.getP(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip P Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_prv_key.getQ(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip Q Size & Value avail -= (short) (2 + size); if (avail < bn_size) ThrowDeleteObjects(SW_NO_MEMORY_LEFT); size = dsa_prv_key.getX(mem.getBuffer(), (short) (base + 2)); mem.setShort(base, size); base += (short) (2 + size); // Skip X Size & Value avail -= (short) (2 + size); break; default: ISOException.throwIt(SW_INVALID_PARAMETER); } // Eventually clamp buffer to make the export object the exact // size of the exported key blob om.clampObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, (short) (buffer_size - avail)); } private void CreatePIN(APDU apdu, byte[] buffer) { byte pin_nb = buffer[ISO7816.OFFSET_P1]; byte num_tries = buffer[ISO7816.OFFSET_P2]; /* Check that Identity n.0 is logged */ if ((create_pin_ACL == (byte) 0xFF) || (((logged_ids & create_pin_ACL) == (short) 0x0000) && (create_pin_ACL != (byte) 0x00))) ISOException.throwIt(SW_UNAUTHORIZED); if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS) || (pins[pin_nb] != null)) ISOException.throwIt(SW_INCORRECT_P1); /* Allow pin lengths > 127 (useful at all ?) */ short avail = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (apdu.setIncomingAndReceive() != avail) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // At least 1 character for PIN and 1 for unblock code (+ lengths) if (avail < 4) ISOException.throwIt(SW_INVALID_PARAMETER); byte pin_size = buffer[ISO7816.OFFSET_CDATA]; if (avail < (short) (1 + pin_size + 1)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); byte ucode_size = buffer[(short) (ISO7816.OFFSET_CDATA + 1 + pin_size)]; if (avail != (short) (1 + pin_size + 1 + ucode_size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), ucode_size)) ISOException.throwIt(SW_INVALID_PARAMETER); pins[pin_nb] = new OwnerPIN(num_tries, PIN_MAX_SIZE); pins[pin_nb].update(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size); ublk_pins[pin_nb] = new OwnerPIN((byte) 3, PIN_MAX_SIZE); // Recycle variable pin_size pin_size = (byte) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1); ublk_pins[pin_nb].update(buffer, pin_size, ucode_size); } private void VerifyPIN(APDU apdu, byte[] buffer) { byte pin_nb = buffer[ISO7816.OFFSET_P1]; if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS)) ISOException.throwIt(SW_INCORRECT_P1); OwnerPIN pin = pins[pin_nb]; if (pin == null) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != 0x00) ISOException.throwIt(SW_INCORRECT_P2); short numBytes = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); /* * Here I suppose the PIN code is small enough to enter in the buffer * TODO: Verify the assumption and eventually adjust code to support * reading PIN in multiple read()s */ if (numBytes != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); if (pin.getTriesRemaining() == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); if (!pin.check(buffer, (short) ISO7816.OFFSET_CDATA, (byte) numBytes)) { LogoutIdentity(pin_nb); ISOException.throwIt(SW_AUTH_FAILED); } // Actually register that PIN has been successfully verified. logged_ids |= (short) (0x0001 << pin_nb); } private void ChangePIN(APDU apdu, byte[] buffer) { /* * Here I suppose the PIN code is small enough that 2 of them enter in * the buffer TODO: Verify the assumption and eventually adjust code to * support reading PINs in multiple read()s */ byte pin_nb = buffer[ISO7816.OFFSET_P1]; if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS)) ISOException.throwIt(SW_INCORRECT_P1); OwnerPIN pin = pins[pin_nb]; if (pin == null) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short avail = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (apdu.setIncomingAndReceive() != avail) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // At least 1 charachter for each PIN code if (avail < 4) ISOException.throwIt(SW_INVALID_PARAMETER); byte pin_size = buffer[ISO7816.OFFSET_CDATA]; if (avail < (short) (1 + pin_size + 1)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); byte new_pin_size = buffer[(short) (ISO7816.OFFSET_CDATA + 1 + pin_size)]; if (avail < (short) (1 + pin_size + 1 + new_pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!CheckPINPolicy(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), new_pin_size)) ISOException.throwIt(SW_INVALID_PARAMETER); if (pin.getTriesRemaining() == (byte) 0x00) ISOException.throwIt(SW_IDENTITY_BLOCKED); if (!pin.check(buffer, (short) (ISO7816.OFFSET_CDATA + 1), pin_size)) { LogoutIdentity(pin_nb); ISOException.throwIt(SW_AUTH_FAILED); } pin.update(buffer, (short) (ISO7816.OFFSET_CDATA + 1 + pin_size + 1), new_pin_size); // JC specifies this resets the validated flag. So we do. logged_ids &= (short) ((short) 0xFFFF ^ (0x01 << pin_nb)); } private void UnblockPIN(APDU apdu, byte[] buffer) { byte pin_nb = buffer[ISO7816.OFFSET_P1]; if ((pin_nb < 0) || (pin_nb >= MAX_NUM_PINS)) ISOException.throwIt(SW_INCORRECT_P1); OwnerPIN pin = pins[pin_nb]; OwnerPIN ublk_pin = ublk_pins[pin_nb]; if (pin == null) ISOException.throwIt(SW_INCORRECT_P1); if (ublk_pin == null) ISOException.throwIt(SW_INTERNAL_ERROR); // If the PIN is not blocked, the call is inconsistent if (pin.getTriesRemaining() != 0) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); if (buffer[ISO7816.OFFSET_P2] != 0x00) ISOException.throwIt(SW_INCORRECT_P2); short numBytes = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); /* * Here I suppose the PIN code is small enough to fit into the buffer * TODO: Verify the assumption and eventually adjust code to support * reading PIN in multiple read()s */ if (numBytes != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (!CheckPINPolicy(buffer, ISO7816.OFFSET_CDATA, (byte) numBytes)) ISOException.throwIt(SW_INVALID_PARAMETER); if (!ublk_pin.check(buffer, ISO7816.OFFSET_CDATA, (byte) numBytes)) ISOException.throwIt(SW_AUTH_FAILED); pin.resetAndUnblock(); } private void CreateObject(APDU apdu, byte[] buffer) { short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if ((create_object_ACL == (byte) 0xFF) || (((logged_ids & create_object_ACL) == (short) 0x0000) && (create_object_ACL != (byte) 0x00))) ISOException.throwIt(SW_UNAUTHORIZED); // ID + Size + ACL = 14 bytes if (bytesLeft != (short) (4 + 4 + ObjectManager.OBJ_ACL_SIZE)) ISOException.throwIt(SW_INVALID_PARAMETER); if (buffer[ISO7816.OFFSET_P1] != 0x00) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != 0x00) ISOException.throwIt(SW_INCORRECT_P2); // Retrieve Object ID. short obj_class = Util.getShort(buffer, ISO7816.OFFSET_CDATA); short obj_id = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + (short) 2)); // Check if object exists if (om.exists(obj_class, obj_id)) ISOException.throwIt(SW_OBJECT_EXISTS); // Check if object size in supported range: M.S.Word must be 0x0000 AND // M.S.Bit of L.S.Word must be 0 if ((Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 4)) != 0x0000) || (buffer[(short) (ISO7816.OFFSET_CDATA + 6)] < 0)) ISOException.throwIt(SW_NO_MEMORY_LEFT); // Check for zero size if (Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 6)) == 0x0000) ISOException.throwIt(SW_INVALID_PARAMETER); // Actually create object om.createObject(obj_class, obj_id, // Skip 2 M.S.Bytes of Size (only handle short sizes) Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 6)), buffer, (short) (ISO7816.OFFSET_CDATA + 8)); } private void DeleteObject(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P1); if ((buffer[ISO7816.OFFSET_P2] != (byte) 0x00) && (buffer[ISO7816.OFFSET_P2] != (byte) 0x01)) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (bytesLeft != (short) 0x04) ISOException.throwIt(SW_INVALID_PARAMETER); short obj_class = Util.getShort(buffer, ISO7816.OFFSET_CDATA); short obj_id = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + (short) 2)); // TODO: Here there are 2 object lookups. Optimize, please ! // (single destroy function with logged_ids param) short base = om.getBaseAddress(obj_class, obj_id); // Verify that object exists if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); // Enforce Access Control if (!om.authorizeDeleteFromAddress(base, logged_ids)) ISOException.throwIt(SW_UNAUTHORIZED); // Actually delete the object om.destroyObject(obj_class, obj_id, buffer[ISO7816.OFFSET_P2] == 0x01); } private void ReadObject(APDU apdu, byte[] buffer) { // Checking P1 & P2 if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (bytesLeft != (short) 9) ISOException.throwIt(SW_INVALID_PARAMETER); short obj_class = Util.getShort(buffer, ISO7816.OFFSET_CDATA); short obj_id = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + (short) 2)); // Skip 2 M.S.Bytes of the offset short offset = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + (short) 6)); short size = Util.makeShort((byte) 0x00, buffer[(short) ISO7816.OFFSET_CDATA + (short) 8]); short base = om.getBaseAddress(obj_class, obj_id); // Verify that object exists if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_INVALID_PARAMETER); // Enforce Access Control if (!om.authorizeReadFromAddress(base, logged_ids)) ISOException.throwIt(SW_UNAUTHORIZED); /* * Additional checks: buffer overflow protection (prevents reading * memory contents following the object) */ if ((short) (offset + size) > om.getSizeFromAddress(base)) ISOException.throwIt(SW_INVALID_PARAMETER); // Sending data sendData(apdu, mem.getBuffer(), (short) (base + offset), size); } private void WriteObject(APDU apdu, byte[] buffer) { // Checking P1 & P2 if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); short obj_class = Util.getShort(buffer, ISO7816.OFFSET_CDATA); short obj_id = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 2)); // Skip 2 M.S.Bytes of the offset short offset = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 6)); short size = Util.makeShort((byte) 0x00, buffer[(short) (ISO7816.OFFSET_CDATA + 8)]); short base = om.getBaseAddress(obj_class, obj_id); // Verify that object exists if (base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_INVALID_PARAMETER); // Enforce Access Control if (!om.authorizeWriteFromAddress(base, logged_ids)) ISOException.throwIt(SW_UNAUTHORIZED); /* * Additional checks: buffer overflow protection (prevents writing * memory contents following the object) */ if ((short) (offset + size) > om.getSizeFromAddress(base)) ISOException.throwIt(SW_INVALID_PARAMETER); // Update object data mem.setBytes(base, offset, buffer, (short) (ISO7816.OFFSET_CDATA + 9), size); } private void LogOutAll() { logged_ids = (short) 0x0000; // Nobody is logged in byte i; for (i = (byte) 0; i < MAX_NUM_PINS; i++) if (pins[i] != null) pins[i].reset(); } private void ListPINs(APDU apdu, byte[] buffer) { // Checking P1 & P2 if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); byte expectedBytes = (byte) (buffer[ISO7816.OFFSET_LC]); if (expectedBytes != (short) 2) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); // Build the PIN bit mask short mask = (short) 0x00; short b; for (b = (short) 0; b < MAX_NUM_PINS; b++) if (pins[b] != null) mask |= (short) (((short) 0x01) << b); // Fill the buffer Util.setShort(buffer, (short) 0, mask); // Send response apdu.setOutgoingAndSend((short) 0, (short) 2); } private void ListObjects(APDU apdu, byte[] buffer) { // Checking P1 & P2 if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); byte expectedBytes = (byte) (buffer[ISO7816.OFFSET_LC]); if (expectedBytes < ObjectManager.RECORD_SIZE) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); boolean found = false; // Suppress compiler warning if (buffer[ISO7816.OFFSET_P1] == LIST_OPT_RESET) found = om.getFirstRecord(buffer, (short) 0); else if (buffer[ISO7816.OFFSET_P1] != LIST_OPT_NEXT) ISOException.throwIt(SW_INCORRECT_P1); else found = om.getNextRecord(buffer, (short) 0); if (found) apdu.setOutgoingAndSend((short) 0, (short) ObjectManager.RECORD_SIZE); else ISOException.throwIt(SW_SEQUENCE_END); } private void ListKeys(APDU apdu, byte[] buffer) { // Checking P2 if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short expectedBytes = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (expectedBytes != (short) 0x0B) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (buffer[ISO7816.OFFSET_P1] == LIST_OPT_RESET) key_it = (byte) 0; else if (buffer[ISO7816.OFFSET_P1] != LIST_OPT_NEXT) ISOException.throwIt(SW_INCORRECT_P1); while ((key_it < MAX_NUM_KEYS) && ((keys[key_it] == null) || !keys[key_it].isInitialized())) key_it++; if (key_it < MAX_NUM_KEYS) { Key key = keys[key_it]; buffer[(short) 0] = key_it; buffer[(short) 1] = getKeyType(key); buffer[(short) 2] = (byte) 0xFF; // No partner information available Util.setShort(buffer, (short) 3, key.getSize()); Util.arrayCopyNonAtomic(keyACLs, (short) (key_it * KEY_ACL_SIZE), buffer, (short) 5, KEY_ACL_SIZE); // Advance iterator key_it++; apdu.setOutgoingAndSend((short) 0, (short) (5 + KEY_ACL_SIZE)); } } private void GetChallenge(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P1); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); if (bytesLeft < 4) ISOException.throwIt(SW_INVALID_PARAMETER); short size = Util.getShort(buffer, ISO7816.OFFSET_CDATA); short seed_size = Util.getShort(buffer, (short) (ISO7816.OFFSET_CDATA + 2)); if (bytesLeft != (short) (seed_size + 4)) ISOException.throwIt(SW_INVALID_PARAMETER); byte data_loc = buffer[ISO7816.OFFSET_P2]; if ((data_loc != DL_APDU) && (data_loc != DL_OBJECT)) ISOException.throwIt(SW_INVALID_PARAMETER); if (randomData == null) randomData = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM); if (seed_size != (short) 0x0000) randomData.setSeed(buffer, (short) (ISO7816.OFFSET_CDATA + 4), seed_size); // Allow size = 0 for only seeding purposes if (size != (short) 0x0000) { // Automatically throws exception if no memory short base = om.createObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, (short) (size + 2), getRestrictedACL(), (short) 0); mem.setShort(base, size); randomData.generateData(mem.getBuffer(), (short) (base + 2), size); /* * Remember that out object contains getChallenge data (to avoid * attacks pretending to write the out object before extAuth) */ getChallengeDone = true; // Actually return data in APDU only if DL_APDU specified. if (data_loc == DL_APDU) { sendData(apdu, mem.getBuffer(), base, (short) (size + 2)); /* * Don't destroy out object ! Generated data is needed in * ExtAuth ! */ /* Not if running without external authentication */ om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); } } } private void ExternalAuthenticate(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short bytesLeft = Util.makeShort((byte) 0x00, buffer[ISO7816.OFFSET_LC]); if (bytesLeft != apdu.setIncomingAndReceive()) ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); byte key_nb = buffer[ISO7816.OFFSET_P1]; if ((key_nb < 0) || (key_nb >= MAX_NUM_AUTH_KEYS) || (keys[key_nb] == null)) ISOException.throwIt(SW_INCORRECT_P1); if (bytesLeft < 3) ISOException.throwIt(SW_INVALID_PARAMETER); /* Verify that a GetChallenge has been issued */ if (!getChallengeDone) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); /* * Clear getChallengeDone flag getChallengeDone = false; /* Retrieve * getChallenge() data position and check it */ short chall_base = om.getBaseAddress(OUT_OBJECT_CLA, OUT_OBJECT_ID); if (chall_base == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OPERATION_NOT_ALLOWED); short obj_size = om.getSizeFromAddress(chall_base); if (obj_size < 3) ISOException.throwIt(SW_INVALID_PARAMETER); short chall_size = mem.getShort(chall_base); /* Actually GetChallenge() creates an object of exact size */ if (obj_size != (short) (2 + chall_size)) ISOException.throwIt(SW_INVALID_PARAMETER); byte ciph_mode = buffer[ISO7816.OFFSET_CDATA]; byte ciph_dir = buffer[(short) (ISO7816.OFFSET_CDATA + 1)]; byte[] src_buffer; /* The buffer of encrypted data */ short src_offset; /* The offset of encrypted data in src_buffer[] */ short src_avail; /* The available encrypted data (+ size) */ switch (buffer[(short) (ISO7816.OFFSET_CDATA + 2)]) { case DL_APDU: src_buffer = buffer; src_offset = (short) (ISO7816.OFFSET_CDATA + 3); src_avail = (short) (bytesLeft - 3); break; case DL_OBJECT: src_offset = om.getBaseAddress(IN_OBJECT_CLA, IN_OBJECT_ID); if (src_offset == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_OBJECT_NOT_FOUND); src_buffer = mem.getBuffer(); src_avail = om.getSizeFromAddress(src_offset); default: ISOException.throwIt(SW_INVALID_PARAMETER); return; // Suppress compiler warning } if (src_avail < 2) ISOException.throwIt(SW_INVALID_PARAMETER); short size = Util.getShort(src_buffer, src_offset); if (src_avail < (short) (size + 2)) ISOException.throwIt(SW_INVALID_PARAMETER); // Null key already checked above Key key = keys[key_nb]; // Check if identity is actually blocked if (keyTries[key_nb] == (byte) 0) ISOException.throwIt(SW_IDENTITY_BLOCKED); byte key_type = key.getType(); boolean result = false; switch (ciph_dir) { case CD_DECRYPT: byte jc_ciph_alg; switch (ciph_mode) { case CM_RSA_NOPAD: if (key_type != KeyBuilder.TYPE_RSA_PUBLIC) ISOException.throwIt(SW_INVALID_PARAMETER); jc_ciph_alg = Cipher.ALG_RSA_NOPAD; break; case CM_RSA_PAD_PKCS1: if (key_type != KeyBuilder.TYPE_RSA_PUBLIC) ISOException.throwIt(SW_INVALID_PARAMETER); jc_ciph_alg = Cipher.ALG_RSA_PKCS1; break; case CM_DES_CBC_NOPAD: if (key_type != KeyBuilder.TYPE_DES) ISOException.throwIt(SW_INVALID_PARAMETER); jc_ciph_alg = Cipher.ALG_DES_CBC_NOPAD; break; case CM_DES_ECB_NOPAD: if (key_type != KeyBuilder.TYPE_DES) ISOException.throwIt(SW_INVALID_PARAMETER); jc_ciph_alg = Cipher.ALG_DES_ECB_NOPAD; break; default: ISOException.throwIt(SW_INVALID_PARAMETER); return; // Suppress compiler warning } Cipher ciph = getCipher(key_nb, jc_ciph_alg); ciph.init(key, Cipher.MODE_DECRYPT); // Create temporary buffer short temp = mem.alloc(chall_size); if (temp == MemoryManager.NULL_OFFSET) ISOException.throwIt(SW_NO_MEMORY_LEFT); short written_bytes = ciph.doFinal(src_buffer, (short) (src_offset + 2), size, mem.getBuffer(), temp); /* * JC specifies that, when decrypting, padding bytes are cut out * * so after a decrypt we should get the same size as the challenge* * and they should be less than provided encrypted data */ if ((written_bytes == chall_size) && (Util.arrayCompare(mem.getBuffer(), temp, mem.getBuffer(), (short) (chall_base + 2), chall_size) == (byte) 0)) result = true; sendData(apdu, mem.getBuffer(), temp, written_bytes); mem.free(temp); break; case CD_VERIFY: byte jc_sign_alg; switch (ciph_mode) { case CM_DSA_SHA: if (key_type != KeyBuilder.TYPE_DSA_PUBLIC) ISOException.throwIt(SW_INVALID_PARAMETER); jc_sign_alg = Signature.ALG_DSA_SHA; break; default: ISOException.throwIt(SW_INVALID_PARAMETER); return; // Suppress compiler warning } Signature sign = getSignature(key_nb, jc_sign_alg); sign.init(key, Signature.MODE_VERIFY); if (sign.verify(mem.getBuffer(), (short) (chall_base + 2), chall_size, src_buffer, (short) (src_offset + 2), size)) result = true; break; default: ISOException.throwIt(SW_INVALID_PARAMETER); } if (result) { LoginStrongIdentity(key_nb); // Reset try counter keyTries[key_nb] = MAX_KEY_TRIES; om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); } else { // Decrease try counter keyTries[key_nb]--; LogoutIdentity((byte) (key_nb + 8)); om.destroyObject(IN_OBJECT_CLA, IN_OBJECT_ID, true); om.destroyObject(OUT_OBJECT_CLA, OUT_OBJECT_ID, true); ISOException.throwIt(SW_AUTH_FAILED); } } private void GetStatus(APDU apdu, byte[] buffer) { if (buffer[ISO7816.OFFSET_P1] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P1); if (buffer[ISO7816.OFFSET_P2] != (byte) 0x00) ISOException.throwIt(SW_INCORRECT_P2); short pos = (short) 0; buffer[pos++] = (byte) 1; // Major Card Edge Protocol version n. buffer[pos++] = (byte) 3; // Minor Card Edge Protocol version n. buffer[pos++] = (byte) 0; // Major Applet version n. buffer[pos++] = (byte) 9; // Minor Applet version n. Util.setShort(buffer, pos, (short) 0x00); // Total mem M.S. pos += (short) 2; Util.setShort(buffer, pos, (short) mem.getBuffer().length); // Total mem // L.S. pos += (short) 2; Util.setShort(buffer, pos, (short) 0x00); // Free mem M.S. pos += (short) 2; Util.setShort(buffer, pos, mem.freemem()); // Free mem L.S. pos += (short) 2; byte cnt = (byte) 0; for (short i = 0; i < pins.length; i++) if (pins[i] != null) cnt++; buffer[pos++] = cnt; // Number of used PINs cnt = (byte) 0; for (short i = 0; i < keys.length; i++) if (keys[i] != null) cnt++; buffer[pos++] = cnt; // Number of used Keys Util.setShort(buffer, pos, logged_ids); // Logged ids pos += (short) 2; apdu.setOutgoingAndSend((short) 0, pos); } } // end of class JAVA_APPLET